# HG changeset patch # User Paper # Date 1718253401 14400 # Node ID 78929794e7d8a961f8b5eb95f6017f7163b05032 # Parent 5d3c9b31aa6e5636c2b837a79d280ea419c41118 pages/seasons: run seasons search in a separate thread diff -r 5d3c9b31aa6e -r 78929794e7d8 include/gui/pages/seasons.h --- 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 +#include #include "core/anime.h" #include "core/date.h" +#include +#include + 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 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_ diff -r 5d3c9b31aa6e -r 78929794e7d8 include/services/anilist.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 Search(const std::string& search); -std::vector GetSeason(Anime::SeriesSeason season, Date::Year year); +bool GetSeason(Anime::SeriesSeason season, Date::Year year); /* Write queries (mutations) */ int UpdateAnimeEntry(int id); diff -r 5d3c9b31aa6e -r 78929794e7d8 include/services/kitsu.h --- 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 Search(const std::string& search); -std::vector GetSeason(Anime::SeriesSeason season, Date::Year year); +bool GetSeason(Anime::SeriesSeason season, Date::Year year); int UpdateAnimeEntry(int id); } // namespace Kitsu diff -r 5d3c9b31aa6e -r 78929794e7d8 include/services/services.h --- 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 Search(const std::string& search); -std::vector GetSeason(Anime::SeriesSeason season, Date::Year year); +bool GetSeason(Anime::SeriesSeason season, Date::Year year); void UpdateAnimeEntry(int id); }; // namespace Services diff -r 5d3c9b31aa6e -r 78929794e7d8 src/gui/pages/seasons.cc --- 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 #include +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); } diff -r 5d3c9b31aa6e -r 78929794e7d8 src/main.cc --- 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>(); /* window.cc */ qRegisterMetaType>(); /* search.cc */ qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType("Date::Year"); session.config.Load(); Anime::db.LoadDatabaseFromDisk(); diff -r 5d3c9b31aa6e -r 78929794e7d8 src/services/anilist.cc --- 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 -/* 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(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 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 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 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 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} }; diff -r 5d3c9b31aa6e -r 78929794e7d8 src/services/kitsu.cc --- 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()); - if (attributes.contains("/endDate"_json_pointer) && attributes["/endDate"_json_pointer].is_string()) - anime.SetCompletedDate(attributes["/endDate"_json_pointer].get()); - else - anime.SetCompletedDate(anime.GetStartedDate()); + anime.SetCompletedDate(attributes.contains("/endDate"_json_pointer) && attributes["/endDate"_json_pointer].is_string() + ? attributes["/endDate"_json_pointer].get() + : anime.GetStartedDate()); if (attributes.contains("/subtype"_json_pointer) && attributes["/subtype"_json_pointer].is_string()) ParseSubtype(anime, attributes["/subtype"_json_pointer].get()); @@ -549,8 +548,8 @@ return {}; } -std::vector GetSeason(Anime::SeriesSeason season, Date::Year year) { - return {}; +bool GetSeason(Anime::SeriesSeason season, Date::Year year) { + return false; } int UpdateAnimeEntry(int id) { diff -r 5d3c9b31aa6e -r 78929794e7d8 src/services/services.cc --- 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 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) {