changeset 325:78929794e7d8

pages/seasons: run seasons search in a separate thread
author Paper <paper@paper.us.eu.org>
date Thu, 13 Jun 2024 00:36:41 -0400
parents 5d3c9b31aa6e
children 10096c5489e3
files include/gui/pages/seasons.h include/services/anilist.h include/services/kitsu.h include/services/services.h src/gui/pages/seasons.cc src/main.cc src/services/anilist.cc src/services/kitsu.cc src/services/services.cc
diffstat 9 files changed, 181 insertions(+), 116 deletions(-) [+]
line wrap: on
line diff
--- a/include/gui/pages/seasons.h	Wed Jun 12 23:03:22 2024 -0400
+++ b/include/gui/pages/seasons.h	Thu Jun 13 00:36:41 2024 -0400
@@ -2,23 +2,57 @@
 #define MINORI_GUI_PAGES_SEASONS_H_
 
 #include <QFrame>
+#include <QThread>
 
 #include "core/anime.h"
 #include "core/date.h"
 
+#include <utility>
+#include <queue>
+
 class QListWidget;
 class QToolButton;
 
+Q_DECLARE_METATYPE(Anime::SeriesSeason);
+Q_DECLARE_METATYPE(Date::Year);
+
+class SeasonsPageSearchThread : public QThread {
+	Q_OBJECT
+
+public:
+	SeasonsPageSearchThread(QObject* parent = nullptr);
+	void AddToQueue(Anime::SeriesSeason season, Date::Year year);
+
+protected:
+	void run() override;
+
+private:
+	struct Season {
+		Anime::SeriesSeason season;
+		Date::Year year;
+	};
+
+	std::queue<Season> queue_;
+	std::mutex queue_mutex_;
+
+signals:
+	void ReceivedSeason(Anime::SeriesSeason season, Date::Year year);
+};
+
 class SeasonsPage final : public QFrame {
 	Q_OBJECT
 
 public:
 	SeasonsPage(QWidget* parent = nullptr);
 	void SetSeason(Anime::SeriesSeason season, Date::Year year);
+	void Refresh();
 
 protected:
 	QListWidget* buttons = nullptr;
 	QToolButton* season_button = nullptr;
+
+	Anime::SeriesSeason season_;
+	Date::Year year_;
 };
 
 #endif // MINORI_GUI_PAGES_SEASONS_H_
--- a/include/services/anilist.h	Wed Jun 12 23:03:22 2024 -0400
+++ b/include/services/anilist.h	Thu Jun 13 00:36:41 2024 -0400
@@ -17,7 +17,7 @@
 
 /* Search query */
 std::vector<int> Search(const std::string& search);
-std::vector<int> GetSeason(Anime::SeriesSeason season, Date::Year year);
+bool GetSeason(Anime::SeriesSeason season, Date::Year year);
 
 /* Write queries (mutations) */
 int UpdateAnimeEntry(int id);
--- a/include/services/kitsu.h	Wed Jun 12 23:03:22 2024 -0400
+++ b/include/services/kitsu.h	Thu Jun 13 00:36:41 2024 -0400
@@ -17,7 +17,7 @@
 
 int GetAnimeList();
 std::vector<int> Search(const std::string& search);
-std::vector<int> GetSeason(Anime::SeriesSeason season, Date::Year year);
+bool GetSeason(Anime::SeriesSeason season, Date::Year year);
 int UpdateAnimeEntry(int id);
 
 } // namespace Kitsu
--- a/include/services/services.h	Wed Jun 12 23:03:22 2024 -0400
+++ b/include/services/services.h	Thu Jun 13 00:36:41 2024 -0400
@@ -17,7 +17,7 @@
 bool RetrieveAnimeMetadata(int id);
 
 std::vector<int> Search(const std::string& search);
-std::vector<int> GetSeason(Anime::SeriesSeason season, Date::Year year);
+bool GetSeason(Anime::SeriesSeason season, Date::Year year);
 void UpdateAnimeEntry(int id);
 
 }; // namespace Services
