xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision 40afabe6a340b294781b7b56560a121e13ebf184)
1 /*
2  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2013, Rene Gollent, rene@gollent.com.
4  * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
5  * All rights reserved. Distributed under the terms of the MIT License.
6  */
7 
8 #include "MainWindow.h"
9 
10 #include <map>
11 
12 #include <stdio.h>
13 
14 #include <Alert.h>
15 #include <Autolock.h>
16 #include <Application.h>
17 #include <Button.h>
18 #include <Catalog.h>
19 #include <LayoutBuilder.h>
20 #include <MenuBar.h>
21 #include <MenuItem.h>
22 #include <Messenger.h>
23 #include <ScrollView.h>
24 #include <StringList.h>
25 #include <TabView.h>
26 
27 #include <package/Context.h>
28 #include <package/manager/Exceptions.h>
29 #include <package/manager/RepositoryBuilder.h>
30 #include <package/RefreshRepositoryRequest.h>
31 #include <package/PackageRoster.h>
32 #include "package/RepositoryCache.h"
33 #include <package/solver/SolverPackage.h>
34 #include <package/solver/SolverProblem.h>
35 #include <package/solver/SolverProblemSolution.h>
36 #include <package/solver/SolverRepository.h>
37 #include <package/solver/SolverResult.h>
38 
39 #include "AutoDeleter.h"
40 #include "AutoLocker.h"
41 #include "DecisionProvider.h"
42 #include "FilterView.h"
43 #include "JobStateListener.h"
44 #include "PackageInfoView.h"
45 #include "PackageListView.h"
46 #include "PackageManager.h"
47 #include "RatePackageWindow.h"
48 #include "UserLoginWindow.h"
49 
50 
51 #undef B_TRANSLATION_CONTEXT
52 #define B_TRANSLATION_CONTEXT "MainWindow"
53 
54 
55 enum {
56 	MSG_MODEL_WORKER_DONE		= 'mmwd',
57 	MSG_REFRESH_DEPOTS			= 'mrdp',
58 	MSG_LOG_IN					= 'lgin',
59 	MSG_LOG_OUT					= 'lgot',
60 	MSG_AUTHORIZATION_CHANGED	= 'athc',
61 	MSG_PACKAGE_STATE_CHANGED	= 'mpsc',
62 	MSG_SHOW_SOURCE_PACKAGES	= 'ssrc',
63 	MSG_SHOW_DEVELOP_PACKAGES	= 'sdvl'
64 };
65 
66 
67 using namespace BPackageKit;
68 using namespace BPackageKit::BManager::BPrivate;
69 
70 
71 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
72 typedef std::map<BString, DepotInfo> DepotInfoMap;
73 
74 
75 struct RefreshWorkerParameters {
76 	MainWindow* window;
77 	bool forceRefresh;
78 
79 	RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
80 		:
81 		window(window),
82 		forceRefresh(forceRefresh)
83 	{
84 	}
85 };
86 
87 
88 class MessageModelListener : public ModelListener {
89 public:
90 	MessageModelListener(const BMessenger& messenger)
91 		:
92 		fMessenger(messenger)
93 	{
94 	}
95 
96 	virtual void AuthorizationChanged()
97 	{
98 		if (fMessenger.IsValid())
99 			fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
100 	}
101 
102 private:
103 	BMessenger	fMessenger;
104 };
105 
106 
107 MainWindow::MainWindow(BRect frame, const BMessage& settings)
108 	:
109 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
110 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
111 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
112 	fModelListener(new MessageModelListener(BMessenger(this)), true),
113 	fTerminating(false),
114 	fSinglePackageMode(false),
115 	fModelWorker(B_BAD_THREAD_ID)
116 {
117 	BMenuBar* menuBar = new BMenuBar(B_TRANSLATE("Main Menu"));
118 	_BuildMenu(menuBar);
119 
120 	fFilterView = new FilterView();
121 	fPackageListView = new PackageListView(fModel.Lock());
122 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
123 
124 	fSplitView = new BSplitView(B_VERTICAL, 5.0f);
125 
126 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
127 		.Add(menuBar)
128 		.Add(fFilterView)
129 		.AddSplit(fSplitView)
130 			.AddGroup(B_VERTICAL)
131 				.Add(fPackageListView)
132 				.SetInsets(
133 					B_USE_DEFAULT_SPACING, 0.0f,
134 					B_USE_DEFAULT_SPACING, 0.0f)
135 			.End()
136 			.Add(fPackageInfoView)
137 		.End()
138 	;
139 
140 	fSplitView->SetCollapsible(0, false);
141 	fSplitView->SetCollapsible(1, false);
142 
143 	fModel.AddListener(fModelListener);
144 
145 	// Restore settings
146 	BMessage columnSettings;
147 	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
148 		fPackageListView->LoadState(&columnSettings);
149 
150 	bool showOption;
151 	if (settings.FindBool("show develop packages", &showOption) == B_OK)
152 		fModel.SetShowDevelopPackages(showOption);
153 	if (settings.FindBool("show source packages", &showOption) == B_OK)
154 		fModel.SetShowSourcePackages(showOption);
155 
156 	BString username;
157 	if (settings.FindString("username", &username) == B_OK
158 		&& username.Length() > 0) {
159 		fModel.SetUsername(username);
160 	}
161 
162 	// start worker threads
163 	BPackageRoster().StartWatching(this,
164 		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
165 
166 	_StartRefreshWorker();
167 
168 	_InitWorkerThreads();
169 }
170 
171 
172 MainWindow::MainWindow(BRect frame, const BMessage& settings,
173 	const PackageInfoRef& package)
174 	:
175 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
176 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
177 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
178 	fLogOutItem(NULL),
179 	fModelListener(new MessageModelListener(BMessenger(this)), true),
180 	fTerminating(false),
181 	fSinglePackageMode(true),
182 	fModelWorker(B_BAD_THREAD_ID)
183 {
184 	fFilterView = new FilterView();
185 	fPackageListView = new PackageListView(fModel.Lock());
186 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
187 
188 	BLayoutBuilder::Group<>(this, B_VERTICAL)
189 		.Add(fPackageInfoView)
190 		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
191 	;
192 
193 	fModel.AddListener(fModelListener);
194 
195 	// Restore settings
196 	BString username;
197 	if (settings.FindString("username", &username) == B_OK
198 		&& username.Length() > 0) {
199 		fModel.SetUsername(username);
200 	}
201 
202 	fPackageInfoView->SetPackage(package);
203 
204 	_InitWorkerThreads();
205 }
206 
207 
208 MainWindow::~MainWindow()
209 {
210 	BPackageRoster().StopWatching(this);
211 
212 	fTerminating = true;
213 	if (fModelWorker > 0)
214 		wait_for_thread(fModelWorker, NULL);
215 
216 	delete_sem(fPendingActionsSem);
217 	wait_for_thread(fPendingActionsWorker, NULL);
218 
219 	delete_sem(fPackageToPopulateSem);
220 	wait_for_thread(fPopulatePackageWorker, NULL);
221 }
222 
223 
224 bool
225 MainWindow::QuitRequested()
226 {
227 	BMessage settings;
228 	StoreSettings(settings);
229 
230 	BMessage message(MSG_MAIN_WINDOW_CLOSED);
231 	message.AddMessage("window settings", &settings);
232 
233 	be_app->PostMessage(&message);
234 
235 	return true;
236 }
237 
238 
239 void
240 MainWindow::MessageReceived(BMessage* message)
241 {
242 	switch (message->what) {
243 		case MSG_MODEL_WORKER_DONE:
244 		{
245 			fModelWorker = B_BAD_THREAD_ID;
246 			_AdoptModel();
247 			fFilterView->AdoptModel(fModel);
248 			break;
249 		}
250 		case B_SIMPLE_DATA:
251 		case B_REFS_RECEIVED:
252 			// TODO: ?
253 			break;
254 
255 		case B_PACKAGE_UPDATE:
256 			// TODO: We should do a more selective update depending on the
257 			// "event", "location", and "change count" fields!
258 			_StartRefreshWorker(false);
259 			break;
260 
261 		case MSG_REFRESH_DEPOTS:
262 			_StartRefreshWorker(true);
263 			break;
264 
265 		case MSG_LOG_IN:
266 			_OpenLoginWindow(BMessage());
267 			break;
268 
269 		case MSG_LOG_OUT:
270 			fModel.SetUsername("");
271 			break;
272 
273 		case MSG_AUTHORIZATION_CHANGED:
274 			_UpdateAuthorization();
275 			break;
276 
277 		case MSG_SHOW_SOURCE_PACKAGES:
278 			{
279 				BAutolock locker(fModel.Lock());
280 				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
281 			}
282 			_AdoptModel();
283 			break;
284 
285 		case MSG_SHOW_DEVELOP_PACKAGES:
286 			{
287 				BAutolock locker(fModel.Lock());
288 				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
289 			}
290 			_AdoptModel();
291 			break;
292 
293 		case MSG_PACKAGE_SELECTED:
294 		{
295 			BString title;
296 			if (message->FindString("title", &title) == B_OK) {
297 				int count = fVisiblePackages.CountItems();
298 				for (int i = 0; i < count; i++) {
299 					const PackageInfoRef& package
300 						= fVisiblePackages.ItemAtFast(i);
301 					if (package.Get() != NULL && package->Title() == title) {
302 						_AdoptPackage(package);
303 						break;
304 					}
305 				}
306 			} else {
307 				_ClearPackage();
308 			}
309 			break;
310 		}
311 
312 		case MSG_CATEGORY_SELECTED:
313 		{
314 			BString name;
315 			if (message->FindString("name", &name) != B_OK)
316 				name = "";
317 			{
318 				BAutolock locker(fModel.Lock());
319 				fModel.SetCategory(name);
320 			}
321 			_AdoptModel();
322 			break;
323 		}
324 
325 		case MSG_FILTER_SELECTED:
326 		{
327 			BString name;
328 			int32 value;
329 			if (message->FindString("name", &name) != B_OK
330 				|| message->FindInt32("be:value", &value) != B_OK) {
331 				break;
332 			}
333 			{
334 				BAutolock locker(fModel.Lock());
335 				if (name == "available") {
336 					fModel.SetShowAvailablePackages(
337 						value == B_CONTROL_ON);
338 				} else if (name == "installed") {
339 					fModel.SetShowInstalledPackages(
340 						value == B_CONTROL_ON);
341 				} else if (name == "development") {
342 					fModel.SetShowDevelopPackages(
343 						value == B_CONTROL_ON);
344 				} else if (name == "source code") {
345 					fModel.SetShowSourcePackages(
346 						value == B_CONTROL_ON);
347 				} else {
348 					break;
349 				}
350 			}
351 			_AdoptModel();
352 			break;
353 		}
354 
355 		case MSG_DEPOT_SELECTED:
356 		{
357 			BString name;
358 			if (message->FindString("name", &name) != B_OK)
359 				name = "";
360 			{
361 				BAutolock locker(fModel.Lock());
362 				fModel.SetDepot(name);
363 			}
364 			_AdoptModel();
365 			break;
366 		}
367 
368 		case MSG_SEARCH_TERMS_MODIFIED:
369 		{
370 			// TODO: Do this with a delay!
371 			BString searchTerms;
372 			if (message->FindString("search terms", &searchTerms) != B_OK)
373 				searchTerms = "";
374 			{
375 				BAutolock locker(fModel.Lock());
376 				fModel.SetSearchTerms(searchTerms);
377 			}
378 			_AdoptModel();
379 			break;
380 		}
381 
382 		case MSG_PACKAGE_STATE_CHANGED:
383 		{
384 			PackageInfo* info;
385 			if (message->FindPointer("package", (void**)&info) == B_OK) {
386 				PackageInfoRef ref(info, true);
387 				BAutolock locker(fModel.Lock());
388 				fModel.SetPackageState(ref, ref->State());
389 			}
390 			break;
391 		}
392 
393 		case MSG_RATE_PACKAGE:
394 			_RatePackage();
395 			break;
396 
397 		default:
398 			BWindow::MessageReceived(message);
399 			break;
400 	}
401 }
402 
403 
404 void
405 MainWindow::StoreSettings(BMessage& settings) const
406 {
407 	if (fSinglePackageMode)
408 		settings.AddRect("small window frame", Frame());
409 	else {
410 		settings.AddRect("window frame", Frame());
411 
412 		BMessage columnSettings;
413 		fPackageListView->SaveState(&columnSettings);
414 
415 		settings.AddMessage("column settings", &columnSettings);
416 
417 		settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
418 		settings.AddBool("show source packages", fModel.ShowSourcePackages());
419 	}
420 
421 	settings.AddString("username", fModel.Username());
422 }
423 
424 
425 void
426 MainWindow::PackageChanged(const PackageInfoEvent& event)
427 {
428 	if ((event.Changes() & PKG_CHANGED_STATE) != 0) {
429 		PackageInfoRef ref(event.Package());
430 		BMessage message(MSG_PACKAGE_STATE_CHANGED);
431 		message.AddPointer("package", ref.Get());
432 		ref.Detach();
433 			// reference needs to be released by MessageReceived();
434 		PostMessage(&message);
435 	}
436 }
437 
438 
439 status_t
440 MainWindow::SchedulePackageActions(PackageActionList& list)
441 {
442 	AutoLocker<BLocker> lock(&fPendingActionsLock);
443 	for (int32 i = 0; i < list.CountItems(); i++) {
444 		if (!fPendingActions.Add(list.ItemAtFast(i)))
445 			return B_NO_MEMORY;
446 	}
447 
448 	return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
449 }
450 
451 
452 Model*
453 MainWindow::GetModel()
454 {
455 	return &fModel;
456 }
457 
458 
459 void
460 MainWindow::_BuildMenu(BMenuBar* menuBar)
461 {
462 	BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
463 	menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh depots"),
464 			new BMessage(MSG_REFRESH_DEPOTS)));
465 	menu->AddSeparatorItem();
466 	menu->AddItem(new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
467 			new BMessage(MSG_LOG_IN)));
468 	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
469 		new BMessage(MSG_LOG_OUT));
470 	menu->AddItem(fLogOutItem);
471 	menuBar->AddItem(menu);
472 
473 //	menu = new BMenu(B_TRANSLATE("Options"));
474 //
475 //	fShowDevelopPackagesItem = new BMenuItem(
476 //		B_TRANSLATE("Show develop packages"),
477 //		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
478 //	menu->AddItem(fShowDevelopPackagesItem);
479 //
480 //	fShowSourcePackagesItem = new BMenuItem(B_TRANSLATE("Show source packages"),
481 //		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
482 //	menu->AddItem(fShowSourcePackagesItem);
483 //
484 //	menuBar->AddItem(menu);
485 }
486 
487 
488 void
489 MainWindow::_InitWorkerThreads()
490 {
491 	fPendingActionsSem = create_sem(0, "PendingPackageActions");
492 	if (fPendingActionsSem >= 0) {
493 		fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
494 			"Planet Express", B_NORMAL_PRIORITY, this);
495 		if (fPendingActionsWorker >= 0)
496 			resume_thread(fPendingActionsWorker);
497 	}
498 
499 	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
500 	if (fPackageToPopulateSem >= 0) {
501 		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
502 			"Package Populator", B_NORMAL_PRIORITY, this);
503 		if (fPopulatePackageWorker >= 0)
504 			resume_thread(fPopulatePackageWorker);
505 	}
506 }
507 
508 
509 void
510 MainWindow::_AdoptModel()
511 {
512 	fVisiblePackages = fModel.CreatePackageList();
513 
514 	fPackageListView->Clear();
515 	for (int32 i = 0; i < fVisiblePackages.CountItems(); i++) {
516 		BAutolock locker(fModel.Lock());
517 		fPackageListView->AddPackage(fVisiblePackages.ItemAtFast(i));
518 	}
519 
520 	BAutolock locker(fModel.Lock());
521 //	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
522 //	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
523 	fFilterView->AdoptCheckmarks(fModel);
524 }
525 
526 
527 void
528 MainWindow::_AdoptPackage(const PackageInfoRef& package)
529 {
530 	fPackageInfoView->SetPackage(package);
531 
532 	// Trigger asynchronous package population from the web-app
533 	{
534 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
535 		fPackageToPopulate = package;
536 	}
537 	release_sem_etc(fPackageToPopulateSem, 1, 0);
538 }
539 
540 
541 void
542 MainWindow::_ClearPackage()
543 {
544 	fPackageInfoView->Clear();
545 }
546 
547 
548 void
549 MainWindow::_RefreshRepositories(bool force)
550 {
551 	BPackageRoster roster;
552 	BStringList repositoryNames;
553 
554 	status_t result = roster.GetRepositoryNames(repositoryNames);
555 	if (result != B_OK)
556 		return;
557 
558 	DecisionProvider decisionProvider;
559 	JobStateListener listener;
560 	BContext context(decisionProvider, listener);
561 
562 	BRepositoryCache cache;
563 	for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) {
564 		const BString& repoName = repositoryNames.StringAt(i);
565 		BRepositoryConfig repoConfig;
566 		result = roster.GetRepositoryConfig(repoName, &repoConfig);
567 		if (result != B_OK) {
568 			// TODO: notify user
569 			continue;
570 		}
571 
572 		if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) {
573 			try {
574 				BRefreshRepositoryRequest refreshRequest(context, repoConfig);
575 
576 				result = refreshRequest.Process();
577 			} catch (BFatalErrorException ex) {
578 				BString message(B_TRANSLATE("An error occurred while "
579 					"refreshing the repository: %error% (%details%)"));
580  				message.ReplaceFirst("%error%", ex.Message());
581 				message.ReplaceFirst("%details%", ex.Details());
582 				_NotifyUser("Error", message.String());
583 			} catch (BException ex) {
584 				BString message(B_TRANSLATE("An error occurred while "
585 					"refreshing the repository: %error%"));
586 				message.ReplaceFirst("%error%", ex.Message());
587 				_NotifyUser("Error", message.String());
588 			}
589 		}
590 	}
591 }
592 
593 
594 void
595 MainWindow::_RefreshPackageList()
596 {
597 	BPackageRoster roster;
598 	BStringList repositoryNames;
599 
600 	status_t result = roster.GetRepositoryNames(repositoryNames);
601 	if (result != B_OK)
602 		return;
603 
604 	DepotInfoMap depots;
605 	for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
606 		const BString& repoName = repositoryNames.StringAt(i);
607 		depots[repoName] = DepotInfo(repoName);
608 	}
609 
610 	PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
611 	try {
612 		manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
613 			| PackageManager::B_ADD_REMOTE_REPOSITORIES);
614 	} catch (BException ex) {
615 		BString message(B_TRANSLATE("An error occurred while "
616 			"initializing the package manager: %message%"));
617 		message.ReplaceFirst("%message%", ex.Message());
618 		_NotifyUser("Error", message.String());
619 		return;
620 	}
621 
622 	BObjectList<BSolverPackage> packages;
623 	result = manager.Solver()->FindPackages("",
624 		BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
625 			| BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
626 			| BSolver::B_FIND_IN_PROVIDES,
627 		packages);
628 	if (result != B_OK) {
629 		BString message(B_TRANSLATE("An error occurred while "
630 			"obtaining the package list: %message%"));
631 		message.ReplaceFirst("%message%", strerror(result));
632 		_NotifyUser("Error", message.String());
633 		return;
634 	}
635 
636 	if (packages.IsEmpty())
637 		return;
638 
639 	PackageInfoMap foundPackages;
640 		// if a given package is installed locally, we will potentially
641 		// get back multiple entries, one for each local installation
642 		// location, and one for each remote repository the package
643 		// is available in. The above map is used to ensure that in such
644 		// cases we consolidate the information, rather than displaying
645 		// duplicates
646 	PackageInfoMap remotePackages;
647 		// any package that we find in a remote repository goes in this map.
648 		// this is later used to discern which packages came from a local
649 		// installation only, as those must be handled a bit differently
650 		// upon uninstallation, since we'd no longer be able to pull them
651 		// down remotely.
652 	BStringList systemFlaggedPackages;
653 		// any packages flagged as a system package are added to this list.
654 		// such packages cannot be uninstalled, nor can any of their deps.
655 	PackageInfoMap systemInstalledPackages;
656 		// any packages installed in system are added to this list.
657 		// This is later used for dependency resolution of the actual
658 		// system packages in order to compute the list of protected
659 		// dependencies indicated above.
660 
661 	for (int32 i = 0; i < packages.CountItems(); i++) {
662 		BSolverPackage* package = packages.ItemAt(i);
663 		const BPackageInfo& repoPackageInfo = package->Info();
664 		PackageInfoRef modelInfo;
665 		PackageInfoMap::iterator it = foundPackages.find(
666 			repoPackageInfo.Name());
667 		if (it != foundPackages.end())
668 			modelInfo.SetTo(it->second);
669 		else {
670 			// Add new package info
671 			modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
672 				true);
673 
674 			if (modelInfo.Get() == NULL)
675 				return;
676 
677 			foundPackages[repoPackageInfo.Name()] = modelInfo;
678 		}
679 
680 		modelInfo->AddListener(this);
681 
682 		BSolverRepository* repository = package->Repository();
683 		if (dynamic_cast<BPackageManager::RemoteRepository*>(repository)
684 				!= NULL) {
685 			depots[repository->Name()].AddPackage(modelInfo);
686 			remotePackages[modelInfo->Title()] = modelInfo;
687 		} else {
688 			if (repository == static_cast<const BSolverRepository*>(
689 					manager.SystemRepository())) {
690 				modelInfo->AddInstallationLocation(
691 					B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
692 				if (!modelInfo->IsSystemPackage()) {
693 					systemInstalledPackages[repoPackageInfo.FileName()]
694 						= modelInfo;
695 				}
696 			} else if (repository == static_cast<const BSolverRepository*>(
697 					manager.HomeRepository())) {
698 				modelInfo->AddInstallationLocation(
699 					B_PACKAGE_INSTALLATION_LOCATION_HOME);
700 			}
701 		}
702 
703 		if (modelInfo->IsSystemPackage())
704 			systemFlaggedPackages.Add(repoPackageInfo.FileName());
705 	}
706 
707 	BAutolock lock(fModel.Lock());
708 
709 	fModel.Clear();
710 
711 	// filter remote packages from the found list
712 	// any packages remaining will be locally installed packages
713 	// that weren't acquired from a repository
714 	for (PackageInfoMap::iterator it = remotePackages.begin();
715 			it != remotePackages.end(); it++) {
716 		foundPackages.erase(it->first);
717 	}
718 
719 	if (!foundPackages.empty()) {
720 		BString repoName = B_TRANSLATE("Local");
721 		depots[repoName] = DepotInfo(repoName);
722 		DepotInfoMap::iterator depot = depots.find(repoName);
723 		for (PackageInfoMap::iterator it = foundPackages.begin();
724 				it != foundPackages.end(); ++it) {
725 			depot->second.AddPackage(it->second);
726 		}
727 	}
728 
729 	for (DepotInfoMap::iterator it = depots.begin(); it != depots.end(); it++) {
730 		fModel.AddDepot(it->second);
731 	}
732 
733 	// start retrieving package icons and average ratings
734 	fModel.PopulateAllPackages();
735 
736 	// compute the OS package dependencies
737 	try {
738 		// create the solver
739 		BSolver* solver;
740 		status_t error = BSolver::Create(solver);
741 		if (error != B_OK)
742 			throw BFatalErrorException(error, "Failed to create solver.");
743 
744 		ObjectDeleter<BSolver> solverDeleter(solver);
745 		BPath systemPath;
746 		error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
747 		if (error != B_OK) {
748 			throw BFatalErrorException(error,
749 				"Unable to retrieve system packages directory.");
750 		}
751 
752 		// add the "installed" repository with the given packages
753 		BSolverRepository installedRepository;
754 		{
755 			BRepositoryBuilder installedRepositoryBuilder(installedRepository,
756 				"installed");
757 			for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
758 				BPath packagePath(systemPath);
759 				packagePath.Append(systemFlaggedPackages.StringAt(i));
760 				installedRepositoryBuilder.AddPackage(packagePath.Path());
761 			}
762 			installedRepositoryBuilder.AddToSolver(solver, true);
763 		}
764 
765 		// add system repository
766 		BSolverRepository systemRepository;
767 		{
768 			BRepositoryBuilder systemRepositoryBuilder(systemRepository,
769 				"system");
770 			for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
771 					it != systemInstalledPackages.end(); it++) {
772 				BPath packagePath(systemPath);
773 				packagePath.Append(it->first);
774 				systemRepositoryBuilder.AddPackage(packagePath.Path());
775 			}
776 			systemRepositoryBuilder.AddToSolver(solver, false);
777 		}
778 
779 		// solve
780 		error = solver->VerifyInstallation();
781 		if (error != B_OK) {
782 			throw BFatalErrorException(error, "Failed to compute packages to "
783 				"install.");
784 		}
785 
786 		BSolverResult solverResult;
787 		error = solver->GetResult(solverResult);
788 		if (error != B_OK) {
789 			throw BFatalErrorException(error, "Failed to retrieve system "
790 				"package dependency list.");
791 		}
792 
793 		for (int32 i = 0; const BSolverResultElement* element
794 				= solverResult.ElementAt(i); i++) {
795 			BSolverPackage* package = element->Package();
796 			if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
797 				PackageInfoMap::iterator it = systemInstalledPackages.find(
798 					package->Info().FileName());
799 				if (it != systemInstalledPackages.end())
800 					it->second->SetSystemDependency(true);
801 			}
802 		}
803 	} catch (BFatalErrorException ex) {
804 		printf("Fatal exception occurred while resolving system dependencies: "
805 			"%s, details: %s\n", strerror(ex.Error()), ex.Details().String());
806 	} catch (BNothingToDoException) {
807 		// do nothing
808 	} catch (BException ex) {
809 		printf("Exception occurred while resolving system dependencies: %s\n",
810 			ex.Message().String());
811 	} catch (...) {
812 		printf("Unknown exception occurred while resolving system "
813 			"dependencies.\n");
814 	}
815 }
816 
817 
818 void
819 MainWindow::_StartRefreshWorker(bool force)
820 {
821 	if (fModelWorker != B_BAD_THREAD_ID)
822 		return;
823 
824 	RefreshWorkerParameters* parameters = new(std::nothrow)
825 		RefreshWorkerParameters(this, force);
826 	if (parameters == NULL)
827 		return;
828 
829 	ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
830 	fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader",
831 		B_LOW_PRIORITY, parameters);
832 
833 	if (fModelWorker > 0) {
834 		deleter.Detach();
835 		resume_thread(fModelWorker);
836 	}
837 }
838 
839 
840 status_t
841 MainWindow::_RefreshModelThreadWorker(void* arg)
842 {
843 	RefreshWorkerParameters* parameters
844 		= reinterpret_cast<RefreshWorkerParameters*>(arg);
845 	MainWindow* mainWindow = parameters->window;
846 	ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
847 
848 	BMessenger messenger(mainWindow);
849 
850 	mainWindow->_RefreshRepositories(parameters->forceRefresh);
851 
852 	if (mainWindow->fTerminating)
853 		return B_OK;
854 
855 	mainWindow->_RefreshPackageList();
856 
857 	messenger.SendMessage(MSG_MODEL_WORKER_DONE);
858 
859 	return B_OK;
860 }
861 
862 
863 status_t
864 MainWindow::_PackageActionWorker(void* arg)
865 {
866 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
867 
868 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
869 		PackageActionRef ref;
870 		{
871 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
872 			ref = window->fPendingActions.ItemAt(0);
873 			if (ref.Get() == NULL)
874 				break;
875 			window->fPendingActions.Remove(0);
876 		}
877 
878 		ref->Perform();
879 	}
880 
881 	return 0;
882 }
883 
884 
885 status_t
886 MainWindow::_PopulatePackageWorker(void* arg)
887 {
888 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
889 
890 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
891 		PackageInfoRef package;
892 		{
893 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
894 			package = window->fPackageToPopulate;
895 		}
896 
897 		if (package.Get() != NULL) {
898 			window->fModel.PopulatePackage(package,
899 				Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS
900 					| Model::POPULATE_CHANGELOG);
901 		}
902 	}
903 
904 	return 0;
905 }
906 
907 
908 void
909 MainWindow::_NotifyUser(const char* title, const char* message)
910 {
911 	BAlert* alert = new(std::nothrow) BAlert(title, message,
912 		B_TRANSLATE("Close"));
913 
914 	if (alert != NULL)
915 		alert->Go();
916 }
917 
918 
919 void
920 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
921 {
922 	UserLoginWindow* window = new UserLoginWindow(this,
923 		BRect(0, 0, 500, 400), fModel);
924 
925 	if (onSuccessMessage.what != 0)
926 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
927 
928 	window->Show();
929 }
930 
931 
932 void
933 MainWindow::_UpdateAuthorization()
934 {
935 	BString username(fModel.Username());
936 	if (fLogOutItem != NULL)
937 		fLogOutItem->SetEnabled(username.Length() > 0);
938 	fFilterView->SetUsername(username);
939 }
940 
941 
942 void
943 MainWindow::_RatePackage()
944 {
945 	if (fModel.Username().IsEmpty()) {
946 		BAlert* alert = new(std::nothrow) BAlert(
947 			B_TRANSLATE("Not logged in"),
948 			B_TRANSLATE("You need to be logged into an account before you "
949 				"can rate packages."),
950 			B_TRANSLATE("Cancel"),
951 			B_TRANSLATE("Login or Create account"));
952 
953 		if (alert == NULL)
954 			return;
955 
956 		int32 choice = alert->Go();
957 		if (choice == 1)
958 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
959 		return;
960 	}
961 
962 	// TODO: Allow only one RatePackageWindow
963 	// TODO: Mechanism for remembering the window frame
964 	RatePackageWindow* window = new RatePackageWindow(this,
965 		BRect(0, 0, 500, 400), fModel);
966 	window->SetPackage(fPackageInfoView->Package());
967 	window->Show();
968 }
969