1 /* 2 * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>. 3 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. 4 * Copyright 2013, Rene Gollent, rene@gollent.com. 5 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. 6 * Copyright 2016-2019, Andrew Lindesay <apl@lindesay.co.nz>. 7 * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>. 8 * All rights reserved. Distributed under the terms of the MIT License. 9 */ 10 11 12 #include "MainWindow.h" 13 14 #include <map> 15 #include <vector> 16 17 #include <stdio.h> 18 #include <Alert.h> 19 #include <Autolock.h> 20 #include <Application.h> 21 #include <Button.h> 22 #include <Catalog.h> 23 #include <CardLayout.h> 24 #include <LayoutBuilder.h> 25 #include <MenuBar.h> 26 #include <MenuItem.h> 27 #include <Messenger.h> 28 #include <Roster.h> 29 #include <Screen.h> 30 #include <ScrollView.h> 31 #include <StringList.h> 32 #include <StringView.h> 33 #include <TabView.h> 34 35 #include "AppUtils.h" 36 #include "AutoDeleter.h" 37 #include "AutoLocker.h" 38 #include "DecisionProvider.h" 39 #include "FeaturedPackagesView.h" 40 #include "FilterView.h" 41 #include "Logger.h" 42 #include "PackageInfoView.h" 43 #include "PackageListView.h" 44 #include "PackageManager.h" 45 #include "ProcessCoordinator.h" 46 #include "ProcessCoordinatorFactory.h" 47 #include "RatePackageWindow.h" 48 #include "support.h" 49 #include "ScreenshotWindow.h" 50 #include "UserLoginWindow.h" 51 #include "UserUsageConditionsWindow.h" 52 #include "WorkStatusView.h" 53 54 55 #undef B_TRANSLATION_CONTEXT 56 #define B_TRANSLATION_CONTEXT "MainWindow" 57 58 59 enum { 60 MSG_BULK_LOAD_DONE = 'mmwd', 61 MSG_REFRESH_REPOS = 'mrrp', 62 MSG_MANAGE_REPOS = 'mmrp', 63 MSG_SOFTWARE_UPDATER = 'mswu', 64 MSG_LOG_IN = 'lgin', 65 MSG_LOG_OUT = 'lgot', 66 MSG_AUTHORIZATION_CHANGED = 'athc', 67 MSG_CATEGORIES_LIST_CHANGED = 'clic', 68 MSG_PACKAGE_CHANGED = 'pchd', 69 MSG_WORK_STATUS_CHANGE = 'wsch', 70 MSG_WORK_STATUS_CLEAR = 'wscl', 71 MSG_VIEW_LATEST_USER_USAGE_CONDITIONS = 'vluc', 72 73 MSG_SHOW_FEATURED_PACKAGES = 'sofp', 74 MSG_SHOW_AVAILABLE_PACKAGES = 'savl', 75 MSG_SHOW_INSTALLED_PACKAGES = 'sins', 76 MSG_SHOW_SOURCE_PACKAGES = 'ssrc', 77 MSG_SHOW_DEVELOP_PACKAGES = 'sdvl' 78 }; 79 80 81 using namespace BPackageKit; 82 using namespace BPackageKit::BManager::BPrivate; 83 84 85 typedef std::map<BString, PackageInfoRef> PackageInfoMap; 86 87 88 struct RefreshWorkerParameters { 89 MainWindow* window; 90 bool forceRefresh; 91 92 RefreshWorkerParameters(MainWindow* window, bool forceRefresh) 93 : 94 window(window), 95 forceRefresh(forceRefresh) 96 { 97 } 98 }; 99 100 101 class MainWindowModelListener : public ModelListener { 102 public: 103 MainWindowModelListener(const BMessenger& messenger) 104 : 105 fMessenger(messenger) 106 { 107 } 108 109 virtual void AuthorizationChanged() 110 { 111 if (fMessenger.IsValid()) 112 fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED); 113 } 114 115 virtual void CategoryListChanged() 116 { 117 if (fMessenger.IsValid()) 118 fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED); 119 } 120 121 private: 122 BMessenger fMessenger; 123 }; 124 125 126 MainWindow::MainWindow(const BMessage& settings) 127 : 128 BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 129 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 130 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 131 fScreenshotWindow(NULL), 132 fUserMenu(NULL), 133 fLogInItem(NULL), 134 fLogOutItem(NULL), 135 fModelListener(new MainWindowModelListener(BMessenger(this)), true), 136 fBulkLoadProcessCoordinator(NULL), 137 fSinglePackageMode(false) 138 { 139 BMenuBar* menuBar = new BMenuBar("Main Menu"); 140 _BuildMenu(menuBar); 141 142 BMenuBar* userMenuBar = new BMenuBar("User Menu"); 143 _BuildUserMenu(userMenuBar); 144 set_small_font(userMenuBar); 145 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET, 146 menuBar->MaxSize().height)); 147 148 fFilterView = new FilterView(); 149 fFeaturedPackagesView = new FeaturedPackagesView(); 150 fPackageListView = new PackageListView(fModel.Lock()); 151 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 152 153 fSplitView = new BSplitView(B_VERTICAL, 5.0f); 154 155 fWorkStatusView = new WorkStatusView("work status"); 156 fPackageListView->AttachWorkStatusView(fWorkStatusView); 157 158 fListTabs = new TabView(BMessenger(this), 159 BMessage(MSG_SHOW_FEATURED_PACKAGES), "list tabs"); 160 fListTabs->AddTab(fFeaturedPackagesView); 161 fListTabs->AddTab(fPackageListView); 162 163 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) 164 .AddGroup(B_HORIZONTAL, 0.0f) 165 .Add(menuBar, 1.0f) 166 .Add(userMenuBar, 0.0f) 167 .End() 168 .Add(fFilterView) 169 .AddSplit(fSplitView) 170 .AddGroup(B_VERTICAL) 171 .Add(fListTabs) 172 .SetInsets( 173 B_USE_DEFAULT_SPACING, 0.0f, 174 B_USE_DEFAULT_SPACING, 0.0f) 175 .End() 176 .Add(fPackageInfoView) 177 .End() 178 .Add(fWorkStatusView) 179 ; 180 181 fSplitView->SetCollapsible(0, false); 182 fSplitView->SetCollapsible(1, false); 183 184 fModel.AddListener(fModelListener); 185 186 // Restore settings 187 BMessage columnSettings; 188 if (settings.FindMessage("column settings", &columnSettings) == B_OK) 189 fPackageListView->LoadState(&columnSettings); 190 191 bool showOption; 192 if (settings.FindBool("show featured packages", &showOption) == B_OK) 193 fModel.SetShowFeaturedPackages(showOption); 194 if (settings.FindBool("show available packages", &showOption) == B_OK) 195 fModel.SetShowAvailablePackages(showOption); 196 if (settings.FindBool("show installed packages", &showOption) == B_OK) 197 fModel.SetShowInstalledPackages(showOption); 198 if (settings.FindBool("show develop packages", &showOption) == B_OK) 199 fModel.SetShowDevelopPackages(showOption); 200 if (settings.FindBool("show source packages", &showOption) == B_OK) 201 fModel.SetShowSourcePackages(showOption); 202 203 if (fModel.ShowFeaturedPackages()) 204 fListTabs->Select(0); 205 else 206 fListTabs->Select(1); 207 208 _RestoreUserName(settings); 209 _RestoreWindowFrame(settings); 210 211 atomic_set(&fPackagesToShowListID, 0); 212 213 // start worker threads 214 BPackageRoster().StartWatching(this, 215 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS); 216 217 _StartBulkLoad(); 218 219 _InitWorkerThreads(); 220 } 221 222 223 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package) 224 : 225 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 226 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 227 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 228 fWorkStatusView(NULL), 229 fScreenshotWindow(NULL), 230 fUserMenu(NULL), 231 fLogInItem(NULL), 232 fLogOutItem(NULL), 233 fModelListener(new MainWindowModelListener(BMessenger(this)), true), 234 fBulkLoadProcessCoordinator(NULL), 235 fSinglePackageMode(true) 236 { 237 fFilterView = new FilterView(); 238 fPackageListView = new PackageListView(fModel.Lock()); 239 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 240 241 BLayoutBuilder::Group<>(this, B_VERTICAL) 242 .Add(fPackageInfoView) 243 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0) 244 ; 245 246 fModel.AddListener(fModelListener); 247 248 // Restore settings 249 _RestoreUserName(settings); 250 _RestoreWindowFrame(settings); 251 252 fPackageInfoView->SetPackage(package); 253 254 _InitWorkerThreads(); 255 } 256 257 258 MainWindow::~MainWindow() 259 { 260 BPackageRoster().StopWatching(this); 261 262 delete_sem(fPendingActionsSem); 263 if (fPendingActionsWorker >= 0) 264 wait_for_thread(fPendingActionsWorker, NULL); 265 266 delete_sem(fPackageToPopulateSem); 267 if (fPopulatePackageWorker >= 0) 268 wait_for_thread(fPopulatePackageWorker, NULL); 269 270 delete_sem(fNewPackagesToShowSem); 271 delete_sem(fShowPackagesAcknowledgeSem); 272 if (fShowPackagesWorker >= 0) 273 wait_for_thread(fShowPackagesWorker, NULL); 274 275 if (fScreenshotWindow != NULL && fScreenshotWindow->Lock()) 276 fScreenshotWindow->Quit(); 277 } 278 279 280 bool 281 MainWindow::QuitRequested() 282 { 283 BMessage settings; 284 StoreSettings(settings); 285 286 BMessage message(MSG_MAIN_WINDOW_CLOSED); 287 message.AddMessage(KEY_WINDOW_SETTINGS, &settings); 288 289 be_app->PostMessage(&message); 290 291 _StopBulkLoad(); 292 293 return true; 294 } 295 296 297 void 298 MainWindow::MessageReceived(BMessage* message) 299 { 300 switch (message->what) { 301 case MSG_BULK_LOAD_DONE: 302 _BulkLoadCompleteReceived(); 303 break; 304 case B_SIMPLE_DATA: 305 case B_REFS_RECEIVED: 306 // TODO: ? 307 break; 308 309 case B_PACKAGE_UPDATE: 310 // TODO: We should do a more selective update depending on the 311 // "event", "location", and "change count" fields! 312 _StartBulkLoad(false); 313 break; 314 315 case MSG_REFRESH_REPOS: 316 _StartBulkLoad(true); 317 break; 318 319 case MSG_WORK_STATUS_CHANGE: 320 _HandleWorkStatusChangeMessageReceived(message); 321 break; 322 323 case MSG_MANAGE_REPOS: 324 be_roster->Launch("application/x-vnd.Haiku-Repositories"); 325 break; 326 327 case MSG_SOFTWARE_UPDATER: 328 be_roster->Launch("application/x-vnd.haiku-softwareupdater"); 329 break; 330 331 case MSG_LOG_IN: 332 _OpenLoginWindow(BMessage()); 333 break; 334 335 case MSG_LOG_OUT: 336 fModel.SetUsername(""); 337 break; 338 339 case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS: 340 _ViewLatestUserUsageConditions(); 341 break; 342 343 case MSG_AUTHORIZATION_CHANGED: 344 _UpdateAuthorization(); 345 break; 346 347 case MSG_CATEGORIES_LIST_CHANGED: 348 fFilterView->AdoptModel(fModel); 349 break; 350 351 case MSG_SHOW_FEATURED_PACKAGES: 352 // check to see if we aren't already on the current tab 353 if (fListTabs->Selection() == 354 (fModel.ShowFeaturedPackages() ? 0 : 1)) 355 break; 356 { 357 BAutolock locker(fModel.Lock()); 358 fModel.SetShowFeaturedPackages( 359 fListTabs->Selection() == 0); 360 } 361 _AdoptModel(); 362 break; 363 364 case MSG_SHOW_AVAILABLE_PACKAGES: 365 { 366 BAutolock locker(fModel.Lock()); 367 fModel.SetShowAvailablePackages( 368 !fModel.ShowAvailablePackages()); 369 } 370 _AdoptModel(); 371 break; 372 373 case MSG_SHOW_INSTALLED_PACKAGES: 374 { 375 BAutolock locker(fModel.Lock()); 376 fModel.SetShowInstalledPackages( 377 !fModel.ShowInstalledPackages()); 378 } 379 _AdoptModel(); 380 break; 381 382 case MSG_SHOW_SOURCE_PACKAGES: 383 { 384 BAutolock locker(fModel.Lock()); 385 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages()); 386 } 387 _AdoptModel(); 388 break; 389 390 case MSG_SHOW_DEVELOP_PACKAGES: 391 { 392 BAutolock locker(fModel.Lock()); 393 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages()); 394 } 395 _AdoptModel(); 396 break; 397 398 // this may be triggered by, for example, a user rating being added 399 // or having been altered. 400 case MSG_SERVER_DATA_CHANGED: 401 { 402 BString name; 403 if (message->FindString("name", &name) == B_OK) { 404 BAutolock locker(fModel.Lock()); 405 if (fPackageInfoView->Package()->Name() == name) { 406 _PopulatePackageAsync(true); 407 } else { 408 if (Logger::IsDebugEnabled()) { 409 printf("pkg [%s] is updated on the server, but is " 410 "not selected so will not be updated.\n", 411 name.String()); 412 } 413 } 414 } 415 break; 416 } 417 418 case MSG_PACKAGE_SELECTED: 419 { 420 BString name; 421 if (message->FindString("name", &name) == B_OK) { 422 BAutolock locker(fModel.Lock()); 423 int count = fVisiblePackages.CountItems(); 424 for (int i = 0; i < count; i++) { 425 const PackageInfoRef& package 426 = fVisiblePackages.ItemAtFast(i); 427 if (package.Get() != NULL && package->Name() == name) { 428 locker.Unlock(); 429 _AdoptPackage(package); 430 break; 431 } 432 } 433 } else { 434 _ClearPackage(); 435 } 436 break; 437 } 438 439 case MSG_CATEGORY_SELECTED: 440 { 441 BString code; 442 if (message->FindString("code", &code) != B_OK) 443 code = ""; 444 { 445 BAutolock locker(fModel.Lock()); 446 fModel.SetCategory(code); 447 } 448 _AdoptModel(); 449 break; 450 } 451 452 case MSG_DEPOT_SELECTED: 453 { 454 BString name; 455 if (message->FindString("name", &name) != B_OK) 456 name = ""; 457 { 458 BAutolock locker(fModel.Lock()); 459 fModel.SetDepot(name); 460 } 461 _AdoptModel(); 462 _UpdateAvailableRepositories(); 463 break; 464 } 465 466 case MSG_SEARCH_TERMS_MODIFIED: 467 { 468 // TODO: Do this with a delay! 469 BString searchTerms; 470 if (message->FindString("search terms", &searchTerms) != B_OK) 471 searchTerms = ""; 472 { 473 BAutolock locker(fModel.Lock()); 474 fModel.SetSearchTerms(searchTerms); 475 } 476 _AdoptModel(); 477 break; 478 } 479 480 case MSG_PACKAGE_CHANGED: 481 { 482 PackageInfo* info; 483 if (message->FindPointer("package", (void**)&info) == B_OK) { 484 PackageInfoRef ref(info, true); 485 uint32 changes; 486 if (message->FindUInt32("changes", &changes) != B_OK) 487 changes = 0; 488 if ((changes & PKG_CHANGED_STATE) != 0) { 489 BAutolock locker(fModel.Lock()); 490 fModel.SetPackageState(ref, ref->State()); 491 } 492 493 // Asynchronous updates to the package information 494 // can mean that the package needs to be added or 495 // removed to/from the visible packages when the current 496 // filter parameters are re-evaluated on this package. 497 bool wasVisible = fVisiblePackages.Contains(ref); 498 bool isVisible; 499 { 500 BAutolock locker(fModel.Lock()); 501 // The package didn't get a chance yet to be in the 502 // visible package list 503 isVisible = fModel.MatchesFilter(ref); 504 505 // Transfer this single package, otherwise we miss 506 // other packages if they appear or disappear along 507 // with this one before receive a notification for 508 // them. 509 if (isVisible) { 510 fVisiblePackages.Add(ref); 511 } else if (wasVisible) 512 fVisiblePackages.Remove(ref); 513 } 514 515 if (wasVisible != isVisible) { 516 if (!isVisible) { 517 fPackageListView->RemovePackage(ref); 518 fFeaturedPackagesView->RemovePackage(ref); 519 } else { 520 fPackageListView->AddPackage(ref); 521 if (ref->IsProminent()) 522 fFeaturedPackagesView->AddPackage(ref); 523 } 524 } 525 526 if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0) 527 fWorkStatusView->PackageStatusChanged(ref); 528 } 529 break; 530 } 531 532 case MSG_RATE_PACKAGE: 533 _RatePackage(); 534 break; 535 536 case MSG_SHOW_SCREENSHOT: 537 _ShowScreenshot(); 538 break; 539 540 case MSG_PACKAGE_WORKER_BUSY: 541 { 542 BString reason; 543 status_t status = message->FindString("reason", &reason); 544 if (status != B_OK) 545 break; 546 if (!fSinglePackageMode) 547 fWorkStatusView->SetBusy(reason); 548 break; 549 } 550 551 case MSG_PACKAGE_WORKER_IDLE: 552 if (!fSinglePackageMode) 553 fWorkStatusView->SetIdle(); 554 break; 555 556 case MSG_ADD_VISIBLE_PACKAGES: 557 { 558 struct SemaphoreReleaser { 559 SemaphoreReleaser(sem_id semaphore) 560 : 561 fSemaphore(semaphore) 562 { } 563 564 ~SemaphoreReleaser() { release_sem(fSemaphore); } 565 566 sem_id fSemaphore; 567 }; 568 569 // Make sure acknowledge semaphore is released even on error, 570 // so the worker thread won't be blocked 571 SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem); 572 573 int32 numPackages = 0; 574 type_code unused; 575 status_t status = message->GetInfo("package_ref", &unused, 576 &numPackages); 577 if (status != B_OK || numPackages == 0) 578 break; 579 580 int32 listID = 0; 581 status = message->FindInt32("list_id", &listID); 582 if (status != B_OK) 583 break; 584 if (listID != atomic_get(&fPackagesToShowListID)) { 585 // list is outdated, ignore 586 break; 587 } 588 589 for (int i = 0; i < numPackages; i++) { 590 PackageInfo* packageRaw = NULL; 591 status = message->FindPointer("package_ref", i, 592 (void**)&packageRaw); 593 if (status != B_OK) 594 break; 595 PackageInfoRef package(packageRaw, true); 596 597 fPackageListView->AddPackage(package); 598 if (package->IsProminent()) 599 fFeaturedPackagesView->AddPackage(package); 600 } 601 break; 602 } 603 604 case MSG_UPDATE_SELECTED_PACKAGE: 605 { 606 const PackageInfoRef& selectedPackage = fPackageInfoView->Package(); 607 fFeaturedPackagesView->SelectPackage(selectedPackage, true); 608 fPackageListView->SelectPackage(selectedPackage); 609 610 AutoLocker<BLocker> modelLocker(fModel.Lock()); 611 if (!fVisiblePackages.Contains(fPackageInfoView->Package())) 612 fPackageInfoView->Clear(); 613 break; 614 } 615 616 default: 617 BWindow::MessageReceived(message); 618 break; 619 } 620 } 621 622 623 void 624 MainWindow::StoreSettings(BMessage& settings) const 625 { 626 settings.AddRect(_WindowFrameName(), Frame()); 627 if (!fSinglePackageMode) { 628 settings.AddRect("window frame", Frame()); 629 630 BMessage columnSettings; 631 fPackageListView->SaveState(&columnSettings); 632 633 settings.AddMessage("column settings", &columnSettings); 634 635 settings.AddBool("show featured packages", 636 fModel.ShowFeaturedPackages()); 637 settings.AddBool("show available packages", 638 fModel.ShowAvailablePackages()); 639 settings.AddBool("show installed packages", 640 fModel.ShowInstalledPackages()); 641 settings.AddBool("show develop packages", fModel.ShowDevelopPackages()); 642 settings.AddBool("show source packages", fModel.ShowSourcePackages()); 643 } 644 645 settings.AddString("username", fModel.Username()); 646 } 647 648 649 void 650 MainWindow::PackageChanged(const PackageInfoEvent& event) 651 { 652 uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; 653 if ((event.Changes() & watchedChanges) != 0) { 654 PackageInfoRef ref(event.Package()); 655 BMessage message(MSG_PACKAGE_CHANGED); 656 message.AddPointer("package", ref.Get()); 657 message.AddUInt32("changes", event.Changes()); 658 ref.Detach(); 659 // reference needs to be released by MessageReceived(); 660 PostMessage(&message); 661 } 662 } 663 664 665 status_t 666 MainWindow::SchedulePackageActions(PackageActionList& list) 667 { 668 AutoLocker<BLocker> lock(&fPendingActionsLock); 669 for (int32 i = 0; i < list.CountItems(); i++) { 670 if (!fPendingActions.Add(list.ItemAtFast(i))) 671 return B_NO_MEMORY; 672 } 673 674 return release_sem_etc(fPendingActionsSem, list.CountItems(), 0); 675 } 676 677 678 Model* 679 MainWindow::GetModel() 680 { 681 return &fModel; 682 } 683 684 685 void 686 MainWindow::_BuildMenu(BMenuBar* menuBar) 687 { 688 BMenu* menu = new BMenu(B_TRANSLATE("Tools")); 689 fRefreshRepositoriesItem = new BMenuItem( 690 B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS)); 691 menu->AddItem(fRefreshRepositoriesItem); 692 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories" 693 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS))); 694 menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates" 695 B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER))); 696 697 menuBar->AddItem(menu); 698 699 fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories")); 700 menuBar->AddItem(fRepositoryMenu); 701 702 menu = new BMenu(B_TRANSLATE("Show")); 703 704 fShowAvailablePackagesItem = new BMenuItem( 705 B_TRANSLATE("Available packages"), 706 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES)); 707 menu->AddItem(fShowAvailablePackagesItem); 708 709 fShowInstalledPackagesItem = new BMenuItem( 710 B_TRANSLATE("Installed packages"), 711 new BMessage(MSG_SHOW_INSTALLED_PACKAGES)); 712 menu->AddItem(fShowInstalledPackagesItem); 713 714 menu->AddSeparatorItem(); 715 716 fShowDevelopPackagesItem = new BMenuItem( 717 B_TRANSLATE("Develop packages"), 718 new BMessage(MSG_SHOW_DEVELOP_PACKAGES)); 719 menu->AddItem(fShowDevelopPackagesItem); 720 721 fShowSourcePackagesItem = new BMenuItem( 722 B_TRANSLATE("Source packages"), 723 new BMessage(MSG_SHOW_SOURCE_PACKAGES)); 724 menu->AddItem(fShowSourcePackagesItem); 725 726 menuBar->AddItem(menu); 727 } 728 729 730 void 731 MainWindow::_BuildUserMenu(BMenuBar* menuBar) 732 { 733 fUserMenu = new BMenu(B_TRANSLATE("Not logged in")); 734 735 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), 736 new BMessage(MSG_LOG_IN)); 737 fUserMenu->AddItem(fLogInItem); 738 739 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), 740 new BMessage(MSG_LOG_OUT)); 741 fUserMenu->AddItem(fLogOutItem); 742 743 fLogOutItem = new BMenuItem(B_TRANSLATE("View latest user usage conditions" 744 B_UTF8_ELLIPSIS), 745 new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS)); 746 fUserMenu->AddItem(fLogOutItem); 747 748 menuBar->AddItem(fUserMenu); 749 } 750 751 752 void 753 MainWindow::_RestoreUserName(const BMessage& settings) 754 { 755 BString username; 756 if (settings.FindString("username", &username) == B_OK 757 && username.Length() > 0) { 758 fModel.SetUsername(username); 759 } 760 } 761 762 763 const char* 764 MainWindow::_WindowFrameName() const 765 { 766 if (fSinglePackageMode) 767 return "small window frame"; 768 769 return "window frame"; 770 } 771 772 773 void 774 MainWindow::_RestoreWindowFrame(const BMessage& settings) 775 { 776 BRect frame = Frame(); 777 778 BRect windowFrame; 779 bool fromSettings = false; 780 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) { 781 frame = windowFrame; 782 fromSettings = true; 783 } else if (!fSinglePackageMode) { 784 // Resize to occupy a certain screen size 785 BRect screenFrame = BScreen(this).Frame(); 786 float width = frame.Width(); 787 float height = frame.Height(); 788 if (width < screenFrame.Width() * .666f 789 && height < screenFrame.Height() * .666f) { 790 frame.bottom = frame.top + screenFrame.Height() * .666f; 791 frame.right = frame.left 792 + std::min(screenFrame.Width() * .666f, height * 7 / 5); 793 } 794 } 795 796 MoveTo(frame.LeftTop()); 797 ResizeTo(frame.Width(), frame.Height()); 798 799 if (fromSettings) 800 MoveOnScreen(); 801 else 802 CenterOnScreen(); 803 } 804 805 806 void 807 MainWindow::_InitWorkerThreads() 808 { 809 fPendingActionsSem = create_sem(0, "PendingPackageActions"); 810 if (fPendingActionsSem >= 0) { 811 fPendingActionsWorker = spawn_thread(&_PackageActionWorker, 812 "Planet Express", B_NORMAL_PRIORITY, this); 813 if (fPendingActionsWorker >= 0) 814 resume_thread(fPendingActionsWorker); 815 } else 816 fPendingActionsWorker = -1; 817 818 fPackageToPopulateSem = create_sem(0, "PopulatePackage"); 819 if (fPackageToPopulateSem >= 0) { 820 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker, 821 "Package Populator", B_NORMAL_PRIORITY, this); 822 if (fPopulatePackageWorker >= 0) 823 resume_thread(fPopulatePackageWorker); 824 } else 825 fPopulatePackageWorker = -1; 826 827 fNewPackagesToShowSem = create_sem(0, "ShowPackages"); 828 fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck"); 829 if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) { 830 fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker, 831 "Good news everyone", B_NORMAL_PRIORITY, this); 832 if (fShowPackagesWorker >= 0) 833 resume_thread(fShowPackagesWorker); 834 } else 835 fShowPackagesWorker = -1; 836 } 837 838 839 void 840 MainWindow::_AdoptModel() 841 { 842 { 843 AutoLocker<BLocker> modelLocker(fModel.Lock()); 844 fVisiblePackages = fModel.CreatePackageList(); 845 AutoLocker<BLocker> listLocker(fPackagesToShowListLock); 846 fPackagesToShowList = fVisiblePackages; 847 atomic_add(&fPackagesToShowListID, 1); 848 } 849 850 fFeaturedPackagesView->Clear(); 851 fPackageListView->Clear(); 852 853 release_sem(fNewPackagesToShowSem); 854 855 BAutolock locker(fModel.Lock()); 856 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages()); 857 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages()); 858 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages()); 859 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages()); 860 861 if (fModel.ShowFeaturedPackages()) 862 fListTabs->Select(0); 863 else 864 fListTabs->Select(1); 865 866 fFilterView->AdoptModel(fModel); 867 } 868 869 870 void 871 MainWindow::_AdoptPackage(const PackageInfoRef& package) 872 { 873 { 874 BAutolock locker(fModel.Lock()); 875 fPackageInfoView->SetPackage(package); 876 877 if (fFeaturedPackagesView != NULL) 878 fFeaturedPackagesView->SelectPackage(package); 879 if (fPackageListView != NULL) 880 fPackageListView->SelectPackage(package); 881 } 882 883 _PopulatePackageAsync(false); 884 } 885 886 887 void 888 MainWindow::_ClearPackage() 889 { 890 fPackageInfoView->Clear(); 891 } 892 893 894 void 895 MainWindow::_StopBulkLoad() 896 { 897 AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock); 898 899 if (fBulkLoadProcessCoordinator != NULL) { 900 printf("will stop full update process coordinator\n"); 901 fBulkLoadProcessCoordinator->Stop(); 902 } 903 } 904 905 906 void 907 MainWindow::_StartBulkLoad(bool force) 908 { 909 AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock); 910 911 if (fBulkLoadProcessCoordinator == NULL) { 912 fBulkLoadProcessCoordinator 913 = ProcessCoordinatorFactory::CreateBulkLoadCoordinator( 914 this, 915 // PackageInfoListener 916 this, 917 // ProcessCoordinatorListener 918 &fModel, force); 919 fBulkLoadProcessCoordinator->Start(); 920 fRefreshRepositoriesItem->SetEnabled(false); 921 } 922 } 923 924 925 /*! This method is called when there is some change in the bulk load process. 926 A change may mean that a new process has started / stopped etc... or it 927 may mean that the entire coordinator has finished. 928 */ 929 930 void 931 MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState) 932 { 933 AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock); 934 935 if (fBulkLoadProcessCoordinator == coordinatorState.Coordinator()) { 936 if (!coordinatorState.IsRunning()) 937 _BulkLoadProcessCoordinatorFinished(coordinatorState); 938 else { 939 _NotifyWorkStatusChange(coordinatorState.Message(), 940 coordinatorState.Progress()); 941 // show the progress to the user. 942 } 943 } else { 944 if (Logger::IsInfoEnabled()) { 945 printf("unknown process coordinator changed\n"); 946 } 947 } 948 } 949 950 951 void 952 MainWindow::_BulkLoadProcessCoordinatorFinished( 953 ProcessCoordinatorState& coordinatorState) 954 { 955 if (coordinatorState.ErrorStatus() != B_OK) { 956 AppUtils::NotifySimpleError( 957 B_TRANSLATE("Package Update Error"), 958 B_TRANSLATE("While updating package data, a problem has arisen " 959 "that may cause data to be outdated or missing from the " 960 "application's display. Additional details regarding this " 961 "problem may be able to be obtained from the application " 962 "logs.")); 963 } 964 BMessenger messenger(this); 965 messenger.SendMessage(MSG_BULK_LOAD_DONE); 966 // it is safe to delete the coordinator here because it is already known 967 // that all of the processes have completed and their threads will have 968 // exited safely by this point. 969 delete fBulkLoadProcessCoordinator; 970 fBulkLoadProcessCoordinator = NULL; 971 fRefreshRepositoriesItem->SetEnabled(true); 972 } 973 974 975 void 976 MainWindow::_BulkLoadCompleteReceived() 977 { 978 _AdoptModel(); 979 _UpdateAvailableRepositories(); 980 fWorkStatusView->SetIdle(); 981 } 982 983 984 /*! Sends off a message to the Window so that it can change the status view 985 on the front-end in the UI thread. 986 */ 987 988 void 989 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress) 990 { 991 BMessage message(MSG_WORK_STATUS_CHANGE); 992 993 if (!text.IsEmpty()) 994 message.AddString(KEY_WORK_STATUS_TEXT, text); 995 message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress); 996 997 this->PostMessage(&message, this); 998 } 999 1000 1001 void 1002 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message) 1003 { 1004 BString text; 1005 float progress; 1006 1007 if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK) 1008 fWorkStatusView->SetText(text); 1009 1010 if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK) 1011 fWorkStatusView->SetProgress(progress); 1012 } 1013 1014 1015 status_t 1016 MainWindow::_PackageActionWorker(void* arg) 1017 { 1018 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1019 1020 while (acquire_sem(window->fPendingActionsSem) == B_OK) { 1021 PackageActionRef ref; 1022 { 1023 AutoLocker<BLocker> lock(&window->fPendingActionsLock); 1024 ref = window->fPendingActions.ItemAt(0); 1025 if (ref.Get() == NULL) 1026 break; 1027 window->fPendingActions.Remove(0); 1028 } 1029 1030 BMessenger messenger(window); 1031 BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY); 1032 BString text(ref->Label()); 1033 text << B_UTF8_ELLIPSIS; 1034 busyMessage.AddString("reason", text); 1035 1036 messenger.SendMessage(&busyMessage); 1037 ref->Perform(); 1038 messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE); 1039 } 1040 1041 return 0; 1042 } 1043 1044 1045 /*! This method will cause the package to have its data refreshed from 1046 the server application. The refresh happens in the background; this method 1047 is asynchronous. 1048 */ 1049 1050 void 1051 MainWindow::_PopulatePackageAsync(bool forcePopulate) 1052 { 1053 // Trigger asynchronous package population from the web-app 1054 { 1055 AutoLocker<BLocker> lock(&fPackageToPopulateLock); 1056 fPackageToPopulate = fPackageInfoView->Package(); 1057 fForcePopulatePackage = forcePopulate; 1058 } 1059 release_sem_etc(fPackageToPopulateSem, 1, 0); 1060 1061 if (Logger::IsDebugEnabled()) { 1062 printf("pkg [%s] will be updated from the server.\n", 1063 fPackageToPopulate->Name().String()); 1064 } 1065 } 1066 1067 1068 /*! This method will run in the background. The thread will block until there 1069 is a package to be updated. When the thread unblocks, it will update the 1070 package with information from the server. 1071 */ 1072 1073 status_t 1074 MainWindow::_PopulatePackageWorker(void* arg) 1075 { 1076 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1077 1078 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { 1079 PackageInfoRef package; 1080 bool force; 1081 { 1082 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); 1083 package = window->fPackageToPopulate; 1084 force = window->fForcePopulatePackage; 1085 } 1086 1087 if (package.Get() != NULL) { 1088 uint32 populateFlags = Model::POPULATE_USER_RATINGS 1089 | Model::POPULATE_SCREEN_SHOTS 1090 | Model::POPULATE_CHANGELOG; 1091 1092 if (force) 1093 populateFlags |= Model::POPULATE_FORCE; 1094 1095 window->fModel.PopulatePackage(package, populateFlags); 1096 1097 if (Logger::IsDebugEnabled()) { 1098 printf("populating package [%s]\n", 1099 package->Name().String()); 1100 } 1101 } 1102 } 1103 1104 return 0; 1105 } 1106 1107 1108 /* static */ status_t 1109 MainWindow::_PackagesToShowWorker(void* arg) 1110 { 1111 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1112 1113 while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) { 1114 PackageList packageList; 1115 int32 listID = 0; 1116 { 1117 AutoLocker<BLocker> lock(&window->fPackagesToShowListLock); 1118 packageList = window->fPackagesToShowList; 1119 listID = atomic_get(&window->fPackagesToShowListID); 1120 window->fPackagesToShowList.Clear(); 1121 } 1122 1123 // Add packages to list views in batches of kPackagesPerUpdate so we 1124 // don't block the window thread for long with each iteration 1125 enum { 1126 kPackagesPerUpdate = 20 1127 }; 1128 uint32 packagesInMessage = 0; 1129 BMessage message(MSG_ADD_VISIBLE_PACKAGES); 1130 BMessenger messenger(window); 1131 bool listIsOutdated = false; 1132 1133 for (int i = 0; i < packageList.CountItems(); i++) { 1134 const PackageInfoRef& package = packageList.ItemAtFast(i); 1135 1136 if (packagesInMessage >= kPackagesPerUpdate) { 1137 if (listID != atomic_get(&window->fPackagesToShowListID)) { 1138 // The model was changed again in the meantime, and thus 1139 // our package list isn't current anymore. Send no further 1140 // messags based on this list and go back to start. 1141 listIsOutdated = true; 1142 break; 1143 } 1144 1145 message.AddInt32("list_id", listID); 1146 messenger.SendMessage(&message); 1147 message.MakeEmpty(); 1148 packagesInMessage = 0; 1149 1150 // Don't spam the window's message queue, which would make it 1151 // unresponsive (i.e. allows UI messages to get in between our 1152 // messages). When it has processed the message we just sent, 1153 // it will let us know by releasing the semaphore. 1154 acquire_sem(window->fShowPackagesAcknowledgeSem); 1155 } 1156 package->AcquireReference(); 1157 message.AddPointer("package_ref", package.Get()); 1158 packagesInMessage++; 1159 } 1160 1161 if (listIsOutdated) 1162 continue; 1163 1164 // Send remaining package infos, if any, which didn't make it into 1165 // the last message (count < kPackagesPerUpdate) 1166 if (packagesInMessage > 0) { 1167 message.AddInt32("list_id", listID); 1168 messenger.SendMessage(&message); 1169 acquire_sem(window->fShowPackagesAcknowledgeSem); 1170 } 1171 1172 // Update selected package in list views 1173 messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE); 1174 } 1175 1176 return 0; 1177 } 1178 1179 1180 void 1181 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage) 1182 { 1183 UserLoginWindow* window = new UserLoginWindow(this, 1184 BRect(0, 0, 500, 400), fModel); 1185 1186 if (onSuccessMessage.what != 0) 1187 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage); 1188 1189 window->Show(); 1190 } 1191 1192 1193 void 1194 MainWindow::_UpdateAuthorization() 1195 { 1196 BString username(fModel.Username()); 1197 bool hasUser = !username.IsEmpty(); 1198 1199 if (fLogOutItem != NULL) 1200 fLogOutItem->SetEnabled(hasUser); 1201 if (fLogInItem != NULL) { 1202 if (hasUser) 1203 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS)); 1204 else 1205 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS)); 1206 } 1207 1208 if (fUserMenu != NULL) { 1209 BString label; 1210 if (username.Length() == 0) { 1211 label = B_TRANSLATE("Not logged in"); 1212 } else { 1213 label = B_TRANSLATE("Logged in as %User%"); 1214 label.ReplaceAll("%User%", username); 1215 } 1216 fUserMenu->Superitem()->SetLabel(label); 1217 } 1218 } 1219 1220 1221 void 1222 MainWindow::_UpdateAvailableRepositories() 1223 { 1224 fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true); 1225 1226 fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"), 1227 new BMessage(MSG_DEPOT_SELECTED))); 1228 1229 fRepositoryMenu->AddItem(new BSeparatorItem()); 1230 1231 bool foundSelectedDepot = false; 1232 const DepotList& depots = fModel.Depots(); 1233 for (int i = 0; i < depots.CountItems(); i++) { 1234 const DepotInfo& depot = depots.ItemAtFast(i); 1235 1236 if (depot.Name().Length() != 0) { 1237 BMessage* message = new BMessage(MSG_DEPOT_SELECTED); 1238 message->AddString("name", depot.Name()); 1239 BMenuItem* item = new BMenuItem(depot.Name(), message); 1240 fRepositoryMenu->AddItem(item); 1241 1242 if (depot.Name() == fModel.Depot()) { 1243 item->SetMarked(true); 1244 foundSelectedDepot = true; 1245 } 1246 } 1247 } 1248 1249 if (!foundSelectedDepot) 1250 fRepositoryMenu->ItemAt(0)->SetMarked(true); 1251 } 1252 1253 1254 bool 1255 MainWindow::_SelectedPackageHasWebAppRepositoryCode() 1256 { 1257 const PackageInfoRef& package = fPackageInfoView->Package(); 1258 const BString depotName = package->DepotName(); 1259 1260 if (depotName.IsEmpty()) { 1261 if (Logger::IsDebugEnabled()) { 1262 printf("the package [%s] has no depot name\n", 1263 package->Name().String()); 1264 } 1265 } else { 1266 const DepotInfo* depot = fModel.DepotForName(depotName); 1267 1268 if (depot == NULL) { 1269 printf("the depot [%s] was not able to be found\n", 1270 depotName.String()); 1271 } else { 1272 BString repositoryCode = depot->WebAppRepositoryCode(); 1273 1274 if (repositoryCode.IsEmpty()) { 1275 printf("the depot [%s] has no web app repository code\n", 1276 depotName.String()); 1277 } else { 1278 return true; 1279 } 1280 } 1281 } 1282 1283 return false; 1284 } 1285 1286 1287 void 1288 MainWindow::_RatePackage() 1289 { 1290 if (!_SelectedPackageHasWebAppRepositoryCode()) { 1291 BAlert* alert = new(std::nothrow) BAlert( 1292 B_TRANSLATE("Rating not possible"), 1293 B_TRANSLATE("This package doesn't seem to be on the HaikuDepot " 1294 "Server, so it's not possible to create a new rating " 1295 "or edit an existing rating."), 1296 B_TRANSLATE("OK")); 1297 alert->Go(); 1298 return; 1299 } 1300 1301 if (fModel.Username().IsEmpty()) { 1302 BAlert* alert = new(std::nothrow) BAlert( 1303 B_TRANSLATE("Not logged in"), 1304 B_TRANSLATE("You need to be logged into an account before you " 1305 "can rate packages."), 1306 B_TRANSLATE("Cancel"), 1307 B_TRANSLATE("Login or Create account")); 1308 1309 if (alert == NULL) 1310 return; 1311 1312 int32 choice = alert->Go(); 1313 if (choice == 1) 1314 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE)); 1315 return; 1316 } 1317 1318 // TODO: Allow only one RatePackageWindow 1319 // TODO: Mechanism for remembering the window frame 1320 RatePackageWindow* window = new RatePackageWindow(this, 1321 BRect(0, 0, 500, 400), fModel); 1322 window->SetPackage(fPackageInfoView->Package()); 1323 window->Show(); 1324 } 1325 1326 1327 void 1328 MainWindow::_ShowScreenshot() 1329 { 1330 // TODO: Mechanism for remembering the window frame 1331 if (fScreenshotWindow == NULL) 1332 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400)); 1333 1334 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK) 1335 return; 1336 1337 fScreenshotWindow->SetPackage(fPackageInfoView->Package()); 1338 1339 if (fScreenshotWindow->IsHidden()) 1340 fScreenshotWindow->Show(); 1341 else 1342 fScreenshotWindow->Activate(); 1343 1344 fScreenshotWindow->Unlock(); 1345 } 1346 1347 1348 void 1349 MainWindow::_ViewLatestUserUsageConditions() 1350 { 1351 UserUsageConditionsWindow* window = new UserUsageConditionsWindow( 1352 this, BRect(0, 0, 500, 400), fModel, LATEST); 1353 window->Show(); 1354 }