--- a/src/gui/pages/seasons.cc	Wed Jun 12 23:03:22 2024 -0400
+++ b/src/gui/pages/seasons.cc	Thu Jun 13 00:36:41 2024 -0400
@@ -16,17 +16,53 @@
 #include <QToolButton>
 #include <QVBoxLayout>
 
+SeasonsPageSearchThread::SeasonsPageSearchThread(QObject* parent) : QThread(parent) {
+}
+
+void SeasonsPageSearchThread::AddToQueue(Anime::SeriesSeason season, Date::Year year) {
+	queue_mutex_.lock();
+	queue_.push({season, year});
+	queue_mutex_.unlock();
+}
+
+void SeasonsPageSearchThread::run() {
+	queue_mutex_.lock();
+
+	while (!queue_.empty() && !isInterruptionRequested()) {
+		Season season = queue_.front();
+
+		/* unlock the mutex for a long blocking operation, so items
+		 * can be added without worry */
+		queue_mutex_.unlock();
+
+		if (Services::GetSeason(season.season, season.year))
+			emit ReceivedSeason(season.season, season.year);
+
+		queue_mutex_.lock();
+
+		queue_.pop();
+	}
+
+	queue_mutex_.unlock();
+}
+
+static SeasonsPageSearchThread search_thread_;
+
+/* ------------------------------------------------------------------------------------- */
+
 static constexpr Date::Year GetClosestDecade(Date::Year year) {
 	return year - (year % 10);
 }
 
