aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--c/data.cpp131
-rw-r--r--c/data.h22
-rw-r--r--c/episodelistview.cpp7
-rw-r--r--c/main.cpp55
-rw-r--r--c/test.cpp2
-rw-r--r--c/util.h53
-rw-r--r--c/win.cpp1
-rw-r--r--c/win.h12
8 files changed, 184 insertions, 99 deletions
diff --git a/c/data.cpp b/c/data.cpp
index 7722cd0..86da266 100644
--- a/c/data.cpp
+++ b/c/data.cpp
@@ -1,3 +1,4 @@
+#include <thread>
#include <windows.h>
#include <wininet.h>
#include <libxml/HTMLparser.h>
@@ -5,8 +6,14 @@
#include <libxml/xpath.h>
#include "data.h"
+#include "episodelistview.h"
#include "win.h"
+/* Signals for thread communication. */
+constexpr unsigned char SIG_READY = 1<<0;
+constexpr unsigned char SIG_DONE = 1<<1;
+constexpr unsigned char SIG_ABORT = 1<<2;
+
struct XmlError : public std::exception
{
const char* msg;
@@ -20,14 +27,17 @@ struct XmlError : public std::exception
}
};
+static inline void XmlFree(void* p) { xmlFree(p); }
+
+/* RAII types for WinINet and libxml2. */
+using InternetHandle = Managed<HINTERNET, InternetCloseHandle, InternetError>;
using XmlXPathContextPtr = Managed<xmlXPathContextPtr, xmlXPathFreeContext, XmlError>;
using XmlXPathObjectPtr = Managed<xmlXPathObjectPtr, xmlXPathFreeObject, XmlError>;
-static inline void XmlFree(void* p) { xmlFree(p); }
using XmlCharPtr = Managed<xmlChar*, XmlFree, XmlError>;
-using InternetHandle = Managed<HINTERNET, InternetCloseHandle, InternetError>;
static InternetHandle s_hi = InternetOpen(L"Episode Browser", INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0);
+/* ParsedDoc downloads and parses an HTML document. */
struct ParsedDoc
{
using HtmlParserCtxtPtr = Managed<htmlParserCtxtPtr, xmlFreeParserCtxt, XmlError>;
@@ -83,7 +93,7 @@ bool WcharsFromXmlchars(wchar_t (&dst)[N], XmlCharPtr utf8) noexcept
return MultiByteToWideChar(CP_UTF8, 0, src, cchNarrow, dst, cchWide);
}
-void FetchData()
+void FetchData(unsigned char* sig)
{
/* The remote data is retrieved using WinINet from the
* Detective Conan World wiki. Using libxml2's "push parser",
@@ -94,20 +104,23 @@ void FetchData()
ParsedDoc doc(L"https://www.detectiveconanworld.com/wiki/Anime",
"https://www.detectiveconanworld.com/wiki/Anime");
+
XmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
XmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(
reinterpret_cast<const xmlChar*>("//tr[./td[1] != '' and ./td[3][@style='background:#f2fde9;']]"),
xpathCtx);
xmlNodeSetPtr nodes = xpathObj->nodesetval;
- int cNodes = nodes? nodes->nodeNr: 0;
- if (!cNodes)
+ if (!nodes || !nodes->nodeNr)
throw std::runtime_error("could not find remote episode information");
- for (int i = 0; i < cNodes; i++) {
+ for (int i = 0; i < nodes->nodeNr; i++) {
extern FileView<ElvDataA> g_fvElv;
extern FileView<DlvDataA> g_fvDlv;
+ if (*sig & SIG_ABORT)
+ return;
+
const xmlNodePtr node = nodes->nodeTab[i];
if (xmlChildElementCount(node) != 8)
throw std::runtime_error("unexpected remote data format");
@@ -142,13 +155,105 @@ void FetchData()
}
}
-void WaitFetchData(bool* bDone) noexcept
+void FetchScreenwriters(unsigned char* sig)
+{
+ extern FileView<DlvDataA> g_fvDlv;
+ extern CfgA& g_cfg;
+
+ static int iLast = -1;
+ int iMax = g_cfg.cEp-1;
+
+ /* Find the last fetched screenwriter. */
+ if (iLast == -1)
+ for (size_t i = 0; i < g_fvDlv.c; i++)
+ if (const DlvDataA& d = g_fvDlv[i]; !d.date[0]) {
+ iMax = i-1;
+ break;
+ } else if (d.screenwriter[0])
+ iLast = i;
+
+ FINALLY { Status(L""); };
+
+ /* Fetch screenwriters for the rest of the episodes. */
+ const wchar_t prefix[] = L"https://www.detectiveconanworld.com";
+ wchar_t url[256];
+ Wcscpy(url, prefix);
+ for (iLast++; iLast < iMax; iLast++) {
+ if (*sig & SIG_ABORT)
+ return;
+
+ wchar_t msg[48];
+ Swprintf(msg, L"Fetching screenwriter for episode %d...", iLast+1);
+ Status(msg);
+
+ /* Retrieve URL for episode's wiki page. */
+ DlvDataA& d = g_fvDlv[iLast];
+ Wcscpy(Buf(url)+Len(prefix), d.wiki);
+
+ /* Retrieve screenwriter from HTML. */
+ ParsedDoc doc(url, nullptr);
+ XmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
+ XmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(reinterpret_cast<const xmlChar*>(
+ "//th[contains(text(), 'Screenplay:')]/following-sibling::td"),
+ xpathCtx);
+ xmlNodeSetPtr nodes = xpathObj->nodesetval;
+ if (!nodes || !nodes->nodeNr)
+ continue;
+ xmlChar* s = xmlNodeGetContent(nodes->nodeTab[0]);
+ WcharsFromXmlchars(d.screenwriter, s);
+ }
+}
+
+void WaitFor(void (*f)(unsigned char*))
{
- try {
- FetchData();
- *bDone = true;
- } catch (...) {
- *bDone = true;
- ShowException(L"Remote data could not be fetched due to an error: %s", L"Error", MB_ICONWARNING);
+ static unsigned char sig = SIG_READY;
+ static UINT_PTR iTimer = 0;
+
+ /* The timer procedure animates an ellipsis in the status bar
+ * while the thread is running. */
+ static auto procTimer = [](HWND, UINT, UINT_PTR, DWORD) -> void
+ {
+ static int i = 0;
+ static const wchar_t* text[] = {L".", L"..", L"...", L""};
+
+ if (sig & SIG_DONE) {
+ extern EpisodeListView* const g_elv;
+ KillTimer(nullptr, iTimer);
+ i = 0;
+ sig |= SIG_READY;
+ g_elv->Update(); /* Reset status bar. */
+ } else {
+ i = (i+1)%(sizeof(text)/sizeof(*text));
+ Status(text[i], 1);
+ }
+ };
+
+ static auto procThread = [](void (*f)(unsigned char*)) noexcept -> void
+ {
+ while (!(sig & SIG_READY))
+ Sleep(100);
+ sig &= ~SIG_READY;
+ try {
+ f(&sig);
+ sig |= SIG_DONE;
+ } catch (...) {
+ sig |= SIG_DONE;
+ ShowException(L"Remote data could not be fetched due to an error: %s",
+ L"Error", MB_ICONWARNING);
+ }
+ };
+
+ /* Ensure that only a single thread is waited on. */
+ if (!(sig & SIG_READY)) {
+ if (EBMessageBox(L"Another task is active. "
+ L"Do you want to cancel the existing task and start a new one?",
+ L"Error", MB_YESNO|MB_ICONWARNING) == IDYES)
+ sig |= SIG_ABORT;
+ else
+ return;
}
+
+ std::thread(procThread, f).detach();
+ Status(L".", 1);
+ Prefer(iTimer = SetTimer(nullptr, iTimer, 500, procTimer));
}
diff --git a/c/data.h b/c/data.h
index 14e8920..bf45511 100644
--- a/c/data.h
+++ b/c/data.h
@@ -10,8 +10,11 @@
#include "win.h"
/* Fetch data from the web. */
-void FetchData();
-void WaitFetchData(bool* bDone) noexcept;
+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
@@ -54,7 +57,7 @@ struct CfgA
unsigned short cEp = 4096;
unsigned short iFocus = 0;
unsigned short heightDlv = 0;
- wchar_t limitScreenwriter[64] = {0};
+ wchar_t limitScreenwriter[64] = {0}; /* TODO: 64 -> 48 */
wchar_t root[260] = {0};
wchar_t glob[64] = {0};
wchar_t url[192] = {0};
@@ -76,6 +79,11 @@ constexpr inline bool HasVersion<T, decltype((void) T::version, 0)> = true;
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;
@@ -150,6 +158,9 @@ struct FileView
return t;
}
+ T* begin() { return *view; }
+ T* end() { return view[c-1]; }
+
inline operator T*() noexcept
{
return view;
@@ -159,11 +170,6 @@ struct FileView
{
return view;
}
-
- HANDLE hf;
- HANDLE hm;
- T* view;
- size_t c;
};
inline int FromWeb(const int iEp, ElvDataA& e, DlvDataA& d) noexcept
diff --git a/c/episodelistview.cpp b/c/episodelistview.cpp
index 645c11f..cadd751 100644
--- a/c/episodelistview.cpp
+++ b/c/episodelistview.cpp
@@ -281,7 +281,9 @@ void EpisodeListView::SelectUnwatched(int dir)
iEpNew = lvfi.lParam;
for (;;) {
iEpNew += dir;
- if (iEpNew == 0 || iEpNew == g_cfg.cEp || !g_fvElv[iEpNew-1].siEp[0])
+ if (iEpNew == 0
+ || static_cast<size_t>(iEpNew) > g_fvElv.c
+ || !g_fvElv[iEpNew-1].siEp[0])
return;
if (!g_fvElv[iEpNew-1].bWatched)
break;
@@ -421,10 +423,9 @@ void EpisodeListView::Update()
}
/* Show number of displayed items in status bar. */
- extern HWND g_hWndStatus;
wchar_t disp[16];
Swprintf(disp, L"%d", cItem);
- SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), reinterpret_cast<LPARAM>(disp));
+ Status(disp, 1);
}
Sort();
diff --git a/c/main.cpp b/c/main.cpp
index 771b323..8916860 100644
--- a/c/main.cpp
+++ b/c/main.cpp
@@ -1,6 +1,5 @@
#include <exception>
#include <stdexcept>
-#include <thread>
#include <windows.h>
#include <commctrl.h>
#include <SWI-Prolog.h>
@@ -77,8 +76,6 @@ static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK HandleMsg(HWND, UINT, WPARAM, LPARAM);
/* Process main menu commands. */
static void HandleMainMenu(HWND, WORD);
-/* Wait for thread. */
-void WaitFor(void (*f)(bool*));
/* Handle messages to Help > About dialog. */
static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
/* Try to style application according to current Windows theme. */
@@ -353,7 +350,7 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
const wchar_t** const vTip = group == IDG_MENU? vTipMenu: vTipCtx;
tip = vTip[ID_INDEX(command)];
}
- SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(0,0), reinterpret_cast<LPARAM>(tip));
+ Status(tip, 0);
return 0;
}
@@ -390,12 +387,12 @@ void HandleMainMenu(const HWND hWnd, const WORD command)
case IDM_FILE_FETCH_DATA:
{
- WaitFor(WaitFetchData);
+ WaitFor(FetchData);
break;
}
case IDM_FILE_FETCH_SCREENWRITERS:
- //WaitFor("episode_data","update_screenwriters");
+ WaitFor(FetchScreenwriters);
break;
case IDM_FILE_ABOUT:
@@ -442,52 +439,6 @@ void HandleMainMenu(const HWND hWnd, const WORD command)
}
}
-void WaitFor(void (*f)(bool*))
-{
- static bool bActive = false;
- static bool bDone = false;
- static UINT_PTR iTimer;
-
- /* Ensure that only a single thread is waited on. */
- if (bActive) {
- if (EBMessageBox(L"Another task is active. "
- L"Do you want to cancel the existing task and start a new one?",
- L"Error", MB_YESNO|MB_ICONWARNING) == IDYES) {
- KillTimer(nullptr, iTimer);
- bActive = false;
- g_elv->Update();
- } else
- return;
- }
-
- /* The timer procedure animates an ellipsis in the status bar
- * while the thread is running. */
- static auto proc = [](HWND, UINT, UINT_PTR, DWORD) -> void
- {
- static int i = 0;
- static const wchar_t* text[] = {L".", L"..", L"...", L""};
-
- if (bDone) {
- KillTimer(nullptr, iTimer);
- i = 0;
- bActive = 0;
- g_elv->Update();
- } else {
- i = (i+1)%(sizeof(text)/sizeof(*text));
- SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0),
- reinterpret_cast<LPARAM>(text[i]));
- }
- };
-
- /* The waited-on function signals its completion by setting a
- * shared boolean value to true. */
- bDone = false;
- bActive = true;
- std::thread(f, &bDone).detach();
- SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), reinterpret_cast<LPARAM>(L"."));
- Prefer(iTimer = SetTimer(nullptr, -1, 500, proc));
-}
-
INT_PTR CALLBACK AboutDlgProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM)
{
switch (uMsg) {
diff --git a/c/test.cpp b/c/test.cpp
index e56d7aa..4c7c7ea 100644
--- a/c/test.cpp
+++ b/c/test.cpp
@@ -173,7 +173,7 @@ int RunTests()
//IO(),
//MigrateElvDataFromPrologToDisk(),
//MigrateDlvDataFromPrologToDisk(),
- //MigrateCfg()
+ //MigrateCfg(),
};
printf("Results (%llu tests):\n", sizeof(tests)/sizeof(*tests));
diff --git a/c/util.h b/c/util.h
index 7c2b293..7f704df 100644
--- a/c/util.h
+++ b/c/util.h
@@ -11,17 +11,14 @@
#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define _ CONCAT(unused_, __LINE__)
-inline size_t Min(size_t a, size_t b)
+template <typename F>
+struct Finally
{
- return a < b? a: b;
-}
-
-inline int Cmp(const int a, const int b)
-{
- if (a == b) return 0;
- if (a > b) return 1;
- return -1;
-}
+ F f;
+ inline Finally(F f) : f(f) {}
+ inline ~Finally() { f(); }
+};
+#define FINALLY Finally _ = [=]()
/* Generic RAII type. */
template <typename T, auto F, typename U, auto E = 0>
@@ -45,33 +42,53 @@ template <typename T>
struct Buf
{
T* data;
- size_t size;
- Buf(T* data, size_t size) : data(data), size(size) {}
- Buf(std::basic_string<T>& s) : data(s.data()), size(s.capacity()) {}
- template <size_t N> Buf(T (&data)[N]) : data(data), size(N) {}
+ size_t c;
+ Buf(T* data, size_t c) : data(data), c(c) {}
+ Buf(std::basic_string<T>& s) : data(s.data()), c(s.capacity()) {}
+ template <size_t N> Buf(T (&data)[N]) : data(data), c(N) {}
operator T*() { return data; }
T& operator *() { return *data; }
T& operator [](size_t i) { return data[i]; }
+ Buf<T> operator +(size_t i) { return {data+i, c-i}; }
+ //T operator -(size_t i) { return {data-i, c+i}; }
};
+inline int Cmp(const int a, const int b)
+{
+ if (a == b) return 0;
+ if (a > b) return 1;
+ return -1;
+}
+
+inline size_t Min(size_t a, size_t b)
+{
+ return a < b? a: b;
+}
+
+template <typename T, size_t N>
+inline size_t Len(T (&)[N])
+{
+ return N-1;
+}
+
/* Format wide string. */
template<typename... T>
inline int Swprintf(Buf<wchar_t> buf, const wchar_t* const fmt, T... xs)
{
- return _snwprintf_s(buf, buf.size, _TRUNCATE, fmt, xs...);
+ return _snwprintf_s(buf, buf.c, _TRUNCATE, fmt, xs...);
}
/* Format static narrow string. */
template<typename... T>
inline int Sprintf(Buf<char> buf, const char* const fmt, T... xs)
{
- return _snprintf_s(buf, buf.size, _TRUNCATE, fmt, xs...);
+ return _snprintf_s(buf, buf.c, _TRUNCATE, fmt, xs...);
}
/* Copy to static wide string buffer. */
inline wchar_t* Wcscpy(Buf<wchar_t> dst, const wchar_t* const src)
{
- const size_t len = Min(dst.size, wcslen(src)+1);
+ const size_t len = Min(dst.c, wcslen(src)+1);
memcpy(dst, src, len*sizeof(wchar_t));
dst[len-1] = 0;
return dst;
@@ -80,7 +97,7 @@ inline wchar_t* Wcscpy(Buf<wchar_t> dst, const wchar_t* const src)
/* Copy to static narrow string buffer. */
inline char* Strcpy(Buf<char> dst, const char* const src)
{
- const size_t len = Min(dst.size, strlen(src)+1);
+ const size_t len = Min(dst.c, strlen(src)+1);
memcpy(dst, src, len);
dst[len-1] = 0;
return dst;
diff --git a/c/win.cpp b/c/win.cpp
index e8c429d..ba42508 100644
--- a/c/win.cpp
+++ b/c/win.cpp
@@ -107,7 +107,6 @@ void ShowException(const wchar_t* const fmt, const wchar_t* const title, const U
Swprintf(msg, fmt, what);
EBMessageBox(msg, title, uType);
}
-
}
int GetRelativeCursorPos(const HWND hWnd, POINT* const pt) noexcept
diff --git a/c/win.h b/c/win.h
index 62cb2ac..834aaec 100644
--- a/c/win.h
+++ b/c/win.h
@@ -4,6 +4,7 @@
#include <optional>
#include <string_view>
#include <windows.h>
+#include <commctrl.h>
/* Run given procedure at creation of next window. */
void WithNextWindow(void (*proc)(HWND));
@@ -125,9 +126,8 @@ inline BOOL SetWindowRect(const HWND hWnd, const RECT r)
return SetWindowRect(hWnd, r.left, r.top, r.right, r.bottom);
}
-/* The following functions call uxtheme.dll functions, if uxtheme.dll
- * exists, and otherwise do nothing. */
-
+/* The following two functions call uxtheme.dll functions, if
+ * uxtheme.dll exists, and otherwise do nothing. */
inline BOOL EBIsThemeActive()
{
extern BOOL (*IsThemeActive)();
@@ -141,4 +141,10 @@ inline BOOL EBSetWindowTheme(const HWND hWnd, const wchar_t* const pszSubAppName
return SetWindowTheme? SetWindowTheme(hWnd, pszSubAppName, pszSubIdList): 0;
}
+inline void Status(const wchar_t* msg, unsigned short i = 0)
+{
+ extern HWND g_hWndStatus;
+ SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(i,0), reinterpret_cast<LPARAM>(msg));
+}
+
#endif