From 6fd66a9264731bd7ee6d7602675965021d929a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Wed, 24 Aug 2022 15:17:08 +0200 Subject: Remove Prolog dependency. The only thing left to reimplement is the tracking of watched episodes in MPC-HC. --- Makefile | 44 +++++++----- README | 78 ++++++++++---------- c/data.h | 52 -------------- c/datalistview.cpp | 2 - c/episodelistview.cpp | 8 +-- c/main.cpp | 14 +--- c/pl.cpp | 93 ------------------------ c/pl.h | 124 -------------------------------- c/test.cpp | 64 ++--------------- c/util.h | 1 - c/wcharptr.cpp | 64 ----------------- c/wcharptr.h | 33 --------- c/win.cpp | 25 ++++--- c/win.h | 3 + pl/atom_dcg.pl | 82 --------------------- pl/cfg.pl | 76 -------------------- pl/episode_data.pl | 196 -------------------------------------------------- pl/local_episodes.pl | 50 ------------- pl/track_episodes.pl | 112 ----------------------------- 19 files changed, 91 insertions(+), 1030 deletions(-) delete mode 100644 c/pl.cpp delete mode 100644 c/pl.h delete mode 100644 c/wcharptr.cpp delete mode 100644 c/wcharptr.h delete mode 100644 pl/atom_dcg.pl delete mode 100644 pl/cfg.pl delete mode 100644 pl/episode_data.pl delete mode 100644 pl/local_episodes.pl delete mode 100644 pl/track_episodes.pl diff --git a/Makefile b/Makefile index dcdbafe..610e81c 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,25 @@ EXE = EpisodeBrowser.exe -OBJ = b/datalistview.obj b/debug.obj b/data.obj b/episodelistview.obj b/ext.obj b/wcharptr.obj -OBJ += b/win.obj b/layout.obj b/listview.obj b/pl.obj b/test.obj b/resource.obj -PL = pl/cfg.pl pl/episode_data.pl pl/local_episodes.pl pl/track_episodes.pl - -CL = swipl-ld -CLFLAGS = -DUNICODE -D_UNICODE -CLFLAGS += -DDEBUG -CLFLAGS += -cc-options,-std=c++17 -O0 -CLFLAGS += -ld-options,-mwindows,-static-libstdc++ -lcomctl32 -lwininet -llibxml2 -CLFLAGS += -IC:/msys64/mingw64/include/libxml2 -CLFLAGS += -Wall -Wextra -Wpedantic -Wno-missing-field-initializers -Wno-parentheses + +OBJ = b/datalistview.obj +OBJ += b/data.obj +OBJ += b/debug.obj +OBJ += b/episodelistview.obj +OBJ += b/ext.obj +OBJ += b/layout.obj +OBJ += b/listview.obj +OBJ += b/main.obj +OBJ += b/resource.obj +OBJ += b/test.obj +OBJ += b/wcharptr.obj +OBJ += b/win.obj + +CC = gcc +CFLAGS = -DUNICODE -D_UNICODE +CFLAGS += -DDEBUG +CFLAGS += -std=c++17 -mwindows -O0 +CFLAGS += -IC:/msys64/mingw64/include/libxml2 +CFLAGS += -Wall -Wextra -Wpedantic -Wno-missing-field-initializers -Wno-parentheses +LDFLAGS += -lstdc++ -lcomctl32 -lwininet -llibxml2 all: showdeps b/$(EXE) cp b/$(EXE) "C:\Users\John\Desktop\Delat" @@ -17,20 +27,20 @@ all: showdeps b/$(EXE) clean: rm -fr b/$(EXE) b/*.obj -TAGS: c/*.cpp c/*.h pl/*.pl - etags --declarations -lc++ c/*.cpp c/*.h -lprolog pl/*.pl +TAGS: c/*.cpp c/*.h + etags --declarations -lc++ c/*.cpp c/*.h -b/$(EXE): Makefile c/main.cpp $(OBJ) $(PL) - $(CL) $(CLFLAGS) -goal true -o $@ c/main.cpp $(OBJ) $(PL) +b/$(EXE): Makefile $(OBJ) + $(CC) $(CFLAGS) -o $@ $(OBJ) $(LDFLAGS) b/resource.obj: c/resource.h c/resource.rc c/application.manifest windres -i c/resource.rc -o b/resource.obj b/%.obj: c/%.cpp - $(CL) -c $(CLFLAGS) -o $@ $< + $(CC) -c $(CFLAGS) -o $@ $< s/%.s: c/%.cpp - $(CL) $(CLFLAGS) -cc-options,-fverbose-asm,-masm=intel -S -o $@ $< + $(CC) $(CFLAGS) -fverbose-asm -masm=intel -S -o $@ $< # showdeps prints a short summary of which dependencies have changed, # causing which targets to be rebuilt. It is run by the `all' target diff --git a/README b/README index 1be0d2c..62e0a71 100644 --- a/README +++ b/README @@ -11,50 +11,52 @@ fetching episode information from the internet. In its current form, it is designed specifically for Detective Conan. -Backend -~~~~~~~ -The fundamental features of Episode Browser, such as detecing and -keeping track of local episodes, fetching information from the -internet and interacting with the MPC-HC media player, are implemented -in Prolog (see the pl folder). - -At the time of writing, the backend implementation is tightly coupled -to the author's specific system. To be used for anything else than -episodes of Detective Conan stored in a specific folder on the hard -drive, the predicates defined in local_episodes.pl must be modified -and the program recompiled. To fetch episode information from other -sources than Detective Conan World, the episode_data.pl file must be -modified. - -Frontend -~~~~~~~~ -The graphical interface is implemented in C++ using the Win32 API (see -the c folder). The source code is spread across a small number of -files. In summary: - -main.cpp Entry point and initialization. Contains the main event handler. -listview.* Creates and handles the shared aspects of the two list views. -episodelistview.* Defines the interface to the episode list view. -datalistview.* Defines the interface to the data list view. -layout.* Handles the placement of child windows. -pl.* Interface to Prolog backend. -win.* Helpers for interacting with the Win32 API. -wcharptr.* Defines WcharPtr, a class that owns a wchar_t*. -util.h Simple utility functions. -debug.* Helpers for debugging. - -The code operates under the assumption that "raw" pointers are -non-owning -- i.e., their lifetime is managed by somebody else, such -as Windows or Prolog. When strings need to be heap-allocated, like -when converting from a narrow to a wide string, they are put within a -WcharPtr object, which manages the lifetime of the string. +Implementation +~~~~~~~~~~~~~~ +Episode Browser is implemented in C++ using the Win32 API (see the c +folder). The source code is spread across a number of files: + +>> Frontend << + +main.cpp + Entry point and initialization. Contains the main event handler. +listview.cpp,h + Defines the shared aspects of the two list views. +episodelistview.cpp,h + Defines the interface to the episode list view. +datalistview.cpp,h + Defines the interface to the data list view. +layout.cpp,h + Handles the placement of child windows. +resource.rc + Defines menu and dialog window resources. +resource.h + Defines identifiers and macros for resources. + +>> Backend << + +ext.cpp,h + Interacts with the external world (to open an episode). +data.cpp,h + Defines the underlying data representation, as well as + functions to retrieve data from Detective Conan World. + +>> Miscellanea << + +win.cpp,h + Helpers for interacting with the Win32 API. +util.h + Utility functions. +test.cpp,h + Simple test framework (work in progress). +debug.cpp,h + Helpers for debugging. Building ~~~~~~~~ Episode Browser is built with GNU Make. For the build process, the following additional programs are required: - - swipl-ld (included with SWI-Prolog) - GCC/G++ - Perl diff --git a/c/data.h b/c/data.h index b974cec..1216359 100644 --- a/c/data.h +++ b/c/data.h @@ -4,9 +4,7 @@ #include #include -#include "pl.h" #include "util.h" -#include "wcharptr.h" #include "win.h" /* Fetch data from the web. */ @@ -172,54 +170,4 @@ struct FileView } }; -inline int FromWeb(const int iEp, ElvDataA& e, DlvDataA& d) noexcept -{ - WcharPtr title, wiki, date, source, hint; - const int r = Pl("episode_data","fetch_episode_data",iEp,&title,&wiki,&date,&source,&hint); - if (title) Wcscpy(e.title, title); - if (wiki) Wcscpy(d.wiki, wiki); - if (date) Wcscpy(d.date, date); - if (source) Wcscpy(d.source, source); - if (hint) Wcscpy(d.hint, hint); - return r; -} - -inline bool FromProlog(const int iEp, ElvDataA& e) noexcept -{ - if (WcharPtr title; Pl("episode_data","episode_title",iEp,&title)) - Wcscpy(e.title, title); - else - return false; - - int rating; - if (Pl("episode_data","episode_rating",iEp,&rating)) { - e.rating = rating; - Swprintf(e.sRating, L"%d", e.rating); - } - - if (Pl("episode_data","tv_original",iEp)) - e.bTVOriginal = true; - - if (Pl("track_episodes","watched",iEp)) - e.bWatched = true; - - Swprintf(e.siEp, L"%d", iEp); - - return true; -} - -inline void FromProlog(const int iEp, DlvDataA& d) noexcept -{ - if (WcharPtr wiki; Pl("episode_data","episode_wiki",iEp,&wiki)) - Wcscpy(d.wiki, wiki); - if (WcharPtr screenwriter; Pl("episode_data","episode_datum",iEp,"Screenwriter",&screenwriter)) - Wcscpy(d.screenwriter, screenwriter); - if (WcharPtr date; Pl("episode_data","episode_datum",iEp,"Date",&date)) - Wcscpy(d.date, date); - if (WcharPtr source; Pl("episode_data","episode_datum",iEp,"Source",&source)) - Wcscpy(d.source, source); - if (WcharPtr hint; Pl("episode_data","episode_datum",iEp,"Hint",&hint)) - Wcscpy(d.hint, hint); -} - #endif diff --git a/c/datalistview.cpp b/c/datalistview.cpp index a71a270..ec9592d 100644 --- a/c/datalistview.cpp +++ b/c/datalistview.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "resource.h" #include "data.h" @@ -10,7 +9,6 @@ #include "episodelistview.h" #include "listview.h" #include "layout.h" -#include "wcharptr.h" extern CfgA& g_cfg; extern FileView g_fvDlv; diff --git a/c/episodelistview.cpp b/c/episodelistview.cpp index a76717a..72a8618 100644 --- a/c/episodelistview.cpp +++ b/c/episodelistview.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "resource.h" #include "data.h" @@ -10,7 +9,6 @@ #include "episodelistview.h" #include "ext.h" #include "listview.h" -#include "pl.h" #include "util.h" #include "win.h" @@ -358,9 +356,6 @@ int CALLBACK EpisodeListView::SortProc(const LPARAM iItem1, const LPARAM iItem2, void EpisodeListView::Update() { - if (!Pl("episode_data","ensure_episode_data")) - return; - /* Save scrolling position. */ int iEpTop = 0; { @@ -371,9 +366,8 @@ void EpisodeListView::Update() /* Save selected episodes. */ int iItemMark; - static std::vector vEpSel; + std::vector vEpSel; { - vEpSel.clear(); LVITEM lvi = {LVIF_PARAM, -1}; while (FindNextItem(&lvi, LVNI_SELECTED)) vEpSel.push_back(lvi.lParam); diff --git a/c/main.cpp b/c/main.cpp index 244d1fe..9919200 100644 --- a/c/main.cpp +++ b/c/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "debug.h" @@ -10,7 +9,6 @@ #include "datalistview.h" #include "episodelistview.h" #include "layout.h" -#include "pl.h" #include "test.h" #include "util.h" @@ -86,13 +84,6 @@ int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, char* const, cons setbuf(stdout, nullptr); LIBXML_TEST_VERSION; - /* Initialize Prolog. */ - const char* argv[] = {"EpisodeBrowser", nullptr}; - if (!PL_initialise(1, const_cast(argv))) - throw std::runtime_error("Could not initialize Prolog."); - if (!Pl("track_episodes","attach") || !Pl("episode_data","attach")) - throw std::runtime_error("Could not attach databases."); - INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(icc); icc.dwICC = ICC_WIN95_CLASSES; @@ -134,7 +125,7 @@ int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, char* const, cons ShowWindow(hWnd, nCmdShow); /* Populate episode list view. */ - Pl("track_episodes","update_tracked_episodes"); + /* TODO: Update tracked episodes. */ g_elv->Update(); g_elv->RestoreFocus(); @@ -149,7 +140,6 @@ int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, char* const, cons DispatchMessage(&msg); } - PL_halt(0); return 0; } @@ -274,7 +264,7 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam g_hWndFocus = GetFocus(); else { SetFocus(g_hWndFocus); - Pl("track_episodes","update_tracked_episodes"); + /* TODO: Update tracked episodes. */ g_elv->Redraw(); } return 0; diff --git a/c/pl.cpp b/c/pl.cpp deleted file mode 100644 index bab2f31..0000000 --- a/c/pl.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include - -#include "pl.h" - -Frame::Frame() -{ - m_f = PL_open_foreign_frame(); -} - -Frame::~Frame() -{ - PL_close_foreign_frame(m_f); -} - -void Frame::Close() -{ - PL_close_foreign_frame(m_f); -} - -void Frame::Discard() -{ - PL_discard_foreign_frame(m_f); -} - -void Frame::Rewind() -{ - PL_rewind_foreign_frame(m_f); -} - -Mark::Mark() -{ - PL_mark_string_buffers(&m_m); -} - -Mark::~Mark() -{ - PL_release_string_buffers_from_mark(m_m); -} - -Query::Query(const module_t ctx, const predicate_t p, const term_t t0) -{ - m_q = PL_open_query(ctx, PL_Q_CATCH_EXCEPTION, p, t0); -} - -Query::~Query() -{ - PL_cut_query(m_q); -} - -int Query::Cut(std::nothrow_t) -{ - return PL_cut_query(m_q); -} -int Query::Cut() -{ - if (PL_cut_query(m_q)) return 1; - if (const term_t t = PL_exception(m_q)) throw t; - return 0; -} - -int Query::Close(std::nothrow_t) -{ - return PL_close_query(m_q); -} - -int Query::Close() -{ - if (PL_close_query(m_q)) return 1; - if (const term_t t = PL_exception(m_q)) throw t; - return 0; -} - -int Query::NextSolution(std::nothrow_t) -{ - return PL_next_solution(m_q); -} - -int Query::NextSolution() -{ - if (PL_next_solution(m_q)) return 1; - if (const term_t t = PL_exception(m_q)) throw t; - return 0; -} - -WcharPtr PlString(const term_t t, const int flags) -{ - char* s; - if (PL_get_chars(t, &s, flags)) - return {WcharPtr::FromNarrow(s)}; - else - return {}; -} diff --git a/c/pl.h b/c/pl.h deleted file mode 100644 index 2992e86..0000000 --- a/c/pl.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef PL_H -#define PL_H - -#include -#include -#include - -#include "wcharptr.h" - -/* Convert Prolog term to wide characters. */ -WcharPtr PlString(const term_t t, const int flags = CVT_WRITE); - -/* Wrapper for opening and closing Prolog foreign frames. */ -struct Frame -{ - Frame(); - ~Frame(); - void Close(); - /* Close foreign frame AND invalidate associated data. */ - void Discard(); - void Rewind(); -private: - fid_t m_f; -}; - -/* Wrapper for marking and releasing strings on the Prolog stack. */ -struct Mark -{ - Mark(); - ~Mark(); -private: - buf_mark_t m_m; -}; - -/* Wrapper for opening, solving and cutting Prolog query. */ -struct Query -{ - Query(module_t ctx, predicate_t p, term_t t0); - ~Query(); - int Cut(); - int Cut(std::nothrow_t); - /* Cut query AND invalidate associated data. */ - int Close(); - int Close(std::nothrow_t); - int NextSolution(); - int NextSolution(std::nothrow_t); -private: - qid_t m_q; -}; - -/* Polymorphic aliases for PL_put_*, PL_get_*. */ -inline int PlPut(term_t t, int x) { return PL_put_integer(t, x); } -inline int PlPut(term_t t, long x) { return PL_put_integer(t, x); } -inline int PlPut(term_t t, long long x) { return PL_put_int64(t, x); } -inline int PlPut(term_t t, atom_t x) { return PL_put_atom(t, x); } -inline int PlPut(term_t t, char* x) { return PL_put_atom_chars(t, x); } -inline int PlPut(term_t t, const char* x) { return PL_put_atom_chars(t, x); } -inline int PlPut(term_t, int*) { return -1; } -inline int PlPut(term_t, long*) { return -1; } -inline int PlPut(term_t, long long*) { return -1; } -inline int PlPut(term_t, atom_t*) { return -1; } -inline int PlPut(term_t, char**) { return -1; } -inline int PlPut(term_t, WcharPtr*) { return -1; } - -inline int PlGet(term_t, int) { return -1; } -inline int PlGet(term_t, long) { return -1; } -inline int PlGet(term_t, long long) { return -1; } -inline int PlGet(term_t, atom_t) { return -1; } -inline int PlGet(term_t, char*) { return -1; } -inline int PlGet(term_t, const char*) { return -1; } -inline int PlGet(term_t t, int* x) { return PL_get_integer(t, x); } -inline int PlGet(term_t t, long* x) { return PL_get_long(t, x); } -inline int PlGet(term_t t, long long* x) { return PL_get_int64(t, x); } -inline int PlGet(term_t t, atom_t* x) { return PL_get_atom(t, x); } -inline int PlGet(term_t t, char** x) { return PL_get_atom_chars(t, x); } -inline int PlGet(term_t t, WcharPtr* x) -{ - Mark m; - char* s; - if (!PlGet(t, &s)) return 0; - *x = WcharPtr::FromNarrow(s); - return 1; -} - -/* Put in or get from a term reference an arbitrary number of values, - * returning false if any value could not be put/got. */ -template -inline bool PlPutN(term_t) { return true; } -template -inline bool PlPutN(term_t t, T x, U... xs) { return PlPut(t, x) && PlPutN(t+1, xs...); } - -template -inline bool PlGetN(term_t) { return true; } -template -inline bool PlGetN(term_t t, T x, U... xs) { return PlGet(t, x) && PlGetN(t+1, xs...); } - -/* Call Prolog predicate. */ -template -int Pl(const char* const mod, const char* const pred, T... xs) -{ - Frame f; - const term_t t = PL_new_term_refs(sizeof...(T)); - - if (!PlPutN(t, xs...)) return 0; - Query q(nullptr, PL_predicate(pred, sizeof...(T), mod), t); - - if constexpr (Except) { - if (!q.NextSolution()) return 0; - } else { - if (!q.NextSolution(std::nothrow)) return 0; - } - - if (!PlGetN(t, xs...)) return 0; - return 1; -} - -/* Call Prolog predicate, propagating Prolog exceptions. */ -template -int Plx(const char* const mod, const char* const pred, T... xs) -{ - return Pl(mod, pred, xs...); -} - -#endif diff --git a/c/test.cpp b/c/test.cpp index 0f59af4..6e291c7 100644 --- a/c/test.cpp +++ b/c/test.cpp @@ -4,7 +4,6 @@ #include "data.h" #include "episodelistview.h" #include "ext.h" -#include "pl.h" #include "util.h" #include "win.h" @@ -36,9 +35,9 @@ TESTS TEST(IO) { - ElvDataA e1_0, e2_0; - FromProlog(6, e1_0); - FromProlog(10, e2_0); + extern FileView g_fvElv; + ElvDataA& e1_0 = g_fvElv.At(5); + ElvDataA& e2_0 = g_fvElv.At(9); /* Write two ElvDataA structs to disk. */ { @@ -89,63 +88,10 @@ TESTS //DeleteFile(L"tmp.dat"); } - TEST(MigrateElvDataFromPrologToDisk) - { - int cEp; - if (!Pl("episode_data","episode_count",&cEp)) - return; - - { - FileView fv(L"tmp.dat", g_cfg.cEp+128u); - ElvDataA* p = fv; - - for (int iEp = 1; iEp <= cEp; iEp++) { - ElvDataA e; - FromProlog(iEp, e); - memcpy(p, &e, sizeof(e)); - p++; - } - } - { - FileView fv(L"tmp.dat", g_cfg.cEp+128u); - ElvDataA& e = fv.At(9); - if (wcscmp(e.title, L"Pro Soccer Player Blackmail Case") != 0) - FAIL("title is not correct"); - } - //DeleteFile(L"tmp.dat"); - } - - TEST(MigrateDlvDataFromPrologToDisk) - { - int cEp; - if (!Pl("episode_data","episode_count",&cEp)) - return; - - { - FileView fv(L"tmp.dat", g_cfg.cEp+128u); - DlvDataA* p = fv; - - for (int iEp = 1; iEp <= cEp; iEp++) { - DlvDataA d; - FromProlog(iEp, d); - memcpy(p, &d, sizeof(d)); - p++; - } - } - { - FileView fv(L"tmp.dat", g_cfg.cEp+128u); - DlvDataA& e = fv.At(9); - if (wcscmp(e.date, L"March 11, 1996") != 0) - FAIL("date is not correct"); - } - //DeleteFile(L"tmp.dat"); - } - // TEST(MigrateCfg) // { -// FileView fva(L"cfga.dat", 1); // FileView fvb = FileView::Initialized(L"cfgb.dat", 1); -// CfgA* a = fva+0; +// CfgA* a = &g_cfg; // CfgB* b = fvb+0; // #define CPY(member) b->member = a->member; @@ -172,8 +118,6 @@ int RunTests() const Test tests[] = { StrcpyWithSmallerDestination(), //IO(), - //MigrateElvDataFromPrologToDisk(), - //MigrateDlvDataFromPrologToDisk(), //MigrateCfg(), }; diff --git a/c/util.h b/c/util.h index 7f704df..729e64d 100644 --- a/c/util.h +++ b/c/util.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #define CONCAT_IMPL(a, b) a##b diff --git a/c/wcharptr.cpp b/c/wcharptr.cpp deleted file mode 100644 index f659767..0000000 --- a/c/wcharptr.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include - -#include "wcharptr.h" -#include "win.h" - -WcharPtr WcharPtr::FromNarrow(const char* const src, const int cp) -{ - int cchNarrow = strlen(src)+1; - int cchWide = MultiByteToWideChar(cp, 0, src, cchNarrow, nullptr, 0); - wchar_t* dst = new wchar_t[cchWide]; - if (!MultiByteToWideChar(cp, 0, src, cchNarrow, dst, cchWide)) { - delete dst; - throw Win32Error(); - } - return dst; -} - -WcharPtr WcharPtr::Copy(const wchar_t* const src) -{ - const int cch = wcslen(src)+1; - wchar_t* dst = new wchar_t[cch]; - memcpy(dst, src, cch*sizeof(wchar_t)); - return dst; -} - -WcharPtr::WcharPtr() noexcept {} - -WcharPtr::~WcharPtr() noexcept -{ - delete m_p; -} - -WcharPtr::operator wchar_t*() noexcept -{ - return m_p; -} - -WcharPtr::WcharPtr(wchar_t* const s) noexcept : m_p(s) {} - -WcharPtr& WcharPtr::operator=(wchar_t* const s) noexcept -{ - if (m_p != s) { - delete m_p; - m_p = s; - } - return *this; -} - -WcharPtr::WcharPtr(WcharPtr&& other) noexcept - : m_p(std::exchange(other.m_p, nullptr)) {} - -WcharPtr& WcharPtr::operator=(WcharPtr&& other) noexcept -{ - std::swap(m_p, other.m_p); - return *this; -} - -wchar_t* WcharPtr::Release() noexcept -{ - wchar_t* const p = m_p; - m_p = nullptr; - return p; -} diff --git a/c/wcharptr.h b/c/wcharptr.h deleted file mode 100644 index 2267017..0000000 --- a/c/wcharptr.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef WCHARPTR_H -#define WCHARPTR_H - -#include - -/* WcharPtr: Simple wrapper for wide C strings. */ -struct WcharPtr -{ - /* Named copy constructors (expensive). */ - static WcharPtr FromNarrow(const char* buf, int cp = CP_UTF8); - static WcharPtr Copy(const wchar_t* s); - - /* Non-explicit copies are disabled. */ - WcharPtr(WcharPtr& other) = delete; - WcharPtr& operator=(WcharPtr& other) = delete; - - WcharPtr() noexcept; - ~WcharPtr(); - operator wchar_t*() noexcept; - - WcharPtr(wchar_t* s) noexcept; - WcharPtr& operator=(wchar_t* s) noexcept; - - WcharPtr(WcharPtr&& other) noexcept; - WcharPtr& operator=(WcharPtr&& other) noexcept; - - /* Return pointer, releasing ownership. */ - wchar_t *Release() noexcept; -private: - wchar_t* m_p = nullptr; -}; - -#endif diff --git a/c/win.cpp b/c/win.cpp index ba42508..43744dd 100644 --- a/c/win.cpp +++ b/c/win.cpp @@ -1,13 +1,21 @@ #include #include -#include +#include #include #include -#include "pl.h" #include "util.h" #include "win.h" -#include "wcharptr.h" + +std::wstring WideFromNarrow(const std::string_view src, const int cp) +{ + int cchNarrow = src.length()+1; + int cchWide = MultiByteToWideChar(cp, 0, src.data(), cchNarrow, nullptr, 0); + std::wstring dst(cchWide, 0); + if (!MultiByteToWideChar(cp, 0, src.data(), cchNarrow, dst.data(), cchWide)) + throw Win32Error(); + return dst; +} void WithNextWindow(void (*proc)(HWND)) { @@ -87,19 +95,14 @@ void ShowException(const wchar_t* const fmt, const wchar_t* const title, const U { try { std::rethrow_exception(std::current_exception()); - } catch (const term_t& t) { - WcharPtr what = PlString(t); - std::wstring msg(wcslen(fmt)+wcslen(what), 0); - Swprintf(msg, fmt, static_cast(what)); - EBMessageBox(msg, title, uType); } catch (const WideException& e) { std::wstring msg(wcslen(fmt)+wcslen(e.What()), 0); Swprintf(msg, fmt, e.What()); EBMessageBox(msg, title, uType); } catch (const std::exception& e) { - auto what = WcharPtr::FromNarrow(e.what()); - std::wstring msg(wcslen(fmt)+wcslen(what), 0); - Swprintf(msg, fmt, static_cast(what)); + std::wstring what = WideFromNarrow(e.what()); + std::wstring msg(wcslen(fmt)+what.length(), 0); + Swprintf(msg, fmt, what.c_str()); EBMessageBox(msg, title, uType); } catch (...) { const wchar_t* what = L"an unknown error occurred"; diff --git a/c/win.h b/c/win.h index 834aaec..8329f68 100644 --- a/c/win.h +++ b/c/win.h @@ -6,6 +6,9 @@ #include #include +/* Convert narrow to wide string. */ +std::wstring WideFromNarrow(const std::string_view src, const int cp = CP_UTF8); + /* Run given procedure at creation of next window. */ void WithNextWindow(void (*proc)(HWND)); diff --git a/pl/atom_dcg.pl b/pl/atom_dcg.pl deleted file mode 100644 index c5b9bfc..0000000 --- a/pl/atom_dcg.pl +++ /dev/null @@ -1,82 +0,0 @@ -:- module(atom_dcg, [compound_atom_codes/2]). - -% with_atoms/1-9 automatically convert atoms for the caller to codes -% for the callee, primarily to make atoms easier to use with -% definite-clause grammars. atom_phrase/2 is defined as a shortcut. - -% with_codes/1-9, conversely, convert codes for the caller to atoms -% for the callee. - -:- meta_predicate user:with_atoms(0). -:- meta_predicate user:with_atoms(0,0). -:- meta_predicate user:with_atoms(0,0,0). -:- meta_predicate user:with_atoms(0,0,0,0). -:- meta_predicate user:with_atoms(0,0,0,0,0). -:- meta_predicate user:with_atoms(0,0,0,0,0,0). -:- meta_predicate user:with_atoms(0,0,0,0,0,0,0). -:- meta_predicate user:with_atoms(0,0,0,0,0,0,0,0). -:- meta_predicate user:with_atoms(0,0,0,0,0,0,0,0,0). -user:with_atoms(A) :- atom_dcg:compound_atom_codes(A, A1), call(A1). -user:with_atoms(A, B) :- maplist(user:with_atoms, [A,B]). -user:with_atoms(A, B, C) :- maplist(user:with_atoms, [A,B,C]). -user:with_atoms(A, B, C, D) :- maplist(user:with_atoms, [A,B,C,D]). -user:with_atoms(A, B, C, D, E) :- maplist(user:with_atoms, [A,B,C,D,E]). -user:with_atoms(A, B, C, D, E, F) :- maplist(user:with_atoms, [A,B,C,D,E,F]). -user:with_atoms(A, B, C, D, E, F, G) :- maplist(user:with_atoms, [A,B,C,D,E,F,G]). -user:with_atoms(A, B, C, D, E, F, G, H) :- maplist(user:with_atoms, [A,B,C,D,E,F,G,H]). -user:with_atoms(A, B, C, D, E, F, G, H, I) :- maplist(user:with_atoms, [A,B,C,D,E,F,G,H,I]). - -:- meta_predicate user:with_codes(0). -:- meta_predicate user:with_codes(0,0). -:- meta_predicate user:with_codes(0,0,0). -:- meta_predicate user:with_codes(0,0,0,0). -:- meta_predicate user:with_codes(0,0,0,0,0). -:- meta_predicate user:with_codes(0,0,0,0,0,0). -:- meta_predicate user:with_codes(0,0,0,0,0,0,0). -:- meta_predicate user:with_codes(0,0,0,0,0,0,0,0). -:- meta_predicate user:with_codes(0,0,0,0,0,0,0,0,0). -user:with_codes(A) :- atom_dcg:compound_atom_codes(A1, A), call(A1). -user:with_codes(A, B) :- maplist(user:with_codes, [A,B]). -user:with_codes(A, B, C) :- maplist(user:with_codes, [A,B,C]). -user:with_codes(A, B, C, D) :- maplist(user:with_codes, [A,B,C,D]). -user:with_codes(A, B, C, D, E) :- maplist(user:with_codes, [A,B,C,D,E]). -user:with_codes(A, B, C, D, E, F) :- maplist(user:with_codes, [A,B,C,D,E,F]). -user:with_codes(A, B, C, D, E, F, G) :- maplist(user:with_codes, [A,B,C,D,E,F,G]). -user:with_codes(A, B, C, D, E, F, G, H) :- maplist(user:with_codes, [A,B,C,D,E,F,G,H]). -user:with_codes(A, B, C, D, E, F, G, H, I) :- maplist(user:with_codes, [A,B,C,D,E,F,G,H,I]). - -:- meta_predicate user:atom_phrase(2, ?). -user:atom_phrase(G, A) :- - atom_dcg:compound_atom_codes(G, G1), - ( var(A) - -> phrase(G1, C), - atom_codes(A, C) - ; atom_codes(A, C), - phrase(G1, C) - ). - -compound_atom_codes(Module:G, Module:G1) :- !, compound_atom_codes(G, G1). -compound_atom_codes(G, G1) :- - ( nonvar(G) - -> G =.. [F|Args0], - maplist(maybe_atom_codes, Args0, Args), - G1 =.. [F|Args] - ; G1 =.. [F|Args], - maplist(maybe_atom_codes, Args0, Args), - G =.. [F|Args0] - ). - -maybe_atom_codes(A, C) :- - (compound(A) ; compound(C)), !, - compound_atom_codes(A, C). - -maybe_atom_codes(A, C) :- - nonvar(A), !, - ( atom(A) - -> atom_codes(A, C) - ; C = A - ). - -maybe_atom_codes(A, C) :- - var(A), !, - when((ground(A) ; ground(C)), catch(atom_codes(A, C), _, A = C)). diff --git a/pl/cfg.pl b/pl/cfg.pl deleted file mode 100644 index 297d426..0000000 --- a/pl/cfg.pl +++ /dev/null @@ -1,76 +0,0 @@ -:- module(cfg, [set_glob/1, - get_glob/1, - set_root/1, - get_root/1, - set_url/1, - get_url/1]). - -:- use_module(library(registry)). - -set_key(Key, Value) :- - registry_set_key(current_user/software/'JohnAJ'/'EpisodeBrowser'/'Settings', Key, Value). -get_key(Key, Value) :- - catch(registry_get_key(current_user/software/'JohnAJ'/'EpisodeBrowser'/'Settings', Key, Value), - _, - fail). - -set_root(V) :- set_key('EpisodeRoot', V). -get_root(V) :- - get_key('EpisodeRoot', V0), - re_replace('\\\\', '/', V0, V1), - atom_string(V, V1). - -set_glob(V) :- set_key('EpisodeGlob', V). -get_glob(V) :- - ( get_key('EpisodeGlob', V), ! - ; V = '*/*.*' - ). - -set_url(V) :- set_key('UrlPrefix', V). -get_url(V) :- - ( get_key('UrlPrefix', V), ! - ; V = 'https://animixplay.to/v1/detective-conan/ep' - ). - -set_view_watched(V) :- set_key('ViewWatched', V). -get_view_watched(V) :- - ( get_key('ViewWatched', V), - integer(V), ! - ; V = 1 - ). - -set_view_tv_original(V) :- set_key('ViewTVOriginal', V). -get_view_tv_original(V) :- - ( get_key('ViewTVOriginal', V), - integer(V), ! - ; V = 1 - ). - -set_limit_screenwriter(V) :- set_key('LimitScreenwriter', V). -get_limit_screenwriter(V) :- - ( get_key('LimitScreenwriter', V), - atom(V), ! - ; V = "" - ). - -set_sort(V) :- W is V + 2147483647, set_key('Sort', W). -get_sort(V) :- - ( get_key('Sort', W), - integer(W), !, - V is W - 2147483647 - ; V = 1 - ). - -set_focus(V) :- set_key('Focus', V). -get_focus(V) :- - ( get_key('Focus', V), - integer(V), ! - ; V = 1 - ). - -set_dlv_height(V) :- set_key('DlvHeight', V). -get_dlv_height(V) :- - ( get_key('DlvHeight', V), - integer(V), ! - ; V = 0 - ). diff --git a/pl/episode_data.pl b/pl/episode_data.pl deleted file mode 100644 index 5b83546..0000000 --- a/pl/episode_data.pl +++ /dev/null @@ -1,196 +0,0 @@ -:- module(episode_data, [ensure_episode_data/0, - update_episode_data/0, - update_screenwriters/0, - retract_episode/1, - episode_count/1, - rate_episode/2, - episode_rating/2, - tv_original/1, - thread_running/1, - thread_exception/2]). - -:- use_module(library(clpfd)). -:- use_module(library(dcg/basics)). -:- use_module(library(http/http_open)). -:- use_module(library(sgml)). -:- use_module(library(xpath)). -:- use_module(library(persistency)). -:- use_module(atom_dcg). - -:- persistent episode_title(episode:integer, title:atom). -:- persistent episode_wiki(episode:integer, wiki:atom). -:- persistent episode_datum(episode:integer, key:atom, value:atom). -:- persistent episode_rating(episode:integer, rating:integer). - -attach :- - absolute_file_name('episode_data.db', F, [access(write)]), - db_attach(F, []). - -detach :- - db_detach. - -ensure_episode_data :- episode_title(_, _), !. -ensure_episode_data :- update_episode_data. - -retract_episode(Ep) :- - ( episode_title(Ep, _) - -> retractall_episode_title(Ep, _) - ; true - ), - ( episode_datum(Ep, 'Hint', _) - -> retractall_episode_datum(Ep, 'Hint', _) - ; true - ). - -episode_count(N) :- - setof(E, T^episode_title(E,T), Es), - last(Es, N). - -rate_episode(Ep, 0) :- - ( episode_rating(Ep, _) - -> retractall_episode_rating(Ep, _) - ; true - ), - !. - -rate_episode(Ep, R) :- - dif(R, 0), - ( episode_rating(Ep, R) - -> true - ; ( episode_rating(Ep, _) - -> retractall_episode_rating(Ep, _) - ; true - ), - assert_episode_rating(Ep, R) - ). - -open_episode_wiki(Ep) :- - episode_wiki(Ep, W), - win_shell(open, W). - -tv_original(Ep) :- episode_datum(Ep, 'Source', 'TV Original'). - -% Remote data retrieval. - -absolute_url(R) --> "https://www.detectiveconanworld.com", R. - -thread_running(T) :- thread_property(T, status(running)). -thread_exception(T, E) :- thread_property(T, status(exception(E))). - -update_episode_data :- - findall(Ep-Info, - (fetch_episode_info(Ep, Info)), - Set), - maplist(set_episode_info, Set). - -set_episode_info(Ep-Info) :- - maplist(set_episode_datum(Ep), Info). - -set_episode_datum(Ep, 'Title'-Title) :- !, - maybe_assert_episode_title(Ep, Title). -set_episode_datum(Ep, 'Wiki'-W) :- !, - maybe_assert_episode_wiki(Ep, W). -set_episode_datum(Ep, Key-Value) :- - maybe_assert_episode_datum(Ep, Key, Value). - -maybe_assert_episode_title(Ep, Title) :- - ( episode_title(Ep, Title), ! - ; assert_episode_title(Ep, Title) - ). - -maybe_assert_episode_wiki(Ep, W) :- - ( episode_wiki(Ep, W), ! - ; assert_episode_wiki(Ep, W) - ). - -maybe_assert_episode_datum(Ep, Key, Value) :- - ( episode_datum(Ep, Key, Value), ! - ; ( episode_datum(Ep, Key, _) - -> retract_episode_datum(Ep, Key, _) - ; true - ), - assert_episode_datum(Ep, Key, Value) - ). - -episode_number(Ep) --> integer(Ep). -episode_number(Ep) --> integer(Ep), "WPS", integer(_). - -% Temporary helper. -fetch_episode_data(Ep, Title, W, Date, Source, Hint) :- - fetch_episode_info(Ep, ['Title'-Title, 'Wiki'-W, 'Date'-Date, 'Source'-Source, 'Hint'-Hint]). - -fetch_episode_info(Ep, ['Title'-Title, 'Wiki'-W, 'Date'-Date, 'Source'-Source, 'Hint'-Hint]) :- - cached_html('https://www.detectiveconanworld.com/wiki/Anime', H), - xpath(H, //tr(td(index(3),@style='background:#f2fde9;')), R), - xpath(R, td(index(1),normalize_space), Ep0), - atom_phrase(episode_number(Ep), Ep0), - xpath(R, td(index(3),normalize_space), Title), - xpath(R, td(index(3))/a(@href), W0), - atom_phrase(absolute_url(W0), W), - xpath(R, td(index(4),normalize_space), Date), - xpath(R, td(index(7),normalize_space), Source0), - re_replace('\\(([0-9])', ' (\\1', Source0, Source1), - atom_string(Source, Source1), - xpath(R, td(index(8),normalize_space), Hint). - -update_screenwriters :- - findall(Ep-Name, - (maybe_fetch_screenwriter_episode(Name, Ep)), - Set), - maplist(set_episode_screenwriter, Set). - -set_episode_screenwriter(Ep-Name) :- - maybe_assert_episode_datum(Ep, 'Screenwriter', Name). - -maybe_fetch_screenwriter_episode(Name, Ep) :- - \+ episode_datum(Ep, 'Screenwriter', Ep), - fetch_screenwriter_episode(Name, Ep). - -fetch_screenwriter(Name) :- - cached_html('https://www.detectiveconanworld.com/wiki/Category:Screenplay_writers', H), - xpath(H, //'div'(@id='mw-pages')//a, A), - xpath(A, /self(normalize_space), Name). - -fetch_screenwriter_url(Name, U) :- - cached_html('https://www.detectiveconanworld.com/wiki/Category:Screenplay_writers', H), - xpath(H, //'div'(@id='mw-pages')//a, A), - xpath(A, /self(normalize_space), Name), - xpath(A, /self(@href), U). - -screenwriter_episode(Ep) --> string(_), "(Episode ", integer(Ep), ")". -screenwriter_episode(Ep) --> string(_), "(Episodes ", integer(Ep1), "-", integer(Ep2), ")", - { between(Ep1, Ep2, Ep) }. -screenwriter_episode(Ep) --> string(_), "(Episodes ", integer(Ep1), "-", integer(Ep2), " only)", - { between(Ep1, Ep2, Ep) }. - -fetch_screenwriter_episode(Name, Ep) :- - fetch_screenwriter_url(Name, U0), - atom_phrase(absolute_url(U0), U), - cached_html(U, H), - xpath(H, //div(@id='mw-content-text')//li, L), - xpath(L, /self(normalize_space), T), - atom_phrase(screenwriter_episode(Ep), T). - -fetch_html(U, H) :- - catch(http_load_html(U, H), _, fail), - !, - nb_setval(U, H). - -cached_html(U, H) :- nb_current(U, H), !. -cached_html(U, H) :- fetch_html(U, H). - -http_load_html(URL, DOM) :- - setup_call_cleanup(http_open(URL, In, - [ timeout(60) - ]), - ( dtd(html, DTD), - load_structure(stream(In), - DOM, - [ dtd(DTD), - dialect(sgml), - shorttag(false), - max_errors(-1), - syntax_errors(quiet) - ]) - ), - close(In)). diff --git a/pl/local_episodes.pl b/pl/local_episodes.pl deleted file mode 100644 index 68809a7..0000000 --- a/pl/local_episodes.pl +++ /dev/null @@ -1,50 +0,0 @@ -:- module(local_episodes, [local_episode//1, - episode_file/2, - open_episode_locally/1, - open_episode_online/1]). - -:- use_module(library(dcg/basics)). -:- use_module(atom_dcg). -:- use_module(cfg). - -local_episode_prefix --> string(_), "Detective_Conan_-_". -local_episode_prefix --> string(_), "Detective Conan - ". -local_episode_prefix --> string(_), "Detective-Conan-". -local_episode_prefix --> string(_), "detective_conan_". - -nondigit --> [C], !, { \+ code_type(C, digit) }. -nondigit --> []. - -zeroes --> "0". -zeroes --> "0", zeroes. - -paddedint(N) --> integer(N). -paddedint(N) --> zeroes, integer(N). - -local_episode --> - local_episode(_). -local_episode(N) --> - local_episode_prefix, paddedint(N), nondigit, string(_). -local_episode(N) --> - local_episode_prefix, paddedint(First), "-", paddedint(Last), nondigit, string(_), - { Second is First + 1, between(Second, Last, N) }. - -% Find episode on disk. - -episode_file(N, F) :- - get_root(R), - get_glob(G), - atomic_list_concat([R, '/', G], G1), - expand_file_name(G1, F1), - ( nonvar(N) - -> include(atom_phrase(local_episode(N)), F1, [F|_]) - ; include(atom_phrase(local_episode), F1, F2), - member(F, F2), - atom_phrase(local_episode(N), F) - ). - -% Open episode. - -episode_url(N) --> { with_codes(get_url(U)) }, string(U), integer(N). -open_episode_locally(N) :- episode_file(N, F), !, win_shell(open, F). -open_episode_online(N) :- atom_phrase(episode_url(N), U), win_shell(open, U). diff --git a/pl/track_episodes.pl b/pl/track_episodes.pl deleted file mode 100644 index 1973a75..0000000 --- a/pl/track_episodes.pl +++ /dev/null @@ -1,112 +0,0 @@ -:- module(track_episodes, [update_tracked_episodes/0, - toggle_episode/1, - forget_episode/1, - most_recently_watched/1, - watched/1, - next_unwatched/2, - previous_unwatched/2]). - -:- use_module(library(dcg/basics)). -:- use_module(library(registry)). -:- use_module(library(persistency)). -:- use_module(local_episodes). - -attach :- - absolute_file_name('track_episodes.db', F, [access(write)]), - db_attach(F, []). - -detach :- - db_detach. - -:- persistent episode_watched(episode:integer, watched:atom). - -% Helpers. - -swapbc(P, A, B, C, D) :- call(P, A, C, B, D). -compound(F, A, B, T) :- T =.. [F, A, B]. - -% Get data from registry. - -file(N) --> "File Name ", integer(N). - -pos(N) --> "File Position ", integer(N). - -mpc_reg(Phrase, Value) :- - phrase(Phrase, Name), - atom_codes(Name1, Name), - registry_get_key(current_user/software/'mpc-hc'/'mpc-hc'/settings, - Name1, Value). - -% Create episode-position list. - -epipos(L) :- epipos_x(0, L). - -epipos_x(N, L) :- - ( catch((mpc_reg(file(N), File), - mpc_reg(pos(N), Pos)), - _, - fail) - -> M is N + 1, - ( atom_codes(File, File1), - findall(Ep, phrase(local_episode(Ep), File1), Eps) - -> atom_codes(Pos, Pos1), - phrase(integer(Pos2), Pos1), - maplist(swapbc(compound, '-', Pos2), Eps, Eps1), - append(Eps1, T, L), - epipos_x(M, T) - ; epipos_x(M, L)) - ; L = [] - ). - -considered_watched(_-Pos) :- - Pos > 11000000000. - -% Update database. - -update_tracked_episodes :- - epipos(All), - include(considered_watched, All, Watched), - maplist(add, Watched). - -add(Ep-_) :- episode_watched(Ep, _), !. -add(Ep-_) :- assert_episode_watched(Ep, true). - -forget_episode(Ep) :- - retractall_episode_watched(Ep, true), - retractall_episode_watched(Ep, false). - -toggle_episode(Ep) :- - episode_watched(Ep, true), !, - retractall_episode_watched(Ep, true), - assert_episode_watched(Ep, false). - -toggle_episode(Ep) :- - episode_watched(Ep, false), !, - retractall_episode_watched(Ep, false), - assert_episode_watched(Ep, true). - -toggle_episode(Ep) :- - assert_episode_watched(Ep, true). - -% Utility. - -most_recently_watched(Ep) :- - findall(N, episode_watched(N, true), All), - last(All, Ep). - -watched(Ep) :- - episode_watched(Ep, true). - -next_unwatched(Ep0, Ep) :- - Ep1 is Ep0 + 1, - ( \+ watched(Ep1) - -> Ep = Ep1 - ; next_unwatched(Ep1, Ep) - ). - -previous_unwatched(Ep0, Ep) :- - Ep1 is Ep0 - 1, - ( \+ watched(Ep1) - -> Ep = Ep1 - ; previous_unwatched(Ep1, Ep) - ). -- cgit v1.2.3