changeset 409:8d06825d96d1

library: refresh in a separate thread this is fugly but it works
author Paper <paper@tflc.us>
date Thu, 02 Apr 2026 00:18:56 -0400
parents 9323153786dc
children eb554255ea5f
files include/library/library.h src/core/filesystem.cc src/gui/pages/anime_list.cc src/library/library.cc
diffstat 4 files changed, 67 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/include/library/library.h	Wed Jan 21 11:35:32 2026 -0500
+++ b/include/library/library.h	Thu Apr 02 00:18:56 2026 -0400
@@ -7,6 +7,8 @@
 #include <filesystem>
 #include <optional>
 #include <unordered_map>
+#include <thread>
+#include <mutex>
 
 namespace Library {
 
@@ -26,16 +28,27 @@
 	void Refresh();
 	void Refresh(int id);
 
-	// Anime episodes. Indexed as `folders[id][episode]'
-	std::unordered_map<int, std::unordered_map<int, std::filesystem::path>> items;
+	std::optional<std::filesystem::path> GetAnimeEpisodePath(int anime, int episode);
 
 private:
 	void Refresh(std::optional<int> find_id);
+	/* refresh_thread entry point */
+	void RefreshThread();
 
 	Filesystem::PathMap<std::unique_ptr<Filesystem::IWatcher>> watchers_;
 
-	/* ID we're looking for */
+	/* ID we're looking for (This is terrible, but other ways are also terrible in their own unique ways) */
 	std::optional<int> find_id_;
+
+	/* Anime episodes. Indexed as `folders[id][episode]' */
+	std::unordered_map<int, std::unordered_map<int, std::filesystem::path>> items;
+	std::recursive_mutex items_mutex_;
+
+	/* Thread that runs the refreshing.
+	 * When Refresh() is called, if this is a valid thread, then it will be
+	 * join()'d. This stinks, and a better method would be to have the
+	 * thread running in the background waiting on a queue. Oh well. */
+	std::thread refresh_thread_;
 };
 
 extern Database db;
--- a/src/core/filesystem.cc	Wed Jan 21 11:35:32 2026 -0500
+++ b/src/core/filesystem.cc	Thu Apr 02 00:18:56 2026 -0400
@@ -260,6 +260,7 @@
 	Win32WatcherVista(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive)
 	    : Win32Watcher(opaque, path, handler, recursive), dirhandle_(INVALID_HANDLE_VALUE)
 	{
+		ZeroMemory(&overlapped_, sizeof(overlapped_));
 		overlapped_.hEvent = nullptr;
 	}
 
