Add file name filter/blacklist

Blacklist filtered file names from being downloaded from torrent(s).
Files matching any of the filters in this list will have their priority automatically set to "Do not download".
See Options > Downloads >Do not download.

Closes #3369.
PR #17106.
This commit is contained in:
mxtsdev 2022-06-09 22:37:46 -07:00 committed by GitHub
commit 5e6174c087
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 144 additions and 17 deletions

View file

@ -414,6 +414,7 @@ Session::Session(QObject *parent)
, m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_qs), 90) , m_peerTurnoverCutoff(BITTORRENT_SESSION_KEY(u"PeerTurnoverCutOff"_qs), 90)
, m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_qs), 300) , m_peerTurnoverInterval(BITTORRENT_SESSION_KEY(u"PeerTurnoverInterval"_qs), 300)
, m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_qs), 500) , m_requestQueueSize(BITTORRENT_SESSION_KEY(u"RequestQueueSize"_qs), 500)
, m_excludedFileNames(BITTORRENT_SESSION_KEY(u"ExcludedFileNames"_qs))
, m_bannedIPs(u"State/BannedIPs"_qs , m_bannedIPs(u"State/BannedIPs"_qs
, QStringList() , QStringList()
, [](const QStringList &value) , [](const QStringList &value)
@ -466,6 +467,7 @@ Session::Session(QObject *parent)
enqueueRefresh(); enqueueRefresh();
updateSeedingLimitTimer(); updateSeedingLimitTimer();
populateAdditionalTrackers(); populateAdditionalTrackers();
populateExcludedFileNamesRegExpList();
enableTracker(isTrackerEnabled()); enableTracker(isTrackerEnabled());
@ -2244,19 +2246,30 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
} }
const auto nativeIndexes = torrentInfo.nativeIndexes(); const auto nativeIndexes = torrentInfo.nativeIndexes();
if (!filePaths.isEmpty()) for (int index = 0; index < filePaths.size(); ++index)
{ p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
for (int index = 0; index < filePaths.size(); ++index)
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
}
Q_ASSERT(p.file_priorities.empty()); Q_ASSERT(p.file_priorities.empty());
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
// Use qBittorrent default priority rather than libtorrent's (4) // Use qBittorrent default priority rather than libtorrent's (4)
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal)); p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i) if (addTorrentParams.filePriorities.size() == 0)
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]); {
// Check file name blacklist when priorities are not explicitly set
for (int i = 0; i < filePaths.size(); ++i)
{
if (isFilenameExcluded(filePaths.at(i).filename()))
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
}
}
else
{
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
}
p.ti = torrentInfo.nativeInfo(); p.ti = torrentInfo.nativeInfo();
} }
@ -3078,6 +3091,43 @@ void Session::setIPFilterFile(const Path &path)
} }
} }
QStringList Session::excludedFileNames() const
{
return m_excludedFileNames;
}
void Session::setExcludedFileNames(const QStringList &excludedFileNames)
{
if (excludedFileNames != m_excludedFileNames)
{
m_excludedFileNames = excludedFileNames;
populateExcludedFileNamesRegExpList();
}
}
void Session::populateExcludedFileNamesRegExpList()
{
const QStringList excludedNames = excludedFileNames();
m_excludedFileNamesRegExpList.clear();
m_excludedFileNamesRegExpList.reserve(excludedNames.size());
for (const QString &str : excludedNames)
{
const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
m_excludedFileNamesRegExpList.append(re);
}
}
bool Session::isFilenameExcluded(const QString &fileName) const
{
return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
{
return re.match(fileName).hasMatch();
});
}
void Session::setBannedIPs(const QStringList &newList) void Session::setBannedIPs(const QStringList &newList)
{ {
if (newList == m_bannedIPs) if (newList == m_bannedIPs)

View file

@ -455,6 +455,9 @@ namespace BitTorrent
void setBlockPeersOnPrivilegedPorts(bool enabled); void setBlockPeersOnPrivilegedPorts(bool enabled);
bool isTrackerFilteringEnabled() const; bool isTrackerFilteringEnabled() const;
void setTrackerFilteringEnabled(bool enabled); void setTrackerFilteringEnabled(bool enabled);
QStringList excludedFileNames() const;
void setExcludedFileNames(const QStringList &newList);
bool isFilenameExcluded(const QString &fileName) const;
QStringList bannedIPs() const; QStringList bannedIPs() const;
void setBannedIPs(const QStringList &newList); void setBannedIPs(const QStringList &newList);
ResumeDataStorageType resumeDataStorageType() const; ResumeDataStorageType resumeDataStorageType() const;
@ -624,6 +627,7 @@ namespace BitTorrent
void applyOSMemoryPriority() const; void applyOSMemoryPriority() const;
#endif #endif
void processTrackerStatuses(); void processTrackerStatuses();
void populateExcludedFileNamesRegExpList();
bool loadTorrent(LoadTorrentParams params); bool loadTorrent(LoadTorrentParams params);
LoadTorrentParams initLoadTorrentParams(const AddTorrentParams &addTorrentParams); LoadTorrentParams initLoadTorrentParams(const AddTorrentParams &addTorrentParams);
@ -778,6 +782,7 @@ namespace BitTorrent
CachedSettingValue<int> m_peerTurnoverCutoff; CachedSettingValue<int> m_peerTurnoverCutoff;
CachedSettingValue<int> m_peerTurnoverInterval; CachedSettingValue<int> m_peerTurnoverInterval;
CachedSettingValue<int> m_requestQueueSize; CachedSettingValue<int> m_requestQueueSize;
CachedSettingValue<QStringList> m_excludedFileNames;
CachedSettingValue<QStringList> m_bannedIPs; CachedSettingValue<QStringList> m_bannedIPs;
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType; CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType;
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
@ -792,6 +797,7 @@ namespace BitTorrent
int m_numResumeData = 0; int m_numResumeData = 0;
int m_extraLimit = 0; int m_extraLimit = 0;
QVector<TrackerEntry> m_additionalTrackerList; QVector<TrackerEntry> m_additionalTrackerList;
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
bool m_refreshEnqueued = false; bool m_refreshEnqueued = false;
QTimer *m_seedingLimitTimer = nullptr; QTimer *m_seedingLimitTimer = nullptr;

View file

@ -1519,9 +1519,8 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
m_torrentInfo = TorrentInfo(*metadata); m_torrentInfo = TorrentInfo(*metadata);
m_filePriorities.reserve(filesCount()); m_filePriorities.reserve(filesCount());
const auto nativeIndexes = m_torrentInfo.nativeIndexes(); const auto nativeIndexes = m_torrentInfo.nativeIndexes();
const std::vector<lt::download_priority_t> filePriorities = p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
resized(p.file_priorities, metadata->files().num_files() , LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
, LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount()); m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
@ -1529,17 +1528,20 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
{ {
const auto nativeIndex = nativeIndexes.at(i); const auto nativeIndex = nativeIndexes.at(i);
const Path filePath = fileNames.at(i); const Path actualFilePath = fileNames.at(i);
p.renamed_files[nativeIndex] = filePath.toString().toStdString(); p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
m_filePaths.append(filePath.removedExtension(QB_EXT)); const Path filePath = actualFilePath.removedExtension(QB_EXT);
m_filePaths.append(filePath);
const auto priority = LT::fromNative(filePriorities[LT::toUnderlyingType(nativeIndex)]); lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
nativePriority = lt::dont_download;
const auto priority = LT::fromNative(nativePriority);
m_filePriorities.append(priority); m_filePriorities.append(priority);
} }
p.save_path = savePath.toString().toStdString(); p.save_path = savePath.toString().toStdString();
p.ti = metadata; p.ti = metadata;
p.file_priorities = filePriorities;
reload(); reload();

View file

@ -975,6 +975,21 @@ void AddNewTorrentDialog::setupTreeview()
currentIndex = m_contentModel->index(0, 0, currentIndex); currentIndex = m_contentModel->index(0, 0, currentIndex);
m_ui->contentTreeView->setExpanded(currentIndex, true); m_ui->contentTreeView->setExpanded(currentIndex, true);
} }
// Check file name blacklist for torrents that are manually added
QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities();
Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
for (int i = 0; i < priorities.size(); ++i)
{
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
continue;
if (BitTorrent::Session::instance()->isFilenameExcluded(m_torrentInfo.filePath(i).filename()))
priorities[i] = BitTorrent::DownloadPriority::Ignored;
}
m_contentModel->model()->updateFilesPriorities(priorities);
} }
updateDiskSpaceLabel(); updateDiskSpaceLabel();

