From 6e73fa80b88e5ff4d16b8261c341d4d619eef817 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Thu, 7 Apr 2016 16:58:30 +0200 Subject: [PATCH] Add option to automatically remove .torrent files upon adding Some browsers do not download files, intended for immediate opening, into a temporary directory, and thus a regular download directories accumulate those unneeded files. The option allows qBittorrent to clean after itself and delete those files whether they were succesfully added or not (user-selectable policy). --- src/base/CMakeLists.txt | 2 + src/base/base.pri | 2 + src/base/bittorrent/session.cpp | 3 + src/base/torrentfileguard.cpp | 100 ++++++++++++++++++ src/base/torrentfileguard.h | 95 +++++++++++++++++ src/gui/addnewtorrentdialog.cpp | 11 ++ src/gui/addnewtorrentdialog.h | 6 +- src/gui/addnewtorrentdialog.ui | 16 ++- src/gui/options.ui | 180 ++++++++++++++++++++++++-------- src/gui/options_imp.cpp | 25 +++++ 10 files changed, 391 insertions(+), 49 deletions(-) create mode 100644 src/base/torrentfileguard.cpp create mode 100644 src/base/torrentfileguard.h diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 4fb66de83..6f57029ba 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -52,6 +52,7 @@ qinisettings.h scanfoldersmodel.h searchengine.h settingsstorage.h +torrentfileguard.h torrentfilter.h tristatebool.h types.h @@ -107,6 +108,7 @@ preferences.cpp scanfoldersmodel.cpp searchengine.cpp settingsstorage.cpp +torrentfileguard.cpp torrentfilter.cpp tristatebool.cpp ) diff --git a/src/base/base.pri b/src/base/base.pri index d52c23445..725ece8c5 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -51,6 +51,7 @@ HEADERS += \ $$PWD/utils/misc.h \ $$PWD/utils/string.h \ $$PWD/unicodestrings.h \ + $$PWD/torrentfileguard.h \ $$PWD/torrentfilter.h \ $$PWD/scanfoldersmodel.h \ $$PWD/searchengine.h @@ -103,6 +104,7 @@ SOURCES += \ $$PWD/utils/gzip.cpp \ $$PWD/utils/misc.cpp \ $$PWD/utils/string.cpp \ + $$PWD/torrentfileguard.cpp \ $$PWD/torrentfilter.cpp \ $$PWD/scanfoldersmodel.cpp \ $$PWD/searchengine.cpp diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 35638d8fe..cac2663ec 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -70,6 +70,7 @@ #include "base/net/portforwarder.h" #include "base/preferences.h" #include "base/settingsstorage.h" +#include "base/torrentfileguard.h" #include "base/torrentfilter.h" #include "base/unicodestrings.h" #include "base/utils/misc.h" @@ -1229,6 +1230,8 @@ bool Session::addTorrent(QString source, const AddTorrentParams ¶ms) m_downloadedTorrents[handler->url()] = params; } else { + TorrentFileGuard guard(source); + guard.markAsAddedToSession(); return addTorrent_impl(params, MagnetUri(), TorrentInfo::loadFromFile(source)); } diff --git a/src/base/torrentfileguard.cpp b/src/base/torrentfileguard.cpp new file mode 100644 index 000000000..6ccc896f6 --- /dev/null +++ b/src/base/torrentfileguard.cpp @@ -0,0 +1,100 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "torrentfileguard.h" + +#include +#include "settingsstorage.h" +#include "utils/fs.h" + +namespace +{ + const QLatin1String KEY_AUTO_DELETE_ENABLED ("Core/AutoDeleteAddedTorrentFile"); +} + +FileGuard::FileGuard(const QString &path) + : m_path {path} + , m_remove {true} +{ +} + +void FileGuard::setAutoRemove(bool remove) noexcept +{ + m_remove = remove; +} + +FileGuard::~FileGuard() +{ + if (m_remove && !m_path.isEmpty()) + Utils::Fs::forceRemove(m_path); // forceRemove() checks for file existence +} + +TorrentFileGuard::TorrentFileGuard(const QString &path) + : m_mode {autoDeleteMode()} + , m_wasAdded {false} + , m_guard {m_mode != Never ? path : QString()} +{ +} + +TorrentFileGuard::~TorrentFileGuard() +{ + if (!m_wasAdded && (m_mode != Always)) + m_guard.setAutoRemove(false); +} + +void TorrentFileGuard::markAsAddedToSession() +{ + m_wasAdded = true; +} + +void TorrentFileGuard::setAutoRemove(bool remove) +{ + m_guard.setAutoRemove(remove); +} + +TorrentFileGuard::AutoDeleteMode TorrentFileGuard::autoDeleteMode() +{ + QMetaEnum meta {modeMetaEnum()}; + return static_cast(meta.keyToValue(SettingsStorage::instance()->loadValue( + KEY_AUTO_DELETE_ENABLED, meta.valueToKey(Never)).toByteArray())); +} + +void TorrentFileGuard::setautoDeleteMode(TorrentFileGuard::AutoDeleteMode mode) +{ + QMetaEnum meta {modeMetaEnum()}; + SettingsStorage::instance()->storeValue(KEY_AUTO_DELETE_ENABLED, meta.valueToKey(mode)); +} + +QMetaEnum TorrentFileGuard::modeMetaEnum() +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + return QMetaEnum::fromType(); +#else + return staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("AutoDeleteMode")); +#endif +} diff --git a/src/base/torrentfileguard.h b/src/base/torrentfileguard.h new file mode 100644 index 000000000..cd5bb68f0 --- /dev/null +++ b/src/base/torrentfileguard.h @@ -0,0 +1,95 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include +#include + +class QMetaEnum; +/// Utility class to defer file deletion +class FileGuard +{ +public: + FileGuard(const QString &path = QString()); + ~FileGuard(); + + /// Cancels or re-enables deferred file deletion + void setAutoRemove(bool remove) noexcept; + +private: + QString m_path; + bool m_remove; +}; + +/// Reads settings for .torrent files from preferences +/// and sets the file guard up accordingly +class TorrentFileGuard +{ + Q_GADGET + +public: + TorrentFileGuard(const QString &path = QString()); + ~TorrentFileGuard(); + + /// marks the torrent file as loaded (added) into the BitTorrent::Session + void markAsAddedToSession(); + void setAutoRemove(bool remove); + + enum AutoDeleteMode // do not change these names: they are stored in config file + { + Never, + IfAdded, + Always + }; + + // static interface to get/set preferences + static AutoDeleteMode autoDeleteMode(); + static void setautoDeleteMode(AutoDeleteMode mode); + +private: + static QMetaEnum modeMetaEnum(); +#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) + Q_ENUMS(AutoDeleteMode) +#else + Q_ENUM(AutoDeleteMode) +#endif + AutoDeleteMode m_mode; + bool m_wasAdded; + // Qt 4 moc has troubles with Q_GADGET: if Q_GADGET is present in a class, moc unconditionally + // references in the generated code the statiMetaObject from the class ancestor. + // Moreover, if the ancestor class has Q_GADGET but does not have other + // Q_ declarations, moc does not generate staticMetaObject for it. These results + // in referencing the non existent staticMetaObject and such code fails to compile. + // This problem is NOT present in Qt 5.7.0 and maybe in some older Qt 5 versions too + // Qt 4.8.7 has it. + // Therefore, we can't inherit FileGuard :( + FileGuard m_guard; +}; + +#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) +Q_DECLARE_METATYPE(TorrentFileGuard::AutoDeleteMode) +#endif diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index ac4365526..df8812ea1 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -45,6 +45,7 @@ #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/utils/string.h" +#include "base/torrentfileguard.h" #include "base/unicodestrings.h" #include "guiiconprovider.h" #include "autoexpandabledialog.h" @@ -94,6 +95,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked())); ui->defaultSavePathCheckBox->setVisible(false); // Default path is selected by default + ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never); + // Load categories QStringList categories = session->categories(); std::sort(categories.begin(), categories.end(), Utils::String::naturalCompareCaseInsensitive); @@ -112,6 +115,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) loadState(); // Signal / slots connect(ui->adv_button, SIGNAL(clicked(bool)), SLOT(showAdvancedSettings(bool))); + connect(ui->doNotDeleteTorrentCheckBox, SIGNAL(clicked(bool)), SLOT(doNotDeleteTorrentClicked(bool))); editHotkey = new QShortcut(QKeySequence("F2"), ui->contentTreeView, 0, 0, Qt::WidgetShortcut); connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedFile())); connect(ui->contentTreeView, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedFile())); @@ -221,6 +225,7 @@ bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath) return false; } + m_torrentGuard.reset(new TorrentFileGuard(m_filePath)); m_hash = m_torrentInfo.hash(); // Prevent showing the dialog if download is already present @@ -647,6 +652,7 @@ void AddNewTorrentDialog::accept() else BitTorrent::Session::instance()->addTorrent(m_torrentInfo, params); + m_torrentGuard->markAsAddedToSession(); QDialog::accept(); } @@ -795,3 +801,8 @@ void AddNewTorrentDialog::setCommentText(const QString &str) const int height = lineHeight * lines; ui->scrollArea->setMaximumHeight(height); } + +void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked) +{ + m_torrentGuard->setAutoRemove(!checked); +} diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index f7fb9383d..e7bf345c3 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -31,8 +31,9 @@ #ifndef ADDNEWTORRENTDIALOG_H #define ADDNEWTORRENTDIALOG_H -#include #include +#include +#include #include #include "base/bittorrent/infohash.h" @@ -49,6 +50,7 @@ namespace Ui } class TorrentContentFilterModel; +class TorrentFileGuard; class PropListDelegate; class AddNewTorrentDialog: public QDialog @@ -79,6 +81,7 @@ private slots: void handleDownloadFinished(const QString &url, const QString &filePath); void savingModeChanged(bool enabled); void categoryChanged(int index); + void doNotDeleteTorrentClicked(bool checked); void accept() override; void reject() override; @@ -109,6 +112,7 @@ private: QShortcut *editHotkey; QByteArray m_headerState; int m_oldIndex; + QScopedPointer m_torrentGuard; }; #endif // ADDNEWTORRENTDIALOG_H diff --git a/src/gui/addnewtorrentdialog.ui b/src/gui/addnewtorrentdialog.ui index 689bbe354..778703d9f 100644 --- a/src/gui/addnewtorrentdialog.ui +++ b/src/gui/addnewtorrentdialog.ui @@ -7,7 +7,7 @@ 0 0 414 - 590 + 661 @@ -95,6 +95,16 @@ + + + + When checked, the .torrent file will not be deleted despite the settings at the "Download" page of the options dialog + + + Do not delete .torrent file + + + @@ -278,8 +288,8 @@ 0 0 - 301 - 73 + 308 + 74 diff --git a/src/gui/options.ui b/src/gui/options.ui index 2250351b5..7e6649e89 100644 --- a/src/gui/options.ui +++ b/src/gui/options.ui @@ -113,9 +113,9 @@ 0 - -190 - 486 - 732 + 0 + 514 + 968 @@ -670,8 +670,8 @@ 0 0 - 487 - 1334 + 514 + 1537 @@ -719,6 +719,64 @@ + + + + Should the .torrent file be deleted after adding it + + + Delete .torrent files afterwards + + + true + + + false + + + + + + Also delete .torrent files whose addition was cancelled + + + Also when addition is cancelled + + + + + + + + + + 0 + 0 + + + + <> + + + + + + + + 0 + 0 + + + + Warning! Data loss possible! + + + + + + + + @@ -1364,8 +1422,8 @@ 0 0 - 450 - 658 + 457 + 713 @@ -1894,8 +1952,8 @@ 0 0 - 376 - 444 + 362 + 484 @@ -2281,8 +2339,8 @@ 0 0 - 555 - 527 + 587 + 578 @@ -2678,8 +2736,8 @@ 0 0 - 419 - 537 + 460 + 562 @@ -3130,12 +3188,12 @@ setEnabled(bool) - 544 - 172 + 604 + 205 - 603 - 171 + 677 + 206 @@ -3146,12 +3204,12 @@ setEnabled(bool) - 544 - 198 + 604 + 238 - 603 - 197 + 677 + 239 @@ -3162,12 +3220,12 @@ setEnabled(bool) - 544 - 250 + 604 + 304 - 603 - 249 + 677 + 305 @@ -3178,12 +3236,12 @@ setEnabled(bool) - 509 - 372 + 547 + 415 - 584 - 373 + 642 + 414 @@ -3194,12 +3252,12 @@ setEnabled(bool) - 509 - 372 + 547 + 415 - 721 - 373 + 815 + 413 @@ -3210,12 +3268,12 @@ setEnabled(bool) - 423 - 224 + 604 + 271 - 571 - 224 + 677 + 272 @@ -3226,12 +3284,12 @@ setEnabled(bool) - 398 - 292 + 395 + 203 - 477 - 292 + 496 + 204 @@ -3242,12 +3300,44 @@ setEnabled(bool) - 398 - 263 + 395 + 170 + + + 496 + 171 + + + + + deleteTorrentBox + toggled(bool) + deleteTorrentWarningIcon + setVisible(bool) + + + 554 + 153 + + + 324 + 214 + + + + + deleteTorrentBox + toggled(bool) + deleteTorrentWarningLabel + setVisible(bool) + + + 646 + 158 - 477 - 263 + 629 + 207 diff --git a/src/gui/options_imp.cpp b/src/gui/options_imp.cpp index 74d3927cc..b4cf8905d 100644 --- a/src/gui/options_imp.cpp +++ b/src/gui/options_imp.cpp @@ -49,6 +49,7 @@ #include "base/bittorrent/session.h" #include "base/net/dnsupdater.h" #include "base/unicodestrings.h" +#include "base/torrentfileguard.h" #include "advancedsettings.h" #include "guiiconprovider.h" #include "scanfoldersdelegate.h" @@ -88,6 +89,22 @@ options_imp::options_imp(QWidget *parent) IpFilterRefreshBtn->setIcon(GuiIconProvider::instance()->getIcon("view-refresh")); + deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16)); + deleteTorrentWarningIcon->hide(); + deleteTorrentWarningLabel->hide(); + deleteTorrentWarningLabel->setToolTip(QLatin1String("

