diff --git a/src/apps/haikudepot/HaikuDepotConstants.h b/src/apps/haikudepot/HaikuDepotConstants.h index 74cd6d61ac..190f8cb825 100644 --- a/src/apps/haikudepot/HaikuDepotConstants.h +++ b/src/apps/haikudepot/HaikuDepotConstants.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019, Andrew Lindesay . + * Copyright 2018-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef HAIKU_DEPOT_CONSTANTS_H @@ -81,4 +81,6 @@ enum UserUsageConditionsSelectionMode { #define ALERT_MSG_LOGS_USER_GUIDE "\nInformation about how to view the logs is " \ "available in the HaikuDepot section of the user guide." +#define CACHE_DIRECTORY_APP "HaikuDepot" + #endif // HAIKU_DEPOT_CONSTANTS_H \ No newline at end of file diff --git a/src/apps/haikudepot/model/Model.cpp b/src/apps/haikudepot/model/Model.cpp index 1e25fef781..ff8266aeb8 100644 --- a/src/apps/haikudepot/model/Model.cpp +++ b/src/apps/haikudepot/model/Model.cpp @@ -1,7 +1,7 @@ /* * Copyright 2013-2014, Stephan Aßmus . * Copyright 2014, Axel Dörfler . - * Copyright 2016-2019, Andrew Lindesay . + * Copyright 2016-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -847,36 +847,6 @@ Model::SetAuthorization(const BString& nickname, const BString& passwordClear, } -status_t -Model::_LocalDataPath(const BString leaf, BPath& path) const -{ - BPath resultPath; - status_t result = B_OK; - - if (result == B_OK) - result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); - - if (result == B_OK) - result = resultPath.Append("HaikuDepot"); - - if (result == B_OK) - result = create_directory(resultPath.Path(), 0777); - - if (result == B_OK) - result = resultPath.Append(leaf); - - if (result == B_OK) - path.SetTo(resultPath.Path()); - else { - path.Unset(); - fprintf(stdout, "unable to find the user cache file for " - "[%s] data; %s\n", leaf.String(), strerror(result)); - } - - return result; -} - - /*! When bulk repository data comes down from the server, it will arrive as a json.gz payload. This is stored locally as a cache and this method will provide the on-disk storage location for @@ -889,7 +859,7 @@ Model::DumpExportRepositoryDataPath(BPath& path) const BString leaf; leaf.SetToFormat("repository-all_%s.json.gz", LanguageModel().PreferredLanguage().Code()); - return _LocalDataPath(leaf, path); + return StorageUtils::LocalWorkingFilesPath(leaf, path); } @@ -904,37 +874,14 @@ Model::DumpExportReferenceDataPath(BPath& path) const BString leaf; leaf.SetToFormat("reference-all_%s.json.gz", LanguageModel().PreferredLanguage().Code()); - return _LocalDataPath(leaf, path); + return StorageUtils::LocalWorkingFilesPath(leaf, path); } status_t Model::IconStoragePath(BPath& path) const { - BPath iconStoragePath; - status_t result = B_OK; - - if (result == B_OK) - result = find_directory(B_USER_CACHE_DIRECTORY, &iconStoragePath); - - if (result == B_OK) - result = iconStoragePath.Append("HaikuDepot"); - - if (result == B_OK) - result = iconStoragePath.Append("__allicons"); - - if (result == B_OK) - result = create_directory(iconStoragePath.Path(), 0777); - - if (result == B_OK) - path.SetTo(iconStoragePath.Path()); - else { - path.Unset(); - fprintf(stdout, "unable to find the user cache directory for " - "icons; %s\n", strerror(result)); - } - - return result; + return StorageUtils::LocalWorkingDirectoryPath("__allicons", path); } @@ -945,7 +892,7 @@ Model::DumpExportPkgDataPath(BPath& path, BString leaf; leaf.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(), LanguageModel().PreferredLanguage().Code()); - return _LocalDataPath(leaf, path); + return StorageUtils::LocalWorkingFilesPath(leaf, path); } @@ -966,15 +913,21 @@ Model::_PopulatePackageScreenshot(const PackageInfoRef& package, // See if there is a cached screenshot BFile screenshotFile; BPath screenshotCachePath; + + status_t result = StorageUtils::LocalWorkingDirectoryPath( + "Screenshots", screenshotCachePath); + + if (result != B_OK) { + printf("[!] unable to get the screenshot dir - unable to proceed"); + return; + } + bool fileExists = false; BString screenshotName(info.Code()); screenshotName << "@" << scaledWidth; screenshotName << ".png"; time_t modifiedTime; - if (find_directory(B_USER_CACHE_DIRECTORY, &screenshotCachePath) == B_OK - && screenshotCachePath.Append("HaikuDepot/Screenshots") == B_OK - && create_directory(screenshotCachePath.Path(), 0777) == B_OK - && screenshotCachePath.Append(screenshotName) == B_OK) { + if (screenshotCachePath.Append(screenshotName) == B_OK) { // Try opening the file in read-only mode, which will fail if its // not a file or does not exist. fileExists = screenshotFile.SetTo(screenshotCachePath.Path(), diff --git a/src/apps/haikudepot/model/Model.h b/src/apps/haikudepot/model/Model.h index 6925b6aef8..2bcb4755b1 100644 --- a/src/apps/haikudepot/model/Model.h +++ b/src/apps/haikudepot/model/Model.h @@ -1,12 +1,11 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2016-2019, Andrew Lindesay . + * Copyright 2016-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef MODEL_H #define MODEL_H -#include #include #include "AbstractProcess.h" @@ -155,8 +154,6 @@ public: private: void _AddCategory(const CategoryRef& category); - status_t _LocalDataPath(const BString leaf, - BPath& path) const; void _MaybeLogJsonRpcError( const BMessage &responsePayload, @@ -174,17 +171,6 @@ private: const ScreenshotInfo& info, int32 scaledWidth, bool fromCacheOnly); - bool _GetCacheFile(BPath& path, BFile& file, - directory_which directory, - const char* relativeLocation, - const char* fileName, - uint32 openMode) const; - bool _GetCacheFile(BPath& path, BFile& file, - directory_which directory, - const char* relativeLocation, - const char* fileName, - bool ignoreAge, time_t maxAge) const; - void _NotifyAuthorizationChanged(); void _NotifyCategoryListChanged(); diff --git a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp index c715802978..69ea8ee457 100644 --- a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp +++ b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019, Andrew Lindesay . + * Copyright 2018-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -25,6 +25,7 @@ #include "ServerReferenceDataUpdateProcess.h" #include "ServerRepositoryDataUpdateProcess.h" #include "ServerSettings.h" +#include "StorageUtils.h" using namespace BPackageKit; @@ -36,6 +37,7 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator( ProcessCoordinatorListener* processCoordinatorListener, Model* model, bool forceLocalUpdate) { + bool areWorkingFilesAvailable = StorageUtils::AreWorkingFilesAvailable(); uint32 serverProcessOptions = _CalculateServerProcessOptions(); BAutolock locker(model->Lock()); ProcessCoordinator* processCoordinator = new ProcessCoordinator( @@ -52,46 +54,49 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator( localPkgDataLoad->AddPredecessor(localRepositoryUpdate); processCoordinator->AddNode(localPkgDataLoad); - ProcessNode *serverIconExportUpdate = - new ProcessNode(new ServerIconExportUpdateProcess(model, - serverProcessOptions)); - serverIconExportUpdate->AddPredecessor(localPkgDataLoad); - processCoordinator->AddNode(serverIconExportUpdate); + if (areWorkingFilesAvailable) { + ProcessNode *serverIconExportUpdate = + new ProcessNode(new ServerIconExportUpdateProcess(model, + serverProcessOptions)); + serverIconExportUpdate->AddPredecessor(localPkgDataLoad); + processCoordinator->AddNode(serverIconExportUpdate); - ProcessNode *serverRepositoryDataUpdate = - new ProcessNode(new ServerRepositoryDataUpdateProcess(model, - serverProcessOptions)); - serverRepositoryDataUpdate->AddPredecessor(localPkgDataLoad); - processCoordinator->AddNode(serverRepositoryDataUpdate); + ProcessNode *serverRepositoryDataUpdate = + new ProcessNode(new ServerRepositoryDataUpdateProcess(model, + serverProcessOptions)); + serverRepositoryDataUpdate->AddPredecessor(localPkgDataLoad); + processCoordinator->AddNode(serverRepositoryDataUpdate); - ProcessNode *serverReferenceDataUpdate = - new ProcessNode(new ServerReferenceDataUpdateProcess(model, - serverProcessOptions)); - processCoordinator->AddNode(serverReferenceDataUpdate); + ProcessNode *serverReferenceDataUpdate = + new ProcessNode(new ServerReferenceDataUpdateProcess(model, + serverProcessOptions)); + processCoordinator->AddNode(serverReferenceDataUpdate); - // create a process for each of the repositories that are configured on the - // local system. Later, only those that have a web-app repository server - // code will be actually processed, but this means that the creation of the - // 'processes' does not need to be dynamic as the process coordinator runs. + // create a process for each of the repositories that are configured on + // the local system. Later, only those that have a web-app repository + // server code will be actually processed, but this means that the + // creation of the 'processes' does not need to be dynamic as the + // process coordinator runs. - BPackageRoster roster; - BStringList repoNames; - status_t repoNamesResult = roster.GetRepositoryNames(repoNames); + BPackageRoster roster; + BStringList repoNames; + status_t repoNamesResult = roster.GetRepositoryNames(repoNames); - if (repoNamesResult == B_OK) { - AutoLocker locker(model->Lock()); + if (repoNamesResult == B_OK) { + AutoLocker locker(model->Lock()); - for (int32 i = 0; i < repoNames.CountStrings(); i++) { - ProcessNode* processNode = new ProcessNode( - new ServerPkgDataUpdateProcess( - model->Language().PreferredLanguage().Code(), - repoNames.StringAt(i), model, serverProcessOptions)); - processNode->AddPredecessor(serverRepositoryDataUpdate); - processNode->AddPredecessor(serverReferenceDataUpdate); - processCoordinator->AddNode(processNode); + for (int32 i = 0; i < repoNames.CountStrings(); i++) { + ProcessNode* processNode = new ProcessNode( + new ServerPkgDataUpdateProcess( + model->Language().PreferredLanguage().Code(), + repoNames.StringAt(i), model, serverProcessOptions)); + processNode->AddPredecessor(serverRepositoryDataUpdate); + processNode->AddPredecessor(serverReferenceDataUpdate); + processCoordinator->AddNode(processNode); + } + } else { + printf("a problem has arisen getting the repository names.\n"); } - } else { - printf("a problem has arisen getting the repository names.\n"); } return processCoordinator; diff --git a/src/apps/haikudepot/ui/App.cpp b/src/apps/haikudepot/ui/App.cpp index fbc339bc89..e48573cd45 100644 --- a/src/apps/haikudepot/ui/App.cpp +++ b/src/apps/haikudepot/ui/App.cpp @@ -1,6 +1,6 @@ /* * Copyright 2013, Stephan Aßmus . - * Copyright 2017-2018, Andrew Lindesay . + * Copyright 2017-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -29,6 +29,7 @@ #include "ServerHelper.h" #include "ServerSettings.h" #include "ScreenshotWindow.h" +#include "StorageUtils.h" #undef B_TRANSLATION_CONTEXT @@ -42,6 +43,7 @@ App::App() fWindowCount(0), fSettingsRead(false) { + srand((unsigned int) time(NULL)); _CheckPackageDaemonRuns(); } @@ -83,6 +85,12 @@ App::ReadyToRun() BMessage settings; _LoadSettings(settings); + if (!_CheckTestFile()) + { + Quit(); + return; + } + fMainWindow = new MainWindow(settings); _ShowWindow(fMainWindow); } @@ -506,3 +514,46 @@ App::_LaunchPackageDaemon() return true; } + +/*! \brief Checks to ensure that a working file is able to be written. + \return false if the startup should be stopped and the application should + quit. +*/ + +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; +} diff --git a/src/apps/haikudepot/ui/App.h b/src/apps/haikudepot/ui/App.h index 2fb02f8c08..1eb3f00864 100644 --- a/src/apps/haikudepot/ui/App.h +++ b/src/apps/haikudepot/ui/App.h @@ -1,6 +1,6 @@ /* * Copyright 2013, Stephan Aßmus . - * Copyright 2018, Andrew Lindesay + * Copyright 2018-2020, Andrew Lindesay * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef APP_H @@ -36,6 +36,8 @@ private: void _CheckPackageDaemonRuns(); bool _LaunchPackageDaemon(); + bool _CheckTestFile(); + private: MainWindow* fMainWindow; int32 fWindowCount; diff --git a/src/apps/haikudepot/util/StorageUtils.cpp b/src/apps/haikudepot/util/StorageUtils.cpp index 020004577f..9f742ea674 100644 --- a/src/apps/haikudepot/util/StorageUtils.cpp +++ b/src/apps/haikudepot/util/StorageUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017, Andrew Lindesay . + * Copyright 2017-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -10,14 +10,33 @@ #include #include +#include #include #include +#include "HaikuDepotConstants.h" #include "Logger.h" #define FILE_TO_STRING_BUFFER_LEN 64 +static bool sAreWorkingFilesAvailable = true; + + +/*static*/ bool +StorageUtils::AreWorkingFilesAvailable() +{ + return sAreWorkingFilesAvailable; +} + + +/*static*/ void +StorageUtils::SetWorkingFilesUnavailable() +{ + sAreWorkingFilesAvailable = false; +} + + /* This method will append the contents of the file at the supplied path to the * string provided. */ @@ -90,7 +109,7 @@ StorageUtils::RemoveDirectoryContents(BPath& path) */ status_t -StorageUtils::ExistsObject(BPath& path, +StorageUtils::ExistsObject(const BPath& path, bool* exists, bool* isDirectory, off_t* size) @@ -122,3 +141,148 @@ StorageUtils::ExistsObject(BPath& path, return B_OK; } + + +/*! This method will check that it is possible to write to the specified file. + This may create the file, write some data to it and then read that data + back again to be sure. This can be used as an effective safety measure as + the application starts up in order to ensure that the storage systems are + in place for the application to startup. + + It is assumed here that the directory containing the test file exists. +*/ + +/*static*/ status_t +StorageUtils::CheckCanWriteTo(const BPath& path) +{ + status_t result = B_OK; + bool exists = false; + uint8 buffer[16]; + + // create some random latin letters into the buffer to write. + for (int i = 0; i < 16; i++) + buffer[i] = 65 + (abs(rand()) % 26); + + if (result == B_OK) + result = ExistsObject(path, &exists, NULL, NULL); + + if (result == B_OK && exists) { + if (Logger::IsTraceEnabled()) { + printf("an object exists at the candidate path " + "[%s] - it will be deleted\n", path.Path()); + } + + if (remove(path.Path()) == 0) { + if (Logger::IsTraceEnabled()) { + printf("did delete the candidate file [%s]\n", path.Path()); + } + } else { + printf("unable to delete the candidate file [%s]\n", path.Path()); + result = B_ERROR; + } + } + + if (result == B_OK) { + BFile file(path.Path(), O_WRONLY | O_CREAT); + if (file.Write(buffer, 16) != 16) { + printf("unable to write test data to candidate file [%s]\n", + path.Path()); + result = B_ERROR; + } + } + + if (result == B_OK) { + BFile file(path.Path(), O_RDONLY); + uint8 readBuffer[16]; + if (file.Read(readBuffer, 16) != 16) { + printf("unable to read test data from candidate file [%s]\n", + path.Path()); + result = B_ERROR; + } + + for (int i = 0; result == B_OK && i < 16; i++) { + if (readBuffer[i] != buffer[i]) { + printf("mismatched read..write check on candidate file [%s]\n", + path.Path()); + result = B_ERROR; + } + } + } + + return result; +} + + +/*! As the application runs it will need to store some files into the local + disk system. This method, given a leafname, will write into the supplied + path variable, a final path where this leafname should be stored. +*/ + +/*static*/ status_t +StorageUtils::LocalWorkingFilesPath(const BString leaf, BPath& path, + bool failOnCreateDirectory) +{ + BPath resultPath; + status_t result = B_OK; + + if (result == B_OK) + result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); + + if (result == B_OK) + result = resultPath.Append(CACHE_DIRECTORY_APP); + + if (result == B_OK) { + if (failOnCreateDirectory) + result = create_directory(resultPath.Path(), 0777); + else + create_directory(resultPath.Path(), 0777); + } + + if (result == B_OK) + result = resultPath.Append(leaf); + + if (result == B_OK) + path.SetTo(resultPath.Path()); + else { + path.Unset(); + fprintf(stdout, "unable to find the user cache file for " + "[%s] data; %s\n", leaf.String(), strerror(result)); + } + + return result; +} + + +/*static*/ status_t +StorageUtils::LocalWorkingDirectoryPath(const BString leaf, BPath& path, + bool failOnCreateDirectory) +{ + BPath resultPath; + status_t result = B_OK; + + if (result == B_OK) + result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); + + if (result == B_OK) + result = resultPath.Append(CACHE_DIRECTORY_APP); + + if (result == B_OK) + result = resultPath.Append(leaf); + + if (result == B_OK) { + if (failOnCreateDirectory) + result = create_directory(resultPath.Path(), 0777); + else + create_directory(resultPath.Path(), 0777); + } + + if (result == B_OK) + path.SetTo(resultPath.Path()); + else { + path.Unset(); + fprintf(stdout, "unable to find the user cache directory for " + "[%s] data; %s\n", leaf.String(), strerror(result)); + } + + return result; +} diff --git a/src/apps/haikudepot/util/StorageUtils.h b/src/apps/haikudepot/util/StorageUtils.h index 45c025f494..cd39ebd1d5 100644 --- a/src/apps/haikudepot/util/StorageUtils.h +++ b/src/apps/haikudepot/util/StorageUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2017, Andrew Lindesay . + * Copyright 2017-2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef PATH_UTILS_H @@ -10,9 +10,21 @@ class StorageUtils { public: + static bool AreWorkingFilesAvailable(); + static void SetWorkingFilesUnavailable(); + + static status_t LocalWorkingFilesPath(const BString leaf, + BPath& path, + bool failOnCreateDirectory = true); + static status_t LocalWorkingDirectoryPath(const BString leaf, + BPath& path, + bool failOnCreateDirectory = true); + + static status_t CheckCanWriteTo(const BPath& path); + static status_t RemoveDirectoryContents(BPath& path); static status_t AppendToString(BPath& path, BString& result); - static status_t ExistsObject(BPath& directory, + static status_t ExistsObject(const BPath& path, bool* exists, bool* isDirectory, off_t* size);