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, Andrew Lindesay <apl@lindesay.co.nz>. 7 * All rights reserved. Distributed under the terms of the MIT License. 8 */ 9 10 11 #include "MainWindow.h" 12 13 #include <map> 14 15 #include <stdio.h> 16 17 #include <Alert.h> 18 #include <Autolock.h> 19 #include <Application.h> 20 #include <Button.h> 21 #include <Catalog.h> 22 #include <CardLayout.h> 23 #include <LayoutBuilder.h> 24 #include <MenuBar.h> 25 #include <MenuItem.h> 26 #include <Messenger.h> 27 #include <Screen.h> 28 #include <ScrollView.h> 29 #include <StringList.h> 30 #include <StringView.h> 31 #include <TabView.h> 32 33 #include <package/Context.h> 34 #include <package/manager/Exceptions.h> 35 #include <package/manager/RepositoryBuilder.h> 36 #include <package/RefreshRepositoryRequest.h> 37 #include <package/PackageRoster.h> 38 #include "package/RepositoryCache.h" 39 #include <package/solver/SolverPackage.h> 40 #include <package/solver/SolverProblem.h> 41 #include <package/solver/SolverProblemSolution.h> 42 #include <package/solver/SolverRepository.h> 43 #include <package/solver/SolverResult.h> 44 45 #include "AutoDeleter.h" 46 #include "AutoLocker.h" 47 #include "DecisionProvider.h" 48 #include "FeaturedPackagesView.h" 49 #include "FilterView.h" 50 #include "JobStateListener.h" 51 #include "PackageInfoView.h" 52 #include "PackageListView.h" 53 #include "PackageManager.h" 54 #include "RatePackageWindow.h" 55 #include "support.h" 56 #include "ScreenshotWindow.h" 57 #include "UserLoginWindow.h" 58 59 60 #undef B_TRANSLATION_CONTEXT 61 #define B_TRANSLATION_CONTEXT "MainWindow" 62 63 64 enum { 65 MSG_MODEL_WORKER_DONE = 'mmwd', 66 MSG_REFRESH_DEPOTS = 'mrdp', 67 MSG_LOG_IN = 'lgin', 68 MSG_LOG_OUT = 'lgot', 69 MSG_AUTHORIZATION_CHANGED = 'athc', 70 MSG_PACKAGE_CHANGED = 'pchd', 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 typedef std::map<BString, DepotInfo> DepotInfoMap; 86 87 88 struct RefreshWorkerParameters { 89 MainWindow* window; 90 bool forceRefresh; 91 92 RefreshWorkerParameters(MainWindow* window, bool forceRefresh) 93 : 94 window(window), 95 forceRefresh(forceRefresh) 96 { 97 } 98 }; 99 100 101 class MessageModelListener : public ModelListener { 102 public: 103 MessageModelListener(const BMessenger& messenger) 104 : 105 fMessenger(messenger) 106 { 107 } 108 109 virtual void AuthorizationChanged() 110 { 111 if (fMessenger.IsValid()) 112 fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED); 113 } 114 115 private: 116 BMessenger fMessenger; 117 }; 118 119 120 MainWindow::MainWindow(const BMessage& settings) 121 : 122 BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 123 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 124 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 125 fScreenshotWindow(NULL), 126 fUserMenu(NULL), 127 fLogInItem(NULL), 128 fLogOutItem(NULL), 129 fModelListener(new MessageModelListener(BMessenger(this)), true), 130 fTerminating(false), 131 fSinglePackageMode(false), 132 fModelWorker(B_BAD_THREAD_ID) 133 { 134 BMenuBar* menuBar = new BMenuBar(B_TRANSLATE("Main Menu")); 135 _BuildMenu(menuBar); 136 137 BMenuBar* userMenuBar = new BMenuBar(B_TRANSLATE("User Menu")); 138 _BuildUserMenu(userMenuBar); 139 set_small_font(userMenuBar); 140 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET, 141 menuBar->MaxSize().height)); 142 143 fFilterView = new FilterView(); 144 fFeaturedPackagesView = new FeaturedPackagesView(); 145 fPackageListView = new PackageListView(fModel.Lock()); 146 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 147 148 fSplitView = new BSplitView(B_VERTICAL, 5.0f); 149 150 BGroupView* featuredPackagesGroup = new BGroupView(B_VERTICAL); 151 BStringView* featuredPackagesTitle = new BStringView( 152 "featured packages title", B_TRANSLATE("Featured packages")); 153 BFont font(be_bold_font); 154 font.SetSize(font.Size() * 1.3f); 155 featuredPackagesTitle->SetFont(&font); 156 featuredPackagesGroup->SetExplicitMaxSize( 157 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 158 BLayoutBuilder::Group<>(featuredPackagesGroup) 159 .Add(featuredPackagesTitle) 160 .Add(fFeaturedPackagesView) 161 ; 162 163 BView* listArea = new BView("list area", 0); 164 fListLayout = new BCardLayout(); 165 listArea->SetLayout(fListLayout); 166 listArea->AddChild(featuredPackagesGroup); 167 listArea->AddChild(fPackageListView); 168 169 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) 170 .AddGroup(B_HORIZONTAL, 0.0f) 171 .Add(menuBar, 1.0f) 172 .Add(userMenuBar, 0.0f) 173 .End() 174 .Add(fFilterView) 175 .AddSplit(fSplitView) 176 .AddGroup(B_VERTICAL) 177 .Add(listArea) 178 .SetInsets( 179 B_USE_DEFAULT_SPACING, 0.0f, 180 B_USE_DEFAULT_SPACING, 0.0f) 181 .End() 182 .Add(fPackageInfoView) 183 .End() 184 ; 185 186 fSplitView->SetCollapsible(0, false); 187 fSplitView->SetCollapsible(1, false); 188 189 fModel.AddListener(fModelListener); 190 191 // Restore settings 192 BMessage columnSettings; 193 if (settings.FindMessage("column settings", &columnSettings) == B_OK) 194 fPackageListView->LoadState(&columnSettings); 195 196 bool showOption; 197 if (settings.FindBool("show featured packages", &showOption) == B_OK) 198 fModel.SetShowFeaturedPackages(showOption); 199 if (settings.FindBool("show available packages", &showOption) == B_OK) 200 fModel.SetShowAvailablePackages(showOption); 201 if (settings.FindBool("show installed packages", &showOption) == B_OK) 202 fModel.SetShowInstalledPackages(showOption); 203 if (settings.FindBool("show develop packages", &showOption) == B_OK) 204 fModel.SetShowDevelopPackages(showOption); 205 if (settings.FindBool("show source packages", &showOption) == B_OK) 206 fModel.SetShowSourcePackages(showOption); 207 208 if (fModel.ShowFeaturedPackages()) 209 fListLayout->SetVisibleItem((int32)0); 210 else 211 fListLayout->SetVisibleItem(1); 212 213 _RestoreUserName(settings); 214 _RestoreWindowFrame(settings); 215 216 // start worker threads 217 BPackageRoster().StartWatching(this, 218 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS); 219 220 _StartRefreshWorker(); 221 222 _InitWorkerThreads(); 223 } 224 225 226 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package) 227 : 228 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), 229 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 230 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 231 fScreenshotWindow(NULL), 232 fUserMenu(NULL), 233 fLogInItem(NULL), 234 fLogOutItem(NULL), 235 fModelListener(new MessageModelListener(BMessenger(this)), true), 236 fTerminating(false), 237 fSinglePackageMode(true), 238 fModelWorker(B_BAD_THREAD_ID) 239 { 240 fFilterView = new FilterView(); 241 fPackageListView = new PackageListView(fModel.Lock()); 242 fPackageInfoView = new PackageInfoView(fModel.Lock(), this); 243 244 BLayoutBuilder::Group<>(this, B_VERTICAL) 245 .Add(fPackageInfoView) 246 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0) 247 ; 248 249 fModel.AddListener(fModelListener); 250 251 // Restore settings 252 _RestoreUserName(settings); 253 _RestoreWindowFrame(settings); 254 255 fPackageInfoView->SetPackage(package); 256 257 _InitWorkerThreads(); 258 } 259 260 261 MainWindow::~MainWindow() 262 { 263 BPackageRoster().StopWatching(this); 264 265 fTerminating = true; 266 if (fModelWorker >= 0) 267 wait_for_thread(fModelWorker, NULL); 268 269 delete_sem(fPendingActionsSem); 270 if (fPendingActionsWorker >= 0) 271 wait_for_thread(fPendingActionsWorker, NULL); 272 273 delete_sem(fPackageToPopulateSem); 274 if (fPopulatePackageWorker >= 0) 275 wait_for_thread(fPopulatePackageWorker, 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("window settings", &settings); 290 291 be_app->PostMessage(&message); 292 293 return true; 294 } 295 296 297 void 298 MainWindow::MessageReceived(BMessage* message) 299 { 300 switch (message->what) { 301 case MSG_MODEL_WORKER_DONE: 302 { 303 fModelWorker = B_BAD_THREAD_ID; 304 _AdoptModel(); 305 fFilterView->AdoptModel(fModel); 306 break; 307 } 308 case B_SIMPLE_DATA: 309 case B_REFS_RECEIVED: 310 // TODO: ? 311 break; 312 313 case B_PACKAGE_UPDATE: 314 // TODO: We should do a more selective update depending on the 315 // "event", "location", and "change count" fields! 316 _StartRefreshWorker(false); 317 break; 318 319 case MSG_REFRESH_DEPOTS: 320 _StartRefreshWorker(true); 321 break; 322 323 case MSG_LOG_IN: 324 _OpenLoginWindow(BMessage()); 325 break; 326 327 case MSG_LOG_OUT: 328 fModel.SetUsername(""); 329 break; 330 331 case MSG_AUTHORIZATION_CHANGED: 332 _UpdateAuthorization(); 333 break; 334 335 case MSG_SHOW_FEATURED_PACKAGES: 336 { 337 BAutolock locker(fModel.Lock()); 338 fModel.SetShowFeaturedPackages( 339 !fModel.ShowFeaturedPackages()); 340 } 341 _AdoptModel(); 342 break; 343 344 case MSG_SHOW_AVAILABLE_PACKAGES: 345 { 346 BAutolock locker(fModel.Lock()); 347 fModel.SetShowAvailablePackages( 348 !fModel.ShowAvailablePackages()); 349 } 350 _AdoptModel(); 351 break; 352 353 case MSG_SHOW_INSTALLED_PACKAGES: 354 { 355 BAutolock locker(fModel.Lock()); 356 fModel.SetShowInstalledPackages( 357 !fModel.ShowInstalledPackages()); 358 } 359 _AdoptModel(); 360 break; 361 362 case MSG_SHOW_SOURCE_PACKAGES: 363 { 364 BAutolock locker(fModel.Lock()); 365 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages()); 366 } 367 _AdoptModel(); 368 break; 369 370 case MSG_SHOW_DEVELOP_PACKAGES: 371 { 372 BAutolock locker(fModel.Lock()); 373 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages()); 374 } 375 _AdoptModel(); 376 break; 377 378 case MSG_PACKAGE_SELECTED: 379 { 380 BString name; 381 if (message->FindString("name", &name) == B_OK) { 382 BAutolock locker(fModel.Lock()); 383 int count = fVisiblePackages.CountItems(); 384 for (int i = 0; i < count; i++) { 385 const PackageInfoRef& package 386 = fVisiblePackages.ItemAtFast(i); 387 if (package.Get() != NULL && package->Name() == name) { 388 locker.Unlock(); 389 _AdoptPackage(package); 390 break; 391 } 392 } 393 } else { 394 _ClearPackage(); 395 } 396 break; 397 } 398 399 case MSG_CATEGORY_SELECTED: 400 { 401 BString name; 402 if (message->FindString("name", &name) != B_OK) 403 name = ""; 404 { 405 BAutolock locker(fModel.Lock()); 406 fModel.SetCategory(name); 407 } 408 _AdoptModel(); 409 break; 410 } 411 412 case MSG_DEPOT_SELECTED: 413 { 414 BString name; 415 if (message->FindString("name", &name) != B_OK) 416 name = ""; 417 { 418 BAutolock locker(fModel.Lock()); 419 fModel.SetDepot(name); 420 } 421 _AdoptModel(); 422 break; 423 } 424 425 case MSG_SEARCH_TERMS_MODIFIED: 426 { 427 // TODO: Do this with a delay! 428 BString searchTerms; 429 if (message->FindString("search terms", &searchTerms) != B_OK) 430 searchTerms = ""; 431 { 432 BAutolock locker(fModel.Lock()); 433 fModel.SetSearchTerms(searchTerms); 434 } 435 _AdoptModel(); 436 break; 437 } 438 439 case MSG_PACKAGE_CHANGED: 440 { 441 PackageInfo* info; 442 if (message->FindPointer("package", (void**)&info) == B_OK) { 443 PackageInfoRef ref(info, true); 444 uint32 changes; 445 if (message->FindUInt32("changes", &changes) != B_OK) 446 changes = 0; 447 if ((changes & PKG_CHANGED_STATE) != 0) { 448 BAutolock locker(fModel.Lock()); 449 fModel.SetPackageState(ref, ref->State()); 450 } 451 452 // Asynchronous updates to the package information 453 // can mean that the package needs to be added or 454 // removed to/from the visible packages when the current 455 // filter parameters are re-evaluated on this package. 456 bool wasVisible = fVisiblePackages.Contains(ref); 457 bool isVisible; 458 { 459 BAutolock locker(fModel.Lock()); 460 // The package didn't get a chance yet to be in the 461 // visible package list 462 PackageList visiblePackages = fModel.CreatePackageList(); 463 isVisible = visiblePackages.Contains(ref); 464 465 // Transfer this single package, otherwise we miss 466 // other packages if they appear or disappear along 467 // with this one before receive a notification for 468 // them. 469 if (isVisible) { 470 fVisiblePackages.Add(ref); 471 } else if (wasVisible) 472 fVisiblePackages.Remove(ref); 473 } 474 475 if (wasVisible != isVisible) { 476 if (!isVisible) { 477 fPackageListView->RemovePackage(ref); 478 fFeaturedPackagesView->RemovePackage(ref); 479 } else { 480 fPackageListView->AddPackage(ref); 481 if (ref->IsProminent()) 482 fFeaturedPackagesView->AddPackage(ref); 483 } 484 } 485 } 486 break; 487 } 488 489 case MSG_RATE_PACKAGE: 490 _RatePackage(); 491 break; 492 493 case MSG_SHOW_SCREENSHOT: 494 _ShowScreenshot(); 495 break; 496 497 default: 498 BWindow::MessageReceived(message); 499 break; 500 } 501 } 502 503 504 void 505 MainWindow::StoreSettings(BMessage& settings) const 506 { 507 settings.AddRect(_WindowFrameName(), Frame()); 508 if (!fSinglePackageMode) { 509 settings.AddRect("window frame", Frame()); 510 511 BMessage columnSettings; 512 fPackageListView->SaveState(&columnSettings); 513 514 settings.AddMessage("column settings", &columnSettings); 515 516 settings.AddBool("show featured packages", 517 fModel.ShowFeaturedPackages()); 518 settings.AddBool("show available packages", 519 fModel.ShowAvailablePackages()); 520 settings.AddBool("show installed packages", 521 fModel.ShowInstalledPackages()); 522 settings.AddBool("show develop packages", fModel.ShowDevelopPackages()); 523 settings.AddBool("show source packages", fModel.ShowSourcePackages()); 524 } 525 526 settings.AddString("username", fModel.Username()); 527 } 528 529 530 void 531 MainWindow::PackageChanged(const PackageInfoEvent& event) 532 { 533 uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; 534 if ((event.Changes() & whatchedChanges) != 0) { 535 PackageInfoRef ref(event.Package()); 536 BMessage message(MSG_PACKAGE_CHANGED); 537 message.AddPointer("package", ref.Get()); 538 message.AddUInt32("changes", event.Changes()); 539 ref.Detach(); 540 // reference needs to be released by MessageReceived(); 541 PostMessage(&message); 542 } 543 } 544 545 546 status_t 547 MainWindow::SchedulePackageActions(PackageActionList& list) 548 { 549 AutoLocker<BLocker> lock(&fPendingActionsLock); 550 for (int32 i = 0; i < list.CountItems(); i++) { 551 if (!fPendingActions.Add(list.ItemAtFast(i))) 552 return B_NO_MEMORY; 553 } 554 555 return release_sem_etc(fPendingActionsSem, list.CountItems(), 0); 556 } 557 558 559 Model* 560 MainWindow::GetModel() 561 { 562 return &fModel; 563 } 564 565 566 void 567 MainWindow::_BuildMenu(BMenuBar* menuBar) 568 { 569 BMenu* menu = new BMenu(B_TRANSLATE("Tools")); 570 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh depots"), 571 new BMessage(MSG_REFRESH_DEPOTS))); 572 573 menuBar->AddItem(menu); 574 575 menu = new BMenu(B_TRANSLATE("Show")); 576 577 fShowFeaturedPackagesItem = new BMenuItem( 578 B_TRANSLATE("Only featured packages"), 579 new BMessage(MSG_SHOW_FEATURED_PACKAGES)); 580 menu->AddItem(fShowFeaturedPackagesItem); 581 582 menu->AddSeparatorItem(); 583 584 fShowAvailablePackagesItem = new BMenuItem( 585 B_TRANSLATE("Available packages"), 586 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES)); 587 menu->AddItem(fShowAvailablePackagesItem); 588 589 fShowInstalledPackagesItem = new BMenuItem( 590 B_TRANSLATE("Installed packages"), 591 new BMessage(MSG_SHOW_INSTALLED_PACKAGES)); 592 menu->AddItem(fShowInstalledPackagesItem); 593 594 menu->AddSeparatorItem(); 595 596 fShowDevelopPackagesItem = new BMenuItem( 597 B_TRANSLATE("Develop packages"), 598 new BMessage(MSG_SHOW_DEVELOP_PACKAGES)); 599 menu->AddItem(fShowDevelopPackagesItem); 600 601 fShowSourcePackagesItem = new BMenuItem( 602 B_TRANSLATE("Source packages"), 603 new BMessage(MSG_SHOW_SOURCE_PACKAGES)); 604 menu->AddItem(fShowSourcePackagesItem); 605 606 menuBar->AddItem(menu); 607 } 608 609 610 void 611 MainWindow::_BuildUserMenu(BMenuBar* menuBar) 612 { 613 fUserMenu = new BMenu(B_TRANSLATE("Not logged in")); 614 615 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), 616 new BMessage(MSG_LOG_IN)); 617 fUserMenu->AddItem(fLogInItem); 618 619 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), 620 new BMessage(MSG_LOG_OUT)); 621 fUserMenu->AddItem(fLogOutItem); 622 623 menuBar->AddItem(fUserMenu); 624 } 625 626 627 void 628 MainWindow::_RestoreUserName(const BMessage& settings) 629 { 630 BString username; 631 if (settings.FindString("username", &username) == B_OK 632 && username.Length() > 0) { 633 fModel.SetUsername(username); 634 } 635 } 636 637 638 const char* 639 MainWindow::_WindowFrameName() const 640 { 641 if (fSinglePackageMode) 642 return "small window frame"; 643 644 return "window frame"; 645 } 646 647 648 void 649 MainWindow::_RestoreWindowFrame(const BMessage& settings) 650 { 651 BRect frame = Frame(); 652 653 BRect windowFrame; 654 bool fromSettings = false; 655 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) { 656 frame = windowFrame; 657 fromSettings = true; 658 } else if (!fSinglePackageMode) { 659 // Resize to occupy a certain screen size 660 BRect screenFrame = BScreen(this).Frame(); 661 float width = frame.Width(); 662 float height = frame.Height(); 663 if (width < screenFrame.Width() * .666f 664 && height < screenFrame.Height() * .666f) { 665 frame.bottom = frame.top + screenFrame.Height() * .666f; 666 frame.right = frame.left 667 + std::min(screenFrame.Width() * .666f, height * 7 / 5); 668 } 669 } 670 671 MoveTo(frame.LeftTop()); 672 ResizeTo(frame.Width(), frame.Height()); 673 674 if (fromSettings) 675 MoveOnScreen(); 676 else 677 CenterOnScreen(); 678 } 679 680 681 void 682 MainWindow::_InitWorkerThreads() 683 { 684 fPendingActionsSem = create_sem(0, "PendingPackageActions"); 685 if (fPendingActionsSem >= 0) { 686 fPendingActionsWorker = spawn_thread(&_PackageActionWorker, 687 "Planet Express", B_NORMAL_PRIORITY, this); 688 if (fPendingActionsWorker >= 0) 689 resume_thread(fPendingActionsWorker); 690 } else 691 fPendingActionsWorker = -1; 692 693 fPackageToPopulateSem = create_sem(0, "PopulatePackage"); 694 if (fPackageToPopulateSem >= 0) { 695 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker, 696 "Package Populator", B_NORMAL_PRIORITY, this); 697 if (fPopulatePackageWorker >= 0) 698 resume_thread(fPopulatePackageWorker); 699 } else 700 fPopulatePackageWorker = -1; 701 } 702 703 704 void 705 MainWindow::_AdoptModel() 706 { 707 fVisiblePackages = fModel.CreatePackageList(); 708 709 fFeaturedPackagesView->Clear(); 710 fPackageListView->Clear(); 711 for (int32 i = 0; i < fVisiblePackages.CountItems(); i++) { 712 BAutolock locker(fModel.Lock()); 713 714 const PackageInfoRef& package = fVisiblePackages.ItemAtFast(i); 715 fPackageListView->AddPackage(package); 716 717 if (package->IsProminent()) 718 fFeaturedPackagesView->AddPackage(package); 719 } 720 721 BAutolock locker(fModel.Lock()); 722 fShowFeaturedPackagesItem->SetMarked(fModel.ShowFeaturedPackages()); 723 fShowFeaturedPackagesItem->SetEnabled(fModel.SearchTerms() == ""); 724 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages()); 725 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages()); 726 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages()); 727 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages()); 728 729 if (fModel.ShowFeaturedPackages() && fModel.SearchTerms() == "") 730 fListLayout->SetVisibleItem((int32)0); 731 else 732 fListLayout->SetVisibleItem((int32)1); 733 734 // Maintain selection 735 const PackageInfoRef& selectedPackage = fPackageInfoView->Package(); 736 fFeaturedPackagesView->SelectPackage(selectedPackage); 737 fPackageListView->SelectPackage(selectedPackage); 738 739 if (!fVisiblePackages.Contains(fPackageInfoView->Package())) 740 fPackageInfoView->Clear(); 741 } 742 743 744 void 745 MainWindow::_AdoptPackage(const PackageInfoRef& package) 746 { 747 { 748 BAutolock locker(fModel.Lock()); 749 fPackageInfoView->SetPackage(package); 750 751 if (fFeaturedPackagesView != NULL) 752 fFeaturedPackagesView->SelectPackage(package); 753 if (fPackageListView != NULL) 754 fPackageListView->SelectPackage(package); 755 } 756 757 // Trigger asynchronous package population from the web-app 758 { 759 AutoLocker<BLocker> lock(&fPackageToPopulateLock); 760 fPackageToPopulate = package; 761 } 762 release_sem_etc(fPackageToPopulateSem, 1, 0); 763 } 764 765 766 void 767 MainWindow::_ClearPackage() 768 { 769 fPackageInfoView->Clear(); 770 } 771 772 773 void 774 MainWindow::_RefreshRepositories(bool force) 775 { 776 if (fSinglePackageMode) 777 return; 778 779 BPackageRoster roster; 780 BStringList repositoryNames; 781 782 status_t result = roster.GetRepositoryNames(repositoryNames); 783 if (result != B_OK) 784 return; 785 786 DecisionProvider decisionProvider; 787 JobStateListener listener; 788 BContext context(decisionProvider, listener); 789 790 BRepositoryCache cache; 791 for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) { 792 const BString& repoName = repositoryNames.StringAt(i); 793 BRepositoryConfig repoConfig; 794 result = roster.GetRepositoryConfig(repoName, &repoConfig); 795 if (result != B_OK) { 796 // TODO: notify user 797 continue; 798 } 799 800 if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) { 801 try { 802 BRefreshRepositoryRequest refreshRequest(context, repoConfig); 803 804 result = refreshRequest.Process(); 805 } catch (BFatalErrorException ex) { 806 BString message(B_TRANSLATE("An error occurred while " 807 "refreshing the repository: %error% (%details%)")); 808 message.ReplaceFirst("%error%", ex.Message()); 809 message.ReplaceFirst("%details%", ex.Details()); 810 _NotifyUser("Error", message.String()); 811 } catch (BException ex) { 812 BString message(B_TRANSLATE("An error occurred while " 813 "refreshing the repository: %error%")); 814 message.ReplaceFirst("%error%", ex.Message()); 815 _NotifyUser("Error", message.String()); 816 } 817 } 818 } 819 } 820 821 822 void 823 MainWindow::_RefreshPackageList(bool force) 824 { 825 if (fSinglePackageMode) 826 return; 827 828 BPackageRoster roster; 829 BStringList repositoryNames; 830 831 status_t result = roster.GetRepositoryNames(repositoryNames); 832 if (result != B_OK) 833 return; 834 835 DepotInfoMap depots; 836 for (int32 i = 0; i < repositoryNames.CountStrings(); i++) { 837 const BString& repoName = repositoryNames.StringAt(i); 838 DepotInfo depotInfo = DepotInfo(repoName); 839 840 BRepositoryConfig repoConfig; 841 status_t getRepositoryConfigStatus = roster.GetRepositoryConfig( 842 repoName, &repoConfig); 843 844 if (getRepositoryConfigStatus == B_OK) { 845 depotInfo.SetBaseURL(repoConfig.BaseURL()); 846 847 // it would be nice if this could be more logically located such as 848 // when the repository is added to the model, but that is probably 849 // a bigger change. 850 851 fModel.PopulateWebAppRepositoryCode(depotInfo); 852 } else { 853 printf("unable to obtain the repository config for local " 854 "repository '%s'; %s\n", 855 repoName.String(), strerror(getRepositoryConfigStatus)); 856 } 857 858 depots[repoName] = depotInfo; 859 } 860 861 PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME); 862 try { 863 manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES 864 | PackageManager::B_ADD_REMOTE_REPOSITORIES); 865 } catch (BException ex) { 866 BString message(B_TRANSLATE("An error occurred while " 867 "initializing the package manager: %message%")); 868 message.ReplaceFirst("%message%", ex.Message()); 869 _NotifyUser("Error", message.String()); 870 return; 871 } 872 873 BObjectList<BSolverPackage> packages; 874 result = manager.Solver()->FindPackages("", 875 BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME 876 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION 877 | BSolver::B_FIND_IN_PROVIDES, 878 packages); 879 if (result != B_OK) { 880 BString message(B_TRANSLATE("An error occurred while " 881 "obtaining the package list: %message%")); 882 message.ReplaceFirst("%message%", strerror(result)); 883 _NotifyUser("Error", message.String()); 884 return; 885 } 886 887 if (packages.IsEmpty()) 888 return; 889 890 PackageInfoMap foundPackages; 891 // if a given package is installed locally, we will potentially 892 // get back multiple entries, one for each local installation 893 // location, and one for each remote repository the package 894 // is available in. The above map is used to ensure that in such 895 // cases we consolidate the information, rather than displaying 896 // duplicates 897 PackageInfoMap remotePackages; 898 // any package that we find in a remote repository goes in this map. 899 // this is later used to discern which packages came from a local 900 // installation only, as those must be handled a bit differently 901 // upon uninstallation, since we'd no longer be able to pull them 902 // down remotely. 903 BStringList systemFlaggedPackages; 904 // any packages flagged as a system package are added to this list. 905 // such packages cannot be uninstalled, nor can any of their deps. 906 PackageInfoMap systemInstalledPackages; 907 // any packages installed in system are added to this list. 908 // This is later used for dependency resolution of the actual 909 // system packages in order to compute the list of protected 910 // dependencies indicated above. 911 912 for (int32 i = 0; i < packages.CountItems(); i++) { 913 BSolverPackage* package = packages.ItemAt(i); 914 const BPackageInfo& repoPackageInfo = package->Info(); 915 const BString repositoryName = package->Repository()->Name(); 916 PackageInfoRef modelInfo; 917 PackageInfoMap::iterator it = foundPackages.find( 918 repoPackageInfo.Name()); 919 if (it != foundPackages.end()) 920 modelInfo.SetTo(it->second); 921 else { 922 // Add new package info 923 modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo), 924 true); 925 926 if (modelInfo.Get() == NULL) 927 return; 928 929 modelInfo->SetDepotName(repositoryName); 930 931 foundPackages[repoPackageInfo.Name()] = modelInfo; 932 } 933 934 modelInfo->AddListener(this); 935 936 BSolverRepository* repository = package->Repository(); 937 if (dynamic_cast<BPackageManager::RemoteRepository*>(repository) 938 != NULL) { 939 depots[repository->Name()].AddPackage(modelInfo); 940 remotePackages[modelInfo->Name()] = modelInfo; 941 } else { 942 if (repository == static_cast<const BSolverRepository*>( 943 manager.SystemRepository())) { 944 modelInfo->AddInstallationLocation( 945 B_PACKAGE_INSTALLATION_LOCATION_SYSTEM); 946 if (!modelInfo->IsSystemPackage()) { 947 systemInstalledPackages[repoPackageInfo.FileName()] 948 = modelInfo; 949 } 950 } else if (repository == static_cast<const BSolverRepository*>( 951 manager.HomeRepository())) { 952 modelInfo->AddInstallationLocation( 953 B_PACKAGE_INSTALLATION_LOCATION_HOME); 954 } 955 } 956 957 if (modelInfo->IsSystemPackage()) 958 systemFlaggedPackages.Add(repoPackageInfo.FileName()); 959 } 960 961 bool wasEmpty = fModel.Depots().IsEmpty(); 962 if (force || wasEmpty) 963 fModel.StopPopulatingAllPackages(); 964 965 BAutolock lock(fModel.Lock()); 966 967 if (force) 968 fModel.Clear(); 969 970 // filter remote packages from the found list 971 // any packages remaining will be locally installed packages 972 // that weren't acquired from a repository 973 for (PackageInfoMap::iterator it = remotePackages.begin(); 974 it != remotePackages.end(); it++) { 975 foundPackages.erase(it->first); 976 } 977 978 if (!foundPackages.empty()) { 979 BString repoName = B_TRANSLATE("Local"); 980 depots[repoName] = DepotInfo(repoName); 981 DepotInfoMap::iterator depot = depots.find(repoName); 982 for (PackageInfoMap::iterator it = foundPackages.begin(); 983 it != foundPackages.end(); ++it) { 984 depot->second.AddPackage(it->second); 985 } 986 } 987 988 for (DepotInfoMap::iterator it = depots.begin(); it != depots.end(); it++) { 989 if (fModel.HasDepot(it->second.Name())) 990 fModel.SyncDepot(it->second); 991 else 992 fModel.AddDepot(it->second); 993 } 994 995 // start retrieving package icons and average ratings 996 if (force || wasEmpty) 997 fModel.PopulateAllPackages(); 998 999 // compute the OS package dependencies 1000 try { 1001 // create the solver 1002 BSolver* solver; 1003 status_t error = BSolver::Create(solver); 1004 if (error != B_OK) 1005 throw BFatalErrorException(error, "Failed to create solver."); 1006 1007 ObjectDeleter<BSolver> solverDeleter(solver); 1008 BPath systemPath; 1009 error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath); 1010 if (error != B_OK) { 1011 throw BFatalErrorException(error, 1012 "Unable to retrieve system packages directory."); 1013 } 1014 1015 // add the "installed" repository with the given packages 1016 BSolverRepository installedRepository; 1017 { 1018 BRepositoryBuilder installedRepositoryBuilder(installedRepository, 1019 "installed"); 1020 for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) { 1021 BPath packagePath(systemPath); 1022 packagePath.Append(systemFlaggedPackages.StringAt(i)); 1023 installedRepositoryBuilder.AddPackage(packagePath.Path()); 1024 } 1025 installedRepositoryBuilder.AddToSolver(solver, true); 1026 } 1027 1028 // add system repository 1029 BSolverRepository systemRepository; 1030 { 1031 BRepositoryBuilder systemRepositoryBuilder(systemRepository, 1032 "system"); 1033 for (PackageInfoMap::iterator it = systemInstalledPackages.begin(); 1034 it != systemInstalledPackages.end(); it++) { 1035 BPath packagePath(systemPath); 1036 packagePath.Append(it->first); 1037 systemRepositoryBuilder.AddPackage(packagePath.Path()); 1038 } 1039 systemRepositoryBuilder.AddToSolver(solver, false); 1040 } 1041 1042 // solve 1043 error = solver->VerifyInstallation(); 1044 if (error != B_OK) { 1045 throw BFatalErrorException(error, "Failed to compute packages to " 1046 "install."); 1047 } 1048 1049 BSolverResult solverResult; 1050 error = solver->GetResult(solverResult); 1051 if (error != B_OK) { 1052 throw BFatalErrorException(error, "Failed to retrieve system " 1053 "package dependency list."); 1054 } 1055 1056 for (int32 i = 0; const BSolverResultElement* element 1057 = solverResult.ElementAt(i); i++) { 1058 BSolverPackage* package = element->Package(); 1059 if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) { 1060 PackageInfoMap::iterator it = systemInstalledPackages.find( 1061 package->Info().FileName()); 1062 if (it != systemInstalledPackages.end()) 1063 it->second->SetSystemDependency(true); 1064 } 1065 } 1066 } catch (BFatalErrorException ex) { 1067 printf("Fatal exception occurred while resolving system dependencies: " 1068 "%s, details: %s\n", strerror(ex.Error()), ex.Details().String()); 1069 } catch (BNothingToDoException) { 1070 // do nothing 1071 } catch (BException ex) { 1072 printf("Exception occurred while resolving system dependencies: %s\n", 1073 ex.Message().String()); 1074 } catch (...) { 1075 printf("Unknown exception occurred while resolving system " 1076 "dependencies.\n"); 1077 } 1078 } 1079 1080 1081 void 1082 MainWindow::_StartRefreshWorker(bool force) 1083 { 1084 if (fModelWorker != B_BAD_THREAD_ID) 1085 return; 1086 1087 RefreshWorkerParameters* parameters = new(std::nothrow) 1088 RefreshWorkerParameters(this, force); 1089 if (parameters == NULL) 1090 return; 1091 1092 ObjectDeleter<RefreshWorkerParameters> deleter(parameters); 1093 fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader", 1094 B_LOW_PRIORITY, parameters); 1095 1096 if (fModelWorker > 0) { 1097 deleter.Detach(); 1098 resume_thread(fModelWorker); 1099 } 1100 } 1101 1102 1103 status_t 1104 MainWindow::_RefreshModelThreadWorker(void* arg) 1105 { 1106 RefreshWorkerParameters* parameters 1107 = reinterpret_cast<RefreshWorkerParameters*>(arg); 1108 MainWindow* mainWindow = parameters->window; 1109 ObjectDeleter<RefreshWorkerParameters> deleter(parameters); 1110 1111 BMessenger messenger(mainWindow); 1112 1113 mainWindow->_RefreshRepositories(parameters->forceRefresh); 1114 1115 if (mainWindow->fTerminating) 1116 return B_OK; 1117 1118 mainWindow->_RefreshPackageList(parameters->forceRefresh); 1119 1120 messenger.SendMessage(MSG_MODEL_WORKER_DONE); 1121 1122 return B_OK; 1123 } 1124 1125 1126 status_t 1127 MainWindow::_PackageActionWorker(void* arg) 1128 { 1129 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1130 1131 while (acquire_sem(window->fPendingActionsSem) == B_OK) { 1132 PackageActionRef ref; 1133 { 1134 AutoLocker<BLocker> lock(&window->fPendingActionsLock); 1135 ref = window->fPendingActions.ItemAt(0); 1136 if (ref.Get() == NULL) 1137 break; 1138 window->fPendingActions.Remove(0); 1139 } 1140 1141 ref->Perform(); 1142 } 1143 1144 return 0; 1145 } 1146 1147 1148 status_t 1149 MainWindow::_PopulatePackageWorker(void* arg) 1150 { 1151 MainWindow* window = reinterpret_cast<MainWindow*>(arg); 1152 1153 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { 1154 PackageInfoRef package; 1155 { 1156 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); 1157 package = window->fPackageToPopulate; 1158 } 1159 1160 if (package.Get() != NULL) { 1161 window->fModel.PopulatePackage(package, 1162 Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS 1163 | Model::POPULATE_CHANGELOG); 1164 } 1165 } 1166 1167 return 0; 1168 } 1169 1170 1171 void 1172 MainWindow::_NotifyUser(const char* title, const char* message) 1173 { 1174 BAlert* alert = new(std::nothrow) BAlert(title, message, 1175 B_TRANSLATE("Close")); 1176 1177 if (alert != NULL) 1178 alert->Go(); 1179 } 1180 1181 1182 void 1183 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage) 1184 { 1185 UserLoginWindow* window = new UserLoginWindow(this, 1186 BRect(0, 0, 500, 400), fModel); 1187 1188 if (onSuccessMessage.what != 0) 1189 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage); 1190 1191 window->Show(); 1192 } 1193 1194 1195 void 1196 MainWindow::_UpdateAuthorization() 1197 { 1198 BString username(fModel.Username()); 1199 bool hasUser = !username.IsEmpty(); 1200 1201 if (fLogOutItem != NULL) 1202 fLogOutItem->SetEnabled(hasUser); 1203 if (fLogInItem != NULL) { 1204 if (hasUser) 1205 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS)); 1206 else 1207 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS)); 1208 } 1209 1210 if (fUserMenu != NULL) { 1211 BString label; 1212 if (username.Length() == 0) { 1213 label = B_TRANSLATE("Not logged in"); 1214 } else { 1215 label = B_TRANSLATE("Logged in as %User%"); 1216 label.ReplaceAll("%User%", username); 1217 } 1218 fUserMenu->Superitem()->SetLabel(label); 1219 } 1220 } 1221 1222 1223 void 1224 MainWindow::_RatePackage() 1225 { 1226 if (fModel.Username().IsEmpty()) { 1227 BAlert* alert = new(std::nothrow) BAlert( 1228 B_TRANSLATE("Not logged in"), 1229 B_TRANSLATE("You need to be logged into an account before you " 1230 "can rate packages."), 1231 B_TRANSLATE("Cancel"), 1232 B_TRANSLATE("Login or Create account")); 1233 1234 if (alert == NULL) 1235 return; 1236 1237 int32 choice = alert->Go(); 1238 if (choice == 1) 1239 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE)); 1240 return; 1241 } 1242 1243 // TODO: Allow only one RatePackageWindow 1244 // TODO: Mechanism for remembering the window frame 1245 RatePackageWindow* window = new RatePackageWindow(this, 1246 BRect(0, 0, 500, 400), fModel); 1247 window->SetPackage(fPackageInfoView->Package()); 1248 window->Show(); 1249 } 1250 1251 1252 void 1253 MainWindow::_ShowScreenshot() 1254 { 1255 // TODO: Mechanism for remembering the window frame 1256 if (fScreenshotWindow == NULL) 1257 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400)); 1258 1259 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK) 1260 return; 1261 1262 fScreenshotWindow->SetPackage(fPackageInfoView->Package()); 1263 1264 if (fScreenshotWindow->IsHidden()) 1265 fScreenshotWindow->Show(); 1266 else 1267 fScreenshotWindow->Activate(); 1268 1269 fScreenshotWindow->Unlock(); 1270 } 1271