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