View file

@ -375,6 +375,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->checkUseDownloadPath, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkUseDownloadPath, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkUseDownloadPath, &QAbstractButton::toggled, m_ui->textDownloadPath, &QWidget::setEnabled); connect(m_ui->checkUseDownloadPath, &QAbstractButton::toggled, m_ui->textDownloadPath, &QWidget::setEnabled);
connect(m_ui->addWatchedFolderButton, &QAbstractButton::clicked, this, &ThisType::enableApplyButton); connect(m_ui->addWatchedFolderButton, &QAbstractButton::clicked, this, &ThisType::enableApplyButton);
connect(m_ui->textExcludedFileNames, &QPlainTextEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->removeWatchedFolderButton, &QAbstractButton::clicked, this, &ThisType::enableApplyButton); connect(m_ui->removeWatchedFolderButton, &QAbstractButton::clicked, this, &ThisType::enableApplyButton);
connect(m_ui->groupMailNotification, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->groupMailNotification, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->senderEmailTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->senderEmailTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
@ -757,6 +758,7 @@ void OptionsDialog::saveOptions()
session->setTorrentContentLayout(static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex())); session->setTorrentContentLayout(static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
auto watchedFoldersModel = static_cast<WatchedFoldersModel *>(m_ui->scanFoldersView->model()); auto watchedFoldersModel = static_cast<WatchedFoldersModel *>(m_ui->scanFoldersView->model());
watchedFoldersModel->apply(); watchedFoldersModel->apply();
session->setExcludedFileNames(m_ui->textExcludedFileNames->toPlainText().split(u'\n', Qt::SkipEmptyParts));
session->setTorrentExportDirectory(getTorrentExportDir()); session->setTorrentExportDirectory(getTorrentExportDir());
session->setFinishedTorrentExportDirectory(getFinishedTorrentExportDir()); session->setFinishedTorrentExportDirectory(getFinishedTorrentExportDir());
pref->setMailNotificationEnabled(m_ui->groupMailNotification->isChecked()); pref->setMailNotificationEnabled(m_ui->groupMailNotification->isChecked());
@ -1016,6 +1018,7 @@ void OptionsDialog::loadOptions()
m_ui->checkAppendqB->setChecked(session->isAppendExtensionEnabled()); m_ui->checkAppendqB->setChecked(session->isAppendExtensionEnabled());
m_ui->checkPreallocateAll->setChecked(session->isPreallocationEnabled()); m_ui->checkPreallocateAll->setChecked(session->isPreallocationEnabled());
m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled()); m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled());
m_ui->textExcludedFileNames->setPlainText(session->excludedFileNames().join(u'\n'));
if (session->torrentExportDirectory().isEmpty()) if (session->torrentExportDirectory().isEmpty())
{ {

View file

@ -1261,6 +1261,44 @@ Manual: Various torrent properties (e.g. save path) must be assigned manually</s
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupExcludedFileNames">
<property name="title">
<string>Excluded file names</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_311">
<item>
<widget class="QLabel" name="label_1">
<property name="text">
<string>Filters:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="textExcludedFileNames">
<property name="toolTip">
<string>Blacklist filtered file names from being downloaded from torrent(s).
Files matching any of the filters in this list will have their priority automatically set to &quot;Do not download&quot;.
Use newlines to separate multiple entries. Can use wildcards as outlined below.
*: matches zero or more of any characters.
?: matches any single character.
[...]: sets of characters can be represented in square brackets.
Examples
*.exe: filter '.exe' file extension.
readme.txt: filter exact file name.
?.txt: filter 'a.txt', 'b.txt' but not 'aa.txt'.
readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</string>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupMailNotification"> <widget class="QGroupBox" name="groupMailNotification">
<property name="title"> <property name="title">

View file

@ -141,6 +141,8 @@ void AppController::preferencesAction()
data[u"scan_dirs"_qs] = nativeDirs; data[u"scan_dirs"_qs] = nativeDirs;
// === END DEPRECATED CODE === // // === END DEPRECATED CODE === //
data[u"excluded_file_names"_qs] = session->excludedFileNames().join(u'\n');
// Email notification upon download completion // Email notification upon download completion
data[u"mail_notification_enabled"_qs] = pref->isMailNotificationEnabled(); data[u"mail_notification_enabled"_qs] = pref->isMailNotificationEnabled();
data[u"mail_notification_sender"_qs] = pref->getMailNotificationSender(); data[u"mail_notification_sender"_qs] = pref->getMailNotificationSender();
@ -476,6 +478,9 @@ void AppController::setPreferencesAction()
} }
// === END DEPRECATED CODE === // // === END DEPRECATED CODE === //
if (hasKey(u"excluded_file_names"_qs))
session->setExcludedFileNames(it.value().toString().split(u'\n'));
// Email notification upon download completion // Email notification upon download completion
if (hasKey(u"mail_notification_enabled"_qs)) if (hasKey(u"mail_notification_enabled"_qs))
pref->setMailNotificationEnabled(it.value().toBool()); pref->setMailNotificationEnabled(it.value().toBool());

