xref: /haiku/src/apps/haikudepot/ui/App.cpp (revision 15fb7d88e971c4d6c787c6a3a5c159afb1ebf77b)
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 
38 App::App()
39 	:
40 	BApplication("application/x-vnd.Haiku-HaikuDepot"),
41 	fMainWindow(NULL),
42 	fWindowCount(0),
43 	fSettingsRead(false)
44 {
45 	_CheckPackageDaemonRuns();
46 }
47 
48 
49 App::~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 
60 bool
61 App::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 
77 void
78 App::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 
91 void
92 App::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 
136 void
137 App::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 
148 enum 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 
160 static void
161 app_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 
185 static arg_switch
186 app_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 
232 void
233 App::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 
321 void
322 App::_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 
343 void
344 App::_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 
407 void
408 App::_ShowWindow(MainWindow* window)
409 {
410 	window->Show();
411 	fWindowCount++;
412 }
413 
414 
415 bool
416 App::_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 
428 void
429 App::_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 
458 static const char* kPackageDaemonSignature
459 	= "application/x-vnd.haiku-package_daemon";
460 
461 void
462 App::_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 
484 bool
485 App::_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