mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-24 05:25:37 +00:00
parent
cecf2d28e6
commit
b8cd614775
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -133,6 +133,8 @@ AutoDownloader::AutoDownloader()
|
||||
|
||||
load();
|
||||
|
||||
connect(Session::instance(), &Session::feedURLChanged, this, &AutoDownloader::handleFeedURLChanged);
|
||||
|
||||
m_processingTimer->setSingleShot(true);
|
||||
connect(m_processingTimer, &QTimer::timeout, this, &AutoDownloader::process);
|
||||
|
||||
@ -331,22 +333,28 @@ void AutoDownloader::setDownloadRepacks(const bool enabled)
|
||||
|
||||
void AutoDownloader::process()
|
||||
{
|
||||
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
||||
if (m_processingQueue.isEmpty()) // processing was disabled
|
||||
return;
|
||||
|
||||
processJob(m_processingQueue.takeFirst());
|
||||
if (!m_processingQueue.isEmpty())
|
||||
{
|
||||
// Schedule to process the next torrent (if any)
|
||||
m_processingTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoDownloader::handleTorrentDownloadFinished(const QString &url)
|
||||
{
|
||||
const auto job = m_waitingJobs.take(url);
|
||||
if (!job) return;
|
||||
if (!job)
|
||||
return;
|
||||
|
||||
if (Feed *feed = Session::instance()->feedByURL(job->feedURL))
|
||||
{
|
||||
if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString()))
|
||||
article->markAsRead();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoDownloader::handleTorrentDownloadFailed(const QString &url)
|
||||
@ -361,6 +369,34 @@ void AutoDownloader::handleNewArticle(const Article *article)
|
||||
addJobForArticle(article);
|
||||
}
|
||||
|
||||
void AutoDownloader::handleFeedURLChanged(Feed *feed, const QString &oldURL)
|
||||
{
|
||||
for (AutoDownloadRule &rule : m_rules)
|
||||
{
|
||||
if (const auto i = rule.feedURLs().indexOf(oldURL); i >= 0)
|
||||
{
|
||||
auto feedURLs = rule.feedURLs();
|
||||
feedURLs.replace(i, feed->url());
|
||||
rule.setFeedURLs(feedURLs);
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (QSharedPointer<ProcessingJob> job : asConst(m_processingQueue))
|
||||
{
|
||||
if (job->feedURL == oldURL)
|
||||
job->feedURL = feed->url();
|
||||
}
|
||||
|
||||
for (QSharedPointer<ProcessingJob> job : asConst(m_waitingJobs))
|
||||
{
|
||||
if (job->feedURL == oldURL)
|
||||
job->feedURL = feed->url();
|
||||
}
|
||||
|
||||
store();
|
||||
}
|
||||
|
||||
void AutoDownloader::setRule_impl(const AutoDownloadRule &rule)
|
||||
{
|
||||
m_rules.insert(rule.name(), rule);
|
||||
@ -369,9 +405,10 @@ void AutoDownloader::setRule_impl(const AutoDownloadRule &rule)
|
||||
void AutoDownloader::addJobForArticle(const Article *article)
|
||||
{
|
||||
const QString torrentURL = article->torrentUrl();
|
||||
if (m_waitingJobs.contains(torrentURL)) return;
|
||||
if (m_waitingJobs.contains(torrentURL))
|
||||
return;
|
||||
|
||||
QSharedPointer<ProcessingJob> job(new ProcessingJob);
|
||||
auto job = QSharedPointer<ProcessingJob>::create();
|
||||
job->feedURL = article->feed()->url();
|
||||
job->articleData = article->data();
|
||||
m_processingQueue.append(job);
|
||||
@ -383,9 +420,12 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
||||
{
|
||||
for (AutoDownloadRule &rule : m_rules)
|
||||
{
|
||||
if (!rule.isEnabled()) continue;
|
||||
if (!rule.feedURLs().contains(job->feedURL)) continue;
|
||||
if (!rule.accepts(job->articleData)) continue;
|
||||
if (!rule.isEnabled())
|
||||
continue;
|
||||
if (!rule.feedURLs().contains(job->feedURL))
|
||||
continue;
|
||||
if (!rule.accepts(job->articleData))
|
||||
continue;
|
||||
|
||||
m_dirty = true;
|
||||
storeDeferred();
|
||||
@ -423,12 +463,18 @@ void AutoDownloader::load()
|
||||
QFile rulesFile {(m_fileStorage->storageDir() / Path(RULES_FILE_NAME)).data()};
|
||||
|
||||
if (!rulesFile.exists())
|
||||
{
|
||||
loadRulesLegacy();
|
||||
}
|
||||
else if (rulesFile.open(QFile::ReadOnly))
|
||||
{
|
||||
loadRules(rulesFile.readAll());
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
||||
.arg(rulesFile.fileName(), rulesFile.errorString()), Log::CRITICAL);
|
||||
.arg(rulesFile.fileName(), rulesFile.errorString()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoDownloader::loadRules(const QByteArray &data)
|
||||
@ -442,7 +488,7 @@ void AutoDownloader::loadRules(const QByteArray &data)
|
||||
catch (const ParsingError &error)
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1")
|
||||
.arg(error.message()), Log::CRITICAL);
|
||||
.arg(error.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,7 +506,8 @@ void AutoDownloader::loadRulesLegacy()
|
||||
|
||||
void AutoDownloader::store()
|
||||
{
|
||||
if (!m_dirty) return;
|
||||
if (!m_dirty)
|
||||
return;
|
||||
|
||||
m_dirty = false;
|
||||
m_savingTimer.stop();
|
||||
@ -486,7 +533,8 @@ bool AutoDownloader::isProcessingEnabled() const
|
||||
void AutoDownloader::resetProcessingQueue()
|
||||
{
|
||||
m_processingQueue.clear();
|
||||
if (!isProcessingEnabled()) return;
|
||||
if (!isProcessingEnabled())
|
||||
return;
|
||||
|
||||
for (Article *article : asConst(Session::instance()->rootFolder()->articles()))
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -113,6 +113,7 @@ namespace RSS
|
||||
void handleTorrentDownloadFinished(const QString &url);
|
||||
void handleTorrentDownloadFailed(const QString &url);
|
||||
void handleNewArticle(const Article *article);
|
||||
void handleFeedURLChanged(Feed *feed, const QString &oldURL);
|
||||
|
||||
private:
|
||||
void timerEvent(QTimerEvent *event) override;
|
||||
|
@ -455,6 +455,13 @@ Path Feed::iconPath() const
|
||||
return m_iconPath;
|
||||
}
|
||||
|
||||
void Feed::setURL(const QString &url)
|
||||
{
|
||||
const QString oldURL = m_url;
|
||||
m_url = url;
|
||||
emit urlChanged(oldURL);
|
||||
}
|
||||
|
||||
QJsonValue Feed::toJsonValue(const bool withData) const
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
|
@ -91,6 +91,7 @@ namespace RSS
|
||||
void iconLoaded(Feed *feed = nullptr);
|
||||
void titleChanged(Feed *feed = nullptr);
|
||||
void stateChanged(Feed *feed = nullptr);
|
||||
void urlChanged(const QString &oldURL);
|
||||
|
||||
private slots:
|
||||
void handleSessionProcessingEnabledChanged(bool enabled);
|
||||
@ -113,12 +114,13 @@ namespace RSS
|
||||
void decreaseUnreadCount();
|
||||
void downloadIcon();
|
||||
int updateArticles(const QList<QVariantHash> &loadedArticles);
|
||||
void setURL(const QString &url);
|
||||
|
||||
Session *m_session = nullptr;
|
||||
Private::Parser *m_parser = nullptr;
|
||||
Private::FeedSerializer *m_serializer = nullptr;
|
||||
const QUuid m_uid;
|
||||
const QString m_url;
|
||||
QString m_url;
|
||||
QString m_title;
|
||||
QString m_lastBuildDate;
|
||||
bool m_hasError = false;
|
||||
|
@ -156,10 +156,39 @@ nonstd::expected<void, QString> Session::addFeed(const QString &url, const QStri
|
||||
return result.get_unexpected();
|
||||
|
||||
const auto destFolder = result.value();
|
||||
addItem(new Feed(generateUID(), url, path, this), destFolder);
|
||||
auto *feed = new Feed(generateUID(), url, path, this);
|
||||
addItem(feed, destFolder);
|
||||
store();
|
||||
if (isProcessingEnabled())
|
||||
feedByURL(url)->refresh();
|
||||
feed->refresh();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> Session::setFeedURL(const QString &path, const QString &url)
|
||||
{
|
||||
auto *feed = qobject_cast<Feed *>(m_itemsByPath.value(path));
|
||||
if (!feed)
|
||||
return nonstd::make_unexpected(tr("Feed doesn't exist: %1.").arg(path));
|
||||
|
||||
return setFeedURL(feed, url);
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> Session::setFeedURL(Feed *feed, const QString &url)
|
||||
{
|
||||
Q_ASSERT(feed);
|
||||
|
||||
if (url == feed->url())
|
||||
return {};
|
||||
|
||||
if (m_feedsByURL.contains(url))
|
||||
return nonstd::make_unexpected(tr("RSS feed with given URL already exists: %1.").arg(url));
|
||||
|
||||
m_feedsByURL[url] = m_feedsByURL.take(feed->url());
|
||||
feed->setURL(url);
|
||||
store();
|
||||
if (isProcessingEnabled())
|
||||
feed->refresh();
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -409,6 +438,16 @@ void Session::addItem(Item *item, Folder *destFolder)
|
||||
connect(feed, &Feed::titleChanged, this, &Session::handleFeedTitleChanged);
|
||||
connect(feed, &Feed::iconLoaded, this, &Session::feedIconLoaded);
|
||||
connect(feed, &Feed::stateChanged, this, &Session::feedStateChanged);
|
||||
connect(feed, &Feed::urlChanged, this, [this, feed](const QString &oldURL)
|
||||
{
|
||||
if (feed->name() == oldURL)
|
||||
{
|
||||
// If feed still use an URL as a name trying to rename it to match new URL...
|
||||
moveItem(feed, Item::joinPath(Item::parentPath(feed->path()), feed->url()));
|
||||
}
|
||||
|
||||
emit feedURLChanged(feed, oldURL);
|
||||
});
|
||||
m_feedsByUID[feed->uid()] = feed;
|
||||
m_feedsByURL[feed->url()] = feed;
|
||||
}
|
||||
@ -502,9 +541,11 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
|
||||
void Session::handleFeedTitleChanged(Feed *feed)
|
||||
{
|
||||
if (feed->name() == feed->url())
|
||||
{
|
||||
// Now we have something better than a URL.
|
||||
// Trying to rename feed...
|
||||
moveItem(feed, Item::joinPath(Item::parentPath(feed->path()), feed->title()));
|
||||
}
|
||||
}
|
||||
|
||||
QUuid Session::generateUID() const
|
||||
|
@ -116,6 +116,8 @@ namespace RSS
|
||||
|
||||
nonstd::expected<void, QString> addFolder(const QString &path);
|
||||
nonstd::expected<void, QString> addFeed(const QString &url, const QString &path);
|
||||
nonstd::expected<void, QString> setFeedURL(const QString &path, const QString &url);
|
||||
nonstd::expected<void, QString> setFeedURL(Feed *feed, const QString &url);
|
||||
nonstd::expected<void, QString> moveItem(const QString &itemPath, const QString &destPath);
|
||||
nonstd::expected<void, QString> moveItem(Item *item, const QString &destPath);
|
||||
nonstd::expected<void, QString> removeItem(const QString &itemPath);
|
||||
@ -138,6 +140,7 @@ namespace RSS
|
||||
void itemAboutToBeRemoved(Item *item);
|
||||
void feedIconLoaded(Feed *feed);
|
||||
void feedStateChanged(Feed *feed);
|
||||
void feedURLChanged(Feed *feed, const QString &oldURL);
|
||||
|
||||
private slots:
|
||||
void handleItemAboutToBeDestroyed(Item *item);
|
||||
|
@ -65,6 +65,7 @@ RSSWidget::RSSWidget(QWidget *parent)
|
||||
m_ui->actionCopyFeedURL->setIcon(UIThemeManager::instance()->getIcon(u"edit-copy"_qs));
|
||||
m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs));
|
||||
m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs));
|
||||
m_ui->actionEditFeedURL->setIcon(UIThemeManager::instance()->getIcon(u"edit-rename"_qs));
|
||||
m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"mail-mark-read"_qs));
|
||||
m_ui->actionNewFolder->setIcon(UIThemeManager::instance()->getIcon(u"folder-new"_qs));
|
||||
m_ui->actionNewSubscription->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
|
||||
@ -101,6 +102,7 @@ RSSWidget::RSSWidget(QWidget *parent)
|
||||
// Feeds list actions
|
||||
connect(m_ui->actionDelete, &QAction::triggered, this, &RSSWidget::deleteSelectedItems);
|
||||
connect(m_ui->actionRename, &QAction::triggered, this, &RSSWidget::renameSelectedRSSItem);
|
||||
connect(m_ui->actionEditFeedURL, &QAction::triggered, this, &RSSWidget::editSelectedRSSFeedURL);
|
||||
connect(m_ui->actionUpdate, &QAction::triggered, this, &RSSWidget::refreshSelectedItems);
|
||||
connect(m_ui->actionNewFolder, &QAction::triggered, this, &RSSWidget::askNewFolder);
|
||||
connect(m_ui->actionNewSubscription, &QAction::triggered, this, &RSSWidget::on_newFeedButton_clicked);
|
||||
@ -158,12 +160,15 @@ void RSSWidget::displayRSSListMenu(const QPoint &pos)
|
||||
|
||||
if (selectedItems.size() == 1)
|
||||
{
|
||||
if (selectedItems.first() != m_feedListWidget->stickyUnreadItem())
|
||||
QTreeWidgetItem *selectedItem = selectedItems.first();
|
||||
if (selectedItem != m_feedListWidget->stickyUnreadItem())
|
||||
{
|
||||
menu->addAction(m_ui->actionRename);
|
||||
if (m_feedListWidget->isFeed(selectedItem))
|
||||
menu->addAction(m_ui->actionEditFeedURL);
|
||||
menu->addAction(m_ui->actionDelete);
|
||||
menu->addSeparator();
|
||||
if (m_feedListWidget->isFolder(selectedItems.first()))
|
||||
if (m_feedListWidget->isFolder(selectedItem))
|
||||
menu->addAction(m_ui->actionNewFolder);
|
||||
}
|
||||
}
|
||||
@ -420,6 +425,29 @@ void RSSWidget::renameSelectedRSSItem()
|
||||
} while (!ok);
|
||||
}
|
||||
|
||||
void RSSWidget::editSelectedRSSFeedURL()
|
||||
{
|
||||
QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
|
||||
if (selectedItems.size() != 1)
|
||||
return;
|
||||
|
||||
QTreeWidgetItem *item = selectedItems.first();
|
||||
RSS::Feed *rssFeed = qobject_cast<RSS::Feed *>(m_feedListWidget->getRSSItem(item));
|
||||
Q_ASSERT(rssFeed);
|
||||
if (Q_UNLIKELY(!rssFeed))
|
||||
return;
|
||||
|
||||
bool ok = false;
|
||||
QString newURL = AutoExpandableDialog::getText(this, tr("Please type a RSS feed URL")
|
||||
, tr("Feed URL:"), QLineEdit::Normal, rssFeed->url(), &ok).trimmed();
|
||||
if (!ok || newURL.isEmpty())
|
||||
return;
|
||||
|
||||
const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(rssFeed, newURL);
|
||||
if (!result)
|
||||
QMessageBox::warning(this, u"qBittorrent"_qs, result.error(), QMessageBox::Ok);
|
||||
}
|
||||
|
||||
void RSSWidget::refreshSelectedItems()
|
||||
{
|
||||
for (QTreeWidgetItem *item : asConst(m_feedListWidget->selectedItems()))
|
||||
|
@ -66,6 +66,7 @@ private slots:
|
||||
void displayRSSListMenu(const QPoint &pos);
|
||||
void displayItemsListMenu();
|
||||
void renameSelectedRSSItem();
|
||||
void editSelectedRSSFeedURL();
|
||||
void refreshSelectedItems();
|
||||
void copySelectedFeedsURL();
|
||||
void handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem);
|
||||
|
@ -197,6 +197,14 @@
|
||||
<string>New folder...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEditFeedURL">
|
||||
<property name="text">
|
||||
<string>Edit feed URL...</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Edit feed URL</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -66,6 +66,17 @@ void RSSController::addFeedAction()
|
||||
throw APIError(APIErrorType::Conflict, result.error());
|
||||
}
|
||||
|
||||
void RSSController::setFeedURLAction()
|
||||
{
|
||||
requireParams({u"path"_qs, u"url"_qs});
|
||||
|
||||
const QString path = params()[u"path"_qs].trimmed();
|
||||
const QString url = params()[u"url"_qs].trimmed();
|
||||
const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(path, url);
|
||||
if (!result)
|
||||
throw APIError(APIErrorType::Conflict, result.error());
|
||||
}
|
||||
|
||||
void RSSController::removeItemAction()
|
||||
{
|
||||
requireParams({u"path"_qs});
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
private slots:
|
||||
void addFolderAction();
|
||||
void addFeedAction();
|
||||
void setFeedURLAction();
|
||||
void removeItemAction();
|
||||
void moveItemAction();
|
||||
void itemsAction();
|
||||
|
@ -148,6 +148,7 @@ private:
|
||||
{{u"auth"_qs, u"login"_qs}, Http::METHOD_POST},
|
||||
{{u"auth"_qs, u"logout"_qs}, Http::METHOD_POST},
|
||||
{{u"rss"_qs, u"addFeed"_qs}, Http::METHOD_POST},
|
||||
{{u"rss"_qs, u"setFeedURL"_qs}, Http::METHOD_POST},
|
||||
{{u"rss"_qs, u"addFolder"_qs}, Http::METHOD_POST},
|
||||
{{u"rss"_qs, u"markAsRead"_qs}, Http::METHOD_POST},
|
||||
{{u"rss"_qs, u"moveItem"_qs}, Http::METHOD_POST},
|
||||
|
Loading…
x
Reference in New Issue
Block a user