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