View file

@ -48,7 +48,7 @@
#include "base/utils/version.h" #include "base/utils/version.h"
#include "api/isessionmanager.h" #include "api/isessionmanager.h"
inline const Utils::Version<int, 3, 2> API_VERSION {2, 8, 11}; inline const Utils::Version<int, 3, 2> API_VERSION {2, 8, 12};
class APIController; class APIController;
class AuthController; class AuthController;

View file

@ -130,6 +130,12 @@
</table> </table>
</fieldset> </fieldset>
<fieldset class="settings">
<legend>QBT_TR(Excluded file names)QBT_TR[CONTEXT=OptionsDialog]</legend>
<label for="excludedFileNamesTextarea">QBT_TR(Filters:)QBT_TR[CONTEXT=OptionsDialog]</label><br>
<textarea id="excludedFileNamesTextarea" rows="6" cols="70"></textarea>
</fieldset>
<fieldset class="settings"> <fieldset class="settings">
<legend> <legend>
<input type="checkbox" id="mail_notification_checkbox" onclick="qBittorrent.Preferences.updateMailNotification();" /> <input type="checkbox" id="mail_notification_checkbox" onclick="qBittorrent.Preferences.updateMailNotification();" />
@ -1733,6 +1739,7 @@
addWatchFolder(folder, sel, other); addWatchFolder(folder, sel, other);
} }
addWatchFolder(); addWatchFolder();
$('excludedFileNamesTextarea').setProperty('value', pref.excluded_file_names);
// Email notification upon download completion // Email notification upon download completion
$('mail_notification_checkbox').setProperty('checked', pref.mail_notification_enabled); $('mail_notification_checkbox').setProperty('checked', pref.mail_notification_enabled);
@ -2053,6 +2060,7 @@
// Automatically add torrents from // Automatically add torrents from
settings.set('scan_dirs', getWatchedFolders()); settings.set('scan_dirs', getWatchedFolders());
settings.set('excluded_file_names', $('excludedFileNamesTextarea').getProperty('value'));
// Email notification upon download completion // Email notification upon download completion
settings.set('mail_notification_enabled', $('mail_notification_checkbox').getProperty('checked')); settings.set('mail_notification_enabled', $('mail_notification_checkbox').getProperty('checked'));