From 7ce06139776fd1f5602e51965fbbbe1c1711f6ab Mon Sep 17 00:00:00 2001 From: iTrooz Date: Mon, 22 May 2023 13:24:48 +0200 Subject: [PATCH] impr: Added better crash backup and restore mechanism (#1094) - Add a new file 'crash.json' to store metadata about the crash, like the log file or project opened - show the log file of the session that caused the crash to the user - Correctly restore the project path --- lib/libimhex/include/hex/helpers/logger.hpp | 3 + lib/libimhex/source/helpers/logger.cpp | 4 + main/source/window/window.cpp | 35 ++++++++- plugins/builtin/romfs/lang/base.json | 1 + plugins/builtin/romfs/lang/en_US.json | 1 + .../builtin/source/content/welcome_screen.cpp | 78 +++++++++++++++---- 6 files changed, 103 insertions(+), 19 deletions(-) diff --git a/lib/libimhex/include/hex/helpers/logger.hpp b/lib/libimhex/include/hex/helpers/logger.hpp index 9c1016fab..69e07fc60 100644 --- a/lib/libimhex/include/hex/helpers/logger.hpp +++ b/lib/libimhex/include/hex/helpers/logger.hpp @@ -9,9 +9,12 @@ #include #include +#include + namespace hex::log { FILE *getDestination(); + wolv::io::File& getFile(); bool isRedirected(); namespace { diff --git a/lib/libimhex/source/helpers/logger.cpp b/lib/libimhex/source/helpers/logger.cpp index 073b8b060..ec2251d67 100644 --- a/lib/libimhex/source/helpers/logger.cpp +++ b/lib/libimhex/source/helpers/logger.cpp @@ -15,6 +15,10 @@ namespace hex::log { return stdout; } + wolv::io::File& getFile() { + return g_loggerFile; + } + bool isRedirected() { return g_loggerFile.isValid(); } diff --git a/main/source/window/window.cpp b/main/source/window/window.cpp index f000cce61..3ee11e067 100644 --- a/main/source/window/window.cpp +++ b/main/source/window/window.cpp @@ -45,11 +45,34 @@ namespace hex { using namespace std::literals::chrono_literals; + static void saveCrashFile(){ + nlohmann::json crashData{ + {"logFile", hex::log::getFile().getPath()}, + {"project", ProjectFile::getPath()}, + }; + + for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { + wolv::io::File file(path / "crash.json", wolv::io::File::Mode::Write); + if (file.isValid()) { + file.writeString(crashData.dump(4)); + file.close(); + log::info("Wrote crash.json file to {}", wolv::util::toUTF8String(file.getPath())); + return; + } + } + log::warn("Could not write crash.json file !"); + } + // Custom signal handler to print various information and a stacktrace when the application crashes static void signalHandler(int signalNumber, const std::string &signalName) { log::fatal("Terminating with signal '{}' ({})", signalName, signalNumber); + // save crash.json file + saveCrashFile(); + // Trigger an event so that plugins can handle crashes + // It may affect things (like the project path), + // so we do this after saving the crash file EventManager::post(signalNumber); // Detect if the crash was due to an uncaught exception @@ -175,12 +198,12 @@ namespace hex { constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; // Save a backup project when the application crashes + // We need to save the project no mater if it is dirty, + // because this save is responsible for telling us which files + // were opened in case there wasn't a project EventManager::subscribe(this, [this](int) { ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str()); - if (!ImHexApi::Provider::isDirty()) - return; - for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { if (ProjectFile::store(path / CrashBackupFileName)) break; @@ -219,6 +242,12 @@ namespace hex { ex.what() ); } + + // save crash.json file + saveCrashFile(); + + // send the event. It may affect things (like the project path), + // so we do this after saving the crash file EventManager::post(0); }); } diff --git a/plugins/builtin/romfs/lang/base.json b/plugins/builtin/romfs/lang/base.json index 4c5fe35a5..feb7e0e48 100644 --- a/plugins/builtin/romfs/lang/base.json +++ b/plugins/builtin/romfs/lang/base.json @@ -842,6 +842,7 @@ "hex.builtin.welcome.plugins.plugin", "hex.builtin.popup.safety_backup.delete", "hex.builtin.popup.safety_backup.desc", + "hex.builtin.popup.safety_backup.log_file", "hex.builtin.popup.safety_backup.restore", "hex.builtin.popup.safety_backup.title", "hex.builtin.welcome.start.create_file", diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 06c45fff3..139f9f9be 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -921,6 +921,7 @@ "hex.builtin.welcome.plugins.plugin": "Plugin", "hex.builtin.popup.safety_backup.delete": "No, Delete", "hex.builtin.popup.safety_backup.desc": "Oh no, ImHex crashed last time.\nDo you want to restore your past work?", + "hex.builtin.popup.safety_backup.log_file": "Log file: ", "hex.builtin.popup.safety_backup.restore": "Yes, Restore", "hex.builtin.popup.safety_backup.title": "Restore lost data", "hex.builtin.welcome.start.create_file": "Create New File", diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index c2bb8d743..9596b01a5 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -68,30 +68,39 @@ namespace hex::plugin::builtin { }; class PopupRestoreBackup : public Popup { + private: + std::fs::path m_logFilePath; + std::function m_restoreCallback; + std::function m_deleteCallback; public: - PopupRestoreBackup() : Popup("hex.builtin.popup.safety_backup.title") { } + PopupRestoreBackup(std::fs::path logFilePath, std::function restoreCallback, std::function deleteCallback) + : Popup("hex.builtin.popup.safety_backup.title"), + m_logFilePath(logFilePath), + m_restoreCallback(restoreCallback), + m_deleteCallback(deleteCallback) { } void drawContent() override { ImGui::TextUnformatted("hex.builtin.popup.safety_backup.desc"_lang); - ImGui::NewLine(); - + if (!this->m_logFilePath.empty()) { + ImGui::TextUnformatted("hex.builtin.popup.safety_backup.log_file"_lang); + if (ImGui::Hyperlink(this->m_logFilePath.filename().string().c_str())) { + fs::openFolderWithSelectionExternal(this->m_logFilePath); + } + ImGui::NewLine(); + } + auto width = ImGui::GetWindowWidth(); ImGui::SetCursorPosX(width / 9); if (ImGui::Button("hex.builtin.popup.safety_backup.restore"_lang, ImVec2(width / 3, 0))) { - ProjectFile::load(s_safetyBackupPath); - ProjectFile::clearPath(); - - for (const auto &provider : ImHexApi::Provider::getProviders()) - provider->markDirty(); - - wolv::io::fs::remove(s_safetyBackupPath); + this->m_restoreCallback(); + this->m_deleteCallback(); this->close(); } ImGui::SameLine(); ImGui::SetCursorPosX(width / 9 * 5); if (ImGui::Button("hex.builtin.popup.safety_backup.delete"_lang, ImVec2(width / 3, 0))) { - wolv::io::fs::remove(s_safetyBackupPath); + this->m_deleteCallback(); this->close(); } @@ -608,17 +617,54 @@ namespace hex::plugin::builtin { }); // Check for crash backup - constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; + constexpr static auto CrashFileName = "crash.json"; + constexpr static auto BackupFileName = "crash_backup.hexproj"; + bool hasCrashed = false; + for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { - if (auto filePath = std::fs::path(path) / CrashBackupFileName; wolv::io::fs::exists(filePath)) { - s_safetyBackupPath = filePath; - PopupRestoreBackup::open(); + if (auto crashFilePath = std::fs::path(path) / CrashFileName; wolv::io::fs::exists(crashFilePath)) { + hasCrashed = true; + + log::info("Found crash.json file at {}", wolv::util::toUTF8String(crashFilePath)); + wolv::io::File crashFile(crashFilePath, wolv::io::File::Mode::Read); + auto crashFileData = nlohmann::json::parse(crashFile.readString()); + crashFile.close(); + bool hasProject = !crashFileData.value("project", "").empty(); + + auto backupFilePath = path / BackupFileName; + bool hasBackupFile = wolv::io::fs::exists(backupFilePath); + + PopupRestoreBackup::open( + // path of log file + crashFileData.value("logFile", ""), + // restore callback + [=]{ + if (hasBackupFile) { + ProjectFile::load(backupFilePath); + if (hasProject) { + ProjectFile::setPath(crashFileData["project"].get()); + } else { + ProjectFile::setPath(""); + } + EventManager::post(); + }else{ + if (hasProject) { + ProjectFile::setPath(crashFileData["project"].get()); + } + } + }, + // delete callback (also executed after restore) + [crashFilePath, backupFilePath]{ + wolv::io::fs::remove(crashFilePath); + wolv::io::fs::remove(backupFilePath); + } + ); } } // Tip of the day auto tipsData = romfs::get("tips.json"); - if(s_safetyBackupPath.empty() && tipsData.valid()){ + if (!hasCrashed && tipsData.valid()) { auto tipsCategories = nlohmann::json::parse(tipsData.string()); auto now = std::chrono::system_clock::now();