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