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-2021, 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 "SettingsWindow.h" 51 #include "ToLatestUserUsageConditionsWindow.h" 52 #include "UserLoginWindow.h" 53 #include "UserUsageConditionsWindow.h" 54 #include "WorkStatusView.h" 55 56 57 #undef B_TRANSLATION_CONTEXT 58 #define B_TRANSLATION_CONTEXT "MainWindow" 59 60 61 enum { 62 MSG_REFRESH_REPOS = 'mrrp', 63 MSG_MANAGE_REPOS = 'mmrp', 64 MSG_SOFTWARE_UPDATER = 'mswu', 65 MSG_SETTINGS = 'stgs', 66 MSG_LOG_IN = 'lgin', 67 MSG_AUTHORIZATION_CHANGED = 'athc', 68 MSG_CATEGORIES_LIST_CHANGED = 'clic', 69 MSG_PACKAGE_CHANGED = 'pchd', 70 MSG_WORK_STATUS_CHANGE = 'wsch', 71 MSG_WORK_STATUS_CLEAR = 'wscl', 72 73 MSG_CHANGE_PACKAGE_LIST_VIEW_MODE = 'cplm', 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 #define KEY_ERROR_STATUS "errorStatus" 81 82 #define TAB_PROMINENT_PACKAGES 0 83 #define TAB_ALL_PACKAGES 1 84 85 using namespace BPackageKit; 86 using namespace BPackageKit::BManager::BPrivate; 87 88 89 typedef std::map<BString, PackageInfoRef> PackageInfoMap; 90 91 92 struct RefreshWorkerParameters { 93 MainWindow* window; 94 bool forceRefresh; 95 96 RefreshWorkerParameters(MainWindow* window, bool forceRefresh) 97 : 98 window(window), 99 forceRefresh(forceRefresh) 100 { 101 } 102 }; 103 104 105 class MainWindowModelListener : public ModelListener { 106 public: 107 MainWindowModelListener(const BMessenger& messenger) 108 : 109 fMessenger(messenger) 110 { 111 } 112 113 virtual void AuthorizationChanged() 114 { 115 if (fMessenger.IsValid()) 116 fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED); 117 } 118 119 virtual void CategoryListChanged() 120 { 121 if (fMessenger.IsValid()) 122 fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED); 123 } 124 125 private: 126 BMessenger fMessenger; 127 }; 128 129 130 MainWindow::MainWindow(const BMessage& settings) 131 : 132 BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 133 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 134 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 135 fScreenshotWindow(NULL), 136 fUserMenu(NULL), 137 fLogInItem(NULL), 138 fLogOutItem(NULL), 139 fUsersUserUsageConditionsMenuItem(NULL), 140 fModelListener(new MainWindowModelListener(BMessenger(this)), true), 141 fCoordinator(NULL), 142 fSinglePackageMode(false) 143 { 144 if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK) 145 debugger("unable to create the process coordinator semaphore"); 146 147 BMenuBar* menuBar = new BMenuBar("Main Menu"); 148 _BuildMenu(menuBar); 149 150 BMenuBar* userMenuBar = new BMenuBar("User Menu"); 151 _BuildUserMenu(userMenuBar); 152 set_small_font(userMenuBar); 153 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET, 154 menuBar->MaxSize().height)); 155 156 fFilterView = new FilterView(); 157 fFeaturedPackagesView = new FeaturedPackagesView(fModel); 158 fPackageListView = new PackageListView(&fModel); 159 fPackageInfoView = new PackageInfoView(&fModel, this); 160 161 fSplitView = new BSplitView(B_VERTICAL, 5.0f); 162 163 fWorkStatusView = new WorkStatusView("work status"); 164 fPackageListView->AttachWorkStatusView(fWorkStatusView); 165 166 fListTabs = new TabView(BMessenger(this), 167 BMessage(MSG_CHANGE_PACKAGE_LIST_VIEW_MODE), "list tabs"); 168 fListTabs->AddTab(fFeaturedPackagesView); 169 fListTabs->AddTab(fPackageListView); 170 171 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) 172 .AddGroup(B_HORIZONTAL, 0.0f) 173 .Add(menuBar, 1.0f) 174 .Add(userMenuBar, 0.0f) 175 .End() 176 .Add(fFilterView) 177 .AddSplit(fSplitView) 178 .AddGroup(B_VERTICAL) 179 .Add(fListTabs) 180 .SetInsets( 181 B_USE_DEFAULT_SPACING, 0.0f, 182 B_USE_DEFAULT_SPACING, 0.0f) 183 .End() 184 .Add(fPackageInfoView) 185 .End() 186 .Add(fWorkStatusView) 187 ; 188 189 fSplitView->SetCollapsible(0, false); 190 fSplitView->SetCollapsible(1, false); 191 192 fModel.AddListener(fModelListener); 193 194 BMessage columnSettings; 195 if (settings.FindMessage("column settings", &columnSettings) == B_OK) 196 fPackageListView->LoadState(&columnSettings); 197 198 _RestoreModelSettings(settings); 199 _MaybePromptCanShareAnonymousUserData(settings); 200 201 if (fModel.PackageListViewMode() == PROMINENT) 202 fListTabs->Select(TAB_PROMINENT_PACKAGES); 203 else 204 fListTabs->Select(TAB_ALL_PACKAGES); 205 206 _RestoreNickname(settings); 207 _UpdateAuthorization(); 208 _RestoreWindowFrame(settings); 209 210 // start worker threads 211 BPackageRoster().StartWatching(this, 212 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS); 213 214 _InitWorkerThreads(); 215 _AdoptModel(); 216 _StartBulkLoad(); 217 } 218 219 220 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package) 221 : 222 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 223 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 224 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 225 fFeaturedPackagesView(NULL), 226 fPackageListView(NULL), 227 fWorkStatusView(NULL), 228 fScreenshotWindow(NULL), 229 fUserMenu(NULL), 230 fLogInItem(NULL), 231 fLogOutItem(NULL), 232 fUsersUserUsageConditionsMenuItem(NULL), 233 fModelListener(new MainWindowModelListener(BMessenger(this)), true), 234 fCoordinator(NULL), 235 fSinglePackageMode(true) 236 { 237 if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK) 238 debugger("unable to create the process coordinator semaphore"); 239 240 fFilterView = new FilterView(); 241 fPackageInfoView = new PackageInfoView(&fModel, this); 242 fWorkStatusView = new WorkStatusView("work status"); 243 244 BLayoutBuilder::Group<>(this, B_VERTICAL) 245 .Add(fPackageInfoView) 246 .Add(fWorkStatusView) 247 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0) 248 ; 249 250 fModel.AddListener(fModelListener); 251 252 // Restore settings 253 _RestoreNickname(settings); 254 _UpdateAuthorization(); 255 _RestoreWindowFrame(settings); 256 257 fPackageInfoView->SetPackage(package); 258 259 _InitWorkerThreads(); 260 } 261 262 263 MainWindow::~MainWindow() 264 { 265 _SpinUntilProcessCoordinatorComplete(); 266 delete_sem(fCoordinatorRunningSem); 267 fCoordinatorRunningSem = 0; 268 269 BPackageRoster().StopWatching(this); 270 271 delete_sem(fPendingActionsSem); 272 if (fPendingActionsWorker >= 0) 273 wait_for_thread(fPendingActionsWorker, 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 _StopProcessCoordinators(); 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 { 303 int64 errorStatus64; 304 if (message->FindInt64(KEY_ERROR_STATUS, &errorStatus64) == B_OK) 305 _BulkLoadCompleteReceived((status_t) errorStatus64); 306 else 307 HDERROR("expected [%s] value in message", KEY_ERROR_STATUS); 308 break; 309 } 310 case B_SIMPLE_DATA: 311 case B_REFS_RECEIVED: 312 // TODO: ? 313 break; 314 315 case B_PACKAGE_UPDATE: 316 // TODO: see ticket #15879 317 // work needs to be done here to selectively update package data in 318 // the running HaikuDepot application when there are changes on the 319 // system. There is now too much data to just load everything when 320 // there is a change. 321 //_StartBulkLoad(false); 322 break; 323 324 case MSG_REFRESH_REPOS: 325 _StartBulkLoad(true); 326 break; 327 328 case MSG_WORK_STATUS_CLEAR: 329 _HandleWorkStatusClear(); 330 break; 331 332 case MSG_WORK_STATUS_CHANGE: 333 _HandleWorkStatusChangeMessageReceived(message); 334 break; 335 336 case MSG_MANAGE_REPOS: 337 be_roster->Launch("application/x-vnd.Haiku-Repositories"); 338 break; 339 340 case MSG_SOFTWARE_UPDATER: 341 be_roster->Launch("application/x-vnd.haiku-softwareupdater"); 342 break; 343 344 case MSG_LOG_IN: 345 _OpenLoginWindow(BMessage()); 346 break; 347 348 case MSG_SETTINGS: 349 _OpenSettingsWindow(); 350 break; 351 352 case MSG_LOG_OUT: 353 fModel.SetNickname(""); 354 break; 355 356 case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS: 357 _ViewUserUsageConditions(LATEST); 358 break; 359 360 case MSG_VIEW_USERS_USER_USAGE_CONDITIONS: 361 _ViewUserUsageConditions(USER); 362 break; 363 364 case MSG_AUTHORIZATION_CHANGED: 365 _StartUserVerify(); 366 _UpdateAuthorization(); 367 break; 368 369 case MSG_CATEGORIES_LIST_CHANGED: 370 fFilterView->AdoptModel(fModel); 371 break; 372 373 case MSG_CHANGE_PACKAGE_LIST_VIEW_MODE: 374 _HandleChangePackageListViewMode(); 375 break; 376 377 case MSG_SHOW_AVAILABLE_PACKAGES: 378 { 379 BAutolock locker(fModel.Lock()); 380 fModel.SetShowAvailablePackages( 381 !fModel.ShowAvailablePackages()); 382 } 383 _AdoptModel(); 384 break; 385 386 case MSG_SHOW_INSTALLED_PACKAGES: 387 { 388 BAutolock locker(fModel.Lock()); 389 fModel.SetShowInstalledPackages( 390 !fModel.ShowInstalledPackages()); 391 } 392 _AdoptModel(); 393 break; 394 395 case MSG_SHOW_SOURCE_PACKAGES: 396 { 397 BAutolock locker(fModel.Lock()); 398 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages()); 399 } 400 _AdoptModel(); 401 break; 402 403 case MSG_SHOW_DEVELOP_PACKAGES: 404 { 405 BAutolock locker(fModel.Lock()); 406 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages()); 407 } 408 _AdoptModel(); 409 break; 410 411 // this may be triggered by, for example, a user rating being added 412 // or having been altered. 413 case MSG_SERVER_DATA_CHANGED: 414 { 415 BString name; 416 if (message->FindString("name", &name) == B_OK) { 417 BAutolock locker(fModel.Lock()); 418 if (fPackageInfoView->Package()->Name() == name) { 419 _PopulatePackageAsync(true); 420 } else { 421 HDDEBUG("pkg [%s] is updated on the server, but is " 422 "not selected so will not be updated.", 423 name.String()); 424 } 425 } 426 break; 427 } 428 429 case MSG_PACKAGE_SELECTED: 430 { 431 BString name; 432 if (message->FindString("name", &name) == B_OK) { 433 PackageInfoRef package; 434 { 435 BAutolock locker(fModel.Lock()); 436 package = fModel.PackageForName(name); 437 } 438 if (!package.IsSet() || name != package->Name()) 439 debugger("unable to find the named package"); 440 else { 441 _AdoptPackage(package); 442 _IncrementViewCounter(package); 443 } 444 } else { 445 _ClearPackage(); 446 } 447 break; 448 } 449 450 case MSG_CATEGORY_SELECTED: 451 { 452 BString code; 453 if (message->FindString("code", &code) != B_OK) 454 code = ""; 455 { 456 BAutolock locker(fModel.Lock()); 457 fModel.SetCategory(code); 458 } 459 _AdoptModel(); 460 break; 461 } 462 463 case MSG_DEPOT_SELECTED: 464 { 465 BString name; 466 if (message->FindString("name", &name) != B_OK) 467 name = ""; 468 { 469 BAutolock locker(fModel.Lock()); 470 fModel.SetDepot(name); 471 } 472 _AdoptModel(); 473 _UpdateAvailableRepositories(); 474 break; 475 } 476 477 case MSG_SEARCH_TERMS_MODIFIED: 478 { 479 // TODO: Do this with a delay! 480 BString searchTerms; 481 if (message->FindString("search terms", &searchTerms) != B_OK) 482 searchTerms = ""; 483 { 484 BAutolock locker(fModel.Lock()); 485 fModel.SetSearchTerms(searchTerms); 486 } 487 _AdoptModel(); 488 break; 489 } 490 491 case MSG_PACKAGE_CHANGED: 492 { 493 PackageInfo* info; 494 if (message->FindPointer("package", (void**)&info) == B_OK) { 495 PackageInfoRef ref(info, true); 496 uint32 changes; 497 if (message->FindUInt32("changes", &changes) != B_OK) 498 changes = 0; 499 if ((changes & PKG_CHANGED_STATE) != 0) { 500 BAutolock locker(fModel.Lock()); 501 fModel.SetPackageState(ref, ref->State()); 502 } 503 504 fFeaturedPackagesView->BeginAddRemove(); 505 _AddRemovePackageFromLists(ref); 506 fFeaturedPackagesView->EndAddRemove(); 507 508 if ((changes & PKG_CHANGED_STATE) != 0 509 && !fCoordinator.IsSet()) { 510 fWorkStatusView->PackageStatusChanged(ref); 511 } 512 } 513 break; 514 } 515 516 case MSG_RATE_PACKAGE: 517 _RatePackage(); 518 break; 519 520 case MSG_SHOW_SCREENSHOT: 521 _ShowScreenshot(); 522 break; 523 524 case MSG_PACKAGE_WORKER_BUSY: 525 { 526 BString reason; 527 status_t status = message->FindString("reason", &reason); 528 if (status != B_OK) 529 break; 530 fWorkStatusView->SetBusy(reason); 531 break; 532 } 533 534 case MSG_PACKAGE_WORKER_IDLE: 535 fWorkStatusView->SetIdle(); 536 break; 537 538 case MSG_USER_USAGE_CONDITIONS_NOT_LATEST: 539 { 540 BMessage userDetailMsg; 541 if (message->FindMessage("userDetail", &userDetailMsg) != B_OK) { 542 debugger("expected the [userDetail] data to be carried in the " 543 "message."); 544 } 545 UserDetail userDetail(&userDetailMsg); 546 _HandleUserUsageConditionsNotLatest(userDetail); 547 break; 548 } 549 550 default: 551 BWindow::MessageReceived(message); 552 break; 553 } 554 } 555 556 557 static const char* 558 main_window_package_list_view_mode_str(package_list_view_mode mode) 559 { 560 if (mode == PROMINENT) 561 return "PROMINENT"; 562 return "ALL"; 563 } 564 565 566 static package_list_view_mode 567 main_window_str_to_package_list_view_mode(const BString& str) 568 { 569 if (str == "PROMINENT") 570 return PROMINENT; 571 return ALL; 572 } 573 574 575 void 576 MainWindow::StoreSettings(BMessage& settings) const 577 { 578 settings.AddRect(_WindowFrameName(), Frame()); 579 if (!fSinglePackageMode) { 580 settings.AddRect("window frame", Frame()); 581 582 BMessage columnSettings; 583 if (fPackageListView != NULL) 584 fPackageListView->SaveState(&columnSettings); 585 586 settings.AddMessage("column settings", &columnSettings); 587 588 settings.AddString(SETTING_PACKAGE_LIST_VIEW_MODE, 589 main_window_package_list_view_mode_str( 590 fModel.PackageListViewMode())); 591 settings.AddBool(SETTING_SHOW_AVAILABLE_PACKAGES, 592 fModel.ShowAvailablePackages()); 593 settings.AddBool(SETTING_SHOW_INSTALLED_PACKAGES, 594 fModel.ShowInstalledPackages()); 595 settings.AddBool(SETTING_SHOW_DEVELOP_PACKAGES, 596 fModel.ShowDevelopPackages()); 597 settings.AddBool(SETTING_SHOW_SOURCE_PACKAGES, 598 fModel.ShowSourcePackages()); 599 settings.AddBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, 600 fModel.CanShareAnonymousUsageData()); 601 } 602 603 settings.AddString("username", fModel.Nickname()); 604 } 605 606 607 void 608 MainWindow::PackageChanged(const PackageInfoEvent& event) 609 { 610 uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; 611 if ((event.Changes() & watchedChanges) != 0) { 612 PackageInfoRef ref(event.Package()); 613 BMessage message(MSG_PACKAGE_CHANGED); 614 message.AddPointer("package", ref.Get()); 615 message.AddUInt32("changes", event.Changes()); 616 ref.Detach(); 617 // reference needs to be released by MessageReceived(); 618 PostMessage(&message); 619 } 620 } 621 622 623 status_t 624 MainWindow::SchedulePackageAction(PackageActionRef action) 625 { 626 AutoLocker<BLocker> lock(&fPendingActionsLock); 627 fPendingActions.push(action); 628 return release_sem_etc(fPendingActionsSem, 1, 0); 629 } 630 631 632 Model* 633 MainWindow::GetModel() 634 { 635 return &fModel; 636 } 637 638 639 void 640 MainWindow::_BuildMenu(BMenuBar* menuBar) 641 { 642 BMenu* menu = new BMenu(B_TRANSLATE_SYSTEM_NAME("HaikuDepot")); 643 fRefreshRepositoriesItem = new BMenuItem( 644 B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS)); 645 menu->AddItem(fRefreshRepositoriesItem); 646 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories" 647 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS))); 648 menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates" 649 B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER))); 650 menu->AddSeparatorItem(); 651 menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 652 new BMessage(MSG_SETTINGS), ',')); 653 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 654 new BMessage(B_QUIT_REQUESTED), 'Q')); 655 menuBar->AddItem(menu); 656 657 fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories")); 658 menuBar->AddItem(fRepositoryMenu); 659 660 menu = new BMenu(B_TRANSLATE("Show")); 661 662 fShowAvailablePackagesItem = new BMenuItem( 663 B_TRANSLATE("Available packages"), 664 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES)); 665 menu->AddItem(fShowAvailablePackagesItem); 666 667 fShowInstalledPackagesItem = new BMenuItem( 668 B_TRANSLATE("Installed packages"), 669 new BMessage(MSG_SHOW_INSTALLED_PACKAGES)); 670 menu->AddItem(fShowInstalledPackagesItem); 671 672 menu->AddSeparatorItem(); 673 674 fShowDevelopPackagesItem = new BMenuItem( 675 B_TRANSLATE("Develop packages"), 676 new BMessage(MSG_SHOW_DEVELOP_PACKAGES)); 677 menu->AddItem(fShowDevelopPackagesItem); 678 679 fShowSourcePackagesItem = new BMenuItem( 680 B_TRANSLATE("Source packages"), 681 new BMessage(MSG_SHOW_SOURCE_PACKAGES)); 682 menu->AddItem(fShowSourcePackagesItem); 683 684 menuBar->AddItem(menu); 685 } 686 687 688 void 689 MainWindow::_BuildUserMenu(BMenuBar* menuBar) 690 { 691 fUserMenu = new BMenu(B_TRANSLATE("Not logged in")); 692 693 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), 694 new BMessage(MSG_LOG_IN)); 695 fUserMenu->AddItem(fLogInItem); 696 697 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), 698 new BMessage(MSG_LOG_OUT)); 699 fUserMenu->AddItem(fLogOutItem); 700 701 BMenuItem *latestUserUsageConditionsMenuItem = 702 new BMenuItem(B_TRANSLATE("View latest usage conditions" 703 B_UTF8_ELLIPSIS), 704 new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS)); 705 fUserMenu->AddItem(latestUserUsageConditionsMenuItem); 706 707 fUsersUserUsageConditionsMenuItem = 708 new BMenuItem(B_TRANSLATE("View agreed usage conditions" 709 B_UTF8_ELLIPSIS), 710 new BMessage(MSG_VIEW_USERS_USER_USAGE_CONDITIONS)); 711 fUserMenu->AddItem(fUsersUserUsageConditionsMenuItem); 712 713 menuBar->AddItem(fUserMenu); 714 } 715 716 717 void 718 MainWindow::_RestoreNickname(const BMessage& settings) 719 { 720 BString nickname; 721 if (settings.FindString("username", &nickname) == B_OK 722 && nickname.Length() > 0) { 723 fModel.SetNickname(nickname); 724 } 725 } 726 727 728 const char* 729 MainWindow::_WindowFrameName() const 730 { 731 if (fSinglePackageMode) 732 return "small window frame"; 733 734 return "window frame"; 735 } 736 737 738 void 739 MainWindow::_RestoreWindowFrame(const BMessage& settings) 740 { 741 BRect frame = Frame(); 742 743 BRect windowFrame; 744 bool fromSettings = false; 745 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) { 746 frame = windowFrame; 747 fromSettings = true; 748 } else if (!fSinglePackageMode) { 749 // Resize to occupy a certain screen size 750 BRect screenFrame = BScreen(this).Frame(); 751 float width = frame.Width(); 752 float height = frame.Height(); 753 if (width < screenFrame.Width() * .666f 754 && height < screenFrame.Height() * .666f) { 755 frame.bottom = frame.top + screenFrame.Height() * .666f; 756 frame.right = frame.left 757 + std::min(screenFrame.Width() * .666f, height * 7 / 5); 758 } 759 } 760 761 MoveTo(frame.LeftTop()); 762 ResizeTo(frame.Width(), frame.Height()); 763 764 if (fromSettings) 765 MoveOnScreen(); 766 else 767 CenterOnScreen(); 768 } 769 770 771 void 772 MainWindow::_RestoreModelSettings(const BMessage& settings) 773 { 774 BString packageListViewMode; 775 if (settings.FindString(SETTING_PACKAGE_LIST_VIEW_MODE, 776 &packageListViewMode) == B_OK) { 777 fModel.SetPackageListViewMode( 778 main_window_str_to_package_list_view_mode(packageListViewMode)); 779 } 780 781 bool showOption; 782 if (settings.FindBool(SETTING_SHOW_AVAILABLE_PACKAGES, &showOption) == B_OK) 783 fModel.SetShowAvailablePackages(showOption); 784 if (settings.FindBool(SETTING_SHOW_INSTALLED_PACKAGES, &showOption) == B_OK) 785 fModel.SetShowInstalledPackages(showOption); 786 if (settings.FindBool(SETTING_SHOW_DEVELOP_PACKAGES, &showOption) == B_OK) 787 fModel.SetShowDevelopPackages(showOption); 788 if (settings.FindBool(SETTING_SHOW_SOURCE_PACKAGES, &showOption) == B_OK) 789 fModel.SetShowSourcePackages(showOption); 790 if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, 791 &showOption) == B_OK) { 792 fModel.SetCanShareAnonymousUsageData(showOption); 793 } 794 } 795 796 797 void 798 MainWindow::_MaybePromptCanShareAnonymousUserData(const BMessage& settings) 799 { 800 bool showOption; 801 if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, 802 &showOption) == B_NAME_NOT_FOUND) { 803 _PromptCanShareAnonymousUserData(); 804 } 805 } 806 807 808 void 809 MainWindow::_PromptCanShareAnonymousUserData() 810 { 811 BAlert* alert = new(std::nothrow) BAlert( 812 B_TRANSLATE("Sending anonymous usage data"), 813 B_TRANSLATE("Would it be acceptable to send anonymous usage data to the" 814 " HaikuDepotServer system from this computer? You can change your" 815 " preference in the \"Settings\" window later."), 816 B_TRANSLATE("No"), 817 B_TRANSLATE("Yes")); 818 819 int32 result = alert->Go(); 820 fModel.SetCanShareAnonymousUsageData(1 == result); 821 } 822 823 824 void 825 MainWindow::_InitWorkerThreads() 826 { 827 fPendingActionsSem = create_sem(0, "PendingPackageActions"); 828 if (fPendingActionsSem >= 0) { 829 fPendingActionsWorker = spawn_thread(&_PackageActionWorker, 830 "Planet Express", B_NORMAL_PRIORITY, this); 831 if (fPendingActionsWorker >= 0) 832 resume_thread(fPendingActionsWorker); 833 } else 834 fPendingActionsWorker = -1; 835 836 fPackageToPopulateSem = create_sem(0, "PopulatePackage"); 837 if (fPackageToPopulateSem >= 0) { 838 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker, 839 "Package Populator", B_NORMAL_PRIORITY, this); 840 if (fPopulatePackageWorker >= 0) 841 resume_thread(fPopulatePackageWorker); 842 } else 843 fPopulatePackageWorker = -1; 844 } 845 846 847 void 848 MainWindow::_AdoptModelControls() 849 { 850 if (fSinglePackageMode) 851 return; 852 853 BAutolock locker(fModel.Lock()); 854 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages()); 855 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages()); 856 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages()); 857 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages()); 858 859 if (fModel.PackageListViewMode() == PROMINENT) 860 fListTabs->Select(TAB_PROMINENT_PACKAGES); 861 else 862 fListTabs->Select(TAB_ALL_PACKAGES); 863 864 fFilterView->AdoptModel(fModel); 865 } 866 867 868 void 869 MainWindow::_AdoptModel() 870 { 871 HDTRACE("adopting model to main window ui"); 872 873 if (fSinglePackageMode) 874 return; 875 876 std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots(); 877 std::vector<DepotInfoRef>::iterator it; 878 879 fFeaturedPackagesView->BeginAddRemove(); 880 881 for (it = depots.begin(); it != depots.end(); it++) { 882 DepotInfoRef depotInfoRef = *it; 883 for (int i = 0; i < depotInfoRef->CountPackages(); i++) { 884 PackageInfoRef package = depotInfoRef->PackageAtIndex(i); 885 _AddRemovePackageFromLists(package); 886 } 887 } 888 889 fFeaturedPackagesView->EndAddRemove(); 890 891 _AdoptModelControls(); 892 } 893 894 895 void 896 MainWindow::_AddRemovePackageFromLists(const PackageInfoRef& package) 897 { 898 bool matches; 899 900 { 901 AutoLocker<BLocker> modelLocker(fModel.Lock()); 902 matches = fModel.MatchesFilter(package); 903 } 904 905 if (matches) { 906 if (package->IsProminent()) 907 fFeaturedPackagesView->AddPackage(package); 908 fPackageListView->AddPackage(package); 909 } else { 910 fFeaturedPackagesView->RemovePackage(package); 911 fPackageListView->RemovePackage(package); 912 } 913 } 914 915 916 void 917 MainWindow::_IncrementViewCounter(const PackageInfoRef& package) 918 { 919 bool shouldIncrementViewCounter = false; 920 921 { 922 AutoLocker<BLocker> modelLocker(fModel.Lock()); 923 bool canShareAnonymousUsageData = fModel.CanShareAnonymousUsageData(); 924 if (canShareAnonymousUsageData && !package->Viewed()) { 925 package->SetViewed(); 926 shouldIncrementViewCounter = true; 927 } 928 } 929 930 if (shouldIncrementViewCounter) { 931 ProcessCoordinator* bulkLoadCoordinator = 932 ProcessCoordinatorFactory::CreateIncrementViewCounter( 933 this, 934 // ProcessCoordinatorListener 935 &fModel, package); 936 _AddProcessCoordinator(bulkLoadCoordinator); 937 } 938 } 939 940 941 void 942 MainWindow::_AdoptPackage(const PackageInfoRef& package) 943 { 944 { 945 BAutolock locker(fModel.Lock()); 946 fPackageInfoView->SetPackage(package); 947 948 if (fFeaturedPackagesView != NULL) 949 fFeaturedPackagesView->SelectPackage(package); 950 if (fPackageListView != NULL) 951 fPackageListView->SelectPackage(package); 952 } 953 954 _PopulatePackageAsync(false); 955 } 956 957 958 void 959 MainWindow::_ClearPackage() 960 { 961 fPackageInfoView->Clear(); 962 } 963 964 965 void 966 MainWindow::_StartBulkLoad(bool force) 967 { 968 if (fFeaturedPackagesView != NULL) 969 fFeaturedPackagesView->Clear(); 970 if (fPackageListView != NULL) 971 fPackageListView->Clear(); 972 fPackageInfoView->Clear(); 973 974 fRefreshRepositoriesItem->SetEnabled(false); 975 ProcessCoordinator* bulkLoadCoordinator = 976 ProcessCoordinatorFactory::CreateBulkLoadCoordinator( 977 this, 978 // PackageInfoListener 979 this, 980 // ProcessCoordinatorListener 981 &fModel, force); 982 _AddProcessCoordinator(bulkLoadCoordinator); 983 } 984 985 986 void 987 MainWindow::_BulkLoadCompleteReceived(status_t errorStatus) 988 { 989 if (errorStatus != B_OK) { 990 AppUtils::NotifySimpleError( 991 B_TRANSLATE("Package update error"), 992 B_TRANSLATE("While updating package data, a problem has arisen " 993 "that may cause data to be outdated or missing from the " 994 "application's display. Additional details regarding this " 995 "problem may be able to be obtained from the application " 996 "logs." 997 ALERT_MSG_LOGS_USER_GUIDE)); 998 } 999 1000 fRefreshRepositoriesItem->SetEnabled(true); 1001 _AdoptModel(); 1002 _UpdateAvailableRepositories(); 1003 1004 // if after loading everything in, it transpires that there are no 1005 // featured packages then the featured packages should be disabled 1006 // and the user should be switched to the "all packages" view so 1007 // that they are not presented with a blank window! 1008 1009 bool hasProminentPackages = fModel.HasAnyProminentPackages(); 1010 fListTabs->TabAt(TAB_PROMINENT_PACKAGES)->SetEnabled(hasProminentPackages); 1011 if (!hasProminentPackages 1012 && fListTabs->Selection() == TAB_PROMINENT_PACKAGES) { 1013 fModel.SetPackageListViewMode(ALL); 1014 fListTabs->Select(TAB_ALL_PACKAGES); 1015 } 1016 } 1017 1018 1019 void 1020 MainWindow::_NotifyWorkStatusClear() 1021 { 1022 BMessage message(MSG_WORK_STATUS_CLEAR); 1023 this->PostMessage(&message, this); 1024 } 1025 1026 1027 void 1028 MainWindow::_HandleWorkStatusClear() 1029 { 1030 fWorkStatusView->SetText(""); 1031 fWorkStatusView->SetIdle(); 1032 } 1033 1034 1035 /*! Sends off a message to the Window so that it can change the status view 1036 on the front-end in the UI thread. 1037 */ 1038 1039 void 1040 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress) 1041 { 1042 BMessage message(MSG_WORK_STATUS_CHANGE); 1043 1044 if (!text.IsEmpty()) 1045 message.AddString(KEY_WORK_STATUS_TEXT, text); 1046 message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress); 1047 1048 this->PostMessage(&message, this); 1049 } 1050 1051 1052 void 1053 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message) 1054 { 1055 if (fWorkStatusView == NULL) 1056 return; 1057 1058 BString text; 1059 float progress; 1060 1061 if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK) 1062 fWorkStatusView->SetText(text); 1063 1064 if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK) 1065 fWorkStatusView->SetProgress(progress); 1066 } 1067 1068 1069 /*static*/ status_t 1070 MainWindow::_PackageActionWorker(void* arg) 1071 { 1072 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1073 1074 while (acquire_sem(window->fPendingActionsSem) == B_OK) { 1075 PackageActionRef ref; 1076 { 1077 AutoLocker<BLocker> lock(&window->fPendingActionsLock); 1078 ref = window->fPendingActions.front(); 1079 window->fPendingActions.pop(); 1080 if (!ref.IsSet()) 1081 break; 1082 } 1083 1084 BMessenger messenger(window); 1085 BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY); 1086 BString text(ref->Label()); 1087 text << B_UTF8_ELLIPSIS; 1088 busyMessage.AddString("reason", text); 1089 1090 messenger.SendMessage(&busyMessage); 1091 ref->Perform(); 1092 messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE); 1093 } 1094 1095 return 0; 1096 } 1097 1098 1099 /*! This method will cause the package to have its data refreshed from 1100 the server application. The refresh happens in the background; this method 1101 is asynchronous. 1102 */ 1103 1104 void 1105 MainWindow::_PopulatePackageAsync(bool forcePopulate) 1106 { 1107 // Trigger asynchronous package population from the web-app 1108 { 1109 AutoLocker<BLocker> lock(&fPackageToPopulateLock); 1110 fPackageToPopulate = fPackageInfoView->Package(); 1111 fForcePopulatePackage = forcePopulate; 1112 } 1113 release_sem_etc(fPackageToPopulateSem, 1, 0); 1114 1115 HDDEBUG("pkg [%s] will be updated from the server.", 1116 fPackageToPopulate->Name().String()); 1117 } 1118 1119 1120 /*! This method will run in the background. The thread will block until there 1121 is a package to be updated. When the thread unblocks, it will update the 1122 package with information from the server. 1123 */ 1124 1125 status_t 1126 MainWindow::_PopulatePackageWorker(void* arg) 1127 { 1128 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1129 1130 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { 1131 PackageInfoRef package; 1132 bool force; 1133 { 1134 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); 1135 package = window->fPackageToPopulate; 1136 force = window->fForcePopulatePackage; 1137 } 1138 1139 if (package.IsSet()) { 1140 uint32 populateFlags = Model::POPULATE_USER_RATINGS 1141 | Model::POPULATE_SCREEN_SHOTS 1142 | Model::POPULATE_CHANGELOG; 1143 1144 if (force) 1145 populateFlags |= Model::POPULATE_FORCE; 1146 1147 window->fModel.PopulatePackage(package, populateFlags); 1148 1149 HDDEBUG("populating package [%s]", package->Name().String()); 1150 } 1151 } 1152 1153 return 0; 1154 } 1155 1156 1157 void 1158 MainWindow::_OpenSettingsWindow() 1159 { 1160 SettingsWindow* window = new SettingsWindow(this, &fModel); 1161 window->Show(); 1162 } 1163 1164 1165 void 1166 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage) 1167 { 1168 UserLoginWindow* window = new UserLoginWindow(this, 1169 BRect(0, 0, 500, 400), fModel); 1170 1171 if (onSuccessMessage.what != 0) 1172 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage); 1173 1174 window->Show(); 1175 } 1176 1177 1178 void 1179 MainWindow::_StartUserVerify() 1180 { 1181 if (!fModel.Nickname().IsEmpty()) { 1182 _AddProcessCoordinator( 1183 ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator( 1184 this, 1185 // UserDetailVerifierListener 1186 this, 1187 // ProcessCoordinatorListener 1188 &fModel) ); 1189 } 1190 } 1191 1192 1193 void 1194 MainWindow::_UpdateAuthorization() 1195 { 1196 BString nickname(fModel.Nickname()); 1197 bool hasUser = !nickname.IsEmpty(); 1198 1199 if (fLogOutItem != NULL) 1200 fLogOutItem->SetEnabled(hasUser); 1201 if (fUsersUserUsageConditionsMenuItem != NULL) 1202 fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser); 1203 if (fLogInItem != NULL) { 1204 if (hasUser) 1205 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS)); 1206 else 1207 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS)); 1208 } 1209 1210 if (fUserMenu != NULL) { 1211 BString label; 1212 if (hasUser) { 1213 label = B_TRANSLATE("Logged in as %User%"); 1214 label.ReplaceAll("%User%", nickname); 1215 } else { 1216 label = B_TRANSLATE("Not logged in"); 1217 } 1218 fUserMenu->Superitem()->SetLabel(label); 1219 } 1220 } 1221 1222 1223 void 1224 MainWindow::_UpdateAvailableRepositories() 1225 { 1226 fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true); 1227 1228 fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"), 1229 new BMessage(MSG_DEPOT_SELECTED))); 1230 1231 fRepositoryMenu->AddItem(new BSeparatorItem()); 1232 1233 bool foundSelectedDepot = false; 1234 std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots(); 1235 std::vector<DepotInfoRef>::iterator it; 1236 1237 for (it = depots.begin(); it != depots.end(); it++) { 1238 DepotInfoRef depot = *it; 1239 1240 if (depot->Name().Length() != 0) { 1241 BMessage* message = new BMessage(MSG_DEPOT_SELECTED); 1242 message->AddString("name", depot->Name()); 1243 BMenuItem* item = new(std::nothrow) BMenuItem(depot->Name(), message); 1244 1245 if (item == NULL) 1246 HDFATAL("memory exhaustion"); 1247 1248 fRepositoryMenu->AddItem(item); 1249 1250 if (depot->Name() == fModel.Depot()) { 1251 item->SetMarked(true); 1252 foundSelectedDepot = true; 1253 } 1254 } 1255 } 1256 1257 if (!foundSelectedDepot) 1258 fRepositoryMenu->ItemAt(0)->SetMarked(true); 1259 } 1260 1261 1262 bool 1263 MainWindow::_SelectedPackageHasWebAppRepositoryCode() 1264 { 1265 const PackageInfoRef& package = fPackageInfoView->Package(); 1266 const BString depotName = package->DepotName(); 1267 1268 if (depotName.IsEmpty()) { 1269 HDDEBUG("the package [%s] has no depot name", package->Name().String()); 1270 } else { 1271 const DepotInfo* depot = fModel.DepotForName(depotName); 1272 1273 if (depot == NULL) { 1274 HDINFO("the depot [%s] was not able to be found", 1275 depotName.String()); 1276 } else { 1277 BString repositoryCode = depot->WebAppRepositoryCode(); 1278 1279 if (repositoryCode.IsEmpty()) { 1280 HDINFO("the depot [%s] has no web app repository code", 1281 depotName.String()); 1282 } else 1283 return true; 1284 } 1285 } 1286 1287 return false; 1288 } 1289 1290 1291 void 1292 MainWindow::_RatePackage() 1293 { 1294 if (!_SelectedPackageHasWebAppRepositoryCode()) { 1295 BAlert* alert = new(std::nothrow) BAlert( 1296 B_TRANSLATE("Rating not possible"), 1297 B_TRANSLATE("This package doesn't seem to be on the HaikuDepot " 1298 "Server, so it's not possible to create a new rating " 1299 "or edit an existing rating."), 1300 B_TRANSLATE("OK")); 1301 alert->Go(); 1302 return; 1303 } 1304 1305 if (fModel.Nickname().IsEmpty()) { 1306 BAlert* alert = new(std::nothrow) BAlert( 1307 B_TRANSLATE("Not logged in"), 1308 B_TRANSLATE("You need to be logged into an account before you " 1309 "can rate packages."), 1310 B_TRANSLATE("Cancel"), 1311 B_TRANSLATE("Login or Create account")); 1312 1313 if (alert == NULL) 1314 return; 1315 1316 int32 choice = alert->Go(); 1317 if (choice == 1) 1318 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE)); 1319 return; 1320 } 1321 1322 // TODO: Allow only one RatePackageWindow 1323 // TODO: Mechanism for remembering the window frame 1324 RatePackageWindow* window = new RatePackageWindow(this, 1325 BRect(0, 0, 500, 400), fModel); 1326 window->SetPackage(fPackageInfoView->Package()); 1327 window->Show(); 1328 } 1329 1330 1331 void 1332 MainWindow::_ShowScreenshot() 1333 { 1334 // TODO: Mechanism for remembering the window frame 1335 if (fScreenshotWindow == NULL) 1336 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400)); 1337 1338 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK) 1339 return; 1340 1341 fScreenshotWindow->SetPackage(fPackageInfoView->Package()); 1342 1343 if (fScreenshotWindow->IsHidden()) 1344 fScreenshotWindow->Show(); 1345 else 1346 fScreenshotWindow->Activate(); 1347 1348 fScreenshotWindow->Unlock(); 1349 } 1350 1351 1352 void 1353 MainWindow::_ViewUserUsageConditions( 1354 UserUsageConditionsSelectionMode mode) 1355 { 1356 UserUsageConditionsWindow* window = new UserUsageConditionsWindow( 1357 fModel, mode); 1358 window->Show(); 1359 } 1360 1361 1362 void 1363 MainWindow::UserCredentialsFailed() 1364 { 1365 BString message = B_TRANSLATE("The password previously " 1366 "supplied for the user [%Nickname%] is not currently " 1367 "valid. The user will be logged-out of this application " 1368 "and you should login again with your updated password."); 1369 message.ReplaceAll("%Nickname%", fModel.Nickname()); 1370 1371 AppUtils::NotifySimpleError(B_TRANSLATE("Login issue"), 1372 message); 1373 1374 { 1375 AutoLocker<BLocker> locker(fModel.Lock()); 1376 fModel.SetNickname(""); 1377 } 1378 } 1379 1380 1381 /*! \brief This method is invoked from the UserDetailVerifierProcess on a 1382 background thread. For this reason it lodges a message into itself 1383 which can then be handled on the main thread. 1384 */ 1385 1386 void 1387 MainWindow::UserUsageConditionsNotLatest(const UserDetail& userDetail) 1388 { 1389 BMessage message(MSG_USER_USAGE_CONDITIONS_NOT_LATEST); 1390 BMessage detailsMessage; 1391 if (userDetail.Archive(&detailsMessage, true) != B_OK 1392 || message.AddMessage("userDetail", &detailsMessage) != B_OK) { 1393 HDERROR("unable to archive the user detail into a message"); 1394 } 1395 else 1396 BMessenger(this).SendMessage(&message); 1397 } 1398 1399 1400 void 1401 MainWindow::_HandleUserUsageConditionsNotLatest( 1402 const UserDetail& userDetail) 1403 { 1404 ToLatestUserUsageConditionsWindow* window = 1405 new ToLatestUserUsageConditionsWindow(this, fModel, userDetail); 1406 window->Show(); 1407 } 1408 1409 1410 void 1411 MainWindow::_AddProcessCoordinator(ProcessCoordinator* item) 1412 { 1413 AutoLocker<BLocker> lock(&fCoordinatorLock); 1414 1415 if (!fCoordinator.IsSet()) { 1416 if (acquire_sem(fCoordinatorRunningSem) != B_OK) 1417 debugger("unable to acquire the process coordinator sem"); 1418 HDINFO("adding and starting a process coordinator [%s]", 1419 item->Name().String()); 1420 fCoordinator = BReference<ProcessCoordinator>(item); 1421 fCoordinator->Start(); 1422 } 1423 else { 1424 HDINFO("adding process coordinator [%s] to the queue", 1425 item->Name().String()); 1426 fCoordinatorQueue.push(item); 1427 } 1428 } 1429 1430 1431 void 1432 MainWindow::_SpinUntilProcessCoordinatorComplete() 1433 { 1434 while (true) { 1435 if (acquire_sem(fCoordinatorRunningSem) != B_OK) 1436 debugger("unable to acquire the process coordinator sem"); 1437 if (release_sem(fCoordinatorRunningSem) != B_OK) 1438 debugger("unable to release the process coordinator sem"); 1439 { 1440 AutoLocker<BLocker> lock(&fCoordinatorLock); 1441 if (!fCoordinator.IsSet()) 1442 return; 1443 } 1444 } 1445 } 1446 1447 1448 void 1449 MainWindow::_StopProcessCoordinators() 1450 { 1451 HDINFO("will stop all process coordinators"); 1452 1453 { 1454 AutoLocker<BLocker> lock(&fCoordinatorLock); 1455 1456 while (!fCoordinatorQueue.empty()) { 1457 BReference<ProcessCoordinator> processCoordinator 1458 = fCoordinatorQueue.front(); 1459 HDINFO("will drop queued process coordinator [%s]", 1460 processCoordinator->Name().String()); 1461 fCoordinatorQueue.pop(); 1462 } 1463 1464 if (fCoordinator.IsSet()) { 1465 fCoordinator->Stop(); 1466 } 1467 } 1468 1469 HDINFO("will wait until the process coordinator has stopped"); 1470 1471 _SpinUntilProcessCoordinatorComplete(); 1472 1473 HDINFO("did stop all process coordinators"); 1474 } 1475 1476 1477 /*! This method is called when there is some change in the bulk load process 1478 or other process coordinator. 1479 A change may mean that a new process has started / stopped etc... or it 1480 may mean that the entire coordinator has finished. 1481 */ 1482 1483 void 1484 MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState) 1485 { 1486 AutoLocker<BLocker> lock(&fCoordinatorLock); 1487 1488 if (fCoordinator.Get() == coordinatorState.Coordinator()) { 1489 if (!coordinatorState.IsRunning()) { 1490 if (release_sem(fCoordinatorRunningSem) != B_OK) 1491 debugger("unable to release the process coordinator sem"); 1492 HDINFO("process coordinator [%s] did complete", 1493 fCoordinator->Name().String()); 1494 // complete the last one that just finished 1495 BMessage* message = fCoordinator->Message(); 1496 1497 if (message != NULL) { 1498 BMessenger messenger(this); 1499 message->AddInt64(KEY_ERROR_STATUS, 1500 (int64) fCoordinator->ErrorStatus()); 1501 messenger.SendMessage(message); 1502 } 1503 1504 fCoordinator = BReference<ProcessCoordinator>(NULL); 1505 // will delete the old process coordinator if it is not used 1506 // elsewhere. 1507 1508 // now schedule the next one. 1509 if (!fCoordinatorQueue.empty()) { 1510 if (acquire_sem(fCoordinatorRunningSem) != B_OK) 1511 debugger("unable to acquire the process coordinator sem"); 1512 fCoordinator = fCoordinatorQueue.front(); 1513 HDINFO("starting next process coordinator [%s]", 1514 fCoordinator->Name().String()); 1515 fCoordinatorQueue.pop(); 1516 fCoordinator->Start(); 1517 } 1518 else { 1519 _NotifyWorkStatusClear(); 1520 } 1521 } 1522 else { 1523 _NotifyWorkStatusChange(coordinatorState.Message(), 1524 coordinatorState.Progress()); 1525 // show the progress to the user. 1526 } 1527 } else 1528 HDINFO("! unknown process coordinator changed"); 1529 } 1530 1531 1532 static package_list_view_mode 1533 main_window_tab_to_package_list_view_mode(int32 tab) 1534 { 1535 if (tab == TAB_PROMINENT_PACKAGES) 1536 return PROMINENT; 1537 return ALL; 1538 } 1539 1540 1541 void 1542 MainWindow::_HandleChangePackageListViewMode() 1543 { 1544 package_list_view_mode tabMode = main_window_tab_to_package_list_view_mode( 1545 fListTabs->Selection()); 1546 package_list_view_mode modelMode = fModel.PackageListViewMode(); 1547 1548 if (tabMode != modelMode) { 1549 BAutolock locker(fModel.Lock()); 1550 fModel.SetPackageListViewMode(tabMode); 1551 } 1552 } 1553 1554 1555 std::vector<DepotInfoRef> 1556 MainWindow::_CreateSnapshotOfDepots() 1557 { 1558 std::vector<DepotInfoRef> result; 1559 BAutolock locker(fModel.Lock()); 1560 int32 countDepots = fModel.CountDepots(); 1561 for(int32 i = 0; i < countDepots; i++) 1562 result.push_back(fModel.DepotAtIndex(i)); 1563 return result; 1564 } 1565