-void SeasonsPage::SetSeason(Anime::SeriesSeason season, Date::Year year) {
+void SeasonsPage::Refresh() {
+	setUpdatesEnabled(false);
+
 	if (!buttons || !season_button)
 		return;
 
 	buttons->clear();
 
-	for (const auto& id : Anime::Season::GetAllAnimeForSeason(season, year)) {
+	for (const auto& id : Anime::Season::GetAllAnimeForSeason(season_, year_)) {
 		QListWidgetItem* item = new QListWidgetItem;
 		AnimeButton* button = new AnimeButton(this);
 		button->SetAnime(Anime::db.items[id]);
@@ -35,7 +71,16 @@
 		buttons->setItemWidget(item, button);
 	}
 
-	season_button->setText(Strings::ToQString(Translate::ToLocalString(season)) + " " + QString::number(year));
+	season_button->setText(Strings::ToQString(Translate::ToLocalString(season_)) + " " + QString::number(year_));
+
+	setUpdatesEnabled(true);
+}
+
+void SeasonsPage::SetSeason(Anime::SeriesSeason season, Date::Year year) {
+	season_ = season;
+	year_ = year;
+
+	Refresh();
 }
 
 SeasonsPage::SeasonsPage(QWidget* parent) : QFrame(parent) {
@@ -53,7 +98,6 @@
 		toolbar->setMovable(false);
 
 		{
-			/* todo: clean this up... this sucks... */
 			static constexpr Date::Year last_year = 1960;
 
 			auto create_year_menu = [this](QWidget* parent, QMenu* parent_menu, Date::Year year){
@@ -62,7 +106,7 @@
 				QMenu* menu = new QMenu(year_s, parent);
 				for (const auto& season : Anime::SeriesSeasons) {
 					QAction* action = menu->addAction(Strings::ToQString(Translate::ToLocalString(season)) + " " + year_s);
-					connect(action, &QAction::triggered, this, [this, season, year]{
+					connect(action, &QAction::triggered, this, [this, season, year] {
 						SetSeason(season, year);
 					});
 				}
@@ -100,8 +144,9 @@
 
 		{
 			toolbar->addAction(QIcon(":/icons/16x16/arrow-circle-315.png"), tr("Refresh data"), [this]{
-				Services::GetSeason(Anime::SeriesSeason::Summer, 2011U);
-				SetSeason(Anime::SeriesSeason::Summer, 2011U);
+				search_thread_.AddToQueue(season_, year_);
+				if (!search_thread_.isRunning())
+					search_thread_.start();
 			});
 		}
 
@@ -190,6 +235,11 @@
 	full_layout->setContentsMargins(0, 0, 0, 0);
 	full_layout->setSpacing(0);
 
+	connect(&search_thread_, &SeasonsPageSearchThread::ReceivedSeason, this, [this](Anime::SeriesSeason season, Date::Year year) {
+		if (season == season_ && year == year_)
+			Refresh();
+	});
+
 	/* Do NOT move this up in this function, buttons HAS to be initialized */
 	SetSeason(Anime::SeriesSeason::Summer, 2011U);
 }
--- a/src/main.cc	Wed Jun 12 23:03:22 2024 -0400
+++ b/src/main.cc	Thu Jun 13 00:36:41 2024 -0400
@@ -1,4 +1,6 @@
+#include "core/anime.h"
 #include "core/anime_db.h"
+#include "core/date.h"
 #include "core/session.h"
 #include "core/strings.h"
 #include "gui/window.h"
@@ -20,6 +22,8 @@
 	qRegisterMetaType<std::vector<std::string>>(); /* window.cc */
 	qRegisterMetaType<std::vector<int>>(); /* search.cc */
 	qRegisterMetaType<std::string>();
+	qRegisterMetaType<Anime::SeriesSeason>();
+	qRegisterMetaType<Date::Year>("Date::Year");
 
 	session.config.Load();
 	Anime::db.LoadDatabaseFromDisk();
--- a/src/services/anilist.cc	Wed Jun 12 23:03:22 2024 -0400
+++ b/src/services/anilist.cc	Thu Jun 13 00:36:41 2024 -0400
@@ -23,15 +23,12 @@
 
 #include <iostream>
 
-/* This file really sucks because it was made when I was first
- * really "learning" C++ */
-
-using namespace nlohmann::literals::json_literals;
-
 namespace Services {
 namespace AniList {
 
 static constexpr std::string_view CLIENT_ID = "13706";
+
+/* This is used in multiple queries, so just put it here I guess. */
 #define MEDIA_FIELDS \
 	"coverImage {\n" \
 	"  large\n" \
@@ -198,10 +195,9 @@
 	if (json.contains("/startDate"_json_pointer) && json["/startDate"_json_pointer].is_object())
 		anime.SetStartedDate(Date(json["/startDate"_json_pointer]));
 
-	if (json.contains("/endDate"_json_pointer) && json["/endDate"_json_pointer].is_object())
-		anime.SetCompletedDate(Date(json["/endDate"_json_pointer]));
-	else
-		anime.SetCompletedDate(anime.GetStartedDate());
+	anime.SetCompletedDate(json.contains("/endDate"_json_pointer) && json["/endDate"_json_pointer].is_object()
+		? Date(json["/endDate"_json_pointer])
+		: anime.GetStartedDate());
 
 	anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, ""));
 
@@ -265,34 +261,35 @@
 int GetAnimeList() {
 	auto& auth = session.config.auth.anilist;
 
-	/* NOTE: these really ought to be in the qrc file */
-	constexpr std::string_view query = "query ($id: Int) {\n"
-									   "  MediaListCollection (userId: $id, type: ANIME) {\n"
-									   "    lists {\n"
-									   "      name\n"
-									   "      entries {\n"
-									   "        score\n"
-									   "        notes\n"
-									   "        status\n"
-									   "        progress\n"
-									   "        startedAt {\n"
-									   "          year\n"
-									   "          month\n"
-									   "          day\n"
-									   "        }\n"
-									   "        completedAt {\n"
-									   "          year\n"
-									   "          month\n"
-									   "          day\n"
-									   "        }\n"
-									   "        updatedAt\n"
-									   "        media {\n"
-									   MEDIA_FIELDS
-									   "        }\n"
-									   "      }\n"
-									   "    }\n"
-									   "  }\n"
-									   "}\n";
+	static constexpr std::string_view query =
+		"query ($id: Int) {\n"
+		"  MediaListCollection (userId: $id, type: ANIME) {\n"
+		"    lists {\n"
+		"      name\n"
+		"      entries {\n"
+		"        score\n"
+		"        notes\n"
+		"        status\n"
+		"        progress\n"
+		"        startedAt {\n"
+		"          year\n"
+		"          month\n"
+		"          day\n"
+		"        }\n"
+		"        completedAt {\n"
+		"          year\n"
+		"          month\n"
+		"          day\n"
+		"        }\n"
+		"        updatedAt\n"
+		"        media {\n"
+		MEDIA_FIELDS
+		"        }\n"
+		"      }\n"
+		"    }\n"
+		"  }\n"
+		"}\n";
+
 	// clang-format off
 	nlohmann::json request = {
 		{"query", query},
@@ -326,13 +323,14 @@
 
 /* return is a vector of anime ids */
 std::vector<int> Search(const std::string& search) {
-	constexpr std::string_view query = "query ($search: String) {\n"
-									   "  Page (page: 1, perPage: 50) {\n"
-									   "    media (search: $search, type: ANIME) {\n"
-									   MEDIA_FIELDS
-									   "    }\n"
-									   "  }\n"
-									   "}\n";
+	static constexpr std::string_view query =
+		"query ($search: String) {\n"
+		"  Page (page: 1, perPage: 50) {\n"
+		"    media (search: $search, type: ANIME) {\n"
+		MEDIA_FIELDS
+		"    }\n"
+		"  }\n"
+		"}\n";
 
 	// clang-format off
 	nlohmann::json json = {
@@ -359,74 +357,54 @@
 	return ret;
 }
 
-std::vector<int> GetSeason(Anime::SeriesSeason season, Date::Year year) {
-	constexpr std::string_view query = "query ($season: MediaSeason!, $season_year: Int!, $page: Int) {\n"
-									   "  Page(page: $page) {\n"
-									   "    media(season: $season, seasonYear: $season_year, type: ANIME, sort: START_DATE) {\n"
-									   MEDIA_FIELDS
-									   "    }\n"
-									   "    pageInfo {\n"
-									   "      total\n"
-									   "      perPage\n"
-									   "      currentPage\n"
-									   "      lastPage\n"
-									   "      hasNextPage\n"
-									   "    }\n"
-									   "  }\n"
-									   "}\n";
-	std::vector<int> ret;
+bool GetSeason(Anime::SeriesSeason season, Date::Year year) {
+	static constexpr std::string_view query =
+		"query ($season: MediaSeason!, $season_year: Int!, $page: Int) {\n"
+		"  Page(page: $page) {\n"
+		"    media(season: $season, seasonYear: $season_year, type: ANIME, sort: START_DATE) {\n"
+		MEDIA_FIELDS
+		"    }\n"
+		"    pageInfo {\n"
+		"      total\n"
+		"      perPage\n"
+		"      currentPage\n"
+		"      lastPage\n"
+		"      hasNextPage\n"
+		"    }\n"
+		"  }\n"
+		"}\n";
 
 	int page = 0;
 	bool has_next_page = true;
+
 	while (has_next_page) {
 		nlohmann::json json = {
 			{"query", query},
 			{"variables", {
 				{"season", Translate::AniList::ToString(season)},
 				{"season_year", Strings::ToUtf8String(year)},
-				{"page", page}
-			}}
+				{"page", page},
+			}},
 		};
 
 		const std::optional<nlohmann::json> res = SendJSONRequest(json);
 		if (!res)
-			return {};
+			return false;
 
 		const nlohmann::json& result = res.value();
 
-		ret.reserve(ret.capacity() + result["data"]["Page"]["media"].size());
-
-		for (const auto& media : result["data"]["Page"]["media"].items())
-			ret.push_back(ParseMediaJson(media.value()));
+		for (const auto& media : result["/data/Page/media"_json_pointer].items())
+			ParseMediaJson(media.value());
 
 		has_next_page = JSON::GetBoolean(result, "/data/Page/pageInfo/hasNextPage"_json_pointer, false);
 		if (has_next_page)
 			page++;
 	}
 
-	return ret;
+	return true;
 }
 
 int UpdateAnimeEntry(int id) {
-	/**
-	 * possible values:
-	 *
-	 * int mediaId,
-	 * MediaListStatus status,
-	 * float score,
-	 * int scoreRaw,
-	 * int progress,
-	 * int progressVolumes, // manga-specific.
-	 * int repeat, // rewatch
-	 * int priority,
-	 * bool private,
-	 * string notes,
-	 * bool hiddenFromStatusLists,
-	 * string[] customLists,
-	 * float[] advancedScores,
-	 * Date startedAt,
-	 * Date completedAt
-	 **/
 	Anime::Anime& anime = Anime::db.items[id];
 	if (!anime.IsInUserList())
 		return 0;
@@ -435,13 +413,11 @@
 	if (!service_id)
 		return 0;
 
-	session.SetStatusBar("AniList: Updating anime entry...");
-
-	constexpr std::string_view query =
-		"mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String, $start: "
-		"FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n"
-		"  SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score, notes: "
-		"$notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n"
+	static constexpr std::string_view query =
+		"mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String,"
+		"     $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n"
+		"  SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score,"
+		"        notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n"
 		"    id\n"
 		"  }\n"
 		"}\n";
@@ -497,11 +473,13 @@
 
 	session.SetStatusBar("AniList: Requesting user ID...");
 
-	constexpr std::string_view query = "query {\n"
-									   "  Viewer {\n"
-									   "    id\n"
-									   "  }\n"
-									   "}\n";
+	static constexpr std::string_view query =
+		"query {\n"
+		"  Viewer {\n"
+		"    id\n"
+		"  }\n"
+		"}\n";
+
 	nlohmann::json json = {
 		{"query", query}
 	};
--- a/src/services/kitsu.cc	Wed Jun 12 23:03:22 2024 -0400
+++ b/src/services/kitsu.cc	Thu Jun 13 00:36:41 2024 -0400
@@ -299,10 +299,9 @@
 	if (attributes.contains("/startDate"_json_pointer) && attributes["/startDate"_json_pointer].is_string())
 		anime.SetStartedDate(attributes["/startDate"_json_pointer].get<std::string>());
 
-	if (attributes.contains("/endDate"_json_pointer) && attributes["/endDate"_json_pointer].is_string())
-		anime.SetCompletedDate(attributes["/endDate"_json_pointer].get<std::string>());
-	else
-		anime.SetCompletedDate(anime.GetStartedDate());
+	anime.SetCompletedDate(attributes.contains("/endDate"_json_pointer) && attributes["/endDate"_json_pointer].is_string()
+		? attributes["/endDate"_json_pointer].get<std::string>()
+		: anime.GetStartedDate());
 
 	if (attributes.contains("/subtype"_json_pointer) && attributes["/subtype"_json_pointer].is_string())
 		ParseSubtype(anime, attributes["/subtype"_json_pointer].get<std::string>());
@@ -549,8 +548,8 @@
 	return {};
 }
 
-std::vector<int> GetSeason(Anime::SeriesSeason season, Date::Year year) {
-	return {};
+bool GetSeason(Anime::SeriesSeason season, Date::Year year) {
+	return false;
 }
 
 int UpdateAnimeEntry(int id) {
--- a/src/services/services.cc	Wed Jun 12 23:03:22 2024 -0400
+++ b/src/services/services.cc	Thu Jun 13 00:36:41 2024 -0400
@@ -33,7 +33,7 @@
 	}
 }
 
-std::vector<int> GetSeason(Anime::SeriesSeason season, Date::Year year) {
+bool GetSeason(Anime::SeriesSeason season, Date::Year year) {
 	session.SetStatusBar(Translate::ToString(session.config.service) + ": Retrieving anime season data...");
 
 	switch (session.config.service) {