diff --git a/plugins/builtin/include/content/recent.hpp b/plugins/builtin/include/content/recent.hpp index 968f1b300..99d1336ab 100644 --- a/plugins/builtin/include/content/recent.hpp +++ b/plugins/builtin/include/content/recent.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace hex::plugin::builtin::recent { @@ -53,6 +54,31 @@ namespace hex::plugin::builtin::recent { }; + class PopupAutoBackups : public Popup { + private: + struct BackupEntry { + std::string displayName; + std::fs::path path; + std::tm time; + + bool operator<(const BackupEntry& other) const { + auto a = this->time; + auto b = other.time; + return std::mktime(&a) < std::mktime(&b); + } + }; + public: + PopupAutoBackups(); + + void drawContent() override; + [[nodiscard]] ImGuiWindowFlags getFlags() const override; + + static std::vector getAutoBackups(); + + private: + std::vector m_backups; + }; + void registerEventHandlers(); /** diff --git a/plugins/builtin/source/content/project.cpp b/plugins/builtin/source/content/project.cpp index a82c61e00..1906586d7 100644 --- a/plugins/builtin/source/content/project.cpp +++ b/plugins/builtin/source/content/project.cpp @@ -170,12 +170,12 @@ namespace hex::plugin::builtin { // Request, as this puts us into a project state RequestUpdateWindowTitle::post(); + + AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.save_project.name"); + + EventProjectSaved::post(); } - AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.save_project.name"); - - EventProjectSaved::post(); - return result; } diff --git a/plugins/builtin/source/content/recent.cpp b/plugins/builtin/source/content/recent.cpp index 99ad8801e..cc6b72fa1 100644 --- a/plugins/builtin/source/content/recent.cpp +++ b/plugins/builtin/source/content/recent.cpp @@ -1,3 +1,5 @@ +#include + #include #include @@ -17,7 +19,6 @@ #include #include -#include #include #include @@ -36,55 +37,68 @@ namespace hex::plugin::builtin::recent { std::list s_recentEntries; std::atomic_bool s_autoBackupsFound = false; + } - class PopupAutoBackups : public Popup { - private: - struct BackupEntry { - std::string displayName; - std::fs::path path; - }; - public: - PopupAutoBackups() : Popup("hex.builtin.welcome.start.recent.auto_backups", true, true) { - for (const auto &backupPath : paths::Backups.read()) { - for (const auto &entry : std::fs::directory_iterator(backupPath)) { - if (entry.is_regular_file() && entry.path().extension() == ".hexproj") { - wolv::io::File backupFile(entry.path(), wolv::io::File::Mode::Read); + std::vector PopupAutoBackups::getAutoBackups() { + std::set result; - m_backups.emplace_back( - fmt::format("hex.builtin.welcome.start.recent.auto_backups.backup"_lang, fmt::gmtime(backupFile.getFileInfo()->st_ctime)), - entry.path() - ); - } - } + for (const auto &backupPath : paths::Backups.read()) { + for (const auto &entry : std::fs::directory_iterator(backupPath)) { + if (entry.is_regular_file() && entry.path().extension() == ".hexproj") { + // auto_backup.{:%y%m%d_%H%M%S}.hexproj + auto fileName = wolv::util::toUTF8String(entry.path().stem()); + if (!fileName.starts_with("auto_backup.")) + continue; + + wolv::io::File backupFile(entry.path(), wolv::io::File::Mode::Read); + + auto creationTimeString = fileName.substr(12); + + std::chrono::sys_seconds utcSeconds; + std::istringstream ss(creationTimeString); + ss >> std::chrono::parse("%y%m%d_%H%M%S", utcSeconds); + if (ss.fail()) + continue; + + auto utcTime = std::chrono::system_clock::to_time_t(utcSeconds); + std::tm localTm = *std::localtime(&utcTime); + + result.emplace( + fmt::format("hex.builtin.welcome.start.recent.auto_backups.backup"_lang, localTm), + entry.path(), + localTm + ); + } + } + } + + return { result.begin(), result.end() }; + } + + PopupAutoBackups::PopupAutoBackups() : Popup("hex.builtin.welcome.start.recent.auto_backups", true, true) { + m_backups = getAutoBackups(); + } + + void PopupAutoBackups::drawContent() { + if (ImGui::BeginTable("AutoBackups", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5))) { + for (const auto &backup : m_backups | std::views::reverse | std::views::take(10)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(backup.displayName.c_str())) { + ProjectFile::load(backup.path); + Popup::close(); } } - void drawContent() override { - if (ImGui::BeginTable("AutoBackups", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5))) { - for (const auto &backup : m_backups | std::views::reverse | std::views::take(10)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(backup.displayName.c_str())) { - ProjectFile::load(backup.path); - Popup::close(); - } - } + ImGui::EndTable(); + } - ImGui::EndTable(); - } - - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) - this->close(); - } - - [[nodiscard]] ImGuiWindowFlags getFlags() const override { - return ImGuiWindowFlags_AlwaysAutoResize; - } - - private: - std::vector m_backups; - }; + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) + this->close(); + } + [[nodiscard]] ImGuiWindowFlags PopupAutoBackups::getFlags() const { + return ImGuiWindowFlags_AlwaysAutoResize; } void saveCurrentProjectAsRecent() { diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index 383c99d65..e8fea7ee7 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -56,13 +56,15 @@ namespace hex::plugin::builtin { class PopupRestoreBackup : public Popup { private: std::fs::path m_logFilePath; + bool m_hasAutoBackups; std::function m_restoreCallback; std::function m_deleteCallback; bool m_reportError = true; public: - PopupRestoreBackup(std::fs::path logFilePath, const std::function &restoreCallback, const std::function &deleteCallback) + PopupRestoreBackup(std::fs::path logFilePath, bool hasAutoBackups, const std::function &restoreCallback, const std::function &deleteCallback) : Popup("hex.builtin.popup.safety_backup.title"), m_logFilePath(std::move(logFilePath)), + m_hasAutoBackups(hasAutoBackups), m_restoreCallback(restoreCallback), m_deleteCallback(deleteCallback) { @@ -123,6 +125,12 @@ namespace hex::plugin::builtin { this->close(); } + + if (m_hasAutoBackups) { + if (ImGui::Button("Show Automatic Backups", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + recent::PopupAutoBackups::open(); + } + } } }; @@ -739,6 +747,10 @@ namespace hex::plugin::builtin { auto backupFilePathOld = path / BackupFileName; backupFilePathOld.replace_extension(".hexproj.old"); + bool autoBackupsEnabled = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.auto_backup_time", 0) > 0; + auto autoBackups = recent::PopupAutoBackups::getAutoBackups(); + bool hasAutoBackups = autoBackupsEnabled && !autoBackups.empty(); + bool hasBackupFile = wolv::io::fs::exists(backupFilePath); if (!hasProject && !hasBackupFile) { @@ -762,13 +774,16 @@ namespace hex::plugin::builtin { PopupRestoreBackup::open( // Path of log file crashFileData.value("logFile", ""), + hasAutoBackups, // Restore callback - [crashFileData, backupFilePath, hasProject, hasBackupFile] { + [crashFileData, backupFilePath, hasProject, hasBackupFile, hasAutoBackups, autoBackups = std::move(autoBackups)] { if (hasBackupFile) { if (ProjectFile::load(backupFilePath)) { if (hasProject) { ProjectFile::setPath(crashFileData["project"].get()); + } else if (hasAutoBackups) { + ProjectFile::setPath(autoBackups.front().path); } else { ProjectFile::setPath(""); } @@ -779,6 +794,8 @@ namespace hex::plugin::builtin { } else { if (hasProject) { ProjectFile::setPath(crashFileData["project"].get()); + } else if (hasAutoBackups) { + ProjectFile::setPath(autoBackups.front().path); } } },