1/*
2 * Copyright 2013, Stephan A��mus <superstippi@gmx.de>.
3 * Copyright 2017-2018, Andrew Lindesay <apl@lindesay.co.nz>.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
6
7
8#include "App.h"
9
10#include <stdio.h>
11
12#include <Alert.h>
13#include <Catalog.h>
14#include <Entry.h>
15#include <Message.h>
16#include <package/PackageDefs.h>
17#include <package/PackageInfo.h>
18#include <package/PackageRoster.h>
19#include <Path.h>
20#include <Roster.h>
21#include <Screen.h>
22#include <String.h>
23
24#include "support.h"
25
26#include "FeaturedPackagesView.h"
27#include "Logger.h"
28#include "MainWindow.h"
29#include "ServerHelper.h"
30#include "ServerSettings.h"
31#include "ScreenshotWindow.h"
32
33
34#undef B_TRANSLATION_CONTEXT
35#define B_TRANSLATION_CONTEXT "App"
36
37
38App::App()
39	:
40	BApplication("application/x-vnd.Haiku-HaikuDepot"),
41	fMainWindow(NULL),
42	fWindowCount(0),
43	fSettingsRead(false)
44{
45	_CheckPackageDaemonRuns();
46}
47
48
49App::~App()
50{
51	// We cannot let global destructors cleanup static BitmapRef objects,
52	// since calling BBitmap destructors needs a valid BApplication still
53	// around. That's why we do it here.
54	PackageInfo::CleanupDefaultIcon();
55	FeaturedPackagesView::CleanupIcons();
56	ScreenshotWindow::CleanupIcons();
57}
58
59
60bool
61App::QuitRequested()
62{
63	if (fMainWindow != NULL
64		&& fMainWindow->LockLooperWithTimeout(1500000) == B_OK) {
65		BMessage windowSettings;
66		fMainWindow->StoreSettings(windowSettings);
67
68		fMainWindow->UnlockLooper();
69
70		_StoreSettings(windowSettings);
71	}
72
73	return BApplication::QuitRequested();
74}
75
76
77void
78App::ReadyToRun()
79{
80	if (fWindowCount > 0)
81		return;
82
83	BMessage settings;
84	_LoadSettings(settings);
85
86	fMainWindow = new MainWindow(settings);
87	_ShowWindow(fMainWindow);
88}
89
90
91void
92App::MessageReceived(BMessage* message)
93{
94	switch (message->what) {
95		case MSG_MAIN_WINDOW_CLOSED:
96		{
97			BMessage windowSettings;
98			if (message->FindMessage(KEY_WINDOW_SETTINGS,
99					&windowSettings) == B_OK) {
100				_StoreSettings(windowSettings);
101			}
102
103			fWindowCount--;
104			if (fWindowCount == 0)
105				Quit();
106			break;
107		}
108
109		case MSG_CLIENT_TOO_OLD:
110			ServerHelper::AlertClientTooOld(message);
111			break;
112
113		case MSG_NETWORK_TRANSPORT_ERROR:
114			ServerHelper::AlertTransportError(message);
115			break;
116
117		case MSG_SERVER_ERROR:
118			ServerHelper::AlertServerJsonRpcError(message);
119			break;
120
121		case MSG_ALERT_SIMPLE_ERROR:
122			_AlertSimpleError(message);
123			break;
124
125		case MSG_SERVER_DATA_CHANGED:
126			fMainWindow->PostMessage(message);
127			break;
128
129		default:
130			BApplication::MessageReceived(message);
131			break;
132	}
133}
134
135
136void
137App::RefsReceived(BMessage* message)
138{
139	entry_ref ref;
140	int32 index = 0;
141	while (message->FindRef("refs", index++, &ref) == B_OK) {
142		BEntry entry(&ref, true);
143		_Open(entry);
144	}
145}
146
147
148enum arg_switch {
149	UNKNOWN_SWITCH,
150	NOT_SWITCH,
151	HELP_SWITCH,
152	WEB_APP_BASE_URL_SWITCH,
153	VERBOSITY_SWITCH,
154	FORCE_NO_NETWORKING_SWITCH,
155	PREFER_CACHE_SWITCH,
156	DROP_CACHE_SWITCH
157};
158
159
160static void
161app_print_help()
162{
163	fprintf(stdout, "HaikuDepot ");
164	fprintf(stdout, "[-u|--webappbaseurl <web-app-base-url>]\n");
165	fprintf(stdout, "[-v|--verbosity [off|info|debug|trace]\n");
166	fprintf(stdout, "[--nonetworking]\n");
167	fprintf(stdout, "[--prefercache]\n");
168	fprintf(stdout, "[--dropcache]\n");
169	fprintf(stdout, "[-h|--help]\n");
170	fprintf(stdout, "\n");
171	fprintf(stdout, "'-h' : causes this help text to be printed out.\n");
172	fprintf(stdout, "'-v' : allows for the verbosity level to be set.\n");
173	fprintf(stdout, "'-u' : allows for the haiku depot server url to be\n");
174	fprintf(stdout, "   configured.\n");
175	fprintf(stdout, "'--nonetworking' : prevents network access.\n");
176	fprintf(stdout, "'--prefercache' : prefer to get data from cache rather\n");
177	fprintf(stdout, "  then obtain data from the network.**\n");
178	fprintf(stdout, "'--dropcache' : drop cached data before performing\n");
179	fprintf(stdout, "  bulk operations.**\n");
180	fprintf(stdout, "\n");
181	fprintf(stdout, "** = only applies to bulk operations.\n");
182}
183
184
185static arg_switch
186app_resolve_switch(char *arg)
187{
188	int arglen = strlen(arg);
189
190	if (arglen > 0 && arg[0] == '-') {
191
192		if (arglen > 3 && arg[1] == '-') { // long form
193			if (0 == strcmp(&arg[2], "webappbaseurl"))
194				return WEB_APP_BASE_URL_SWITCH;
195
196			if (0 == strcmp(&arg[2], "help"))
197				return HELP_SWITCH;
198
199			if (0 == strcmp(&arg[2], "verbosity"))
200				return VERBOSITY_SWITCH;
201
202			if (0 == strcmp(&arg[2], "nonetworking"))
203				return FORCE_NO_NETWORKING_SWITCH;
204
205			if (0 == strcmp(&arg[2], "prefercache"))
206				return PREFER_CACHE_SWITCH;
207
208			if (0 == strcmp(&arg[2], "dropcache"))
209				return DROP_CACHE_SWITCH;
210		} else {
211			if (arglen == 2) { // short form
212				switch (arg[1]) {
213					case 'u':
214						return WEB_APP_BASE_URL_SWITCH;
215
216					case 'h':
217						return HELP_SWITCH;
218
219					case 'v':
220						return VERBOSITY_SWITCH;
221				}
222			}
223		}
224
225		return UNKNOWN_SWITCH;
226	}
227
228	return NOT_SWITCH;
229}
230
231
232void
233App::ArgvReceived(int32 argc, char* argv[])
234{
235	for (int i = 1; i < argc;) {
236
237			// check to make sure that if there is a value for the switch,
238			// that the value is in fact supplied.
239
240		switch (app_resolve_switch(argv[i])) {
241			case VERBOSITY_SWITCH:
242			case WEB_APP_BASE_URL_SWITCH:
243				if (i == argc-1) {
244					fprintf(stdout, "unexpected end of arguments; missing "
245						"value for switch [%s]\n", argv[i]);
246					Quit();
247					return;
248				}
249				break;
250
251			default:
252				break;
253		}
254
255			// now process each switch.
256
257		switch (app_resolve_switch(argv[i])) {
258
259			case VERBOSITY_SWITCH:
260				if (!Logger::SetLevelByName(argv[i+1])) {
261					fprintf(stdout, "unknown log level [%s]\n", argv[i + 1]);
262					Quit();
263				}
264				i++; // also move past the log level value
265				break;
266
267			case HELP_SWITCH:
268				app_print_help();
269				Quit();
270				break;
271
272			case WEB_APP_BASE_URL_SWITCH:
273				if (ServerSettings::SetBaseUrl(BUrl(argv[i + 1])) != B_OK) {
274					fprintf(stdout, "malformed web app base url; %s\n",
275						argv[i + 1]);
276					Quit();
277				}
278				else {
279					fprintf(stdout, "did configure the web base url; %s\n",
280						argv[i + 1]);
281				}
282
283				i++; // also move past the url value
284
285				break;
286
287			case FORCE_NO_NETWORKING_SWITCH:
288				ServerSettings::SetForceNoNetwork(true);
289				break;
290
291			case PREFER_CACHE_SWITCH:
292				ServerSettings::SetPreferCache(true);
293				break;
294
295			case DROP_CACHE_SWITCH:
296				ServerSettings::SetDropCache(true);
297				break;
298
299			case NOT_SWITCH:
300			{
301				BEntry entry(argv[i], true);
302				_Open(entry);
303				break;
304			}
305
306			case UNKNOWN_SWITCH:
307				fprintf(stdout, "unknown switch; %s\n", argv[i]);
308				Quit();
309				break;
310		}
311
312		i++; // move on at least one arg
313	}
314}
315
316
317/*! This method will display an alert based on a message.  This message arrives
318    from a number of possible background threads / processes in the application.
319*/
320
321void
322App::_AlertSimpleError(BMessage* message)
323{
324	BString alertTitle;
325	BString alertText;
326
327	if (message->FindString(KEY_ALERT_TEXT, &alertText) != B_OK)
328		alertText = "?";
329
330	if (message->FindString(KEY_ALERT_TITLE, &alertTitle) != B_OK)
331		alertTitle = B_TRANSLATE("Error");
332
333	BAlert* alert = new BAlert(alertTitle, alertText, B_TRANSLATE("OK"));
334
335	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
336	alert->Go();
337}
338
339
340// #pragma mark - private
341
342
343void
344App::_Open(const BEntry& entry)
345{
346	BPath path;
347	if (!entry.Exists() || entry.GetPath(&path) != B_OK) {
348		fprintf(stderr, "Package file not found: %s\n", path.Path());
349		return;
350	}
351
352	// Try to parse package file via Package Kit
353	BPackageKit::BPackageInfo info;
354	status_t status = info.ReadFromPackageFile(path.Path());
355	if (status != B_OK) {
356		fprintf(stderr, "Failed to parse package file: %s\n",
357			strerror(status));
358		return;
359	}
360
361	// Transfer information into PackageInfo
362	PackageInfoRef package(new(std::nothrow) PackageInfo(info), true);
363	if (package.Get() == NULL) {
364		fprintf(stderr, "Could not allocate PackageInfo\n");
365		return;
366	}
367
368	package->SetLocalFilePath(path.Path());
369
370	// Set if the package is active
371	//
372	// TODO(leavengood): It is very awkward having to check these two locations
373	// here, and in many other places in HaikuDepot. Why do clients of the
374	// package kit have to know about these locations?
375	bool active = false;
376	BPackageKit::BPackageRoster roster;
377	status = roster.IsPackageActive(
378		BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, info, &active);
379	if (status != B_OK) {
380		fprintf(stderr, "Could not check if package was active in system: %s\n",
381			strerror(status));
382		return;
383	}
384	if (!active) {
385		status = roster.IsPackageActive(
386			BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_HOME, info, &active);
387		if (status != B_OK) {
388			fprintf(stderr,
389				"Could not check if package was active in home: %s\n",
390				strerror(status));
391			return;
392		}
393	}
394
395	if (active) {
396		package->SetState(ACTIVATED);
397	}
398
399	BMessage settings;
400	_LoadSettings(settings);
401
402	MainWindow* window = new MainWindow(settings, package);
403	_ShowWindow(window);
404}
405
406
407void
408App::_ShowWindow(MainWindow* window)
409{
410	window->Show();
411	fWindowCount++;
412}
413
414
415bool
416App::_LoadSettings(BMessage& settings)
417{
418	if (!fSettingsRead) {
419		fSettingsRead = true;
420		if (load_settings(&fSettings, KEY_MAIN_SETTINGS, "HaikuDepot") != B_OK)
421			fSettings.MakeEmpty();
422	}
423	settings = fSettings;
424	return !fSettings.IsEmpty();
425}
426
427
428void
429App::_StoreSettings(const BMessage& settings)
430{
431	// Take what is in settings and replace data under the same name in
432	// fSettings, leaving anything in fSettings that is not contained in
433	// settings.
434	int32 i = 0;
435
436	char* name;
437	type_code type;
438	int32 count;
439
440	while (settings.GetInfo(B_ANY_TYPE, i++, &name, &type, &count) == B_OK) {
441		fSettings.RemoveName(name);
442		for (int32 j = 0; j < count; j++) {
443			const void* data;
444			ssize_t size;
445			if (settings.FindData(name, type, j, &data, &size) != B_OK)
446				break;
447			fSettings.AddData(name, type, data, size);
448		}
449	}
450
451	save_settings(&fSettings, KEY_MAIN_SETTINGS, "HaikuDepot");
452}
453
454
455// #pragma mark -
456
457
458static const char* kPackageDaemonSignature
459	= "application/x-vnd.haiku-package_daemon";
460
461void
462App::_CheckPackageDaemonRuns()
463{
464	while (!be_roster->IsRunning(kPackageDaemonSignature)) {
465		BAlert* alert = new BAlert(
466			B_TRANSLATE("Start package daemon"),
467			B_TRANSLATE("HaikuDepot needs the package daemon to function, "
468				"and it appears to be not running.\n"
469				"Would you like to start it now?"),
470			B_TRANSLATE("No, quit HaikuDepot"),
471			B_TRANSLATE("Start package daemon"), NULL, B_WIDTH_AS_USUAL,
472			B_WARNING_ALERT);
473		alert->SetShortcut(0, B_ESCAPE);
474
475		if (alert->Go() == 0)
476			exit(1);
477
478		if (!_LaunchPackageDaemon())
479			break;
480	}
481}
482
483
484bool
485App::_LaunchPackageDaemon()
486{
487	status_t ret = be_roster->Launch(kPackageDaemonSignature);
488	if (ret != B_OK) {
489		BString errorMessage
490			= B_TRANSLATE("Starting the package daemon failed:\n\n%Error%");
491		errorMessage.ReplaceAll("%Error%", strerror(ret));
492
493		BAlert* alert = new BAlert(
494			B_TRANSLATE("Package daemon problem"), errorMessage,
495			B_TRANSLATE("Quit HaikuDepot"),
496			B_TRANSLATE("Try again"), NULL, B_WIDTH_AS_USUAL,
497			B_WARNING_ALERT);
498		alert->SetShortcut(0, B_ESCAPE);
499
500		if (alert->Go() == 0)
501			return false;
502	}
503	// TODO: Would be nice to send a message to the package daemon instead
504	// and get a reply once it is ready.
505	snooze(2000000);
506	return true;
507}
508
509