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