") + + tr("By enabling these options, you can irrevocably lose your .torrent files!") + + QLatin1String("

") + + tr("When these options are enabled, qBittorent will delete .torrent files " + "after they were successfully (the first option) or not (the second option) added to its " + "download queue. This will be applied not only to the files opened via " + "“Add torrent” menu action but to those opened via file type association as well") + + QLatin1String("

") + + tr("If you enable the second option (“Also when addition is cancelled”) the " + ".torrent file will be deleted even if you press “Cancel” in " + "the “Add torrent” dialog") + + QLatin1String("

")); + hsplitter->setCollapsible(0, false); hsplitter->setCollapsible(1, false); // Get apply button in button box @@ -193,6 +210,8 @@ options_imp::options_imp(QWidget *parent) connect(checkAdditionDialog, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkAdditionDialogFront, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkStartPaused, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); + connect(deleteTorrentBox, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); + connect(deleteCancelledTorrentBox, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkExportDir, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkExportDirFin, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(textExportDir, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); @@ -500,6 +519,9 @@ void options_imp::saveOptions() pref->setAutoRunProgram(autoRun_txt->text().trimmed()); pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl()); pref->setActionOnDblClOnTorrentFn(getActionOnDblClOnTorrentFn()); + TorrentFileGuard::setautoDeleteMode(!deleteTorrentBox->isChecked() ? TorrentFileGuard::Never + : !deleteCancelledTorrentBox->isChecked() ? TorrentFileGuard::IfAdded + : TorrentFileGuard::Always); // End Downloads preferences // Connection preferences @@ -676,6 +698,9 @@ void options_imp::loadOptions() checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled()); checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel()); checkStartPaused->setChecked(session->isAddTorrentPaused()); + const TorrentFileGuard::AutoDeleteMode autoDeleteMode = TorrentFileGuard::autoDeleteMode(); + deleteTorrentBox->setChecked(autoDeleteMode != TorrentFileGuard::Never); + deleteCancelledTorrentBox->setChecked(autoDeleteMode == TorrentFileGuard::Always); textSavePath->setText(Utils::Fs::toNativePath(session->defaultSavePath())); (session->isSubcategoriesEnabled() ? radioBtnEnableSubcategories : radioBtnDisableSubcategories)->setChecked(true);