Browse Source

Merge pull request #5532 from briankendall/torrent_command_line_arguments

Command line arguments for specifying options when adding torrents
adaptive-webui-19844
sledgehammer999 8 years ago committed by GitHub
parent
commit
4ea9a2f07e
  1. 54
      src/app/application.cpp
  2. 4
      src/app/main.cpp
  3. 249
      src/app/options.cpp
  4. 12
      src/app/options.h
  5. 1
      src/base/bittorrent/addtorrentparams.h
  6. 2
      src/base/bittorrent/session.cpp
  7. 42
      src/base/bittorrent/torrenthandle.cpp
  8. 2
      src/base/bittorrent/torrenthandle.h
  9. 81
      src/gui/addnewtorrentdialog.cpp
  10. 8
      src/gui/addnewtorrentdialog.h
  11. 10
      src/gui/addnewtorrentdialog.ui
  12. 2
      src/gui/rss/rsswidget.cpp

54
src/app/application.cpp

@ -402,15 +402,63 @@ void Application::processParams(const QStringList &params)
return; return;
} }
#endif #endif
BitTorrent::AddTorrentParams torrentParams;
TriStateBool skipTorrentDialog;
foreach (QString param, params) { foreach (QString param, params) {
param = param.trimmed(); 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 #ifndef DISABLE_GUI
if (AddNewTorrentDialog::isEnabled()) // There are two circumstances in which we want to show the torrent
AddNewTorrentDialog::show(param, m_window); // 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 else
#endif #endif
BitTorrent::Session::instance()->addTorrent(param); BitTorrent::Session::instance()->addTorrent(param, torrentParams);
} }
} }

4
src/app/main.cpp

