vfs: check userland buffers before reading them.
[haiku.git] / src / apps / haikudepot / ui / MainWindow.cpp
bloba06ca7f04767e585acbbdffb5a66428a9ff9e10d
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_SOFTWARE_UPDATER = 'mswu',
72 MSG_LOG_IN = 'lgin',
73 MSG_LOG_OUT = 'lgot',
74 MSG_AUTHORIZATION_CHANGED = 'athc',
75 MSG_PACKAGE_CHANGED = 'pchd',
77 MSG_SHOW_FEATURED_PACKAGES = 'sofp',
78 MSG_SHOW_AVAILABLE_PACKAGES = 'savl',
79 MSG_SHOW_INSTALLED_PACKAGES = 'sins',
80 MSG_SHOW_SOURCE_PACKAGES = 'ssrc',
81 MSG_SHOW_DEVELOP_PACKAGES = 'sdvl'
85 using namespace BPackageKit;
86 using namespace BPackageKit::BManager::BPrivate;
89 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
90 typedef std::map<BString, DepotInfo> DepotInfoMap;
93 struct RefreshWorkerParameters {
94 MainWindow* window;
95 bool forceRefresh;
97 RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
99 window(window),
100 forceRefresh(forceRefresh)
106 class MessageModelListener : public ModelListener {
107 public:
108 MessageModelListener(const BMessenger& messenger)
110 fMessenger(messenger)
114 virtual void AuthorizationChanged()
116 if (fMessenger.IsValid())
117 fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
120 private:
121 BMessenger fMessenger;
125 MainWindow::MainWindow(const BMessage& settings)
127 BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
128 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
129 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
130 fScreenshotWindow(NULL),
131 fUserMenu(NULL),
132 fLogInItem(NULL),
133 fLogOutItem(NULL),
134 fModelListener(new MessageModelListener(BMessenger(this)), true),
135 fBulkLoadStateMachine(&fModel),
136 fTerminating(false),
137 fSinglePackageMode(false),
138 fModelWorker(B_BAD_THREAD_ID)
140 BMenuBar* menuBar = new BMenuBar(B_TRANSLATE("Main Menu"));
141 _BuildMenu(menuBar);
143 BMenuBar* userMenuBar = new BMenuBar(B_TRANSLATE("User Menu"));
144 _BuildUserMenu(userMenuBar);
145 set_small_font(userMenuBar);
146 userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
147 menuBar->MaxSize().height));
149 fFilterView = new FilterView();
150 fFeaturedPackagesView = new FeaturedPackagesView();
151 fPackageListView = new PackageListView(fModel.Lock());
152 fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
154 fSplitView = new BSplitView(B_VERTICAL, 5.0f);
156 BGroupView* featuredPackagesGroup = new BGroupView(B_VERTICAL);
157 BStringView* featuredPackagesTitle = new BStringView(
158 "featured packages title", B_TRANSLATE("Featured packages"));
159 BFont font(be_bold_font);
160 font.SetSize(font.Size() * 1.3f);
161 featuredPackagesTitle->SetFont(&font);
162 featuredPackagesGroup->SetExplicitMaxSize(
163 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
164 BLayoutBuilder::Group<>(featuredPackagesGroup)
165 .Add(featuredPackagesTitle)
166 .Add(fFeaturedPackagesView)
169 fWorkStatusView = new WorkStatusView("work status");
170 fPackageListView->AttachWorkStatusView(fWorkStatusView);
172 BView* listArea = new BView("list area", 0);
173 fListLayout = new BCardLayout();
174 listArea->SetLayout(fListLayout);
175 listArea->AddChild(featuredPackagesGroup);
176 listArea->AddChild(fPackageListView);
178 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
179 .AddGroup(B_HORIZONTAL, 0.0f)
180 .Add(menuBar, 1.0f)
181 .Add(userMenuBar, 0.0f)
182 .End()
183 .Add(fFilterView)
184 .AddSplit(fSplitView)
185 .AddGroup(B_VERTICAL)
186 .Add(listArea)
187 .SetInsets(
188 B_USE_DEFAULT_SPACING, 0.0f,
189 B_USE_DEFAULT_SPACING, 0.0f)
190 .End()
191 .Add(fPackageInfoView)
192 .End()
193 .Add(fWorkStatusView)
196 fSplitView->SetCollapsible(0, false);
197 fSplitView->SetCollapsible(1, false);
199 fModel.AddListener(fModelListener);
201 // Restore settings
202 BMessage columnSettings;
203 if (settings.FindMessage("column settings", &columnSettings) == B_OK)
204 fPackageListView->LoadState(&columnSettings);
206 bool showOption;
207 if (settings.FindBool("show featured packages", &showOption) == B_OK)
208 fModel.SetShowFeaturedPackages(showOption);
209 if (settings.FindBool("show available packages", &showOption) == B_OK)
210 fModel.SetShowAvailablePackages(showOption);
211 if (settings.FindBool("show installed packages", &showOption) == B_OK)
212 fModel.SetShowInstalledPackages(showOption);
213 if (settings.FindBool("show develop packages", &showOption) == B_OK)
214 fModel.SetShowDevelopPackages(showOption);
215 if (settings.FindBool("show source packages", &showOption) == B_OK)
216 fModel.SetShowSourcePackages(showOption);
218 if (fModel.ShowFeaturedPackages())
219 fListLayout->SetVisibleItem((int32)0);
220 else
221 fListLayout->SetVisibleItem(1);
223 _RestoreUserName(settings);
224 _RestoreWindowFrame(settings);
226 atomic_set(&fPackagesToShowListID, 0);
228 // start worker threads
229 BPackageRoster().StartWatching(this,
230 B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
232 _StartRefreshWorker();
234 _InitWorkerThreads();
238 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
240 BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
241 B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
242 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
243 fWorkStatusView(NULL),
244 fScreenshotWindow(NULL),
245 fUserMenu(NULL),
246 fLogInItem(NULL),
247 fLogOutItem(NULL),
248 fModelListener(new MessageModelListener(BMessenger(this)), true),
249 fBulkLoadStateMachine(&fModel),
250 fTerminating(false),
251 fSinglePackageMode(true),
252 fModelWorker(B_BAD_THREAD_ID)
254 fFilterView = new FilterView();
255 fPackageListView = new PackageListView(fModel.Lock());
256 fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
258 BLayoutBuilder::Group<>(this, B_VERTICAL)
259 .Add(fPackageInfoView)
260 .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
263 fModel.AddListener(fModelListener);
265 // Restore settings
266 _RestoreUserName(settings);
267 _RestoreWindowFrame(settings);
269 fPackageInfoView->SetPackage(package);
271 _InitWorkerThreads();
275 MainWindow::~MainWindow()
277 BPackageRoster().StopWatching(this);
279 fTerminating = true;
280 if (fModelWorker >= 0)
281 wait_for_thread(fModelWorker, NULL);
283 delete_sem(fPendingActionsSem);
284 if (fPendingActionsWorker >= 0)
285 wait_for_thread(fPendingActionsWorker, NULL);
287 delete_sem(fPackageToPopulateSem);
288 if (fPopulatePackageWorker >= 0)
289 wait_for_thread(fPopulatePackageWorker, NULL);
291 delete_sem(fNewPackagesToShowSem);
292 delete_sem(fShowPackagesAcknowledgeSem);
293 if (fShowPackagesWorker >= 0)
294 wait_for_thread(fShowPackagesWorker, NULL);
296 if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
297 fScreenshotWindow->Quit();
301 bool
302 MainWindow::QuitRequested()
304 BMessage settings;
305 StoreSettings(settings);
307 BMessage message(MSG_MAIN_WINDOW_CLOSED);
308 message.AddMessage("window settings", &settings);
310 be_app->PostMessage(&message);
312 return true;
316 void
317 MainWindow::MessageReceived(BMessage* message)
319 switch (message->what) {
320 case MSG_MODEL_WORKER_DONE:
322 fModelWorker = B_BAD_THREAD_ID;
323 _AdoptModel();
324 fFilterView->AdoptModel(fModel);
325 fWorkStatusView->SetIdle();
326 break;
328 case B_SIMPLE_DATA:
329 case B_REFS_RECEIVED:
330 // TODO: ?
331 break;
333 case B_PACKAGE_UPDATE:
334 // TODO: We should do a more selective update depending on the
335 // "event", "location", and "change count" fields!
336 _StartRefreshWorker(false);
337 break;
339 case MSG_REFRESH_REPOS:
340 _StartRefreshWorker(true);
341 break;
343 case MSG_MANAGE_REPOS:
344 be_roster->Launch("application/x-vnd.Haiku-Repositories");
345 break;
347 case MSG_SOFTWARE_UPDATER:
348 be_roster->Launch("application/x-vnd.haiku-softwareupdater");
349 break;
351 case MSG_LOG_IN:
352 _OpenLoginWindow(BMessage());
353 break;
355 case MSG_LOG_OUT:
356 fModel.SetUsername("");
357 break;
359 case MSG_AUTHORIZATION_CHANGED:
360 _UpdateAuthorization();
361 break;
363 case MSG_SHOW_FEATURED_PACKAGES:
365 BAutolock locker(fModel.Lock());
366 fModel.SetShowFeaturedPackages(
367 !fModel.ShowFeaturedPackages());
369 _AdoptModel();
370 break;
372 case MSG_SHOW_AVAILABLE_PACKAGES:
374 BAutolock locker(fModel.Lock());
375 fModel.SetShowAvailablePackages(
376 !fModel.ShowAvailablePackages());
378 _AdoptModel();
379 break;
381 case MSG_SHOW_INSTALLED_PACKAGES:
383 BAutolock locker(fModel.Lock());
384 fModel.SetShowInstalledPackages(
385 !fModel.ShowInstalledPackages());
387 _AdoptModel();
388 break;
390 case MSG_SHOW_SOURCE_PACKAGES:
392 BAutolock locker(fModel.Lock());
393 fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
395 _AdoptModel();
396 break;
398 case MSG_SHOW_DEVELOP_PACKAGES:
400 BAutolock locker(fModel.Lock());
401 fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
403 _AdoptModel();
404 break;
406 case MSG_PACKAGE_SELECTED:
408 BString name;
409 if (message->FindString("name", &name) == B_OK) {
410 BAutolock locker(fModel.Lock());
411 int count = fVisiblePackages.CountItems();
412 for (int i = 0; i < count; i++) {
413 const PackageInfoRef& package
414 = fVisiblePackages.ItemAtFast(i);
415 if (package.Get() != NULL && package->Name() == name) {
416 locker.Unlock();
417 _AdoptPackage(package);
418 break;
421 } else {
422 _ClearPackage();
424 break;
427 case MSG_CATEGORY_SELECTED:
429 BString name;
430 if (message->FindString("name", &name) != B_OK)
431 name = "";
433 BAutolock locker(fModel.Lock());
434 fModel.SetCategory(name);
436 _AdoptModel();
437 break;
440 case MSG_DEPOT_SELECTED:
442 BString name;
443 if (message->FindString("name", &name) != B_OK)
444 name = "";
446 BAutolock locker(fModel.Lock());
447 fModel.SetDepot(name);
449 _AdoptModel();
450 break;
453 case MSG_SEARCH_TERMS_MODIFIED:
455 // TODO: Do this with a delay!
456 BString searchTerms;
457 if (message->FindString("search terms", &searchTerms) != B_OK)
458 searchTerms = "";
460 BAutolock locker(fModel.Lock());
461 fModel.SetSearchTerms(searchTerms);
463 _AdoptModel();
464 break;
467 case MSG_PACKAGE_CHANGED:
469 PackageInfo* info;
470 if (message->FindPointer("package", (void**)&info) == B_OK) {
471 PackageInfoRef ref(info, true);
472 uint32 changes;
473 if (message->FindUInt32("changes", &changes) != B_OK)
474 changes = 0;
475 if ((changes & PKG_CHANGED_STATE) != 0) {
476 BAutolock locker(fModel.Lock());
477 fModel.SetPackageState(ref, ref->State());
480 // Asynchronous updates to the package information
481 // can mean that the package needs to be added or
482 // removed to/from the visible packages when the current
483 // filter parameters are re-evaluated on this package.
484 bool wasVisible = fVisiblePackages.Contains(ref);
485 bool isVisible;
487 BAutolock locker(fModel.Lock());
488 // The package didn't get a chance yet to be in the
489 // visible package list
490 isVisible = fModel.MatchesFilter(ref);
492 // Transfer this single package, otherwise we miss
493 // other packages if they appear or disappear along
494 // with this one before receive a notification for
495 // them.
496 if (isVisible) {
497 fVisiblePackages.Add(ref);
498 } else if (wasVisible)
499 fVisiblePackages.Remove(ref);
502 if (wasVisible != isVisible) {
503 if (!isVisible) {
504 fPackageListView->RemovePackage(ref);
505 fFeaturedPackagesView->RemovePackage(ref);
506 } else {
507 fPackageListView->AddPackage(ref);
508 if (ref->IsProminent())
509 fFeaturedPackagesView->AddPackage(ref);
513 if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
514 fWorkStatusView->PackageStatusChanged(ref);
516 break;
519 case MSG_RATE_PACKAGE:
520 _RatePackage();
521 break;
523 case MSG_SHOW_SCREENSHOT:
524 _ShowScreenshot();
525 break;
527 case MSG_PACKAGE_WORKER_BUSY:
529 BString reason;
530 status_t status = message->FindString("reason", &reason);
531 if (status != B_OK)
532 break;
533 if (!fSinglePackageMode)
534 fWorkStatusView->SetBusy(reason);
535 break;
538 case MSG_PACKAGE_WORKER_IDLE:
539 if (!fSinglePackageMode)
540 fWorkStatusView->SetIdle();
541 break;
543 case MSG_ADD_VISIBLE_PACKAGES:
545 struct SemaphoreReleaser {
546 SemaphoreReleaser(sem_id semaphore)
548 fSemaphore(semaphore)
551 ~SemaphoreReleaser() { release_sem(fSemaphore); }
553 sem_id fSemaphore;
556 // Make sure acknowledge semaphore is released even on error,
557 // so the worker thread won't be blocked
558 SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
560 int32 numPackages = 0;
561 type_code unused;
562 status_t status = message->GetInfo("package_ref", &unused,
563 &numPackages);
564 if (status != B_OK || numPackages == 0)
565 break;
567 int32 listID = 0;
568 status = message->FindInt32("list_id", &listID);
569 if (status != B_OK)
570 break;
571 if (listID != atomic_get(&fPackagesToShowListID)) {
572 // list is outdated, ignore
573 break;
576 for (int i = 0; i < numPackages; i++) {
577 PackageInfo* packageRaw = NULL;
578 status = message->FindPointer("package_ref", i,
579 (void**)&packageRaw);
580 if (status != B_OK)
581 break;
582 PackageInfoRef package(packageRaw, true);
584 fPackageListView->AddPackage(package);
585 if (package->IsProminent())
586 fFeaturedPackagesView->AddPackage(package);
588 break;
591 case MSG_UPDATE_SELECTED_PACKAGE:
593 const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
594 fFeaturedPackagesView->SelectPackage(selectedPackage, true);
595 fPackageListView->SelectPackage(selectedPackage);
597 AutoLocker<BLocker> modelLocker(fModel.Lock());
598 if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
599 fPackageInfoView->Clear();
600 break;
603 default:
604 BWindow::MessageReceived(message);
605 break;
610 void
611 MainWindow::StoreSettings(BMessage& settings) const
613 settings.AddRect(_WindowFrameName(), Frame());
614 if (!fSinglePackageMode) {
615 settings.AddRect("window frame", Frame());
617 BMessage columnSettings;
618 fPackageListView->SaveState(&columnSettings);
620 settings.AddMessage("column settings", &columnSettings);
622 settings.AddBool("show featured packages",
623 fModel.ShowFeaturedPackages());
624 settings.AddBool("show available packages",
625 fModel.ShowAvailablePackages());
626 settings.AddBool("show installed packages",
627 fModel.ShowInstalledPackages());
628 settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
629 settings.AddBool("show source packages", fModel.ShowSourcePackages());
632 settings.AddString("username", fModel.Username());
636 void
637 MainWindow::PackageChanged(const PackageInfoEvent& event)
639 uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
640 if ((event.Changes() & whatchedChanges) != 0) {
641 PackageInfoRef ref(event.Package());
642 BMessage message(MSG_PACKAGE_CHANGED);
643 message.AddPointer("package", ref.Get());
644 message.AddUInt32("changes", event.Changes());
645 ref.Detach();
646 // reference needs to be released by MessageReceived();
647 PostMessage(&message);
652 status_t
653 MainWindow::SchedulePackageActions(PackageActionList& list)
655 AutoLocker<BLocker> lock(&fPendingActionsLock);
656 for (int32 i = 0; i < list.CountItems(); i++) {
657 if (!fPendingActions.Add(list.ItemAtFast(i)))
658 return B_NO_MEMORY;
661 return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
665 Model*
666 MainWindow::GetModel()
668 return &fModel;
672 void
673 MainWindow::_BuildMenu(BMenuBar* menuBar)
675 BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
676 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"),
677 new BMessage(MSG_REFRESH_REPOS)));
678 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
679 B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
680 menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
681 B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
683 menuBar->AddItem(menu);
685 menu = new BMenu(B_TRANSLATE("Show"));
687 fShowFeaturedPackagesItem = new BMenuItem(
688 B_TRANSLATE("Only featured packages"),
689 new BMessage(MSG_SHOW_FEATURED_PACKAGES));
690 menu->AddItem(fShowFeaturedPackagesItem);
692 menu->AddSeparatorItem();
694 fShowAvailablePackagesItem = new BMenuItem(
695 B_TRANSLATE("Available packages"),
696 new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
697 menu->AddItem(fShowAvailablePackagesItem);
699 fShowInstalledPackagesItem = new BMenuItem(
700 B_TRANSLATE("Installed packages"),
701 new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
702 menu->AddItem(fShowInstalledPackagesItem);
704 menu->AddSeparatorItem();
706 fShowDevelopPackagesItem = new BMenuItem(
707 B_TRANSLATE("Develop packages"),
708 new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
709 menu->AddItem(fShowDevelopPackagesItem);
711 fShowSourcePackagesItem = new BMenuItem(
712 B_TRANSLATE("Source packages"),
713 new BMessage(MSG_SHOW_SOURCE_PACKAGES));
714 menu->AddItem(fShowSourcePackagesItem);
716 menuBar->AddItem(menu);
720 void
721 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
723 fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
725 fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
726 new BMessage(MSG_LOG_IN));
727 fUserMenu->AddItem(fLogInItem);
729 fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
730 new BMessage(MSG_LOG_OUT));
731 fUserMenu->AddItem(fLogOutItem);
733 menuBar->AddItem(fUserMenu);
737 void
738 MainWindow::_RestoreUserName(const BMessage& settings)
740 BString username;
741 if (settings.FindString("username", &username) == B_OK
742 && username.Length() > 0) {
743 fModel.SetUsername(username);
748 const char*
749 MainWindow::_WindowFrameName() const
751 if (fSinglePackageMode)
752 return "small window frame";
754 return "window frame";
758 void
759 MainWindow::_RestoreWindowFrame(const BMessage& settings)
761 BRect frame = Frame();
763 BRect windowFrame;
764 bool fromSettings = false;
765 if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
766 frame = windowFrame;
767 fromSettings = true;
768 } else if (!fSinglePackageMode) {
769 // Resize to occupy a certain screen size
770 BRect screenFrame = BScreen(this).Frame();
771 float width = frame.Width();
772 float height = frame.Height();
773 if (width < screenFrame.Width() * .666f
774 && height < screenFrame.Height() * .666f) {
775 frame.bottom = frame.top + screenFrame.Height() * .666f;
776 frame.right = frame.left
777 + std::min(screenFrame.Width() * .666f, height * 7 / 5);
781 MoveTo(frame.LeftTop());
782 ResizeTo(frame.Width(), frame.Height());
784 if (fromSettings)
785 MoveOnScreen();
786 else
787 CenterOnScreen();
791 void
792 MainWindow::_InitWorkerThreads()
794 fPendingActionsSem = create_sem(0, "PendingPackageActions");
795 if (fPendingActionsSem >= 0) {
796 fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
797 "Planet Express", B_NORMAL_PRIORITY, this);
798 if (fPendingActionsWorker >= 0)
799 resume_thread(fPendingActionsWorker);
800 } else
801 fPendingActionsWorker = -1;
803 fPackageToPopulateSem = create_sem(0, "PopulatePackage");
804 if (fPackageToPopulateSem >= 0) {
805 fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
806 "Package Populator", B_NORMAL_PRIORITY, this);
807 if (fPopulatePackageWorker >= 0)
808 resume_thread(fPopulatePackageWorker);
809 } else
810 fPopulatePackageWorker = -1;
812 fNewPackagesToShowSem = create_sem(0, "ShowPackages");
813 fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
814 if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
815 fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
816 "Good news everyone", B_NORMAL_PRIORITY, this);
817 if (fShowPackagesWorker >= 0)
818 resume_thread(fShowPackagesWorker);
819 } else
820 fShowPackagesWorker = -1;
824 void
825 MainWindow::_AdoptModel()
827 fVisiblePackages = fModel.CreatePackageList();
830 AutoLocker<BLocker> modelLocker(fModel.Lock());
831 AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
832 fPackagesToShowList = fVisiblePackages;
833 atomic_add(&fPackagesToShowListID, 1);
836 fFeaturedPackagesView->Clear();
837 fPackageListView->Clear();
839 release_sem(fNewPackagesToShowSem);
841 BAutolock locker(fModel.Lock());
842 fShowFeaturedPackagesItem->SetMarked(fModel.ShowFeaturedPackages());
843 fShowFeaturedPackagesItem->SetEnabled(fModel.SearchTerms() == "");
844 fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
845 fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
846 fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
847 fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
849 if (fModel.ShowFeaturedPackages() && fModel.SearchTerms() == "")
850 fListLayout->SetVisibleItem((int32)0);
851 else
852 fListLayout->SetVisibleItem((int32)1);
856 void
857 MainWindow::_AdoptPackage(const PackageInfoRef& package)
860 BAutolock locker(fModel.Lock());
861 fPackageInfoView->SetPackage(package);
863 if (fFeaturedPackagesView != NULL)
864 fFeaturedPackagesView->SelectPackage(package);
865 if (fPackageListView != NULL)
866 fPackageListView->SelectPackage(package);
869 // Trigger asynchronous package population from the web-app
871 AutoLocker<BLocker> lock(&fPackageToPopulateLock);
872 fPackageToPopulate = package;
874 release_sem_etc(fPackageToPopulateSem, 1, 0);
878 void
879 MainWindow::_ClearPackage()
881 fPackageInfoView->Clear();
885 void
886 MainWindow::_RefreshRepositories(bool force)
888 if (fSinglePackageMode)
889 return;
891 BPackageRoster roster;
892 BStringList repositoryNames;
894 status_t result = roster.GetRepositoryNames(repositoryNames);
895 if (result != B_OK)
896 return;
898 DecisionProvider decisionProvider;
899 JobStateListener listener;
900 BContext context(decisionProvider, listener);
902 BRepositoryCache cache;
903 for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) {
904 const BString& repoName = repositoryNames.StringAt(i);
905 BRepositoryConfig repoConfig;
906 result = roster.GetRepositoryConfig(repoName, &repoConfig);
907 if (result != B_OK) {
908 // TODO: notify user
909 continue;
912 if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) {
913 try {
914 BRefreshRepositoryRequest refreshRequest(context, repoConfig);
916 result = refreshRequest.Process();
917 } catch (BFatalErrorException ex) {
918 BString message(B_TRANSLATE("An error occurred while "
919 "refreshing the repository: %error% (%details%)"));
920 message.ReplaceFirst("%error%", ex.Message());
921 message.ReplaceFirst("%details%", ex.Details());
922 _NotifyUser("Error", message.String());
923 } catch (BException ex) {
924 BString message(B_TRANSLATE("An error occurred while "
925 "refreshing the repository: %error%"));
926 message.ReplaceFirst("%error%", ex.Message());
927 _NotifyUser("Error", message.String());
934 void
935 MainWindow::_RefreshPackageList(bool force)
937 if (fSinglePackageMode)
938 return;
940 BPackageRoster roster;
941 BStringList repositoryNames;
943 status_t result = roster.GetRepositoryNames(repositoryNames);
944 if (result != B_OK)
945 return;
947 DepotInfoMap depots;
948 for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
949 const BString& repoName = repositoryNames.StringAt(i);
950 DepotInfo depotInfo = DepotInfo(repoName);
952 BRepositoryConfig repoConfig;
953 status_t getRepositoryConfigStatus = roster.GetRepositoryConfig(
954 repoName, &repoConfig);
956 if (getRepositoryConfigStatus == B_OK) {
957 depotInfo.SetBaseURL(repoConfig.BaseURL());
958 } else {
959 printf("unable to obtain the repository config for local "
960 "repository '%s'; %s\n",
961 repoName.String(), strerror(getRepositoryConfigStatus));
964 depots[repoName] = depotInfo;
967 PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
968 try {
969 manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
970 | PackageManager::B_ADD_REMOTE_REPOSITORIES);
971 } catch (BException ex) {
972 BString message(B_TRANSLATE("An error occurred while "
973 "initializing the package manager: %message%"));
974 message.ReplaceFirst("%message%", ex.Message());
975 _NotifyUser("Error", message.String());
976 return;
979 BObjectList<BSolverPackage> packages;
980 result = manager.Solver()->FindPackages("",
981 BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
982 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
983 | BSolver::B_FIND_IN_PROVIDES,
984 packages);
985 if (result != B_OK) {
986 BString message(B_TRANSLATE("An error occurred while "
987 "obtaining the package list: %message%"));
988 message.ReplaceFirst("%message%", strerror(result));
989 _NotifyUser("Error", message.String());
990 return;
993 if (packages.IsEmpty())
994 return;
996 PackageInfoMap foundPackages;
997 // if a given package is installed locally, we will potentially
998 // get back multiple entries, one for each local installation
999 // location, and one for each remote repository the package
1000 // is available in. The above map is used to ensure that in such
1001 // cases we consolidate the information, rather than displaying
1002 // duplicates
1003 PackageInfoMap remotePackages;
1004 // any package that we find in a remote repository goes in this map.
1005 // this is later used to discern which packages came from a local
1006 // installation only, as those must be handled a bit differently
1007 // upon uninstallation, since we'd no longer be able to pull them
1008 // down remotely.
1009 BStringList systemFlaggedPackages;
1010 // any packages flagged as a system package are added to this list.
1011 // such packages cannot be uninstalled, nor can any of their deps.
1012 PackageInfoMap systemInstalledPackages;
1013 // any packages installed in system are added to this list.
1014 // This is later used for dependency resolution of the actual
1015 // system packages in order to compute the list of protected
1016 // dependencies indicated above.
1018 for (int32 i = 0; i < packages.CountItems(); i++) {
1019 BSolverPackage* package = packages.ItemAt(i);
1020 const BPackageInfo& repoPackageInfo = package->Info();
1021 const BString repositoryName = package->Repository()->Name();
1022 PackageInfoRef modelInfo;
1023 PackageInfoMap::iterator it = foundPackages.find(
1024 repoPackageInfo.Name());
1025 if (it != foundPackages.end())
1026 modelInfo.SetTo(it->second);
1027 else {
1028 // Add new package info
1029 modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
1030 true);
1032 if (modelInfo.Get() == NULL)
1033 return;
1035 modelInfo->SetDepotName(repositoryName);
1037 foundPackages[repoPackageInfo.Name()] = modelInfo;
1040 modelInfo->AddListener(this);
1042 BSolverRepository* repository = package->Repository();
1043 if (dynamic_cast<BPackageManager::RemoteRepository*>(repository)
1044 != NULL) {
1045 depots[repository->Name()].AddPackage(modelInfo);
1046 remotePackages[modelInfo->Name()] = modelInfo;
1047 } else {
1048 if (repository == static_cast<const BSolverRepository*>(
1049 manager.SystemRepository())) {
1050 modelInfo->AddInstallationLocation(
1051 B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
1052 if (!modelInfo->IsSystemPackage()) {
1053 systemInstalledPackages[repoPackageInfo.FileName()]
1054 = modelInfo;
1056 } else if (repository == static_cast<const BSolverRepository*>(
1057 manager.HomeRepository())) {
1058 modelInfo->AddInstallationLocation(
1059 B_PACKAGE_INSTALLATION_LOCATION_HOME);
1063 if (modelInfo->IsSystemPackage())
1064 systemFlaggedPackages.Add(repoPackageInfo.FileName());
1067 bool wasEmpty = fModel.Depots().IsEmpty();
1068 if (force || wasEmpty)
1069 fBulkLoadStateMachine.Stop();
1071 BAutolock lock(fModel.Lock());
1073 if (force)
1074 fModel.Clear();
1076 // filter remote packages from the found list
1077 // any packages remaining will be locally installed packages
1078 // that weren't acquired from a repository
1079 for (PackageInfoMap::iterator it = remotePackages.begin();
1080 it != remotePackages.end(); it++) {
1081 foundPackages.erase(it->first);
1084 if (!foundPackages.empty()) {
1085 BString repoName = B_TRANSLATE("Local");
1086 depots[repoName] = DepotInfo(repoName);
1087 DepotInfoMap::iterator depot = depots.find(repoName);
1088 for (PackageInfoMap::iterator it = foundPackages.begin();
1089 it != foundPackages.end(); ++it) {
1090 depot->second.AddPackage(it->second);
1094 for (DepotInfoMap::iterator it = depots.begin(); it != depots.end(); it++) {
1095 if (fModel.HasDepot(it->second.Name()))
1096 fModel.SyncDepot(it->second);
1097 else
1098 fModel.AddDepot(it->second);
1101 // start retrieving package icons and average ratings
1102 if (force || wasEmpty) {
1103 fBulkLoadStateMachine.Start();
1106 // compute the OS package dependencies
1107 try {
1108 // create the solver
1109 BSolver* solver;
1110 status_t error = BSolver::Create(solver);
1111 if (error != B_OK)
1112 throw BFatalErrorException(error, "Failed to create solver.");
1114 ObjectDeleter<BSolver> solverDeleter(solver);
1115 BPath systemPath;
1116 error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
1117 if (error != B_OK) {
1118 throw BFatalErrorException(error,
1119 "Unable to retrieve system packages directory.");
1122 // add the "installed" repository with the given packages
1123 BSolverRepository installedRepository;
1125 BRepositoryBuilder installedRepositoryBuilder(installedRepository,
1126 "installed");
1127 for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
1128 BPath packagePath(systemPath);
1129 packagePath.Append(systemFlaggedPackages.StringAt(i));
1130 installedRepositoryBuilder.AddPackage(packagePath.Path());
1132 installedRepositoryBuilder.AddToSolver(solver, true);
1135 // add system repository
1136 BSolverRepository systemRepository;
1138 BRepositoryBuilder systemRepositoryBuilder(systemRepository,
1139 "system");
1140 for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
1141 it != systemInstalledPackages.end(); it++) {
1142 BPath packagePath(systemPath);
1143 packagePath.Append(it->first);
1144 systemRepositoryBuilder.AddPackage(packagePath.Path());
1146 systemRepositoryBuilder.AddToSolver(solver, false);
1149 // solve
1150 error = solver->VerifyInstallation();
1151 if (error != B_OK) {
1152 throw BFatalErrorException(error, "Failed to compute packages to "
1153 "install.");
1156 BSolverResult solverResult;
1157 error = solver->GetResult(solverResult);
1158 if (error != B_OK) {
1159 throw BFatalErrorException(error, "Failed to retrieve system "
1160 "package dependency list.");
1163 for (int32 i = 0; const BSolverResultElement* element
1164 = solverResult.ElementAt(i); i++) {
1165 BSolverPackage* package = element->Package();
1166 if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
1167 PackageInfoMap::iterator it = systemInstalledPackages.find(
1168 package->Info().FileName());
1169 if (it != systemInstalledPackages.end())
1170 it->second->SetSystemDependency(true);
1173 } catch (BFatalErrorException ex) {
1174 printf("Fatal exception occurred while resolving system dependencies: "
1175 "%s, details: %s\n", strerror(ex.Error()), ex.Details().String());
1176 } catch (BNothingToDoException) {
1177 // do nothing
1178 } catch (BException ex) {
1179 printf("Exception occurred while resolving system dependencies: %s\n",
1180 ex.Message().String());
1181 } catch (...) {
1182 printf("Unknown exception occurred while resolving system "
1183 "dependencies.\n");
1188 void
1189 MainWindow::_StartRefreshWorker(bool force)
1191 if (fModelWorker != B_BAD_THREAD_ID)
1192 return;
1194 RefreshWorkerParameters* parameters = new(std::nothrow)
1195 RefreshWorkerParameters(this, force);
1196 if (parameters == NULL)
1197 return;
1199 fWorkStatusView->SetBusy(B_TRANSLATE("Refreshing..."));
1201 ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1202 fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader",
1203 B_LOW_PRIORITY, parameters);
1205 if (fModelWorker > 0) {
1206 deleter.Detach();
1207 resume_thread(fModelWorker);
1212 status_t
1213 MainWindow::_RefreshModelThreadWorker(void* arg)
1215 RefreshWorkerParameters* parameters
1216 = reinterpret_cast<RefreshWorkerParameters*>(arg);
1217 MainWindow* mainWindow = parameters->window;
1218 ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1220 BMessenger messenger(mainWindow);
1222 mainWindow->_RefreshRepositories(parameters->forceRefresh);
1224 if (mainWindow->fTerminating)
1225 return B_OK;
1227 mainWindow->_RefreshPackageList(parameters->forceRefresh);
1229 messenger.SendMessage(MSG_MODEL_WORKER_DONE);
1231 return B_OK;
1235 status_t
1236 MainWindow::_PackageActionWorker(void* arg)
1238 MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1240 while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1241 PackageActionRef ref;
1243 AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1244 ref = window->fPendingActions.ItemAt(0);
1245 if (ref.Get() == NULL)
1246 break;
1247 window->fPendingActions.Remove(0);
1250 BMessenger messenger(window);
1251 BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1252 BString text(ref->Label());
1253 text << "...";
1254 busyMessage.AddString("reason", text);
1256 messenger.SendMessage(&busyMessage);
1257 ref->Perform();
1258 messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1261 return 0;
1265 status_t
1266 MainWindow::_PopulatePackageWorker(void* arg)
1268 MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1270 while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1271 PackageInfoRef package;
1273 AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1274 package = window->fPackageToPopulate;
1277 if (package.Get() != NULL) {
1278 window->fModel.PopulatePackage(package,
1279 Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS
1280 | Model::POPULATE_CHANGELOG);
1284 return 0;
1288 /* static */ status_t
1289 MainWindow::_PackagesToShowWorker(void* arg)
1291 MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1293 while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1294 PackageList packageList;
1295 int32 listID = 0;
1297 AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1298 packageList = window->fPackagesToShowList;
1299 listID = atomic_get(&window->fPackagesToShowListID);
1300 window->fPackagesToShowList.Clear();
1303 // Add packages to list views in batches of kPackagesPerUpdate so we
1304 // don't block the window thread for long with each iteration
1305 enum {
1306 kPackagesPerUpdate = 20
1308 uint32 packagesInMessage = 0;
1309 BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1310 BMessenger messenger(window);
1311 bool listIsOutdated = false;
1313 for (int i = 0; i < packageList.CountItems(); i++) {
1314 const PackageInfoRef& package = packageList.ItemAtFast(i);
1316 if (packagesInMessage >= kPackagesPerUpdate) {
1317 if (listID != atomic_get(&window->fPackagesToShowListID)) {
1318 // The model was changed again in the meantime, and thus
1319 // our package list isn't current anymore. Send no further
1320 // messags based on this list and go back to start.
1321 listIsOutdated = true;
1322 break;
1325 message.AddInt32("list_id", listID);
1326 messenger.SendMessage(&message);
1327 message.MakeEmpty();
1328 packagesInMessage = 0;
1330 // Don't spam the window's message queue, which would make it
1331 // unresponsive (i.e. allows UI messages to get in between our
1332 // messages). When it has processed the message we just sent,
1333 // it will let us know by releasing the semaphore.
1334 acquire_sem(window->fShowPackagesAcknowledgeSem);
1336 package->AcquireReference();
1337 message.AddPointer("package_ref", package.Get());
1338 packagesInMessage++;
1341 if (listIsOutdated)
1342 continue;
1344 // Send remaining package infos, if any, which didn't make it into
1345 // the last message (count < kPackagesPerUpdate)
1346 if (packagesInMessage > 0) {
1347 message.AddInt32("list_id", listID);
1348 messenger.SendMessage(&message);
1349 acquire_sem(window->fShowPackagesAcknowledgeSem);
1352 // Update selected package in list views
1353 messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1356 return 0;
1360 void
1361 MainWindow::_NotifyUser(const char* title, const char* message)
1363 BAlert* alert = new(std::nothrow) BAlert(title, message,
1364 B_TRANSLATE("Close"));
1366 if (alert != NULL)
1367 alert->Go();
1371 void
1372 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1374 UserLoginWindow* window = new UserLoginWindow(this,
1375 BRect(0, 0, 500, 400), fModel);
1377 if (onSuccessMessage.what != 0)
1378 window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1380 window->Show();
1384 void
1385 MainWindow::_UpdateAuthorization()
1387 BString username(fModel.Username());
1388 bool hasUser = !username.IsEmpty();
1390 if (fLogOutItem != NULL)
1391 fLogOutItem->SetEnabled(hasUser);
1392 if (fLogInItem != NULL) {
1393 if (hasUser)
1394 fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1395 else
1396 fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1399 if (fUserMenu != NULL) {
1400 BString label;
1401 if (username.Length() == 0) {
1402 label = B_TRANSLATE("Not logged in");
1403 } else {
1404 label = B_TRANSLATE("Logged in as %User%");
1405 label.ReplaceAll("%User%", username);
1407 fUserMenu->Superitem()->SetLabel(label);
1412 void
1413 MainWindow::_RatePackage()
1415 if (fModel.Username().IsEmpty()) {
1416 BAlert* alert = new(std::nothrow) BAlert(
1417 B_TRANSLATE("Not logged in"),
1418 B_TRANSLATE("You need to be logged into an account before you "
1419 "can rate packages."),
1420 B_TRANSLATE("Cancel"),
1421 B_TRANSLATE("Login or Create account"));
1423 if (alert == NULL)
1424 return;
1426 int32 choice = alert->Go();
1427 if (choice == 1)
1428 _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1429 return;
1432 // TODO: Allow only one RatePackageWindow
1433 // TODO: Mechanism for remembering the window frame
1434 RatePackageWindow* window = new RatePackageWindow(this,
1435 BRect(0, 0, 500, 400), fModel);
1436 window->SetPackage(fPackageInfoView->Package());
1437 window->Show();
1441 void
1442 MainWindow::_ShowScreenshot()
1444 // TODO: Mechanism for remembering the window frame
1445 if (fScreenshotWindow == NULL)
1446 fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1448 if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1449 return;
1451 fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1453 if (fScreenshotWindow->IsHidden())
1454 fScreenshotWindow->Show();
1455 else
1456 fScreenshotWindow->Activate();
1458 fScreenshotWindow->Unlock();