xref: /haiku/src/apps/haikudepot/ui/MainWindow.cpp (revision cac30e419035ddd49010a81c59f65b68e4919ce1)
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-2020, 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 "AppUtils.h"
36 #include "AutoDeleter.h"
37 #include "AutoLocker.h"
38 #include "DecisionProvider.h"
39 #include "FeaturedPackagesView.h"
40 #include "FilterView.h"
41 #include "Logger.h"
42 #include "PackageInfoView.h"
43 #include "PackageListView.h"
44 #include "PackageManager.h"
45 #include "ProcessCoordinator.h"
46 #include "ProcessCoordinatorFactory.h"
47 #include "RatePackageWindow.h"
48 #include "support.h"
49 #include "ScreenshotWindow.h"
50 #include "ToLatestUserUsageConditionsWindow.h"
51 #include "UserLoginWindow.h"
52 #include "UserUsageConditionsWindow.h"
53 #include "WorkStatusView.h"
54 
55 
56 #undef B_TRANSLATION_CONTEXT
57 #define B_TRANSLATION_CONTEXT "MainWindow"
58 
59 
60 enum {
61 	MSG_REFRESH_REPOS						= 'mrrp',
62 	MSG_MANAGE_REPOS						= 'mmrp',
63 	MSG_SOFTWARE_UPDATER					= 'mswu',
64 	MSG_LOG_IN								= 'lgin',
65 	MSG_AUTHORIZATION_CHANGED				= 'athc',
66 	MSG_CATEGORIES_LIST_CHANGED				= 'clic',
67 	MSG_PACKAGE_CHANGED						= 'pchd',
68 	MSG_WORK_STATUS_CHANGE					= 'wsch',
69 	MSG_WORK_STATUS_CLEAR					= 'wscl',
70 
71 	MSG_CHANGE_PACKAGE_LIST_VIEW_MODE		= 'cplm',
72 	MSG_SHOW_AVAILABLE_PACKAGES				= 'savl',
73 	MSG_SHOW_INSTALLED_PACKAGES				= 'sins',
74 	MSG_SHOW_SOURCE_PACKAGES				= 'ssrc',
75 	MSG_SHOW_DEVELOP_PACKAGES				= 'sdvl'
76 };
77 
78 #define KEY_ERROR_STATUS				"errorStatus"
79 #define KEY_PACKAGE_LIST_VIEW_MODE		"packageListViewMode"
80 
81 #define TAB_PROMINENT_PACKAGES	0
82 #define TAB_ALL_PACKAGES		1
83 
84 using namespace BPackageKit;
85 using namespace BPackageKit::BManager::BPrivate;
86 
87 
88 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
89 
90 
91 struct RefreshWorkerParameters {
92 	MainWindow* window;
93 	bool forceRefresh;
94 
95 	RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
96 		:
97 		window(window),
98 		forceRefresh(forceRefresh)
99 	{
100 	}
101 };
102 
103 
104 class MainWindowModelListener : public ModelListener {
105 public:
106 	MainWindowModelListener(const BMessenger& messenger)
107 		:
108 		fMessenger(messenger)
109 	{
110 	}
111 
112 	virtual void AuthorizationChanged()
113 	{
114 		if (fMessenger.IsValid())
115 			fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
116 	}
117 
118 	virtual void CategoryListChanged()
119 	{
120 		if (fMessenger.IsValid())
121 			fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED);
122 	}
123 
124 private:
125 	BMessenger	fMessenger;
126 };
127 
128 
129 MainWindow::MainWindow(const BMessage& settings)
130 	:
131 	BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
132 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
133 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
134 	fScreenshotWindow(NULL),
135 	fUserMenu(NULL),
136 	fLogInItem(NULL),
137 	fLogOutItem(NULL),
138 	fUsersUserUsageConditionsMenuItem(NULL),
139 	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
140 	fCoordinator(NULL),
141 	fSinglePackageMode(false)
142 {
143 	if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
144 		debugger("unable to create the process coordinator semaphore");
145 
146 	BMenuBar* menuBar = new BMenuBar("Main Menu");
147 	_BuildMenu(menuBar);
148 
149 	BMenuBar* userMenuBar = new BMenuBar("User Menu");
150 	_BuildUserMenu(userMenuBar);
151 	set_small_font(userMenuBar);
152 	userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
153 		menuBar->MaxSize().height));
154 
155 	fFilterView = new FilterView();
156 	fFeaturedPackagesView = new FeaturedPackagesView();
157 	fPackageListView = new PackageListView(fModel.Lock());
158 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
159 
160 	fSplitView = new BSplitView(B_VERTICAL, 5.0f);
161 
162 	fWorkStatusView = new WorkStatusView("work status");
163 	fPackageListView->AttachWorkStatusView(fWorkStatusView);
164 
165 	fListTabs = new TabView(BMessenger(this),
166 		BMessage(MSG_CHANGE_PACKAGE_LIST_VIEW_MODE), "list tabs");
167 	fListTabs->AddTab(fFeaturedPackagesView);
168 	fListTabs->AddTab(fPackageListView);
169 
170 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
171 		.AddGroup(B_HORIZONTAL, 0.0f)
172 			.Add(menuBar, 1.0f)
173 			.Add(userMenuBar, 0.0f)
174 		.End()
175 		.Add(fFilterView)
176 		.AddSplit(fSplitView)
177 			.AddGroup(B_VERTICAL)
178 				.Add(fListTabs)
179 				.SetInsets(
180 					B_USE_DEFAULT_SPACING, 0.0f,
181 					B_USE_DEFAULT_SPACING, 0.0f)
182 			.End()
183 			.Add(fPackageInfoView)
184 		.End()
185 		.Add(fWorkStatusView)
186 	;
187 
188 	fSplitView->SetCollapsible(0, false);
189 	fSplitView->SetCollapsible(1, false);
190 
191 	fModel.AddListener(fModelListener);
192 
193 	BMessage columnSettings;
194 	if (settings.FindMessage("column settings", &columnSettings) == B_OK)
195 		fPackageListView->LoadState(&columnSettings);
196 
197 	_RestoreModelSettings(settings);
198 
199 	if (fModel.PackageListViewMode() == PROMINENT)
200 		fListTabs->Select(TAB_PROMINENT_PACKAGES);
201 	else
202 		fListTabs->Select(TAB_ALL_PACKAGES);
203 
204 	_RestoreNickname(settings);
205 	_UpdateAuthorization();
206 	_RestoreWindowFrame(settings);
207 
208 	atomic_set(&fPackagesToShowListID, 0);
209 
210 	// start worker threads
211 	BPackageRoster().StartWatching(this,
212 		B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
213 
214 	_StartBulkLoad();
215 
216 	_InitWorkerThreads();
217 }
218 
219 
220 MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
221 	:
222 	BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
223 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
224 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
225 	fWorkStatusView(NULL),
226 	fScreenshotWindow(NULL),
227 	fUserMenu(NULL),
228 	fLogInItem(NULL),
229 	fLogOutItem(NULL),
230 	fUsersUserUsageConditionsMenuItem(NULL),
231 	fModelListener(new MainWindowModelListener(BMessenger(this)), true),
232 	fCoordinator(NULL),
233 	fSinglePackageMode(true)
234 {
235 	if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
236 		debugger("unable to create the process coordinator semaphore");
237 
238 	fFilterView = new FilterView();
239 	fPackageListView = new PackageListView(fModel.Lock());
240 	fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
241 
242 	BLayoutBuilder::Group<>(this, B_VERTICAL)
243 		.Add(fPackageInfoView)
244 		.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
245 	;
246 
247 	fModel.AddListener(fModelListener);
248 
249 	// Restore settings
250 	_RestoreNickname(settings);
251 	_UpdateAuthorization();
252 	_RestoreWindowFrame(settings);
253 
254 	fPackageInfoView->SetPackage(package);
255 
256 	_InitWorkerThreads();
257 }
258 
259 
260 MainWindow::~MainWindow()
261 {
262 	_SpinUntilProcessCoordinatorComplete();
263 	delete_sem(fCoordinatorRunningSem);
264 	fCoordinatorRunningSem = 0;
265 
266 	BPackageRoster().StopWatching(this);
267 
268 	delete_sem(fPendingActionsSem);
269 	if (fPendingActionsWorker >= 0)
270 		wait_for_thread(fPendingActionsWorker, NULL);
271 
272 	delete_sem(fPackageToPopulateSem);
273 	if (fPopulatePackageWorker >= 0)
274 		wait_for_thread(fPopulatePackageWorker, NULL);
275 
276 	delete_sem(fNewPackagesToShowSem);
277 	delete_sem(fShowPackagesAcknowledgeSem);
278 	if (fShowPackagesWorker >= 0)
279 		wait_for_thread(fShowPackagesWorker, NULL);
280 
281 	if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
282 		fScreenshotWindow->Quit();
283 }
284 
285 
286 bool
287 MainWindow::QuitRequested()
288 {
289 	BMessage settings;
290 	StoreSettings(settings);
291 
292 	BMessage message(MSG_MAIN_WINDOW_CLOSED);
293 	message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
294 
295 	be_app->PostMessage(&message);
296 
297 	_StopProcessCoordinators();
298 
299 	return true;
300 }
301 
302 
303 void
304 MainWindow::MessageReceived(BMessage* message)
305 {
306 	switch (message->what) {
307 		case MSG_BULK_LOAD_DONE:
308 		{
309 			int64 errorStatus64;
310 			if (message->FindInt64(KEY_ERROR_STATUS, &errorStatus64) == B_OK)
311 				_BulkLoadCompleteReceived((status_t) errorStatus64);
312 			else
313 				printf("! expected [%s] value in message\n", KEY_ERROR_STATUS);
314 			break;
315 		}
316 		case B_SIMPLE_DATA:
317 		case B_REFS_RECEIVED:
318 			// TODO: ?
319 			break;
320 
321 		case B_PACKAGE_UPDATE:
322 			// TODO: see ticket #15879
323 			// work needs to be done here to selectively update package data in
324 			// the running HaikuDepot application when there are changes on the
325 			// system.  There is now too much data to just load everything when
326 			// there is a change.
327 			//_StartBulkLoad(false);
328 			break;
329 
330 		case MSG_REFRESH_REPOS:
331 			_StartBulkLoad(true);
332 			break;
333 
334 		case MSG_WORK_STATUS_CLEAR:
335 			_HandleWorkStatusClear();
336 			break;
337 
338 		case MSG_WORK_STATUS_CHANGE:
339 			_HandleWorkStatusChangeMessageReceived(message);
340 			break;
341 
342 		case MSG_MANAGE_REPOS:
343 			be_roster->Launch("application/x-vnd.Haiku-Repositories");
344 			break;
345 
346 		case MSG_SOFTWARE_UPDATER:
347 			be_roster->Launch("application/x-vnd.haiku-softwareupdater");
348 			break;
349 
350 		case MSG_LOG_IN:
351 			_OpenLoginWindow(BMessage());
352 			break;
353 
354 		case MSG_LOG_OUT:
355 			fModel.SetNickname("");
356 			break;
357 
358 		case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
359 			_ViewUserUsageConditions(LATEST);
360 			break;
361 
362 		case MSG_VIEW_USERS_USER_USAGE_CONDITIONS:
363 			_ViewUserUsageConditions(USER);
364 			break;
365 
366 		case MSG_AUTHORIZATION_CHANGED:
367 			_StartUserVerify();
368 			_UpdateAuthorization();
369 			break;
370 
371 		case MSG_CATEGORIES_LIST_CHANGED:
372 			fFilterView->AdoptModel(fModel);
373 			break;
374 
375 		case MSG_CHANGE_PACKAGE_LIST_VIEW_MODE:
376 			_HandleChangePackageListViewMode();
377 			break;
378 
379 		case MSG_SHOW_AVAILABLE_PACKAGES:
380 			{
381 				BAutolock locker(fModel.Lock());
382 				fModel.SetShowAvailablePackages(
383 					!fModel.ShowAvailablePackages());
384 			}
385 			_AdoptModel();
386 			break;
387 
388 		case MSG_SHOW_INSTALLED_PACKAGES:
389 			{
390 				BAutolock locker(fModel.Lock());
391 				fModel.SetShowInstalledPackages(
392 					!fModel.ShowInstalledPackages());
393 			}
394 			_AdoptModel();
395 			break;
396 
397 		case MSG_SHOW_SOURCE_PACKAGES:
398 			{
399 				BAutolock locker(fModel.Lock());
400 				fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
401 			}
402 			_AdoptModel();
403 			break;
404 
405 		case MSG_SHOW_DEVELOP_PACKAGES:
406 			{
407 				BAutolock locker(fModel.Lock());
408 				fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
409 			}
410 			_AdoptModel();
411 			break;
412 
413 			// this may be triggered by, for example, a user rating being added
414 			// or having been altered.
415 		case MSG_SERVER_DATA_CHANGED:
416 		{
417 			BString name;
418 			if (message->FindString("name", &name) == B_OK) {
419 				BAutolock locker(fModel.Lock());
420 				if (fPackageInfoView->Package()->Name() == name) {
421 					_PopulatePackageAsync(true);
422 				} else {
423 					if (Logger::IsDebugEnabled()) {
424 						printf("pkg [%s] is updated on the server, but is "
425 							"not selected so will not be updated.\n",
426 							name.String());
427 					}
428 				}
429 			}
430         	break;
431         }
432 
433 		case MSG_PACKAGE_SELECTED:
434 		{
435 			BString name;
436 			if (message->FindString("name", &name) == B_OK) {
437 				BAutolock locker(fModel.Lock());
438 				int count = fVisiblePackages.CountItems();
439 				for (int i = 0; i < count; i++) {
440 					const PackageInfoRef& package
441 						= fVisiblePackages.ItemAtFast(i);
442 					if (package.Get() != NULL && package->Name() == name) {
443 						locker.Unlock();
444 						_AdoptPackage(package);
445 						break;
446 					}
447 				}
448 			} else {
449 				_ClearPackage();
450 			}
451 			break;
452 		}
453 
454 		case MSG_CATEGORY_SELECTED:
455 		{
456 			BString code;
457 			if (message->FindString("code", &code) != B_OK)
458 				code = "";
459 			{
460 				BAutolock locker(fModel.Lock());
461 				fModel.SetCategory(code);
462 			}
463 			_AdoptModel();
464 			break;
465 		}
466 
467 		case MSG_DEPOT_SELECTED:
468 		{
469 			BString name;
470 			if (message->FindString("name", &name) != B_OK)
471 				name = "";
472 			{
473 				BAutolock locker(fModel.Lock());
474 				fModel.SetDepot(name);
475 			}
476 			_AdoptModel();
477 			_UpdateAvailableRepositories();
478 			break;
479 		}
480 
481 		case MSG_SEARCH_TERMS_MODIFIED:
482 		{
483 			// TODO: Do this with a delay!
484 			BString searchTerms;
485 			if (message->FindString("search terms", &searchTerms) != B_OK)
486 				searchTerms = "";
487 			{
488 				BAutolock locker(fModel.Lock());
489 				fModel.SetSearchTerms(searchTerms);
490 			}
491 			_AdoptModel();
492 			break;
493 		}
494 
495 		case MSG_PACKAGE_CHANGED:
496 		{
497 			PackageInfo* info;
498 			if (message->FindPointer("package", (void**)&info) == B_OK) {
499 				PackageInfoRef ref(info, true);
500 				uint32 changes;
501 				if (message->FindUInt32("changes", &changes) != B_OK)
502 					changes = 0;
503 				if ((changes & PKG_CHANGED_STATE) != 0) {
504 					BAutolock locker(fModel.Lock());
505 					fModel.SetPackageState(ref, ref->State());
506 				}
507 
508 				// Asynchronous updates to the package information
509 				// can mean that the package needs to be added or
510 				// removed to/from the visible packages when the current
511 				// filter parameters are re-evaluated on this package.
512 				bool wasVisible = fVisiblePackages.Contains(ref);
513 				bool isVisible;
514 				{
515 					BAutolock locker(fModel.Lock());
516 					// The package didn't get a chance yet to be in the
517 					// visible package list
518 					isVisible = fModel.MatchesFilter(ref);
519 
520 					// Transfer this single package, otherwise we miss
521 					// other packages if they appear or disappear along
522 					// with this one before receive a notification for
523 					// them.
524 					if (isVisible) {
525 						fVisiblePackages.Add(ref);
526 					} else if (wasVisible)
527 						fVisiblePackages.Remove(ref);
528 				}
529 
530 				if (wasVisible != isVisible) {
531 					if (!isVisible) {
532 						fPackageListView->RemovePackage(ref);
533 						fFeaturedPackagesView->RemovePackage(ref);
534 					} else {
535 						fPackageListView->AddPackage(ref);
536 						if (ref->IsProminent())
537 							fFeaturedPackagesView->AddPackage(ref);
538 					}
539 				}
540 
541 				if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0
542 						&& fCoordinator == NULL) {
543 					fWorkStatusView->PackageStatusChanged(ref);
544 				}
545 			}
546 			break;
547 		}
548 
549 		case MSG_RATE_PACKAGE:
550 			_RatePackage();
551 			break;
552 
553 		case MSG_SHOW_SCREENSHOT:
554 			_ShowScreenshot();
555 			break;
556 
557 		case MSG_PACKAGE_WORKER_BUSY:
558 		{
559 			BString reason;
560 			status_t status = message->FindString("reason", &reason);
561 			if (status != B_OK)
562 				break;
563 			if (!fSinglePackageMode)
564 				fWorkStatusView->SetBusy(reason);
565 			break;
566 		}
567 
568 		case MSG_PACKAGE_WORKER_IDLE:
569 			if (!fSinglePackageMode)
570 				fWorkStatusView->SetIdle();
571 			break;
572 
573 		case MSG_ADD_VISIBLE_PACKAGES:
574 		{
575 			struct SemaphoreReleaser {
576 				SemaphoreReleaser(sem_id semaphore)
577 					:
578 					fSemaphore(semaphore)
579 				{ }
580 
581 				~SemaphoreReleaser() { release_sem(fSemaphore); }
582 
583 				sem_id fSemaphore;
584 			};
585 
586 			// Make sure acknowledge semaphore is released even on error,
587 			// so the worker thread won't be blocked
588 			SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
589 
590 			int32 numPackages = 0;
591 			type_code unused;
592 			status_t status = message->GetInfo("package_ref", &unused,
593 				&numPackages);
594 			if (status != B_OK || numPackages == 0)
595 				break;
596 
597 			int32 listID = 0;
598 			status = message->FindInt32("list_id", &listID);
599 			if (status != B_OK)
600 				break;
601 			if (listID != atomic_get(&fPackagesToShowListID)) {
602 				// list is outdated, ignore
603 				break;
604 			}
605 
606 			for (int i = 0; i < numPackages; i++) {
607 				PackageInfo* packageRaw = NULL;
608 				status = message->FindPointer("package_ref", i,
609 					(void**)&packageRaw);
610 				if (status != B_OK)
611 					break;
612 				PackageInfoRef package(packageRaw, true);
613 
614 				fPackageListView->AddPackage(package);
615 				if (package->IsProminent())
616 					fFeaturedPackagesView->AddPackage(package);
617 			}
618 			break;
619 		}
620 
621 		case MSG_UPDATE_SELECTED_PACKAGE:
622 		{
623 			const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
624 			fFeaturedPackagesView->SelectPackage(selectedPackage, true);
625 			fPackageListView->SelectPackage(selectedPackage);
626 
627 			AutoLocker<BLocker> modelLocker(fModel.Lock());
628 			if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
629 				fPackageInfoView->Clear();
630 			break;
631 		}
632 
633 		case MSG_USER_USAGE_CONDITIONS_NOT_LATEST:
634 		{
635 			BMessage userDetailMsg;
636 			if (message->FindMessage("userDetail", &userDetailMsg) != B_OK) {
637 				debugger("expected the [userDetail] data to be carried in the "
638 					"message.");
639 			}
640 			UserDetail userDetail(&userDetailMsg);
641 			_HandleUserUsageConditionsNotLatest(userDetail);
642 			break;
643 		}
644 
645 		default:
646 			BWindow::MessageReceived(message);
647 			break;
648 	}
649 }
650 
651 
652 static const char*
653 main_window_package_list_view_mode_str(package_list_view_mode mode)
654 {
655 	if (mode == PROMINENT)
656 		return "PROMINENT";
657 	return "ALL";
658 }
659 
660 
661 static package_list_view_mode
662 main_window_str_to_package_list_view_mode(const BString& str)
663 {
664 	if (str == "PROMINENT")
665 		return PROMINENT;
666 	return ALL;
667 }
668 
669 
670 void
671 MainWindow::StoreSettings(BMessage& settings) const
672 {
673 	settings.AddRect(_WindowFrameName(), Frame());
674 	if (!fSinglePackageMode) {
675 		settings.AddRect("window frame", Frame());
676 
677 		BMessage columnSettings;
678 		fPackageListView->SaveState(&columnSettings);
679 
680 		settings.AddMessage("column settings", &columnSettings);
681 
682 		settings.AddString(KEY_PACKAGE_LIST_VIEW_MODE,
683 			main_window_package_list_view_mode_str(
684 				fModel.PackageListViewMode()));
685 		settings.AddBool("show available packages",
686 			fModel.ShowAvailablePackages());
687 		settings.AddBool("show installed packages",
688 			fModel.ShowInstalledPackages());
689 		settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
690 		settings.AddBool("show source packages", fModel.ShowSourcePackages());
691 	}
692 
693 	settings.AddString("username", fModel.Nickname());
694 }
695 
696 
697 void
698 MainWindow::PackageChanged(const PackageInfoEvent& event)
699 {
700 	uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
701 	if ((event.Changes() & watchedChanges) != 0) {
702 		PackageInfoRef ref(event.Package());
703 		BMessage message(MSG_PACKAGE_CHANGED);
704 		message.AddPointer("package", ref.Get());
705 		message.AddUInt32("changes", event.Changes());
706 		ref.Detach();
707 			// reference needs to be released by MessageReceived();
708 		PostMessage(&message);
709 	}
710 }
711 
712 
713 status_t
714 MainWindow::SchedulePackageActions(PackageActionList& list)
715 {
716 	AutoLocker<BLocker> lock(&fPendingActionsLock);
717 	for (int32 i = 0; i < list.CountItems(); i++) {
718 		if (!fPendingActions.Add(list.ItemAtFast(i)))
719 			return B_NO_MEMORY;
720 	}
721 
722 	return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
723 }
724 
725 
726 Model*
727 MainWindow::GetModel()
728 {
729 	return &fModel;
730 }
731 
732 
733 void
734 MainWindow::_BuildMenu(BMenuBar* menuBar)
735 {
736 	BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
737 	fRefreshRepositoriesItem = new BMenuItem(
738 		B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS));
739 	menu->AddItem(fRefreshRepositoriesItem);
740 	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
741 		B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
742 	menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
743 		B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
744 
745 	menuBar->AddItem(menu);
746 
747 	fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
748 	menuBar->AddItem(fRepositoryMenu);
749 
750 	menu = new BMenu(B_TRANSLATE("Show"));
751 
752 	fShowAvailablePackagesItem = new BMenuItem(
753 		B_TRANSLATE("Available packages"),
754 		new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
755 	menu->AddItem(fShowAvailablePackagesItem);
756 
757 	fShowInstalledPackagesItem = new BMenuItem(
758 		B_TRANSLATE("Installed packages"),
759 		new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
760 	menu->AddItem(fShowInstalledPackagesItem);
761 
762 	menu->AddSeparatorItem();
763 
764 	fShowDevelopPackagesItem = new BMenuItem(
765 		B_TRANSLATE("Develop packages"),
766 		new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
767 	menu->AddItem(fShowDevelopPackagesItem);
768 
769 	fShowSourcePackagesItem = new BMenuItem(
770 		B_TRANSLATE("Source packages"),
771 		new BMessage(MSG_SHOW_SOURCE_PACKAGES));
772 	menu->AddItem(fShowSourcePackagesItem);
773 
774 	menuBar->AddItem(menu);
775 }
776 
777 
778 void
779 MainWindow::_BuildUserMenu(BMenuBar* menuBar)
780 {
781 	fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
782 
783 	fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
784 		new BMessage(MSG_LOG_IN));
785 	fUserMenu->AddItem(fLogInItem);
786 
787 	fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
788 		new BMessage(MSG_LOG_OUT));
789 	fUserMenu->AddItem(fLogOutItem);
790 
791 	BMenuItem *latestUserUsageConditionsMenuItem =
792 		new BMenuItem(B_TRANSLATE("View latest usage conditions"
793 			B_UTF8_ELLIPSIS),
794 			new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
795 	fUserMenu->AddItem(latestUserUsageConditionsMenuItem);
796 
797 	fUsersUserUsageConditionsMenuItem =
798 		new BMenuItem(B_TRANSLATE("View agreed usage conditions"
799 			B_UTF8_ELLIPSIS),
800 			new BMessage(MSG_VIEW_USERS_USER_USAGE_CONDITIONS));
801 	fUserMenu->AddItem(fUsersUserUsageConditionsMenuItem);
802 
803 	menuBar->AddItem(fUserMenu);
804 }
805 
806 
807 void
808 MainWindow::_RestoreNickname(const BMessage& settings)
809 {
810 	BString nickname;
811 	if (settings.FindString("username", &nickname) == B_OK
812 		&& nickname.Length() > 0) {
813 		fModel.SetNickname(nickname);
814 	}
815 }
816 
817 
818 const char*
819 MainWindow::_WindowFrameName() const
820 {
821 	if (fSinglePackageMode)
822 		return "small window frame";
823 
824 	return "window frame";
825 }
826 
827 
828 void
829 MainWindow::_RestoreWindowFrame(const BMessage& settings)
830 {
831 	BRect frame = Frame();
832 
833 	BRect windowFrame;
834 	bool fromSettings = false;
835 	if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
836 		frame = windowFrame;
837 		fromSettings = true;
838 	} else if (!fSinglePackageMode) {
839 		// Resize to occupy a certain screen size
840 		BRect screenFrame = BScreen(this).Frame();
841 		float width = frame.Width();
842 		float height = frame.Height();
843 		if (width < screenFrame.Width() * .666f
844 			&& height < screenFrame.Height() * .666f) {
845 			frame.bottom = frame.top + screenFrame.Height() * .666f;
846 			frame.right = frame.left
847 				+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
848 		}
849 	}
850 
851 	MoveTo(frame.LeftTop());
852 	ResizeTo(frame.Width(), frame.Height());
853 
854 	if (fromSettings)
855 		MoveOnScreen();
856 	else
857 		CenterOnScreen();
858 }
859 
860 
861 void
862 MainWindow::_RestoreModelSettings(const BMessage& settings)
863 {
864 	BString packageListViewMode;
865 	if (settings.FindString(KEY_PACKAGE_LIST_VIEW_MODE,
866 			&packageListViewMode) == B_OK) {
867 		fModel.SetPackageListViewMode(
868 			main_window_str_to_package_list_view_mode(packageListViewMode));
869 	}
870 
871 	bool showOption;
872 	if (settings.FindBool("show available packages", &showOption) == B_OK)
873 		fModel.SetShowAvailablePackages(showOption);
874 	if (settings.FindBool("show installed packages", &showOption) == B_OK)
875 		fModel.SetShowInstalledPackages(showOption);
876 	if (settings.FindBool("show develop packages", &showOption) == B_OK)
877 		fModel.SetShowDevelopPackages(showOption);
878 	if (settings.FindBool("show source packages", &showOption) == B_OK)
879 		fModel.SetShowSourcePackages(showOption);
880 }
881 
882 
883 void
884 MainWindow::_InitWorkerThreads()
885 {
886 	fPendingActionsSem = create_sem(0, "PendingPackageActions");
887 	if (fPendingActionsSem >= 0) {
888 		fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
889 			"Planet Express", B_NORMAL_PRIORITY, this);
890 		if (fPendingActionsWorker >= 0)
891 			resume_thread(fPendingActionsWorker);
892 	} else
893 		fPendingActionsWorker = -1;
894 
895 	fPackageToPopulateSem = create_sem(0, "PopulatePackage");
896 	if (fPackageToPopulateSem >= 0) {
897 		fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
898 			"Package Populator", B_NORMAL_PRIORITY, this);
899 		if (fPopulatePackageWorker >= 0)
900 			resume_thread(fPopulatePackageWorker);
901 	} else
902 		fPopulatePackageWorker = -1;
903 
904 	fNewPackagesToShowSem = create_sem(0, "ShowPackages");
905 	fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
906 	if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
907 		fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
908 			"Good news everyone", B_NORMAL_PRIORITY, this);
909 		if (fShowPackagesWorker >= 0)
910 			resume_thread(fShowPackagesWorker);
911 	} else
912 		fShowPackagesWorker = -1;
913 }
914 
915 
916 void
917 MainWindow::_AdoptModel()
918 {
919 	if (Logger::IsTraceEnabled())
920 		printf("adopting model to main window ui");
921 
922 	{
923 		AutoLocker<BLocker> modelLocker(fModel.Lock());
924 		fVisiblePackages = fModel.CreatePackageList();
925 		AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
926 		fPackagesToShowList = fVisiblePackages;
927 		atomic_add(&fPackagesToShowListID, 1);
928 	}
929 
930 	fFeaturedPackagesView->Clear();
931 	fPackageListView->Clear();
932 
933 	release_sem(fNewPackagesToShowSem);
934 
935 	BAutolock locker(fModel.Lock());
936 	fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
937 	fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
938 	fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
939 	fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
940 
941 	if (fModel.PackageListViewMode() == PROMINENT)
942 		fListTabs->Select(TAB_PROMINENT_PACKAGES);
943 	else
944 		fListTabs->Select(TAB_ALL_PACKAGES);
945 
946 	fFilterView->AdoptModel(fModel);
947 }
948 
949 
950 void
951 MainWindow::_AdoptPackage(const PackageInfoRef& package)
952 {
953 	{
954 		BAutolock locker(fModel.Lock());
955 		fPackageInfoView->SetPackage(package);
956 
957 		if (fFeaturedPackagesView != NULL)
958 			fFeaturedPackagesView->SelectPackage(package);
959 		if (fPackageListView != NULL)
960 			fPackageListView->SelectPackage(package);
961 	}
962 
963 	_PopulatePackageAsync(false);
964 }
965 
966 
967 void
968 MainWindow::_ClearPackage()
969 {
970 	fPackageInfoView->Clear();
971 }
972 
973 
974 void
975 MainWindow::_StartBulkLoad(bool force)
976 {
977 	fRefreshRepositoriesItem->SetEnabled(false);
978 	ProcessCoordinator* bulkLoadCoordinator =
979 		ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
980 			this,
981 				// PackageInfoListener
982 			this,
983 				// ProcessCoordinatorListener
984 			&fModel, force);
985 	_AddProcessCoordinator(bulkLoadCoordinator);
986 }
987 
988 
989 void
990 MainWindow::_BulkLoadCompleteReceived(status_t errorStatus)
991 {
992 	if (errorStatus != B_OK) {
993 		AppUtils::NotifySimpleError(
994 			B_TRANSLATE("Package update error"),
995 			B_TRANSLATE("While updating package data, a problem has arisen "
996 				"that may cause data to be outdated or missing from the "
997 				"application's display. Additional details regarding this "
998 				"problem may be able to be obtained from the application "
999 				"logs."
1000 				ALERT_MSG_LOGS_USER_GUIDE));
1001 	}
1002 
1003 	fRefreshRepositoriesItem->SetEnabled(true);
1004 	_AdoptModel();
1005 	_UpdateAvailableRepositories();
1006 
1007 	// if after loading everything in, it transpires that there are no
1008 	// featured packages then the featured packages should be disabled
1009 	// and the user should be switched to the "all packages" view so
1010 	// that they are not presented with a blank window!
1011 
1012 	bool hasProminentPackages = fModel.HasAnyProminentPackages();
1013 	fListTabs->TabAt(TAB_PROMINENT_PACKAGES)->SetEnabled(hasProminentPackages);
1014 	if (!hasProminentPackages
1015 			&& fListTabs->Selection() == TAB_PROMINENT_PACKAGES) {
1016 		fModel.SetPackageListViewMode(ALL);
1017 		fListTabs->Select(TAB_ALL_PACKAGES);
1018 	}
1019 }
1020 
1021 
1022 void
1023 MainWindow::_NotifyWorkStatusClear()
1024 {
1025 	BMessage message(MSG_WORK_STATUS_CLEAR);
1026 	this->PostMessage(&message, this);
1027 }
1028 
1029 
1030 void
1031 MainWindow::_HandleWorkStatusClear()
1032 {
1033 	fWorkStatusView->SetText("");
1034 	fWorkStatusView->SetIdle();
1035 }
1036 
1037 
1038 /*! Sends off a message to the Window so that it can change the status view
1039     on the front-end in the UI thread.
1040 */
1041 
1042 void
1043 MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
1044 {
1045 	BMessage message(MSG_WORK_STATUS_CHANGE);
1046 
1047 	if (!text.IsEmpty())
1048 		message.AddString(KEY_WORK_STATUS_TEXT, text);
1049 	message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress);
1050 
1051 	this->PostMessage(&message, this);
1052 }
1053 
1054 
1055 void
1056 MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
1057 {
1058 	BString text;
1059 	float progress;
1060 
1061 	if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
1062 		fWorkStatusView->SetText(text);
1063 
1064 	if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK)
1065 		fWorkStatusView->SetProgress(progress);
1066 }
1067 
1068 
1069 status_t
1070 MainWindow::_PackageActionWorker(void* arg)
1071 {
1072 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1073 
1074 	while (acquire_sem(window->fPendingActionsSem) == B_OK) {
1075 		PackageActionRef ref;
1076 		{
1077 			AutoLocker<BLocker> lock(&window->fPendingActionsLock);
1078 			ref = window->fPendingActions.ItemAt(0);
1079 			if (ref.Get() == NULL)
1080 				break;
1081 			window->fPendingActions.Remove(0);
1082 		}
1083 
1084 		BMessenger messenger(window);
1085 		BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
1086 		BString text(ref->Label());
1087 		text << B_UTF8_ELLIPSIS;
1088 		busyMessage.AddString("reason", text);
1089 
1090 		messenger.SendMessage(&busyMessage);
1091 		ref->Perform();
1092 		messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
1093 	}
1094 
1095 	return 0;
1096 }
1097 
1098 
1099 /*! This method will cause the package to have its data refreshed from
1100     the server application.  The refresh happens in the background; this method
1101     is asynchronous.
1102 */
1103 
1104 void
1105 MainWindow::_PopulatePackageAsync(bool forcePopulate)
1106 {
1107 		// Trigger asynchronous package population from the web-app
1108 	{
1109 		AutoLocker<BLocker> lock(&fPackageToPopulateLock);
1110 		fPackageToPopulate = fPackageInfoView->Package();
1111 		fForcePopulatePackage = forcePopulate;
1112 	}
1113 	release_sem_etc(fPackageToPopulateSem, 1, 0);
1114 
1115 	if (Logger::IsDebugEnabled()) {
1116 		printf("pkg [%s] will be updated from the server.\n",
1117 			fPackageToPopulate->Name().String());
1118 	}
1119 }
1120 
1121 
1122 /*! This method will run in the background.  The thread will block until there
1123     is a package to be updated.  When the thread unblocks, it will update the
1124     package with information from the server.
1125 */
1126 
1127 status_t
1128 MainWindow::_PopulatePackageWorker(void* arg)
1129 {
1130 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1131 
1132 	while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
1133 		PackageInfoRef package;
1134 		bool force;
1135 		{
1136 			AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
1137 			package = window->fPackageToPopulate;
1138 			force = window->fForcePopulatePackage;
1139 		}
1140 
1141 		if (package.Get() != NULL) {
1142 			uint32 populateFlags = Model::POPULATE_USER_RATINGS
1143 				| Model::POPULATE_SCREEN_SHOTS
1144 				| Model::POPULATE_CHANGELOG;
1145 
1146 			if (force)
1147 				populateFlags |= Model::POPULATE_FORCE;
1148 
1149 			window->fModel.PopulatePackage(package, populateFlags);
1150 
1151 			if (Logger::IsDebugEnabled()) {
1152 				printf("populating package [%s]\n",
1153 					package->Name().String());
1154 			}
1155 		}
1156 	}
1157 
1158 	return 0;
1159 }
1160 
1161 
1162 /* static */ status_t
1163 MainWindow::_PackagesToShowWorker(void* arg)
1164 {
1165 	MainWindow* window = reinterpret_cast<MainWindow*>(arg);
1166 
1167 	while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
1168 		PackageList packageList;
1169 		int32 listID = 0;
1170 		{
1171 			AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
1172 			packageList = window->fPackagesToShowList;
1173 			listID = atomic_get(&window->fPackagesToShowListID);
1174 			window->fPackagesToShowList.Clear();
1175 		}
1176 
1177 		// Add packages to list views in batches of kPackagesPerUpdate so we
1178 		// don't block the window thread for long with each iteration
1179 		enum {
1180 			kPackagesPerUpdate = 20
1181 		};
1182 		uint32 packagesInMessage = 0;
1183 		BMessage message(MSG_ADD_VISIBLE_PACKAGES);
1184 		BMessenger messenger(window);
1185 		bool listIsOutdated = false;
1186 
1187 		for (int i = 0; i < packageList.CountItems(); i++) {
1188 			const PackageInfoRef& package = packageList.ItemAtFast(i);
1189 
1190 			if (packagesInMessage >= kPackagesPerUpdate) {
1191 				if (listID != atomic_get(&window->fPackagesToShowListID)) {
1192 					// The model was changed again in the meantime, and thus
1193 					// our package list isn't current anymore. Send no further
1194 					// messags based on this list and go back to start.
1195 					listIsOutdated = true;
1196 					break;
1197 				}
1198 
1199 				message.AddInt32("list_id", listID);
1200 				messenger.SendMessage(&message);
1201 				message.MakeEmpty();
1202 				packagesInMessage = 0;
1203 
1204 				// Don't spam the window's message queue, which would make it
1205 				// unresponsive (i.e. allows UI messages to get in between our
1206 				// messages). When it has processed the message we just sent,
1207 				// it will let us know by releasing the semaphore.
1208 				acquire_sem(window->fShowPackagesAcknowledgeSem);
1209 			}
1210 			package->AcquireReference();
1211 			message.AddPointer("package_ref", package.Get());
1212 			packagesInMessage++;
1213 		}
1214 
1215 		if (listIsOutdated)
1216 			continue;
1217 
1218 		// Send remaining package infos, if any, which didn't make it into
1219 		// the last message (count < kPackagesPerUpdate)
1220 		if (packagesInMessage > 0) {
1221 			message.AddInt32("list_id", listID);
1222 			messenger.SendMessage(&message);
1223 			acquire_sem(window->fShowPackagesAcknowledgeSem);
1224 		}
1225 
1226 		// Update selected package in list views
1227 		messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
1228 	}
1229 
1230 	return 0;
1231 }
1232 
1233 
1234 void
1235 MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
1236 {
1237 	UserLoginWindow* window = new UserLoginWindow(this,
1238 		BRect(0, 0, 500, 400), fModel);
1239 
1240 	if (onSuccessMessage.what != 0)
1241 		window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
1242 
1243 	window->Show();
1244 }
1245 
1246 
1247 void
1248 MainWindow::_StartUserVerify()
1249 {
1250 	if (!fModel.Nickname().IsEmpty()) {
1251 		_AddProcessCoordinator(
1252 			ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator(
1253 				this,
1254 					// UserDetailVerifierListener
1255 				this,
1256 					// ProcessCoordinatorListener
1257 				&fModel) );
1258 	}
1259 }
1260 
1261 
1262 void
1263 MainWindow::_UpdateAuthorization()
1264 {
1265 	BString nickname(fModel.Nickname());
1266 	bool hasUser = !nickname.IsEmpty();
1267 
1268 	if (fLogOutItem != NULL)
1269 		fLogOutItem->SetEnabled(hasUser);
1270 	if (fUsersUserUsageConditionsMenuItem != NULL)
1271 		fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser);
1272 	if (fLogInItem != NULL) {
1273 		if (hasUser)
1274 			fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
1275 		else
1276 			fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
1277 	}
1278 
1279 	if (fUserMenu != NULL) {
1280 		BString label;
1281 		if (hasUser) {
1282 			label = B_TRANSLATE("Logged in as %User%");
1283 			label.ReplaceAll("%User%", nickname);
1284 		} else {
1285 			label = B_TRANSLATE("Not logged in");
1286 		}
1287 		fUserMenu->Superitem()->SetLabel(label);
1288 	}
1289 }
1290 
1291 
1292 void
1293 MainWindow::_UpdateAvailableRepositories()
1294 {
1295 	fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
1296 
1297 	fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
1298 		new BMessage(MSG_DEPOT_SELECTED)));
1299 
1300 	fRepositoryMenu->AddItem(new BSeparatorItem());
1301 
1302 	bool foundSelectedDepot = false;
1303 	const DepotList& depots = fModel.Depots();
1304 	for (int i = 0; i < depots.CountItems(); i++) {
1305 		const DepotInfo& depot = depots.ItemAtFast(i);
1306 
1307 		if (depot.Name().Length() != 0) {
1308 			BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
1309 			message->AddString("name", depot.Name());
1310 			BMenuItem* item = new BMenuItem(depot.Name(), message);
1311 			fRepositoryMenu->AddItem(item);
1312 
1313 			if (depot.Name() == fModel.Depot()) {
1314 				item->SetMarked(true);
1315 				foundSelectedDepot = true;
1316 			}
1317 		}
1318 	}
1319 
1320 	if (!foundSelectedDepot)
1321 		fRepositoryMenu->ItemAt(0)->SetMarked(true);
1322 }
1323 
1324 
1325 bool
1326 MainWindow::_SelectedPackageHasWebAppRepositoryCode()
1327 {
1328 	const PackageInfoRef& package = fPackageInfoView->Package();
1329 	const BString depotName = package->DepotName();
1330 
1331 	if (depotName.IsEmpty()) {
1332 		if (Logger::IsDebugEnabled()) {
1333 			printf("the package [%s] has no depot name\n",
1334 				package->Name().String());
1335 		}
1336 	} else {
1337 		const DepotInfo* depot = fModel.DepotForName(depotName);
1338 
1339 		if (depot == NULL) {
1340 			printf("the depot [%s] was not able to be found\n",
1341 				depotName.String());
1342 		} else {
1343 			BString repositoryCode = depot->WebAppRepositoryCode();
1344 
1345 			if (repositoryCode.IsEmpty()) {
1346 				printf("the depot [%s] has no web app repository code\n",
1347 					depotName.String());
1348 			} else {
1349 				return true;
1350 			}
1351 		}
1352 	}
1353 
1354 	return false;
1355 }
1356 
1357 
1358 void
1359 MainWindow::_RatePackage()
1360 {
1361 	if (!_SelectedPackageHasWebAppRepositoryCode()) {
1362 		BAlert* alert = new(std::nothrow) BAlert(
1363 			B_TRANSLATE("Rating not possible"),
1364 			B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
1365 				"Server, so it's not possible to create a new rating "
1366 				"or edit an existing rating."),
1367 			B_TRANSLATE("OK"));
1368 		alert->Go();
1369     	return;
1370 	}
1371 
1372 	if (fModel.Nickname().IsEmpty()) {
1373 		BAlert* alert = new(std::nothrow) BAlert(
1374 			B_TRANSLATE("Not logged in"),
1375 			B_TRANSLATE("You need to be logged into an account before you "
1376 				"can rate packages."),
1377 			B_TRANSLATE("Cancel"),
1378 			B_TRANSLATE("Login or Create account"));
1379 
1380 		if (alert == NULL)
1381 			return;
1382 
1383 		int32 choice = alert->Go();
1384 		if (choice == 1)
1385 			_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
1386 		return;
1387 	}
1388 
1389 	// TODO: Allow only one RatePackageWindow
1390 	// TODO: Mechanism for remembering the window frame
1391 	RatePackageWindow* window = new RatePackageWindow(this,
1392 		BRect(0, 0, 500, 400), fModel);
1393 	window->SetPackage(fPackageInfoView->Package());
1394 	window->Show();
1395 }
1396 
1397 
1398 void
1399 MainWindow::_ShowScreenshot()
1400 {
1401 	// TODO: Mechanism for remembering the window frame
1402 	if (fScreenshotWindow == NULL)
1403 		fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
1404 
1405 	if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
1406 		return;
1407 
1408 	fScreenshotWindow->SetPackage(fPackageInfoView->Package());
1409 
1410 	if (fScreenshotWindow->IsHidden())
1411 		fScreenshotWindow->Show();
1412 	else
1413 		fScreenshotWindow->Activate();
1414 
1415 	fScreenshotWindow->Unlock();
1416 }
1417 
1418 
1419 void
1420 MainWindow::_ViewUserUsageConditions(
1421 	UserUsageConditionsSelectionMode mode)
1422 {
1423 	UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
1424 		fModel, mode);
1425 	window->Show();
1426 }
1427 
1428 
1429 void
1430 MainWindow::UserCredentialsFailed()
1431 {
1432 	BString message = B_TRANSLATE("The password previously "
1433 		"supplied for the user [%Nickname%] is not currently "
1434 		"valid. The user will be logged-out of this application "
1435 		"and you should login again with your updated password.");
1436 	message.ReplaceAll("%Nickname%", fModel.Nickname());
1437 
1438 	AppUtils::NotifySimpleError(B_TRANSLATE("Login issue"),
1439 		message);
1440 
1441 	{
1442 		AutoLocker<BLocker> locker(fModel.Lock());
1443 		fModel.SetNickname("");
1444 	}
1445 }
1446 
1447 
1448 /*! \brief This method is invoked from the UserDetailVerifierProcess on a
1449 		   background thread.  For this reason it lodges a message into itself
1450 		   which can then be handled on the main thread.
1451 */
1452 
1453 void
1454 MainWindow::UserUsageConditionsNotLatest(const UserDetail& userDetail)
1455 {
1456 	BMessage message(MSG_USER_USAGE_CONDITIONS_NOT_LATEST);
1457 	BMessage detailsMessage;
1458 	if (userDetail.Archive(&detailsMessage, true) != B_OK
1459 			|| message.AddMessage("userDetail", &detailsMessage) != B_OK) {
1460 		printf("!! unable to archive the user detail into a message\n");
1461 	}
1462 	else
1463 		BMessenger(this).SendMessage(&message);
1464 }
1465 
1466 
1467 void
1468 MainWindow::_HandleUserUsageConditionsNotLatest(
1469 	const UserDetail& userDetail)
1470 {
1471 	ToLatestUserUsageConditionsWindow* window =
1472 		new ToLatestUserUsageConditionsWindow(this, fModel, userDetail);
1473 	window->Show();
1474 }
1475 
1476 
1477 void
1478 MainWindow::_AddProcessCoordinator(ProcessCoordinator* item)
1479 {
1480 	AutoLocker<BLocker> lock(&fCoordinatorLock);
1481 
1482 	if (fCoordinator == NULL) {
1483 		if (acquire_sem(fCoordinatorRunningSem) != B_OK)
1484 			debugger("unable to acquire the process coordinator sem");
1485 		if (Logger::IsInfoEnabled()) {
1486 			printf("adding and starting a process coordinator [%s]\n",
1487 				item->Name().String());
1488 		}
1489 		fCoordinator = item;
1490 		fCoordinator->Start();
1491 	}
1492 	else {
1493 		if (Logger::IsInfoEnabled()) {
1494 			printf("adding process coordinator [%s] to the queue\n",
1495 				item->Name().String());
1496 		}
1497 		fCoordinatorQueue.push(item);
1498 	}
1499 }
1500 
1501 
1502 void
1503 MainWindow::_SpinUntilProcessCoordinatorComplete()
1504 {
1505 	while (true) {
1506 		if (acquire_sem(fCoordinatorRunningSem) != B_OK)
1507 			debugger("unable to acquire the process coordinator sem");
1508 		if (release_sem(fCoordinatorRunningSem) != B_OK)
1509 			debugger("unable to release the process coordinator sem");
1510 		{
1511 			AutoLocker<BLocker> lock(&fCoordinatorLock);
1512 			if (fCoordinator == NULL)
1513 				return;
1514 		}
1515 	}
1516 }
1517 
1518 
1519 void
1520 MainWindow::_StopProcessCoordinators()
1521 {
1522 	if (Logger::IsInfoEnabled())
1523 		printf("will stop all process coordinators\n");
1524 
1525 	{
1526 		AutoLocker<BLocker> lock(&fCoordinatorLock);
1527 
1528 		while (!fCoordinatorQueue.empty()) {
1529 			ProcessCoordinator *processCoordinator = fCoordinatorQueue.front();
1530 			if (Logger::IsInfoEnabled()) {
1531 				printf("will drop queued process coordinator [%s]\n",
1532 					processCoordinator->Name().String());
1533 			}
1534 			fCoordinatorQueue.pop();
1535 			delete processCoordinator;
1536 		}
1537 
1538 		if (fCoordinator != NULL) {
1539 			fCoordinator->Stop();
1540 		}
1541 	}
1542 
1543 	if (Logger::IsInfoEnabled())
1544 		printf("will wait until the process coordinator has stopped\n");
1545 
1546 	_SpinUntilProcessCoordinatorComplete();
1547 
1548 	if (Logger::IsInfoEnabled())
1549 		printf("did stop all process coordinators\n");
1550 }
1551 
1552 
1553 /*! This method is called when there is some change in the bulk load process
1554 	or other process coordinator.
1555 	A change may mean that a new process has started / stopped etc... or it
1556 	may mean that the entire coordinator has finished.
1557 */
1558 
1559 void
1560 MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
1561 {
1562 	AutoLocker<BLocker> lock(&fCoordinatorLock);
1563 
1564 	if (fCoordinator == coordinatorState.Coordinator()) {
1565 		if (!coordinatorState.IsRunning()) {
1566 			if (release_sem(fCoordinatorRunningSem) != B_OK)
1567 				debugger("unable to release the process coordinator sem");
1568 			if (Logger::IsInfoEnabled()) {
1569 				printf("process coordinator [%s] did complete\n",
1570 					fCoordinator->Name().String());
1571 			}
1572 			// complete the last one that just finished
1573 			BMessage* message = fCoordinator->Message();
1574 
1575 			if (message != NULL) {
1576 				BMessenger messenger(this);
1577 				message->AddInt64(KEY_ERROR_STATUS,
1578 					(int64) fCoordinator->ErrorStatus());
1579 				messenger.SendMessage(message);
1580 			}
1581 
1582 			delete fCoordinator;
1583 			fCoordinator = NULL;
1584 
1585 			// now schedule the next one.
1586 			if (!fCoordinatorQueue.empty()) {
1587 				if (acquire_sem(fCoordinatorRunningSem) != B_OK)
1588 					debugger("unable to acquire the process coordinator sem");
1589 				fCoordinator = fCoordinatorQueue.front();
1590 				if (Logger::IsInfoEnabled()) {
1591 					printf("starting next process coordinator [%s]\n",
1592 						fCoordinator->Name().String());
1593 				}
1594 				fCoordinatorQueue.pop();
1595 				fCoordinator->Start();
1596 			}
1597 			else {
1598 				_NotifyWorkStatusClear();
1599 			}
1600 		}
1601 		else {
1602 			_NotifyWorkStatusChange(coordinatorState.Message(),
1603 				coordinatorState.Progress());
1604 				// show the progress to the user.
1605 		}
1606 	} else {
1607 		if (Logger::IsInfoEnabled())
1608 			printf("! unknown process coordinator changed\n");
1609 	}
1610 }
1611 
1612 
1613 static package_list_view_mode
1614 main_window_tab_to_package_list_view_mode(int32 tab)
1615 {
1616 	if (tab == TAB_PROMINENT_PACKAGES)
1617 		return PROMINENT;
1618 	return ALL;
1619 }
1620 
1621 
1622 void
1623 MainWindow::_HandleChangePackageListViewMode()
1624 {
1625 	package_list_view_mode tabMode = main_window_tab_to_package_list_view_mode(
1626 		fListTabs->Selection());
1627 	package_list_view_mode modelMode = fModel.PackageListViewMode();
1628 
1629 	if (tabMode != modelMode) {
1630 		BAutolock locker(fModel.Lock());
1631 		fModel.SetPackageListViewMode(tabMode);
1632 	}
1633 }