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