# HG changeset patch # User Paper # Date 1699418454 18000 # Node ID 39521c47c7a3fd460fab631c023fbf588ecedd0e # Parent 2c1b6782e1d0799e03256cbf7b9cd797b67387f4 *: another huge megacommit, SORRY The torrents page works a lot better now Added the edit option to the anime list right click menu Vectorized currently playing files Available player and extensions are now loaded at runtime from files in (dotpath)/players.json and (dotpath)/extensions.json These paths are not permanent and will likely be moved to (dotpath)/recognition ... ... ... diff -r 2c1b6782e1d0 -r 39521c47c7a3 CMakeLists.txt --- a/CMakeLists.txt Tue Nov 07 16:06:17 2023 -0500 +++ b/CMakeLists.txt Tue Nov 07 23:40:54 2023 -0500 @@ -105,6 +105,7 @@ # Tracking src/track/constants.cc src/track/media.cc + src/track/types.cc # Qt resources rc/icons.qrc diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/core/config.h --- a/include/core/config.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/core/config.h Tue Nov 07 23:40:54 2023 -0500 @@ -4,11 +4,13 @@ #include "core/anime.h" #include "gui/theme.h" #include "gui/locale.h" +#include +#include class Config { public: int Load(); - int Save(); + int Save() const; Anime::Services service; Theme::Theme theme; diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/core/filesystem.h --- a/include/core/filesystem.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/core/filesystem.h Tue Nov 07 23:40:54 2023 -0500 @@ -22,9 +22,11 @@ std::string _path; }; -Path GetDotPath(); // %APPDATA%/minori, ~/Library/Application Support/minori, ~/.config/minori... -Path GetConfigPath(); // (dotpath)/config.json -Path GetAnimeDBPath(); // (dotpath)/anime/db.json +Path GetDotPath(); // %APPDATA%/minori, ~/Library/Application Support/minori, ~/.config/minori... +Path GetConfigPath(); // (dotpath)/config.json +Path GetAnimeDBPath(); // (dotpath)/anime/db.json +Path GetPlayersPath(); // (dotpath)/player.json +Path GetExtensionsPath(); // (dotpath)/extensions.json } // namespace Filesystem diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/core/session.h --- a/include/core/session.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/core/session.h Tue Nov 07 23:40:54 2023 -0500 @@ -2,20 +2,27 @@ #define __core__session_h #include "core/config.h" +#include "track/types.h" #include "gui/locale.h" #include struct Session { public: - Config config; Session() { timer.start(); } /* we literally *cannot* be lying to the user by doing this */ void IncrementRequests() { requests++; }; int GetRequests() { return requests; }; int uptime() { return timer.elapsed(); } + Config config; + + struct { + std::vector players; + std::vector extensions; + } recognition; + private: - int requests = 0; + uint32_t requests = 0; QElapsedTimer timer; }; diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/core/strings.h --- a/include/core/strings.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/core/strings.h Tue Nov 07 23:40:54 2023 -0500 @@ -14,6 +14,7 @@ /* Implode function: takes a vector of strings and turns it into a string, separated by delimiters. */ std::string Implode(const std::vector& vector, const std::string& delimiter); +std::vector Split(const std::string &text, const std::string& delimiter); /* Substring removal functions */ std::string ReplaceAll(std::string string, const std::string& find, const std::string& replace); diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/core/torrent.h --- a/include/core/torrent.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/core/torrent.h Tue Nov 07 23:40:54 2023 -0500 @@ -4,11 +4,14 @@ #include #include -/* this will be moved into its own namespace if +/* this is really just a fancy struct... + + this will be moved into its own namespace if it's deemed necessary */ class Torrent { public: std::string GetTitle() const { return _title; }; + std::string GetCategory() const { return _category; }; std::string GetEpisode() const { return _episode; }; std::string GetGroup() const { return _group; }; size_t GetSize() const { return _size; }; @@ -23,6 +26,7 @@ QDateTime GetDate() const { return _date; }; void SetTitle(const std::string& title) { _title = title; }; + void SetCategory(const std::string& category) { _category = category; }; void SetEpisode(const std::string& episode) { _episode = episode; }; void SetGroup(const std::string& group) { _group = group; }; void SetSize(const size_t size) { _size = size; }; @@ -38,6 +42,7 @@ private: std::string _title; + std::string _category; std::string _episode; std::string _group; size_t _size = 0; diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/gui/dialog/information.h --- a/include/gui/dialog/information.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/gui/dialog/information.h Tue Nov 07 23:40:54 2023 -0500 @@ -10,7 +10,13 @@ Q_OBJECT public: - InformationDialog(Anime::Anime& anime, std::function accept, QWidget* parent = nullptr); + enum Pages { + PAGE_MAIN_INFO, + PAGE_MY_LIST + }; + + InformationDialog(Anime::Anime& anime, std::function accept = {}, + enum Pages page = Pages::PAGE_MAIN_INFO, QWidget* parent = nullptr); protected: void showEvent(QShowEvent* event) override; diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/gui/locale.h --- a/include/gui/locale.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/gui/locale.h Tue Nov 07 23:40:54 2023 -0500 @@ -15,10 +15,10 @@ public: Locale(); Locale(const std::string& name); - QLocale GetLocale(); - std::vector GetAvailableLocales(); + QLocale GetLocale() const; + std::vector GetAvailableLocales() const; void RefreshAvailableLocales(); // why would this ever be called? - bool IsLocaleAvailable(const QLocale& locale); + bool IsLocaleAvailable(const QLocale& locale) const; bool SetActiveLocale(const QLocale& locale); private: diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/gui/pages/torrents.h --- a/include/gui/pages/torrents.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/gui/pages/torrents.h Tue Nov 07 23:40:54 2023 -0500 @@ -6,15 +6,6 @@ #include #include -class TorrentModelItem : public Torrent { - public: - bool GetChecked() const { return _checked; }; - void SetChecked(bool checked) { _checked = checked; }; - - private: - bool _checked = false; -}; - class TorrentsPageListSortFilter final : public QSortFilterProxyModel { Q_OBJECT @@ -59,6 +50,15 @@ void RefreshTorrentList(); private: + class TorrentModelItem : public Torrent { + public: + bool GetChecked() const { return _checked; }; + void SetChecked(bool checked) { _checked = checked; }; + + private: + bool _checked = false; + }; + std::vector list; }; diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/gui/theme.h --- a/include/gui/theme.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/gui/theme.h Tue Nov 07 23:40:54 2023 -0500 @@ -15,15 +15,15 @@ public: Theme(Themes theme = Themes::OS); void SetTheme(Themes theme); - Themes GetTheme(); - bool IsInDarkTheme(); + Themes GetTheme() const; + bool IsInDarkTheme() const; void RepaintCurrentTheme(); private: void SetToDarkTheme(); void SetToLightTheme(); void SetStyleSheet(Themes theme); - Themes GetCurrentOSTheme(); + Themes GetCurrentOSTheme() const; Themes theme; }; diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/track/constants.h --- a/include/track/constants.h Tue Nov 07 16:06:17 2023 -0500 +++ b/include/track/constants.h Tue Nov 07 23:40:54 2023 -0500 @@ -3,7 +3,13 @@ #include #include -extern const std::vector media_extensions; -extern const std::vector media_players; +namespace Track { +namespace Constants { + +extern const std::vector default_media_extensions; +extern const std::vector default_media_players; + +} +} #endif // __track__constants_h diff -r 2c1b6782e1d0 -r 39521c47c7a3 include/track/types.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/track/types.h Tue Nov 07 23:40:54 2023 -0500 @@ -0,0 +1,48 @@ +#ifndef __track__types_h +#define __track__types_h + +#include +#include + +namespace Track { +namespace Types { + +struct MediaPlayer { + public: + std::string GetName() const { return _name; }; + std::string GetExecutable() const { return _executable; }; + bool GetEnabled() const { return _enabled; }; + + void SetName(const std::string& name) { _name = name; }; + void SetExecutable(const std::string& executable) { _executable = executable; }; + void SetEnabled(const bool enabled) { _enabled = enabled; }; + + private: + std::string _name; + std::string _executable; + bool _enabled; +}; + +struct MediaExtension { + public: + std::string GetExtension() const { return _extension; }; + bool GetEnabled() const { return _enabled; }; + + void SetExtension(const std::string& extension) { _extension = extension; }; + void SetEnabled(const bool enabled) { _enabled = enabled; }; + + private: + std::string _extension; + bool _enabled; +}; + +void LoadPlayers(std::vector& players); +void LoadExtensions(std::vector& extensions); + +void SavePlayers(const std::vector& players); +void SaveExtensions(const std::vector& extensions); + +} +} + +#endif // __track__types_h \ No newline at end of file diff -r 2c1b6782e1d0 -r 39521c47c7a3 rc/dark.qss --- a/rc/dark.qss Tue Nov 07 16:06:17 2023 -0500 +++ b/rc/dark.qss Tue Nov 07 23:40:54 2023 -0500 @@ -161,6 +161,7 @@ QLineEdit { background: transparent; + color: white; } QLineEdit:!read-only { diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/core/config.cc --- a/src/core/config.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/core/config.cc Tue Nov 07 23:40:54 2023 -0500 @@ -37,14 +37,14 @@ anilist.auth_token = INI::GetIniString(ini, "AniList", "Auth Token", ""); anilist.user_id = Strings::ToInt(INI::GetIniString(ini, "AniList", "User ID", ""), 0); - torrents.feed_link = INI::GetIniString(ini, "Torrents", "RSS Feed Link", "https://www.tokyotosho.info/rss.php?filter=1,11&zwnj=0"); + torrents.feed_link = INI::GetIniString(ini, "Torrents", "RSS feed", "https://www.tokyotosho.info/rss.php?filter=1,11&zwnj=0"); theme.SetTheme(Translate::ToTheme(INI::GetIniString(ini, "Appearance", "Theme", "Default"))); return 0; } -int Config::Save() { +int Config::Save() const { Filesystem::Path cfg_path = Filesystem::GetConfigPath(); if (!cfg_path.GetParent().Exists()) cfg_path.GetParent().CreateDirectories(); @@ -62,7 +62,7 @@ ini["AniList"]["Auth Token"] = anilist.auth_token; ini["AniList"]["User ID"] = std::to_string(anilist.user_id); ini["Appearance"]["Theme"] = Translate::ToString(theme.GetTheme()); - ini["Torrents"]["RSS Feed Link"] = torrents.feed_link; + ini["Torrents"]["RSS feed"] = torrents.feed_link; file.write(ini); diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/core/filesystem.cc --- a/src/core/filesystem.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/core/filesystem.cc Tue Nov 07 23:40:54 2023 -0500 @@ -128,6 +128,22 @@ return ret; } +Path GetPlayersPath(void) { + std::string ret = ""; + ret += GetDotPath().GetPath(); + if (!ret.empty()) + ret += DELIM "players.json"; + return ret; +} + +Path GetExtensionsPath(void) { + std::string ret = ""; + ret += GetDotPath().GetPath(); + if (!ret.empty()) + ret += DELIM "extensions.json"; + return ret; +} + Path GetAnimeDBPath(void) { std::string ret = ""; ret += GetDotPath().GetPath(); diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/core/strings.cc --- a/src/core/strings.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/core/strings.cc Tue Nov 07 23:40:54 2023 -0500 @@ -29,6 +29,19 @@ return out; } +std::vector Split(const std::string &text, const std::string& delimiter) { + std::vector tokens; + + std::size_t start = 0, end = 0; + while ((end = text.find(delimiter, start)) != std::string::npos) { + tokens.push_back(text.substr(start, end - start)); + start = end + delimiter.length(); + } + tokens.push_back(text.substr(start)); + + return tokens; +} + /* This function is really only used for cleaning up the synopsis of horrible HTML debris from AniList :) */ std::string ReplaceAll(std::string string, const std::string& find, const std::string& replace) { @@ -173,11 +186,11 @@ uint64_t HumanReadableSizeToBytes(const std::string& str) { const std::unordered_map bytes_map = { - {"KB", 1 << 10}, - {"MB", 1 << 20}, - {"GB", 1 << 30}, - {"TB", 1 << 40}, - {"PB", 1 << 50} /* surely we won't need more than this */ + {"KB", 1ull << 10}, + {"MB", 1ull << 20}, + {"GB", 1ull << 30}, + {"TB", 1ull << 40}, + {"PB", 1ull << 50} /* surely we won't need more than this */ }; for (const auto& suffix : bytes_map) { @@ -195,8 +208,8 @@ } std::string RemoveLeadingChars(std::string s, const char c) { - s.erase(0, std::min(s.find_first_not_of(c), s.size() - 1)); - return s; + s.erase(0, std::min(s.find_first_not_of(c), s.size() - 1)); + return s; } std::string RemoveTrailingChars(std::string s, const char c) { diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/dialog/information.cc --- a/src/gui/dialog/information.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/dialog/information.cc Tue Nov 07 23:40:54 2023 -0500 @@ -39,7 +39,7 @@ anime.SetUserDateCompleted(_completed); } -InformationDialog::InformationDialog(Anime::Anime& anime, std::function accept, QWidget* parent) +InformationDialog::InformationDialog(Anime::Anime& anime, std::function accept, enum Pages page, QWidget* parent) : QDialog(parent) { /* ack. lots of brackets here, but MUCH, MUCH MUCH better than what it used to be */ setFixedSize(842, 613); @@ -276,6 +276,7 @@ tabbed_widget->addTab(settings_widget, tr("My list and settings")); } + tabbed_widget->setCurrentIndex(static_cast(page)); main_layout->addWidget(tabbed_widget); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(12); diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/dialog/settings.cc --- a/src/gui/dialog/settings.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/dialog/settings.cc Tue Nov 07 23:40:54 2023 -0500 @@ -30,6 +30,7 @@ pal.setColor(QPalette::WindowText, Qt::white); page_title->setPalette(pal); } + page_title->setAutoFillBackground(true); page_title->setFixedHeight(23); diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/locale.cc --- a/src/gui/locale.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/locale.cc Tue Nov 07 23:40:54 2023 -0500 @@ -31,11 +31,11 @@ SetActiveLocale(QLocale(Strings::ToQString(name))); } -QLocale Locale::GetLocale() { +QLocale Locale::GetLocale() const { return _locale; } -std::vector Locale::GetAvailableLocales() { +std::vector Locale::GetAvailableLocales() const { return _available_translations; } @@ -57,7 +57,7 @@ } } -bool Locale::IsLocaleAvailable(const QLocale& locale) { +bool Locale::IsLocaleAvailable(const QLocale& locale) const { for (const QLocale& l : _available_translations) if (l == locale) return true; diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/pages/anime_list.cc --- a/src/gui/pages/anime_list.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/pages/anime_list.cc Tue Nov 07 23:40:54 2023 -0500 @@ -108,13 +108,13 @@ case AL_TITLE: return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); case AL_PROGRESS: return QString::number(list[index.row()].GetUserProgress()) + "/" + - QString::number(list[index.row()].GetEpisodes()); + QString::number(list[index.row()].GetEpisodes()); case AL_EPISODES: return list[index.row()].GetEpisodes(); case AL_SCORE: return list[index.row()].GetUserScore(); case AL_TYPE: return Strings::ToQString(Translate::ToString(list[index.row()].GetFormat())); case AL_SEASON: return Strings::ToQString(Translate::ToString(list[index.row()].GetSeason())) + " " + - QString::number(list[index.row()].GetAirDate().GetYear()); + QString::number(list[index.row()].GetAirDate().GetYear()); case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%"; case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate(); case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate(); @@ -163,8 +163,8 @@ } void AnimeListPageModel::RefreshList() { - bool has_children = !!rowCount(index(0)); - if (!has_children) { + /* equivalent to hasChildren()... */ + if (!rowCount(index(0))) { beginInsertRows(QModelIndex(), 0, 0); endInsertRows(); } @@ -233,7 +233,8 @@ if (i == AnimeListPageModel::AL_TITLE) continue; const auto column_name = - sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { if (!checked && (VisibleColumnsCount() <= 1)) return; @@ -245,6 +246,7 @@ // SaveSettings(); }); + action->setCheckable(true); action->setChecked(!tree_view->isColumnHidden(i)); } @@ -266,9 +268,9 @@ menu->setToolTipsVisible(true); AnimeListPageModel* source_model = - reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); + reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); std::set animes; for (const auto& index : selection.indexes()) { @@ -281,8 +283,9 @@ menu->addAction(tr("Information"), [this, animes] { for (auto& anime : animes) { - InformationDialog* dialog = new InformationDialog( - *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this); + InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { + UpdateAnime(anime->GetId()); + }, InformationDialog::PAGE_MAIN_INFO, this); dialog->show(); dialog->raise(); @@ -290,6 +293,17 @@ } }); menu->addSeparator(); + menu->addAction(tr("Edit"), [this, animes] { + for (auto& anime : animes) { + InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { + UpdateAnime(anime->GetId()); + }, InformationDialog::PAGE_MY_LIST, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + } + }); menu->addAction(tr("Delete from list..."), [this, animes] { for (auto& anime : animes) { RemoveAnime(anime->GetId()); @@ -301,19 +315,20 @@ void AnimeListPage::ItemDoubleClicked() { /* throw out any other garbage */ const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); if (!selection.indexes().first().isValid()) { return; } AnimeListPageModel* source_model = - reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); + reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); const QModelIndex index = source_model->index(selection.indexes().first().row()); Anime::Anime* anime = source_model->GetAnimeFromIndex(index); - InformationDialog* dialog = new InformationDialog( - *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this); + InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { + UpdateAnime(anime->GetId()); + }, InformationDialog::PAGE_MAIN_INFO, this); dialog->show(); dialog->raise(); @@ -328,7 +343,7 @@ void AnimeListPage::RefreshTabs() { for (unsigned int i = 0; i < sort_models.size(); i++) tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + - QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); } void AnimeListPage::Refresh() { @@ -423,7 +438,7 @@ for (unsigned int i = 0; i < sort_models.size(); i++) { tab_bar->addTab(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + - QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); sort_models[i] = new AnimeListPageSortFilter(tree_view); sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i])); sort_models[i]->setSortRole(Qt::UserRole); @@ -441,10 +456,10 @@ /* Enter & return keys */ connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, - &AnimeListPage::ItemDoubleClicked); + &AnimeListPage::ItemDoubleClicked); connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, - &AnimeListPage::ItemDoubleClicked); + &AnimeListPage::ItemDoubleClicked); tree_view->header()->setStretchLastSection(false); tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/pages/torrents.cc --- a/src/gui/pages/torrents.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/pages/torrents.cc Tue Nov 07 23:40:54 2023 -0500 @@ -17,6 +17,12 @@ #include #include +/* This file is very, very similar to the anime list page. + + It differs from Taiga in that it uses tabs instead of + those "groups", but those are custom painted and a pain in the ass to + maintain over multiple platforms. */ + TorrentsPageListSortFilter::TorrentsPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { } @@ -74,16 +80,38 @@ } torrent.SetDescription(Strings::TextifySynopsis(item.child_value("description"))); { + /* Parse description... */ + enum class Keys { SIZE, AUTHORIZED, SUBMITTER, COMMENT }; + + const std::unordered_map KeyMap = { + {"Size", Keys::SIZE}, + {"Authorized", Keys::AUTHORIZED}, + {"Submitter", Keys::SUBMITTER}, + {"Comment", Keys::COMMENT} + }; + + const std::string description = Strings::TextifySynopsis(item.child_value("description")); + /* Parse size from description */ - std::istringstream descstream(torrent.GetDescription()); + std::istringstream descstream(description); for (std::string line; std::getline(descstream, line);) { - const std::string match = "Size: "; - size_t pos = line.find(match); + const size_t pos = line.find_first_of(':', 0); + if (pos == std::string::npos) + continue; + + const std::string key = line.substr(0, pos); + const std::string value = line.substr(line.find_first_not_of(": ", pos)); - if (!pos) { - const std::string size = line.substr(pos + match.length()); - torrent.SetSize(Strings::HumanReadableSizeToBytes(size)); + switch (KeyMap.at(key)) { + case Keys::COMMENT: + torrent.SetDescription(value); + break; + case Keys::SIZE: + torrent.SetSize(Strings::HumanReadableSizeToBytes(value)); + break; + default: + break; } } } @@ -202,11 +230,6 @@ default: return data(index, Qt::DisplayRole); } break; - case Qt::CheckStateRole: - switch (index.column()) { - case 0: return item.GetChecked() ? Qt::Checked : Qt::Unchecked; - default: return {}; - } case Qt::SizeHintRole: { switch (index.column()) { default: { @@ -243,12 +266,7 @@ if (!index.isValid()) return Qt::NoItemFlags; - const TorrentModelItem& item = list.at(index.row()); - - if (item.GetChecked() || index.column() == 0) - return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; - else - return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } TorrentsPage::TorrentsPage(QWidget* parent) : QFrame(parent) { diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/theme.cc --- a/src/gui/theme.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/theme.cc Tue Nov 07 23:40:54 2023 -0500 @@ -27,11 +27,11 @@ this->theme = theme; } -Themes Theme::GetTheme() { +Themes Theme::GetTheme() const { return theme; } -bool Theme::IsInDarkTheme() { +bool Theme::IsInDarkTheme() const { if (theme != Themes::OS) return (theme == Themes::DARK); #ifdef MACOSX @@ -70,7 +70,7 @@ SetStyleSheet(Themes::LIGHT); } -Themes Theme::GetCurrentOSTheme() { +Themes Theme::GetCurrentOSTheme() const { #ifdef MACOSX if (osx::DarkThemeAvailable()) return osx::IsInDarkTheme() ? Themes::DARK : Themes::LIGHT; diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/gui/window.cc --- a/src/gui/window.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/gui/window.cc Tue Nov 07 23:40:54 2023 -0500 @@ -16,6 +16,7 @@ #include "gui/widgets/sidebar.h" #include "services/services.h" #include "track/media.h" +#include "track/types.h" #include #include #include @@ -447,6 +448,8 @@ void MainWindow::closeEvent(QCloseEvent* event) { session.config.Save(); + Track::Types::SavePlayers(session.recognition.players); + Track::Types::SaveExtensions(session.recognition.extensions); event->accept(); } diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/main.cc --- a/src/main.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/main.cc Tue Nov 07 23:40:54 2023 -0500 @@ -1,5 +1,6 @@ #include "core/session.h" #include "gui/window.h" +#include "track/types.h" #include #include #include @@ -11,6 +12,8 @@ QApplication app(argc, argv); session.config.Load(); + Track::Types::LoadPlayers(session.recognition.players); + Track::Types::LoadExtensions(session.recognition.extensions); MainWindow window; diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/track/constants.cc --- a/src/track/constants.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/track/constants.cc Tue Nov 07 23:40:54 2023 -0500 @@ -1,11 +1,12 @@ #include "track/constants.h" -// clang-format off -// https://github.com/llvm/llvm-project/issues/62676 - /* right now, these are just const vectors, but eventually I'll make a class to manage these and make them disableable */ -const std::vector media_extensions = { + +namespace Track { +namespace Constants { + +const std::vector default_media_extensions = { "mkv", "mp4", "m4v", /* apple's stupid DRM thing */ @@ -46,7 +47,7 @@ "mxf" }; -const std::vector media_players = { +const std::vector default_media_players = { #ifdef MACOSX "VLC", "IINA", "QuickTime Player" #elif WIN32 @@ -55,4 +56,6 @@ "vlc", "mpv", "mpc-qt" #endif }; -// clang-format on + +} +} diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/track/media.cc --- a/src/track/media.cc Tue Nov 07 16:06:17 2023 -0500 +++ b/src/track/media.cc Tue Nov 07 23:40:54 2023 -0500 @@ -4,29 +4,43 @@ #include "anitomy/anitomy.h" #include "core/filesystem.h" #include "core/strings.h" +#include "core/session.h" #include #include #include +#include namespace Track { namespace Media { -Filesystem::Path GetCurrentPlaying() { +std::vector GetCurrentlyPlayingFiles() { /* getting all open files */ + std::vector ret; + std::vector pids = Animia::get_all_pids(); - for (int i : pids) { - for (const std::string& player : media_players) { - if (Animia::get_process_name(i) != player) + for (int pid : pids) { + for (const Types::MediaPlayer& player : session.recognition.players) { + if (!player.GetEnabled() || Animia::get_process_name(pid) != player.GetExecutable()) continue; - for (const std::string& f : Animia::filter_system_files(Animia::get_open_files(i))) { - Filesystem::Path p(f); - for (const std::string& ext : media_extensions) { - if (p.Extension() == ext) - return p; - } + + for (const std::string& file : Animia::filter_system_files(Animia::get_open_files(pid))) { + const Filesystem::Path path(file); + + for (const Types::MediaExtension& ext : session.recognition.extensions) + if (path.Extension() == ext.GetExtension()) + ret.push_back(path); } } } + + return ret; +} + +Filesystem::Path GetCurrentPlaying() { + /* getting all open files */ + std::vector paths = GetCurrentlyPlayingFiles(); + if (paths.size()) + return paths.at(0); return Filesystem::Path(); } diff -r 2c1b6782e1d0 -r 39521c47c7a3 src/track/types.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/track/types.cc Tue Nov 07 23:40:54 2023 -0500 @@ -0,0 +1,270 @@ +#include "track/types.h" +#include "core/filesystem.h" +#include "core/json.h" +#include +#include +#include + +using namespace nlohmann::literals::json_literals; + +namespace Track { +namespace Types { + +static nlohmann::json default_players = { + { + {"name", "VLC"}, +#ifdef MACOSX + {"executable", "VLC"}, +#elif defined(WIN32) + {"executable", "vlc.exe"}, +#else + {"executable", "vlc"}, +#endif + {"enabled", true} + }, + { + {"name", "mpv"}, +#ifdef WIN32 + {"executable", "mpv.exe"}, +#else + {"executable", "mpv"}, +#endif + {"enabled", true} + }, +#ifdef WIN32 + { + {"name", "MPC-HC x64"}, + {"executable", "mpc-hc64.exe"}, + {"enabled", true} + }, + { + {"name", "MPC-HC"}, + {"executable", "mpc-hc.exe"}, + {"enabled", true} + }, + { + {"name", "Windows Media Player"}, + {"executable", "wmplayer.exe"}, + {"enabled", true} + } +#elif defined(MACOSX) + { + {"name", "IINA"}, + {"executable", "IINA"}, + {"enabled", true} + }, + { + {"name", "QuickTime Player"}, + {"executable", "QuickTime Player"}, + {"enabled", false} + } +#else + { + {"name", "MPC-Qt"}, + {"executable", "mpc-qt"}, + {"enabled", true} + } +#endif +}; + +static nlohmann::json default_extensions = { + /* These are the four most common file extensions + according to... me. */ + { + {"extension", "mkv"}, + {"enabled", true} + }, + { + {"extension", "mp4"}, + {"enabled", true} + }, + { + {"extension", "m4v"}, + {"enabled", true} + }, + { + {"extension", "avi"}, + {"enabled", true} + }, + /* Matroska's retarded inbred cousin */ + { + {"extension", "webm"}, + {"enabled", true} + }, + /* QuickTime */ + { + {"extension", "mov"}, + {"enabled", true} + }, + { + {"extension", "qt"}, + {"enabled", true} + }, + /* MPEG transport stream */ + { + {"extension", "mts"}, + {"enabled", true} + }, + { + {"extension", "m2ts"}, + {"enabled", true} + }, + { + {"extension", "ts"}, + {"enabled", true} + }, + /* MPEG-1 */ + { + {"extension", "mpg"}, + {"enabled", true} + }, + { + {"extension", "mpeg"}, + {"enabled", true} + }, + { + {"extension", "mpe"}, + {"enabled", true} + }, + { + {"extension", "mpv"}, + {"enabled", true} + }, + /* MPEG-2 */ + { + {"extension", "m2v"}, + {"enabled", true} + }, + { + {"extension", "mp2"}, + {"enabled", true} + }, + /* 3GPP */ + { + {"extension", "3gp"}, + {"enabled", true} + }, + { + {"extension", "3g2"}, + {"enabled", true} + }, + /* Windows Media */ + { + {"extension", "asf"}, + {"enabled", true} + }, + { + {"extension", "wmv"}, + {"enabled", true} + }, + /* Adobe Flash */ + { + {"extension", "flv"}, + {"enabled", true} + }, + { + {"extension", "swf"}, // lol + {"enabled", false} + }, + /* Ogg Video */ + { + {"extension", "ogv"}, + {"enabled", true} + }, + /* RealPlayer... LOL */ + { + {"extension", "rm"}, + {"enabled", true} + }, + { + {"extension", "rmvb"}, + {"enabled", true} + }, + /* Nullsoft Streaming Video (Winamp) */ + { + {"extension", "nsv"}, + {"enabled", true} + }, + /* Material Exchange Format (Sony) */ + { + {"extension", "mxf"}, + {"enabled", true} + }, +}; + +void LoadPlayers(std::vector& players) { + nlohmann::json json; + { + std::ifstream is(Filesystem::GetPlayersPath().GetPath()); + if (!is.is_open()) + json = default_players; + else + is >> json; + } + + players.reserve(json.size()); + for (const auto& item : json) { + MediaPlayer player; + player.SetName(JSON::GetString(item, "/name"_json_pointer)); + player.SetExecutable(JSON::GetString(item, "/executable"_json_pointer)); + player.SetEnabled(JSON::GetBoolean(item, "/enabled"_json_pointer)); + players.push_back(player); + } +} + +void LoadExtensions(std::vector& extensions) { + nlohmann::json json; + { + std::ifstream is(Filesystem::GetExtensionsPath().GetPath()); + if (!is.is_open()) + json = default_extensions; + else + is >> json; + } + + extensions.reserve(json.size()); + for (const auto& item : json) { + MediaExtension extension; + extension.SetExtension(JSON::GetString(item, "/extension"_json_pointer)); + extension.SetEnabled(JSON::GetBoolean(item, "/enabled"_json_pointer)); + extensions.push_back(extension); + } +} + +void SavePlayers(const std::vector& players) { + nlohmann::json json = {}; + for (const auto& player : players) { + json.push_back({ + {"name", player.GetName()}, + {"executable", player.GetExecutable()}, + {"enabled", player.GetEnabled()} + }); + } + + { + std::ofstream os(Filesystem::GetPlayersPath().GetPath()); + if (!os.is_open()) + return; + os << std::setw(4) << json << std::endl; + } +} + +void SaveExtensions(const std::vector& extensions) { + nlohmann::json json = {}; + for (const auto& extension : extensions) { + json.push_back({ + {"extension", extension.GetExtension()}, + {"enabled", extension.GetEnabled()} + }); + } + + { + std::ofstream os(Filesystem::GetExtensionsPath().GetPath()); + if (!os.is_open()) + return; + os << std::setw(4) << json << std::endl; + } +} + +} +}