@ -182,7 +182,7 @@ int main(int argc, char *argv[])
qDebug("qBittorrent is already running for this user."); qDebug("qBittorrent is already running for this user.");
QThread::msleep(300); QThread::msleep(300);
app->sendParams(params.torrents); app->sendParams(params.paramList());
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@ -247,7 +247,7 @@ int main(int argc, char *argv[])
signal(SIGSEGV, sigAbnormalHandler); signal(SIGSEGV, sigAbnormalHandler);
#endif #endif
return app->exec(params.torrents); return app->exec(params.paramList());
} }
catch (CommandLineParameterError &er) { catch (CommandLineParameterError &er) {
displayBadArgMessage(er.messageForUser()); displayBadArgMessage(er.messageForUser());

249
src/app/options.cpp

@ -47,6 +47,10 @@
namespace namespace
{ {
const int USAGE_INDENTATION = 4;
const int USAGE_TEXT_COLUMN = 31;
const int WRAP_AT_COLUMN = 80;
// Base option class. Encapsulates name operations. // Base option class. Encapsulates name operations.
class Option class Option
{ {
@ -78,14 +82,15 @@ namespace
+ QString(QLatin1String(m_name)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_')); + QString(QLatin1String(m_name)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_'));
} }
public:
static QString padUsageText(const QString &usage) static QString padUsageText(const QString &usage)
{ {
const int TAB_WIDTH = 8; QString res = QString(USAGE_INDENTATION, ' ') + usage;
QString res = QLatin1String("\t") + usage;
if (usage.size() < 2 * TAB_WIDTH) if ((USAGE_TEXT_COLUMN - usage.length() - 4) > 0)
return res + QLatin1String("\t\t"); return res + QString(USAGE_TEXT_COLUMN - usage.length() - 4, ' ');
else else
return res + QLatin1String("\t"); return res;
} }
private: 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; 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; 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=<true|false>'")
.arg(fullParameter())
.arg(QLatin1String("<true|false>")));
}
}
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; return o == s;
} }
@ -247,10 +314,22 @@ namespace
constexpr const StringOption CONFIGURATION_OPTION = {"configuration"}; constexpr const StringOption CONFIGURATION_OPTION = {"configuration"};
constexpr const BoolOption PORTABLE_OPTION = {"portable"}; constexpr const BoolOption PORTABLE_OPTION = {"portable"};
constexpr const BoolOption RELATIVE_FASTRESUME = {"relative-fastresume"}; 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) QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
: showHelp(false) : 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 #ifndef Q_OS_WIN
, showVersion(false) , showVersion(false)
#endif #endif
@ -260,13 +339,59 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
, shouldDaemonize(DAEMON_OPTION.value(env)) , shouldDaemonize(DAEMON_OPTION.value(env))
#endif #endif
, webUiPort(WEBUI_PORT_OPTION.value(env, -1)) , webUiPort(WEBUI_PORT_OPTION.value(env, -1))
, addPaused(PAUSED_OPTION.value(env))
, skipDialog(SKIP_DIALOG_OPTION.value(env))
, profileDir(PROFILE_OPTION.value(env)) , profileDir(PROFILE_OPTION.value(env))
, relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
, portableMode(PORTABLE_OPTION.value(env))
, configurationName(CONFIGURATION_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 parseCommandLine(const QStringList &args)
{ {
QBtCommandLineParameters result {QProcessEnvironment::systemEnvironment()}; QBtCommandLineParameters result {QProcessEnvironment::systemEnvironment()};
@ -312,6 +437,27 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
else if (arg == CONFIGURATION_OPTION) { else if (arg == CONFIGURATION_OPTION) {
result.configurationName = CONFIGURATION_OPTION.value(arg); 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 { else {
// Unknown argument // Unknown argument
result.unknownParameter = arg; result.unknownParameter = arg;
@ -343,43 +489,82 @@ const QString& CommandLineParameterError::messageForUser() const
return m_messageForUser; 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 makeUsage(const QString &prgName)
{ {
QString text; QString text;
QTextStream stream(&text, QIODevice::WriteOnly); QTextStream stream(&text, QIODevice::WriteOnly);
QString indentation = QString(USAGE_INDENTATION, ' ');
stream << QObject::tr("Usage:") << '\n'; stream << QObject::tr("Usage:") << '\n';
#ifndef Q_OS_WIN stream << indentation << prgName << QLatin1String(" [options] [(<filename> | <url>)...]") << '\n';
stream << '\t' << prgName << " [options] [(<filename> | <url>)...]" << '\n';
#endif
stream << QObject::tr("Options:") << '\n'; stream << QObject::tr("Options:") << '\n';
#ifndef Q_OS_WIN #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 #endif
stream << SHOW_HELP_OPTION.usage() << QObject::tr("Displays this help message and exit") << '\n'; stream << SHOW_HELP_OPTION.usage() << wrapText(QObject::tr("Displays this help message and exit")) << '\n';
stream << WEBUI_PORT_OPTION.usage(QLatin1String("port")) stream << WEBUI_PORT_OPTION.usage(QObject::tr("port"))
<< QObject::tr("Changes the Web UI port") << wrapText(QObject::tr("Changes the Web UI port"))
<< '\n'; << '\n';
#ifndef DISABLE_GUI #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 #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 #endif
stream << PROFILE_OPTION.usage(QLatin1String("dir")) << QObject::tr("Store configuration files in <dir>") << '\n'; //: Use appropriate short form or abbreviation of "directory"
stream << CONFIGURATION_OPTION.usage(QLatin1String("name")) << QObject::tr("Store configuration files in directories qBittorrent_<name>") << '\n'; stream << PROFILE_OPTION.usage(QObject::tr("dir"))
stream << RELATIVE_FASTRESUME.usage() << QObject::tr("Hack into libtorrent fastresume files and make file paths relative to the profile directory") << '\n'; << wrapText(QObject::tr("Store configuration files in <dir>")) << '\n';
stream << PORTABLE_OPTION.usage() << QObject::tr("Shortcut for --profile=<exe dir>/profile --relative-fastresume") << '\n'; stream << CONFIGURATION_OPTION.usage(QObject::tr("name"))
stream << "\tfiles or urls\t\t" << QObject::tr("Downloads the torrents passed by the user") << '\n' << wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) << '\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=<exe dir>/profile --relative-fastresume")) << '\n';
stream << Option::padUsageText(QObject::tr("files or urls"))
<< wrapText(QObject::tr("Downloads the torrents passed by the user")) << '\n'
<< '\n'; << '\n';
stream << QObject::tr("Option values may be supplied via environment variables.") << '\n' stream << wrapText(QObject::tr("Options when adding new torrents:"), 0) << '\n';
<< QObject::tr("For option named 'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper case, '-' replaced with '_')") << '\n' stream << SAVE_PATH_OPTION.usage(QObject::tr("path")) << wrapText(QObject::tr("Torrent save path")) << '\n';
<< QObject::tr("To pass flag values, set the variable to '1' or 'TRUE'.") << '\n' stream << PAUSED_OPTION.usage() << wrapText(QObject::tr("Add torrents as started or paused")) << '\n';
<< QObject::tr("For example, to disable the splash screen: ") stream << SKIP_HASH_CHECK_OPTION.usage() << wrapText(QObject::tr("Skip hash check")) << '\n';
<< "QBT_NO_SPLASH=1 " << prgName << '\n' stream << CATEGORY_OPTION.usage(QObject::tr("name"))
<< '\n' << wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
<< QObject::tr("Command line parameters take precedence over environment variables") << '\n'; "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; stream << flush;
return text; return text;

12
src/app/options.h

@ -38,11 +38,13 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include "base/tristatebool.h"
class QProcessEnvironment; class QProcessEnvironment;
struct QBtCommandLineParameters struct QBtCommandLineParameters
{ {
bool showHelp; bool showHelp, relativeFastresumePaths, portableMode, skipChecking, sequential, firstLastPiecePriority;
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
bool showVersion; bool showVersion;
#endif #endif
@ -52,14 +54,12 @@ struct QBtCommandLineParameters
bool shouldDaemonize; bool shouldDaemonize;
#endif #endif
int webUiPort; int webUiPort;
QString profileDir; TriStateBool addPaused, skipDialog;
bool relativeFastresumePaths;
bool portableMode;
QString configurationName;
QStringList torrents; QStringList torrents;
QString unknownParameter; QString profileDir, configurationName, savePath, category, unknownParameter;
QBtCommandLineParameters(const QProcessEnvironment&); QBtCommandLineParameters(const QProcessEnvironment&);
QStringList paramList() const;
}; };
class CommandLineParameterError: public std::runtime_error class CommandLineParameterError: public std::runtime_error

1
src/base/bittorrent/addtorrentparams.h

@ -42,6 +42,7 @@ namespace BitTorrent
QString savePath; QString savePath;
bool disableTempPath = false; // e.g. for imported torrents bool disableTempPath = false; // e.g. for imported torrents
bool sequential = false; bool sequential = false;
bool firstLastPiecePriority = false;
TriStateBool addForced; TriStateBool addForced;
TriStateBool addPaused; TriStateBool addPaused;
QVector<int> filePriorities; // used if TorrentInfo is set QVector<int> filePriorities; // used if TorrentInfo is set

2
src/base/bittorrent/session.cpp

@ -3666,6 +3666,8 @@ namespace
magnetUri = MagnetUri(QString::fromStdString(fast.dict_find_string_value("qBt-magnetUri"))); magnetUri = MagnetUri(QString::fromStdString(fast.dict_find_string_value("qBt-magnetUri")));
torrentData.addPaused = fast.dict_find_int_value("qBt-paused"); torrentData.addPaused = fast.dict_find_int_value("qBt-paused");
torrentData.addForced = fast.dict_find_int_value("qBt-forced"); 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"); prio = fast.dict_find_int_value("qBt-queuePosition");

42
src/base/bittorrent/torrenthandle.cpp

@ -90,6 +90,7 @@ AddTorrentData::AddTorrentData(const AddTorrentParams &params)
, savePath(params.savePath) , savePath(params.savePath)
, disableTempPath(params.disableTempPath) , disableTempPath(params.disableTempPath)
, sequential(params.sequential) , sequential(params.sequential)
, firstLastPiecePriority(params.firstLastPiecePriority)
, hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping , hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping
, skipChecking(params.skipChecking) , skipChecking(params.skipChecking)
, hasRootFolder(params.createSubfolder == TriStateBool::Undefined , hasRootFolder(params.createSubfolder == TriStateBool::Undefined
@ -208,6 +209,7 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
, m_tempPathDisabled(data.disableTempPath) , m_tempPathDisabled(data.disableTempPath)
, m_hasMissingFiles(false) , m_hasMissingFiles(false)
, m_hasRootFolder(data.hasRootFolder) , m_hasRootFolder(data.hasRootFolder)
, m_needsToSetFirstLastPiecePriority(false)
, m_pauseAfterRecheck(false) , m_pauseAfterRecheck(false)
, m_needSaveResumeData(false) , m_needSaveResumeData(false)
{ {
@ -217,12 +219,22 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
updateStatus(); updateStatus();
m_hash = InfoHash(m_nativeStatus.info_hash); 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); setSequentialDownload(data.sequential);
if (hasMetadata()) { if (!data.resumed || data.firstLastPiecePriority)
if (filesCount() == 1) setFirstLastPiecePriority(data.firstLastPiecePriority);
m_hasRootFolder = false;
} if (!data.resumed && hasMetadata()) {
if (filesCount() == 1)
m_hasRootFolder = false;
} }
} }
@ -729,7 +741,8 @@ bool TorrentHandle::isSequentialDownload() const
bool TorrentHandle::hasFirstLastPiecePriority() const bool TorrentHandle::hasFirstLastPiecePriority() const
{ {
if (!hasMetadata()) return false; if (!hasMetadata())
return m_needsToSetFirstLastPiecePriority;
// Get int first media file // Get int first media file
std::vector<int> fp; std::vector<int> fp;
@ -1229,7 +1242,10 @@ void TorrentHandle::toggleSequentialDownload()
void TorrentHandle::setFirstLastPiecePriority(bool b) void TorrentHandle::setFirstLastPiecePriority(bool b)
{ {
if (!hasMetadata()) return; if (!hasMetadata()) {
m_needsToSetFirstLastPiecePriority = b;
return;
}
std::vector<int> fp = m_nativeHandle.file_priorities(); std::vector<int> fp = m_nativeHandle.file_priorities();
std::vector<int> pp = m_nativeHandle.piece_priorities(); std::vector<int> pp = m_nativeHandle.piece_priorities();
@ -1508,6 +1524,11 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert
resumeData["qBt-magnetUri"] = toMagnetUri().toStdString(); resumeData["qBt-magnetUri"] = toMagnetUri().toStdString();
resumeData["qBt-paused"] = isPaused(); resumeData["qBt-paused"] = isPaused();
resumeData["qBt-forced"] = isForced(); 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 { else {
auto savePath = resumeData.find_key("save_path")->string(); auto savePath = resumeData.find_key("save_path")->string();
@ -1634,6 +1655,13 @@ void TorrentHandle::handleMetadataReceivedAlert(libt::metadata_received_alert *p
m_speedMonitor.reset(); m_speedMonitor.reset();
m_session->handleTorrentPaused(this); 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() void TorrentHandle::handleTempPathChanged()

2
src/base/bittorrent/torrenthandle.h

@ -96,6 +96,7 @@ namespace BitTorrent
QString savePath; QString savePath;
bool disableTempPath; bool disableTempPath;
bool sequential; bool sequential;
bool firstLastPiecePriority;
bool hasSeedStatus; bool hasSeedStatus;
bool skipChecking; bool skipChecking;
bool hasRootFolder; bool hasRootFolder;
@ -429,6 +430,7 @@ namespace BitTorrent
bool m_tempPathDisabled; bool m_tempPathDisabled;
bool m_hasMissingFiles; bool m_hasMissingFiles;
bool m_hasRootFolder; bool m_hasRootFolder;
bool m_needsToSetFirstLastPiecePriority;
bool m_pauseAfterRecheck; bool m_pauseAfterRecheck;
bool m_needSaveResumeData; bool m_needSaveResumeData;

81
src/gui/addnewtorrentdialog.cpp

@ -76,14 +76,16 @@ namespace
} }
} }
AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::AddNewTorrentDialog) , ui(new Ui::AddNewTorrentDialog)
, m_contentModel(0) , m_contentModel(0)
, m_contentDelegate(0) , m_contentDelegate(0)
, m_hasMetadata(false) , m_hasMetadata(false)
, m_oldIndex(0) , m_oldIndex(0)
, m_torrentParams(inParams)
{ {
// TODO: set dialog file properties using m_torrentParams.filePriorities
ui->setupUi(this); ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
ui->lblMetaLoading->setVisible(false); ui->lblMetaLoading->setVisible(false);
@ -91,7 +93,13 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent)
auto session = BitTorrent::Session::instance(); 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->blockSignals(true); // the TreeView size isn't correct if the slot does it job at this point
ui->comboTTM->setCurrentIndex(!session->isAutoTMMDisabledByDefault()); ui->comboTTM->setCurrentIndex(!session->isAutoTMMDisabledByDefault());
ui->comboTTM->blockSignals(false); ui->comboTTM->blockSignals(false);
@ -99,8 +107,15 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent)
connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int))); connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked())); connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked()));
ui->defaultSavePathCheckBox->setVisible(false); // Default path is selected by default 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); ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
// Load categories // Load categories
@ -108,12 +123,14 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent)
std::sort(categories.begin(), categories.end(), Utils::String::naturalCompareCaseInsensitive); std::sort(categories.begin(), categories.end(), Utils::String::naturalCompareCaseInsensitive);
QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString(); QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString();
if (!m_torrentParams.category.isEmpty())
ui->categoryComboBox->addItem(m_torrentParams.category);
if (!defaultCategory.isEmpty()) if (!defaultCategory.isEmpty())
ui->categoryComboBox->addItem(defaultCategory); ui->categoryComboBox->addItem(defaultCategory);
ui->categoryComboBox->addItem(""); ui->categoryComboBox->addItem("");
foreach (const QString &category, categories) foreach (const QString &category, categories)
if (category != defaultCategory) if (category != defaultCategory && category != m_torrentParams.category)
ui->categoryComboBox->addItem(category); ui->categoryComboBox->addItem(category);
ui->categoryComboBox->model()->sort(0); ui->categoryComboBox->model()->sort(0);
@ -179,9 +196,9 @@ void AddNewTorrentDialog::saveState()
settings()->storeValue(KEY_EXPANDED, ui->adv_button->isChecked()); 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)) { if (Utils::Misc::isUrl(source)) {
// Launch downloader // 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) bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath)
{ {
if (torrentPath.startsWith("file://", Qt::CaseInsensitive)) 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() void AddNewTorrentDialog::browseButton_clicked()
{ {
disconnect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onSavePathChanged(int))); 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()); newPath = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath());
if (!newPath.isEmpty()) { if (!newPath.isEmpty()) {
const int existingIndex = indexOfSavePath(newPath); setSavePath(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);
} }
else { else {
// Restore index // Restore index
@ -579,6 +603,9 @@ void AddNewTorrentDialog::populateSavePathComboBox()
foreach (const QString &savePath, settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList()) foreach (const QString &savePath, settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList())
if (QDir(savePath) != defaultSaveDir) if (QDir(savePath) != defaultSaveDir)
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath); ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath);
if (!m_torrentParams.savePath.isEmpty())
setSavePath(m_torrentParams.savePath);
} }
void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &) void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
@ -626,27 +653,25 @@ void AddNewTorrentDialog::accept()
if (!m_hasMetadata) if (!m_hasMetadata)
disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&))); disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&)));
BitTorrent::AddTorrentParams params;
// TODO: Check if destination actually exists // TODO: Check if destination actually exists
params.skipChecking = ui->skipCheckingCheckBox->isChecked(); m_torrentParams.skipChecking = ui->skipCheckingCheckBox->isChecked();
// Category // Category
params.category = ui->categoryComboBox->currentText(); m_torrentParams.category = ui->categoryComboBox->currentText();
if (ui->defaultCategoryCheckbox->isChecked()) if (ui->defaultCategoryCheckbox->isChecked())
settings()->storeValue(KEY_DEFAULTCATEGORY, params.category); settings()->storeValue(KEY_DEFAULTCATEGORY, m_torrentParams.category);
// Save file priorities // Save file priorities
if (m_contentModel) if (m_contentModel)
params.filePriorities = m_contentModel->model()->getFilePriorities(); m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities();
params.addPaused = TriStateBool(!ui->startTorrentCheckBox->isChecked()); m_torrentParams.addPaused = TriStateBool(!ui->startTorrentCheckBox->isChecked());
params.createSubfolder = TriStateBool(ui->createSubfolderCheckBox->isChecked()); m_torrentParams.createSubfolder = TriStateBool(ui->createSubfolderCheckBox->isChecked());
QString savePath = ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString(); 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. 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(); saveSavePathHistory();
if (ui->defaultSavePathCheckBox->isChecked()) if (ui->defaultSavePathCheckBox->isChecked())
BitTorrent::Session::instance()->setDefaultSavePath(savePath); BitTorrent::Session::instance()->setDefaultSavePath(savePath);
@ -656,9 +681,9 @@ void AddNewTorrentDialog::accept()
// Add torrent // Add torrent
if (!m_hasMetadata) if (!m_hasMetadata)
BitTorrent::Session::instance()->addTorrent(m_hash, params); BitTorrent::Session::instance()->addTorrent(m_hash, m_torrentParams);
else else
BitTorrent::Session::instance()->addTorrent(m_torrentInfo, params); BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams);
m_torrentGuard->markAsAddedToSession(); m_torrentGuard->markAsAddedToSession();
QDialog::accept(); QDialog::accept();

