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 true; 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("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 default: 112 BApplication::MessageReceived(message); 113 break; 114 } 115 } 116 117 118 void 119 App::RefsReceived(BMessage* message) 120 { 121 entry_ref ref; 122 int32 index = 0; 123 while (message->FindRef("refs", index++, &ref) == B_OK) { 124 BEntry entry(&ref, true); 125 _Open(entry); 126 } 127 } 128 129 130 enum arg_switch { 131 UNKNOWN_SWITCH, 132 NOT_SWITCH, 133 HELP_SWITCH, 134 WEB_APP_BASE_URL_SWITCH, 135 VERBOSITY_SWITCH, 136 FORCE_NO_NETWORKING_SWITCH, 137 PREFER_CACHE_SWITCH, 138 DROP_CACHE_SWITCH 139 }; 140 141 142 static void 143 app_print_help() 144 { 145 fprintf(stdout, "HaikuDepot "); 146 fprintf(stdout, "[-u|--webappbaseurl <web-app-base-url>]\n"); 147 fprintf(stdout, "[-v|--verbosity [off|info|debug|trace]\n"); 148 fprintf(stdout, "[--nonetworking]\n"); 149 fprintf(stdout, "[--prefercache]\n"); 150 fprintf(stdout, "[--dropcache]\n"); 151 fprintf(stdout, "[-h|--help]\n"); 152 fprintf(stdout, "\n"); 153 fprintf(stdout, "'-h' : causes this help text to be printed out.\n"); 154 fprintf(stdout, "'-v' : allows for the verbosity level to be set.\n"); 155 fprintf(stdout, "'-u' : allows for the haiku depot server url to be\n"); 156 fprintf(stdout, " configured.\n"); 157 fprintf(stdout, "'--nonetworking' : prevents network access.\n"); 158 fprintf(stdout, "'--prefercache' : prefer to get data from cache rather\n"); 159 fprintf(stdout, " then obtain data from the network.**\n"); 160 fprintf(stdout, "'--dropcache' : drop cached data before performing\n"); 161 fprintf(stdout, " bulk operations.**\n"); 162 fprintf(stdout, "\n"); 163 fprintf(stdout, "** = only applies to bulk operations.\n"); 164 } 165 166 167 static arg_switch 168 app_resolve_switch(char *arg) 169 { 170 int arglen = strlen(arg); 171 172 if (arglen > 0 && arg[0] == '-') { 173 174 if (arglen > 3 && arg[1] == '-') { // long form 175 if (0 == strcmp(&arg[2], "webappbaseurl")) 176 return WEB_APP_BASE_URL_SWITCH; 177 178 if (0 == strcmp(&arg[2], "help")) 179 return HELP_SWITCH; 180 181 if (0 == strcmp(&arg[2], "verbosity")) 182 return VERBOSITY_SWITCH; 183 184 if (0 == strcmp(&arg[2], "nonetworking")) 185 return FORCE_NO_NETWORKING_SWITCH; 186 187 if (0 == strcmp(&arg[2], "prefercache")) 188 return PREFER_CACHE_SWITCH; 189 190 if (0 == strcmp(&arg[2], "dropcache")) 191 return DROP_CACHE_SWITCH; 192 } else { 193 if (arglen == 2) { // short form 194 switch (arg[1]) { 195 case 'u': 196 return WEB_APP_BASE_URL_SWITCH; 197 198 case 'h': 199 return HELP_SWITCH; 200 201 case 'v': 202 return VERBOSITY_SWITCH; 203 } 204 } 205 } 206 207 return UNKNOWN_SWITCH; 208 } 209 210 return NOT_SWITCH; 211 } 212 213 214 void 215 App::ArgvReceived(int32 argc, char* argv[]) 216 { 217 for (int i = 1; i < argc;) { 218 219 // check to make sure that if there is a value for the switch, 220 // that the value is in fact supplied. 221 222 switch (app_resolve_switch(argv[i])) { 223 case VERBOSITY_SWITCH: 224 case WEB_APP_BASE_URL_SWITCH: 225 if (i == argc-1) { 226 fprintf(stdout, "unexpected end of arguments; missing " 227 "value for switch [%s]\n", argv[i]); 228 Quit(); 229 return; 230 } 231 break; 232 233 default: 234 break; 235 } 236 237 // now process each switch. 238 239 switch (app_resolve_switch(argv[i])) { 240 241 case VERBOSITY_SWITCH: 242 if (!Logger::SetLevelByName(argv[i+1])) { 243 fprintf(stdout, "unknown log level [%s]\n", argv[i + 1]); 244 Quit(); 245 } 246 i++; // also move past the log level value 247 break; 248 249 case HELP_SWITCH: 250 app_print_help(); 251 Quit(); 252 break; 253 254 case WEB_APP_BASE_URL_SWITCH: 255 if (ServerSettings::SetBaseUrl(BUrl(argv[i + 1])) != B_OK) { 256 fprintf(stdout, "malformed web app base url; %s\n", 257 argv[i + 1]); 258 Quit(); 259 } 260 else { 261 fprintf(stdout, "did configure the web base url; %s\n", 262 argv[i + 1]); 263 } 264 265 i++; // also move past the url value 266 267 break; 268 269 case FORCE_NO_NETWORKING_SWITCH: 270 ServerSettings::SetForceNoNetwork(true); 271 break; 272 273 case PREFER_CACHE_SWITCH: 274 ServerSettings::SetPreferCache(true); 275 break; 276 277 case DROP_CACHE_SWITCH: 278 ServerSettings::SetDropCache(true); 279 break; 280 281 case NOT_SWITCH: 282 { 283 BEntry entry(argv[i], true); 284 _Open(entry); 285 break; 286 } 287 288 case UNKNOWN_SWITCH: 289 fprintf(stdout, "unknown switch; %s\n", argv[i]); 290 Quit(); 291 break; 292 } 293 294 i++; // move on at least one arg 295 } 296 } 297 298 299 // #pragma mark - private 300 301 302 void 303 App::_Open(const BEntry& entry) 304 { 305 BPath path; 306 if (!entry.Exists() || entry.GetPath(&path) != B_OK) { 307 fprintf(stderr, "Package file not found: %s\n", path.Path()); 308 return; 309 } 310 311 // Try to parse package file via Package Kit 312 BPackageKit::BPackageInfo info; 313 status_t status = info.ReadFromPackageFile(path.Path()); 314 if (status != B_OK) { 315 fprintf(stderr, "Failed to parse package file: %s\n", 316 strerror(status)); 317 return; 318 } 319 320 // Transfer information into PackageInfo 321 PackageInfoRef package(new(std::nothrow) PackageInfo(info), true); 322 if (package.Get() == NULL) { 323 fprintf(stderr, "Could not allocate PackageInfo\n"); 324 return; 325 } 326 327 package->SetLocalFilePath(path.Path()); 328 329 BMessage settings; 330 _LoadSettings(settings); 331 332 MainWindow* window = new MainWindow(settings, package); 333 _ShowWindow(window); 334 } 335 336 337 void 338 App::_ShowWindow(MainWindow* window) 339 { 340 window->Show(); 341 fWindowCount++; 342 } 343 344 345 bool 346 App::_LoadSettings(BMessage& settings) 347 { 348 if (!fSettingsRead) { 349 fSettings = true; 350 if (load_settings(&fSettings, "main_settings", "HaikuDepot") != B_OK) 351 fSettings.MakeEmpty(); 352 } 353 settings = fSettings; 354 return !fSettings.IsEmpty(); 355 } 356 357 358 void 359 App::_StoreSettings(const BMessage& settings) 360 { 361 // Take what is in settings and replace data under the same name in 362 // fSettings, leaving anything in fSettings that is not contained in 363 // settings. 364 int32 i = 0; 365 366 char* name; 367 type_code type; 368 int32 count; 369 370 while (settings.GetInfo(B_ANY_TYPE, i++, &name, &type, &count) == B_OK) { 371 fSettings.RemoveName(name); 372 for (int32 j = 0; j < count; j++) { 373 const void* data; 374 ssize_t size; 375 if (settings.FindData(name, type, j, &data, &size) != B_OK) 376 break; 377 fSettings.AddData(name, type, data, size); 378 } 379 } 380 381 save_settings(&fSettings, "main_settings", "HaikuDepot"); 382 } 383 384 385 // #pragma mark - 386 387 388 static const char* kPackageDaemonSignature 389 = "application/x-vnd.haiku-package_daemon"; 390 391 void 392 App::_CheckPackageDaemonRuns() 393 { 394 while (!be_roster->IsRunning(kPackageDaemonSignature)) { 395 BAlert* alert = new BAlert("start_package_daemon", 396 B_TRANSLATE("HaikuDepot needs the package daemon to function, " 397 "and it appears to be not running.\n" 398 "Would you like to start it now?"), 399 B_TRANSLATE("No, quit HaikuDepot"), 400 B_TRANSLATE("Start package daemon"), NULL, B_WIDTH_AS_USUAL, 401 B_WARNING_ALERT); 402 alert->SetShortcut(0, B_ESCAPE); 403 404 if (alert->Go() == 0) 405 exit(1); 406 407 if (!_LaunchPackageDaemon()) 408 break; 409 } 410 } 411 412 413 bool 414 App::_LaunchPackageDaemon() 415 { 416 status_t ret = be_roster->Launch(kPackageDaemonSignature); 417 if (ret != B_OK) { 418 BString errorMessage 419 = B_TRANSLATE("Starting the package daemon failed:\n\n%Error%"); 420 errorMessage.ReplaceAll("%Error%", strerror(ret)); 421 422 BAlert* alert = new BAlert("package_daemon_problem", 423 errorMessage, 424 B_TRANSLATE("Quit HaikuDepot"), 425 B_TRANSLATE("Try again"), NULL, B_WIDTH_AS_USUAL, 426 B_WARNING_ALERT); 427 alert->SetShortcut(0, B_ESCAPE); 428 429 if (alert->Go() == 0) 430 return false; 431 } 432 // TODO: Would be nice to send a message to the package daemon instead 433 // and get a reply once it is ready. 434 snooze(2000000); 435 return true; 436 } 437 438