comparison src/gui/pages/torrents.cc @ 370:ea3a74ed2ef9

*: hm, last commit wasn't quite finished?
author Paper <paper@tflc.us>
date Fri, 25 Jul 2025 10:22:04 -0400
parents 8d45d892be88
children
comparison
equal deleted inserted replaced
369:47c9f8502269 370:ea3a74ed2ef9
30 * It differs from Taiga in that it uses tabs instead of 30 * It differs from Taiga in that it uses tabs instead of
31 * those "groups", but those are custom painted and a pain in the ass to 31 * those "groups", but those are custom painted and a pain in the ass to
32 * maintain over multiple platforms. 32 * maintain over multiple platforms.
33 */ 33 */
34 34
35 TorrentsPageListSortFilter::TorrentsPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { 35 TorrentsPageListSortFilter::TorrentsPageListSortFilter(QObject *parent) : QSortFilterProxyModel(parent)
36 } 36 {
37 37 }
38 bool TorrentsPageListSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { 38
39 bool TorrentsPageListSortFilter::lessThan(const QModelIndex &l, const QModelIndex &r) const
40 {
39 QVariant left = sourceModel()->data(l, sortRole()); 41 QVariant left = sourceModel()->data(l, sortRole());
40 QVariant right = sourceModel()->data(r, sortRole()); 42 QVariant right = sourceModel()->data(r, sortRole());
41 43
42 switch (left.userType()) { 44 switch (left.userType()) {
43 case QMetaType::Int: 45 case QMetaType::Int:
50 } 52 }
51 } 53 }
52 54
53 /* -------------------------------------------- */ 55 /* -------------------------------------------- */
54 56
55 TorrentsPageListModel::TorrentsPageListModel(QObject* parent) : QAbstractListModel(parent) { 57 TorrentsPageListModel::TorrentsPageListModel(QObject *parent) : QAbstractListModel(parent)
56 } 58 {
57 59 }
58 void TorrentsPageListModel::DownloadTorrents(QItemSelection selection) { 60
61 void TorrentsPageListModel::DownloadTorrents(QItemSelection selection)
62 {
59 const auto indexes = selection.indexes(); 63 const auto indexes = selection.indexes();
60 64
61 for (const auto& index : indexes) { 65 for (const auto &index : indexes) {
62 /* a torrent file IS literally text... */ 66 /* a torrent file IS literally text... */
63 const std::string link = list.at(index.row()).GetLink(); 67 const std::string link = list.at(index.row()).GetLink();
64 const std::string filename = list.at(index.row()).GetFilename() + ".torrent"; 68 const std::string filename = list.at(index.row()).GetFilename() + ".torrent";
65 69
66 const std::filesystem::path torrents_dir = Filesystem::GetTorrentsPath(); 70 const std::filesystem::path torrents_dir = Filesystem::GetTorrentsPath();
67 std::filesystem::create_directories(torrents_dir); 71 std::filesystem::create_directories(torrents_dir);
68 72
69 /* this sucks */ 73 /* this sucks */
70 HTTP::RequestThread* thread = new HTTP::RequestThread(link, {}, "", HTTP::Type::Get, this); 74 HTTP::RequestThread *thread = new HTTP::RequestThread(link, {}, "", HTTP::Type::Get, this);
71 75
72 connect(thread, &HTTP::RequestThread::ReceivedData, this, [this, torrents_dir, filename](const QByteArray& data) { 76 connect(thread, &HTTP::RequestThread::ReceivedData, this,
73 std::ofstream file(torrents_dir / filename, std::ofstream::out | std::ofstream::trunc); 77 [this, torrents_dir, filename](const QByteArray &data) {
74 if (!file) 78 std::ofstream file(torrents_dir / filename, std::ofstream::out | std::ofstream::trunc);
75 return; // wat 79 if (!file)
76 80 return; // wat
77 file.write(data.data(), data.size()); 81
78 file.close(); 82 file.write(data.data(), data.size());
79 }); 83 file.close();
84 });
80 connect(thread, &HTTP::RequestThread::finished, thread, &HTTP::RequestThread::deleteLater); 85 connect(thread, &HTTP::RequestThread::finished, thread, &HTTP::RequestThread::deleteLater);
81 86
82 thread->start(); 87 thread->start();
83 } 88 }
84 } 89 }
85 90
86 QByteArray TorrentsPageListModel::DownloadTorrentList() { 91 QByteArray TorrentsPageListModel::DownloadTorrentList()
92 {
87 return HTTP::Request(session.config.torrents.feed_link); 93 return HTTP::Request(session.config.torrents.feed_link);
88 } 94 }
89 95
90 void TorrentsPageListModel::ParseFeedDescription(const std::string& description, Torrent& torrent) { 96 void TorrentsPageListModel::ParseFeedDescription(const std::string &description, Torrent &torrent)
97 {
91 /* Parse description... */ 98 /* Parse description... */
92 enum class Keys { 99 enum class Keys {
93 SIZE, 100 SIZE,
94 AUTHORIZED, 101 AUTHORIZED,
95 SUBMITTER, 102 SUBMITTER,
124 default: break; 131 default: break;
125 } 132 }
126 } 133 }
127 } 134 }
128 135
129 void TorrentsPageListModel::ParseTorrentList(const QByteArray& ba) { 136 void TorrentsPageListModel::ParseTorrentList(const QByteArray &ba)
137 {
130 QDomDocument doc; 138 QDomDocument doc;
131 QDomNode node; 139 QDomNode node;
132 QDomNodeList node_nodes; 140 QDomNodeList node_nodes;
133 { 141 {
134 QString err; 142 QString err;
135 int err_ln; 143 int err_ln;
136 int err_col; 144 int err_col;
137 145
138 if (!doc.setContent(ba, &err, &err_ln, &err_col)) { 146 if (!doc.setContent(ba, &err, &err_ln, &err_col)) {
139 session.SetStatusBar(Strings::ToUtf8String(tr("Torrents: Failed to parse XML with error %1 at line %2, column %3").arg(err, QString::number(err_ln), QString::number(err_col)))); 147 session.SetStatusBar(
148 Strings::ToUtf8String(tr("Torrents: Failed to parse XML with error %1 at line %2, column %3")
149 .arg(err, QString::number(err_ln), QString::number(err_col))));
140 return; // peace out 150 return; // peace out
141 } 151 }
142 } 152 }
143 153
144 /* my extra special dumb hack. */ 154 /* my extra special dumb hack. */
151 161
152 list.clear(); 162 list.clear();
153 163
154 node = doc; 164 node = doc;
155 165
156 for (const auto& n : {"rss", "channel"}) { 166 for (const auto &n : {"rss", "channel"}) {
157 node = node.namedItem(n); 167 node = node.namedItem(n);
158 if (node.isNull()) { std::cout << n << std::endl; goto end; } 168 if (node.isNull()) {
159 } 169 std::cout << n << std::endl;
160 170 goto end;
161 if (!node.hasChildNodes()) { std::cout << "no child nodes" << std::endl; goto end; } 171 }
172 }
173
174 if (!node.hasChildNodes()) {
175 std::cout << "no child nodes" << std::endl;
176 goto end;
177 }
162 178
163 node_nodes = node.childNodes(); 179 node_nodes = node.childNodes();
164 180
165 for (int c = 0; c < node_nodes.count(); c++) { 181 for (int c = 0; c < node_nodes.count(); c++) {
166 const QDomNode item = node_nodes.at(c); 182 const QDomNode item = node_nodes.at(c);
167 if (!item.isElement() || item.nodeName() != "item") 183 if (!item.isElement() || item.nodeName() != "item")
168 continue; 184 continue;
169 185
170 const QDomNode title = item.namedItem("title"); 186 const QDomNode title = item.namedItem("title");
171 if (!title.isElement()) continue; 187 if (!title.isElement())
188 continue;
172 const QDomNode description = item.namedItem("description"); 189 const QDomNode description = item.namedItem("description");
173 if (!description.isElement()) continue; 190 if (!description.isElement())
191 continue;
174 const QDomNode link = item.namedItem("link"); 192 const QDomNode link = item.namedItem("link");
175 if (!link.isElement()) continue; 193 if (!link.isElement())
194 continue;
176 const QDomNode guid = item.namedItem("guid"); 195 const QDomNode guid = item.namedItem("guid");
177 if (!guid.isElement()) continue; 196 if (!guid.isElement())
197 continue;
178 const QDomNode pubDate = item.namedItem("pubDate"); 198 const QDomNode pubDate = item.namedItem("pubDate");
179 if (!pubDate.isElement()) continue; 199 if (!pubDate.isElement())
200 continue;
180 201
181 TorrentModelItem torrent; 202 TorrentModelItem torrent;
182 torrent.SetFilename(Strings::ToUtf8String(title.toElement().text())); /* "title" == filename */ 203 torrent.SetFilename(Strings::ToUtf8String(title.toElement().text())); /* "title" == filename */
183 { 204 {
184 anitomy::Anitomy anitomy; 205 anitomy::Anitomy anitomy;
185 anitomy.Parse(torrent.GetFilename()); 206 anitomy.Parse(torrent.GetFilename());
186 207
187 const auto& elements = anitomy.elements(); 208 const auto &elements = anitomy.elements();
188 209
189 /* todo: patch Anitomy so that it doesn't use wide strings */ 210 /* todo: patch Anitomy so that it doesn't use wide strings */
190 torrent.SetTitle(Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle))); 211 torrent.SetTitle(Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)));
191 std::string episode = Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber)); 212 std::string episode = Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber));
192 Strings::RemoveLeadingChars(episode, '0'); 213 Strings::RemoveLeadingChars(episode, '0');
207 228
208 end: 229 end:
209 endResetModel(); 230 endResetModel();
210 } 231 }
211 232
212 void TorrentsPageListModel::RefreshTorrentList() { 233 void TorrentsPageListModel::RefreshTorrentList()
234 {
213 ParseTorrentList(DownloadTorrentList()); 235 ParseTorrentList(DownloadTorrentList());
214 } 236 }
215 237
216 int TorrentsPageListModel::rowCount(const QModelIndex& parent) const { 238 int TorrentsPageListModel::rowCount(const QModelIndex &parent) const
239 {
217 return list.size(); 240 return list.size();
218 (void)(parent); 241 (void)(parent);
219 } 242 }
220 243
221 int TorrentsPageListModel::columnCount(const QModelIndex& parent) const { 244 int TorrentsPageListModel::columnCount(const QModelIndex &parent) const
245 {
222 return NB_COLUMNS; 246 return NB_COLUMNS;
223 (void)(parent); 247 (void)(parent);
224 } 248 }
225 249
226 QVariant TorrentsPageListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { 250 QVariant TorrentsPageListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
251 {
227 switch (role) { 252 switch (role) {
228 case Qt::DisplayRole: { 253 case Qt::DisplayRole: {
229 switch (section) { 254 switch (section) {
230 case TL_TITLE: return tr("Anime title"); 255 case TL_TITLE: return tr("Anime title");
231 case TL_EPISODE: return tr("Episode"); 256 case TL_EPISODE: return tr("Episode");
261 } 286 }
262 } 287 }
263 return QAbstractListModel::headerData(section, orientation, role); 288 return QAbstractListModel::headerData(section, orientation, role);
264 } 289 }
265 290
266 bool TorrentsPageListModel::setData(const QModelIndex& index, const QVariant& value, int role) { 291 bool TorrentsPageListModel::setData(const QModelIndex &index, const QVariant &value, int role)
267 TorrentModelItem& item = list.at(index.row()); 292 {
293 TorrentModelItem &item = list.at(index.row());
268 294
269 if (index.column() == 0) { 295 if (index.column() == 0) {
270 switch (role) { 296 switch (role) {
271 case Qt::EditRole: return false; 297 case Qt::EditRole: return false;
272 case Qt::CheckStateRole: 298 case Qt::CheckStateRole:
277 } 303 }
278 304
279 return QAbstractItemModel::setData(index, value, role); 305 return QAbstractItemModel::setData(index, value, role);
280 } 306 }
281 307
282 QVariant TorrentsPageListModel::data(const QModelIndex& index, int role) const { 308 QVariant TorrentsPageListModel::data(const QModelIndex &index, int role) const
309 {
283 if (!index.isValid()) 310 if (!index.isValid())
284 return QVariant(); 311 return QVariant();
285 312
286 const TorrentModelItem& item = list.at(index.row()); 313 const TorrentModelItem &item = list.at(index.row());
287 314
288 switch (role) { 315 switch (role) {
289 case Qt::DisplayRole: 316 case Qt::DisplayRole:
290 switch (index.column()) { 317 switch (index.column()) {
291 case TL_TITLE: return Strings::ToQString(item.GetTitle()); 318 case TL_TITLE: return Strings::ToQString(item.GetTitle());
342 break; 369 break;
343 } 370 }
344 return QVariant(); 371 return QVariant();
345 } 372 }
346 373
347 Qt::ItemFlags TorrentsPageListModel::flags(const QModelIndex& index) const { 374 Qt::ItemFlags TorrentsPageListModel::flags(const QModelIndex &index) const
375 {
348 if (!index.isValid()) 376 if (!index.isValid())
349 return Qt::NoItemFlags; 377 return Qt::NoItemFlags;
350 378
351 return Qt::ItemIsEnabled | Qt::ItemIsSelectable; 379 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
352 } 380 }
353 381
354 TorrentsPage::TorrentsPage(QWidget* parent) : QFrame(parent) { 382 TorrentsPage::TorrentsPage(QWidget *parent) : QFrame(parent)
383 {
355 setFrameShape(QFrame::Box); 384 setFrameShape(QFrame::Box);
356 setFrameShadow(QFrame::Sunken); 385 setFrameShadow(QFrame::Sunken);
357 386
358 QVBoxLayout* layout = new QVBoxLayout(this); 387 QVBoxLayout *layout = new QVBoxLayout(this);
359 layout->setContentsMargins(0, 0, 0, 0); 388 layout->setContentsMargins(0, 0, 0, 0);
360 layout->setSpacing(0); 389 layout->setSpacing(0);
361 390
362 { 391 {
363 /* Toolbar */ 392 /* Toolbar */
364 QToolBar* toolbar = new QToolBar(this); 393 QToolBar *toolbar = new QToolBar(this);
365 toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 394 toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
366 toolbar->setIconSize(QSize(16, 16)); 395 toolbar->setIconSize(QSize(16, 16));
367 toolbar->setMovable(false); 396 toolbar->setMovable(false);
368 397
369 { 398 {
378 { 407 {
379 toolbar->addAction(QIcon(":/icons/16x16/navigation-270-button.png"), tr("Download &marked torrents"), 408 toolbar->addAction(QIcon(":/icons/16x16/navigation-270-button.png"), tr("Download &marked torrents"),
380 [this] { DownloadSelection(); }); 409 [this] { DownloadSelection(); });
381 } 410 }
382 411
383 { toolbar->addAction(QIcon(":/icons/16x16/cross-button.png"), tr("&Discard all")); } 412 {
413 toolbar->addAction(QIcon(":/icons/16x16/cross-button.png"), tr("&Discard all"));
414 }
384 415
385 toolbar->addSeparator(); 416 toolbar->addSeparator();
386 417
387 { toolbar->addAction(QIcon(":/icons/16x16/gear.png"), tr("&Settings")); } 418 {
419 toolbar->addAction(QIcon(":/icons/16x16/gear.png"), tr("&Settings"));
420 }
388 421
389 layout->addWidget(toolbar); 422 layout->addWidget(toolbar);
390 } 423 }
391 424
392 { 425 {
393 QFrame* line = new QFrame(this); 426 QFrame *line = new QFrame(this);
394 line->setFrameShape(QFrame::HLine); 427 line->setFrameShape(QFrame::HLine);
395 line->setFrameShadow(QFrame::Sunken); 428 line->setFrameShadow(QFrame::Sunken);
396 line->setLineWidth(1); 429 line->setLineWidth(1);
397 layout->addWidget(line); 430 layout->addWidget(line);
398 } 431 }
435 468
436 layout->addWidget(treeview); 469 layout->addWidget(treeview);
437 } 470 }
438 } 471 }
439 472
440 void TorrentsPage::DownloadSelection() { 473 void TorrentsPage::DownloadSelection()
474 {
441 if (!model) 475 if (!model)
442 return; 476 return;
443 477
444 const QItemSelection selection = sort_model->mapSelectionToSource(treeview->selectionModel()->selection()); 478 const QItemSelection selection = sort_model->mapSelectionToSource(treeview->selectionModel()->selection());
445 479
446 model->DownloadTorrents(selection); 480 model->DownloadTorrents(selection);
447 } 481 }
448 482
449 void TorrentsPage::Refresh() { 483 void TorrentsPage::Refresh()
484 {
450 if (!model) 485 if (!model)
451 return; 486 return;
452 487
453 HTTP::RequestThread* thread = new HTTP::RequestThread(session.config.torrents.feed_link); 488 HTTP::RequestThread *thread = new HTTP::RequestThread(session.config.torrents.feed_link);
454 489
455 connect(thread, &HTTP::RequestThread::ReceivedData, this, [&](const QByteArray& ba) { 490 connect(thread, &HTTP::RequestThread::ReceivedData, this, [&](const QByteArray &ba) {
456 /* This is to make sure we aren't in a different thread 491 /* This is to make sure we aren't in a different thread
457 * messing around with GUI stuff 492 * messing around with GUI stuff
458 */ 493 */
459 treeview->setUpdatesEnabled(false); 494 treeview->setUpdatesEnabled(false);
460 model->ParseTorrentList(ba); 495 model->ParseTorrentList(ba);