xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision d0ac609964842f8cdb6d54b3c539c6c15293e172)
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