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