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 <package/Context.h> 36 #include <package/manager/Exceptions.h> 37 #include <package/manager/RepositoryBuilder.h> 38 #include <package/RefreshRepositoryRequest.h> 39 #include <package/PackageRoster.h> 40 #include "package/RepositoryCache.h" 41 #include <package/solver/SolverPackage.h> 42 #include <package/solver/SolverProblem.h> 43 #include <package/solver/SolverProblemSolution.h> 44 #include <package/solver/SolverRepository.h> 45 #include <package/solver/SolverResult.h> 46 47 #include "AutoDeleter.h" 48 #include "AutoLocker.h" 49 #include "DecisionProvider.h" 50 #include "FeaturedPackagesView.h" 51 #include "FilterView.h" 52 #include "JobStateListener.h" 53 #include "Logger.h" 54 #include "PackageInfoView.h" 55 #include "PackageListView.h" 56 #include "PackageManager.h" 57 #include "RatePackageWindow.h" 58 #include "RepositoryUrlUtils.h" 59 #include "support.h" 60 #include "ScreenshotWindow.h" 61 #include "UserLoginWindow.h" 62 #include "WorkStatusView.h" 63 64 65 #undef B_TRANSLATION_CONTEXT 66 #define B_TRANSLATION_CONTEXT "MainWindow" 67 68 69 enum { 70 MSG_MODEL_WORKER_DONE = 'mmwd', 71 MSG_REFRESH_REPOS = 'mrrp', 72 MSG_MANAGE_REPOS = 'mmrp', 73 MSG_SOFTWARE_UPDATER = 'mswu', 74 MSG_LOG_IN = 'lgin', 75 MSG_LOG_OUT = 'lgot', 76 MSG_AUTHORIZATION_CHANGED = 'athc', 77 MSG_PACKAGE_CHANGED = 'pchd', 78 79 MSG_SHOW_AVAILABLE_PACKAGES = 'savl', 80 MSG_SHOW_INSTALLED_PACKAGES = 'sins', 81 MSG_SHOW_SOURCE_PACKAGES = 'ssrc', 82 MSG_SHOW_DEVELOP_PACKAGES = 'sdvl' 83 }; 84 85 86 using namespace BPackageKit; 87 using namespace BPackageKit::BManager::BPrivate; 88 89 90 typedef std::map<BString, PackageInfoRef> PackageInfoMap; 91 92 93 struct RefreshWorkerParameters { 94 MainWindow* window; 95 bool forceRefresh; 96 97 RefreshWorkerParameters(MainWindow* window, bool forceRefresh) 98 : 99 window(window), 100 forceRefresh(forceRefresh) 101 { 102 } 103 }; 104 105 106 class MessageModelListener : public ModelListener { 107 public: 108 MessageModelListener(const BMessenger& messenger) 109 : 110 fMessenger(messenger) 111 { 112 } 113 114 virtual void AuthorizationChanged() 115 { 116 if (fMessenger.IsValid()) 117 fMessenger.SendMessage(MSG_AUTHORIZATION_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 fModelListener(new MessageModelListener(BMessenger(this)), true), 135 fBulkLoadStateMachine(&fModel), 136 fTerminating(false), 137 fSinglePackageMode(false), 138 fModelWorker(B_BAD_THREAD_ID) 139 { 140 BMenuBar* menuBar = new BMenuBar("Main Menu"); 141 _BuildMenu(menuBar); 142 143 BMenuBar* userMenuBar = new BMenuBar("User Menu"); 144 _BuildUserMenu(userMenuBar); 145 set_small_font(userMenuBar); 146 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET, 147 menuBar->MaxSize().height)); 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 BView* listArea = new BView("list area", 0); 160 fListLayout = new BCardLayout(); 161 listArea->SetLayout(fListLayout); 162 listArea->AddChild(fFeaturedPackagesView); 163 listArea->AddChild(fPackageListView); 164 165 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) 166 .AddGroup(B_HORIZONTAL, 0.0f) 167 .Add(menuBar, 1.0f) 168 .Add(userMenuBar, 0.0f) 169 .End() 170 .Add(fFilterView) 171 .AddSplit(fSplitView) 172 .AddGroup(B_VERTICAL) 173 .Add(listArea) 174 .SetInsets( 175 B_USE_DEFAULT_SPACING, 0.0f, 176 B_USE_DEFAULT_SPACING, 0.0f) 177 .End() 178 .Add(fPackageInfoView) 179 .End() 180 .Add(fWorkStatusView) 181 ; 182 183 fSplitView->SetCollapsible(0, false); 184 fSplitView->SetCollapsible(1, false); 185 186 fModel.AddListener(fModelListener); 187 188 // Restore settings 189 BMessage columnSettings; 190 if (settings.FindMessage("column settings", &columnSettings) == B_OK) 191 fPackageListView->LoadState(&columnSettings); 192 193 bool showOption; 194 if (settings.FindBool("show featured packages", &showOption) == B_OK) 195 fModel.SetShowFeaturedPackages(showOption); 196 if (settings.FindBool("show available packages", &showOption) == B_OK) 197 fModel.SetShowAvailablePackages(showOption); 198 if (settings.FindBool("show installed packages", &showOption) == B_OK) 199 fModel.SetShowInstalledPackages(showOption); 200 if (settings.FindBool("show develop packages", &showOption) == B_OK) 201 fModel.SetShowDevelopPackages(showOption); 202 if (settings.FindBool("show source packages", &showOption) == B_OK) 203 fModel.SetShowSourcePackages(showOption); 204 205 if (fModel.ShowFeaturedPackages()) 206 fListLayout->SetVisibleItem((int32)0); 207 else 208 fListLayout->SetVisibleItem(1); 209 210 _RestoreUserName(settings); 211 _RestoreWindowFrame(settings); 212 213 atomic_set(&fPackagesToShowListID, 0); 214 215 // start worker threads 216 BPackageRoster().StartWatching(this, 217 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS); 218 219 _StartRefreshWorker(); 220 221 _InitWorkerThreads(); 222 } 223 224 225 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package) 226 : 227 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 228 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 229 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 230 fWorkStatusView(NULL), 231 fScreenshotWindow(NULL), 232 fUserMenu(NULL), 233 fLogInItem(NULL), 234 fLogOutItem(NULL), 235 fModelListener(new MessageModelListener(BMessenger(this)), true), 236 fBulkLoadStateMachine(&fModel), 237 fTerminating(false), 238 fSinglePackageMode(true), 239 fModelWorker(B_BAD_THREAD_ID) 240 { 241 fFilterView = new FilterView(); 242 fPackageListView = new PackageListView(fModel.Lock()); 243 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 244 245 BLayoutBuilder::Group<>(this, B_VERTICAL) 246 .Add(fPackageInfoView) 247 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0) 248 ; 249 250 fModel.AddListener(fModelListener); 251 252 // Restore settings 253 _RestoreUserName(settings); 254 _RestoreWindowFrame(settings); 255 256 fPackageInfoView->SetPackage(package); 257 258 _InitWorkerThreads(); 259 } 260 261 262 MainWindow::~MainWindow() 263 { 264 BPackageRoster().StopWatching(this); 265 266 fTerminating = true; 267 if (fModelWorker >= 0) 268 wait_for_thread(fModelWorker, NULL); 269 270 delete_sem(fPendingActionsSem); 271 if (fPendingActionsWorker >= 0) 272 wait_for_thread(fPendingActionsWorker, NULL); 273 274 delete_sem(fPackageToPopulateSem); 275 if (fPopulatePackageWorker >= 0) 276 wait_for_thread(fPopulatePackageWorker, NULL); 277 278 delete_sem(fNewPackagesToShowSem); 279 delete_sem(fShowPackagesAcknowledgeSem); 280 if (fShowPackagesWorker >= 0) 281 wait_for_thread(fShowPackagesWorker, NULL); 282 283 if (fScreenshotWindow != NULL && fScreenshotWindow->Lock()) 284 fScreenshotWindow->Quit(); 285 } 286 287 288 bool 289 MainWindow::QuitRequested() 290 { 291 BMessage settings; 292 StoreSettings(settings); 293 294 BMessage message(MSG_MAIN_WINDOW_CLOSED); 295 message.AddMessage("window settings", &settings); 296 297 be_app->PostMessage(&message); 298 299 return true; 300 } 301 302 303 void 304 MainWindow::MessageReceived(BMessage* message) 305 { 306 switch (message->what) { 307 case MSG_MODEL_WORKER_DONE: 308 { 309 fModelWorker = B_BAD_THREAD_ID; 310 _AdoptModel(); 311 _UpdateAvailableRepositories(); 312 fWorkStatusView->SetIdle(); 313 break; 314 } 315 case B_SIMPLE_DATA: 316 case B_REFS_RECEIVED: 317 // TODO: ? 318 break; 319 320 case B_PACKAGE_UPDATE: 321 // TODO: We should do a more selective update depending on the 322 // "event", "location", and "change count" fields! 323 _StartRefreshWorker(false); 324 break; 325 326 case MSG_REFRESH_REPOS: 327 _StartRefreshWorker(true); 328 break; 329 330 case MSG_MANAGE_REPOS: 331 be_roster->Launch("application/x-vnd.Haiku-Repositories"); 332 break; 333 334 case MSG_SOFTWARE_UPDATER: 335 be_roster->Launch("application/x-vnd.haiku-softwareupdater"); 336 break; 337 338 case MSG_LOG_IN: 339 _OpenLoginWindow(BMessage()); 340 break; 341 342 case MSG_LOG_OUT: 343 fModel.SetUsername(""); 344 break; 345 346 case MSG_AUTHORIZATION_CHANGED: 347 _UpdateAuthorization(); 348 break; 349 350 case MSG_SHOW_FEATURED_PACKAGES: 351 { 352 BAutolock locker(fModel.Lock()); 353 fModel.SetShowFeaturedPackages( 354 !fModel.ShowFeaturedPackages()); 355 } 356 _AdoptModel(); 357 break; 358 359 case MSG_SHOW_AVAILABLE_PACKAGES: 360 { 361 BAutolock locker(fModel.Lock()); 362 fModel.SetShowAvailablePackages( 363 !fModel.ShowAvailablePackages()); 364 } 365 _AdoptModel(); 366 break; 367 368 case MSG_SHOW_INSTALLED_PACKAGES: 369 { 370 BAutolock locker(fModel.Lock()); 371 fModel.SetShowInstalledPackages( 372 !fModel.ShowInstalledPackages()); 373 } 374 _AdoptModel(); 375 break; 376 377 case MSG_SHOW_SOURCE_PACKAGES: 378 { 379 BAutolock locker(fModel.Lock()); 380 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages()); 381 } 382 _AdoptModel(); 383 break; 384 385 case MSG_SHOW_DEVELOP_PACKAGES: 386 { 387 BAutolock locker(fModel.Lock()); 388 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages()); 389 } 390 _AdoptModel(); 391 break; 392 393 // this may be triggered by, for example, a user rating being added 394 // or having been altered. 395 case MSG_SERVER_DATA_CHANGED: 396 { 397 BString name; 398 if (message->FindString("name", &name) == B_OK) { 399 BAutolock locker(fModel.Lock()); 400 if (fPackageInfoView->Package()->Name() == name) { 401 _PopulatePackageAsync(true); 402 } else { 403 if (Logger::IsDebugEnabled()) { 404 printf("pkg [%s] is updated on the server, but is " 405 "not selected so will not be updated.\n", 406 name.String()); 407 } 408 } 409 } 410 break; 411 } 412 413 case MSG_PACKAGE_SELECTED: 414 { 415 BString name; 416 if (message->FindString("name", &name) == B_OK) { 417 BAutolock locker(fModel.Lock()); 418 int count = fVisiblePackages.CountItems(); 419 for (int i = 0; i < count; i++) { 420 const PackageInfoRef& package 421 = fVisiblePackages.ItemAtFast(i); 422 if (package.Get() != NULL && package->Name() == name) { 423 locker.Unlock(); 424 _AdoptPackage(package); 425 break; 426 } 427 } 428 } else { 429 _ClearPackage(); 430 } 431 break; 432 } 433 434 case MSG_CATEGORY_SELECTED: 435 { 436 BString name; 437 if (message->FindString("name", &name) != B_OK) 438 name = ""; 439 { 440 BAutolock locker(fModel.Lock()); 441 fModel.SetCategory(name); 442 } 443 _AdoptModel(); 444 break; 445 } 446 447 case MSG_DEPOT_SELECTED: 448 { 449 BString name; 450 if (message->FindString("name", &name) != B_OK) 451 name = ""; 452 { 453 BAutolock locker(fModel.Lock()); 454 fModel.SetDepot(name); 455 } 456 _AdoptModel(); 457 _UpdateAvailableRepositories(); 458 break; 459 } 460 461 case MSG_SEARCH_TERMS_MODIFIED: 462 { 463 // TODO: Do this with a delay! 464 BString searchTerms; 465 if (message->FindString("search terms", &searchTerms) != B_OK) 466 searchTerms = ""; 467 { 468 BAutolock locker(fModel.Lock()); 469 fModel.SetSearchTerms(searchTerms); 470 } 471 _AdoptModel(); 472 break; 473 } 474 475 case MSG_PACKAGE_CHANGED: 476 { 477 PackageInfo* info; 478 if (message->FindPointer("package", (void**)&info) == B_OK) { 479 PackageInfoRef ref(info, true); 480 uint32 changes; 481 if (message->FindUInt32("changes", &changes) != B_OK) 482 changes = 0; 483 if ((changes & PKG_CHANGED_STATE) != 0) { 484 BAutolock locker(fModel.Lock()); 485 fModel.SetPackageState(ref, ref->State()); 486 } 487 488 // Asynchronous updates to the package information 489 // can mean that the package needs to be added or 490 // removed to/from the visible packages when the current 491 // filter parameters are re-evaluated on this package. 492 bool wasVisible = fVisiblePackages.Contains(ref); 493 bool isVisible; 494 { 495 BAutolock locker(fModel.Lock()); 496 // The package didn't get a chance yet to be in the 497 // visible package list 498 isVisible = fModel.MatchesFilter(ref); 499 500 // Transfer this single package, otherwise we miss 501 // other packages if they appear or disappear along 502 // with this one before receive a notification for 503 // them. 504 if (isVisible) { 505 fVisiblePackages.Add(ref); 506 } else if (wasVisible) 507 fVisiblePackages.Remove(ref); 508 } 509 510 if (wasVisible != isVisible) { 511 if (!isVisible) { 512 fPackageListView->RemovePackage(ref); 513 fFeaturedPackagesView->RemovePackage(ref); 514 } else { 515 fPackageListView->AddPackage(ref); 516 if (ref->IsProminent()) 517 fFeaturedPackagesView->AddPackage(ref); 518 } 519 } 520 521 if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0) 522 fWorkStatusView->PackageStatusChanged(ref); 523 } 524 break; 525 } 526 527 case MSG_RATE_PACKAGE: 528 _RatePackage(); 529 break; 530 531 case MSG_SHOW_SCREENSHOT: 532 _ShowScreenshot(); 533 break; 534 535 case MSG_PACKAGE_WORKER_BUSY: 536 { 537 BString reason; 538 status_t status = message->FindString("reason", &reason); 539 if (status != B_OK) 540 break; 541 if (!fSinglePackageMode) 542 fWorkStatusView->SetBusy(reason); 543 break; 544 } 545 546 case MSG_PACKAGE_WORKER_IDLE: 547 if (!fSinglePackageMode) 548 fWorkStatusView->SetIdle(); 549 break; 550 551 case MSG_ADD_VISIBLE_PACKAGES: 552 { 553 struct SemaphoreReleaser { 554 SemaphoreReleaser(sem_id semaphore) 555 : 556 fSemaphore(semaphore) 557 { } 558 559 ~SemaphoreReleaser() { release_sem(fSemaphore); } 560 561 sem_id fSemaphore; 562 }; 563 564 // Make sure acknowledge semaphore is released even on error, 565 // so the worker thread won't be blocked 566 SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem); 567 568 int32 numPackages = 0; 569 type_code unused; 570 status_t status = message->GetInfo("package_ref", &unused, 571 &numPackages); 572 if (status != B_OK || numPackages == 0) 573 break; 574 575 int32 listID = 0; 576 status = message->FindInt32("list_id", &listID); 577 if (status != B_OK) 578 break; 579 if (listID != atomic_get(&fPackagesToShowListID)) { 580 // list is outdated, ignore 581 break; 582 } 583 584 for (int i = 0; i < numPackages; i++) { 585 PackageInfo* packageRaw = NULL; 586 status = message->FindPointer("package_ref", i, 587 (void**)&packageRaw); 588 if (status != B_OK) 589 break; 590 PackageInfoRef package(packageRaw, true); 591 592 fPackageListView->AddPackage(package); 593 if (package->IsProminent()) 594 fFeaturedPackagesView->AddPackage(package); 595 } 596 break; 597 } 598 599 case MSG_UPDATE_SELECTED_PACKAGE: 600 { 601 const PackageInfoRef& selectedPackage = fPackageInfoView->Package(); 602 fFeaturedPackagesView->SelectPackage(selectedPackage, true); 603 fPackageListView->SelectPackage(selectedPackage); 604 605 AutoLocker<BLocker> modelLocker(fModel.Lock()); 606 if (!fVisiblePackages.Contains(fPackageInfoView->Package())) 607 fPackageInfoView->Clear(); 608 break; 609 } 610 611 default: 612 BWindow::MessageReceived(message); 613 break; 614 } 615 } 616 617 618 void 619 MainWindow::StoreSettings(BMessage& settings) const 620 { 621 settings.AddRect(_WindowFrameName(), Frame()); 622 if (!fSinglePackageMode) { 623 settings.AddRect("window frame", Frame()); 624 625 BMessage columnSettings; 626 fPackageListView->SaveState(&columnSettings); 627 628 settings.AddMessage("column settings", &columnSettings); 629 630 settings.AddBool("show featured packages", 631 fModel.ShowFeaturedPackages()); 632 settings.AddBool("show available packages", 633 fModel.ShowAvailablePackages()); 634 settings.AddBool("show installed packages", 635 fModel.ShowInstalledPackages()); 636 settings.AddBool("show develop packages", fModel.ShowDevelopPackages()); 637 settings.AddBool("show source packages", fModel.ShowSourcePackages()); 638 } 639 640 settings.AddString("username", fModel.Username()); 641 } 642 643 644 void 645 MainWindow::PackageChanged(const PackageInfoEvent& event) 646 { 647 uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; 648 if ((event.Changes() & watchedChanges) != 0) { 649 PackageInfoRef ref(event.Package()); 650 BMessage message(MSG_PACKAGE_CHANGED); 651 message.AddPointer("package", ref.Get()); 652 message.AddUInt32("changes", event.Changes()); 653 ref.Detach(); 654 // reference needs to be released by MessageReceived(); 655 PostMessage(&message); 656 } 657 } 658 659 660 status_t 661 MainWindow::SchedulePackageActions(PackageActionList& list) 662 { 663 AutoLocker<BLocker> lock(&fPendingActionsLock); 664 for (int32 i = 0; i < list.CountItems(); i++) { 665 if (!fPendingActions.Add(list.ItemAtFast(i))) 666 return B_NO_MEMORY; 667 } 668 669 return release_sem_etc(fPendingActionsSem, list.CountItems(), 0); 670 } 671 672 673 Model* 674 MainWindow::GetModel() 675 { 676 return &fModel; 677 } 678 679 680 void 681 MainWindow::_BuildMenu(BMenuBar* menuBar) 682 { 683 BMenu* menu = new BMenu(B_TRANSLATE("Tools")); 684 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"), 685 new BMessage(MSG_REFRESH_REPOS))); 686 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories" 687 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS))); 688 menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates" 689 B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER))); 690 691 menuBar->AddItem(menu); 692 693 fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories")); 694 menuBar->AddItem(fRepositoryMenu); 695 696 menu = new BMenu(B_TRANSLATE("Show")); 697 698 fShowAvailablePackagesItem = new BMenuItem( 699 B_TRANSLATE("Available packages"), 700 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES)); 701 menu->AddItem(fShowAvailablePackagesItem); 702 703 fShowInstalledPackagesItem = new BMenuItem( 704 B_TRANSLATE("Installed packages"), 705 new BMessage(MSG_SHOW_INSTALLED_PACKAGES)); 706 menu->AddItem(fShowInstalledPackagesItem); 707 708 menu->AddSeparatorItem(); 709 710 fShowDevelopPackagesItem = new BMenuItem( 711 B_TRANSLATE("Develop packages"), 712 new BMessage(MSG_SHOW_DEVELOP_PACKAGES)); 713 menu->AddItem(fShowDevelopPackagesItem); 714 715 fShowSourcePackagesItem = new BMenuItem( 716 B_TRANSLATE("Source packages"), 717 new BMessage(MSG_SHOW_SOURCE_PACKAGES)); 718 menu->AddItem(fShowSourcePackagesItem); 719 720 menuBar->AddItem(menu); 721 } 722 723 724 void 725 MainWindow::_BuildUserMenu(BMenuBar* menuBar) 726 { 727 fUserMenu = new BMenu(B_TRANSLATE("Not logged in")); 728 729 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), 730 new BMessage(MSG_LOG_IN)); 731 fUserMenu->AddItem(fLogInItem); 732 733 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), 734 new BMessage(MSG_LOG_OUT)); 735 fUserMenu->AddItem(fLogOutItem); 736 737 menuBar->AddItem(fUserMenu); 738 } 739 740 741 void 742 MainWindow::_RestoreUserName(const BMessage& settings) 743 { 744 BString username; 745 if (settings.FindString("username", &username) == B_OK 746 && username.Length() > 0) { 747 fModel.SetUsername(username); 748 } 749 } 750 751 752 const char* 753 MainWindow::_WindowFrameName() const 754 { 755 if (fSinglePackageMode) 756 return "small window frame"; 757 758 return "window frame"; 759 } 760 761 762 void 763 MainWindow::_RestoreWindowFrame(const BMessage& settings) 764 { 765 BRect frame = Frame(); 766 767 BRect windowFrame; 768 bool fromSettings = false; 769 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) { 770 frame = windowFrame; 771 fromSettings = true; 772 } else if (!fSinglePackageMode) { 773 // Resize to occupy a certain screen size 774 BRect screenFrame = BScreen(this).Frame(); 775 float width = frame.Width(); 776 float height = frame.Height(); 777 if (width < screenFrame.Width() * .666f 778 && height < screenFrame.Height() * .666f) { 779 frame.bottom = frame.top + screenFrame.Height() * .666f; 780 frame.right = frame.left 781 + std::min(screenFrame.Width() * .666f, height * 7 / 5); 782 } 783 } 784 785 MoveTo(frame.LeftTop()); 786 ResizeTo(frame.Width(), frame.Height()); 787 788 if (fromSettings) 789 MoveOnScreen(); 790 else 791 CenterOnScreen(); 792 } 793 794 795 void 796 MainWindow::_InitWorkerThreads() 797 { 798 fPendingActionsSem = create_sem(0, "PendingPackageActions"); 799 if (fPendingActionsSem >= 0) { 800 fPendingActionsWorker = spawn_thread(&_PackageActionWorker, 801 "Planet Express", B_NORMAL_PRIORITY, this); 802 if (fPendingActionsWorker >= 0) 803 resume_thread(fPendingActionsWorker); 804 } else 805 fPendingActionsWorker = -1; 806 807 fPackageToPopulateSem = create_sem(0, "PopulatePackage"); 808 if (fPackageToPopulateSem >= 0) { 809 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker, 810 "Package Populator", B_NORMAL_PRIORITY, this); 811 if (fPopulatePackageWorker >= 0) 812 resume_thread(fPopulatePackageWorker); 813 } else 814 fPopulatePackageWorker = -1; 815 816 fNewPackagesToShowSem = create_sem(0, "ShowPackages"); 817 fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck"); 818 if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) { 819 fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker, 820 "Good news everyone", B_NORMAL_PRIORITY, this); 821 if (fShowPackagesWorker >= 0) 822 resume_thread(fShowPackagesWorker); 823 } else 824 fShowPackagesWorker = -1; 825 } 826 827 828 void 829 MainWindow::_AdoptModel() 830 { 831 fVisiblePackages = fModel.CreatePackageList(); 832 833 { 834 AutoLocker<BLocker> modelLocker(fModel.Lock()); 835 AutoLocker<BLocker> listLocker(fPackagesToShowListLock); 836 fPackagesToShowList = fVisiblePackages; 837 atomic_add(&fPackagesToShowListID, 1); 838 } 839 840 fFeaturedPackagesView->Clear(); 841 fPackageListView->Clear(); 842 843 release_sem(fNewPackagesToShowSem); 844 845 BAutolock locker(fModel.Lock()); 846 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages()); 847 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages()); 848 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages()); 849 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages()); 850 851 if (fModel.ShowFeaturedPackages()) 852 fListLayout->SetVisibleItem((int32)0); 853 else 854 fListLayout->SetVisibleItem((int32)1); 855 856 fFilterView->AdoptModel(fModel); 857 } 858 859 860 void 861 MainWindow::_AdoptPackage(const PackageInfoRef& package) 862 { 863 { 864 BAutolock locker(fModel.Lock()); 865 fPackageInfoView->SetPackage(package); 866 867 if (fFeaturedPackagesView != NULL) 868 fFeaturedPackagesView->SelectPackage(package); 869 if (fPackageListView != NULL) 870 fPackageListView->SelectPackage(package); 871 } 872 873 _PopulatePackageAsync(false); 874 } 875 876 877 void 878 MainWindow::_ClearPackage() 879 { 880 fPackageInfoView->Clear(); 881 } 882 883 884 void 885 MainWindow::_RefreshRepositories(bool force) 886 { 887 if (fSinglePackageMode) 888 return; 889 890 BPackageRoster roster; 891 BStringList repositoryNames; 892 893 status_t result = roster.GetRepositoryNames(repositoryNames); 894 if (result != B_OK) 895 return; 896 897 DecisionProvider decisionProvider; 898 JobStateListener listener; 899 BContext context(decisionProvider, listener); 900 901 BRepositoryCache cache; 902 for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) { 903 const BString& repoName = repositoryNames.StringAt(i); 904 BRepositoryConfig repoConfig; 905 result = roster.GetRepositoryConfig(repoName, &repoConfig); 906 if (result != B_OK) { 907 // TODO: notify user 908 continue; 909 } 910 911 if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) { 912 try { 913 BRefreshRepositoryRequest refreshRequest(context, repoConfig); 914 915 result = refreshRequest.Process(); 916 } catch (BFatalErrorException ex) { 917 BString message(B_TRANSLATE("An error occurred while " 918 "refreshing the repository: %error% (%details%)")); 919 message.ReplaceFirst("%error%", ex.Message()); 920 message.ReplaceFirst("%details%", ex.Details()); 921 _NotifyUser("Error", message.String()); 922 } catch (BException ex) { 923 BString message(B_TRANSLATE("An error occurred while " 924 "refreshing the repository: %error%")); 925 message.ReplaceFirst("%error%", ex.Message()); 926 _NotifyUser("Error", message.String()); 927 } 928 } 929 } 930 } 931 932 933 void 934 MainWindow::_RefreshPackageList(bool force) 935 { 936 if (fSinglePackageMode) 937 return; 938 939 if (Logger::IsDebugEnabled()) 940 printf("will refresh the package list\n"); 941 942 BPackageRoster roster; 943 BStringList repositoryNames; 944 945 status_t result = roster.GetRepositoryNames(repositoryNames); 946 if (result != B_OK) 947 return; 948 949 std::vector<DepotInfo> depots(repositoryNames.CountStrings()); 950 for (int32 i = 0; i < repositoryNames.CountStrings(); i++) { 951 const BString& repoName = repositoryNames.StringAt(i); 952 DepotInfo depotInfo = DepotInfo(repoName); 953 954 BRepositoryConfig repoConfig; 955 status_t getRepositoryConfigStatus = roster.GetRepositoryConfig( 956 repoName, &repoConfig); 957 958 if (getRepositoryConfigStatus == B_OK) { 959 depotInfo.SetBaseURL(repoConfig.BaseURL()); 960 depotInfo.SetURL(repoConfig.URL()); 961 962 if (Logger::IsDebugEnabled()) { 963 printf("local repository [%s] info;\n" 964 " * base url [%s]\n" 965 " * url [%s]\n", 966 repoName.String(), repoConfig.BaseURL().String(), 967 repoConfig.URL().String()); 968 } 969 } else { 970 printf("unable to obtain the repository config for local " 971 "repository '%s'; %s\n", 972 repoName.String(), strerror(getRepositoryConfigStatus)); 973 } 974 975 depots[i] = depotInfo; 976 } 977 978 PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME); 979 try { 980 manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES 981 | PackageManager::B_ADD_REMOTE_REPOSITORIES); 982 } catch (BException ex) { 983 BString message(B_TRANSLATE("An error occurred while " 984 "initializing the package manager: %message%")); 985 message.ReplaceFirst("%message%", ex.Message()); 986 _NotifyUser("Error", message.String()); 987 return; 988 } 989 990 BObjectList<BSolverPackage> packages; 991 result = manager.Solver()->FindPackages("", 992 BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME 993 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION 994 | BSolver::B_FIND_IN_PROVIDES, 995 packages); 996 if (result != B_OK) { 997 BString message(B_TRANSLATE("An error occurred while " 998 "obtaining the package list: %message%")); 999 message.ReplaceFirst("%message%", strerror(result)); 1000 _NotifyUser("Error", message.String()); 1001 return; 1002 } 1003 1004 if (packages.IsEmpty()) 1005 return; 1006 1007 PackageInfoMap foundPackages; 1008 // if a given package is installed locally, we will potentially 1009 // get back multiple entries, one for each local installation 1010 // location, and one for each remote repository the package 1011 // is available in. The above map is used to ensure that in such 1012 // cases we consolidate the information, rather than displaying 1013 // duplicates 1014 PackageInfoMap remotePackages; 1015 // any package that we find in a remote repository goes in this map. 1016 // this is later used to discern which packages came from a local 1017 // installation only, as those must be handled a bit differently 1018 // upon uninstallation, since we'd no longer be able to pull them 1019 // down remotely. 1020 BStringList systemFlaggedPackages; 1021 // any packages flagged as a system package are added to this list. 1022 // such packages cannot be uninstalled, nor can any of their deps. 1023 PackageInfoMap systemInstalledPackages; 1024 // any packages installed in system are added to this list. 1025 // This is later used for dependency resolution of the actual 1026 // system packages in order to compute the list of protected 1027 // dependencies indicated above. 1028 1029 for (int32 i = 0; i < packages.CountItems(); i++) { 1030 BSolverPackage* package = packages.ItemAt(i); 1031 const BPackageInfo& repoPackageInfo = package->Info(); 1032 const BString repositoryName = package->Repository()->Name(); 1033 PackageInfoRef modelInfo; 1034 PackageInfoMap::iterator it = foundPackages.find( 1035 repoPackageInfo.Name()); 1036 if (it != foundPackages.end()) 1037 modelInfo.SetTo(it->second); 1038 else { 1039 // Add new package info 1040 modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo), 1041 true); 1042 1043 if (modelInfo.Get() == NULL) 1044 return; 1045 1046 foundPackages[repoPackageInfo.Name()] = modelInfo; 1047 } 1048 1049 // The package list here considers those packages that are installed 1050 // in the system as well as those that exist in remote repositories. 1051 // It is better if the 'depot name' is from the remote repository 1052 // because then it will be possible to perform a rating on it later. 1053 1054 if (modelInfo->DepotName().IsEmpty() 1055 || modelInfo->DepotName() == REPOSITORY_NAME_SYSTEM 1056 || modelInfo->DepotName() == REPOSITORY_NAME_INSTALLED) { 1057 modelInfo->SetDepotName(repositoryName); 1058 } 1059 1060 modelInfo->AddListener(this); 1061 1062 BSolverRepository* repository = package->Repository(); 1063 BPackageManager::RemoteRepository* remoteRepository = 1064 dynamic_cast<BPackageManager::RemoteRepository*>(repository); 1065 1066 if (remoteRepository != NULL) { 1067 1068 std::vector<DepotInfo>::iterator it; 1069 1070 for (it = depots.begin(); it != depots.end(); it++) { 1071 if (RepositoryUrlUtils::EqualsOnUrlOrBaseUrl( 1072 it->URL(), remoteRepository->Config().URL(), 1073 it->BaseURL(), remoteRepository->Config().BaseURL())) { 1074 break; 1075 } 1076 } 1077 1078 if (it == depots.end()) { 1079 if (Logger::IsDebugEnabled()) { 1080 printf("pkg [%s] repository [%s] not recognized" 1081 " --> ignored\n", 1082 modelInfo->Name().String(), repositoryName.String()); 1083 } 1084 } else { 1085 it->AddPackage(modelInfo); 1086 1087 if (Logger::IsTraceEnabled()) { 1088 printf("pkg [%s] assigned to [%s]\n", 1089 modelInfo->Name().String(), repositoryName.String()); 1090 } 1091 } 1092 1093 remotePackages[modelInfo->Name()] = modelInfo; 1094 } else { 1095 if (repository == static_cast<const BSolverRepository*>( 1096 manager.SystemRepository())) { 1097 modelInfo->AddInstallationLocation( 1098 B_PACKAGE_INSTALLATION_LOCATION_SYSTEM); 1099 if (!modelInfo->IsSystemPackage()) { 1100 systemInstalledPackages[repoPackageInfo.FileName()] 1101 = modelInfo; 1102 } 1103 } else if (repository == static_cast<const BSolverRepository*>( 1104 manager.HomeRepository())) { 1105 modelInfo->AddInstallationLocation( 1106 B_PACKAGE_INSTALLATION_LOCATION_HOME); 1107 } 1108 } 1109 1110 if (modelInfo->IsSystemPackage()) 1111 systemFlaggedPackages.Add(repoPackageInfo.FileName()); 1112 } 1113 1114 bool wasEmpty = fModel.Depots().IsEmpty(); 1115 if (force || wasEmpty) 1116 fBulkLoadStateMachine.Stop(); 1117 1118 BAutolock lock(fModel.Lock()); 1119 1120 if (force) 1121 fModel.Clear(); 1122 1123 // filter remote packages from the found list 1124 // any packages remaining will be locally installed packages 1125 // that weren't acquired from a repository 1126 for (PackageInfoMap::iterator it = remotePackages.begin(); 1127 it != remotePackages.end(); it++) { 1128 foundPackages.erase(it->first); 1129 } 1130 1131 if (!foundPackages.empty()) { 1132 BString repoName = B_TRANSLATE("Local"); 1133 depots.push_back(DepotInfo(repoName)); 1134 1135 for (PackageInfoMap::iterator it = foundPackages.begin(); 1136 it != foundPackages.end(); ++it) { 1137 depots.back().AddPackage(it->second); 1138 } 1139 } 1140 1141 { 1142 std::vector<DepotInfo>::iterator it; 1143 1144 for (it = depots.begin(); it != depots.end(); it++) { 1145 if (fModel.HasDepot(it->Name())) 1146 fModel.SyncDepot(*it); 1147 else 1148 fModel.AddDepot(*it); 1149 } 1150 } 1151 1152 // start retrieving package icons and average ratings 1153 if (force || wasEmpty) { 1154 fBulkLoadStateMachine.Start(); 1155 } 1156 1157 // compute the OS package dependencies 1158 try { 1159 // create the solver 1160 BSolver* solver; 1161 status_t error = BSolver::Create(solver); 1162 if (error != B_OK) 1163 throw BFatalErrorException(error, "Failed to create solver."); 1164 1165 ObjectDeleter<BSolver> solverDeleter(solver); 1166 BPath systemPath; 1167 error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath); 1168 if (error != B_OK) { 1169 throw BFatalErrorException(error, 1170 "Unable to retrieve system packages directory."); 1171 } 1172 1173 // add the "installed" repository with the given packages 1174 BSolverRepository installedRepository; 1175 { 1176 BRepositoryBuilder installedRepositoryBuilder(installedRepository, 1177 REPOSITORY_NAME_INSTALLED); 1178 for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) { 1179 BPath packagePath(systemPath); 1180 packagePath.Append(systemFlaggedPackages.StringAt(i)); 1181 installedRepositoryBuilder.AddPackage(packagePath.Path()); 1182 } 1183 installedRepositoryBuilder.AddToSolver(solver, true); 1184 } 1185 1186 // add system repository 1187 BSolverRepository systemRepository; 1188 { 1189 BRepositoryBuilder systemRepositoryBuilder(systemRepository, 1190 REPOSITORY_NAME_SYSTEM); 1191 for (PackageInfoMap::iterator it = systemInstalledPackages.begin(); 1192 it != systemInstalledPackages.end(); it++) { 1193 BPath packagePath(systemPath); 1194 packagePath.Append(it->first); 1195 systemRepositoryBuilder.AddPackage(packagePath.Path()); 1196 } 1197 systemRepositoryBuilder.AddToSolver(solver, false); 1198 } 1199 1200 // solve 1201 error = solver->VerifyInstallation(); 1202 if (error != B_OK) { 1203 throw BFatalErrorException(error, "Failed to compute packages to " 1204 "install."); 1205 } 1206 1207 BSolverResult solverResult; 1208 error = solver->GetResult(solverResult); 1209 if (error != B_OK) { 1210 throw BFatalErrorException(error, "Failed to retrieve system " 1211 "package dependency list."); 1212 } 1213 1214 for (int32 i = 0; const BSolverResultElement* element 1215 = solverResult.ElementAt(i); i++) { 1216 BSolverPackage* package = element->Package(); 1217 if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) { 1218 PackageInfoMap::iterator it = systemInstalledPackages.find( 1219 package->Info().FileName()); 1220 if (it != systemInstalledPackages.end()) 1221 it->second->SetSystemDependency(true); 1222 } 1223 } 1224 } catch (BFatalErrorException ex) { 1225 printf("Fatal exception occurred while resolving system dependencies: " 1226 "%s, details: %s\n", strerror(ex.Error()), ex.Details().String()); 1227 } catch (BNothingToDoException) { 1228 // do nothing 1229 } catch (BException ex) { 1230 printf("Exception occurred while resolving system dependencies: %s\n", 1231 ex.Message().String()); 1232 } catch (...) { 1233 printf("Unknown exception occurred while resolving system " 1234 "dependencies.\n"); 1235 } 1236 1237 if (Logger::IsDebugEnabled()) 1238 printf("did refresh the package list\n"); 1239 } 1240 1241 1242 void 1243 MainWindow::_StartRefreshWorker(bool force) 1244 { 1245 if (fModelWorker != B_BAD_THREAD_ID) 1246 return; 1247 1248 RefreshWorkerParameters* parameters = new(std::nothrow) 1249 RefreshWorkerParameters(this, force); 1250 if (parameters == NULL) 1251 return; 1252 1253 fWorkStatusView->SetBusy(B_TRANSLATE("Refreshing" B_UTF8_ELLIPSIS)); 1254 1255 ObjectDeleter<RefreshWorkerParameters> deleter(parameters); 1256 fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader", 1257 B_LOW_PRIORITY, parameters); 1258 1259 if (fModelWorker > 0) { 1260 deleter.Detach(); 1261 resume_thread(fModelWorker); 1262 } 1263 } 1264 1265 1266 status_t 1267 MainWindow::_RefreshModelThreadWorker(void* arg) 1268 { 1269 RefreshWorkerParameters* parameters 1270 = reinterpret_cast<RefreshWorkerParameters*>(arg); 1271 MainWindow* mainWindow = parameters->window; 1272 ObjectDeleter<RefreshWorkerParameters> deleter(parameters); 1273 1274 BMessenger messenger(mainWindow); 1275 1276 mainWindow->_RefreshRepositories(parameters->forceRefresh); 1277 1278 if (mainWindow->fTerminating) 1279 return B_OK; 1280 1281 mainWindow->_RefreshPackageList(parameters->forceRefresh); 1282 1283 messenger.SendMessage(MSG_MODEL_WORKER_DONE); 1284 1285 return B_OK; 1286 } 1287 1288 1289 status_t 1290 MainWindow::_PackageActionWorker(void* arg) 1291 { 1292 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1293 1294 while (acquire_sem(window->fPendingActionsSem) == B_OK) { 1295 PackageActionRef ref; 1296 { 1297 AutoLocker<BLocker> lock(&window->fPendingActionsLock); 1298 ref = window->fPendingActions.ItemAt(0); 1299 if (ref.Get() == NULL) 1300 break; 1301 window->fPendingActions.Remove(0); 1302 } 1303 1304 BMessenger messenger(window); 1305 BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY); 1306 BString text(ref->Label()); 1307 text << B_UTF8_ELLIPSIS; 1308 busyMessage.AddString("reason", text); 1309 1310 messenger.SendMessage(&busyMessage); 1311 ref->Perform(); 1312 messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE); 1313 } 1314 1315 return 0; 1316 } 1317 1318 1319 /*! This method will cause the package to have its data refreshed from 1320 the server application. The refresh happens in the background; this method 1321 is asynchronous. 1322 */ 1323 1324 void 1325 MainWindow::_PopulatePackageAsync(bool forcePopulate) 1326 { 1327 // Trigger asynchronous package population from the web-app 1328 { 1329 AutoLocker<BLocker> lock(&fPackageToPopulateLock); 1330 fPackageToPopulate = fPackageInfoView->Package(); 1331 fForcePopulatePackage = forcePopulate; 1332 } 1333 release_sem_etc(fPackageToPopulateSem, 1, 0); 1334 1335 if (Logger::IsDebugEnabled()) { 1336 printf("pkg [%s] will be updated from the server.\n", 1337 fPackageToPopulate->Name().String()); 1338 } 1339 } 1340 1341 1342 /*! This method will run in the background. The thread will block until there 1343 is a package to be updated. When the thread unblocks, it will update the 1344 package with information from the server. 1345 */ 1346 1347 status_t 1348 MainWindow::_PopulatePackageWorker(void* arg) 1349 { 1350 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1351 1352 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { 1353 PackageInfoRef package; 1354 bool force; 1355 { 1356 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); 1357 package = window->fPackageToPopulate; 1358 force = window->fForcePopulatePackage; 1359 } 1360 1361 if (package.Get() != NULL) { 1362 uint32 populateFlags = Model::POPULATE_USER_RATINGS 1363 | Model::POPULATE_SCREEN_SHOTS 1364 | Model::POPULATE_CHANGELOG; 1365 1366 if (force) 1367 populateFlags |= Model::POPULATE_FORCE; 1368 1369 window->fModel.PopulatePackage(package, populateFlags); 1370 1371 if (Logger::IsDebugEnabled()) { 1372 printf("populating package [%s]\n", 1373 package->Name().String()); 1374 } 1375 } 1376 } 1377 1378 return 0; 1379 } 1380 1381 1382 /* static */ status_t 1383 MainWindow::_PackagesToShowWorker(void* arg) 1384 { 1385 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1386 1387 while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) { 1388 PackageList packageList; 1389 int32 listID = 0; 1390 { 1391 AutoLocker<BLocker> lock(&window->fPackagesToShowListLock); 1392 packageList = window->fPackagesToShowList; 1393 listID = atomic_get(&window->fPackagesToShowListID); 1394 window->fPackagesToShowList.Clear(); 1395 } 1396 1397 // Add packages to list views in batches of kPackagesPerUpdate so we 1398 // don't block the window thread for long with each iteration 1399 enum { 1400 kPackagesPerUpdate = 20 1401 }; 1402 uint32 packagesInMessage = 0; 1403 BMessage message(MSG_ADD_VISIBLE_PACKAGES); 1404 BMessenger messenger(window); 1405 bool listIsOutdated = false; 1406 1407 for (int i = 0; i < packageList.CountItems(); i++) { 1408 const PackageInfoRef& package = packageList.ItemAtFast(i); 1409 1410 if (packagesInMessage >= kPackagesPerUpdate) { 1411 if (listID != atomic_get(&window->fPackagesToShowListID)) { 1412 // The model was changed again in the meantime, and thus 1413 // our package list isn't current anymore. Send no further 1414 // messags based on this list and go back to start. 1415 listIsOutdated = true; 1416 break; 1417 } 1418 1419 message.AddInt32("list_id", listID); 1420 messenger.SendMessage(&message); 1421 message.MakeEmpty(); 1422 packagesInMessage = 0; 1423 1424 // Don't spam the window's message queue, which would make it 1425 // unresponsive (i.e. allows UI messages to get in between our 1426 // messages). When it has processed the message we just sent, 1427 // it will let us know by releasing the semaphore. 1428 acquire_sem(window->fShowPackagesAcknowledgeSem); 1429 } 1430 package->AcquireReference(); 1431 message.AddPointer("package_ref", package.Get()); 1432 packagesInMessage++; 1433 } 1434 1435 if (listIsOutdated) 1436 continue; 1437 1438 // Send remaining package infos, if any, which didn't make it into 1439 // the last message (count < kPackagesPerUpdate) 1440 if (packagesInMessage > 0) { 1441 message.AddInt32("list_id", listID); 1442 messenger.SendMessage(&message); 1443 acquire_sem(window->fShowPackagesAcknowledgeSem); 1444 } 1445 1446 // Update selected package in list views 1447 messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE); 1448 } 1449 1450 return 0; 1451 } 1452 1453 1454 void 1455 MainWindow::_NotifyUser(const char* title, const char* message) 1456 { 1457 BAlert* alert = new(std::nothrow) BAlert(title, message, 1458 B_TRANSLATE("Close")); 1459 1460 if (alert != NULL) 1461 alert->Go(); 1462 } 1463 1464 1465 void 1466 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage) 1467 { 1468 UserLoginWindow* window = new UserLoginWindow(this, 1469 BRect(0, 0, 500, 400), fModel); 1470 1471 if (onSuccessMessage.what != 0) 1472 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage); 1473 1474 window->Show(); 1475 } 1476 1477 1478 void 1479 MainWindow::_UpdateAuthorization() 1480 { 1481 BString username(fModel.Username()); 1482 bool hasUser = !username.IsEmpty(); 1483 1484 if (fLogOutItem != NULL) 1485 fLogOutItem->SetEnabled(hasUser); 1486 if (fLogInItem != NULL) { 1487 if (hasUser) 1488 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS)); 1489 else 1490 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS)); 1491 } 1492 1493 if (fUserMenu != NULL) { 1494 BString label; 1495 if (username.Length() == 0) { 1496 label = B_TRANSLATE("Not logged in"); 1497 } else { 1498 label = B_TRANSLATE("Logged in as %User%"); 1499 label.ReplaceAll("%User%", username); 1500 } 1501 fUserMenu->Superitem()->SetLabel(label); 1502 } 1503 } 1504 1505 1506 void 1507 MainWindow::_UpdateAvailableRepositories() 1508 { 1509 fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true); 1510 1511 fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"), 1512 new BMessage(MSG_DEPOT_SELECTED))); 1513 1514 fRepositoryMenu->AddItem(new BSeparatorItem()); 1515 1516 bool foundSelectedDepot = false; 1517 const DepotList& depots = fModel.Depots(); 1518 for (int i = 0; i < depots.CountItems(); i++) { 1519 const DepotInfo& depot = depots.ItemAtFast(i); 1520 1521 if (depot.Name().Length() != 0) { 1522 BMessage* message = new BMessage(MSG_DEPOT_SELECTED); 1523 message->AddString("name", depot.Name()); 1524 BMenuItem* item = new BMenuItem(depot.Name(), message); 1525 fRepositoryMenu->AddItem(item); 1526 1527 if (depot.Name() == fModel.Depot()) { 1528 item->SetMarked(true); 1529 foundSelectedDepot = true; 1530 } 1531 } 1532 } 1533 1534 if (!foundSelectedDepot) 1535 fRepositoryMenu->ItemAt(0)->SetMarked(true); 1536 } 1537 1538 1539 bool 1540 MainWindow::_SelectedPackageHasWebAppRepositoryCode() 1541 { 1542 const PackageInfoRef& package = fPackageInfoView->Package(); 1543 const BString depotName = package->DepotName(); 1544 1545 if (depotName.IsEmpty()) { 1546 if (Logger::IsDebugEnabled()) { 1547 printf("the package [%s] has no depot name\n", 1548 package->Name().String()); 1549 } 1550 } else { 1551 const DepotInfo* depot = fModel.DepotForName(depotName); 1552 1553 if (depot == NULL) { 1554 printf("the depot [%s] was not able to be found\n", 1555 depotName.String()); 1556 } else { 1557 BString repositoryCode = depot->WebAppRepositoryCode(); 1558 1559 if (repositoryCode.IsEmpty()) { 1560 printf("the depot [%s] has no web app repository code\n", 1561 depotName.String()); 1562 } else { 1563 return true; 1564 } 1565 } 1566 } 1567 1568 return false; 1569 } 1570 1571 1572 void 1573 MainWindow::_RatePackage() 1574 { 1575 if (!_SelectedPackageHasWebAppRepositoryCode()) { 1576 BAlert* alert = new(std::nothrow) BAlert( 1577 B_TRANSLATE("Rating not possible"), 1578 B_TRANSLATE("This package doesn't seem to be on the HaikuDepot " 1579 "Server, so it's not possible to create a new rating " 1580 "or edit an existing rating."), 1581 B_TRANSLATE("OK")); 1582 alert->Go(); 1583 return; 1584 } 1585 1586 if (fModel.Username().IsEmpty()) { 1587 BAlert* alert = new(std::nothrow) BAlert( 1588 B_TRANSLATE("Not logged in"), 1589 B_TRANSLATE("You need to be logged into an account before you " 1590 "can rate packages."), 1591 B_TRANSLATE("Cancel"), 1592 B_TRANSLATE("Login or Create account")); 1593 1594 if (alert == NULL) 1595 return; 1596 1597 int32 choice = alert->Go(); 1598 if (choice == 1) 1599 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE)); 1600 return; 1601 } 1602 1603 // TODO: Allow only one RatePackageWindow 1604 // TODO: Mechanism for remembering the window frame 1605 RatePackageWindow* window = new RatePackageWindow(this, 1606 BRect(0, 0, 500, 400), fModel); 1607 window->SetPackage(fPackageInfoView->Package()); 1608 window->Show(); 1609 } 1610 1611 1612 void 1613 MainWindow::_ShowScreenshot() 1614 { 1615 // TODO: Mechanism for remembering the window frame 1616 if (fScreenshotWindow == NULL) 1617 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400)); 1618 1619 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK) 1620 return; 1621 1622 fScreenshotWindow->SetPackage(fPackageInfoView->Package()); 1623 1624 if (fScreenshotWindow->IsHidden()) 1625 fScreenshotWindow->Show(); 1626 else 1627 fScreenshotWindow->Activate(); 1628 1629 fScreenshotWindow->Unlock(); 1630 } 1631