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