From b7d8e462883fab86c0e8421c69d9db8e57eb4ef1 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Wed, 21 Jun 2023 20:07:36 +0200 Subject: [PATCH] feat: Display detailed error message when loading of project fails (#1135) In order to do this I add to make some other additions : - Add a warning popup (TODO, maybe add some icons to differentiate error/warning popups in a future PR ?) - create showError() and showWarning() functions, as helpers to show a message both to the logs and as a popup --- .../include/hex/api/project_file_manager.hpp | 28 ++- lib/libimhex/include/hex/helpers/tar.hpp | 10 ++ .../source/api/project_file_manager.cpp | 136 ++------------ lib/libimhex/source/helpers/tar.cpp | 28 ++- plugins/builtin/CMakeLists.txt | 2 + .../include/content/helpers/notification.hpp | 10 ++ .../content/popups/popup_notification.hpp | 11 ++ plugins/builtin/romfs/lang/en_US.json | 9 +- .../source/content/helpers/notification.cpp | 16 ++ plugins/builtin/source/content/project.cpp | 166 ++++++++++++++++++ plugins/builtin/source/content/providers.cpp | 122 ++++++++----- .../content/providers/file_provider.cpp | 5 + plugins/builtin/source/plugin_builtin.cpp | 2 + 13 files changed, 363 insertions(+), 182 deletions(-) create mode 100644 plugins/builtin/include/content/helpers/notification.hpp create mode 100644 plugins/builtin/source/content/helpers/notification.cpp create mode 100644 plugins/builtin/source/content/project.cpp diff --git a/lib/libimhex/include/hex/api/project_file_manager.hpp b/lib/libimhex/include/hex/api/project_file_manager.hpp index f07bf9134..9723c3522 100644 --- a/lib/libimhex/include/hex/api/project_file_manager.hpp +++ b/lib/libimhex/include/hex/api/project_file_manager.hpp @@ -12,15 +12,15 @@ #include #include +/** + * @brief Project file manager + * + * The project file manager is used to load and store project files. It is used by all features of ImHex + * that want to store any data to a Project File. + * + */ namespace hex { - /** - * @brief Project file manager - * - * The project file manager is used to load and store project files. It is used by all features of ImHex - * that want to store any data to a Project File. - * - */ class ProjectFile { public: struct Handler { @@ -39,6 +39,17 @@ namespace hex { Function load, store; //< Functions to load and store data }; + /** + * @brief Set implementations for loading and restoring a project + * + * @param loadFun function to use to load a project in ImHex + * @param storeFun function to use to store a project to disk + */ + static void setProjectFunctions( + const std::function &loadFun, + const std::function)> &storeFun + ); + /** * @brief Load a project file * @@ -119,6 +130,9 @@ namespace hex { private: ProjectFile() = default; + static std::function s_loadProjectFunction; + static std::function)> s_storeProjectFunction; + static std::fs::path s_currProjectPath; static std::vector s_handlers; static std::vector s_providerHandlers; diff --git a/lib/libimhex/include/hex/helpers/tar.hpp b/lib/libimhex/include/hex/helpers/tar.hpp index 2613449fd..fee117da0 100644 --- a/lib/libimhex/include/hex/helpers/tar.hpp +++ b/lib/libimhex/include/hex/helpers/tar.hpp @@ -26,6 +26,12 @@ namespace hex { void close(); + /** + * @brief get the error string explaining the error that occured when opening the file. + * This error is a combination of the tar error and the native file open error + */ + std::string getOpenErrorString(); + [[nodiscard]] std::vector readVector(const std::fs::path &path); [[nodiscard]] std::string readString(const std::fs::path &path); @@ -45,6 +51,10 @@ namespace hex { std::fs::path m_path; bool m_valid = false; + + // these will be updated when the constructor is called + int m_tarOpenErrno = MTAR_ESUCCESS; + int m_fileOpenErrno = 0; }; } \ No newline at end of file diff --git a/lib/libimhex/source/api/project_file_manager.cpp b/lib/libimhex/source/api/project_file_manager.cpp index 43c979d5c..a9fb11d10 100644 --- a/lib/libimhex/source/api/project_file_manager.cpp +++ b/lib/libimhex/source/api/project_file_manager.cpp @@ -11,138 +11,28 @@ namespace hex { - constexpr static auto MetadataHeaderMagic = "HEX"; - constexpr static auto MetadataPath = "IMHEX_METADATA"; - std::vector ProjectFile::s_handlers; std::vector ProjectFile::s_providerHandlers; std::fs::path ProjectFile::s_currProjectPath; + std::function ProjectFile::s_loadProjectFunction; + std::function)> ProjectFile::s_storeProjectFunction; + + void ProjectFile::setProjectFunctions( + const std::function &loadFun, + const std::function)> &storeFun + ) { + ProjectFile::s_loadProjectFunction = loadFun; + ProjectFile::s_storeProjectFunction = storeFun; + } + bool ProjectFile::load(const std::fs::path &filePath) { - auto originalPath = ProjectFile::s_currProjectPath; - - ProjectFile::s_currProjectPath = filePath; - auto resetPath = SCOPE_GUARD { - ProjectFile::s_currProjectPath = originalPath; - }; - - if (!wolv::io::fs::exists(filePath) || !wolv::io::fs::isRegularFile(filePath)) - return false; - if (filePath.extension() != ".hexproj") - return false; - - Tar tar(filePath, Tar::Mode::Read); - if (!tar.isValid()) - return false; - - if (!tar.contains(MetadataPath)) - return false; - - { - const auto metadataContent = tar.readVector(MetadataPath); - - if (!std::string(metadataContent.begin(), metadataContent.end()).starts_with(MetadataHeaderMagic)) - return false; - } - - auto providers = auto(ImHexApi::Provider::getProviders()); - for (const auto &provider : providers) { - ImHexApi::Provider::remove(provider); - } - - bool result = true; - for (const auto &handler : ProjectFile::getHandlers()) { - try { - if (!handler.load(handler.basePath, tar)) { - log::warn("Project file handler for {} failed to load {}", filePath.string(), handler.basePath.string()); - result = false; - } - } catch (std::exception &e) { - log::warn("Project file handler for {} failed to load {}: {}", filePath.string(), handler.basePath.string(), e.what()); - result = false; - } - - if (!result && handler.required) { - return false; - } - } - - for (const auto &provider : ImHexApi::Provider::getProviders()) { - const auto basePath = std::fs::path(std::to_string(provider->getID())); - for (const auto &handler: ProjectFile::getProviderHandlers()) { - try { - if (!handler.load(provider, basePath / handler.basePath, tar)) - result = false; - } catch (std::exception &e) { - log::info("{}", e.what()); - result = false; - } - - if (!result && handler.required) { - return false; - } - } - } - - resetPath.release(); - EventManager::post(); - EventManager::post(); - - return true; + return s_loadProjectFunction(filePath); } bool ProjectFile::store(std::optional filePath) { - auto originalPath = ProjectFile::s_currProjectPath; - - if (!filePath.has_value()) - filePath = ProjectFile::s_currProjectPath; - - ProjectFile::s_currProjectPath = filePath.value(); - auto resetPath = SCOPE_GUARD { - ProjectFile::s_currProjectPath = originalPath; - }; - - Tar tar(*filePath, Tar::Mode::Create); - if (!tar.isValid()) - return false; - - bool result = true; - for (const auto &handler : ProjectFile::getHandlers()) { - try { - if (!handler.store(handler.basePath, tar) && handler.required) - result = false; - } catch (std::exception &e) { - log::info("{}", e.what()); - - if (handler.required) - result = false; - } - } - for (const auto &provider : ImHexApi::Provider::getProviders()) { - const auto basePath = std::fs::path(std::to_string(provider->getID())); - for (const auto &handler: ProjectFile::getProviderHandlers()) { - try { - if (!handler.store(provider, basePath / handler.basePath, tar) && handler.required) - result = false; - } catch (std::exception &e) { - log::info("{}", e.what()); - - if (handler.required) - result = false; - } - } - } - - { - const auto metadataContent = hex::format("{}\n{}", MetadataHeaderMagic, IMHEX_VERSION); - tar.writeString(MetadataPath, metadataContent); - } - - ImHexApi::Provider::resetDirty(); - resetPath.release(); - - return result; + return s_storeProjectFunction(filePath); } bool ProjectFile::hasPath() { diff --git a/lib/libimhex/source/helpers/tar.cpp b/lib/libimhex/source/helpers/tar.cpp index d76efe988..e0c1e0212 100644 --- a/lib/libimhex/source/helpers/tar.cpp +++ b/lib/libimhex/source/helpers/tar.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -10,7 +11,7 @@ namespace hex { using namespace hex::literals; Tar::Tar(const std::fs::path &path, Mode mode) { - int error = MTAR_ESUCCESS; + int tar_error = MTAR_ESUCCESS; // Explicitly create file so a short path gets generated if (mode == Mode::Create) { @@ -20,16 +21,23 @@ namespace hex { auto shortPath = wolv::io::fs::toShortPath(path); if (mode == Tar::Mode::Read) - error = mtar_open(&this->m_ctx, shortPath.string().c_str(), "r"); + tar_error = mtar_open(&this->m_ctx, shortPath.string().c_str(), "r"); else if (mode == Tar::Mode::Write) - error = mtar_open(&this->m_ctx, shortPath.string().c_str(), "a"); + tar_error = mtar_open(&this->m_ctx, shortPath.string().c_str(), "a"); else if (mode == Tar::Mode::Create) - error = mtar_open(&this->m_ctx, shortPath.string().c_str(), "w"); + tar_error = mtar_open(&this->m_ctx, shortPath.string().c_str(), "w"); else - error = MTAR_EFAILURE; + tar_error = MTAR_EFAILURE; this->m_path = path; - this->m_valid = (error == MTAR_ESUCCESS); + this->m_valid = (tar_error == MTAR_ESUCCESS); + + if (!this->m_valid) { + this->m_tarOpenErrno = tar_error; + + // hopefully this errno corresponds to the file open call in mtar_open + this->m_fileOpenErrno = errno; + } } Tar::~Tar() { @@ -40,6 +48,8 @@ namespace hex { this->m_ctx = other.m_ctx; this->m_path = other.m_path; this->m_valid = other.m_valid; + this->m_tarOpenErrno = other.m_tarOpenErrno; + this->m_fileOpenErrno = other.m_fileOpenErrno; other.m_ctx = { }; other.m_valid = false; @@ -54,6 +64,8 @@ namespace hex { this->m_valid = other.m_valid; other.m_valid = false; + this->m_tarOpenErrno = other.m_tarOpenErrno; + this->m_fileOpenErrno = other.m_fileOpenErrno; return *this; } @@ -79,6 +91,10 @@ namespace hex { return mtar_find(&this->m_ctx, path.string().c_str(), &header) == MTAR_ESUCCESS; } + std::string Tar::getOpenErrorString(){ + return hex::format("{}: {}", mtar_strerror(this->m_tarOpenErrno), std::strerror(this->m_fileOpenErrno)); + } + void Tar::close() { if (this->m_valid) { mtar_finalize(&this->m_ctx); diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index 425da2dd3..e61a26917 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -29,6 +29,7 @@ add_library(${PROJECT_NAME} SHARED source/content/themes.cpp source/content/recent.cpp source/content/file_handlers.cpp + source/content/project.cpp source/content/providers/file_provider.cpp source/content/providers/gdb_provider.cpp @@ -60,6 +61,7 @@ add_library(${PROJECT_NAME} SHARED source/content/views/view_theme_manager.cpp source/content/helpers/math_evaluator.cpp + source/content/helpers/notification.cpp source/ui/hex_editor.cpp source/ui/pattern_drawer.cpp diff --git a/plugins/builtin/include/content/helpers/notification.hpp b/plugins/builtin/include/content/helpers/notification.hpp new file mode 100644 index 000000000..351dc8671 --- /dev/null +++ b/plugins/builtin/include/content/helpers/notification.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace hex::plugin::builtin { + + void showError(const std::string& message); + + void showWarning(const std::string& message); +} diff --git a/plugins/builtin/include/content/popups/popup_notification.hpp b/plugins/builtin/include/content/popups/popup_notification.hpp index 406ec3ff6..3a76515f2 100644 --- a/plugins/builtin/include/content/popups/popup_notification.hpp +++ b/plugins/builtin/include/content/popups/popup_notification.hpp @@ -1,6 +1,9 @@ +#pragma once + #include #include +#include #include #include @@ -53,6 +56,14 @@ namespace hex::plugin::builtin { }) { } }; + class PopupWarning : public impl::PopupNotification { + public: + explicit PopupWarning(std::string message) + : PopupNotification("hex.builtin.common.warning", std::move(message), [this]() { + Popup::close(); + }) { } + }; + class PopupError : public impl::PopupNotification { public: explicit PopupError(std::string message) diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index dea8bace4..ba815cdac 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -30,6 +30,7 @@ "hex.builtin.common.encoding.utf8": "UTF-8", "hex.builtin.common.end": "End", "hex.builtin.common.endian": "Endian", + "hex.builtin.common.warning": "Warning", "hex.builtin.common.error": "Error", "hex.builtin.common.fatal": "Fatal Error", "hex.builtin.common.file": "File", @@ -393,8 +394,14 @@ "hex.builtin.popup.error.create": "Failed to create new file!", "hex.builtin.popup.error.file_dialog.common": "An error occurred while opening the file browser: {}", "hex.builtin.popup.error.file_dialog.portal": "There was an error while opening the file browser: {}.\nThis might be caused by your system not having a xdg-desktop-portal backend installed correctly.\n\nOn KDE, it's xdg-desktop-portal-kde.\nOn Gnome it's xdg-desktop-portal-gnome.\nOn wlroots it's xdg-desktop-portal-wlr.\nOtherwise, you can try to use xdg-desktop-portal-gtk.\n\nReboot your system after installing it.\n\nIf the file browser still doesn't work after this, submit an issue at https://github.com/WerWolv/ImHex/issues\n\nIn the meantime files can still be opened by dragging them onto the ImHex window!", - "hex.builtin.popup.error.project.load": "Failed to load project!", + "hex.builtin.popup.error.project.load": "Failed to load project: {}", "hex.builtin.popup.error.project.save": "Failed to save project!", + "hex.builtin.popup.error.project.load.create_provider": "Failed to create provider with type {}", + "hex.builtin.popup.error.project.load.no_providers": "There are no openable providers", + "hex.builtin.popup.error.project.load.some_providers_failed": "Some providers failed to load: {}", + "hex.builtin.popup.error.project.load.file_not_found": "Project file {} not found", + "hex.builtin.popup.error.project.load.invalid_tar": "Could not open tarred project file: {}", + "hex.builtin.popup.error.project.load.invalid_magic": "Invalid magic file in project file", "hex.builtin.popup.error.read_only": "Couldn't get write access. File opened in read-only mode.", "hex.builtin.popup.error.task_exception": "Exception thrown in Task '{}':\n\n{}", "hex.builtin.popup.exit_application.desc": "You have unsaved changes made to your Project.\nAre you sure you want to exit?", diff --git a/plugins/builtin/source/content/helpers/notification.cpp b/plugins/builtin/source/content/helpers/notification.cpp new file mode 100644 index 000000000..7d0c295c1 --- /dev/null +++ b/plugins/builtin/source/content/helpers/notification.cpp @@ -0,0 +1,16 @@ +#include + +#include + +namespace hex::plugin::builtin { + + void showError(const std::string& message){ + PopupError::open(message); + log::error(message); + } + + void showWarning(const std::string& message){ + PopupWarning::open(message); + log::warn(message); + } +} diff --git a/plugins/builtin/source/content/project.cpp b/plugins/builtin/source/content/project.cpp new file mode 100644 index 000000000..4af250ccf --- /dev/null +++ b/plugins/builtin/source/content/project.cpp @@ -0,0 +1,166 @@ +#include + +#include +#include + +#include +#include +#include +#include + +#include + +namespace hex::plugin::builtin { + + constexpr static auto MetadataHeaderMagic = "HEX"; + constexpr static auto MetadataPath = "IMHEX_METADATA"; + + bool load(const std::fs::path &filePath) { + auto originalPath = ProjectFile::getPath(); + + ProjectFile::setPath(filePath); + auto resetPath = SCOPE_GUARD { + ProjectFile::setPath(originalPath); + }; + + if (!wolv::io::fs::exists(filePath) || !wolv::io::fs::isRegularFile(filePath)) { + showError(hex::format("hex.builtin.popup.error.project.load"_lang, + hex::format("hex.builtin.popup.error.project.load.file_not_found"_lang, + wolv::util::toUTF8String(filePath) + ))); + return false; + } + + Tar tar(filePath, Tar::Mode::Read); + if (!tar.isValid()) { + showError(hex::format("hex.builtin.popup.error.project.load"_lang, + hex::format("hex.builtin.popup.error.project.load.invalid_tar"_lang, + tar.getOpenErrorString() + ))); + return false; + } + + if (!tar.contains(MetadataPath)) { + showError(hex::format("hex.builtin.popup.error.project.load"_lang, + hex::format("hex.builtin.popup.error.project.load.invalid_magic"_lang) + )); + return false; + } + + { + const auto metadataContent = tar.readVector(MetadataPath); + + if (!std::string(metadataContent.begin(), metadataContent.end()).starts_with(MetadataHeaderMagic)) { + showError(hex::format("hex.builtin.popup.error.project.load"_lang, + hex::format("hex.builtin.popup.error.project.load.invalid_magic"_lang) + )); + return false; + } + } + + auto providers = auto(ImHexApi::Provider::getProviders()); + for (const auto &provider : providers) { + ImHexApi::Provider::remove(provider); + } + + for (const auto &handler : ProjectFile::getHandlers()) { + bool result = true; + // handlers are supposed to show the error/warning popup to the user themselves, so we don't show one here + try { + if (!handler.load(handler.basePath, tar)) { + log::warn("Project file handler for {} failed to load {}", filePath.string(), handler.basePath.string()); + result = false; + } + } catch (std::exception &e) { + log::warn("Project file handler for {} failed to load {}: {}", filePath.string(), handler.basePath.string(), e.what()); + result = false; + } + + if (!result && handler.required) { + return false; + } + } + + for (const auto &provider : ImHexApi::Provider::getProviders()) { + const auto basePath = std::fs::path(std::to_string(provider->getID())); + for (const auto &handler: ProjectFile::getProviderHandlers()) { + bool result = true; + // Handlers are supposed to show the error/warning popup to the user themselves, so we don't show one here + try { + if (!handler.load(provider, basePath / handler.basePath, tar)) + result = false; + } catch (std::exception &e) { + log::info("{}", e.what()); + result = false; + } + + if (!result && handler.required) { + return false; + } + } + } + + resetPath.release(); + EventManager::post(); + EventManager::post(); + + return true; + } + + bool store(std::optional filePath = std::nullopt) { + auto originalPath = ProjectFile::getPath(); + + if (!filePath.has_value()) + filePath = originalPath; + + ProjectFile::setPath(filePath.value()); + auto resetPath = SCOPE_GUARD { + ProjectFile::setPath(originalPath); + }; + + Tar tar(*filePath, Tar::Mode::Create); + if (!tar.isValid()) + return false; + + bool result = true; + for (const auto &handler : ProjectFile::getHandlers()) { + try { + if (!handler.store(handler.basePath, tar) && handler.required) + result = false; + } catch (std::exception &e) { + log::info("{}", e.what()); + + if (handler.required) + result = false; + } + } + for (const auto &provider : ImHexApi::Provider::getProviders()) { + const auto basePath = std::fs::path(std::to_string(provider->getID())); + for (const auto &handler: ProjectFile::getProviderHandlers()) { + try { + if (!handler.store(provider, basePath / handler.basePath, tar) && handler.required) + result = false; + } catch (std::exception &e) { + log::info("{}", e.what()); + + if (handler.required) + result = false; + } + } + } + + { + const auto metadataContent = hex::format("{}\n{}", MetadataHeaderMagic, IMHEX_VERSION); + tar.writeString(MetadataPath, metadataContent); + } + + ImHexApi::Provider::resetDirty(); + resetPath.release(); + + return result; + } + + void registerProjectHandlers() { + hex::ProjectFile::setProjectFunctions(load, store); + } +} \ No newline at end of file diff --git a/plugins/builtin/source/content/providers.cpp b/plugins/builtin/source/content/providers.cpp index 17a6532fd..894a5b102 100644 --- a/plugins/builtin/source/content/providers.cpp +++ b/plugins/builtin/source/content/providers.cpp @@ -8,6 +8,8 @@ #include "content/providers/motorola_srec_provider.hpp" #include "content/providers/memory_file_provider.hpp" #include "content/providers/view_provider.hpp" +#include "content/popups/popup_notification.hpp" +#include "content/helpers/notification.hpp" #include #include @@ -31,63 +33,93 @@ namespace hex::plugin::builtin { ContentRegistry::Provider::add(false); ProjectFile::registerHandler({ - .basePath = "providers", - .required = true, - .load = [](const std::fs::path &basePath, Tar &tar) { - auto json = nlohmann::json::parse(tar.readString(basePath / "providers.json")); - auto providerIds = json.at("providers").get>(); + .basePath = "providers", + .required = true, + .load = [](const std::fs::path &basePath, Tar &tar) { + auto json = nlohmann::json::parse(tar.readString(basePath / "providers.json")); + auto providerIds = json.at("providers").get>(); - bool success = true; - for (const auto &id : providerIds) { - auto providerSettings = nlohmann::json::parse(tar.readString(basePath / hex::format("{}.json", id))); + bool success = true; + std::map providerWarnings; + for (const auto &id : providerIds) { + auto providerSettings = nlohmann::json::parse(tar.readString(basePath / hex::format("{}.json", id))); - auto provider = ImHexApi::Provider::createProvider(providerSettings.at("type").get(), true, false); - ON_SCOPE_EXIT { - if (!success) { - for (auto &task : TaskManager::getRunningTasks()) - task->interrupt(); + auto providerType = providerSettings.at("type").get(); + auto provider = ImHexApi::Provider::createProvider(providerType, true, false); + ON_SCOPE_EXIT { + if (!success) { + for (auto &task : TaskManager::getRunningTasks()) + task->interrupt(); - TaskManager::runWhenTasksFinished([]{ - for (auto provider : ImHexApi::Provider::getProviders()) + TaskManager::runWhenTasksFinished([]{ + for (auto provider : ImHexApi::Provider::getProviders()) ImHexApi::Provider::remove(provider, true); - }); - } - }; + }); + } + }; - if (provider == nullptr) { - success = false; - continue; - } + if (provider == nullptr) { + // if a provider is not created, it will be overwritten when saving the project, + // so we should prevent the project from loading at all + showError(hex::format("hex.builtin.popup.error.project.load"_lang, + hex::format("hex.builtin.popup.error.project.load.create_provider"_lang, providerType) + )); + success = false; + break; + } - provider->setID(id); - provider->loadSettings(providerSettings.at("settings")); - if (!provider->open() || !provider->isAvailable() || !provider->isReadable()) - success = false; - else - EventManager::post(provider); - } + provider->setID(id); + provider->loadSettings(providerSettings.at("settings")); + if (!provider->open() || !provider->isAvailable() || !provider->isReadable()) { + providerWarnings[provider] = provider->getErrorMessage(); + } else + EventManager::post(provider); + } - return success; - }, - .store = [](const std::fs::path &basePath, Tar &tar) { - std::vector providerIds; - for (const auto &provider : ImHexApi::Provider::getProviders()) { - auto id = provider->getID(); - providerIds.push_back(id); + std::string warningMsg; + for(const auto &warning : providerWarnings){ + ImHexApi::Provider::remove(warning.first); + warningMsg.append( + hex::format("\n - {} : {}", warning.first->getName(), warning.second)); + } - nlohmann::json json; - json["type"] = provider->getTypeName(); - json["settings"] = provider->storeSettings(); + // if no providers were opened, display an error with + // the warnings that happened when opening them + if (ImHexApi::Provider::getProviders().size() == 0) { + showError(hex::format("hex.builtin.popup.error.project.load"_lang, + hex::format("hex.builtin.popup.error.project.load.no_providers"_lang)) + warningMsg); - tar.writeString(basePath / hex::format("{}.json", id), json.dump(4)); - } + return false; + } else { - tar.writeString(basePath / "providers.json", + // else, if are warnings, still display them + if (warningMsg.empty()) return true; + else { + showWarning( + hex::format("hex.builtin.popup.error.project.load.some_providers_failed"_lang, warningMsg)); + } + return success; + } + }, + .store = [](const std::fs::path &basePath, Tar &tar) { + std::vector providerIds; + for (const auto &provider : ImHexApi::Provider::getProviders()) { + auto id = provider->getID(); + providerIds.push_back(id); + + nlohmann::json json; + json["type"] = provider->getTypeName(); + json["settings"] = provider->storeSettings(); + + tar.writeString(basePath / hex::format("{}.json", id), json.dump(4)); + } + + tar.writeString(basePath / "providers.json", nlohmann::json({ { "providers", providerIds } }).dump(4) - ); + ); - return true; - } + return true; + } }); } diff --git a/plugins/builtin/source/content/providers/file_provider.cpp b/plugins/builtin/source/content/providers/file_provider.cpp index 93eb0cf34..41f223ddf 100644 --- a/plugins/builtin/source/content/providers/file_provider.cpp +++ b/plugins/builtin/source/content/providers/file_provider.cpp @@ -205,6 +205,11 @@ namespace hex::plugin::builtin { this->m_readable = true; this->m_writable = true; + if (!std::fs::exists(this->m_path)) { + this->setErrorMessage(hex::format("hex.builtin.provider.file.error.open"_lang, this->m_path.string(), ::strerror(ENOENT))); + return false; + } + wolv::io::File file(this->m_path, wolv::io::File::Mode::Write); if (!file.isValid()) { this->m_writable = false; diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index 360aeca0a..51c28682a 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -31,6 +31,7 @@ namespace hex::plugin::builtin { void registerBackgroundServices(); void registerNetworkEndpoints(); void registerFileHandlers(); + void registerProjectHandlers(); void addFooterItems(); void addTitleBarButtons(); @@ -71,6 +72,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { registerBackgroundServices(); registerNetworkEndpoints(); registerFileHandlers(); + registerProjectHandlers(); addFooterItems(); addTitleBarButtons();