1
0
mirror of synced 2024-12-02 03:07:18 +01:00
ImHex/plugins/builtin/source/content/providers/file_provider.cpp
SparkyTD adbcc48de7
fix: Multiple file reload popups stacking on top of each other (#1654)
<!--
Please provide as much information as possible about what your PR aims
to do.
PRs with no description will most likely be closed until more
information is provided.
If you're planing on changing fundamental behaviour or add big new
features, please open a GitHub Issue first before starting to work on
it.
If it's not something big and you still want to contact us about it,
feel free to do so !
-->

### Problem description
This PR aims to address #1645 that caused the built in file provider's
change monitor to trigger the notification popup dialog multiple times
in a row after multiple external file changes.

### Implementation description
I added an additional boolean field
`m_changeEventAcknowledgementPending` that tracks whether there are any
pending or unacknowledged change notification dialogs to prevent further
dialogs from being opened. The flag is only reset to its initial value
once the user has acknowledged the first `PopupQuestion` dialog.

Since the file is reloaded only after the user clicks 'Yes', it is
unnecessary to ensure that only the latest popup is acknowledged.
2024-05-07 23:43:20 +02:00

361 lines
12 KiB
C++

#include "content/providers/file_provider.hpp"
#include "content/providers/memory_file_provider.hpp"
#include <hex/api/imhex_api.hpp>
#include <hex/api/localization_manager.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/api/task_manager.hpp>
#include <toasts/toast_notification.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/fmt.hpp>
#include <fmt/chrono.h>
#include <wolv/utils/string.hpp>
#include <nlohmann/json.hpp>
#include <cstring>
#include <popups/popup_question.hpp>
#if defined(OS_WINDOWS)
#include <windows.h>
#endif
namespace hex::plugin::builtin {
std::set<FileProvider*> FileProvider::s_openedFiles;
bool FileProvider::isAvailable() const {
return true;
}
bool FileProvider::isReadable() const {
return isAvailable() && m_readable;
}
bool FileProvider::isWritable() const {
return isAvailable() && m_writable;
}
bool FileProvider::isResizable() const {
return isAvailable() && isWritable();
}
bool FileProvider::isSavable() const {
return m_loadedIntoMemory;
}
void FileProvider::readRaw(u64 offset, void *buffer, size_t size) {
if (m_fileSize == 0 || (offset + size) > m_fileSize || buffer == nullptr || size == 0)
return;
if (m_loadedIntoMemory)
std::memcpy(buffer, m_data.data() + offset, size);
else
m_file.readBufferAtomic(offset, static_cast<u8*>(buffer), size);
}
void FileProvider::writeRaw(u64 offset, const void *buffer, size_t size) {
if ((offset + size) > this->getActualSize() || buffer == nullptr || size == 0)
return;
if (m_loadedIntoMemory)
std::memcpy(m_data.data() + offset, buffer, size);
else
m_file.writeBufferAtomic(offset, static_cast<const u8*>(buffer), size);
}
void FileProvider::save() {
if (m_loadedIntoMemory) {
m_ignoreNextChangeEvent = true;
m_file.open();
m_file.writeVectorAtomic(0x00, m_data);
m_file.setSize(m_data.size());
} else {
m_file.flush();
}
#if defined(OS_WINDOWS)
FILETIME ft;
SYSTEMTIME st;
if (m_file.isValid()) {
GetSystemTime(&st);
if (SystemTimeToFileTime(&st, &ft)) {
auto fileHandle = HANDLE(_get_osfhandle(_fileno(m_file.getHandle())));
SetFileTime(fileHandle, nullptr, nullptr, &ft);
}
}
#endif
if (m_loadedIntoMemory)
m_file.close();
Provider::save();
}
void FileProvider::saveAs(const std::fs::path &path) {
if (path == m_path)
this->save();
else
Provider::saveAs(path);
}
void FileProvider::resizeRaw(u64 newSize) {
if (m_loadedIntoMemory)
m_data.resize(newSize);
else
m_file.setSize(newSize);
m_fileSize = newSize;
}
u64 FileProvider::getActualSize() const {
return m_fileSize;
}
std::string FileProvider::getName() const {
return wolv::util::toUTF8String(m_path.filename());
}
std::vector<FileProvider::Description> FileProvider::getDataDescription() const {
std::vector<Description> result;
result.emplace_back("hex.builtin.provider.file.path"_lang, wolv::util::toUTF8String(m_path));
result.emplace_back("hex.builtin.provider.file.size"_lang, hex::toByteString(this->getActualSize()));
if (m_fileStats.has_value()) {
std::string creationTime, accessTime, modificationTime;
try { creationTime = hex::format("{:%Y-%m-%d %H:%M:%S}", fmt::localtime(m_fileStats->st_ctime)); }
catch (const std::exception&) { creationTime = "???"; }
try { accessTime = hex::format("{:%Y-%m-%d %H:%M:%S}", fmt::localtime(m_fileStats->st_atime)); }
catch (const std::exception&) { accessTime = "???"; }
try { modificationTime = hex::format("{:%Y-%m-%d %H:%M:%S}", fmt::localtime(m_fileStats->st_mtime)); }
catch (const std::exception&) { modificationTime = "???"; }
result.emplace_back("hex.builtin.provider.file.creation"_lang, creationTime);
result.emplace_back("hex.builtin.provider.file.access"_lang, accessTime);
result.emplace_back("hex.builtin.provider.file.modification"_lang, modificationTime);
}
return result;
}
std::variant<std::string, i128> FileProvider::queryInformation(const std::string &category, const std::string &argument) {
if (category == "file_path")
return wolv::util::toUTF8String(m_path);
else if (category == "file_name")
return wolv::util::toUTF8String(m_path.filename());
else if (category == "file_extension")
return wolv::util::toUTF8String(m_path.extension());
else if (category == "creation_time")
return m_fileStats->st_ctime;
else if (category == "access_time")
return m_fileStats->st_atime;
else if (category == "modification_time")
return m_fileStats->st_mtime;
else if (category == "permissions")
return m_fileStats->st_mode & 0777;
else
return Provider::queryInformation(category, argument);
}
bool FileProvider::handleFilePicker() {
return fs::openFileBrowser(fs::DialogMode::Open, {}, [this](const auto &path) {
this->setPath(path);
});
}
std::vector<FileProvider::MenuEntry> FileProvider::getMenuEntries(){
return {
{ "hex.builtin.provider.file.menu.open_folder"_lang, [this] { fs::openFolderWithSelectionExternal(m_path); } },
{ "hex.builtin.provider.file.menu.open_file"_lang, [this] { fs::openFileExternal(m_path); } },
{ "hex.builtin.provider.file.menu.into_memory"_lang, [this] { this->convertToMemoryFile(); } }
};
}
void FileProvider::setPath(const std::fs::path &path) {
m_path = path;
}
bool FileProvider::open() {
m_readable = true;
m_writable = true;
if (!wolv::io::fs::exists(m_path)) {
this->setErrorMessage(hex::format("hex.builtin.provider.file.error.open"_lang, m_path.string(), ::strerror(ENOENT)));
return false;
}
wolv::io::File file(m_path, wolv::io::File::Mode::Write);
if (!file.isValid()) {
m_writable = false;
file = wolv::io::File(m_path, wolv::io::File::Mode::Read);
if (!file.isValid()) {
m_readable = false;
this->setErrorMessage(hex::format("hex.builtin.provider.file.error.open"_lang, m_path.string(), ::strerror(errno)));
return false;
}
ui::ToastInfo::open("hex.builtin.popup.error.read_only"_lang);
}
m_file = std::move(file);
m_fileStats = m_file.getFileInfo();
m_fileSize = m_file.getSize();
// Make sure the current file is not already opened
{
auto alreadyOpenedFileProvider = std::ranges::find_if(s_openedFiles, [this](const FileProvider *provider) {
return provider->m_path == m_path;
});
if (alreadyOpenedFileProvider != s_openedFiles.end()) {
ImHexApi::Provider::setCurrentProvider(*alreadyOpenedFileProvider);
return false;
} else {
s_openedFiles.insert(this);
}
}
if (m_writable) {
if (m_fileSize < MaxMemoryFileSize) {
m_data = m_file.readVectorAtomic(0x00, m_fileSize);
if (!m_data.empty()) {
m_changeTracker = wolv::io::ChangeTracker(m_file);
m_changeTracker.startTracking([this]{ this->handleFileChange(); });
m_file.close();
m_loadedIntoMemory = true;
}
} else {
m_writable = false;
ui::PopupQuestion::open("hex.builtin.provider.file.too_large"_lang,
[this] {
m_writable = false;
},
[this] {
m_writable = true;
RequestUpdateWindowTitle::post();
});
}
}
return true;
}
void FileProvider::close() {
m_file.close();
m_data.clear();
s_openedFiles.erase(this);
m_changeTracker.stopTracking();
}
void FileProvider::loadSettings(const nlohmann::json &settings) {
Provider::loadSettings(settings);
auto pathString = settings.at("path").get<std::string>();
std::fs::path path = std::u8string(pathString.begin(), pathString.end());
if (auto projectPath = ProjectFile::getPath(); !projectPath.empty()) {
try {
this->setPath(std::fs::weakly_canonical(projectPath.parent_path() / path));
} catch (const std::fs::filesystem_error &) {
try {
this->setPath(projectPath.parent_path() / path);
} catch (const std::fs::filesystem_error &e) {
this->setErrorMessage(hex::format("hex.builtin.provider.file.error.open"_lang, m_path.string(), e.what()));
}
}
} else {
this->setPath(path);
}
}
nlohmann::json FileProvider::storeSettings(nlohmann::json settings) const {
std::string path;
if (auto projectPath = ProjectFile::getPath(); !projectPath.empty())
path = wolv::util::toUTF8String(std::fs::proximate(m_path, projectPath.parent_path()));
if (path.empty())
path = wolv::util::toUTF8String(m_path);
settings["path"] = path;
return Provider::storeSettings(settings);
}
std::pair<Region, bool> FileProvider::getRegionValidity(u64 address) const {
address -= this->getBaseAddress();
if (address < this->getActualSize())
return { Region { this->getBaseAddress() + address, this->getActualSize() - address }, true };
else
return { Region::Invalid(), false };
}
void FileProvider::convertToMemoryFile() {
auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true);
if (auto memoryProvider = dynamic_cast<MemoryFileProvider*>(newProvider); memoryProvider != nullptr) {
if (!memoryProvider->open()) {
ImHexApi::Provider::remove(newProvider);
} else {
const auto size = this->getActualSize();
TaskManager::createTask("Loading into memory", size, [this, size, memoryProvider](Task &task) {
task.setInterruptCallback([memoryProvider]{
ImHexApi::Provider::remove(memoryProvider);
});
constexpr static size_t BufferSize = 0x10000;
std::vector<u8> buffer(BufferSize);
memoryProvider->resize(size);
for (u64 i = 0; i < size; i += BufferSize) {
auto copySize = std::min<size_t>(BufferSize, size - i);
this->read(i, buffer.data(), copySize, true);
memoryProvider->writeRaw(i, buffer.data(), copySize);
task.update(i);
}
memoryProvider->markDirty(true);
memoryProvider->getUndoStack().reset();
TaskManager::runWhenTasksFinished([this]{
ImHexApi::Provider::remove(this, false);
});
});
}
}
}
void FileProvider::handleFileChange() {
if (m_ignoreNextChangeEvent) {
m_ignoreNextChangeEvent = false;
return;
}
if(m_changeEventAcknowledgementPending) {
return;
}
m_changeEventAcknowledgementPending = true;
ui::PopupQuestion::open("hex.builtin.provider.file.reload_changes"_lang, [this] {
this->close();
(void)this->open();
getUndoStack().reapply();
m_changeEventAcknowledgementPending = false;
},
[this]{
m_changeEventAcknowledgementPending = false;
});
}
}