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