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