comparison 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
comparison
equal deleted inserted replaced
113:32afe0e940bf 114:ab191e28e69d
1 #include "gui/pages/torrents.h" 1 #include "gui/pages/torrents.h"
2 2 #include "core/strings.h"
3 TorrentsPage::TorrentsPage(QWidget* parent) : QWidget(parent) { 3 #include "core/http.h"
4 #include "core/session.h"
5 #include "gui/widgets/text.h"
6 #include "track/media.h"
7 #include "pugixml.hpp"
8 #include <QVBoxLayout>
9 #include <QToolBar>
10 #include <QTreeView>
11 #include <QMainWindow>
12 #include <QByteArray>
13 #include <QDataStream>
14 #include <QThreadPool>
15 #include <QDebug>
16 #include <iostream>
17 #include <sstream>
18 #include <algorithm>
19
20 TorrentsPageListSortFilter::TorrentsPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) {
21 }
22
23 bool TorrentsPageListSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const {
24 QVariant left = sourceModel()->data(l, sortRole());
25 QVariant right = sourceModel()->data(r, sortRole());
26
27 switch (left.userType()) {
28 case QMetaType::Int:
29 case QMetaType::UInt:
30 case QMetaType::LongLong:
31 case QMetaType::ULongLong: return left.toInt() < right.toInt();
32 case QMetaType::QDate: return left.toDate() < right.toDate();
33 case QMetaType::QString:
34 default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0;
35 }
36 }
37
38 /* -------------------------------------------- */
39
40 TorrentsPageListModel::TorrentsPageListModel(QObject* parent) : QAbstractListModel(parent) {
41 }
42
43 QByteArray TorrentsPageListModel::DownloadTorrentList() {
44 return HTTP::Get("https://www.tokyotosho.info/rss.php?filter=1,11&zwnj=0");
45 }
46
47 void TorrentsPageListModel::ParseTorrentList(const QByteArray& ba) {
48 std::istringstream stdstream(Strings::ToUtf8String(ba));
49
50 pugi::xml_document doc;
51 if (!doc.load(stdstream))
52 return; // peace out
53
54 /* my extra special dumb hack. */
55 if (!rowCount(index(0))) {
56 beginInsertRows(QModelIndex(), 0, 0);
57 endInsertRows();
58 }
59
60 beginResetModel();
61
62 list.clear();
63 /* this is just an rss parser; it should be in a separate class... */
64 for (pugi::xml_node item : doc.child("rss").child("channel").children("item")) {
65 TorrentModelItem torrent;
66 torrent.SetFilename(item.child_value("title")); /* "title" == filename */
67 {
68 /* Use Anitomy to parse the file's elements (we should *really* not be doing this this way!) */
69 std::unordered_map<std::string, std::string> elements = Track::Media::GetFileElements(torrent.GetFilename());
70 torrent.SetTitle(elements["title"]);
71 torrent.SetEpisode(Strings::RemoveLeadingChars(elements["episode"], '0'));
72 torrent.SetGroup(elements["group"]);
73 torrent.SetResolution(elements["resolution"]);
74 }
75 torrent.SetDescription(Strings::TextifySynopsis(item.child_value("description")));
76 {
77 /* Parse size from description */
78 std::istringstream descstream(torrent.GetDescription());
79
80 for (std::string line; std::getline(descstream, line);) {
81 const std::string match = "Size: ";
82 size_t pos = line.find(match);
83
84 if (!pos) {
85 const std::string size = line.substr(pos + match.length());
86 torrent.SetSize(Strings::HumanReadableSizeToBytes(size));
87 }
88 }
89 }
90 torrent.SetLink(item.child_value("link"));
91 torrent.SetGuid(item.child_value("guid"));
92 {
93 const QString date_str = Strings::ToQString(item.child_value("pubDate"));
94 torrent.SetDate(QDateTime::fromString(date_str, "ddd, dd MMM yyyy HH:mm:ss t"));
95 }
96 list.push_back(torrent);
97 }
98
99 endResetModel();
100 }
101
102 void TorrentsPageListModel::RefreshTorrentList() {
103 ParseTorrentList(DownloadTorrentList());
104 }
105
106 int TorrentsPageListModel::rowCount(const QModelIndex& parent) const {
107 return list.size();
108 (void)(parent);
109 }
110
111 int TorrentsPageListModel::columnCount(const QModelIndex& parent) const {
112 return NB_COLUMNS;
113 (void)(parent);
114 }
115
116 QVariant TorrentsPageListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const {
117 switch (role) {
118 case Qt::DisplayRole: {
119 switch (section) {
120 case TL_TITLE: return tr("Anime title");
121 case TL_EPISODE: return tr("Episode");
122 case TL_GROUP: return tr("Group");
123 case TL_SIZE: return tr("Size");
124 case TL_RESOLUTION: return tr("Resolution"); /* this is named "Video" in Taiga */
125 case TL_SEEDERS: return tr("Seeding"); /* named "S" in Taiga */
126 case TL_LEECHERS: return tr("Leeching"); /* named "L" in Taiga */
127 case TL_DOWNLOADERS: return tr("Downloading"); /* named "D" in Taiga */
128 case TL_DESCRIPTION: return tr("Description");
129 case TL_FILENAME: return tr("Filename");
130 case TL_RELEASEDATE: return tr("Release date");
131 default: return {};
132 }
133 break;
134 }
135 case Qt::TextAlignmentRole: {
136 switch (section) {
137 case TL_FILENAME:
138 case TL_GROUP:
139 case TL_DESCRIPTION:
140 case TL_RESOLUTION:
141 case TL_TITLE: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
142 case TL_SEEDERS:
143 case TL_LEECHERS:
144 case TL_DOWNLOADERS:
145 case TL_SIZE:
146 case TL_EPISODE:
147 case TL_RELEASEDATE: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
148 default: return {};
149 }
150 break;
151 }
152 }
153 return QAbstractListModel::headerData(section, orientation, role);
154 }
155
156 bool TorrentsPageListModel::setData(const QModelIndex& index, const QVariant& value, int role) {
157 TorrentModelItem& item = list.at(index.row());
158
159 if (index.column() == 0) {
160 switch (role) {
161 case Qt::EditRole:
162 return false;
163 case Qt::CheckStateRole:
164 item.SetChecked(value.toBool());
165 emit dataChanged(index, index);
166 return true;
167 }
168 }
169
170 return QAbstractItemModel::setData(index, value, role);
171 }
172
173 QVariant TorrentsPageListModel::data(const QModelIndex& index, int role) const {
174 if (!index.isValid())
175 return QVariant();
176 switch (role) {
177 case Qt::DisplayRole:
178 switch (index.column()) {
179 case TL_TITLE: return Strings::ToQString(list.at(index.row()).GetTitle());
180 case TL_EPISODE: return Strings::ToQString(list.at(index.row()).GetEpisode());
181 case TL_GROUP: return Strings::ToQString(list.at(index.row()).GetGroup());
182 case TL_SIZE: return session.config.locale.GetLocale().formattedDataSize(list.at(index.row()).GetSize());
183 case TL_RESOLUTION: return Strings::ToQString(list.at(index.row()).GetResolution());
184 case TL_SEEDERS: return list.at(index.row()).GetSeeders();
185 case TL_LEECHERS: return list.at(index.row()).GetLeechers();
186 case TL_DOWNLOADERS: return list.at(index.row()).GetDownloaders();
187 case TL_DESCRIPTION: return Strings::ToQString(list.at(index.row()).GetDescription());
188 case TL_FILENAME: return Strings::ToQString(list.at(index.row()).GetFilename());
189 case TL_RELEASEDATE: return list.at(index.row()).GetDate();
190 default: return "";
191 }
192 break;
193 case Qt::UserRole:
194 switch (index.column()) {
195 case TL_EPISODE: return Strings::ToInt(list.at(index.row()).GetEpisode(), -1);
196 case TL_SIZE: return list.at(index.row()).GetSize();
197 default: return data(index, Qt::DisplayRole);
198 }
199 break;
200 case Qt::CheckStateRole:
201 switch (index.column()) {
202 case 0: return list.at(index.row()).GetChecked() ? Qt::Checked : Qt::Unchecked;
203 default: return {};
204 }
205 case Qt::SizeHintRole: {
206 switch (index.column()) {
207 default: {
208 const QString d = data(index, Qt::DisplayRole).toString();
209 const QFontMetrics metric = QFontMetrics(QFont());
210
211 return QSize(std::max(metric.horizontalAdvance(d), 100), metric.height());
212 }
213 }
214 break;
215 }
216 case Qt::TextAlignmentRole:
217 switch (index.column()) {
218 case TL_FILENAME:
219 case TL_GROUP:
220 case TL_DESCRIPTION:
221 case TL_RESOLUTION:
222 case TL_TITLE: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
223 case TL_SEEDERS:
224 case TL_LEECHERS:
225 case TL_DOWNLOADERS:
226 case TL_SIZE:
227 case TL_EPISODE:
228 case TL_RELEASEDATE: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
229 default: return {};
230 }
231 break;
232 }
233 return QVariant();
234 }
235
236 Qt::ItemFlags TorrentsPageListModel::flags(const QModelIndex& index) const {
237 if (!index.isValid())
238 return Qt::NoItemFlags;
239
240 const TorrentModelItem& item = list.at(index.row());
241
242 if (item.GetChecked() || index.column() == 0)
243 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
244 else
245 return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
246 }
247
248 TorrentsPage::TorrentsPage(QWidget* parent) : QFrame(parent) {
249 setFrameShape(QFrame::Box);
250 setFrameShadow(QFrame::Sunken);
251
252 QVBoxLayout* layout = new QVBoxLayout(this);
253 layout->setContentsMargins(0, 0, 0, 0);
254 layout->setSpacing(0);
255
256 {
257 /* Toolbar */
258 QToolBar* toolbar = new QToolBar(this);
259 toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
260 toolbar->setIconSize(QSize(16, 16));
261 toolbar->setMovable(false);
262
263 {
264 /* this needs to be stored somewhere to replicate Taiga's
265 "timer" feature */
266 QAction* action = toolbar->addAction(QIcon(":/icons/16x16/arrow-circle-315.png"), tr("&Check new torrents"), [this]{
267 QThreadPool::globalInstance()->start([this] {
268 Refresh();
269 });
270 });
271 }
272
273 toolbar->addSeparator();
274
275 {
276 QAction* action = toolbar->addAction(QIcon(":/icons/16x16/navigation-270-button.png"), tr("Download &marked torrents"));
277 }
278
279 {
280 QAction* action = toolbar->addAction(QIcon(":/icons/16x16/cross-button.png"), tr("&Discard all"));
281 }
282
283 toolbar->addSeparator();
284
285 {
286 QAction* action = toolbar->addAction(QIcon(":/icons/16x16/gear.png"), tr("&Settings"));
287 }
288
289 layout->addWidget(toolbar);
290 }
291
292 {
293 QFrame* line = new QFrame(this);
294 line->setFrameShape(QFrame::HLine);
295 line->setFrameShadow(QFrame::Sunken);
296 line->setLineWidth(1);
297 layout->addWidget(line);
298 }
299
300 {
301 QTreeView* treeview = new QTreeView(this);
302 treeview->setUniformRowHeights(true);
303 treeview->setAllColumnsShowFocus(false);
304 treeview->setAlternatingRowColors(true);
305 treeview->setSortingEnabled(true);
306 treeview->setSelectionMode(QAbstractItemView::ExtendedSelection);
307 treeview->setItemsExpandable(false);
308 treeview->setRootIsDecorated(false);
309 treeview->setContextMenuPolicy(Qt::CustomContextMenu);
310 treeview->setFrameShape(QFrame::NoFrame);
311
312 {
313 sort_model = new TorrentsPageListSortFilter(treeview);
314 model = new TorrentsPageListModel(treeview);
315 sort_model->setSourceModel(model);
316 sort_model->setSortRole(Qt::UserRole);
317 sort_model->setSortCaseSensitivity(Qt::CaseInsensitive);
318 treeview->setModel(sort_model);
319 }
320
321 layout->addWidget(treeview);
322 }
323 }
324
325 void TorrentsPage::Refresh() {
326 if (!model)
327 return;
328 model->RefreshTorrentList();
4 } 329 }
5 330
6 #include "gui/pages/moc_torrents.cpp" 331 #include "gui/pages/moc_torrents.cpp"