add io::last_write_time & add io::write_zip

This commit is contained in:
MihailRis 2025-02-25 02:03:01 +03:00
parent edb581bee3
commit 57d05bde57
8 changed files with 197 additions and 19 deletions

View File

@ -8,7 +8,6 @@
#include "../path.hpp"
namespace io {
/// @brief Device interface for file system operations
class Device {
public:
@ -28,6 +27,9 @@ namespace io {
/// @brief Get file size in bytes
virtual size_t size(std::string_view path) = 0;
/// @brief Get file last write timestamp
virtual file_time_type lastWriteTime(std::string_view path) = 0;
/// @brief Check if file or directory exists
virtual bool exists(std::string_view path) = 0;
@ -82,6 +84,10 @@ namespace io {
return parent->size((root / path).pathPart());
}
file_time_type lastWriteTime(std::string_view path) override {
return parent->lastWriteTime((root / path).pathPart());
}
bool exists(std::string_view path) override {
return parent->exists((root / path).pathPart());
}

View File

@ -45,23 +45,23 @@ std::unique_ptr<std::istream> StdfsDevice::read(std::string_view path) {
}
size_t StdfsDevice::size(std::string_view path) {
auto resolved = resolve(path);
return fs::file_size(resolved);
return fs::file_size(resolve(path));
}
file_time_type StdfsDevice::lastWriteTime(std::string_view path) {
return fs::last_write_time(resolve(path));
}
bool StdfsDevice::exists(std::string_view path) {
auto resolved = resolve(path);
return fs::exists(resolved);
return fs::exists(resolve(path));
}
bool StdfsDevice::isdir(std::string_view path) {
auto resolved = resolve(path);
return fs::is_directory(resolved);
return fs::is_directory(resolve(path));
}
bool StdfsDevice::isfile(std::string_view path) {
auto resolved = resolve(path);
return fs::is_regular_file(resolved);
return fs::is_regular_file(resolve(path));
}
bool StdfsDevice::mkdir(std::string_view path) {

View File

@ -10,6 +10,7 @@ namespace io {
std::unique_ptr<std::ostream> write(std::string_view path) override;
std::unique_ptr<std::istream> read(std::string_view path) override;
size_t size(std::string_view path) override;
file_time_type lastWriteTime(std::string_view path) override;
bool exists(std::string_view path) override;
bool isdir(std::string_view path) override;
bool isfile(std::string_view path) override;

View File

@ -11,6 +11,7 @@
static debug::Logger logger("zip-file");
using namespace io;
using namespace std::chrono;
static constexpr uint32_t EOCD_SIGNATURE = 0x06054b50;
static constexpr uint32_t CENTRAL_DIR_SIGNATURE = 0x02014b50;
@ -18,17 +19,52 @@ static constexpr uint32_t LOCAL_FILE_SIGNATURE = 0x04034b50;
static constexpr uint32_t COMPRESSION_NONE = 0;
static constexpr uint32_t COMPRESSION_DEFLATE = 8;
template<typename T>
static T read_int(std::unique_ptr<std::istream>& file) {
T value = 0;
file->read(reinterpret_cast<char*>(&value), sizeof(value));
return dataio::le2h(value);
}
namespace {
template<typename T>
T read_int(std::unique_ptr<std::istream>& file) {
T value = 0;
file->read(reinterpret_cast<char*>(&value), sizeof(value));
return dataio::le2h(value);
}
template<typename T>
static void read_int(std::unique_ptr<std::istream>& file, T& value) {
file->read(reinterpret_cast<char*>(&value), sizeof(value));
value = dataio::le2h(value);
template<typename T>
void read_int(std::unique_ptr<std::istream>& file, T& value) {
file->read(reinterpret_cast<char*>(&value), sizeof(value));
value = dataio::le2h(value);
}
file_time_type msdos_to_file_time(uint16_t date, uint16_t time) {
uint16_t year = ((date >> 9) & 0x7F) + 1980;
uint16_t month = (date >> 5) & 0x0F;
uint16_t day = date & 0x1F;
uint16_t hours = (time >> 11) & 0x1F;
uint16_t minutes = (time >> 5) & 0x3F;
uint16_t seconds = (time & 0x1F) * 2;
std::tm time_struct = {};
time_struct.tm_year = year - 1900;
time_struct.tm_mon = month - 1;
time_struct.tm_mday = day;
time_struct.tm_hour = hours;
time_struct.tm_min = minutes;
time_struct.tm_sec = seconds;
time_struct.tm_isdst = -1;
std::time_t time_t_value = std::mktime(&time_struct);
auto time_point = system_clock::from_time_t(time_t_value);
return file_time_type::clock::now() + (time_point - system_clock::now());
}
uint32_t to_ms_dos_timestamp(const file_time_type& fileTime) {
auto timePoint = time_point_cast<system_clock::duration>(
fileTime - file_time_type::clock::now() + system_clock::now()
);
std::time_t timeT = system_clock::to_time_t(timePoint);
std::tm tm = *std::localtime(&timeT);
uint16_t date = (tm.tm_year - 80) << 9 | (tm.tm_mon + 1) << 5 | tm.tm_mday;
uint16_t time = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec / 2);
return (date << 16) | time;
}
}
ZipFileDevice::Entry ZipFileDevice::readEntry() {
@ -192,6 +228,14 @@ size_t ZipFileDevice::size(std::string_view path) {
return found->second.uncompressedSize;
}
file_time_type ZipFileDevice::lastWriteTime(std::string_view path) {
const auto& found = entries.find(std::string(path));
if (found == entries.end()) {
return file_time_type::min();
}
return msdos_to_file_time(found->second.modDate, found->second.modTime);
}
bool ZipFileDevice::exists(std::string_view path) {
return entries.find(std::string(path)) != entries.end();
}
@ -262,3 +306,105 @@ std::unique_ptr<PathsGenerator> ZipFileDevice::list(std::string_view path) {
}
return std::make_unique<ListPathsGenerator>(std::move(names));
}
#include "io/io.hpp"
#include "coders/byte_utils.hpp"
static void write_headers(
std::ostream& file,
const std::string& name,
size_t srcSize,
size_t compressedSize,
uint32_t crc,
const file_time_type& modificationTime,
ByteBuilder& centralDir
) {
auto timestamp = to_ms_dos_timestamp(modificationTime);
ByteBuilder header;
header.putInt32(LOCAL_FILE_SIGNATURE);
header.putInt16(10); // version
header.putInt16(0); // flags
header.putInt16(0); // compression method
header.putInt32(timestamp); // last modification datetime
header.putInt32(crc); // crc32
header.putInt32(compressedSize);
header.putInt32(srcSize);
header.putInt16(name.length());
header.putInt16(0); // extra field length
header.put(reinterpret_cast<const ubyte*>(name.data()), name.length());
size_t localHeaderOffset = file.tellp();
file.write(reinterpret_cast<const char*>(header.data()), header.size());
centralDir.putInt32(CENTRAL_DIR_SIGNATURE);
centralDir.putInt16(10); // version
centralDir.putInt16(0); // version
centralDir.putInt16(0); // flags
centralDir.putInt16(0); // compression method
centralDir.putInt32(timestamp); // last modification datetime
centralDir.putInt32(crc); // crc32
centralDir.putInt32(compressedSize);
centralDir.putInt32(srcSize);
centralDir.putInt16(name.length());
centralDir.putInt16(0); // extra field length
centralDir.putInt16(0); // file comment length
centralDir.putInt16(0); // disk number start
centralDir.putInt16(0); // internal attributes
centralDir.putInt32(0); // external attributes
centralDir.putInt32(localHeaderOffset); // local header offset
centralDir.put(reinterpret_cast<const ubyte*>(name.data()), name.length());
}
static size_t write_zip(
const std::string& root,
const path& folder,
std::ostream& file,
ByteBuilder& centralDir
) {
size_t entries = 0;
ByteBuilder localHeader;
for (const auto& entry : io::directory_iterator(folder)) {
auto name = entry.pathPart().substr(root.length() + 1);
auto modificationTime = io::last_write_time(entry);
if (io::is_directory(entry)) {
name = name + "/";
write_headers(file, name, 0, 0, 0, modificationTime, centralDir);
entries += write_zip(root, entry, file, centralDir) + 1;
} else {
auto data = io::read_bytes_buffer(entry);
uint32_t crc = crc32(0, data.data(), data.size());
write_headers(
file,
name,
data.size(),
data.size(),
crc,
modificationTime,
centralDir
);
file.write(reinterpret_cast<const char*>(data.data()), data.size());
entries++;
}
}
return entries;
}
void io::write_zip(const path& folder, const path& file) {
ByteBuilder centralDir;
auto out = io::write(file);
size_t entries = write_zip(folder.pathPart(), folder, *out, centralDir);
size_t centralDirOffset = out->tellp();
out->write(reinterpret_cast<const char*>(centralDir.data()), centralDir.size());
ByteBuilder eocd;
eocd.putInt32(EOCD_SIGNATURE);
eocd.putInt16(0); // disk number
eocd.putInt16(0); // central dir disk
eocd.putInt16(entries); // num entries
eocd.putInt16(entries); // total entries
eocd.putInt32(centralDir.size()); // central dir size
eocd.putInt32(centralDirOffset); // central dir offset
eocd.putInt16(0); // comment length
out->write(reinterpret_cast<const char*>(eocd.data()), eocd.size());
}

View File

@ -40,6 +40,7 @@ namespace io {
std::unique_ptr<std::ostream> write(std::string_view path) override;
std::unique_ptr<std::istream> read(std::string_view path) override;
size_t size(std::string_view path) override;
io::file_time_type lastWriteTime(std::string_view path) override;
bool exists(std::string_view path) override;
bool isdir(std::string_view path) override;
bool isfile(std::string_view path) override;
@ -56,4 +57,6 @@ namespace io {
Entry readEntry();
void findBlob(Entry& entry);
};
void write_zip(const path& folder, const path& file);
}

View File

@ -107,6 +107,14 @@ bool io::read(const io::path& filename, char* data, size_t size) {
return stream->good();
}
std::unique_ptr<std::ostream> io::write(const io::path& file) {
auto device = io::get_device(file.entryPoint());
if (device == nullptr) {
throw std::runtime_error("io-device not found: " + file.entryPoint());
}
return device->write(file.pathPart());
}
std::unique_ptr<std::istream> io::read(const io::path& filename) {
auto device = io::get_device(filename.entryPoint());
if (device == nullptr) {
@ -307,6 +315,11 @@ size_t io::file_size(const io::path& file) {
return device.size(file.pathPart());
}
io::file_time_type io::last_write_time(const io::path& file) {
auto& device = io::require_device(file.entryPoint());
return device.lastWriteTime(file.pathPart());
}
std::filesystem::path io::resolve(const io::path& file) {
auto device = io::get_device(file.entryPoint());
if (device == nullptr) {

View File

@ -142,6 +142,10 @@ namespace io {
bool compressed = false
);
/// @brief Open file for writing
/// @throw std::runtime_error if file cannot be opened
std::unique_ptr<std::ostream> write(const io::path& file);
/// @brief Open file for reading
/// @throw std::runtime_error if file cannot be opened
std::unique_ptr<std::istream> read(const io::path& file);
@ -202,6 +206,9 @@ namespace io {
/// @brief Get file size in bytes
size_t file_size(const io::path& file);
/// @brief Get file last write time timestamp
file_time_type last_write_time(const io::path& file);
std::filesystem::path resolve(const io::path& file);
/// @brief Check if file is one of the supported data interchange formats

View File

@ -5,6 +5,8 @@
#include <filesystem>
namespace io {
using file_time_type = std::filesystem::file_time_type;
/// @brief Access violation error
class access_error : public std::runtime_error {
public: