From bd857d24443b8c8f5d2f58047c3f8ac5f058acea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Wed, 17 Aug 2022 02:33:26 +0200 Subject: Make FileView more type-safe. --- c/data.h | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 14 deletions(-) (limited to 'c/data.h') diff --git a/c/data.h b/c/data.h index 2cc4c14..a93b256 100644 --- a/c/data.h +++ b/c/data.h @@ -1,15 +1,21 @@ #ifndef DATA_H #define DATA_H +#include + #include "pl.h" #include "util.h" #include "wcharptr.h" +#include "win.h" + +/* The structs ending with A are written as-is to disk. As such, they + * should be regarded as immutable. If the format needs to be changed + * in the future, then new structs ending with B should be added. */ -/* ElvDataA and DlvDataA are written as-is to disk; note the careful - * alignment. As such, they should be regarded as immutable. If the - * format needs to be changed in the future, then new structs called - * ElvDataB and DlvDataB should be added. */ +/* Note that unsigned chars are 1-byte-aligned, wchar_t are + * 2-byte-aligned and ints are 4-byte-aligned (on x86 Windows). */ +/* Basic episode data presented in episode list view. */ struct ElvDataA { unsigned char version = 'a'; @@ -21,6 +27,7 @@ struct ElvDataA wchar_t title[128] = {0}; }; +/* Extra episode data presented in data list view. */ struct DlvDataA { wchar_t date[32] = {0}; @@ -30,21 +37,86 @@ struct DlvDataA wchar_t wiki[192] = {0}; }; +/* Variable template for obtaining the version of a given struct. */ +template +constexpr inline unsigned char Version = T{}.version; + +template +constexpr inline bool HasVersion = false; + +template +constexpr inline bool HasVersion> = true; + +/* FileView objects manage a memory-mapped file. The view buffer may + * be treated as an array of a given type T. Note that reading and + * writing a view can throw structured exceptions. We ignore these. */ +template struct FileView { - FileView(const wchar_t* filename, size_t cb); - ~FileView(); - inline operator unsigned char*() { return view; } - inline operator ElvDataA*() { return reinterpret_cast(view); } + FileView(const wchar_t* filename, size_t c_) : c(c_) + { + hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, + 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hf == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, + 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hf == INVALID_HANDLE_VALUE) + throw Win32Error{}; + } else + throw Win32Error{}; + } + + LARGE_INTEGER cbMap; + cbMap.QuadPart = c*sizeof(T); + hm = CreateFileMapping(hf, nullptr, PAGE_READWRITE, + cbMap.HighPart, cbMap.LowPart, nullptr); + if (!hm) + throw Win32Error{}; + + view = reinterpret_cast(MapViewOfFile(hm, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if (!view) + throw Win32Error{}; + } + + ~FileView() + { + FlushViewOfFile(view, 0); + UnmapViewOfFile(view); + CloseHandle(hm); + CloseHandle(hf); + } + + /* Access element by index, performing bounds check and, if + * applicable for T, version validation. */ + T& At(size_t i) + { + if (i >= c) + throw std::out_of_range("index larger than buffer"); + + T& t = view[i]; + + if constexpr (HasVersion) + if (t.version != Version) + throw std::runtime_error("invalid struct version"); + + /* TODO: Use a custom exception type that informs the + * user of possible data corruption. */ + + return t; + } + + inline operator T*() noexcept + { + return view; + } + HANDLE hf; HANDLE hm; - unsigned char* view; - /* TODO: Handle exceptions on read and write... */ + T* view; + size_t c; }; - -void Write(unsigned char* buf, const ElvDataA& e); -ElvDataA* Read(unsigned char* buf); - + inline int FromWeb(const int iEp, ElvDataA& e, DlvDataA& d) noexcept { WcharPtr title, wiki, date, source, hint; -- cgit v1.2.3