Browse Source

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.
adaptive-webui-19844
Brian Kendall 8 years ago
parent
commit
eba41978b0
  1. 54
      src/app/application.cpp
  2. 4
      src/app/main.cpp
  3. 247
      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

@ -407,15 +407,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

@ -170,7 +170,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;
} }
@ -235,7 +235,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());

247
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