repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / haikudepot / ui / MainWindow.cpp
blob68018f4ad4212dded97ece8f66d5d18feb262587
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 * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
8 * All rights reserved. Distributed under the terms of the MIT License.
9 */
12 #include "MainWindow.h"
14 #include <map>
16 #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>
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>
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 "PackageInfoView.h"
54 #include "PackageListView.h"
55 #include "PackageManager.h"
56 #include "RatePackageWindow.h"
57 #include "support.h"
58 #include "ScreenshotWindow.h"
59 #include "UserLoginWindow.h"
60 #include "WorkStatusView.h"
63 #undef B_TRANSLATION_CONTEXT
64 #define B_TRANSLATION_CONTEXT "MainWindow"
67 enum {
68 MSG_MODEL_WORKER_DONE = 'mmwd',
69 MSG_REFRESH_REPOS = 'mrrp',
70 MSG_MANAGE_REPOS = 'mmrp',
71 MSG_LOG_IN = 'lgin',
72 MSG_LOG_OUT = 'lgot',
73 MSG_AUTHORIZATION_CHANGED = 'athc',
74 MSG_PACKAGE_CHANGED = 'pchd',
76 MSG_SHOW_FEATURED_PACKAGES = 'sofp',
77 MSG_SHOW_AVAILABLE_PACKAGES = 'savl',
78 MSG_SHOW_INSTALLED_PACKAGES = 'sins',
79 MSG_SHOW_SOURCE_PACKAGES = 'ssrc',
80 MSG_SHOW_DEVELOP_PACKAGES = 'sdvl'
84 using namespace BPackageKit;
85 using namespace BPackageKit::BManager::BPrivate;
88 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
89 typedef std::map<BString, DepotInfo> DepotInfoMap;
92 struct RefreshWorkerParameters {
93 MainWindow* window;
94 bool forceRefresh;
96 RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
98 window(window),
99 forceRefresh(forceRefresh)
105 class MessageModelListener : public ModelListener {
106 public:
107 MessageModelListener(const BMessenger& messenger)
109 fMessenger(messenger)
113 virtual void AuthorizationChanged()
115 if (fMessenger.IsValid())
116 fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
119 private:
120 BMessenger fMessenger;
124 MainWindow::MainWindow(const BMessage& settings)
126 BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
127 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
128 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
129 fScreenshotWindow(NULL),
130 fUserMenu(NULL),
131 fLogInItem(NULL),
132 fLogOutItem(NULL),
133 fModelListener(new MessageModelListener(BMessenger(this)), true),
134 fTerminating(false),
135 fSinglePackageMode(false),
136 fModelWorker(B_BAD_THREAD_ID)
138 BMenuBar* menuBar = new BMenuBar(B_TRANSLATE("Main Menu"));
139 _BuildMenu(menuBar);
141 BMenuBar* userMenuBar = new BMenuBar(B_TRANSLATE("User Menu"));
142 _BuildUserMenu(userMenuBar);
143 set_small_font(userMenuBar);
144 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
145 menuBar->MaxSize().height));
147 fFilterView = new FilterView();
148 fFeaturedPackagesView = new FeaturedPackagesView();
149 fPackageListView = new PackageListView(fModel.Lock());
150 fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
152 fSplitView = new BSplitView(B_VERTICAL, 5.0f);
154 BGroupView* featuredPackagesGroup = new BGroupView(B_VERTICAL);
155 BStringView* featuredPackagesTitle = new BStringView(
156 "featured packages title", B_TRANSLATE("Featured packages"));
157 BFont font(be_bold_font);
158 font.SetSize(font.Size() * 1.3f);
159 featuredPackagesTitle->SetFont(&font);
160 featuredPackagesGroup->SetExplicitMaxSize(
161 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
162 BLayoutBuilder::Group<>(featuredPackagesGroup)
163 .Add(featuredPackagesTitle)
164 .Add(fFeaturedPackagesView)
167 fWorkStatusView = new WorkStatusView("work status");
168 fPackageListView->AttachWorkStatusView(fWorkStatusView);
170 BView* listArea = new BView("list area", 0);
171 fListLayout = new BCardLayout();
172 listArea->SetLayout(fListLayout);
173 listArea->AddChild(featuredPackagesGroup);
174 listArea->AddChild(fPackageListView);
176 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
177 .AddGroup(B_HORIZONTAL, 0.0f)
178 .Add(menuBar, 1.0f)
179 .Add(userMenuBar, 0.0f)
180 .End()
181 .Add(fFilterView)
182 .AddSplit(fSplitView)
183 .AddGroup(B_VERTICAL)
184 .Add(listArea)
185 .SetInsets(
186 B_USE_DEFAULT_SPACING, 0.0f,
187 B_USE_DEFAULT_SPACING, 0.0f)
188 .End()
189 .Add(fPackageInfoView)
190 .End()
191 .Add(fWorkStatusView)
194 fSplitView->SetCollapsible(0, false);
195 fSplitView->SetCollapsible(1, false);
197 fModel.AddListener(fModelListener);
199 // Restore settings
200 BMessage columnSettings;
201 if (settings.FindMessage("column settings", &columnSettings) == B_OK)
202 fPackageListView->LoadState(&columnSettings);
204 bool showOption;
205 if (settings.FindBool("show featured packages", &showOption) == B_OK)
206 fModel.SetShowFeaturedPackages(showOption);
207 if (settings.FindBool("show available packages", &showOption) == B_OK)
208 fModel.SetShowAvailablePackages(showOption);
209 if (settings.FindBool("show installed packages", &showOption) == B_OK)
210 fModel.SetShowInstalledPackages(showOption);
211 if (settings.FindBool("show develop packages", &showOption) == B_OK)
212 fModel.SetShowDevelopPackages(showOption);
213 if (settings.FindBool("show source packages", &showOption) == B_OK)
214 fModel.SetShowSourcePackages(showOption);
216 if (fModel.ShowFeaturedPackages())
217 fListLayout->SetVisibleItem((int32)0);
218 else
219 fListLayout->SetVisibleItem(1);
221 _RestoreUserName(settings);
222 _RestoreWindowFrame(settings);
224 atomic_set(&fPackagesToShowListID, 0);
226 // start worker threads
227 BPackageRoster().StartWatching(this,
228 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
230 _StartRefreshWorker();
232 _InitWorkerThreads();
236 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
238 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
239 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
240 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
241 fWorkStatusView(NULL),
242 fScreenshotWindow(NULL),
243 fUserMenu(NULL),
244 fLogInItem(NULL),
245 fLogOutItem(NULL),
246 fModelListener(new MessageModelListener(BMessenger(this)), true),
247 fTerminating(false),
248 fSinglePackageMode(true),
249 fModelWorker(B_BAD_THREAD_ID)
251 fFilterView = new FilterView();
252 fPackageListView = new PackageListView(fModel.Lock());
253 fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
255 BLayoutBuilder::Group<>(this, B_VERTICAL)
256 .Add(fPackageInfoView)
257 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
260 fModel.AddListener(fModelListener);
262 // Restore settings
263 _RestoreUserName(settings);
264 _RestoreWindowFrame(settings);
266 fPackageInfoView->SetPackage(package);
268 _InitWorkerThreads();
272 MainWindow::~MainWindow()
274 BPackageRoster().StopWatching(this);
276 fTerminating = true;
277 if (fModelWorker >= 0)
278 wait_for_thread(fModelWorker, NULL);
280 delete_sem(fPendingActionsSem);
281 if (fPendingActionsWorker >= 0)
282 wait_for_thread(fPendingActionsWorker, NULL);
284 delete_sem(fPackageToPopulateSem);
285 if (fPopulatePackageWorker >= 0)
286 wait_for_thread(fPopulatePackageWorker, NULL);
288 delete_sem(fNewPackagesToShowSem);
289 delete_sem(fShowPackagesAcknowledgeSem);
290 if (fShowPackagesWorker >= 0)
291 wait_for_thread(fShowPackagesWorker, NULL);
293 if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
294 fScreenshotWindow->Quit();
298 bool
299 MainWindow::QuitRequested()
301 BMessage settings;
302 StoreSettings(settings);
304 BMessage message(MSG_MAIN_WINDOW_CLOSED);
305 message.AddMessage("window settings", &settings);
307 be_app->PostMessage(&message);
309 return true;
313 void
314 MainWindow::MessageReceived(BMessage* message)
316 switch (message->what) {
317 case MSG_MODEL_WORKER_DONE:
319 fModelWorker = B_BAD_THREAD_ID;
320 _AdoptModel();
321 fFilterView->AdoptModel(fModel);
322 fWorkStatusView->SetIdle();
323 break;
325 case B_SIMPLE_DATA:
326 case B_REFS_RECEIVED:
327 // TODO: ?
328 break;
330 case B_PACKAGE_UPDATE:
331 // TODO: We should do a more selective update depending on the
332 // "event", "location", and "change count" fields!
333 _StartRefreshWorker(false);
334 break;
336 case MSG_REFRESH_REPOS:
337 _StartRefreshWorker(true);
338 break;
340 case MSG_MANAGE_REPOS:
341 be_roster->Launch("application/x-vnd.Haiku-Repositories");
342 break;
344 case MSG_LOG_IN:
345 _OpenLoginWindow(BMessage());
346 break;
348 case MSG_LOG_OUT:
349 fModel.SetUsername("");
350 break;
352 case MSG_AUTHORIZATION_CHANGED:
353 _UpdateAuthorization();
354 break;
356 case MSG_SHOW_FEATURED_PACKAGES:
358 BAutolock locker(fModel.Lock());
359 fModel.SetShowFeaturedPackages(
360 !fModel.ShowFeaturedPackages());
362 _AdoptModel();
363 break;
365 case MSG_SHOW_AVAILABLE_PACKAGES:
367 BAutolock locker(fModel.Lock());
368 fModel.SetShowAvailablePackages(
369 !fModel.ShowAvailablePackages());
371 _AdoptModel();
372 break;
374 case MSG_SHOW_INSTALLED_PACKAGES:
376 BAutolock locker(fModel.Lock());
377 fModel.SetShowInstalledPackages(
378 !fModel.ShowInstalledPackages());
380 _AdoptModel();
381 break;
383 case MSG_SHOW_SOURCE_PACKAGES:
385 BAutolock locker(fModel.Lock());
386 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
388 _AdoptModel();
389 break;
391 case MSG_SHOW_DEVELOP_PACKAGES:
393 BAutolock locker(fModel.Lock());
394 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
396 _AdoptModel();
397 break;
399 case MSG_PACKAGE_SELECTED:
401 BString name;
402 if (message->FindString("name", &name) == B_OK) {
403 BAutolock locker(fModel.Lock());
404 int count = fVisiblePackages.CountItems();
405 for (int i = 0; i < count; i++) {
406 const PackageInfoRef& package
407 = fVisiblePackages.ItemAtFast(i);
408 if (package.Get() != NULL && package->Name() == name) {
409 locker.Unlock();
410 _AdoptPackage(package);
411 break;
414 } else {
415 _ClearPackage();
417 break;
420 case MSG_CATEGORY_SELECTED:
422 BString name;
423 if (message->FindString("name", &name) != B_OK)
424 name = "";
426 BAutolock locker(fModel.Lock());
427 fModel.SetCategory(name);
429 _AdoptModel();
430 break;
433 case MSG_DEPOT_SELECTED:
435 BString name;
436 if (message->FindString("name", &name) != B_OK)
437 name = "";
439 BAutolock locker(fModel.Lock());
440 fModel.SetDepot(name);
442 _AdoptModel();
443 break;
446 case MSG_SEARCH_TERMS_MODIFIED:
448 // TODO: Do this with a delay!
449 BString searchTerms;
450 if (message->FindString("search terms", &searchTerms) != B_OK)
451 searchTerms = "";
453 BAutolock locker(fModel.Lock());
454 fModel.SetSearchTerms(searchTerms);
456 _AdoptModel();
457 break;
460 case MSG_PACKAGE_CHANGED:
462 PackageInfo* info;
463 if (message->FindPointer("package", (void**)&info) == B_OK) {
464 PackageInfoRef ref(info, true);
465 uint32 changes;
466 if (message->FindUInt32("changes", &changes) != B_OK)
467 changes = 0;
468 if ((changes & PKG_CHANGED_STATE) != 0) {
469 BAutolock locker(fModel.Lock());
470 fModel.SetPackageState(ref, ref->State());
473 // Asynchronous updates to the package information
474 // can mean that the package needs to be added or
475 // removed to/from the visible packages when the current
476 // filter parameters are re-evaluated on this package.
477 bool wasVisible = fVisiblePackages.Contains(ref);
478 bool isVisible;
480 BAutolock locker(fModel.Lock());
481 // The package didn't get a chance yet to be in the
482 // visible package list
483 PackageList visiblePackages = fModel.CreatePackageList();
484 isVisible = visiblePackages.Contains(ref);
486 // Transfer this single package, otherwise we miss
487 // other packages if they appear or disappear along
488 // with this one before receive a notification for
489 // them.
490 if (isVisible) {
491 fVisiblePackages.Add(ref);
492 } else if (wasVisible)
493 fVisiblePackages.Remove(ref);
496 if (wasVisible != isVisible) {
497 if (!isVisible) {
498 fPackageListView->RemovePackage(ref);
499 fFeaturedPackagesView->RemovePackage(ref);
500 } else {
501 fPackageListView->AddPackage(ref);
502 if (ref->IsProminent())
503 fFeaturedPackagesView->AddPackage(ref);
507 break;
510 case MSG_RATE_PACKAGE:
511 _RatePackage();
512 break;
514 case MSG_SHOW_SCREENSHOT:
515 _ShowScreenshot();
516 break;
518 case MSG_PACKAGE_WORKER_BUSY:
520 BString reason;
521 status_t status = message->FindString("reason", &reason);
522 if (status != B_OK)
523 break;
524 if (!fSinglePackageMode)
525 fWorkStatusView->SetBusy(reason);
526 break;
529 case MSG_PACKAGE_WORKER_IDLE:
530 if (!fSinglePackageMode)
531 fWorkStatusView->SetIdle();
532 break;
534 case MSG_ADD_VISIBLE_PACKAGES:
536 struct SemaphoreReleaser {
537 SemaphoreReleaser(sem_id semaphore)
539 fSemaphore(semaphore)
542 ~SemaphoreReleaser() { release_sem(fSemaphore); }
544 sem_id fSemaphore;
547 // Make sure acknowledge semaphore is released even on error,
548 // so the worker thread won't be blocked
549 SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
551 int32 numPackages = 0;
552 type_code unused;
553 status_t status = message->GetInfo("package_ref", &unused,
554 &numPackages);
555 if (status != B_OK || numPackages == 0)
556 break;
558 int32 listID = 0;
559 status = message->FindInt32("list_id", &listID);
560 if (status != B_OK)
561 break;
562 if (listID != atomic_get(&fPackagesToShowListID)) {
563 // list is outdated, ignore
564 break;
567 for (int i = 0; i < numPackages; i++) {
568 PackageInfo* packageRaw = NULL;
569 status = message->FindPointer("package_ref", i,
570 (void**)&packageRaw);
571 if (status != B_OK)
572 break;
573 PackageInfoRef package(packageRaw, true);
575 fPackageListView->AddPackage(package);
576 if (package->IsProminent())
577 fFeaturedPackagesView->AddPackage(package);
579 break;
582 case MSG_UPDATE_SELECTED_PACKAGE:
584 const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
585 fFeaturedPackagesView->SelectPackage(selectedPackage, true);
586 fPackageListView->SelectPackage(selectedPackage);
588 AutoLocker<BLocker> modelLocker(fModel.Lock());
589 if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
590 fPackageInfoView->Clear();
591 break;
594 default:
595 BWindow::MessageReceived(message);
596 break;
601 void
602 MainWindow::StoreSettings(BMessage& settings) const
604 settings.AddRect(_WindowFrameName(), Frame());
605 if (!fSinglePackageMode) {
606 settings.AddRect("window frame", Frame());
608 BMessage columnSettings;
609 fPackageListView->SaveState(&columnSettings);
611 settings.AddMessage("column settings", &columnSettings);
613 settings.AddBool("show featured packages",
614 fModel.ShowFeaturedPackages());
615 settings.AddBool("show available packages",
616 fModel.ShowAvailablePackages());
617 settings.AddBool("show installed packages",
618 fModel.ShowInstalledPackages());
619 settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
620 settings.AddBool("show source packages", fModel.ShowSourcePackages());
623 settings.AddString("username", fModel.Username());
627 void
628 MainWindow::PackageChanged(const PackageInfoEvent& event)
630 uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
631 if ((event.Changes() & whatchedChanges) != 0) {
632 PackageInfoRef ref(event.Package());
633 BMessage message(MSG_PACKAGE_CHANGED);
634 message.AddPointer("package", ref.Get());
635 message.AddUInt32("changes", event.Changes());
636 ref.Detach();
637 // reference needs to be released by MessageReceived();
638 PostMessage(&message);
643 status_t
644 MainWindow::SchedulePackageActions(PackageActionList& list)
646 AutoLocker<BLocker> lock(&fPendingActionsLock);
647 for (int32 i = 0; i < list.CountItems(); i++) {
648 if (!fPendingActions.Add(list.ItemAtFast(i)))
649 return B_NO_MEMORY;
652 return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
656 Model*
657 MainWindow::GetModel()
659 return &fModel;
663 void
664 MainWindow::_BuildMenu(BMenuBar* menuBar)
666 BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
667 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"),
668 new BMessage(MSG_REFRESH_REPOS)));
669 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
670 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
672 menuBar->AddItem(menu);
674 menu = new BMenu(B_TRANSLATE("Show"));
676 fShowFeaturedPackagesItem = new BMenuItem(
677 B_TRANSLATE("Only featured packages"),
678 new BMessage(MSG_SHOW_FEATURED_PACKAGES));
679 menu->AddItem(fShowFeaturedPackagesItem);
681 menu->AddSeparatorItem();
683 fShowAvailablePackagesItem = new BMenuItem(
684 B_TRANSLATE("Available packages"),
685 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
686 menu->AddItem(fShowAvailablePackagesItem);
688 fShowInstalledPackagesItem = new BMenuItem(
689 B_TRANSLATE("Installed packages"),
690 new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
691 menu->AddItem(fShowInstalledPackagesItem);
693 menu->AddSeparatorItem();
695 fShowDevelopPackagesItem = new BMenuItem(
696 B_TRANSLATE("Develop packages"),
697 new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
698 menu->AddItem(fShowDevelopPackagesItem);
700 fShowSourcePackagesItem = new BMenuItem(
701 B_TRANSLATE("Source packages"),
702 new BMessage(MSG_SHOW_SOURCE_PACKAGES));
703 menu->AddItem(fShowSourcePackagesItem);
705 menuBar->AddItem(menu);
709 void
710 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
712 fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
714 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
715 new BMessage(MSG_LOG_IN));
716 fUserMenu->AddItem(fLogInItem);
718 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
719 new BMessage(MSG_LOG_OUT));
720 fUserMenu->AddItem(fLogOutItem);
722 menuBar->AddItem(fUserMenu);
726 void
727 MainWindow::_RestoreUserName(const BMessage& settings)
729 BString username;
730 if (settings.FindString("username", &username) == B_OK
731 && username.Length() > 0) {
732 fModel.SetUsername(username);
737 const char*
738 MainWindow::_WindowFrameName() const
740 if (fSinglePackageMode)
741 return "small window frame";
743 return "window frame";
747 void
748 MainWindow::_RestoreWindowFrame(const BMessage& settings)
750 BRect frame = Frame();
752 BRect windowFrame;
753 bool fromSettings = false;
754 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
755 frame = windowFrame;
756 fromSettings = true;
757 } else if (!fSinglePackageMode) {
758 // Resize to occupy a certain screen size
759 BRect screenFrame = BScreen(this).Frame();
760 float width = frame.Width();
761 float height = frame.Height();
762 if (width < screenFrame.Width() * .666f
763 && height < screenFrame.Height() * .666f) {
764 frame.bottom = frame.top + screenFrame.Height() * .666f;
765 frame.right = frame.left
766 + std::min(screenFrame.Width() * .666f, height * 7 / 5);
770 MoveTo(frame.LeftTop());
771 ResizeTo(frame.Width(), frame.Height());
773 if (fromSettings)
774 MoveOnScreen();
775 else
776 CenterOnScreen();
780 void
781 MainWindow::_InitWorkerThreads()
783 fPendingActionsSem = create_sem(0, "PendingPackageActions");
784 if (fPendingActionsSem >= 0) {
785 fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
786 "Planet Express", B_NORMAL_PRIORITY, this);
787 if (fPendingActionsWorker >= 0)
788 resume_thread(fPendingActionsWorker);
789 } else
790 fPendingActionsWorker = -1;
792 fPackageToPopulateSem = create_sem(0, "PopulatePackage");
793 if (fPackageToPopulateSem >= 0) {
794 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
795 "Package Populator", B_NORMAL_PRIORITY, this);
796 if (fPopulatePackageWorker >= 0)
797 resume_thread(fPopulatePackageWorker);
798 } else
799 fPopulatePackageWorker = -1;
801 fNewPackagesToShowSem = create_sem(0, "ShowPackages");
802 fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
803 if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
804 fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
805 "Good news everyone", B_NORMAL_PRIORITY, this);
806 if (fShowPackagesWorker >= 0)
807 resume_thread(fShowPackagesWorker);
808 } else
809 fShowPackagesWorker = -1;
813 void
814 MainWindow::_AdoptModel()
816 fVisiblePackages = fModel.CreatePackageList();
819 AutoLocker<BLocker> modelLocker(fModel.Lock());
820 AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
821 fPackagesToShowList = fVisiblePackages;
822 atomic_add(&fPackagesToShowListID, 1);
825 fFeaturedPackagesView->Clear();
826 fPackageListView->Clear();
828 release_sem(fNewPackagesToShowSem);
830 BAutolock locker(fModel.Lock());
831 fShowFeaturedPackagesItem->SetMarked(fModel.ShowFeaturedPackages());
832 fShowFeaturedPackagesItem->SetEnabled(fModel.SearchTerms() == "");
833 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
834 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
835 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
836 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
838 if (fModel.ShowFeaturedPackages() && fModel.SearchTerms() == "")
839 fListLayout->SetVisibleItem((int32)0);
840 else
841 fListLayout->SetVisibleItem((int32)1);
845 void
846 MainWindow::_AdoptPackage(const PackageInfoRef& package)
849 BAutolock locker(fModel.Lock());
850 fPackageInfoView->SetPackage(package);
852 if (fFeaturedPackagesView != NULL)
853 fFeaturedPackagesView->SelectPackage(package);
854 if (fPackageListView != NULL)
855 fPackageListView->SelectPackage(package);
858 // Trigger asynchronous package population from the web-app
860 AutoLocker<BLocker> lock(&fPackageToPopulateLock);
861 fPackageToPopulate = package;
863 release_sem_etc(fPackageToPopulateSem, 1, 0);
867 void
868 MainWindow::_ClearPackage()
870 fPackageInfoView->Clear();
874 void
875 MainWindow::_RefreshRepositories(bool force)
877 if (fSinglePackageMode)
878 return;
880 BPackageRoster roster;
881 BStringList repositoryNames;
883 status_t result = roster.GetRepositoryNames(repositoryNames);
884 if (result != B_OK)
885 return;
887 DecisionProvider decisionProvider;
888 JobStateListener listener;
889 BContext context(decisionProvider, listener);
891 BRepositoryCache cache;
892 for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) {
893 const BString& repoName = repositoryNames.StringAt(i);
894 BRepositoryConfig repoConfig;
895 result = roster.GetRepositoryConfig(repoName, &repoConfig);
896 if (result != B_OK) {
897 // TODO: notify user
898 continue;
901 if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) {
902 try {
903 BRefreshRepositoryRequest refreshRequest(context, repoConfig);
905 result = refreshRequest.Process();
906 } catch (BFatalErrorException ex) {
907 BString message(B_TRANSLATE("An error occurred while "
908 "refreshing the repository: %error% (%details%)"));
909 message.ReplaceFirst("%error%", ex.Message());
910 message.ReplaceFirst("%details%", ex.Details());
911 _NotifyUser("Error", message.String());
912 } catch (BException ex) {
913 BString message(B_TRANSLATE("An error occurred while "
914 "refreshing the repository: %error%"));
915 message.ReplaceFirst("%error%", ex.Message());
916 _NotifyUser("Error", message.String());
923 void
924 MainWindow::_RefreshPackageList(bool force)
926 if (fSinglePackageMode)
927 return;
929 BPackageRoster roster;
930 BStringList repositoryNames;
932 status_t result = roster.GetRepositoryNames(repositoryNames);
933 if (result != B_OK)
934 return;
936 DepotInfoMap depots;
937 for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
938 const BString& repoName = repositoryNames.StringAt(i);
939 DepotInfo depotInfo = DepotInfo(repoName);
941 BRepositoryConfig repoConfig;
942 status_t getRepositoryConfigStatus = roster.GetRepositoryConfig(
943 repoName, &repoConfig);
945 if (getRepositoryConfigStatus == B_OK) {
946 depotInfo.SetBaseURL(repoConfig.BaseURL());
947 } else {
948 printf("unable to obtain the repository config for local "
949 "repository '%s'; %s\n",
950 repoName.String(), strerror(getRepositoryConfigStatus));
953 depots[repoName] = depotInfo;
956 PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
957 try {
958 manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
959 | PackageManager::B_ADD_REMOTE_REPOSITORIES);
960 } catch (BException ex) {
961 BString message(B_TRANSLATE("An error occurred while "
962 "initializing the package manager: %message%"));
963 message.ReplaceFirst("%message%", ex.Message());
964 _NotifyUser("Error", message.String());
965 return;
968 BObjectList<BSolverPackage> packages;
969 result = manager.Solver()->FindPackages("",
970 BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
971 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
972 | BSolver::B_FIND_IN_PROVIDES,
973 packages);
974 if (result != B_OK) {
975 BString message(B_TRANSLATE("An error occurred while "
976 "obtaining the package list: %message%"));
977 message.ReplaceFirst("%message%", strerror(result));
978 _NotifyUser("Error", message.String());
979 return;
982 if (packages.IsEmpty())
983 return;
985 PackageInfoMap foundPackages;
986 // if a given package is installed locally, we will potentially
987 // get back multiple entries, one for each local installation
988 // location, and one for each remote repository the package
989 // is available in. The above map is used to ensure that in such
990 // cases we consolidate the information, rather than displaying
991 // duplicates
992 PackageInfoMap remotePackages;
993 // any package that we find in a remote repository goes in this map.
994 // this is later used to discern which packages came from a local
995 // installation only, as those must be handled a bit differently
996 // upon uninstallation, since we'd no longer be able to pull them
997 // down remotely.
998 BStringList systemFlaggedPackages;
999 // any packages flagged as a system package are added to this list.
1000 // such packages cannot be uninstalled, nor can any of their deps.
1001 PackageInfoMap systemInstalledPackages;
1002 // any packages installed in system are added to this list.
1003 // This is later used for dependency resolution of the actual
1004 // system packages in order to compute the list of protected
1005 // dependencies indicated above.
1007 for (int32 i = 0; i < packages.CountItems(); i++) {
1008 BSolverPackage* package = packages.ItemAt(i);
1009 const BPackageInfo& repoPackageInfo = package->Info();
1010 const BString repositoryName = package->Repository()->Name();
1011 PackageInfoRef modelInfo;
1012 PackageInfoMap::iterator it = foundPackages.find(
1013 repoPackageInfo.Name());
1014 if (it != foundPackages.end())
1015 modelInfo.SetTo(it->second);
1016 else {
1017 // Add new package info
1018 modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
1019 true);
1021 if (modelInfo.Get() == NULL)
1022 return;
1024 modelInfo->SetDepotName(repositoryName);
1026 foundPackages[repoPackageInfo.Name()] = modelInfo;
1029 modelInfo->AddListener(this);
1031 BSolverRepository* repository = package->Repository();
1032 if (dynamic_cast<BPackageManager::RemoteRepository*>(repository)
1033 != NULL) {
1034 depots[repository->Name()].AddPackage(modelInfo);
1035 remotePackages[modelInfo->Name()] = modelInfo;
1036 } else {
1037 if (repository == static_cast<const BSolverRepository*>(
1038 manager.SystemRepository())) {
1039 modelInfo->AddInstallationLocation(
1040 B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
1041 if (!modelInfo->IsSystemPackage()) {
1042 systemInstalledPackages[repoPackageInfo.FileName()]
1043 = modelInfo;
1045 } else if (repository == static_cast<const BSolverRepository*>(
1046 manager.HomeRepository())) {
1047 modelInfo->AddInstallationLocation(
1048 B_PACKAGE_INSTALLATION_LOCATION_HOME);
1052 if (modelInfo->IsSystemPackage())
1053 systemFlaggedPackages.Add(repoPackageInfo.FileName());
1056 bool wasEmpty = fModel.Depots().IsEmpty();
1057 if (force || wasEmpty)
1058 fModel.StopPopulatingAllPackages();
1060 BAutolock lock(fModel.Lock());
1062 if (force)
1063 fModel.Clear();
1065 // filter remote packages from the found list
1066 // any packages remaining will be locally installed packages
1067 // that weren't acquired from a repository
1068 for (PackageInfoMap::iterator it = remotePackages.begin();
1069 it != remotePackages.end(); it++) {
1070 foundPackages.erase(it->first);
1073 if (!foundPackages.empty()) {
1074 BString repoName = B_TRANSLATE("Local");
1075 depots[repoName] = DepotInfo(repoName);
1076 DepotInfoMap::iterator depot = depots.find(repoName);
1077 for (PackageInfoMap::iterator it = foundPackages.begin();
1078 it != foundPackages.end(); ++it) {
1079 depot->second.AddPackage(it->second);
1083 for (DepotInfoMap::iterator it = depots.begin(); it != depots.end(); it++) {
1084 if (fModel.HasDepot(it->second.Name()))
1085 fModel.SyncDepot(it->second);
1086 else
1087 fModel.AddDepot(it->second);
1090 fModel.PopulateWebAppRepositoryCodes();
1092 // start retrieving package icons and average ratings
1093 if (force || wasEmpty)
1094 fModel.PopulateAllPackages();
1096 // compute the OS package dependencies
1097 try {
1098 // create the solver
1099 BSolver* solver;
1100 status_t error = BSolver::Create(solver);
1101 if (error != B_OK)
1102 throw BFatalErrorException(error, "Failed to create solver.");
1104 ObjectDeleter<BSolver> solverDeleter(solver);
1105 BPath systemPath;
1106 error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
1107 if (error != B_OK) {
1108 throw BFatalErrorException(error,
1109 "Unable to retrieve system packages directory.");
1112 // add the "installed" repository with the given packages
1113 BSolverRepository installedRepository;
1115 BRepositoryBuilder installedRepositoryBuilder(installedRepository,
1116 "installed");
1117 for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
1118 BPath packagePath(systemPath);
1119 packagePath.Append(systemFlaggedPackages.StringAt(i));
1120 installedRepositoryBuilder.AddPackage(packagePath.Path());
1122 installedRepositoryBuilder.AddToSolver(solver, true);
1125 // add system repository
1126 BSolverRepository systemRepository;
1128 BRepositoryBuilder systemRepositoryBuilder(systemRepository,
1129 "system");
1130 for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
1131 it != systemInstalledPackages.end(); it++) {
1132 BPath packagePath(systemPath);
1133 packagePath.Append(it->first);
1134 systemRepositoryBuilder.AddPackage(packagePath.Path());
1136 systemRepositoryBuilder.AddToSolver(solver, false);
1139 // solve
1140 error = solver->VerifyInstallation();
1141 if (error != B_OK) {
1142 throw BFatalErrorException(error, "Failed to compute packages to "
1143 "install.");
1146 BSolverResult solverResult;
1147 error = solver->GetResult(solverResult);
1148 if (error != B_OK) {
1149 throw BFatalErrorException(error, "Failed to retrieve system "
1150 "package dependency list.");
1153 for (int32 i = 0; const BSolverResultElement* element
1154 = solverResult.ElementAt(i); i++) {
1155 BSolverPackage* package = element->Package();
1156 if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
1157 PackageInfoMap::iterator it = systemInstalledPackages.find(
1158 package->Info().FileName());
1159 if (it != systemInstalledPackages.end())
1160 it->second->SetSystemDependency(true);
1163 } catch (BFatalErrorException ex) {
1164 printf("Fatal exception occurred while resolving system dependencies: "
1165 "%s, details: %s\n", strerror(ex.Error()), ex.Details().String());
1166 } catch (BNothingToDoException) {
1167 // do nothing
1168 } catch (BException ex) {
1169 printf("Exception occurred while resolving system dependencies: %s\n",
1170 ex.Message().String());
1171 } catch (...) {
1172 printf("Unknown exception occurred while resolving system "
1173 "dependencies.\n");
1178 void
1179 MainWindow::_StartRefreshWorker(bool force)
1181 if (fModelWorker != B_BAD_THREAD_ID)
1182 return;
1184 RefreshWorkerParameters* parameters = new(std::nothrow)
1185 RefreshWorkerParameters(this, force);
1186 if (parameters == NULL)
1187 return;
1189 fWorkStatusView->SetBusy(B_TRANSLATE("Refreshing..."));
1191 ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1192 fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader",
1193 B_LOW_PRIORITY, parameters);
1195 if (fModelWorker > 0) {
1196 deleter.Detach();
1197 resume_thread(fModelWorker);
1202 status_t
1203 MainWindow::_RefreshModelThreadWorker(void* arg)
1205 RefreshWorkerParameters* parameters
1206 = reinterpret_cast<RefreshWorkerParameters*>(arg);
1207 MainWindow* mainWindow = parameters->window;
1208 ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1210 BMessenger messenger(mainWindow);
1212 mainWindow->_RefreshRepositories(parameters->forceRefresh);
1214 if (mainWindow->fTerminating)
1215 return B_OK;
1217 mainWindow->_RefreshPackageList(parameters->forceRefresh);
1219 messenger.SendMessage(MSG_MODEL_WORKER_DONE);
1221 return B_OK;
1225 status_t
1226 MainWindow::_PackageActionWorker(void* arg)
1228 MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1230 while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1231 PackageActionRef ref;
1233 AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1234 ref = window->fPendingActions.ItemAt(0);
1235 if (ref.Get() == NULL)
1236 break;
1237 window->fPendingActions.Remove(0);
1240 BMessenger messenger(window);
1241 BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1242 BString text(ref->Label());
1243 text << "...";
1244 busyMessage.AddString("reason", text);
1246 messenger.SendMessage(&busyMessage);
1247 ref->Perform();
1248 messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1251 return 0;
1255 status_t
1256 MainWindow::_PopulatePackageWorker(void* arg)
1258 MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1260 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1261 PackageInfoRef package;
1263 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1264 package = window->fPackageToPopulate;
1267 if (package.Get() != NULL) {
1268 window->fModel.PopulatePackage(package,
1269 Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS
1270 | Model::POPULATE_CHANGELOG);
1274 return 0;
1278 /* static */ status_t
1279 MainWindow::_PackagesToShowWorker(void* arg)
1281 MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1283 while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1284 PackageList packageList;
1285 int32 listID = 0;
1287 AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1288 packageList = window->fPackagesToShowList;
1289 listID = atomic_get(&window->fPackagesToShowListID);
1290 window->fPackagesToShowList.Clear();
1293 // Add packages to list views in batches of kPackagesPerUpdate so we
1294 // don't block the window thread for long with each iteration
1295 enum {
1296 kPackagesPerUpdate = 20
1298 uint32 packagesInMessage = 0;
1299 BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1300 BMessenger messenger(window);
1301 bool listIsOutdated = false;
1303 for (int i = 0; i < packageList.CountItems(); i++) {
1304 const PackageInfoRef& package = packageList.ItemAtFast(i);
1306 if (packagesInMessage >= kPackagesPerUpdate) {
1307 if (listID != atomic_get(&window->fPackagesToShowListID)) {
1308 // The model was changed again in the meantime, and thus
1309 // our package list isn't current anymore. Send no further
1310 // messags based on this list and go back to start.
1311 listIsOutdated = true;
1312 break;
1315 message.AddInt32("list_id", listID);
1316 messenger.SendMessage(&message);
1317 message.MakeEmpty();
1318 packagesInMessage = 0;
1320 // Don't spam the window's message queue, which would make it
1321 // unresponsive (i.e. allows UI messages to get in between our
1322 // messages). When it has processed the message we just sent,
1323 // it will let us know by releasing the semaphore.
1324 acquire_sem(window->fShowPackagesAcknowledgeSem);
1326 package->AcquireReference();
1327 message.AddPointer("package_ref", package.Get());
1328 packagesInMessage++;
1331 if (listIsOutdated)
1332 continue;
1334 // Send remaining package infos, if any, which didn't make it into
1335 // the last message (count < kPackagesPerUpdate)
1336 if (packagesInMessage > 0) {
1337 message.AddInt32("list_id", listID);
1338 messenger.SendMessage(&message);
1339 acquire_sem(window->fShowPackagesAcknowledgeSem);
1342 // Update selected package in list views
1343 messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1346 return 0;
1350 void
1351 MainWindow::_NotifyUser(const char* title, const char* message)
1353 BAlert* alert = new(std::nothrow) BAlert(title, message,
1354 B_TRANSLATE("Close"));
1356 if (alert != NULL)
1357 alert->Go();
1361 void
1362 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1364 UserLoginWindow* window = new UserLoginWindow(this,
1365 BRect(0, 0, 500, 400), fModel);
1367 if (onSuccessMessage.what != 0)
1368 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1370 window->Show();
1374 void
1375 MainWindow::_UpdateAuthorization()
1377 BString username(fModel.Username());
1378 bool hasUser = !username.IsEmpty();
1380 if (fLogOutItem != NULL)
1381 fLogOutItem->SetEnabled(hasUser);
1382 if (fLogInItem != NULL) {
1383 if (hasUser)
1384 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1385 else
1386 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1389 if (fUserMenu != NULL) {
1390 BString label;
1391 if (username.Length() == 0) {
1392 label = B_TRANSLATE("Not logged in");
1393 } else {
1394 label = B_TRANSLATE("Logged in as %User%");
1395 label.ReplaceAll("%User%", username);
1397 fUserMenu->Superitem()->SetLabel(label);
1402 void
1403 MainWindow::_RatePackage()
1405 if (fModel.Username().IsEmpty()) {
1406 BAlert* alert = new(std::nothrow) BAlert(
1407 B_TRANSLATE("Not logged in"),
1408 B_TRANSLATE("You need to be logged into an account before you "
1409 "can rate packages."),
1410 B_TRANSLATE("Cancel"),
1411 B_TRANSLATE("Login or Create account"));
1413 if (alert == NULL)
1414 return;
1416 int32 choice = alert->Go();
1417 if (choice == 1)
1418 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1419 return;
1422 // TODO: Allow only one RatePackageWindow
1423 // TODO: Mechanism for remembering the window frame
1424 RatePackageWindow* window = new RatePackageWindow(this,
1425 BRect(0, 0, 500, 400), fModel);
1426 window->SetPackage(fPackageInfoView->Package());
1427 window->Show();
1431 void
1432 MainWindow::_ShowScreenshot()
1434 // TODO: Mechanism for remembering the window frame
1435 if (fScreenshotWindow == NULL)
1436 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1438 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1439 return;
1441 fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1443 if (fScreenshotWindow->IsHidden())
1444 fScreenshotWindow->Show();
1445 else
1446 fScreenshotWindow->Activate();
1448 fScreenshotWindow->Unlock();