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