aboutsummaryrefslogtreecommitdiff
path: root/c/data.h
blob: b974cec81b1e96d9e49d44edd31fae9c6917ca91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#ifndef DATA_H
#define DATA_H

#include <stdexcept>
#include <windows.h>

#include "pl.h"
#include "util.h"
#include "wcharptr.h"
#include "win.h"

/* Fetch data from the web. */
void FetchData(unsigned char* sig);
void FetchScreenwriters(unsigned char* sig);

/* Wait for thread. */
void WaitFor(void (*f)(unsigned char*));

/* 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, shorts and 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
{
	unsigned char version = 'a';
	unsigned char pad[3] = {0};
	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 = 4096;
	unsigned short iFocus = 0;
	unsigned short heightDlv = 0;
	wchar_t limitToScreenwriter[48] = {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 <typename T>
constexpr inline unsigned char Version = T().version;

template <typename T, typename = int>
constexpr inline bool HasVersion = false;

template <typename T>
constexpr inline bool HasVersion<T, decltype((void) T::version, 0)> = 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 <typename T>
struct FileView
{
	HANDLE hf;
	HANDLE hm;
	T* view;
	size_t c;

	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<T*>(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 as well as
	 * initialization, if needed. */
	T& At(size_t i)
	{
		if (i >= c)
			throw std::out_of_range("index larger than buffer");

		T& t = view[i];

		if constexpr (HasVersion<T>) {
			if (t.version == 0) {
				T t_;
				memcpy(&t, &t_, sizeof(T));
			} else if (t.version != Version<T>)
				throw std::runtime_error("invalid struct version");
		}

		/* TODO: Use a custom exception type that informs the
		 * user of possible data corruption. */

		return t;
	}

	T* begin() { return *view; }
	T* end() { return view[c-1]; }

	inline operator T*() noexcept
	{
		return view;
	}

	inline T* operator ->() noexcept
	{
		return view;
	}
};

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