--- a/src/gui/pages/anime_list.cc	Wed Jan 21 11:35:32 2026 -0500
+++ b/src/gui/pages/anime_list.cc	Thu Apr 02 00:18:56 2026 -0400
@@ -468,6 +468,7 @@
 			QKeySequence(QKeySequence::Refresh));
 
 		if (path) {
+			/* This is really dumb. */
 			menu->addSeparator();
 
 			{
@@ -482,52 +483,36 @@
 			const int progress = anime->GetUserProgress();
 			const int episodes = anime->GetEpisodes();
 
-			// I think this is right?
+			/* I think this is right? */
 			if (progress > 0) {
-				menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] {
-					const int id = anime->GetId();
-
-					if (Library::db.items.find(id) == Library::db.items.end() ||
-					    Library::db.items[id].find(progress) == Library::db.items[id].end())
-						return;
-
-					QDesktopServices::openUrl(
-					    QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string())));
-				});
+				std::optional<std::filesystem::path> x = Library::db.GetAnimeEpisodePath(anime->GetId(), progress - 1);
+				if (x)
+					menu->addAction(tr("Play last episode (#%1)").arg(progress - 1), [this, x] {
+						QDesktopServices::openUrl(
+							QUrl::fromLocalFile(Strings::ToQString(x.value().u8string())));
+					});
 			}
 
 			if (progress < episodes) {
-				menu->addAction(
-				    tr("Play next episode (#%1)").arg(progress + 1),
-				    [this, anime, progress] {
-					    const int id = anime->GetId();
-
-					    if (Library::db.items.find(id) == Library::db.items.end() ||
-					        Library::db.items[id].find(progress + 1) == Library::db.items[id].end())
-						    return;
-
-					    QDesktopServices::openUrl(
-					        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string())));
-				    },
-				    QKeySequence(Qt::CTRL | Qt::Key_N));
+				std::optional<std::filesystem::path> x = Library::db.GetAnimeEpisodePath(anime->GetId(), progress + 1);
+				if (x)
+					menu->addAction(tr("Play next episode (#%1)").arg(progress + 1), [this, x] {
+						QDesktopServices::openUrl(
+							QUrl::fromLocalFile(Strings::ToQString(x.value().u8string())));
+					}, QKeySequence(Qt::CTRL | Qt::Key_N));
 			}
 
 			menu->addAction(
 			    tr("Play random episode"),
 			    [this, anime, episodes] {
-				    const int id = anime->GetId();
-
 				    std::uniform_int_distribution<int> distrib(1, episodes);
 				    const int episode = distrib(session.gen);
 
-				    if (Library::db.items.find(id) == Library::db.items.end() ||
-				        Library::db.items[id].find(episode) == Library::db.items[id].end())
-					    return;
+					std::optional<std::filesystem::path> x = Library::db.GetAnimeEpisodePath(anime->GetId(), episode);
 
-				    QDesktopServices::openUrl(
-				        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string())));
-			    },
-			    QKeySequence(Qt::CTRL | Qt::Key_R));
+					if (x)
+						QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(x.value().u8string())));
+			    }, QKeySequence(Qt::CTRL | Qt::Key_R));
 		}
 
 		menu->popup(QCursor::pos());
--- a/src/library/library.cc	Wed Jan 21 11:35:32 2026 -0500
+++ b/src/library/library.cc	Thu Apr 02 00:18:56 2026 -0400
@@ -19,6 +19,9 @@
 	/* Nevermind, this causes an immediate segfault on Windows
 	UpdateWatchers();
 	*/
+	/* Also, this would be completely useless as the paths in
+	 * the config are not filled until after main() is
+	 * called. */
 }
 
 void Database::UpdateWatchers()
@@ -62,6 +65,9 @@
 	if (!GetPathAnimeAndEpisode(bname, &aid, &ep))
 		return;
 
+	/* Lock the items mutex */
+	const std::lock_guard<std::recursive_mutex> lock(items_mutex_);
+
 	switch (event) {
 	case Filesystem::IWatcher::Created:
 		items[aid][ep] = path;
@@ -91,6 +97,9 @@
 	// episodes are contained in them. whichever directory has more episodes
 	// wins, or the first found if there is an equal amount.
 
+	/* TODO use a shared lock instead */
+	const std::lock_guard<std::recursive_mutex> lock(items_mutex_);
+
 	for (const auto &[anime_id, episodes] : items) {
 		if (id != anime_id)
 			continue;
@@ -104,15 +113,25 @@
 	return std::nullopt;
 }
 
-/* TODO shove this into a separate thread; currently it blocks */
+void Database::RefreshThread()
+{
+	for (const auto &w : watchers_)
+		w.second->Process();
+}
+
+/* TODO the threading code for this could be better */
 void Database::Refresh(std::optional<int> find_id)
 {
+	/* Join the thread if it was already run */
+	if (refresh_thread_.joinable())
+		refresh_thread_.join();
+
+	/* This is terrible */
 	find_id_ = find_id;
 
 	UpdateWatchers();
 
-	for (const auto &w : watchers_)
-		w.second->Process();
+	refresh_thread_ = std::thread(&Database::RefreshThread, this);
 }
 
 void Database::Refresh()
@@ -125,6 +144,16 @@
 	Refresh(std::optional<int>(id));
 }
 
+std::optional<std::filesystem::path> Database::GetAnimeEpisodePath(int anime, int episode)
+{
+	const std::lock_guard<std::recursive_mutex> lock(items_mutex_);
+
+	if (items.find(anime) == items.end() || items[anime].find(episode) == items[anime].end())
+		return std::nullopt;
+
+	return items[anime][episode];
+}
+
 // TODO export to JSON on exit
 
 Database db;