feat: Added remote SSH file provider

This commit is contained in:
WerWolv
2025-07-13 11:24:43 +02:00
parent a89fb542b0
commit bdc108d021
22 changed files with 992 additions and 26 deletions

View File

@@ -45,6 +45,7 @@ set(LIBIMHEX_SOURCES
source/test/tests.cpp
source/providers/provider.cpp
source/providers/cached_provider.cpp
source/providers/memory_provider.cpp
source/providers/undo/stack.cpp

View File

@@ -0,0 +1,59 @@
#pragma once
#include <hex/providers/provider.hpp>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <shared_mutex>
#include <cstddef>
#include <cstdint>
namespace hex::prv {
/**
* @brief A base class for providers that want to cache data in memory.
* Thread-safe for concurrent reads/writes. Reads are cached in memory.
* Subclasses must implement readFromSource and writeToSource.
*/
class CachedProvider : public Provider {
public:
CachedProvider(size_t cacheBlockSize = 4096, size_t maxBlocks = 1024);
~CachedProvider() override;
bool open() override;
void close() override;
void readRaw(u64 offset, void *buffer, size_t size) override;
void writeRaw(u64 offset, const void *buffer, size_t size) override;
void resizeRaw(u64 newSize) override;
u64 getActualSize() const override;
protected:
virtual void readFromSource(uint64_t offset, void* buffer, size_t size) = 0;
virtual void writeToSource(uint64_t offset, const void* buffer, size_t size) = 0;
virtual void resizeSource(uint64_t newSize) { std::ignore = newSize; }
virtual u64 getSourceSize() const = 0;
void clearCache();
struct Block {
uint64_t index;
std::vector<uint8_t> data;
bool dirty = false;
};
size_t m_cacheBlockSize;
size_t m_maxBlocks;
mutable std::shared_mutex m_cacheMutex;
std::vector<std::optional<Block>> m_cache;
mutable u64 m_cachedSize = 0;
constexpr u64 calcBlockIndex(u64 offset) const { return offset / m_cacheBlockSize; }
constexpr size_t calcBlockOffset(u64 offset) const { return offset % m_cacheBlockSize; }
void evictIfNeeded();
};
}

View File

@@ -0,0 +1,130 @@
#include "hex/providers/cached_provider.hpp"
#include <algorithm>
#include <optional>
namespace hex::prv {
CachedProvider::CachedProvider(size_t cacheBlockSize, size_t maxBlocks)
: m_cacheBlockSize(cacheBlockSize), m_maxBlocks(maxBlocks), m_cache(maxBlocks) {}
CachedProvider::~CachedProvider() {
clearCache();
}
bool CachedProvider::open() {
clearCache();
return true;
}
void CachedProvider::close() {
clearCache();
}
void CachedProvider::readRaw(u64 offset, void* buffer, size_t size) {
if (!isAvailable() || !isReadable())
return;
auto out = static_cast<u8 *>(buffer);
while (size > 0) {
const auto blockIndex = calcBlockIndex(offset);
const auto blockOffset = calcBlockOffset(offset);
const auto toRead = std::min(m_cacheBlockSize - blockOffset, size);
const auto cacheSlot = blockIndex % m_maxBlocks;
{
std::shared_lock lock(m_cacheMutex);
const auto &slot = m_cache[cacheSlot];
if (slot && slot->index == blockIndex) {
std::copy_n(slot->data.begin() + blockOffset, toRead, out);
out += toRead;
offset += toRead;
size -= toRead;
continue;
}
}
std::vector<uint8_t> blockData(m_cacheBlockSize);
readFromSource(blockIndex * m_cacheBlockSize, blockData.data(), m_cacheBlockSize);
{
std::unique_lock lock(m_cacheMutex);
m_cache[cacheSlot] = Block{blockIndex, std::move(blockData), false};
std::copy_n(m_cache[cacheSlot]->data.begin() + blockOffset, toRead, out);
}
out += toRead;
offset += toRead;
size -= toRead;
}
}
void CachedProvider::writeRaw(u64 offset, const void* buffer, size_t size) {
if (!isAvailable() || !isWritable())
return;
auto in = static_cast<const u8 *>(buffer);
while (size > 0) {
const auto blockIndex = calcBlockIndex(offset);
const auto blockOffset = calcBlockOffset(offset);
const auto toWrite = std::min(m_cacheBlockSize - blockOffset, size);
const auto cacheSlot = blockIndex % m_maxBlocks;
{
std::unique_lock lock(m_cacheMutex);
auto& slot = m_cache[cacheSlot];
if (!slot || slot->index != blockIndex) {
std::vector<uint8_t> blockData(m_cacheBlockSize);
readFromSource(blockIndex * m_cacheBlockSize, blockData.data(), m_cacheBlockSize);
slot = Block { blockIndex, std::move(blockData), false };
}
std::copy_n(in, toWrite, slot->data.begin() + blockOffset);
slot->dirty = true;
}
writeToSource(offset, in, toWrite);
in += toWrite;
offset += toWrite;
size -= toWrite;
}
}
void CachedProvider::resizeRaw(u64 newSize) {
clearCache();
resizeSource(newSize);
}
u64 CachedProvider::getActualSize() const {
if (!isAvailable())
return 0;
if (m_cachedSize == 0) {
std::unique_lock lock(m_cacheMutex);
m_cachedSize = getSourceSize();
}
return m_cachedSize;
}
void CachedProvider::clearCache() {
std::unique_lock lock(m_cacheMutex);
for (auto& slot : m_cache)
slot.reset();
m_cachedSize = 0;
}
void CachedProvider::evictIfNeeded() {
if (m_cache.size() < m_maxBlocks)
return;
m_cache.erase(m_cache.begin());
}
}