8
src/gui/addnewtorrentdialog.h

@ -38,6 +38,7 @@
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "base/bittorrent/torrentinfo.h" #include "base/bittorrent/torrentinfo.h"
#include "base/bittorrent/addtorrentparams.h"
namespace BitTorrent namespace BitTorrent
{ {
@ -65,7 +66,8 @@ public:
static bool isTopLevel(); static bool isTopLevel();
static void setTopLevel(bool value); 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: private slots:
void showAdvancedSettings(bool show); void showAdvancedSettings(bool show);
@ -87,7 +89,7 @@ private slots:
void reject() override; void reject() override;
private: private:
explicit AddNewTorrentDialog(QWidget *parent = 0); explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
bool loadTorrent(const QString &torrentPath); bool loadTorrent(const QString &torrentPath);
bool loadMagnet(const BitTorrent::MagnetUri &magnetUri); bool loadMagnet(const BitTorrent::MagnetUri &magnetUri);
void populateSavePathComboBox(); void populateSavePathComboBox();
@ -98,6 +100,7 @@ private:
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = QString()); void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = QString());
void setupTreeview(); void setupTreeview();
void setCommentText(const QString &str) const; void setCommentText(const QString &str) const;
void setSavePath(const QString &newPath);
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
@ -112,6 +115,7 @@ private:
QByteArray m_headerState; QByteArray m_headerState;
int m_oldIndex; int m_oldIndex;
QScopedPointer<TorrentFileGuard> m_torrentGuard; QScopedPointer<TorrentFileGuard> m_torrentGuard;
BitTorrent::AddTorrentParams m_torrentParams;
}; };
#endif // ADDNEWTORRENTDIALOG_H #endif // ADDNEWTORRENTDIALOG_H

10
src/gui/addnewtorrentdialog.ui

@ -192,16 +192,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="0" column="0">
<widget class="QCheckBox" name="startTorrentCheckBox_2">
<property name="text">
<string>Start torrent</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QCheckBox" name="createSubfolderCheckBox"> <widget class="QCheckBox" name="createSubfolderCheckBox">
<property name="text"> <property name="text">

2
src/gui/rss/rsswidget.cpp

@ -343,7 +343,7 @@ void RSSWidget::downloadSelectedTorrents()
if (!article->torrentUrl().isEmpty()) { if (!article->torrentUrl().isEmpty()) {
if (AddNewTorrentDialog::isEnabled()) if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(article->torrentUrl()); AddNewTorrentDialog::show(article->torrentUrl(), window());
else else
BitTorrent::Session::instance()->addTorrent(article->torrentUrl()); BitTorrent::Session::instance()->addTorrent(article->torrentUrl());
} }

Loading…
Cancel
Save