From 982ff70193bca89facf898dad9f78fbaf6408219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Wed, 24 Aug 2022 14:07:49 +0200 Subject: Add File > Fetch > Cancel Fetch menu item. --- c/data.cpp | 153 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 87 insertions(+), 66 deletions(-) (limited to 'c/data.cpp') diff --git a/c/data.cpp b/c/data.cpp index da5df7a..d9401c9 100644 --- a/c/data.cpp +++ b/c/data.cpp @@ -5,15 +5,11 @@ #include #include +#include "resource.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; @@ -93,6 +89,80 @@ bool WcharsFromXmlchars(wchar_t (&dst)[N], XmlCharPtr utf8) noexcept return MultiByteToWideChar(CP_UTF8, 0, src, cchNarrow, dst, cchWide); } +/* The Fetch* functions are run in a separate thread via WaitFor. The + * main thread and the fetch thread communicate by setting flags on a + * shared byte. At any given time, only a single fetch thread may be + * performing work. */ + +enum Signal +{ + READY = 1<<0, /* Main -> fetch: start working! */ + DONE = 1<<1, /* Fetch -> main: work is done. */ + ABORT = 1<<2 /* Main -> fetch: exit prematurely! */ +}; + +void WaitFor(void (*f)(unsigned char*)) +{ + extern HWND g_hWnd; + static unsigned char sig = READY; + static UINT_PTR iTimer = 0; + + static auto procTimer = [](HWND, UINT, UINT_PTR, DWORD) -> void + { + static int i = 0; + + if (sig & DONE) { + extern EpisodeListView* const g_elv; + KillTimer(nullptr, iTimer); + i = 0; + sig = READY; /* Reset signals. */ + g_elv->Update(); /* Reset status bar. */ + EnableMenuItem(GetMenu(g_hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED); + } else { + /* Animate ellipsis in status bar. */ + static const wchar_t* text[] = {L".", L"..", L"...", L""}; + i = (i+1)%(sizeof(text)/sizeof(*text)); + Status(text[i], 1); + } + }; + + static auto procThread = [](void (*f)(unsigned char*)) noexcept -> void + { + while (!(sig & READY)) + Sleep(100); + sig = 0; + EnableMenuItem(GetMenu(g_hWnd), IDM_FILE_FETCH_CANCEL, MF_ENABLED); + try { + f(&sig); + sig |= DONE; + } catch (...) { + sig |= DONE; + ShowException(L"Remote data could not be fetched due to an error: %s", + L"Error", MB_ICONWARNING); + } + }; + + /* Null indicates that any active task should be cancelled. */ + if (!f) { + sig |= ABORT; + return; + } + + /* Ensure that only a single thread is waited on. */ + if (!(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 |= ABORT; + else + return; + } + + std::thread(procThread, f).detach(); + Status(L".", 1); + Prefer(iTimer = SetTimer(nullptr, iTimer, 500, procTimer)); +} + void FetchData(unsigned char* sig) { /* The remote data is retrieved using WinINet from the @@ -118,7 +188,7 @@ void FetchData(unsigned char* sig) extern FileView g_fvElv; extern FileView g_fvDlv; - if (*sig & SIG_ABORT) + if (*sig & ABORT) return; const xmlNodePtr node = nodes->nodeTab[i]; @@ -160,10 +230,16 @@ void FetchScreenwriters(unsigned char* sig) extern FileView g_fvDlv; extern CfgA& g_cfg; + /* Screenwriters are expensive to fetch, so we try to avoid + * fetching screenwriters for episodes that already have a + * screenwriter. Additionally, in the same session, we don't + * try to fetch screenwriters for episodes for which we have + * already tried to fetch screenwriters. We keep track of + * these states using the iLast variable. */ static int iLast = -1; int iMax = g_cfg.cEp-1; - /* Find the last fetched screenwriter. */ + /* Find the last episode that has a screenwriter. */ if (iLast == -1) for (size_t i = 0; i < g_fvDlv.c; i++) if (const DlvDataA& d = g_fvDlv[i]; !d.date[0]) { @@ -179,7 +255,7 @@ void FetchScreenwriters(unsigned char* sig) wchar_t url[256]; Wcscpy(url, prefix); for (iLast++; iLast < iMax; iLast++) { - if (*sig & SIG_ABORT) + if (*sig & ABORT) return; wchar_t msg[48]; @@ -197,64 +273,9 @@ void FetchScreenwriters(unsigned char* sig) "//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*)) -{ - 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_DONE; - sig |= SIG_READY; - g_elv->Update(); /* Reset status bar. */ - } else { - i = (i+1)%(sizeof(text)/sizeof(*text)); - Status(text[i], 1); + if (nodes && nodes->nodeNr) { + xmlChar* s = xmlNodeGetContent(nodes->nodeTab[0]); + WcharsFromXmlchars(d.screenwriter, s); } - }; - - 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)); } -- cgit v1.2.3