Mercurial > minori
diff src/gui/pages/torrents.cc @ 114:ab191e28e69d
*: add initial torrent stuff
WOAH!
these checkboxes are a pain in my fucking ass
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Tue, 07 Nov 2023 08:03:42 -0500 |
parents | 9b2b41f83a5e |
children | 254b1d2b7096 |
line wrap: on
line diff
--- a/src/gui/pages/torrents.cc Mon Nov 06 13:48:11 2023 -0500 +++ b/src/gui/pages/torrents.cc Tue Nov 07 08:03:42 2023 -0500 @@ -1,6 +1,331 @@ #include "gui/pages/torrents.h" +#include "core/strings.h" +#include "core/http.h" +#include "core/session.h" +#include "gui/widgets/text.h" +#include "track/media.h" +#include "pugixml.hpp" +#include <QVBoxLayout> +#include <QToolBar> +#include <QTreeView> +#include <QMainWindow> +#include <QByteArray> +#include <QDataStream> +#include <QThreadPool> +#include <QDebug> +#include <iostream> +#include <sstream> +#include <algorithm> + +TorrentsPageListSortFilter::TorrentsPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { +} + +bool TorrentsPageListSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { + QVariant left = sourceModel()->data(l, sortRole()); + QVariant right = sourceModel()->data(r, sortRole()); + + switch (left.userType()) { + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: return left.toInt() < right.toInt(); + case QMetaType::QDate: return left.toDate() < right.toDate(); + case QMetaType::QString: + default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0; + } +} + +/* -------------------------------------------- */ + +TorrentsPageListModel::TorrentsPageListModel(QObject* parent) : QAbstractListModel(parent) { +} + +QByteArray TorrentsPageListModel::DownloadTorrentList() { + return HTTP::Get("https://www.tokyotosho.info/rss.php?filter=1,11&zwnj=0"); +} + +void TorrentsPageListModel::ParseTorrentList(const QByteArray& ba) { + std::istringstream stdstream(Strings::ToUtf8String(ba)); + + pugi::xml_document doc; + if (!doc.load(stdstream)) + return; // peace out + + /* my extra special dumb hack. */ + if (!rowCount(index(0))) { + beginInsertRows(QModelIndex(), 0, 0); + endInsertRows(); + } + + beginResetModel(); + + list.clear(); + /* this is just an rss parser; it should be in a separate class... */ + for (pugi::xml_node item : doc.child("rss").child("channel").children("item")) { + TorrentModelItem torrent; + torrent.SetFilename(item.child_value("title")); /* "title" == filename */ + { + /* Use Anitomy to parse the file's elements (we should *really* not be doing this this way!) */ + std::unordered_map<std::string, std::string> elements = Track::Media::GetFileElements(torrent.GetFilename()); + torrent.SetTitle(elements["title"]); + torrent.SetEpisode(Strings::RemoveLeadingChars(elements["episode"], '0')); + torrent.SetGroup(elements["group"]); + torrent.SetResolution(elements["resolution"]); + } + torrent.SetDescription(Strings::TextifySynopsis(item.child_value("description"))); + { + /* Parse size from description */ + std::istringstream descstream(torrent.GetDescription()); + + for (std::string line; std::getline(descstream, line);) { + const std::string match = "Size: "; + size_t pos = line.find(match); + + if (!pos) { + const std::string size = line.substr(pos + match.length()); + torrent.SetSize(Strings::HumanReadableSizeToBytes(size)); + } + } + } + torrent.SetLink(item.child_value("link")); + torrent.SetGuid(item.child_value("guid")); + { + const QString date_str = Strings::ToQString(item.child_value("pubDate")); + torrent.SetDate(QDateTime::fromString(date_str, "ddd, dd MMM yyyy HH:mm:ss t")); + } + list.push_back(torrent); + } + + endResetModel(); +} + +void TorrentsPageListModel::RefreshTorrentList() { + ParseTorrentList(DownloadTorrentList()); +} + +int TorrentsPageListModel::rowCount(const QModelIndex& parent) const { + return list.size(); + (void)(parent); +} + +int TorrentsPageListModel::columnCount(const QModelIndex& parent) const { + return NB_COLUMNS; + (void)(parent); +} + +QVariant TorrentsPageListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { + switch (role) { + case Qt::DisplayRole: { + switch (section) { + case TL_TITLE: return tr("Anime title"); + case TL_EPISODE: return tr("Episode"); + case TL_GROUP: return tr("Group"); + case TL_SIZE: return tr("Size"); + case TL_RESOLUTION: return tr("Resolution"); /* this is named "Video" in Taiga */ + case TL_SEEDERS: return tr("Seeding"); /* named "S" in Taiga */ + case TL_LEECHERS: return tr("Leeching"); /* named "L" in Taiga */ + case TL_DOWNLOADERS: return tr("Downloading"); /* named "D" in Taiga */ + case TL_DESCRIPTION: return tr("Description"); + case TL_FILENAME: return tr("Filename"); + case TL_RELEASEDATE: return tr("Release date"); + default: return {}; + } + break; + } + case Qt::TextAlignmentRole: { + switch (section) { + case TL_FILENAME: + case TL_GROUP: + case TL_DESCRIPTION: + case TL_RESOLUTION: + case TL_TITLE: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case TL_SEEDERS: + case TL_LEECHERS: + case TL_DOWNLOADERS: + case TL_SIZE: + case TL_EPISODE: + case TL_RELEASEDATE: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: return {}; + } + break; + } + } + return QAbstractListModel::headerData(section, orientation, role); +} + +bool TorrentsPageListModel::setData(const QModelIndex& index, const QVariant& value, int role) { + TorrentModelItem& item = list.at(index.row()); -TorrentsPage::TorrentsPage(QWidget* parent) : QWidget(parent) { + if (index.column() == 0) { + switch (role) { + case Qt::EditRole: + return false; + case Qt::CheckStateRole: + item.SetChecked(value.toBool()); + emit dataChanged(index, index); + return true; + } + } + + return QAbstractItemModel::setData(index, value, role); +} + +QVariant TorrentsPageListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return QVariant(); + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case TL_TITLE: return Strings::ToQString(list.at(index.row()).GetTitle()); + case TL_EPISODE: return Strings::ToQString(list.at(index.row()).GetEpisode()); + case TL_GROUP: return Strings::ToQString(list.at(index.row()).GetGroup()); + case TL_SIZE: return session.config.locale.GetLocale().formattedDataSize(list.at(index.row()).GetSize()); + case TL_RESOLUTION: return Strings::ToQString(list.at(index.row()).GetResolution()); + case TL_SEEDERS: return list.at(index.row()).GetSeeders(); + case TL_LEECHERS: return list.at(index.row()).GetLeechers(); + case TL_DOWNLOADERS: return list.at(index.row()).GetDownloaders(); + case TL_DESCRIPTION: return Strings::ToQString(list.at(index.row()).GetDescription()); + case TL_FILENAME: return Strings::ToQString(list.at(index.row()).GetFilename()); + case TL_RELEASEDATE: return list.at(index.row()).GetDate(); + default: return ""; + } + break; + case Qt::UserRole: + switch (index.column()) { + case TL_EPISODE: return Strings::ToInt(list.at(index.row()).GetEpisode(), -1); + case TL_SIZE: return list.at(index.row()).GetSize(); + default: return data(index, Qt::DisplayRole); + } + break; + case Qt::CheckStateRole: + switch (index.column()) { + case 0: return list.at(index.row()).GetChecked() ? Qt::Checked : Qt::Unchecked; + default: return {}; + } + case Qt::SizeHintRole: { + switch (index.column()) { + default: { + const QString d = data(index, Qt::DisplayRole).toString(); + const QFontMetrics metric = QFontMetrics(QFont()); + + return QSize(std::max(metric.horizontalAdvance(d), 100), metric.height()); + } + } + break; + } + case Qt::TextAlignmentRole: + switch (index.column()) { + case TL_FILENAME: + case TL_GROUP: + case TL_DESCRIPTION: + case TL_RESOLUTION: + case TL_TITLE: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case TL_SEEDERS: + case TL_LEECHERS: + case TL_DOWNLOADERS: + case TL_SIZE: + case TL_EPISODE: + case TL_RELEASEDATE: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: return {}; + } + break; + } + return QVariant(); +} + +Qt::ItemFlags TorrentsPageListModel::flags(const QModelIndex& index) const { + 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; +} + +TorrentsPage::TorrentsPage(QWidget* parent) : QFrame(parent) { + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Sunken); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + { + /* Toolbar */ + QToolBar* toolbar = new QToolBar(this); + toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toolbar->setIconSize(QSize(16, 16)); + toolbar->setMovable(false); + + { + /* this needs to be stored somewhere to replicate Taiga's + "timer" feature */ + QAction* action = toolbar->addAction(QIcon(":/icons/16x16/arrow-circle-315.png"), tr("&Check new torrents"), [this]{ + QThreadPool::globalInstance()->start([this] { + Refresh(); + }); + }); + } + + toolbar->addSeparator(); + + { + QAction* action = toolbar->addAction(QIcon(":/icons/16x16/navigation-270-button.png"), tr("Download &marked torrents")); + } + + { + QAction* action = toolbar->addAction(QIcon(":/icons/16x16/cross-button.png"), tr("&Discard all")); + } + + toolbar->addSeparator(); + + { + QAction* action = toolbar->addAction(QIcon(":/icons/16x16/gear.png"), tr("&Settings")); + } + + layout->addWidget(toolbar); + } + + { + QFrame* line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + line->setLineWidth(1); + layout->addWidget(line); + } + + { + QTreeView* treeview = new QTreeView(this); + treeview->setUniformRowHeights(true); + treeview->setAllColumnsShowFocus(false); + treeview->setAlternatingRowColors(true); + treeview->setSortingEnabled(true); + treeview->setSelectionMode(QAbstractItemView::ExtendedSelection); + treeview->setItemsExpandable(false); + treeview->setRootIsDecorated(false); + treeview->setContextMenuPolicy(Qt::CustomContextMenu); + treeview->setFrameShape(QFrame::NoFrame); + + { + sort_model = new TorrentsPageListSortFilter(treeview); + model = new TorrentsPageListModel(treeview); + sort_model->setSourceModel(model); + sort_model->setSortRole(Qt::UserRole); + sort_model->setSortCaseSensitivity(Qt::CaseInsensitive); + treeview->setModel(sort_model); + } + + layout->addWidget(treeview); + } +} + +void TorrentsPage::Refresh() { + if (!model) + return; + model->RefreshTorrentList(); } #include "gui/pages/moc_torrents.cpp"