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