feat: Add option to create auto backups of files before they're modified

This commit is contained in:
WerWolv 2025-12-07 21:37:14 +01:00
parent c2e07bf7b2
commit 855e4c4913
8 changed files with 86 additions and 10 deletions

View File

@ -239,6 +239,14 @@ EXPORT_MODULE namespace hex {
nlohmann::json store() override { return {}; } nlohmann::json store() override { return {}; }
}; };
class Spacer : public Widget {
public:
bool draw(const std::string &name) override;
void load(const nlohmann::json &) override {}
nlohmann::json store() override { return {}; }
};
} }
namespace impl { namespace impl {

View File

@ -72,6 +72,21 @@ namespace hex::prv {
[[nodiscard]] virtual std::vector<Description> getDataDescription() const = 0; [[nodiscard]] virtual std::vector<Description> getDataDescription() const = 0;
}; };
class IProviderDataBackupable {
public:
explicit IProviderDataBackupable(Provider *provider);
virtual ~IProviderDataBackupable() = default;
void createBackupIfNeeded(const std::fs::path &inputFilePath);
private:
Provider *m_provider = nullptr;
bool m_backupCreated = false;
bool m_shouldCreateBackups = true;
u64 m_maxSize;
std::string m_backupExtension;
};
/** /**
* @brief Represent the data source for a tab in the UI * @brief Represent the data source for a tab in the UI
*/ */

View File

@ -594,6 +594,12 @@ namespace hex {
return false; return false;
} }
bool Spacer::draw(const std::string& name) {
std::ignore = name;
ImGui::NewLine();
return false;
}
} }

View File

