aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2022-08-24 14:07:49 +0200
committerJohn Ankarström <john@ankarstrom.se>2022-08-24 14:07:49 +0200
commit982ff70193bca89facf898dad9f78fbaf6408219 (patch)
tree1a7a1cbc1ab1fd15ef321fe20717ffdb28ef754e
parent4f26641099410779089851009b1e5945a3613f9c (diff)
downloadEpisodeBrowser-982ff70193bca89facf898dad9f78fbaf6408219.tar.gz
Add File > Fetch > Cancel Fetch menu item.
-rw-r--r--c/data.cpp153
-rw-r--r--c/main.cpp5
-rw-r--r--c/resource.h9
-rw-r--r--c/resource.rc1
4 files changed, 98 insertions, 70 deletions
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 <libxml/HTMLtree.h>
#include <libxml/xpath.h>
+#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<ElvDataA> g_fvElv;
extern FileView<DlvDataA> 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<DlvDataA> 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));
}
diff --git a/c/main.cpp b/c/main.cpp
index 5935ee1..244d1fe 100644
--- a/c/main.cpp
+++ b/c/main.cpp
@@ -312,6 +312,7 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
/*IDM_FILE_REFRESH*/L"Quickly refresh episode list.",
/*IDM_FILE_FETCH_DATA*/L"Fetch episode data from the web (may take a few seconds).",
/*IDM_FILE_FETCH_SCREENWRITERS*/L"Fetch screenwriters from the web (may take a minute).",
+ /*IDM_FILE_FETCH_CANCEL*/L"Stop fetching data from the web.",
/*IDM_FILE_ABOUT*/L"Show information about Episode Browser.",
/*IDM_VIEW_WATCHED*/(g_cfg.bViewWatched?
L"Click to hide watched episodes.":
@@ -395,6 +396,10 @@ void HandleMainMenu(const HWND hWnd, const WORD command)
WaitFor(FetchScreenwriters);
break;
+ case IDM_FILE_FETCH_CANCEL:
+ WaitFor(nullptr);
+ break;
+
case IDM_FILE_ABOUT:
DialogBox(
GetModuleHandle(nullptr),
diff --git a/c/resource.h b/c/resource.h
index 3bf23b0..50490bc 100644
--- a/c/resource.h
+++ b/c/resource.h
@@ -31,10 +31,11 @@
#define IDM_FILE_REFRESH 0x0301
#define IDM_FILE_FETCH_DATA 0x0302
#define IDM_FILE_FETCH_SCREENWRITERS 0x0303
-#define IDM_FILE_ABOUT 0x0304
-#define IDM_VIEW_WATCHED 0x0305
-#define IDM_VIEW_TV_ORIGINAL 0x0306
-#define IDM_VIEW_OTHERS 0x0307
+#define IDM_FILE_FETCH_CANCEL 0x0304
+#define IDM_FILE_ABOUT 0x0305
+#define IDM_VIEW_WATCHED 0x0306
+#define IDM_VIEW_TV_ORIGINAL 0x0307
+#define IDM_VIEW_OTHERS 0x0308
/* Context menu items. */
#define IDM_WATCH_LOCALLY 0x0400
diff --git a/c/resource.rc b/c/resource.rc
index 2b7a9a7..acecce4 100644
--- a/c/resource.rc
+++ b/c/resource.rc
@@ -11,6 +11,7 @@ BEGIN
BEGIN
MENUITEM "&General Data", IDM_FILE_FETCH_DATA
MENUITEM "&Screenwriters", IDM_FILE_FETCH_SCREENWRITERS
+ MENUITEM "&Cancel Fetch", IDM_FILE_FETCH_CANCEL, GRAYED
END
MENUITEM "&Refresh", IDM_FILE_REFRESH
MENUITEM "E&xit", IDM_FILE_EXIT