aboutsummaryrefslogtreecommitdiff
path: root/c/data.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'c/data.cpp')
-rw-r--r--c/data.cpp131
1 files changed, 118 insertions, 13 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));
}