@ -7,12 +7,14 @@
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <optional> #include <optional>
#include <hex/api/content_registry/settings.hpp>
#include <hex/helpers/magic.hpp> #include <hex/helpers/magic.hpp>
#include <wolv/io/file.hpp> #include <wolv/io/file.hpp>
#include <wolv/literals.hpp> #include <wolv/literals.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <wolv/utils/string.hpp>
namespace hex::prv { namespace hex::prv {
@ -24,6 +26,28 @@ namespace hex::prv {
} }
IProviderDataBackupable::IProviderDataBackupable(Provider* provider) : m_provider(provider) {
m_shouldCreateBackups = ContentRegistry::Settings::read<bool>("hex.builtin.setting.general", "hex.builtin.setting.general.backups.file_backup.enable", true);
m_maxSize = ContentRegistry::Settings::read<u32>("hex.builtin.setting.general", "hex.builtin.setting.general.backups.file_backup.max_size", 1_MiB);
m_backupExtension = ContentRegistry::Settings::read<std::string>("hex.builtin.setting.general", "hex.builtin.setting.general.backups.file_backup.extension", ".bak");
}
void IProviderDataBackupable::createBackupIfNeeded(const std::fs::path &inputFilePath) {
if (!m_shouldCreateBackups || m_backupCreated)
return;
if (m_provider->getActualSize() > m_maxSize)
return;
const std::fs::path backupFilePath = wolv::util::toUTF8String(inputFilePath) + m_backupExtension;
if (wolv::io::fs::copyFile(inputFilePath, backupFilePath, std::fs::copy_options::overwrite_existing)) {
if (wolv::io::fs::exists(backupFilePath)) {
m_backupCreated = true;
log::info("Created backup of provider data at '{}'", backupFilePath.string());
}
}
}
Provider::Provider() : m_undoRedoStack(this), m_id(s_idCounter++) { Provider::Provider() : m_undoRedoStack(this), m_id(s_idCounter++) {

View File

@ -13,9 +13,10 @@ namespace hex::plugin::builtin {
class FileProvider : public prv::Provider, class FileProvider : public prv::Provider,
public prv::IProviderDataDescription, public prv::IProviderDataDescription,
public prv::IProviderFilePicker, public prv::IProviderFilePicker,
public prv::IProviderMenuItems { public prv::IProviderMenuItems,
public prv::IProviderDataBackupable {
public: public:
FileProvider() = default; FileProvider() : IProviderDataBackupable(this) {}
~FileProvider() override = default; ~FileProvider() override = default;
[[nodiscard]] bool isAvailable() const override; [[nodiscard]] bool isAvailable() const override;

View File

@ -493,11 +493,15 @@
"hex.builtin.setting.folders.description": "Specify additional search paths for patterns, scripts, Yara rules and more", "hex.builtin.setting.folders.description": "Specify additional search paths for patterns, scripts, Yara rules and more",
"hex.builtin.setting.folders.remove_folder": "Remove currently selected folder from list", "hex.builtin.setting.folders.remove_folder": "Remove currently selected folder from list",
"hex.builtin.setting.general": "General", "hex.builtin.setting.general": "General",
"hex.builtin.setting.general.backups": "Backups",
"hex.builtin.setting.general.backups.auto_backup_time": "Periodically backup project",
"hex.builtin.setting.general.backups.auto_backup_time.format.simple": "Every {0}s",
"hex.builtin.setting.general.backups.auto_backup_time.format.extended": "Every {0}m {1}s",
"hex.builtin.setting.general.backups.file_backup.enable": "Backup data sources before modification if possible",
"hex.builtin.setting.general.backups.file_backup.max_size": "Max file size for file backups",
"hex.builtin.setting.general.backups.file_backup.extension": "Backup file extension",
"hex.builtin.setting.general.patterns": "Patterns", "hex.builtin.setting.general.patterns": "Patterns",
"hex.builtin.setting.general.network": "Network", "hex.builtin.setting.general.network": "Network",
"hex.builtin.setting.general.auto_backup_time": "Periodically backup project",
"hex.builtin.setting.general.auto_backup_time.format.simple": "Every {0}s",
"hex.builtin.setting.general.auto_backup_time.format.extended": "Every {0}m {1}s",
"hex.builtin.setting.general.auto_apply_patterns": "Auto-load supported pattern", "hex.builtin.setting.general.auto_apply_patterns": "Auto-load supported pattern",
"hex.builtin.setting.general.suggest_patterns": "Suggest patterns based on loaded data", "hex.builtin.setting.general.suggest_patterns": "Suggest patterns based on loaded data",
"hex.builtin.setting.general.server_contact": "Enable update checks and usage statistics", "hex.builtin.setting.general.server_contact": "Enable update checks and usage statistics",

View File

@ -70,13 +70,16 @@ namespace hex::plugin::builtin {
if (m_loadedIntoMemory) if (m_loadedIntoMemory)
std::memcpy(m_data.data() + offset, buffer, size); std::memcpy(m_data.data() + offset, buffer, size);
else else {
this->createBackupIfNeeded(m_file.getPath());
m_file.writeBufferAtomic(offset, static_cast<const u8*>(buffer), size); m_file.writeBufferAtomic(offset, static_cast<const u8*>(buffer), size);
}
} }
void FileProvider::save() { void FileProvider::save() {
if (m_loadedIntoMemory) { if (m_loadedIntoMemory) {
m_ignoreNextChangeEvent = true; m_ignoreNextChangeEvent = true;
this->createBackupIfNeeded(m_file.getPath());
m_file.open(); m_file.open();
m_file.writeVectorAtomic(0x00, m_data); m_file.writeVectorAtomic(0x00, m_data);
m_file.setSize(m_data.size()); m_file.setSize(m_data.size());
@ -113,8 +116,10 @@ namespace hex::plugin::builtin {
void FileProvider::resizeRaw(u64 newSize) { void FileProvider::resizeRaw(u64 newSize) {
if (m_loadedIntoMemory) if (m_loadedIntoMemory)
m_data.resize(newSize); m_data.resize(newSize);
else else {
this->createBackupIfNeeded(m_file.getPath());
m_file.setSize(newSize); m_file.setSize(newSize);
}
m_fileSize = newSize; m_fileSize = newSize;
} }

View File

@ -227,9 +227,9 @@ namespace hex::plugin::builtin {
if (value == 0) if (value == 0)
return "hex.ui.common.off"_lang; return "hex.ui.common.off"_lang;
else if (value < 60) else if (value < 60)
return fmt::format("hex.builtin.setting.general.auto_backup_time.format.simple"_lang, value); return fmt::format("hex.builtin.setting.general.backups.auto_backup_time.format.simple"_lang, value);
else else
return fmt::format("hex.builtin.setting.general.auto_backup_time.format.extended"_lang, value / 60, value % 60); return fmt::format("hex.builtin.setting.general.backups.auto_backup_time.format.extended"_lang, value / 60, value % 60);
}(); }();
if (ImGui::SliderInt(name.data(), &m_value, 0, (30 * 60) / 30, format.c_str(), ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_NoInput)) { if (ImGui::SliderInt(name.data(), &m_value, 0, (30 * 60) / 30, format.c_str(), ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_NoInput)) {
@ -756,7 +756,6 @@ namespace hex::plugin::builtin {
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "", "hex.builtin.setting.general.show_tips", false); ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "", "hex.builtin.setting.general.show_tips", false);
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "", "hex.builtin.setting.general.save_recent_providers", true); ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "", "hex.builtin.setting.general.save_recent_providers", true);
ContentRegistry::Settings::add<AutoBackupWidget>("hex.builtin.setting.general", "", "hex.builtin.setting.general.auto_backup_time");
ContentRegistry::Settings::add<Widgets::SliderDataSize>("hex.builtin.setting.general", "", "hex.builtin.setting.general.max_mem_file_size", 512_MiB, 0_bytes, 32_GiB, 1_MiB) ContentRegistry::Settings::add<Widgets::SliderDataSize>("hex.builtin.setting.general", "", "hex.builtin.setting.general.max_mem_file_size", 512_MiB, 0_bytes, 32_GiB, 1_MiB)
.setTooltip("hex.builtin.setting.general.max_mem_file_size.desc"); .setTooltip("hex.builtin.setting.general.max_mem_file_size.desc");
ContentRegistry::Settings::add<Widgets::SliderInteger>("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.pattern_data_max_filter_items", 128, 32, 1024); ContentRegistry::Settings::add<Widgets::SliderInteger>("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.pattern_data_max_filter_items", 128, 32, 1024);
@ -773,6 +772,20 @@ namespace hex::plugin::builtin {
ContentRegistry::Settings::add<ServerContactWidget>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.server_contact"); ContentRegistry::Settings::add<ServerContactWidget>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.server_contact");
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.upload_crash_logs", true); ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.upload_crash_logs", true);
#endif #endif
ContentRegistry::Settings::add<AutoBackupWidget>("hex.builtin.setting.general", "hex.builtin.setting.general.backups", "hex.builtin.setting.general.backups.auto_backup_time");
ContentRegistry::Settings::add<Widgets::Spacer>("hex.builtin.setting.general", "hex.builtin.setting.general.backups", "hex.builtin.setting.general.backups.spacer");
auto fileBackupEnabledWidget = ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.backups", "hex.builtin.setting.general.backups.file_backup.enable", true);
ContentRegistry::Settings::add<Widgets::SliderDataSize>("hex.builtin.setting.general", "hex.builtin.setting.general.backups", "hex.builtin.setting.general.backups.file_backup.max_size", 512_MiB, 0_bytes, 32_GiB, 1_MiB)
.setEnabledCallback([=] {
return static_cast<Widgets::Checkbox&>(fileBackupEnabledWidget.getWidget()).isChecked();
});
ContentRegistry::Settings::add<Widgets::TextBox>("hex.builtin.setting.general", "hex.builtin.setting.general.backups", "hex.builtin.setting.general.backups.file_backup.extension", ".bak")
.setEnabledCallback([=] {
return static_cast<Widgets::Checkbox&>(fileBackupEnabledWidget.getWidget()).isChecked();
});
} }
/* Interface */ /* Interface */