diff options
-rw-r--r-- | c/data.cpp | 153 | ||||
-rw-r--r-- | c/main.cpp | 5 | ||||
-rw-r--r-- | c/resource.h | 9 | ||||
-rw-r--r-- | c/resource.rc | 1 |
4 files changed, 98 insertions, 70 deletions
@@ -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)); } @@ -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 |