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