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