From eba41978b033bbfb21c3f10679e00b1891029a5d Mon Sep 17 00:00:00 2001 From: Brian Kendall Date: Thu, 14 Jul 2016 22:15:10 -0400 Subject: [PATCH] Added command line arguments for specifying options when adding torrents Arguments include adding torrents as paused/started, skiping hash check, setting category, downloading in sequential order, downloading first and last pieces first, and skipping the 'add new torrent' dialog. Added TriStateBoolOption class for specifying options that don't have a default value when unused (e.g. add-paused). Also improved command line usage text to include more information, have better organization, and not exceed 80 columns in width. Also also added firstLastPiecePriority field to BitTorrent::AddTorrentData, and modified TorrentHandle so that if first/last piece priority should be on, it will be toggled on after the torrent's metadata has loaded. --- src/app/application.cpp | 54 +++++- src/app/main.cpp | 4 +- src/app/options.cpp | 249 +++++++++++++++++++++---- src/app/options.h | 12 +- src/base/bittorrent/addtorrentparams.h | 1 + src/base/bittorrent/session.cpp | 2 + src/base/bittorrent/torrenthandle.cpp | 42 ++++- src/base/bittorrent/torrenthandle.h | 2 + src/gui/addnewtorrentdialog.cpp | 81 +++++--- src/gui/addnewtorrentdialog.h | 8 +- src/gui/addnewtorrentdialog.ui | 10 - src/gui/rss/rsswidget.cpp | 2 +- 12 files changed, 376 insertions(+), 91 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index 204550b5f..b5ad0887e 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -407,15 +407,63 @@ void Application::processParams(const QStringList ¶ms) return; } #endif + BitTorrent::AddTorrentParams torrentParams; + TriStateBool skipTorrentDialog; foreach (QString param, params) { param = param.trimmed(); + + // Process strings indicating options specified by the user. + + if (param.startsWith(QLatin1String("@savePath="))) { + torrentParams.savePath = param.mid(10); + continue; + } + + if (param.startsWith(QLatin1String("@addPaused="))) { + torrentParams.addPaused = param.mid(11).toInt() ? TriStateBool::True : TriStateBool::False; + continue; + } + + if (param == QLatin1String("@skipChecking")) { + torrentParams.skipChecking = true; + continue; + } + + if (param.startsWith(QLatin1String("@category="))) { + torrentParams.category = param.mid(10); + continue; + } + + if (param == QLatin1String("@sequential")) { + torrentParams.sequential = true; + continue; + } + + if (param == QLatin1String("@firstLastPiecePriority")) { + torrentParams.firstLastPiecePriority = true; + continue; + } + + if (param.startsWith(QLatin1String("@skipDialog="))) { + skipTorrentDialog = param.mid(12).toInt() ? TriStateBool::True : TriStateBool::False; + continue; + } + #ifndef DISABLE_GUI - if (AddNewTorrentDialog::isEnabled()) - AddNewTorrentDialog::show(param, m_window); + // There are two circumstances in which we want to show the torrent + // dialog. One is when the application settings specify that it should + // be shown and skipTorrentDialog is undefined. The other is when + // skipTorrentDialog is false, meaning that the application setting + // should be overridden. + const bool showDialogForThisTorrent = + ((AddNewTorrentDialog::isEnabled() && skipTorrentDialog == TriStateBool::Undefined) + || skipTorrentDialog == TriStateBool::False); + if (showDialogForThisTorrent) + AddNewTorrentDialog::show(param, torrentParams, m_window); else #endif - BitTorrent::Session::instance()->addTorrent(param); + BitTorrent::Session::instance()->addTorrent(param, torrentParams); } } diff --git a/src/app/main.cpp b/src/app/main.cpp index 7defc5293..6e0ad3fde 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -170,7 +170,7 @@ int main(int argc, char *argv[]) qDebug("qBittorrent is already running for this user."); QThread::msleep(300); - app->sendParams(params.torrents); + app->sendParams(params.paramList()); return EXIT_SUCCESS; } @@ -235,7 +235,7 @@ int main(int argc, char *argv[]) signal(SIGSEGV, sigAbnormalHandler); #endif - return app->exec(params.torrents); + return app->exec(params.paramList()); } catch (CommandLineParameterError &er) { displayBadArgMessage(er.messageForUser()); diff --git a/src/app/options.cpp b/src/app/options.cpp index 857ddceea..ecf825286 100644 --- a/src/app/options.cpp +++ b/src/app/options.cpp @@ -47,6 +47,10 @@ namespace { + const int USAGE_INDENTATION = 4; + const int USAGE_TEXT_COLUMN = 31; + const int WRAP_AT_COLUMN = 80; + // Base option class. Encapsulates name operations. class Option { @@ -78,14 +82,15 @@ namespace + QString(QLatin1String(m_name)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_')); } + public: static QString padUsageText(const QString &usage) { - const int TAB_WIDTH = 8; - QString res = QLatin1String("\t") + usage; - if (usage.size() < 2 * TAB_WIDTH) - return res + QLatin1String("\t\t"); + QString res = QString(USAGE_INDENTATION, ' ') + usage; + + if ((USAGE_TEXT_COLUMN - usage.length() - 4) > 0) + return res + QString(USAGE_TEXT_COLUMN - usage.length() - 4, ' '); else - return res + QLatin1String("\t"); + return res; } private: @@ -125,7 +130,7 @@ namespace } }; - inline bool operator==(const QString &s, const BoolOption &o) + bool operator==(const QString &s, const BoolOption &o) { return o == s; } @@ -185,7 +190,7 @@ namespace } }; - inline bool operator==(const QString &s, const StringOption &o) + bool operator==(const QString &s, const StringOption &o) { return o == s; } @@ -230,7 +235,69 @@ namespace } }; - inline bool operator==(const QString &s, const IntOption &o) + bool operator==(const QString &s, const IntOption &o) + { + return o == s; + } + + // Option that is explicitly set to true or false, and whose value is undefined when unspecified. + // May not have a shortcut. + class TriStateBoolOption: protected StringOption + { + public: + constexpr TriStateBoolOption(const char *name) + : StringOption {name} + { + } + + using StringOption::operator==; + + QString usage() const + { + return StringOption::usage(QLatin1String("true|false")); + } + + TriStateBool value(const QString &arg) const + { + QString val = StringOption::value(arg); + + if (val.toUpper() == QLatin1String("TRUE") || val == QLatin1String("1")) { + return TriStateBool::True; + } + else if (val.toUpper() == QLatin1String("FALSE") || val == QLatin1String("0")) { + return TriStateBool::False; + } + else { + throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'", + "e.g. Parameter '--add-paused' must follow syntax " + "'--add-paused='") + .arg(fullParameter()) + .arg(QLatin1String(""))); + } + } + + TriStateBool value(const QProcessEnvironment &env) const + { + QString val = env.value(envVarName()); + + if (val.isEmpty()) { + return TriStateBool::Undefined; + } + else if (val.toUpper() == QLatin1String("TRUE") || val == QLatin1String("1")) { + return TriStateBool::True; + } + else if (val.toUpper() == QLatin1String("FALSE") || val == QLatin1String("0")) { + return TriStateBool::False; + } + else { + qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'") + .arg(QLatin1String("true|false")).arg(envVarName()).arg(val); + return TriStateBool::Undefined; + } + } + }; + + bool operator==(const QString &s, const TriStateBoolOption &o) { return o == s; } @@ -247,10 +314,22 @@ namespace constexpr const StringOption CONFIGURATION_OPTION = {"configuration"}; constexpr const BoolOption PORTABLE_OPTION = {"portable"}; constexpr const BoolOption RELATIVE_FASTRESUME = {"relative-fastresume"}; + constexpr const StringOption SAVE_PATH_OPTION = {"save-path"}; + constexpr const TriStateBoolOption PAUSED_OPTION = {"add-paused"}; + constexpr const BoolOption SKIP_HASH_CHECK_OPTION = {"skip-hash-check"}; + constexpr const StringOption CATEGORY_OPTION = {"category"}; + constexpr const BoolOption SEQUENTIAL_OPTION = {"sequential"}; + constexpr const BoolOption FIRST_AND_LAST_OPTION = {"first-and-last"}; + constexpr const TriStateBoolOption SKIP_DIALOG_OPTION = {"skip-dialog"}; } QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env) : showHelp(false) + , relativeFastresumePaths(RELATIVE_FASTRESUME.value(env)) + , portableMode(PORTABLE_OPTION.value(env)) + , skipChecking(SKIP_HASH_CHECK_OPTION.value(env)) + , sequential(SEQUENTIAL_OPTION.value(env)) + , firstLastPiecePriority(FIRST_AND_LAST_OPTION.value(env)) #ifndef Q_OS_WIN , showVersion(false) #endif @@ -260,13 +339,59 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en , shouldDaemonize(DAEMON_OPTION.value(env)) #endif , webUiPort(WEBUI_PORT_OPTION.value(env, -1)) + , addPaused(PAUSED_OPTION.value(env)) + , skipDialog(SKIP_DIALOG_OPTION.value(env)) , profileDir(PROFILE_OPTION.value(env)) - , relativeFastresumePaths(RELATIVE_FASTRESUME.value(env)) - , portableMode(PORTABLE_OPTION.value(env)) , configurationName(CONFIGURATION_OPTION.value(env)) + , savePath(SAVE_PATH_OPTION.value(env)) + , category(CATEGORY_OPTION.value(env)) { } +QStringList QBtCommandLineParameters::paramList() const +{ + QStringList result; + // Because we're passing a string list to the currently running + // qBittorrent process, we need some way of passing along the options + // the user has specified. Here we place special strings that are + // almost certainly not going to collide with a file path or URL + // specified by the user, and placing them at the beginning of the + // string listr so that they will be processed before the list of + // torrent paths or URLs. + + if (!savePath.isEmpty()) + result.append(QString("@savePath=%1").arg(savePath)); + + if (addPaused == TriStateBool::True) { + result.append(QLatin1String("@addPaused=1")); + } + else if (addPaused == TriStateBool::False) { + result.append(QLatin1String("@addPaused=0")); + } + + if (skipChecking) + result.append(QLatin1String("@skipChecking")); + + if (!category.isEmpty()) + result.append(QString("@category=%1").arg(category)); + + if (sequential) + result.append(QLatin1String("@sequential")); + + if (firstLastPiecePriority) + result.append(QLatin1String("@firstLastPiecePriority")); + + if (skipDialog == TriStateBool::True) { + result.append(QLatin1String("@skipDialog=1")); + } + else if (skipDialog == TriStateBool::False) { + result.append(QLatin1String("@skipDialog=0")); + } + + result += torrents; + return result; +} + QBtCommandLineParameters parseCommandLine(const QStringList &args) { QBtCommandLineParameters result {QProcessEnvironment::systemEnvironment()}; @@ -312,6 +437,27 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args) else if (arg == CONFIGURATION_OPTION) { result.configurationName = CONFIGURATION_OPTION.value(arg); } + else if (arg == SAVE_PATH_OPTION) { + result.savePath = SAVE_PATH_OPTION.value(arg); + } + else if (arg == PAUSED_OPTION) { + result.addPaused = PAUSED_OPTION.value(arg); + } + else if (arg == SKIP_HASH_CHECK_OPTION) { + result.skipChecking = true; + } + else if (arg == CATEGORY_OPTION) { + result.category = CATEGORY_OPTION.value(arg); + } + else if (arg == SEQUENTIAL_OPTION) { + result.sequential = true; + } + else if (arg == FIRST_AND_LAST_OPTION) { + result.firstLastPiecePriority = true; + } + else if (arg == SKIP_DIALOG_OPTION) { + result.skipDialog = SKIP_DIALOG_OPTION.value(arg); + } else { // Unknown argument result.unknownParameter = arg; @@ -343,43 +489,82 @@ const QString& CommandLineParameterError::messageForUser() const return m_messageForUser; } +QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN, int wrapAtColumn = WRAP_AT_COLUMN) +{ + QStringList words = text.split(' '); + QStringList lines = {words.first()}; + int currentLineMaxLength = wrapAtColumn - initialIndentation; + + foreach (const QString &word, words.mid(1)) { + if (lines.last().length() + word.length() + 1 < currentLineMaxLength) { + lines.last().append(" " + word); + } + else { + lines.append(QString(initialIndentation, ' ') + word); + currentLineMaxLength = wrapAtColumn; + } + } + + return lines.join("\n"); +} + QString makeUsage(const QString &prgName) { QString text; QTextStream stream(&text, QIODevice::WriteOnly); + QString indentation = QString(USAGE_INDENTATION, ' '); stream << QObject::tr("Usage:") << '\n'; -#ifndef Q_OS_WIN - stream << '\t' << prgName << " [options] [( | )...]" << '\n'; -#endif + stream << indentation << prgName << QLatin1String(" [options] [( | )...]") << '\n'; stream << QObject::tr("Options:") << '\n'; #ifndef Q_OS_WIN - stream << SHOW_VERSION_OPTION.usage() << QObject::tr("Displays program version and exit") << '\n'; + stream << SHOW_VERSION_OPTION.usage() << wrapText(QObject::tr("Displays program version and exit")) << '\n'; #endif - stream << SHOW_HELP_OPTION.usage() << QObject::tr("Displays this help message and exit") << '\n'; - stream << WEBUI_PORT_OPTION.usage(QLatin1String("port")) - << QObject::tr("Changes the Web UI port") + stream << SHOW_HELP_OPTION.usage() << wrapText(QObject::tr("Displays this help message and exit")) << '\n'; + stream << WEBUI_PORT_OPTION.usage(QObject::tr("port")) + << wrapText(QObject::tr("Changes the Web UI port")) << '\n'; #ifndef DISABLE_GUI - stream << NO_SPLASH_OPTION.usage() << QObject::tr("Disable splash screen") << '\n'; + stream << NO_SPLASH_OPTION.usage() << wrapText(QObject::tr("Disable splash screen")) << '\n'; #else - stream << DAEMON_OPTION.usage() << QObject::tr("Run in daemon-mode (background)") << '\n'; + stream << DAEMON_OPTION.usage() << wrapText(QObject::tr("Run in daemon-mode (background)")) << '\n'; #endif - stream << PROFILE_OPTION.usage(QLatin1String("dir")) << QObject::tr("Store configuration files in ") << '\n'; - stream << CONFIGURATION_OPTION.usage(QLatin1String("name")) << QObject::tr("Store configuration files in directories qBittorrent_") << '\n'; - stream << RELATIVE_FASTRESUME.usage() << QObject::tr("Hack into libtorrent fastresume files and make file paths relative to the profile directory") << '\n'; - stream << PORTABLE_OPTION.usage() << QObject::tr("Shortcut for --profile=/profile --relative-fastresume") << '\n'; - stream << "\tfiles or urls\t\t" << QObject::tr("Downloads the torrents passed by the user") << '\n' + //: Use appropriate short form or abbreviation of "directory" + stream << PROFILE_OPTION.usage(QObject::tr("dir")) + << wrapText(QObject::tr("Store configuration files in ")) << '\n'; + stream << CONFIGURATION_OPTION.usage(QObject::tr("name")) + << wrapText(QObject::tr("Store configuration files in directories qBittorrent_")) << '\n'; + stream << RELATIVE_FASTRESUME.usage() + << wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative " + "to the profile directory")) << '\n'; + stream << PORTABLE_OPTION.usage() + << wrapText(QObject::tr("Shortcut for --profile=/profile --relative-fastresume")) << '\n'; + stream << Option::padUsageText(QObject::tr("files or urls")) + << wrapText(QObject::tr("Downloads the torrents passed by the user")) << '\n' << '\n'; - - stream << QObject::tr("Option values may be supplied via environment variables.") << '\n' - << QObject::tr("For option named 'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper case, '-' replaced with '_')") << '\n' - << QObject::tr("To pass flag values, set the variable to '1' or 'TRUE'.") << '\n' - << QObject::tr("For example, to disable the splash screen: ") - << "QBT_NO_SPLASH=1 " << prgName << '\n' - << '\n' - << QObject::tr("Command line parameters take precedence over environment variables") << '\n'; + + stream << wrapText(QObject::tr("Options when adding new torrents:"), 0) << '\n'; + stream << SAVE_PATH_OPTION.usage(QObject::tr("path")) << wrapText(QObject::tr("Torrent save path")) << '\n'; + stream << PAUSED_OPTION.usage() << wrapText(QObject::tr("Add torrents as started or paused")) << '\n'; + stream << SKIP_HASH_CHECK_OPTION.usage() << wrapText(QObject::tr("Skip hash check")) << '\n'; + stream << CATEGORY_OPTION.usage(QObject::tr("name")) + << wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be " + "created.")) << '\n'; + stream << SEQUENTIAL_OPTION.usage() << wrapText(QObject::tr("Download files in sequential order")) << '\n'; + stream << FIRST_AND_LAST_OPTION.usage() + << wrapText(QObject::tr("Download first and last pieces first")) << '\n'; + stream << SKIP_DIALOG_OPTION.usage() + << wrapText(QObject::tr("Specifies whether the \"Add New Torrent\" dialog opens when adding a " + "torrent.")) << '\n'; + stream << '\n'; + + stream << wrapText(QObject::tr("Option values may be supplied via environment variables. For option named " + "'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper " + "case, '-' replaced with '_'). To pass flag values, set the variable to '1' or " + "'TRUE'. For example, to disable the splash screen: "), 0) << "\n" + << QLatin1String("QBT_NO_SPLASH=1 ") << prgName << '\n' + << wrapText(QObject::tr("Command line parameters take precedence over environment variables"), 0) << '\n'; stream << flush; return text; diff --git a/src/app/options.h b/src/app/options.h index ad15a4e78..0a406cb6c 100644 --- a/src/app/options.h +++ b/src/app/options.h @@ -38,11 +38,13 @@ #include #include +#include "base/tristatebool.h" + class QProcessEnvironment; struct QBtCommandLineParameters { - bool showHelp; + bool showHelp, relativeFastresumePaths, portableMode, skipChecking, sequential, firstLastPiecePriority; #ifndef Q_OS_WIN bool showVersion; #endif @@ -52,14 +54,12 @@ struct QBtCommandLineParameters bool shouldDaemonize; #endif int webUiPort; - QString profileDir; - bool relativeFastresumePaths; - bool portableMode; - QString configurationName; + TriStateBool addPaused, skipDialog; QStringList torrents; - QString unknownParameter; + QString profileDir, configurationName, savePath, category, unknownParameter; QBtCommandLineParameters(const QProcessEnvironment&); + QStringList paramList() const; }; class CommandLineParameterError: public std::runtime_error diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 2f82303f9..82882fa13 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -42,6 +42,7 @@ namespace BitTorrent QString savePath; bool disableTempPath = false; // e.g. for imported torrents bool sequential = false; + bool firstLastPiecePriority = false; TriStateBool addForced; TriStateBool addPaused; QVector filePriorities; // used if TorrentInfo is set diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index fd3a800ed..8a2c162a2 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -3666,6 +3666,8 @@ namespace magnetUri = MagnetUri(QString::fromStdString(fast.dict_find_string_value("qBt-magnetUri"))); torrentData.addPaused = fast.dict_find_int_value("qBt-paused"); torrentData.addForced = fast.dict_find_int_value("qBt-forced"); + torrentData.firstLastPiecePriority = fast.dict_find_int_value("qBt-firstLastPiecePriority"); + torrentData.sequential = fast.dict_find_int_value("qBt-sequential"); prio = fast.dict_find_int_value("qBt-queuePosition"); diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 976afd0a9..0a52254b3 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -90,6 +90,7 @@ AddTorrentData::AddTorrentData(const AddTorrentParams ¶ms) , savePath(params.savePath) , disableTempPath(params.disableTempPath) , sequential(params.sequential) + , firstLastPiecePriority(params.firstLastPiecePriority) , hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping , skipChecking(params.skipChecking) , hasRootFolder(params.createSubfolder == TriStateBool::Undefined @@ -208,6 +209,7 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle , m_tempPathDisabled(data.disableTempPath) , m_hasMissingFiles(false) , m_hasRootFolder(data.hasRootFolder) + , m_needsToSetFirstLastPiecePriority(false) , m_pauseAfterRecheck(false) , m_needSaveResumeData(false) { @@ -217,12 +219,22 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle updateStatus(); m_hash = InfoHash(m_nativeStatus.info_hash); - if (!data.resumed) { + // NB: the following two if statements are present because we don't want + // to set either sequential download or first/last piece priority to false + // if their respective flags in data are false when a torrent is being + // resumed. This is because, in that circumstance, this constructor is + // called with those flags set to false, even if the torrent was set to + // download sequentially or have first/last piece priority enabled when + // its resume data was saved. These two settings are restored later. But + // if we set them to false now, both will erroneously not be restored. + if (!data.resumed || data.sequential) setSequentialDownload(data.sequential); - if (hasMetadata()) { - if (filesCount() == 1) - m_hasRootFolder = false; - } + if (!data.resumed || data.firstLastPiecePriority) + setFirstLastPiecePriority(data.firstLastPiecePriority); + + if (!data.resumed && hasMetadata()) { + if (filesCount() == 1) + m_hasRootFolder = false; } } @@ -729,7 +741,8 @@ bool TorrentHandle::isSequentialDownload() const bool TorrentHandle::hasFirstLastPiecePriority() const { - if (!hasMetadata()) return false; + if (!hasMetadata()) + return m_needsToSetFirstLastPiecePriority; // Get int first media file std::vector fp; @@ -1229,7 +1242,10 @@ void TorrentHandle::toggleSequentialDownload() void TorrentHandle::setFirstLastPiecePriority(bool b) { - if (!hasMetadata()) return; + if (!hasMetadata()) { + m_needsToSetFirstLastPiecePriority = b; + return; + } std::vector fp = m_nativeHandle.file_priorities(); std::vector pp = m_nativeHandle.piece_priorities(); @@ -1508,6 +1524,11 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert resumeData["qBt-magnetUri"] = toMagnetUri().toStdString(); resumeData["qBt-paused"] = isPaused(); resumeData["qBt-forced"] = isForced(); + // Both firstLastPiecePriority and sequential need to be stored in the + // resume data if there is no metadata, otherwise they won't be + // restored if qBittorrent quits before the metadata are retrieved: + resumeData["qBt-firstLastPiecePriority"] = hasFirstLastPiecePriority(); + resumeData["qBt-sequential"] = isSequentialDownload(); } else { auto savePath = resumeData.find_key("save_path")->string(); @@ -1634,6 +1655,13 @@ void TorrentHandle::handleMetadataReceivedAlert(libt::metadata_received_alert *p m_speedMonitor.reset(); m_session->handleTorrentPaused(this); } + + // If first/last piece priority was specified when adding this torrent, we can set it + // now that we have metadata: + if (m_needsToSetFirstLastPiecePriority) { + setFirstLastPiecePriority(true); + m_needsToSetFirstLastPiecePriority = false; + } } void TorrentHandle::handleTempPathChanged() diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index 5f3afa7b7..8cdcfe02e 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -96,6 +96,7 @@ namespace BitTorrent QString savePath; bool disableTempPath; bool sequential; + bool firstLastPiecePriority; bool hasSeedStatus; bool skipChecking; bool hasRootFolder; @@ -429,6 +430,7 @@ namespace BitTorrent bool m_tempPathDisabled; bool m_hasMissingFiles; bool m_hasRootFolder; + bool m_needsToSetFirstLastPiecePriority; bool m_pauseAfterRecheck; bool m_needSaveResumeData; diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index b33814550..2524bc440 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -76,14 +76,16 @@ namespace } } -AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) +AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent) : QDialog(parent) , ui(new Ui::AddNewTorrentDialog) , m_contentModel(0) , m_contentDelegate(0) , m_hasMetadata(false) , m_oldIndex(0) + , m_torrentParams(inParams) { + // TODO: set dialog file properties using m_torrentParams.filePriorities ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); ui->lblMetaLoading->setVisible(false); @@ -91,7 +93,13 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) auto session = BitTorrent::Session::instance(); - ui->startTorrentCheckBox->setChecked(!session->isAddTorrentPaused()); + if (m_torrentParams.addPaused == TriStateBool::True) + ui->startTorrentCheckBox->setChecked(false); + else if (m_torrentParams.addPaused == TriStateBool::False) + ui->startTorrentCheckBox->setChecked(true); + else + ui->startTorrentCheckBox->setChecked(!session->isAddTorrentPaused()); + ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does it job at this point ui->comboTTM->setCurrentIndex(!session->isAutoTMMDisabledByDefault()); ui->comboTTM->blockSignals(false); @@ -99,8 +107,15 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int))); connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked())); ui->defaultSavePathCheckBox->setVisible(false); // Default path is selected by default - ui->createSubfolderCheckBox->setChecked(session->isCreateTorrentSubfolder()); + if (m_torrentParams.createSubfolder == TriStateBool::True) + ui->createSubfolderCheckBox->setChecked(true); + else if (m_torrentParams.createSubfolder == TriStateBool::False) + ui->createSubfolderCheckBox->setChecked(false); + else + ui->createSubfolderCheckBox->setChecked(session->isCreateTorrentSubfolder()); + + ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking); ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never); // Load categories @@ -108,12 +123,14 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) std::sort(categories.begin(), categories.end(), Utils::String::naturalCompareCaseInsensitive); QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString(); + if (!m_torrentParams.category.isEmpty()) + ui->categoryComboBox->addItem(m_torrentParams.category); if (!defaultCategory.isEmpty()) ui->categoryComboBox->addItem(defaultCategory); ui->categoryComboBox->addItem(""); foreach (const QString &category, categories) - if (category != defaultCategory) + if (category != defaultCategory && category != m_torrentParams.category) ui->categoryComboBox->addItem(category); ui->categoryComboBox->model()->sort(0); @@ -179,9 +196,9 @@ void AddNewTorrentDialog::saveState() settings()->storeValue(KEY_EXPANDED, ui->adv_button->isChecked()); } -void AddNewTorrentDialog::show(QString source, QWidget *parent) +void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent) { - AddNewTorrentDialog *dlg = new AddNewTorrentDialog(parent); + AddNewTorrentDialog *dlg = new AddNewTorrentDialog(inParams, parent); if (Utils::Misc::isUrl(source)) { // Launch downloader @@ -205,6 +222,11 @@ void AddNewTorrentDialog::show(QString source, QWidget *parent) } } +void AddNewTorrentDialog::show(QString source, QWidget *parent) +{ + show(source, BitTorrent::AddTorrentParams(), parent); +} + bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath) { if (torrentPath.startsWith("file://", Qt::CaseInsensitive)) @@ -416,6 +438,18 @@ void AddNewTorrentDialog::categoryChanged(int index) } } +void AddNewTorrentDialog::setSavePath(const QString &newPath) +{ + int existingIndex = indexOfSavePath(newPath); + if (existingIndex < 0) { + // New path, prepend to combo box + ui->savePathComboBox->insertItem(0, Utils::Fs::toNativePath(newPath), newPath); + existingIndex = 0; + } + ui->savePathComboBox->setCurrentIndex(existingIndex); + onSavePathChanged(existingIndex); +} + void AddNewTorrentDialog::browseButton_clicked() { disconnect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onSavePathChanged(int))); @@ -430,17 +464,7 @@ void AddNewTorrentDialog::browseButton_clicked() newPath = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath()); if (!newPath.isEmpty()) { - const int existingIndex = indexOfSavePath(newPath); - if (existingIndex >= 0) { - ui->savePathComboBox->setCurrentIndex(existingIndex); - } - else { - // New path, prepend to combo box - ui->savePathComboBox->insertItem(0, Utils::Fs::toNativePath(newPath), newPath); - ui->savePathComboBox->setCurrentIndex(0); - } - - onSavePathChanged(0); + setSavePath(newPath); } else { // Restore index @@ -579,6 +603,9 @@ void AddNewTorrentDialog::populateSavePathComboBox() foreach (const QString &savePath, settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList()) if (QDir(savePath) != defaultSaveDir) ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath); + + if (!m_torrentParams.savePath.isEmpty()) + setSavePath(m_torrentParams.savePath); } void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &) @@ -626,27 +653,25 @@ void AddNewTorrentDialog::accept() if (!m_hasMetadata) disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&))); - BitTorrent::AddTorrentParams params; - // TODO: Check if destination actually exists - params.skipChecking = ui->skipCheckingCheckBox->isChecked(); + m_torrentParams.skipChecking = ui->skipCheckingCheckBox->isChecked(); // Category - params.category = ui->categoryComboBox->currentText(); + m_torrentParams.category = ui->categoryComboBox->currentText(); if (ui->defaultCategoryCheckbox->isChecked()) - settings()->storeValue(KEY_DEFAULTCATEGORY, params.category); + settings()->storeValue(KEY_DEFAULTCATEGORY, m_torrentParams.category); // Save file priorities if (m_contentModel) - params.filePriorities = m_contentModel->model()->getFilePriorities(); + m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities(); - params.addPaused = TriStateBool(!ui->startTorrentCheckBox->isChecked()); - params.createSubfolder = TriStateBool(ui->createSubfolderCheckBox->isChecked()); + m_torrentParams.addPaused = TriStateBool(!ui->startTorrentCheckBox->isChecked()); + m_torrentParams.createSubfolder = TriStateBool(ui->createSubfolderCheckBox->isChecked()); QString savePath = ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString(); if (ui->comboTTM->currentIndex() != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode. - params.savePath = savePath; + m_torrentParams.savePath = savePath; saveSavePathHistory(); if (ui->defaultSavePathCheckBox->isChecked()) BitTorrent::Session::instance()->setDefaultSavePath(savePath); @@ -656,9 +681,9 @@ void AddNewTorrentDialog::accept() // Add torrent if (!m_hasMetadata) - BitTorrent::Session::instance()->addTorrent(m_hash, params); + BitTorrent::Session::instance()->addTorrent(m_hash, m_torrentParams); else - BitTorrent::Session::instance()->addTorrent(m_torrentInfo, params); + BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams); m_torrentGuard->markAsAddedToSession(); QDialog::accept(); diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index 0853f565f..35329b89e 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -38,6 +38,7 @@ #include "base/bittorrent/infohash.h" #include "base/bittorrent/torrentinfo.h" +#include "base/bittorrent/addtorrentparams.h" namespace BitTorrent { @@ -65,7 +66,8 @@ public: static bool isTopLevel(); static void setTopLevel(bool value); - static void show(QString source, QWidget *parent = 0); + static void show(QString source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent); + static void show(QString source, QWidget *parent); private slots: void showAdvancedSettings(bool show); @@ -87,7 +89,7 @@ private slots: void reject() override; private: - explicit AddNewTorrentDialog(QWidget *parent = 0); + explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent); bool loadTorrent(const QString &torrentPath); bool loadMagnet(const BitTorrent::MagnetUri &magnetUri); void populateSavePathComboBox(); @@ -98,6 +100,7 @@ private: void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = QString()); void setupTreeview(); void setCommentText(const QString &str) const; + void setSavePath(const QString &newPath); void showEvent(QShowEvent *event) override; @@ -112,6 +115,7 @@ private: QByteArray m_headerState; int m_oldIndex; QScopedPointer m_torrentGuard; + BitTorrent::AddTorrentParams m_torrentParams; }; #endif // ADDNEWTORRENTDIALOG_H diff --git a/src/gui/addnewtorrentdialog.ui b/src/gui/addnewtorrentdialog.ui index e010403b0..a4cfca76a 100644 --- a/src/gui/addnewtorrentdialog.ui +++ b/src/gui/addnewtorrentdialog.ui @@ -192,16 +192,6 @@ - - - - Start torrent - - - true - - - diff --git a/src/gui/rss/rsswidget.cpp b/src/gui/rss/rsswidget.cpp index 05fab9438..59d204f47 100644 --- a/src/gui/rss/rsswidget.cpp +++ b/src/gui/rss/rsswidget.cpp @@ -343,7 +343,7 @@ void RSSWidget::downloadSelectedTorrents() if (!article->torrentUrl().isEmpty()) { if (AddNewTorrentDialog::isEnabled()) - AddNewTorrentDialog::show(article->torrentUrl()); + AddNewTorrentDialog::show(article->torrentUrl(), window()); else BitTorrent::Session::instance()->addTorrent(article->torrentUrl()); }