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