#ifndef DATA_H #define DATA_H #include #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. */ /* 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'; unsigned char rating = 0; unsigned char bWatched = 0; unsigned char bTVOriginal = 0; wchar_t sRating[4] = {0}; wchar_t siEp[6] = {0}; wchar_t title[128] = {0}; }; /* Extra episode data presented in data list view. */ struct DlvDataA { wchar_t date[32] = {0}; wchar_t source[48] = {0}; wchar_t screenwriter[48] = {0}; wchar_t hint[128] = {0}; wchar_t wiki[192] = {0}; }; /* Configuration. */ struct CfgA { unsigned char version = 'a'; unsigned char bViewWatched = 1; unsigned char bViewTVOriginal = 1; signed char iSortCol = 1; unsigned short cEp = 0; unsigned short iFocus = 0; unsigned short heightDlv = 0; wchar_t limitScreenwriter[64] = {0}; wchar_t root[260] = {0}; wchar_t glob[64] = {0}; wchar_t url[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 { static FileView Initialized(const wchar_t* filename, size_t c, size_t cInit = 0) { bool bExisting = GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES; FileView fv{filename, c}; /* If file didn't exist, initialize it with defaults. */ if (!bExisting) { T t; cInit = cInit? cInit: c; for (size_t i = 0; i < cInit; i++) memcpy(fv+i, &t, sizeof(T)); } return fv; } 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; } inline T* operator ->() noexcept { return view; } HANDLE hf; HANDLE hm; T* view; size_t c; }; inline int FromWeb(const int iEp, ElvDataA& e, DlvDataA& d) noexcept { WcharPtr title, wiki, date, source, hint; const int r = Pl("episode_data","fetch_episode_data",iEp,&title,&wiki,&date,&source,&hint); if (title) Wcscpy(e.title, title); if (wiki) Wcscpy(d.wiki, wiki); if (date) Wcscpy(d.date, date); if (source) Wcscpy(d.source, source); if (hint) Wcscpy(d.hint, hint); return r; } inline bool FromProlog(const int iEp, ElvDataA& e) noexcept { if (WcharPtr title; Pl("episode_data","episode_title",iEp,&title)) Wcscpy(e.title, title); else return false; int rating; if (Pl("episode_data","episode_rating",iEp,&rating)) { e.rating = rating; Swprintf(e.sRating, L"%d", e.rating); } if (Pl("episode_data","tv_original",iEp)) e.bTVOriginal = true; if (Pl("track_episodes","watched",iEp)) e.bWatched = true; Swprintf(e.siEp, L"%d", iEp); return true; } inline void FromProlog(const int iEp, DlvDataA& d) noexcept { if (WcharPtr wiki; Pl("episode_data","episode_wiki",iEp,&wiki)) Wcscpy(d.wiki, wiki); if (WcharPtr screenwriter; Pl("episode_data","episode_datum",iEp,"Screenwriter",&screenwriter)) Wcscpy(d.screenwriter, screenwriter); if (WcharPtr date; Pl("episode_data","episode_datum",iEp,"Date",&date)) Wcscpy(d.date, date); if (WcharPtr source; Pl("episode_data","episode_datum",iEp,"Source",&source)) Wcscpy(d.source, source); if (WcharPtr hint; Pl("episode_data","episode_datum",iEp,"Hint",&hint)) Wcscpy(d.hint, hint); } #endif