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