diff --git a/src/app/application.cpp b/src/app/application.cpp index 4dc8b61dd..eedc9e170 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -402,15 +402,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 c48ea9d59..c5cdd7fca 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -182,7 +182,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; } @@ -247,7 +247,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()); }