xref: /haiku/src/apps/haikudepot/ui/App.cpp (revision 1407e6d7b18ee10eeb8f688e966d45f9599b4abe)
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 true;
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("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 		default:
112 			BApplication::MessageReceived(message);
113 			break;
114 	}
115 }
116 
117 
118 void
119 App::RefsReceived(BMessage* message)
120 {
121 	entry_ref ref;
122 	int32 index = 0;
123 	while (message->FindRef("refs", index++, &ref) == B_OK) {
124 		BEntry entry(&ref, true);
125 		_Open(entry);
126 	}
127 }
128 
129 
130 enum arg_switch {
131 	UNKNOWN_SWITCH,
132 	NOT_SWITCH,
133 	HELP_SWITCH,
134 	WEB_APP_BASE_URL_SWITCH,
135 	VERBOSITY_SWITCH,
136 	FORCE_NO_NETWORKING_SWITCH,
137 	PREFER_CACHE_SWITCH,
138 	DROP_CACHE_SWITCH
139 };
140 
141 
142 static void
143 app_print_help()
144 {
145 	fprintf(stdout, "HaikuDepot ");
146 	fprintf(stdout, "[-u|--webappbaseurl <web-app-base-url>]\n");
147 	fprintf(stdout, "[-v|--verbosity [off|info|debug|trace]\n");
148 	fprintf(stdout, "[--nonetworking]\n");
149 	fprintf(stdout, "[--prefercache]\n");
150 	fprintf(stdout, "[--dropcache]\n");
151 	fprintf(stdout, "[-h|--help]\n");
152 	fprintf(stdout, "\n");
153 	fprintf(stdout, "'-h' : causes this help text to be printed out.\n");
154 	fprintf(stdout, "'-v' : allows for the verbosity level to be set.\n");
155 	fprintf(stdout, "'-u' : allows for the haiku depot server url to be\n");
156 	fprintf(stdout, "   configured.\n");
157 	fprintf(stdout, "'--nonetworking' : prevents network access.\n");
158 	fprintf(stdout, "'--prefercache' : prefer to get data from cache rather\n");
159 	fprintf(stdout, "  then obtain data from the network.**\n");
160 	fprintf(stdout, "'--dropcache' : drop cached data before performing\n");
161 	fprintf(stdout, "  bulk operations.**\n");
162 	fprintf(stdout, "\n");
163 	fprintf(stdout, "** = only applies to bulk operations.\n");
164 }
165 
166 
167 static arg_switch
168 app_resolve_switch(char *arg)
169 {
170 	int arglen = strlen(arg);
171 
172 	if (arglen > 0 && arg[0] == '-') {
173 
174 		if (arglen > 3 && arg[1] == '-') { // long form
175 			if (0 == strcmp(&arg[2], "webappbaseurl"))
176 				return WEB_APP_BASE_URL_SWITCH;
177 
178 			if (0 == strcmp(&arg[2], "help"))
179 				return HELP_SWITCH;
180 
181 			if (0 == strcmp(&arg[2], "verbosity"))
182 				return VERBOSITY_SWITCH;
183 
184 			if (0 == strcmp(&arg[2], "nonetworking"))
185 				return FORCE_NO_NETWORKING_SWITCH;
186 
187 			if (0 == strcmp(&arg[2], "prefercache"))
188 				return PREFER_CACHE_SWITCH;
189 
190 			if (0 == strcmp(&arg[2], "dropcache"))
191 				return DROP_CACHE_SWITCH;
192 		} else {
193 			if (arglen == 2) { // short form
194 				switch (arg[1]) {
195 					case 'u':
196 						return WEB_APP_BASE_URL_SWITCH;
197 
198 					case 'h':
199 						return HELP_SWITCH;
200 
201 					case 'v':
202 						return VERBOSITY_SWITCH;
203 				}
204 			}
205 		}
206 
207 		return UNKNOWN_SWITCH;
208 	}
209 
210 	return NOT_SWITCH;
211 }
212 
213 
214 void
215 App::ArgvReceived(int32 argc, char* argv[])
216 {
217 	for (int i = 1; i < argc;) {
218 
219 			// check to make sure that if there is a value for the switch,
220 			// that the value is in fact supplied.
221 
222 		switch (app_resolve_switch(argv[i])) {
223 			case VERBOSITY_SWITCH:
224 			case WEB_APP_BASE_URL_SWITCH:
225 				if (i == argc-1) {
226 					fprintf(stdout, "unexpected end of arguments; missing "
227 						"value for switch [%s]\n", argv[i]);
228 					Quit();
229 					return;
230 				}
231 				break;
232 
233 			default:
234 				break;
235 		}
236 
237 			// now process each switch.
238 
239 		switch (app_resolve_switch(argv[i])) {
240 
241 			case VERBOSITY_SWITCH:
242 				if (!Logger::SetLevelByName(argv[i+1])) {
243 					fprintf(stdout, "unknown log level [%s]\n", argv[i + 1]);
244 					Quit();
245 				}
246 				i++; // also move past the log level value
247 				break;
248 
249 			case HELP_SWITCH:
250 				app_print_help();
251 				Quit();
252 				break;
253 
254 			case WEB_APP_BASE_URL_SWITCH:
255 				if (ServerSettings::SetBaseUrl(BUrl(argv[i + 1])) != B_OK) {
256 					fprintf(stdout, "malformed web app base url; %s\n",
257 						argv[i + 1]);
258 					Quit();
259 				}
260 				else {
261 					fprintf(stdout, "did configure the web base url; %s\n",
262 						argv[i + 1]);
263 				}
264 
265 				i++; // also move past the url value
266 
267 				break;
268 
269 			case FORCE_NO_NETWORKING_SWITCH:
270 				ServerSettings::SetForceNoNetwork(true);
271 				break;
272 
273 			case PREFER_CACHE_SWITCH:
274 				ServerSettings::SetPreferCache(true);
275 				break;
276 
277 			case DROP_CACHE_SWITCH:
278 				ServerSettings::SetDropCache(true);
279 				break;
280 
281 			case NOT_SWITCH:
282 			{
283 				BEntry entry(argv[i], true);
284 				_Open(entry);
285 				break;
286 			}
287 
288 			case UNKNOWN_SWITCH:
289 				fprintf(stdout, "unknown switch; %s\n", argv[i]);
290 				Quit();
291 				break;
292 		}
293 
294 		i++; // move on at least one arg
295 	}
296 }
297 
298 
299 // #pragma mark - private
300 
301 
302 void
303 App::_Open(const BEntry& entry)
304 {
305 	BPath path;
306 	if (!entry.Exists() || entry.GetPath(&path) != B_OK) {
307 		fprintf(stderr, "Package file not found: %s\n", path.Path());
308 		return;
309 	}
310 
311 	// Try to parse package file via Package Kit
312 	BPackageKit::BPackageInfo info;
313 	status_t status = info.ReadFromPackageFile(path.Path());
314 	if (status != B_OK) {
315 		fprintf(stderr, "Failed to parse package file: %s\n",
316 			strerror(status));
317 		return;
318 	}
319 
320 	// Transfer information into PackageInfo
321 	PackageInfoRef package(new(std::nothrow) PackageInfo(info), true);
322 	if (package.Get() == NULL) {
323 		fprintf(stderr, "Could not allocate PackageInfo\n");
324 		return;
325 	}
326 
327 	package->SetLocalFilePath(path.Path());
328 
329 	BMessage settings;
330 	_LoadSettings(settings);
331 
332 	MainWindow* window = new MainWindow(settings, package);
333 	_ShowWindow(window);
334 }
335 
336 
337 void
338 App::_ShowWindow(MainWindow* window)
339 {
340 	window->Show();
341 	fWindowCount++;
342 }
343 
344 
345 bool
346 App::_LoadSettings(BMessage& settings)
347 {
348 	if (!fSettingsRead) {
349 		fSettings = true;
350 		if (load_settings(&fSettings, "main_settings", "HaikuDepot") != B_OK)
351 			fSettings.MakeEmpty();
352 	}
353 	settings = fSettings;
354 	return !fSettings.IsEmpty();
355 }
356 
357 
358 void
359 App::_StoreSettings(const BMessage& settings)
360 {
361 	// Take what is in settings and replace data under the same name in
362 	// fSettings, leaving anything in fSettings that is not contained in
363 	// settings.
364 	int32 i = 0;
365 
366 	char* name;
367 	type_code type;
368 	int32 count;
369 
370 	while (settings.GetInfo(B_ANY_TYPE, i++, &name, &type, &count) == B_OK) {
371 		fSettings.RemoveName(name);
372 		for (int32 j = 0; j < count; j++) {
373 			const void* data;
374 			ssize_t size;
375 			if (settings.FindData(name, type, j, &data, &size) != B_OK)
376 				break;
377 			fSettings.AddData(name, type, data, size);
378 		}
379 	}
380 
381 	save_settings(&fSettings, "main_settings", "HaikuDepot");
382 }
383 
384 
385 // #pragma mark -
386 
387 
388 static const char* kPackageDaemonSignature
389 	= "application/x-vnd.haiku-package_daemon";
390 
391 void
392 App::_CheckPackageDaemonRuns()
393 {
394 	while (!be_roster->IsRunning(kPackageDaemonSignature)) {
395 		BAlert* alert = new BAlert("start_package_daemon",
396 			B_TRANSLATE("HaikuDepot needs the package daemon to function, "
397 				"and it appears to be not running.\n"
398 				"Would you like to start it now?"),
399 			B_TRANSLATE("No, quit HaikuDepot"),
400 			B_TRANSLATE("Start package daemon"), NULL, B_WIDTH_AS_USUAL,
401 			B_WARNING_ALERT);
402 		alert->SetShortcut(0, B_ESCAPE);
403 
404 		if (alert->Go() == 0)
405 			exit(1);
406 
407 		if (!_LaunchPackageDaemon())
408 			break;
409 	}
410 }
411 
412 
413 bool
414 App::_LaunchPackageDaemon()
415 {
416 	status_t ret = be_roster->Launch(kPackageDaemonSignature);
417 	if (ret != B_OK) {
418 		BString errorMessage
419 			= B_TRANSLATE("Starting the package daemon failed:\n\n%Error%");
420 		errorMessage.ReplaceAll("%Error%", strerror(ret));
421 
422 		BAlert* alert = new BAlert("package_daemon_problem",
423 			errorMessage,
424 			B_TRANSLATE("Quit HaikuDepot"),
425 			B_TRANSLATE("Try again"), NULL, B_WIDTH_AS_USUAL,
426 			B_WARNING_ALERT);
427 		alert->SetShortcut(0, B_ESCAPE);
428 
429 		if (alert->Go() == 0)
430 			return false;
431 	}
432 	// TODO: Would be nice to send a message to the package daemon instead
433 	// and get a reply once it is ready.
434 	snooze(2000000);
435 	return true;
436 }
437 
438