Mercurial > minori
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); |
