#include "App.h"
#include <Alert.h>
#include <Catalog.h>
#include <Entry.h>
#include <Message.h>
#include <Path.h>
#include <Roster.h>
#include <Screen.h>
#include <String.h>
#include <package/PackageDefs.h>
#include <package/PackageInfo.h>
#include <package/PackageRoster.h>
#include "support.h"
#include "AppUtils.h"
#include "Logger.h"
#include "MainWindow.h"
#include "PackageKitUtils.h"
#include "PackageUtils.h"
#include "ServerHelper.h"
#include "ServerSettings.h"
#include "SharedIcons.h"
#include "StorageUtils.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "App"
static const char* const kKeyMainSettings = "main_settings";
App::App()
:
BApplication("application/x-vnd.Haiku-HaikuDepot"),
fMainWindow(NULL),
fWindowCount(0),
fSettingsRead(false)
{
srand(static_cast<unsigned int>(time(NULL)));
_CheckPackageDaemonRuns();
fIsFirstRun = _CheckIsFirstRun();
}
App::~App()
{
SharedIcons::UnsetAllIcons();
}
bool
App::QuitRequested()
{
if (fMainWindow != NULL && fMainWindow->LockLooperWithTimeout(1500000) == B_OK) {
BMessage windowSettings;
fMainWindow->StoreSettings(windowSettings);
fMainWindow->UnlockLooper();
_StoreSettings(windowSettings);
}
return BApplication::QuitRequested();
}
void
App::ReadyToRun()
{
if (fWindowCount > 0)
return;
BMessage settings;
_LoadSettings(settings);
if (!_CheckTestFile()) {
Quit();
return;
}
_ClearCacheOnVersionChange();
fMainWindow = new MainWindow(settings);
_ShowWindow(fMainWindow);
}
bool
App::IsFirstRun()
{
return fIsFirstRun;
}
void
App::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_MAIN_WINDOW_CLOSED:
{
BMessage windowSettings;
if (message->FindMessage(main_window_keys::kKeyWindowSettings, &windowSettings) == B_OK)
_StoreSettings(windowSettings);
fWindowCount--;
if (fWindowCount == 0)
Quit();
break;
}
case MSG_CLIENT_TOO_OLD:
ServerHelper::AlertClientTooOld(message);
break;
case MSG_NETWORK_TRANSPORT_ERROR:
ServerHelper::AlertTransportError(message);
break;
case MSG_SERVER_ERROR:
ServerHelper::AlertServerJsonRpcError(message);
break;
case MSG_ALERT_SIMPLE_ERROR:
_AlertSimpleError(message);
break;
case MSG_PKG_POPULATE_USER_RATINGS:
fMainWindow->PostMessage(message);
break;
default:
BApplication::MessageReceived(message);
break;
}
}
void
App::RefsReceived(BMessage* message)
{
entry_ref ref;
int32 index = 0;
while (message->FindRef("refs", index++, &ref) == B_OK) {
BEntry entry(&ref, true);
_Open(entry);
}
}
enum arg_switch {
UNKNOWN_SWITCH,
NOT_SWITCH,
HELP_SWITCH,
WEB_APP_BASE_URL_SWITCH,
VERBOSITY_SWITCH,
FORCE_NO_NETWORKING_SWITCH,
PREFER_CACHE_SWITCH,
DROP_CACHE_SWITCH
};
static void
app_print_help()
{
fprintf(stdout, "HaikuDepot ");
fprintf(stdout, "[-u|--webappbaseurl <web-app-base-url>]\n");
fprintf(stdout, "[-v|--verbosity [off|info|debug|trace]\n");
fprintf(stdout, "[--nonetworking]\n");
fprintf(stdout, "[--prefercache]\n");
fprintf(stdout, "[--dropcache]\n");
fprintf(stdout, "[-h|--help]\n");
fprintf(stdout, "\n");
fprintf(stdout, "'-h' : causes this help text to be printed out.\n");
fprintf(stdout, "'-v' : allows for the verbosity level to be set.\n");
fprintf(stdout, "'-u' : allows for the haiku depot server url to be\n");
fprintf(stdout, " configured.\n");
fprintf(stdout, "'--nonetworking' : prevents network access.\n");
fprintf(stdout, "'--prefercache' : prefer to get data from cache rather\n");
fprintf(stdout, " then obtain data from the network.**\n");
fprintf(stdout, "'--dropcache' : drop cached data before performing\n");
fprintf(stdout, " bulk operations.**\n");
fprintf(stdout, "\n");
fprintf(stdout, "** = only applies to bulk operations.\n");
}
static arg_switch
app_resolve_switch(char* arg)
{
int arglen = strlen(arg);
if (arglen > 0 && arg[0] == '-') {
if (arglen > 3 && arg[1] == '-') {
if (0 == strcmp(&arg[2], "webappbaseurl"))
return WEB_APP_BASE_URL_SWITCH;
if (0 == strcmp(&arg[2], "help"))
return HELP_SWITCH;
if (0 == strcmp(&arg[2], "verbosity"))
return VERBOSITY_SWITCH;
if (0 == strcmp(&arg[2], "nonetworking"))
return FORCE_NO_NETWORKING_SWITCH;
if (0 == strcmp(&arg[2], "prefercache"))
return PREFER_CACHE_SWITCH;
if (0 == strcmp(&arg[2], "dropcache"))
return DROP_CACHE_SWITCH;
} else if (arglen == 2) {
switch (arg[1]) {
case 'h':
return HELP_SWITCH;
case 'u':
return WEB_APP_BASE_URL_SWITCH;
case 'v':
return VERBOSITY_SWITCH;
}
}
return UNKNOWN_SWITCH;
}
return NOT_SWITCH;
}
void
App::ArgvReceived(int32 argc, char* argv[])
{
for (int i = 1; i < argc;) {
switch (app_resolve_switch(argv[i])) {
case VERBOSITY_SWITCH:
case WEB_APP_BASE_URL_SWITCH:
if (i == argc - 1) {
HDERROR("unexpected end of arguments; missing value for switch [%s]", argv[i]);
Quit();
return;
}
break;
default:
break;
}
switch (app_resolve_switch(argv[i])) {
case VERBOSITY_SWITCH:
if (!Logger::SetLevelByName(argv[i + 1])) {
fprintf(stdout, "unknown log level [%s]\n", argv[i + 1]);
Quit();
}
i++;
break;
case HELP_SWITCH:
app_print_help();
Quit();
break;
case WEB_APP_BASE_URL_SWITCH:
if (ServerSettings::SetBaseUrl(BUrl(argv[i + 1], true)) != B_OK) {
HDERROR("malformed web app base url; %s", argv[i + 1]);
Quit();
} else {
HDERROR("did configure the web base url; %s", argv[i + 1]);
}
i++;
break;
case FORCE_NO_NETWORKING_SWITCH:
ServerSettings::SetForceNoNetwork(true);
break;
case PREFER_CACHE_SWITCH:
ServerSettings::SetPreferCache(true);
break;
case DROP_CACHE_SWITCH:
ServerSettings::SetDropCache(true);
break;
case NOT_SWITCH:
{
BEntry entry(argv[i], true);
_Open(entry);
break;
}
case UNKNOWN_SWITCH:
fprintf(stdout, "unknown switch; %s\n", argv[i]);
Quit();
break;
}
i++;
}
}
void
App::_AlertSimpleError(BMessage* message)
{
SimpleAlert simpleAlert(message);
BAlert* alert = new BAlert(simpleAlert.Title(), simpleAlert.Text(), B_TRANSLATE("OK"), NULL,
NULL, B_WIDTH_AS_USUAL, simpleAlert.Type());
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
void
App::_Open(const BEntry& entry)
{
BPath path;
if (!entry.Exists() || entry.GetPath(&path) != B_OK) {
HDERROR("package file not found: %s", path.Path());
return;
}
BPackageKit::BPackageInfo info;
status_t status = info.ReadFromPackageFile(path.Path());
if (status != B_OK) {
HDERROR("failed to parse package file: %s", strerror(status));
return;
}
PackageInfoRef filePackageInfo = PackageKitUtils::CreatePackageInfo(info);
PackageInfoBuilder packageBuilder(filePackageInfo);
PackageLocalInfoBuilder localInfoBuilder(filePackageInfo->LocalInfo());
localInfoBuilder.WithLocalFilePath(path.Path());
bool active = false;
BPackageKit::BPackageRoster roster;
status = roster.IsPackageActive(BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, info,
&active);
if (status != B_OK) {
HDERROR("could not check if package was active in system: %s", strerror(status));
return;
}
if (!active) {
status = roster.IsPackageActive(BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_HOME, info,
&active);
if (status != B_OK) {
HDERROR("could not check if package was active in home: %s", strerror(status));
return;
}
}
if (active)
localInfoBuilder.WithState(ACTIVATED);
packageBuilder.WithLocalInfo(localInfoBuilder.BuildRef());
BMessage settings;
_LoadSettings(settings);
MainWindow* window = new MainWindow(settings, packageBuilder.BuildRef());
_ShowWindow(window);
}
void
App::_ShowWindow(MainWindow* window)
{
window->Show();
fWindowCount++;
}
bool
App::_LoadSettings(BMessage& settings)
{
if (!fSettingsRead) {
fSettingsRead = true;
if (load_settings(&fSettings, kKeyMainSettings, "HaikuDepot") != B_OK)
fSettings.MakeEmpty();
}
settings = fSettings;
return !fSettings.IsEmpty();
}
void
App::_StoreSettings(const BMessage& settings)
{
int32 i = 0;
char* name;
type_code type;
int32 count;
while (settings.GetInfo(B_ANY_TYPE, i++, &name, &type, &count) == B_OK) {
fSettings.RemoveName(name);
for (int32 j = 0; j < count; j++) {
const void* data;
ssize_t size;
if (settings.FindData(name, type, j, &data, &size) != B_OK)
break;
fSettings.AddData(name, type, data, size);
}
}
save_settings(&fSettings, kKeyMainSettings, "HaikuDepot");
}
static const char* kPackageDaemonSignature = "application/x-vnd.haiku-package_daemon";
void
App::_CheckPackageDaemonRuns()
{
while (!be_roster->IsRunning(kPackageDaemonSignature)) {
BString text(B_TRANSLATE("%appname% needs the package daemon to function, "
"and it appears to be not running.\n"
"Would you like to start it now?"));
text.ReplaceFirst("%appname%", B_TRANSLATE_SYSTEM_NAME("HaikuDepot"));
BAlert* alert = new BAlert(B_TRANSLATE("Start package daemon"), text,
B_TRANSLATE("No, quit"), B_TRANSLATE("Start package daemon"), NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
if (alert->Go() == 0)
HDFATAL("unable to start without the package daemon running");
if (!_LaunchPackageDaemon())
break;
}
}
bool
App::_LaunchPackageDaemon()
{
status_t ret = be_roster->Launch(kPackageDaemonSignature);
if (ret != B_OK) {
BString errorMessage = B_TRANSLATE("Starting the package daemon failed:\n\n%Error%");
errorMessage.ReplaceAll("%Error%", strerror(ret));
BAlert* alert = new BAlert(B_TRANSLATE("Package daemon problem"), errorMessage,
B_TRANSLATE("Quit"), B_TRANSLATE("Try again"), NULL, B_WIDTH_AS_USUAL,
B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
if (alert->Go() == 0)
return false;
}
snooze(2000000);
return true;
}
bool
App::_CheckIsFirstRun()
{
BPath testFilePath;
bool exists = false;
status_t status = StorageUtils::LocalWorkingFilesPath("testfile.txt", testFilePath, false);
if (status != B_OK)
HDERROR("unable to establish the location of the test file");
else
status = StorageUtils::ExistsObject(testFilePath, &exists, NULL, NULL);
return !exists;
}
bool
App::_CheckTestFile()
{
BPath testFilePath;
BString pathDescription = "???";
status_t result = StorageUtils::LocalWorkingFilesPath("testfile.txt", testFilePath, false);
if (result == B_OK) {
pathDescription = testFilePath.Path();
result = StorageUtils::CheckCanWriteTo(testFilePath);
}
if (result != B_OK) {
StorageUtils::SetWorkingFilesUnavailable();
BString msg
= B_TRANSLATE("This application writes and reads some"
" working files on your computer in order to function. It appears"
" that there are problems writing a test file at [%TestFilePath%]."
" Check that there are no issues with your local disk or"
" permissions that might prevent this application from writing"
" files into that directory location. You may choose to acknowledge"
" this problem and continue, but some functionality may be"
" disabled.");
msg.ReplaceAll("%TestFilePath%", pathDescription);
BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Problem with working files"), msg,
B_TRANSLATE("Quit"), B_TRANSLATE("Continue"));
if (alert->Go() == 0)
return false;
}
return true;
}
void
App::_ClearCacheOnVersionChange()
{
BString version;
if (AppUtils::GetAppVersionString(version) != B_OK) {
HDERROR("clear cache; unable to get the application version");
return;
}
BPath lastVersionPath;
if (StorageUtils::LocalWorkingFilesPath("version.txt", lastVersionPath) != B_OK) {
HDERROR("clear cache; unable to get version file path");
return;
}
bool exists;
off_t size;
if (StorageUtils::ExistsObject(lastVersionPath, &exists, NULL, &size) != B_OK) {
HDERROR("clear cache; unable to check version file exists");
return;
}
BString lastVersion;
if (exists && StorageUtils::AppendToString(lastVersionPath, lastVersion) != B_OK) {
HDERROR("clear cache; unable to read the version from [%s]", lastVersionPath.Path());
return;
}
if (lastVersion != version) {
HDINFO("last version [%s] and current version [%s] do not match -> will flush cache",
lastVersion.String(), version.String());
StorageUtils::RemoveWorkingDirectoryContents();
HDINFO("will write version [%s] to [%s]", version.String(), lastVersionPath.Path());
StorageUtils::AppendToFile(version, lastVersionPath);
} else {
HDINFO("last version [%s] and current version [%s] match -> cache retained",
lastVersion.String(), version.String());
}
}