diff --git a/CMakeLists.txt b/CMakeLists.txt index 5067630..ca32343 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.9 FATAL_ERROR) project(solanaceae) add_library(solanaceae_util + ./solanaceae/util/span.hpp + ./solanaceae/util/utils.hpp ./solanaceae/util/utils.cpp @@ -20,3 +22,16 @@ target_compile_features(solanaceae_util PUBLIC cxx_std_17) #target_link_libraries(solanaceae_util PUBLIC #) +######################################## + +add_library(solanaceae_file2 + ./solanaceae/file/file2.hpp + ./solanaceae/file/file2_std.hpp + ./solanaceae/file/file2_std.cpp +) + +target_include_directories(solanaceae_file2 PUBLIC .) +target_compile_features(solanaceae_file2 PUBLIC cxx_std_17) +target_link_libraries(solanaceae_file2 PUBLIC + solanaceae_util +) diff --git a/solanaceae/file/file2.hpp b/solanaceae/file/file2.hpp new file mode 100644 index 0000000..9024b18 --- /dev/null +++ b/solanaceae/file/file2.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include + +struct File2I { + // read only + uint64_t _file_size {0}; + + const bool can_write {false}; + const bool can_read {false}; + // TODO: non-seekable files? + + explicit File2I(bool can_write_, bool can_read_) : can_write(can_write_), can_read(can_read_) {} + virtual ~File2I(void) {} + + virtual bool isGood(void) = 0; + + // pos -1 means stream, append to last written, or read position (independent, like FILE*s) + virtual bool write(const ByteSpan data, int64_t pos = -1) = 0; + virtual std::variant> read(uint64_t size, int64_t pos = -1) = 0; +}; + diff --git a/solanaceae/file/file2_std.cpp b/solanaceae/file/file2_std.cpp new file mode 100644 index 0000000..e516ceb --- /dev/null +++ b/solanaceae/file/file2_std.cpp @@ -0,0 +1,129 @@ +#include "./file2_std.hpp" + +#include +#include + +// why is this so ugly? +// WARNING: has g pos side effect +static uint64_t get_file_size(std::fstream& f) { + // figure out size + f.seekg(0, f.end); + uint64_t file_size = f.tellg(); + f.seekg(0, f.beg); + return file_size; +} + +File2WFile::File2WFile(std::string_view file_path, bool trunc) : + File2I(true, false), + _file( + static_cast(file_path), + std::ios::out | + (trunc ? std::ios::trunc | std::ios::binary : std::ios::binary) // hacky but type safe + ) +{ + _file_size = get_file_size(_file); + + if (!_file.is_open()) { + return; // TODO: error + } +} + +bool File2WFile::isGood(void) { + return _file.is_open() && _file.good(); +} + + //bool write(const ByteSpan data, int64_t pos = -1) override; +bool File2WFile::write(const ByteSpan data, int64_t pos) { + if (pos != -1) { + //std::cerr << "invalid pos\n"; + return false; + } + + if (data.empty()) { + //std::cerr << "no data\n"; + return false; // true instead? + } + + _file.write(reinterpret_cast(data.ptr), data.size); + + return _file.good(); +} + +std::variant> File2WFile::read(uint64_t, int64_t) { + return ByteSpan{}; +} + +File2RWFile::File2RWFile(std::string_view file_path, uint64_t file_size, bool trunc) : + File2I(true, true), + _file( + static_cast(file_path), + std::ios::in | + std::ios::out | + (trunc ? std::ios::trunc | std::ios::binary : std::ios::binary) // hacky but type safe + ) +{ + if (file_size == -1) { + _file_size = get_file_size(_file); + } else { + _file_size = file_size; + } + + if (!_file.is_open()) { + return; // TODO: error + } +} + +bool File2RWFile::isGood(void) { + return _file.is_open() && _file.good(); +} + +bool File2RWFile::write(const ByteSpan data, int64_t pos) { + if (pos >= _file_size) { + return false; + } + + if (data.empty()) { + return false; // true instead? + } + + // if out-of-order, seek + if (pos >= 0 && _file.tellp() != int64_t(pos)) { + // TODO: error check + if (_file.seekp(pos, std::ios::beg).fail()) { + return false; + } + } else if (pos < -1) { + // error !!!! + return false; + } + + _file.write(reinterpret_cast(data.ptr), data.size); + + return _file.good(); +} + +std::variant> File2RWFile::read(uint64_t size, int64_t pos) { + if (pos >= int64_t(_file_size)) { + return ByteSpan{}; + } + + if (pos != -1) { + // TODO: error check + _file.seekg(pos, std::ios::beg); + } else if (pos < -1) { + // error !!!! + return ByteSpan{}; + } + + // we copy the data from file (not mapped) + std::vector chunk(size); + const auto nread = _file.read(reinterpret_cast(chunk.data()), chunk.size()).gcount(); + if (nread != std::numeric_limits::max()) { + chunk.resize(nread); // usually a noop + } else { + chunk.clear(); + } + + return chunk; +} + diff --git a/solanaceae/file/file2_std.hpp b/solanaceae/file/file2_std.hpp new file mode 100644 index 0000000..60f55a1 --- /dev/null +++ b/solanaceae/file/file2_std.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "./file2.hpp" + +#include + +// std fstream backed files + +// write steam file +// no preallocation required, but seeking is disabled +struct File2WFile : public File2I { + std::fstream _file; + + // dont truncate by default + File2WFile(std::string_view file_path, bool trunc = false); + + virtual ~File2WFile(void) {} + + bool isGood(void) override; + + bool write(const ByteSpan data, int64_t pos = -1) override; + std::variant> read(uint64_t size, int64_t pos = -1) override; +}; + +// read write, requires an existing file, file size is fixed +struct File2RWFile : public File2I { + std::fstream _file; + + // dont truncate by default + // pass -1 for fetching the size from file + File2RWFile(std::string_view file_path, uint64_t file_size = -1, bool trunc = false); + + virtual ~File2RWFile(void) {} + + bool isGood(void) override; + + bool write(const ByteSpan data, int64_t pos = -1) override; + std::variant> read(uint64_t size, int64_t pos = -1) override; +}; + +// cut down interface (write disabled) +// TODO: remove +struct File2RFile : public File2RWFile { + File2RFile(std::string_view file_path) : File2RWFile(file_path) {} + virtual ~File2RFile(void) {} + using File2RWFile::isGood; + using File2RWFile::read; + bool write(const ByteSpan, int64_t = -1) override { return false; } +}; + diff --git a/solanaceae/util/span.hpp b/solanaceae/util/span.hpp new file mode 100644 index 0000000..06a26da --- /dev/null +++ b/solanaceae/util/span.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +// non owning view +template +struct Span final { + const T* ptr {nullptr}; + uint64_t size {0}; + + constexpr Span(void) {} + constexpr Span(const T* ptr_, uint64_t size_) : ptr(ptr_), size(size_) {} + constexpr explicit Span(const std::vector& vec) : ptr(vec.data()), size(vec.size()) {} + + explicit operator std::vector() const { + return std::vector{ptr, ptr+size}; + } + + constexpr const T& operator[](uint64_t i) const { + if (i > size) { + throw std::out_of_range("accessed span out of range"); + } + + return ptr[i]; + } + + constexpr const T* cbegin(void) const { return ptr; } + constexpr const T* cend(void) const { return ptr+size; } + constexpr const T* begin(void) const { return ptr; } + constexpr const T* end(void) const { return ptr+size; } + + constexpr bool empty(void) const { return ptr == nullptr || size == 0; } +}; + +// useful alias +using ByteSpan = Span; +