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