xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision d284f7cc43cc0d1106c3b0c40e62c58107648573)
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-2018, Andrew Lindesay <apl@lindesay.co.nz>.
7  * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
8  * All rights reserved. Distributed under the terms of the MIT License.
9  */
10 
11 
12 #include "MainWindow.h"
13 
14 #include <map>
15 #include <vector>
16 
17 #include <stdio.h>
18 #include <Alert.h>
19 #include <Autolock.h>
20 #include <Application.h>
21 #include <Button.h>
22 #include <Catalog.h>
23 #include <CardLayout.h>
24 #include <LayoutBuilder.h>
25 #include <MenuBar.h>
26 #include <MenuItem.h>
27 #include <Messenger.h>
28 #include <Roster.h>
29 #include <Screen.h>
30 #include <ScrollView.h>
31 #include <StringList.h>
32 #include <StringView.h>
33 #include <TabView.h>
34 
35 #include <package/Context.h>
36 #include <package/manager/Exceptions.h>
37 #include <package/manager/RepositoryBuilder.h>
38 #include <package/RefreshRepositoryRequest.h>
39 #include <package/PackageRoster.h>
40 #include "package/RepositoryCache.h"
41 #include <package/solver/SolverPackage.h>
42 #include <package/solver/SolverProblem.h>
43 #include <package/solver/SolverProblemSolution.h>
44 #include <package/solver/SolverRepository.h>
45 #include <package/solver/SolverResult.h>
46 
47 #include "AutoDeleter.h"
48 #include "AutoLocker.h"
49 #include "DecisionProvider.h"
50 #include "FeaturedPackagesView.h"
51 #include "FilterView.h"
52 #include "JobStateListener.h"
53 #include "Logger.h"
54 #include "PackageInfoView.h"
55 #include "PackageListView.h"
56 #include "PackageManager.h"
57 #include "RatePackageWindow.h"
58 #include "RepositoryUrlUtils.h"
59 #include "support.h"
60 #include "ScreenshotWindow.h"
61 #include "UserLoginWindow.h"
62 #include "WorkStatusView.h"
63 
64 
65 #undef B_TRANSLATION_CONTEXT
66 #define B_TRANSLATION_CONTEXT "MainWindow"
67 
68 
69 enum {
70 	MSG_MODEL_WORKER_DONE		= 'mmwd',
71 	MSG_REFRESH_REPOS			= 'mrrp',
72 	MSG_MANAGE_REPOS			= 'mmrp',
73 	MSG_SOFTWARE_UPDATER		= 'mswu',
74 	MSG_LOG_IN					= 'lgin',
75 	MSG_LOG_OUT					= 'lgot',
76 	MSG_AUTHORIZATION_CHANGED	= 'athc',
77 	MSG_PACKAGE_CHANGED			= 'pchd',
78 
79 	MSG_SHOW_AVAILABLE_PACKAGES	= 'savl',
80 	MSG_SHOW_INSTALLED_PACKAGES	= 'sins',
81 	MSG_SHOW_SOURCE_PACKAGES	= 'ssrc',
82 	MSG_SHOW_DEVELOP_PACKAGES	= 'sdvl'
83 };
84 
85 
86 using namespace BPackageKit;
87 using namespace BPackageKit::BManager::BPrivate;
88 
89 
90 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
91 
92 
93 struct RefreshWorkerParameters {
94 	MainWindow* window;
95 	bool forceRefresh;
96 
97 	RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
98 		:
99 		window(window),
100 		forceRefresh(forceRefresh)
101 	{
102 	}
103 };
104 
105 
106 class MessageModelListener : public ModelListener {
107 public:
108 	MessageModelListener(const BMessenger& messenger)
109 		:
110 		fMessenger(messenger)
111 	{
112 	}
113 
114 	virtual void AuthorizationChanged()
115 	{
116 		if (fMessenger.IsValid())
117 			fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
118 	}
119 
120 private:
121 	BMessenger	fMessenger;
122 };
123 
124 
125 MainWindow::MainWindow(const BMessage& settings)
126 	:
127 	BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
128 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
129 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
130 	fScreenshotWindow(NULL),
131 	fUserMenu(NULL),
132 	fLogInItem(NULL),
133 	fLogOutItem(NULL),
134 	fModelListener(new MessageModelListener(BMessenger(this)), true),
135 	fBulkLoadStateMachine(&fModel),
136 	fTerminating(false),
137 	fSinglePackageMode(false),
138 	fModelWorker(B_BAD_THREAD_ID)
139 {
140 	BMenuBar* menuBar = new BMenuBar("Main Menu");
141 	_BuildMenu(menuBar);
142 
143 	BMenuBar* userMenuBar = new BMenuBar("User Menu");
144 	_BuildUserMenu(userMenuBar);
145 	set_small_font(userMenuBar);
146 	userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
147 		menuBar->MaxSize().height));
148 
149 	fFilterView = new FilterView();
150 	fFeaturedPackagesView = new FeaturedPackagesView();
151 	fPackageListView = new PackageListView(fModel.Lock());
152 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
153 
154 	fSplitView = new BSplitView(B_VERTICAL, 5.0f);
155 
156 	fWorkStatusView = new WorkStatusView("work status");
157 	fPackageListView->AttachWorkStatusView(fWorkStatusView);
158 
159 	BView* listArea = new BView("list area", 0);
160 	fListLayout = new BCardLayout();
161 	listArea->SetLayout(fListLayout);
162 	listArea->AddChild(fFeaturedPackagesView);
163 	listArea->AddChild(fPackageListView);
164 
165 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
166 		.AddGroup(B_HORIZONTAL, 0.0f)
167 			.Add(menuBar, 1.0f)
168 			.Add(userMenuBar, 0.0f)
169 		.End()
170 		.Add(fFilterView)
171 		.AddSplit(fSplitView)
172 			.AddGroup(B_VERTICAL)
173 				.Add(listArea)
174 				.SetInsets(
175 					B_USE_DEFAULT_SPACING, 0.0f,
176 					B_USE_DEFAULT_SPACING, 0.0f)
177 			.End()
178 			.Add(fPackageInfoView)
179 		.End()
180 		.Add(fWorkStatusView)
181 	;
182 
183 	fSplitView->SetCollapsible(0, false);
184 	fSplitView->SetCollapsible(1, false);
185 
186 	fModel.AddListener(fModelListener);
187 
188 	// Restore settings
189 	BMessage columnSettings;
190 	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
191 		fPackageListView->LoadState(&columnSettings);
192 
193 	bool showOption;
194 	if (settings.FindBool("show featured packages", &showOption) == B_OK)
195 		fModel.SetShowFeaturedPackages(showOption);
196 	if (settings.FindBool("show available packages", &showOption) == B_OK)
197 		fModel.SetShowAvailablePackages(showOption);
198 	if (settings.FindBool("show installed packages", &showOption) == B_OK)
199 		fModel.SetShowInstalledPackages(showOption);
200 	if (settings.FindBool("show develop packages", &showOption) == B_OK)
201 		fModel.SetShowDevelopPackages(showOption);
202 	if (settings.FindBool("show source packages", &showOption) == B_OK)
203 		fModel.SetShowSourcePackages(showOption);
204 
205 	if (fModel.ShowFeaturedPackages())
206 		fListLayout->SetVisibleItem((int32)0);
207 	else
208 		fListLayout->SetVisibleItem(1);
209 
210 	_RestoreUserName(settings);
211 	_RestoreWindowFrame(settings);
212 
213 	atomic_set(&fPackagesToShowListID, 0);
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 	fWorkStatusView(NULL),
231 	fScreenshotWindow(NULL),
232 	fUserMenu(NULL),
233 	fLogInItem(NULL),
234 	fLogOutItem(NULL),
235 	fModelListener(new MessageModelListener(BMessenger(this)), true),
236 	fBulkLoadStateMachine(&fModel),
237 	fTerminating(false),
238 	fSinglePackageMode(true),
239 	fModelWorker(B_BAD_THREAD_ID)
240 {
241 	fFilterView = new FilterView();
242 	fPackageListView = new PackageListView(fModel.Lock());
243 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
244 
245 	BLayoutBuilder::Group<>(this, B_VERTICAL)
246 		.Add(fPackageInfoView)
247 		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
248 	;
249 
250 	fModel.AddListener(fModelListener);
251 
252 	// Restore settings
253 	_RestoreUserName(settings);
254 	_RestoreWindowFrame(settings);
255 
256 	fPackageInfoView->SetPackage(package);
257 
258 	_InitWorkerThreads();
259 }
260 
261 
262 MainWindow::~MainWindow()
263 {
264 	BPackageRoster().StopWatching(this);
265 
266 	fTerminating = true;
267 	if (fModelWorker >= 0)
268 		wait_for_thread(fModelWorker, NULL);
269 
270 	delete_sem(fPendingActionsSem);
271 	if (fPendingActionsWorker >= 0)
272 		wait_for_thread(fPendingActionsWorker, NULL);
273 
274 	delete_sem(fPackageToPopulateSem);
275 	if (fPopulatePackageWorker >= 0)
276 		wait_for_thread(fPopulatePackageWorker, NULL);
277 
278 	delete_sem(fNewPackagesToShowSem);
279 	delete_sem(fShowPackagesAcknowledgeSem);
280 	if (fShowPackagesWorker >= 0)
281 		wait_for_thread(fShowPackagesWorker, NULL);
282 
283 	if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
284 		fScreenshotWindow->Quit();
285 }
286 
287 
288 bool
289 MainWindow::QuitRequested()
290 {
291 	BMessage settings;
292 	StoreSettings(settings);
293 
294 	BMessage message(MSG_MAIN_WINDOW_CLOSED);
295 	message.AddMessage("window settings", &settings);
296 
297 	be_app->PostMessage(&message);
298 
299 	return true;
300 }
301 
302 
303 void
304 MainWindow::MessageReceived(BMessage* message)
305 {
306 	switch (message->what) {
307 		case MSG_MODEL_WORKER_DONE:
308 		{
309 			fModelWorker = B_BAD_THREAD_ID;
310 			_AdoptModel();
311 			_UpdateAvailableRepositories();
312 			fWorkStatusView->SetIdle();
313 			break;
314 		}
315 		case B_SIMPLE_DATA:
316 		case B_REFS_RECEIVED:
317 			// TODO: ?
318 			break;
319 
320 		case B_PACKAGE_UPDATE:
321 			// TODO: We should do a more selective update depending on the
322 			// "event", "location", and "change count" fields!
323 			_StartRefreshWorker(false);
324 			break;
325 
326 		case MSG_REFRESH_REPOS:
327 			_StartRefreshWorker(true);
328 			break;
329 
330 		case MSG_MANAGE_REPOS:
331 			be_roster->Launch("application/x-vnd.Haiku-Repositories");
332 			break;
333 
334 		case MSG_SOFTWARE_UPDATER:
335 			be_roster->Launch("application/x-vnd.haiku-softwareupdater");
336 			break;
337 
338 		case MSG_LOG_IN:
339 			_OpenLoginWindow(BMessage());
340 			break;
341 
342 		case MSG_LOG_OUT:
343 			fModel.SetUsername("");
344 			break;
345 
346 		case MSG_AUTHORIZATION_CHANGED:
347 			_UpdateAuthorization();
348 			break;
349 
350 		case MSG_SHOW_FEATURED_PACKAGES:
351 			{
352 				BAutolock locker(fModel.Lock());
353 				fModel.SetShowFeaturedPackages(
354 					!fModel.ShowFeaturedPackages());
355 			}
356 			_AdoptModel();
357 			break;
358 
359 		case MSG_SHOW_AVAILABLE_PACKAGES:
360 			{
361 				BAutolock locker(fModel.Lock());
362 				fModel.SetShowAvailablePackages(
363 					!fModel.ShowAvailablePackages());
364 			}
365 			_AdoptModel();
366 			break;
367 
368 		case MSG_SHOW_INSTALLED_PACKAGES:
369 			{
370 				BAutolock locker(fModel.Lock());
371 				fModel.SetShowInstalledPackages(
372 					!fModel.ShowInstalledPackages());
373 			}
374 			_AdoptModel();
375 			break;
376 
377 		case MSG_SHOW_SOURCE_PACKAGES:
378 			{
379 				BAutolock locker(fModel.Lock());
380 				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
381 			}
382 			_AdoptModel();
383 			break;
384 
385 		case MSG_SHOW_DEVELOP_PACKAGES:
386 			{
387 				BAutolock locker(fModel.Lock());
388 				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
389 			}
390 			_AdoptModel();
391 			break;
392 
393 			// this may be triggered by, for example, a user rating being added
394 			// or having been altered.
395 		case MSG_SERVER_DATA_CHANGED:
396 		{
397 			BString name;
398 			if (message->FindString("name", &name) == B_OK) {
399 				BAutolock locker(fModel.Lock());
400 				if (fPackageInfoView->Package()->Name() == name) {
401 					_PopulatePackageAsync(true);
402 				} else {
403 					if (Logger::IsDebugEnabled()) {
404 						printf("pkg [%s] is updated on the server, but is "
405 							"not selected so will not be updated.\n",
406 							name.String());
407 					}
408 				}
409 			}
410         	break;
411         }
412 
413 		case MSG_PACKAGE_SELECTED:
414 		{
415 			BString name;
416 			if (message->FindString("name", &name) == B_OK) {
417 				BAutolock locker(fModel.Lock());
418 				int count = fVisiblePackages.CountItems();
419 				for (int i = 0; i < count; i++) {
420 					const PackageInfoRef& package
421 						= fVisiblePackages.ItemAtFast(i);
422 					if (package.Get() != NULL && package->Name() == name) {
423 						locker.Unlock();
424 						_AdoptPackage(package);
425 						break;
426 					}
427 				}
428 			} else {
429 				_ClearPackage();
430 			}
431 			break;
432 		}
433 
434 		case MSG_CATEGORY_SELECTED:
435 		{
436 			BString name;
437 			if (message->FindString("name", &name) != B_OK)
438 				name = "";
439 			{
440 				BAutolock locker(fModel.Lock());
441 				fModel.SetCategory(name);
442 			}
443 			_AdoptModel();
444 			break;
445 		}
446 
447 		case MSG_DEPOT_SELECTED:
448 		{
449 			BString name;
450 			if (message->FindString("name", &name) != B_OK)
451 				name = "";
452 			{
453 				BAutolock locker(fModel.Lock());
454 				fModel.SetDepot(name);
455 			}
456 			_AdoptModel();
457 			_UpdateAvailableRepositories();
458 			break;
459 		}
460 
461 		case MSG_SEARCH_TERMS_MODIFIED:
462 		{
463 			// TODO: Do this with a delay!
464 			BString searchTerms;
465 			if (message->FindString("search terms", &searchTerms) != B_OK)
466 				searchTerms = "";
467 			{
468 				BAutolock locker(fModel.Lock());
469 				fModel.SetSearchTerms(searchTerms);
470 			}
471 			_AdoptModel();
472 			break;
473 		}
474 
475 		case MSG_PACKAGE_CHANGED:
476 		{
477 			PackageInfo* info;
478 			if (message->FindPointer("package", (void**)&info) == B_OK) {
479 				PackageInfoRef ref(info, true);
480 				uint32 changes;
481 				if (message->FindUInt32("changes", &changes) != B_OK)
482 					changes = 0;
483 				if ((changes & PKG_CHANGED_STATE) != 0) {
484 					BAutolock locker(fModel.Lock());
485 					fModel.SetPackageState(ref, ref->State());
486 				}
487 
488 				// Asynchronous updates to the package information
489 				// can mean that the package needs to be added or
490 				// removed to/from the visible packages when the current
491 				// filter parameters are re-evaluated on this package.
492 				bool wasVisible = fVisiblePackages.Contains(ref);
493 				bool isVisible;
494 				{
495 					BAutolock locker(fModel.Lock());
496 					// The package didn't get a chance yet to be in the
497 					// visible package list
498 					isVisible = fModel.MatchesFilter(ref);
499 
500 					// Transfer this single package, otherwise we miss
501 					// other packages if they appear or disappear along
502 					// with this one before receive a notification for
503 					// them.
504 					if (isVisible) {
505 						fVisiblePackages.Add(ref);
506 					} else if (wasVisible)
507 						fVisiblePackages.Remove(ref);
508 				}
509 
510 				if (wasVisible != isVisible) {
511 					if (!isVisible) {
512 						fPackageListView->RemovePackage(ref);
513 						fFeaturedPackagesView->RemovePackage(ref);
514 					} else {
515 						fPackageListView->AddPackage(ref);
516 						if (ref->IsProminent())
517 							fFeaturedPackagesView->AddPackage(ref);
518 					}
519 				}
520 
521 				if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
522 					fWorkStatusView->PackageStatusChanged(ref);
523 			}
524 			break;
525 		}
526 
527 		case MSG_RATE_PACKAGE:
528 			_RatePackage();
529 			break;
530 
531 		case MSG_SHOW_SCREENSHOT:
532 			_ShowScreenshot();
533 			break;
534 
535 		case MSG_PACKAGE_WORKER_BUSY:
536 		{
537 			BString reason;
538 			status_t status = message->FindString("reason", &reason);
539 			if (status != B_OK)
540 				break;
541 			if (!fSinglePackageMode)
542 				fWorkStatusView->SetBusy(reason);
543 			break;
544 		}
545 
546 		case MSG_PACKAGE_WORKER_IDLE:
547 			if (!fSinglePackageMode)
548 				fWorkStatusView->SetIdle();
549 			break;
550 
551 		case MSG_ADD_VISIBLE_PACKAGES:
552 		{
553 			struct SemaphoreReleaser {
554 				SemaphoreReleaser(sem_id semaphore)
555 					:
556 					fSemaphore(semaphore)
557 				{ }
558 
559 				~SemaphoreReleaser() { release_sem(fSemaphore); }
560 
561 				sem_id fSemaphore;
562 			};
563 
564 			// Make sure acknowledge semaphore is released even on error,
565 			// so the worker thread won't be blocked
566 			SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
567 
568 			int32 numPackages = 0;
569 			type_code unused;
570 			status_t status = message->GetInfo("package_ref", &unused,
571 				&numPackages);
572 			if (status != B_OK || numPackages == 0)
573 				break;
574 
575 			int32 listID = 0;
576 			status = message->FindInt32("list_id", &listID);
577 			if (status != B_OK)
578 				break;
579 			if (listID != atomic_get(&fPackagesToShowListID)) {
580 				// list is outdated, ignore
581 				break;
582 			}
583 
584 			for (int i = 0; i < numPackages; i++) {
585 				PackageInfo* packageRaw = NULL;
586 				status = message->FindPointer("package_ref", i,
587 					(void**)&packageRaw);
588 				if (status != B_OK)
589 					break;
590 				PackageInfoRef package(packageRaw, true);
591 
592 				fPackageListView->AddPackage(package);
593 				if (package->IsProminent())
594 					fFeaturedPackagesView->AddPackage(package);
595 			}
596 			break;
597 		}
598 
599 		case MSG_UPDATE_SELECTED_PACKAGE:
600 		{
601 			const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
602 			fFeaturedPackagesView->SelectPackage(selectedPackage, true);
603 			fPackageListView->SelectPackage(selectedPackage);
604 
605 			AutoLocker<BLocker> modelLocker(fModel.Lock());
606 			if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
607 				fPackageInfoView->Clear();
608 			break;
609 		}
610 
611 		default:
612 			BWindow::MessageReceived(message);
613 			break;
614 	}
615 }
616 
617 
618 void
619 MainWindow::StoreSettings(BMessage& settings) const
620 {
621 	settings.AddRect(_WindowFrameName(), Frame());
622 	if (!fSinglePackageMode) {
623 		settings.AddRect("window frame", Frame());
624 
625 		BMessage columnSettings;
626 		fPackageListView->SaveState(&columnSettings);
627 
628 		settings.AddMessage("column settings", &columnSettings);
629 
630 		settings.AddBool("show featured packages",
631 			fModel.ShowFeaturedPackages());
632 		settings.AddBool("show available packages",
633 			fModel.ShowAvailablePackages());
634 		settings.AddBool("show installed packages",
635 			fModel.ShowInstalledPackages());
636 		settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
637 		settings.AddBool("show source packages", fModel.ShowSourcePackages());
638 	}
639 
640 	settings.AddString("username", fModel.Username());
641 }
642 
643 
644 void
645 MainWindow::PackageChanged(const PackageInfoEvent& event)
646 {
647 	uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
648 	if ((event.Changes() & watchedChanges) != 0) {
649 		PackageInfoRef ref(event.Package());
650 		BMessage message(MSG_PACKAGE_CHANGED);
651 		message.AddPointer("package", ref.Get());
652 		message.AddUInt32("changes", event.Changes());
653 		ref.Detach();
654 			// reference needs to be released by MessageReceived();
655 		PostMessage(&message);
656 	}
657 }
658 
659 
660 status_t
661 MainWindow::SchedulePackageActions(PackageActionList& list)
662 {
663 	AutoLocker<BLocker> lock(&fPendingActionsLock);
664 	for (int32 i = 0; i < list.CountItems(); i++) {
665 		if (!fPendingActions.Add(list.ItemAtFast(i)))
666 			return B_NO_MEMORY;
667 	}
668 
669 	return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
670 }
671 
672 
673 Model*
674 MainWindow::GetModel()
675 {
676 	return &fModel;
677 }
678 
679 
680 void
681 MainWindow::_BuildMenu(BMenuBar* menuBar)
682 {
683 	BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
684 	menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"),
685 		new BMessage(MSG_REFRESH_REPOS)));
686 	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
687 		B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
688 	menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
689 		B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
690 
691 	menuBar->AddItem(menu);
692 
693 	fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
694 	menuBar->AddItem(fRepositoryMenu);
695 
696 	menu = new BMenu(B_TRANSLATE("Show"));
697 
698 	fShowAvailablePackagesItem = new BMenuItem(
699 		B_TRANSLATE("Available packages"),
700 		new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
701 	menu->AddItem(fShowAvailablePackagesItem);
702 
703 	fShowInstalledPackagesItem = new BMenuItem(
704 		B_TRANSLATE("Installed packages"),
705 		new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
706 	menu->AddItem(fShowInstalledPackagesItem);
707 
708 	menu->AddSeparatorItem();
709 
710 	fShowDevelopPackagesItem = new BMenuItem(
711 		B_TRANSLATE("Develop packages"),
712 		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
713 	menu->AddItem(fShowDevelopPackagesItem);
714 
715 	fShowSourcePackagesItem = new BMenuItem(
716 		B_TRANSLATE("Source packages"),
717 		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
718 	menu->AddItem(fShowSourcePackagesItem);
719 
720 	menuBar->AddItem(menu);
721 }
722 
723 
724 void
725 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
726 {
727 	fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
728 
729 	fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
730 		new BMessage(MSG_LOG_IN));
731 	fUserMenu->AddItem(fLogInItem);
732 
733 	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
734 		new BMessage(MSG_LOG_OUT));
735 	fUserMenu->AddItem(fLogOutItem);
736 
737 	menuBar->AddItem(fUserMenu);
738 }
739 
740 
741 void
742 MainWindow::_RestoreUserName(const BMessage& settings)
743 {
744 	BString username;
745 	if (settings.FindString("username", &username) == B_OK
746 		&& username.Length() > 0) {
747 		fModel.SetUsername(username);
748 	}
749 }
750 
751 
752 const char*
753 MainWindow::_WindowFrameName() const
754 {
755 	if (fSinglePackageMode)
756 		return "small window frame";
757 
758 	return "window frame";
759 }
760 
761 
762 void
763 MainWindow::_RestoreWindowFrame(const BMessage& settings)
764 {
765 	BRect frame = Frame();
766 
767 	BRect windowFrame;
768 	bool fromSettings = false;
769 	if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
770 		frame = windowFrame;
771 		fromSettings = true;
772 	} else if (!fSinglePackageMode) {
773 		// Resize to occupy a certain screen size
774 		BRect screenFrame = BScreen(this).Frame();
775 		float width = frame.Width();
776 		float height = frame.Height();
777 		if (width < screenFrame.Width() * .666f
778 			&& height < screenFrame.Height() * .666f) {
779 			frame.bottom = frame.top + screenFrame.Height() * .666f;
780 			frame.right = frame.left
781 				+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
782 		}
783 	}
784 
785 	MoveTo(frame.LeftTop());
786 	ResizeTo(frame.Width(), frame.Height());
787 
788 	if (fromSettings)
789 		MoveOnScreen();
790 	else
791 		CenterOnScreen();
792 }
793 
794 
795 void
796 MainWindow::_InitWorkerThreads()
797 {
798 	fPendingActionsSem = create_sem(0, "PendingPackageActions");
799 	if (fPendingActionsSem >= 0) {
800 		fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
801 			"Planet Express", B_NORMAL_PRIORITY, this);
802 		if (fPendingActionsWorker >= 0)
803 			resume_thread(fPendingActionsWorker);
804 	} else
805 		fPendingActionsWorker = -1;
806 
807 	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
808 	if (fPackageToPopulateSem >= 0) {
809 		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
810 			"Package Populator", B_NORMAL_PRIORITY, this);
811 		if (fPopulatePackageWorker >= 0)
812 			resume_thread(fPopulatePackageWorker);
813 	} else
814 		fPopulatePackageWorker = -1;
815 
816 	fNewPackagesToShowSem = create_sem(0, "ShowPackages");
817 	fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
818 	if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
819 		fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
820 			"Good news everyone", B_NORMAL_PRIORITY, this);
821 		if (fShowPackagesWorker >= 0)
822 			resume_thread(fShowPackagesWorker);
823 	} else
824 		fShowPackagesWorker = -1;
825 }
826 
827 
828 void
829 MainWindow::_AdoptModel()
830 {
831 	fVisiblePackages = fModel.CreatePackageList();
832 
833 	{
834 		AutoLocker<BLocker> modelLocker(fModel.Lock());
835 		AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
836 		fPackagesToShowList = fVisiblePackages;
837 		atomic_add(&fPackagesToShowListID, 1);
838 	}
839 
840 	fFeaturedPackagesView->Clear();
841 	fPackageListView->Clear();
842 
843 	release_sem(fNewPackagesToShowSem);
844 
845 	BAutolock locker(fModel.Lock());
846 	fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
847 	fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
848 	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
849 	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
850 
851 	if (fModel.ShowFeaturedPackages())
852 		fListLayout->SetVisibleItem((int32)0);
853 	else
854 		fListLayout->SetVisibleItem((int32)1);
855 
856 	fFilterView->AdoptModel(fModel);
857 }
858 
859 
860 void
861 MainWindow::_AdoptPackage(const PackageInfoRef& package)
862 {
863 	{
864 		BAutolock locker(fModel.Lock());
865 		fPackageInfoView->SetPackage(package);
866 
867 		if (fFeaturedPackagesView != NULL)
868 			fFeaturedPackagesView->SelectPackage(package);
869 		if (fPackageListView != NULL)
870 			fPackageListView->SelectPackage(package);
871 	}
872 
873 	_PopulatePackageAsync(false);
874 }
875 
876 
877 void
878 MainWindow::_ClearPackage()
879 {
880 	fPackageInfoView->Clear();
881 }
882 
883 
884 void
885 MainWindow::_RefreshRepositories(bool force)
886 {
887 	if (fSinglePackageMode)
888 		return;
889 
890 	BPackageRoster roster;
891 	BStringList repositoryNames;
892 
893 	status_t result = roster.GetRepositoryNames(repositoryNames);
894 	if (result != B_OK)
895 		return;
896 
897 	DecisionProvider decisionProvider;
898 	JobStateListener listener;
899 	BContext context(decisionProvider, listener);
900 
901 	BRepositoryCache cache;
902 	for (int32 i = 0; i < repositoryNames.CountStrings(); ++i) {
903 		const BString& repoName = repositoryNames.StringAt(i);
904 		BRepositoryConfig repoConfig;
905 		result = roster.GetRepositoryConfig(repoName, &repoConfig);
906 		if (result != B_OK) {
907 			// TODO: notify user
908 			continue;
909 		}
910 
911 		if (roster.GetRepositoryCache(repoName, &cache) != B_OK || force) {
912 			try {
913 				BRefreshRepositoryRequest refreshRequest(context, repoConfig);
914 
915 				result = refreshRequest.Process();
916 			} catch (BFatalErrorException ex) {
917 				BString message(B_TRANSLATE("An error occurred while "
918 					"refreshing the repository: %error% (%details%)"));
919  				message.ReplaceFirst("%error%", ex.Message());
920 				message.ReplaceFirst("%details%", ex.Details());
921 				_NotifyUser("Error", message.String());
922 			} catch (BException ex) {
923 				BString message(B_TRANSLATE("An error occurred while "
924 					"refreshing the repository: %error%"));
925 				message.ReplaceFirst("%error%", ex.Message());
926 				_NotifyUser("Error", message.String());
927 			}
928 		}
929 	}
930 }
931 
932 
933 void
934 MainWindow::_RefreshPackageList(bool force)
935 {
936 	if (fSinglePackageMode)
937 		return;
938 
939 	BPackageRoster roster;
940 	BStringList repositoryNames;
941 
942 	status_t result = roster.GetRepositoryNames(repositoryNames);
943 	if (result != B_OK)
944 		return;
945 
946 	std::vector<DepotInfo> depots(repositoryNames.CountStrings());
947 	for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
948 		const BString& repoName = repositoryNames.StringAt(i);
949 		DepotInfo depotInfo = DepotInfo(repoName);
950 
951 		BRepositoryConfig repoConfig;
952 		status_t getRepositoryConfigStatus = roster.GetRepositoryConfig(
953 			repoName, &repoConfig);
954 
955 		if (getRepositoryConfigStatus == B_OK) {
956 			depotInfo.SetBaseURL(repoConfig.BaseURL());
957 			depotInfo.SetURL(repoConfig.URL());
958 
959 			if (Logger::IsDebugEnabled()) {
960 				printf("local repository [%s] info;\n"
961 					" * base url [%s]\n"
962 					" * url [%s]\n",
963 					repoName.String(), repoConfig.BaseURL().String(),
964 					repoConfig.URL().String());
965 			}
966 		} else {
967 			printf("unable to obtain the repository config for local "
968 				"repository '%s'; %s\n",
969 				repoName.String(), strerror(getRepositoryConfigStatus));
970 		}
971 
972 		depots[i] = depotInfo;
973 	}
974 
975 	PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
976 	try {
977 		manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
978 			| PackageManager::B_ADD_REMOTE_REPOSITORIES);
979 	} catch (BException ex) {
980 		BString message(B_TRANSLATE("An error occurred while "
981 			"initializing the package manager: %message%"));
982 		message.ReplaceFirst("%message%", ex.Message());
983 		_NotifyUser("Error", message.String());
984 		return;
985 	}
986 
987 	BObjectList<BSolverPackage> packages;
988 	result = manager.Solver()->FindPackages("",
989 		BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
990 			| BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
991 			| BSolver::B_FIND_IN_PROVIDES,
992 		packages);
993 	if (result != B_OK) {
994 		BString message(B_TRANSLATE("An error occurred while "
995 			"obtaining the package list: %message%"));
996 		message.ReplaceFirst("%message%", strerror(result));
997 		_NotifyUser("Error", message.String());
998 		return;
999 	}
1000 
1001 	if (packages.IsEmpty())
1002 		return;
1003 
1004 	PackageInfoMap foundPackages;
1005 		// if a given package is installed locally, we will potentially
1006 		// get back multiple entries, one for each local installation
1007 		// location, and one for each remote repository the package
1008 		// is available in. The above map is used to ensure that in such
1009 		// cases we consolidate the information, rather than displaying
1010 		// duplicates
1011 	PackageInfoMap remotePackages;
1012 		// any package that we find in a remote repository goes in this map.
1013 		// this is later used to discern which packages came from a local
1014 		// installation only, as those must be handled a bit differently
1015 		// upon uninstallation, since we'd no longer be able to pull them
1016 		// down remotely.
1017 	BStringList systemFlaggedPackages;
1018 		// any packages flagged as a system package are added to this list.
1019 		// such packages cannot be uninstalled, nor can any of their deps.
1020 	PackageInfoMap systemInstalledPackages;
1021 		// any packages installed in system are added to this list.
1022 		// This is later used for dependency resolution of the actual
1023 		// system packages in order to compute the list of protected
1024 		// dependencies indicated above.
1025 
1026 	for (int32 i = 0; i < packages.CountItems(); i++) {
1027 		BSolverPackage* package = packages.ItemAt(i);
1028 		const BPackageInfo& repoPackageInfo = package->Info();
1029 		const BString repositoryName = package->Repository()->Name();
1030 		PackageInfoRef modelInfo;
1031 		PackageInfoMap::iterator it = foundPackages.find(
1032 			repoPackageInfo.Name());
1033 		if (it != foundPackages.end())
1034 			modelInfo.SetTo(it->second);
1035 		else {
1036 			// Add new package info
1037 			modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
1038 				true);
1039 
1040 			if (modelInfo.Get() == NULL)
1041 				return;
1042 
1043 			modelInfo->SetDepotName(repositoryName);
1044 
1045 			foundPackages[repoPackageInfo.Name()] = modelInfo;
1046 		}
1047 
1048 		modelInfo->AddListener(this);
1049 
1050 		BSolverRepository* repository = package->Repository();
1051 		BPackageManager::RemoteRepository* remoteRepository =
1052 			dynamic_cast<BPackageManager::RemoteRepository*>(repository);
1053 
1054 		if (remoteRepository != NULL) {
1055 
1056 			std::vector<DepotInfo>::iterator it;
1057 
1058 			for (it = depots.begin(); it != depots.end(); it++) {
1059 				if (RepositoryUrlUtils::EqualsOnUrlOrBaseUrl(
1060 					it->URL(), remoteRepository->Config().URL(),
1061 					it->BaseURL(), remoteRepository->Config().BaseURL())) {
1062 					break;
1063 				}
1064 			}
1065 
1066 			if (it == depots.end()) {
1067 				if (Logger::IsDebugEnabled()) {
1068 					printf("pkg [%s] repository [%s] not recognized"
1069 						" --> ignored\n",
1070 						modelInfo->Name().String(), repositoryName.String());
1071 				}
1072 			} else {
1073 				it->AddPackage(modelInfo);
1074 
1075 				if (Logger::IsTraceEnabled()) {
1076 					printf("pkg [%s] assigned to [%s]\n",
1077 						modelInfo->Name().String(), repositoryName.String());
1078 				}
1079 			}
1080 
1081 			remotePackages[modelInfo->Name()] = modelInfo;
1082 		} else {
1083 			if (repository == static_cast<const BSolverRepository*>(
1084 					manager.SystemRepository())) {
1085 				modelInfo->AddInstallationLocation(
1086 					B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
1087 				if (!modelInfo->IsSystemPackage()) {
1088 					systemInstalledPackages[repoPackageInfo.FileName()]
1089 						= modelInfo;
1090 				}
1091 			} else if (repository == static_cast<const BSolverRepository*>(
1092 					manager.HomeRepository())) {
1093 				modelInfo->AddInstallationLocation(
1094 					B_PACKAGE_INSTALLATION_LOCATION_HOME);
1095 			}
1096 		}
1097 
1098 		if (modelInfo->IsSystemPackage())
1099 			systemFlaggedPackages.Add(repoPackageInfo.FileName());
1100 	}
1101 
1102 	bool wasEmpty = fModel.Depots().IsEmpty();
1103 	if (force || wasEmpty)
1104 		fBulkLoadStateMachine.Stop();
1105 
1106 	BAutolock lock(fModel.Lock());
1107 
1108 	if (force)
1109 		fModel.Clear();
1110 
1111 	// filter remote packages from the found list
1112 	// any packages remaining will be locally installed packages
1113 	// that weren't acquired from a repository
1114 	for (PackageInfoMap::iterator it = remotePackages.begin();
1115 			it != remotePackages.end(); it++) {
1116 		foundPackages.erase(it->first);
1117 	}
1118 
1119 	if (!foundPackages.empty()) {
1120 		BString repoName = B_TRANSLATE("Local");
1121 		depots.push_back(DepotInfo(repoName));
1122 
1123 		for (PackageInfoMap::iterator it = foundPackages.begin();
1124 				it != foundPackages.end(); ++it) {
1125 			depots.back().AddPackage(it->second);
1126 		}
1127 	}
1128 
1129 	{
1130 		std::vector<DepotInfo>::iterator it;
1131 
1132 		for (it = depots.begin(); it != depots.end(); it++) {
1133 			if (fModel.HasDepot(it->Name()))
1134 				fModel.SyncDepot(*it);
1135 			else
1136 				fModel.AddDepot(*it);
1137 		}
1138 	}
1139 
1140 	// start retrieving package icons and average ratings
1141 	if (force || wasEmpty) {
1142 			fBulkLoadStateMachine.Start();
1143 	}
1144 
1145 	// compute the OS package dependencies
1146 	try {
1147 		// create the solver
1148 		BSolver* solver;
1149 		status_t error = BSolver::Create(solver);
1150 		if (error != B_OK)
1151 			throw BFatalErrorException(error, "Failed to create solver.");
1152 
1153 		ObjectDeleter<BSolver> solverDeleter(solver);
1154 		BPath systemPath;
1155 		error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
1156 		if (error != B_OK) {
1157 			throw BFatalErrorException(error,
1158 				"Unable to retrieve system packages directory.");
1159 		}
1160 
1161 		// add the "installed" repository with the given packages
1162 		BSolverRepository installedRepository;
1163 		{
1164 			BRepositoryBuilder installedRepositoryBuilder(installedRepository,
1165 				"installed");
1166 			for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
1167 				BPath packagePath(systemPath);
1168 				packagePath.Append(systemFlaggedPackages.StringAt(i));
1169 				installedRepositoryBuilder.AddPackage(packagePath.Path());
1170 			}
1171 			installedRepositoryBuilder.AddToSolver(solver, true);
1172 		}
1173 
1174 		// add system repository
1175 		BSolverRepository systemRepository;
1176 		{
1177 			BRepositoryBuilder systemRepositoryBuilder(systemRepository,
1178 				"system");
1179 			for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
1180 					it != systemInstalledPackages.end(); it++) {
1181 				BPath packagePath(systemPath);
1182 				packagePath.Append(it->first);
1183 				systemRepositoryBuilder.AddPackage(packagePath.Path());
1184 			}
1185 			systemRepositoryBuilder.AddToSolver(solver, false);
1186 		}
1187 
1188 		// solve
1189 		error = solver->VerifyInstallation();
1190 		if (error != B_OK) {
1191 			throw BFatalErrorException(error, "Failed to compute packages to "
1192 				"install.");
1193 		}
1194 
1195 		BSolverResult solverResult;
1196 		error = solver->GetResult(solverResult);
1197 		if (error != B_OK) {
1198 			throw BFatalErrorException(error, "Failed to retrieve system "
1199 				"package dependency list.");
1200 		}
1201 
1202 		for (int32 i = 0; const BSolverResultElement* element
1203 				= solverResult.ElementAt(i); i++) {
1204 			BSolverPackage* package = element->Package();
1205 			if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
1206 				PackageInfoMap::iterator it = systemInstalledPackages.find(
1207 					package->Info().FileName());
1208 				if (it != systemInstalledPackages.end())
1209 					it->second->SetSystemDependency(true);
1210 			}
1211 		}
1212 	} catch (BFatalErrorException ex) {
1213 		printf("Fatal exception occurred while resolving system dependencies: "
1214 			"%s, details: %s\n", strerror(ex.Error()), ex.Details().String());
1215 	} catch (BNothingToDoException) {
1216 		// do nothing
1217 	} catch (BException ex) {
1218 		printf("Exception occurred while resolving system dependencies: %s\n",
1219 			ex.Message().String());
1220 	} catch (...) {
1221 		printf("Unknown exception occurred while resolving system "
1222 			"dependencies.\n");
1223 	}
1224 }
1225 
1226 
1227 void
1228 MainWindow::_StartRefreshWorker(bool force)
1229 {
1230 	if (fModelWorker != B_BAD_THREAD_ID)
1231 		return;
1232 
1233 	RefreshWorkerParameters* parameters = new(std::nothrow)
1234 		RefreshWorkerParameters(this, force);
1235 	if (parameters == NULL)
1236 		return;
1237 
1238 	fWorkStatusView->SetBusy(B_TRANSLATE("Refreshing..."));
1239 
1240 	ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1241 	fModelWorker = spawn_thread(&_RefreshModelThreadWorker, "model loader",
1242 		B_LOW_PRIORITY, parameters);
1243 
1244 	if (fModelWorker > 0) {
1245 		deleter.Detach();
1246 		resume_thread(fModelWorker);
1247 	}
1248 }
1249 
1250 
1251 status_t
1252 MainWindow::_RefreshModelThreadWorker(void* arg)
1253 {
1254 	RefreshWorkerParameters* parameters
1255 		= reinterpret_cast<RefreshWorkerParameters*>(arg);
1256 	MainWindow* mainWindow = parameters->window;
1257 	ObjectDeleter<RefreshWorkerParameters> deleter(parameters);
1258 
1259 	BMessenger messenger(mainWindow);
1260 
1261 	mainWindow->_RefreshRepositories(parameters->forceRefresh);
1262 
1263 	if (mainWindow->fTerminating)
1264 		return B_OK;
1265 
1266 	mainWindow->_RefreshPackageList(parameters->forceRefresh);
1267 
1268 	messenger.SendMessage(MSG_MODEL_WORKER_DONE);
1269 
1270 	return B_OK;
1271 }
1272 
1273 
1274 status_t
1275 MainWindow::_PackageActionWorker(void* arg)
1276 {
1277 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1278 
1279 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1280 		PackageActionRef ref;
1281 		{
1282 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1283 			ref = window->fPendingActions.ItemAt(0);
1284 			if (ref.Get() == NULL)
1285 				break;
1286 			window->fPendingActions.Remove(0);
1287 		}
1288 
1289 		BMessenger messenger(window);
1290 		BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1291 		BString text(ref->Label());
1292 		text << "...";
1293 		busyMessage.AddString("reason", text);
1294 
1295 		messenger.SendMessage(&busyMessage);
1296 		ref->Perform();
1297 		messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1298 	}
1299 
1300 	return 0;
1301 }
1302 
1303 
1304 /*! This method will cause the package to have its data refreshed from
1305     the server application.  The refresh happens in the background; this method
1306     is asynchronous.
1307 */
1308 
1309 void
1310 MainWindow::_PopulatePackageAsync(bool forcePopulate)
1311 {
1312 		// Trigger asynchronous package population from the web-app
1313 	{
1314 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
1315 		fPackageToPopulate = fPackageInfoView->Package();
1316 		fForcePopulatePackage = forcePopulate;
1317 	}
1318 	release_sem_etc(fPackageToPopulateSem, 1, 0);
1319 
1320 	if (Logger::IsDebugEnabled()) {
1321 		printf("pkg [%s] will be updated from the server.\n",
1322 			fPackageToPopulate->Name().String());
1323 	}
1324 }
1325 
1326 
1327 /*! This method will run in the background.  The thread will block until there
1328     is a package to be updated.  When the thread unblocks, it will update the
1329     package with information from the server.
1330 */
1331 
1332 status_t
1333 MainWindow::_PopulatePackageWorker(void* arg)
1334 {
1335 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1336 
1337 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1338 		PackageInfoRef package;
1339 		bool force;
1340 		{
1341 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1342 			package = window->fPackageToPopulate;
1343 			force = window->fForcePopulatePackage;
1344 		}
1345 
1346 		if (package.Get() != NULL) {
1347 			uint32 populateFlags = Model::POPULATE_USER_RATINGS
1348 				| Model::POPULATE_SCREEN_SHOTS
1349 				| Model::POPULATE_CHANGELOG;
1350 
1351 			if (force)
1352 				populateFlags |= Model::POPULATE_FORCE;
1353 
1354 			window->fModel.PopulatePackage(package, populateFlags);
1355 
1356 			if (Logger::IsDebugEnabled()) {
1357 				printf("populating package [%s]\n",
1358 					package->Name().String());
1359 			}
1360 		}
1361 	}
1362 
1363 	return 0;
1364 }
1365 
1366 
1367 /* static */ status_t
1368 MainWindow::_PackagesToShowWorker(void* arg)
1369 {
1370 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1371 
1372 	while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1373 		PackageList packageList;
1374 		int32 listID = 0;
1375 		{
1376 			AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1377 			packageList = window->fPackagesToShowList;
1378 			listID = atomic_get(&window->fPackagesToShowListID);
1379 			window->fPackagesToShowList.Clear();
1380 		}
1381 
1382 		// Add packages to list views in batches of kPackagesPerUpdate so we
1383 		// don't block the window thread for long with each iteration
1384 		enum {
1385 			kPackagesPerUpdate = 20
1386 		};
1387 		uint32 packagesInMessage = 0;
1388 		BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1389 		BMessenger messenger(window);
1390 		bool listIsOutdated = false;
1391 
1392 		for (int i = 0; i < packageList.CountItems(); i++) {
1393 			const PackageInfoRef& package = packageList.ItemAtFast(i);
1394 
1395 			if (packagesInMessage >= kPackagesPerUpdate) {
1396 				if (listID != atomic_get(&window->fPackagesToShowListID)) {
1397 					// The model was changed again in the meantime, and thus
1398 					// our package list isn't current anymore. Send no further
1399 					// messags based on this list and go back to start.
1400 					listIsOutdated = true;
1401 					break;
1402 				}
1403 
1404 				message.AddInt32("list_id", listID);
1405 				messenger.SendMessage(&message);
1406 				message.MakeEmpty();
1407 				packagesInMessage = 0;
1408 
1409 				// Don't spam the window's message queue, which would make it
1410 				// unresponsive (i.e. allows UI messages to get in between our
1411 				// messages). When it has processed the message we just sent,
1412 				// it will let us know by releasing the semaphore.
1413 				acquire_sem(window->fShowPackagesAcknowledgeSem);
1414 			}
1415 			package->AcquireReference();
1416 			message.AddPointer("package_ref", package.Get());
1417 			packagesInMessage++;
1418 		}
1419 
1420 		if (listIsOutdated)
1421 			continue;
1422 
1423 		// Send remaining package infos, if any, which didn't make it into
1424 		// the last message (count < kPackagesPerUpdate)
1425 		if (packagesInMessage > 0) {
1426 			message.AddInt32("list_id", listID);
1427 			messenger.SendMessage(&message);
1428 			acquire_sem(window->fShowPackagesAcknowledgeSem);
1429 		}
1430 
1431 		// Update selected package in list views
1432 		messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1433 	}
1434 
1435 	return 0;
1436 }
1437 
1438 
1439 void
1440 MainWindow::_NotifyUser(const char* title, const char* message)
1441 {
1442 	BAlert* alert = new(std::nothrow) BAlert(title, message,
1443 		B_TRANSLATE("Close"));
1444 
1445 	if (alert != NULL)
1446 		alert->Go();
1447 }
1448 
1449 
1450 void
1451 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1452 {
1453 	UserLoginWindow* window = new UserLoginWindow(this,
1454 		BRect(0, 0, 500, 400), fModel);
1455 
1456 	if (onSuccessMessage.what != 0)
1457 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1458 
1459 	window->Show();
1460 }
1461 
1462 
1463 void
1464 MainWindow::_UpdateAuthorization()
1465 {
1466 	BString username(fModel.Username());
1467 	bool hasUser = !username.IsEmpty();
1468 
1469 	if (fLogOutItem != NULL)
1470 		fLogOutItem->SetEnabled(hasUser);
1471 	if (fLogInItem != NULL) {
1472 		if (hasUser)
1473 			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1474 		else
1475 			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1476 	}
1477 
1478 	if (fUserMenu != NULL) {
1479 		BString label;
1480 		if (username.Length() == 0) {
1481 			label = B_TRANSLATE("Not logged in");
1482 		} else {
1483 			label = B_TRANSLATE("Logged in as %User%");
1484 			label.ReplaceAll("%User%", username);
1485 		}
1486 		fUserMenu->Superitem()->SetLabel(label);
1487 	}
1488 }
1489 
1490 
1491 void
1492 MainWindow::_UpdateAvailableRepositories()
1493 {
1494 	fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
1495 
1496 	fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
1497 		new BMessage(MSG_DEPOT_SELECTED)));
1498 
1499 	fRepositoryMenu->AddItem(new BSeparatorItem());
1500 
1501 	bool foundSelectedDepot = false;
1502 	const DepotList& depots = fModel.Depots();
1503 	for (int i = 0; i < depots.CountItems(); i++) {
1504 		const DepotInfo& depot = depots.ItemAtFast(i);
1505 
1506 		if (depot.Name().Length() != 0) {
1507 			BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
1508 			message->AddString("name", depot.Name());
1509 			BMenuItem* item = new BMenuItem(depot.Name(), message);
1510 			fRepositoryMenu->AddItem(item);
1511 
1512 			if (depot.Name() == fModel.Depot()) {
1513 				item->SetMarked(true);
1514 				foundSelectedDepot = true;
1515 			}
1516 		}
1517 	}
1518 
1519 	if (!foundSelectedDepot)
1520 		fRepositoryMenu->ItemAt(0)->SetMarked(true);
1521 }
1522 
1523 
1524 bool
1525 MainWindow::_SelectedPackageHasWebAppRepositoryCode()
1526 {
1527 	const PackageInfoRef& package = fPackageInfoView->Package();
1528 	const DepotInfo* depot = fModel.DepotForName(package->DepotName());
1529 
1530 	BString repositoryCode;
1531 
1532 	if (depot != NULL)
1533 		repositoryCode = depot->WebAppRepositoryCode();
1534 
1535 	return !repositoryCode.IsEmpty();
1536 }
1537 
1538 
1539 void
1540 MainWindow::_RatePackage()
1541 {
1542 	if (!_SelectedPackageHasWebAppRepositoryCode()) {
1543 		BAlert* alert = new(std::nothrow) BAlert(
1544 			B_TRANSLATE("Rating not possible"),
1545 			B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
1546 				"Server, so it's not possible to create a new rating "
1547 				"or edit an existing rating."),
1548 			B_TRANSLATE("OK"));
1549 		alert->Go();
1550     	return;
1551 	}
1552 
1553 	if (fModel.Username().IsEmpty()) {
1554 		BAlert* alert = new(std::nothrow) BAlert(
1555 			B_TRANSLATE("Not logged in"),
1556 			B_TRANSLATE("You need to be logged into an account before you "
1557 				"can rate packages."),
1558 			B_TRANSLATE("Cancel"),
1559 			B_TRANSLATE("Login or Create account"));
1560 
1561 		if (alert == NULL)
1562 			return;
1563 
1564 		int32 choice = alert->Go();
1565 		if (choice == 1)
1566 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1567 		return;
1568 	}
1569 
1570 	// TODO: Allow only one RatePackageWindow
1571 	// TODO: Mechanism for remembering the window frame
1572 	RatePackageWindow* window = new RatePackageWindow(this,
1573 		BRect(0, 0, 500, 400), fModel);
1574 	window->SetPackage(fPackageInfoView->Package());
1575 	window->Show();
1576 }
1577 
1578 
1579 void
1580 MainWindow::_ShowScreenshot()
1581 {
1582 	// TODO: Mechanism for remembering the window frame
1583 	if (fScreenshotWindow == NULL)
1584 		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1585 
1586 	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1587 		return;
1588 
1589 	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1590 
1591 	if (fScreenshotWindow->IsHidden())
1592 		fScreenshotWindow->Show();
1593 	else
1594 		fScreenshotWindow->Activate();
1595 
1596 	fScreenshotWindow->Unlock();
1597 }
1598