2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2016 Eugene Shalygin <eugene.shalygin@gmail.com>
4 * Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
5 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "cmdoptions.h"
37 #include <QProcessEnvironment>
38 #include <QTextStream>
40 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
41 #include <QMessageBox>
44 #include "base/global.h"
45 #include "base/utils/misc.h"
46 #include "base/utils/string.h"
49 #include "gui/utils.h"
54 const int USAGE_INDENTATION
= 4;
55 const int USAGE_TEXT_COLUMN
= 31;
56 const int WRAP_AT_COLUMN
= 80;
58 // Base option class. Encapsulates name operations.
62 explicit constexpr Option(const char *name
, char shortcut
= 0)
64 , m_shortcut
{shortcut
}
68 QString
fullParameter() const
70 return QLatin1String("--") + QLatin1String(m_name
);
73 QString
shortcutParameter() const
75 return QLatin1String("-") + QLatin1Char(m_shortcut
);
78 bool hasShortcut() const
80 return m_shortcut
!= 0;
83 QString
envVarName() const
85 return QLatin1String("QBT_")
86 + QString::fromLatin1(m_name
).toUpper().replace(QLatin1Char('-'), QLatin1Char('_'));
90 static QString
padUsageText(const QString
&usage
)
92 QString res
= QString(USAGE_INDENTATION
, ' ') + usage
;
94 if ((USAGE_TEXT_COLUMN
- usage
.length() - 4) > 0)
95 return res
+ QString(USAGE_TEXT_COLUMN
- usage
.length() - 4, ' ');
102 const char m_shortcut
;
106 class BoolOption
: protected Option
109 explicit constexpr BoolOption(const char *name
, char shortcut
= 0)
110 : Option
{name
, shortcut
}
114 bool operator==(const QString
&arg
) const
116 return (hasShortcut() && ((arg
.size() == 2) && (arg
== shortcutParameter())))
117 || (arg
== fullParameter());
120 bool value(const QProcessEnvironment
&env
) const
122 QString val
= env
.value(envVarName());
123 // we accept "1" and "true" (upper or lower cased) as boolean 'true' values
124 return ((val
== QLatin1String("1")) || (val
.toUpper() == QLatin1String("TRUE")));
127 QString
usage() const
131 res
+= shortcutParameter() + QLatin1String(" | ");
132 res
+= fullParameter();
133 return padUsageText(res
);
137 bool operator==(const QString
&s
, const BoolOption
&o
)
142 // Option with string value. May not have a shortcut
143 struct StringOption
: protected Option
146 explicit constexpr StringOption(const char *name
)
151 bool operator==(const QString
&arg
) const
153 return arg
.startsWith(parameterAssignment());
156 QString
value(const QString
&arg
) const
158 QStringList parts
= arg
.split(QLatin1Char('='));
159 if (parts
.size() == 2)
160 return Utils::String::unquote(parts
[1], QLatin1String("'\""));
161 throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
162 "e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
163 .arg(fullParameter(), QLatin1String("<value>")));
166 QString
value(const QProcessEnvironment
&env
, const QString
&defaultValue
= {}) const
168 QString val
= env
.value(envVarName());
169 return val
.isEmpty() ? defaultValue
: Utils::String::unquote(val
, QLatin1String("'\""));
172 QString
usage(const QString
&valueName
) const
174 return padUsageText(parameterAssignment() + QLatin1Char('<') + valueName
+ QLatin1Char('>'));
178 QString
parameterAssignment() const
180 return fullParameter() + QLatin1Char('=');
184 bool operator==(const QString
&s
, const StringOption
&o
)
189 // Option with integer value. May not have a shortcut
190 class IntOption
: protected StringOption
193 explicit constexpr IntOption(const char *name
)
194 : StringOption
{name
}
198 using StringOption::operator==;
199 using StringOption::usage
;
201 int value(const QString
&arg
) const
203 QString val
= StringOption::value(arg
);
205 int res
= val
.toInt(&ok
);
207 throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
208 "e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
209 .arg(fullParameter(), QLatin1String("<integer value>")));
213 int value(const QProcessEnvironment
&env
, int defaultValue
) const
215 QString val
= env
.value(envVarName());
216 if (val
.isEmpty()) return defaultValue
;
219 int res
= val
.toInt(&ok
);
222 qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
223 .arg(envVarName(), val
);
230 bool operator==(const QString
&s
, const IntOption
&o
)
235 // Option that is explicitly set to true or false, and whose value is undefined when unspecified.
236 // May not have a shortcut.
237 class TriStateBoolOption
: protected Option
240 constexpr TriStateBoolOption(const char *name
, bool defaultValue
)
242 , m_defaultValue(defaultValue
)
246 bool operator==(const QString
&arg
) const
248 QStringList parts
= arg
.split(QLatin1Char('='));
249 return parts
[0] == fullParameter();
252 QString
usage() const
254 return padUsageText(fullParameter() + QLatin1String("=<true|false>"));
257 std::optional
<bool> value(const QString
&arg
) const
259 QStringList parts
= arg
.split(QLatin1Char('='));
261 if (parts
.size() == 1)
263 return m_defaultValue
;
265 if (parts
.size() == 2)
267 QString val
= parts
[1];
269 if ((val
.toUpper() == QLatin1String("TRUE")) || (val
== QLatin1String("1")))
273 if ((val
.toUpper() == QLatin1String("FALSE")) || (val
== QLatin1String("0")))
279 throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
280 "e.g. Parameter '--add-paused' must follow syntax "
281 "'--add-paused=<true|false>'")
282 .arg(fullParameter(), QLatin1String("<true|false>")));
285 std::optional
<bool> value(const QProcessEnvironment
&env
) const
287 const QString val
= env
.value(envVarName(), "-1");
291 return m_defaultValue
;
293 if (val
== QLatin1String("-1"))
297 if ((val
.toUpper() == QLatin1String("TRUE")) || (val
== QLatin1String("1")))
301 if ((val
.toUpper() == QLatin1String("FALSE")) || (val
== QLatin1String("0")))
306 qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
307 .arg(QLatin1String("true|false"), envVarName(), val
);
314 bool operator==(const QString
&s
, const TriStateBoolOption
&o
)
319 constexpr const BoolOption SHOW_HELP_OPTION
{"help", 'h'};
320 constexpr const BoolOption SHOW_VERSION_OPTION
{"version", 'v'};
321 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
322 constexpr const BoolOption DAEMON_OPTION
{"daemon", 'd'};
324 constexpr const BoolOption NO_SPLASH_OPTION
{"no-splash"};
326 constexpr const IntOption WEBUI_PORT_OPTION
{"webui-port"};
327 constexpr const StringOption PROFILE_OPTION
{"profile"};
328 constexpr const StringOption CONFIGURATION_OPTION
{"configuration"};
329 constexpr const BoolOption RELATIVE_FASTRESUME
{"relative-fastresume"};
330 constexpr const StringOption SAVE_PATH_OPTION
{"save-path"};
331 constexpr const TriStateBoolOption PAUSED_OPTION
{"add-paused", true};
332 constexpr const BoolOption SKIP_HASH_CHECK_OPTION
{"skip-hash-check"};
333 constexpr const StringOption CATEGORY_OPTION
{"category"};
334 constexpr const BoolOption SEQUENTIAL_OPTION
{"sequential"};
335 constexpr const BoolOption FIRST_AND_LAST_OPTION
{"first-and-last"};
336 constexpr const TriStateBoolOption SKIP_DIALOG_OPTION
{"skip-dialog", true};
339 QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment
&env
)
341 , relativeFastresumePaths(RELATIVE_FASTRESUME
.value(env
))
342 , skipChecking(SKIP_HASH_CHECK_OPTION
.value(env
))
343 , sequential(SEQUENTIAL_OPTION
.value(env
))
344 , firstLastPiecePriority(FIRST_AND_LAST_OPTION
.value(env
))
345 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
349 , noSplash(NO_SPLASH_OPTION
.value(env
))
350 #elif !defined(Q_OS_WIN)
351 , shouldDaemonize(DAEMON_OPTION
.value(env
))
353 , webUiPort(WEBUI_PORT_OPTION
.value(env
, -1))
354 , addPaused(PAUSED_OPTION
.value(env
))
355 , skipDialog(SKIP_DIALOG_OPTION
.value(env
))
356 , profileDir(PROFILE_OPTION
.value(env
))
357 , configurationName(CONFIGURATION_OPTION
.value(env
))
358 , savePath(SAVE_PATH_OPTION
.value(env
))
359 , category(CATEGORY_OPTION
.value(env
))
363 QStringList
QBtCommandLineParameters::paramList() const
366 // Because we're passing a string list to the currently running
367 // qBittorrent process, we need some way of passing along the options
368 // the user has specified. Here we place special strings that are
369 // almost certainly not going to collide with a file path or URL
370 // specified by the user, and placing them at the beginning of the
371 // string list so that they will be processed before the list of
372 // torrent paths or URLs.
374 if (!savePath
.isEmpty())
375 result
.append(QLatin1String("@savePath=") + savePath
);
377 if (addPaused
.has_value())
378 result
.append(*addPaused
? QLatin1String
{"@addPaused=1"} : QLatin1String
{"@addPaused=0"});
381 result
.append(QLatin1String("@skipChecking"));
383 if (!category
.isEmpty())
384 result
.append(QLatin1String("@category=") + category
);
387 result
.append(QLatin1String("@sequential"));
389 if (firstLastPiecePriority
)
390 result
.append(QLatin1String("@firstLastPiecePriority"));
392 if (skipDialog
.has_value())
393 result
.append(*skipDialog
? QLatin1String
{"@skipDialog=1"} : QLatin1String
{"@skipDialog=0"});
399 QBtCommandLineParameters
parseCommandLine(const QStringList
&args
)
401 QBtCommandLineParameters result
{QProcessEnvironment::systemEnvironment()};
403 for (int i
= 1; i
< args
.count(); ++i
)
405 const QString
&arg
= args
[i
];
407 if ((arg
.startsWith("--") && !arg
.endsWith(".torrent"))
408 || (arg
.startsWith('-') && (arg
.size() == 2)))
410 // Parse known parameters
411 if (arg
== SHOW_HELP_OPTION
)
413 result
.showHelp
= true;
415 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
416 else if (arg
== SHOW_VERSION_OPTION
)
418 result
.showVersion
= true;
421 else if (arg
== WEBUI_PORT_OPTION
)
423 result
.webUiPort
= WEBUI_PORT_OPTION
.value(arg
);
424 if ((result
.webUiPort
< 1) || (result
.webUiPort
> 65535))
425 throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
426 .arg(QLatin1String("--webui-port")));
429 else if (arg
== NO_SPLASH_OPTION
)
431 result
.noSplash
= true;
433 #elif !defined(Q_OS_WIN)
434 else if (arg
== DAEMON_OPTION
)
436 result
.shouldDaemonize
= true;
439 else if (arg
== PROFILE_OPTION
)
441 result
.profileDir
= PROFILE_OPTION
.value(arg
);
443 else if (arg
== RELATIVE_FASTRESUME
)
445 result
.relativeFastresumePaths
= true;
447 else if (arg
== CONFIGURATION_OPTION
)
449 result
.configurationName
= CONFIGURATION_OPTION
.value(arg
);
451 else if (arg
== SAVE_PATH_OPTION
)
453 result
.savePath
= SAVE_PATH_OPTION
.value(arg
);
455 else if (arg
== PAUSED_OPTION
)
457 result
.addPaused
= PAUSED_OPTION
.value(arg
);
459 else if (arg
== SKIP_HASH_CHECK_OPTION
)
461 result
.skipChecking
= true;
463 else if (arg
== CATEGORY_OPTION
)
465 result
.category
= CATEGORY_OPTION
.value(arg
);
467 else if (arg
== SEQUENTIAL_OPTION
)
469 result
.sequential
= true;
471 else if (arg
== FIRST_AND_LAST_OPTION
)
473 result
.firstLastPiecePriority
= true;
475 else if (arg
== SKIP_DIALOG_OPTION
)
477 result
.skipDialog
= SKIP_DIALOG_OPTION
.value(arg
);
482 result
.unknownParameter
= arg
;
488 QFileInfo torrentPath
;
489 torrentPath
.setFile(arg
);
491 if (torrentPath
.exists())
492 result
.torrents
+= torrentPath
.absoluteFilePath();
494 result
.torrents
+= arg
;
501 QString
wrapText(const QString
&text
, int initialIndentation
= USAGE_TEXT_COLUMN
, int wrapAtColumn
= WRAP_AT_COLUMN
)
503 QStringList words
= text
.split(' ');
504 QStringList lines
= {words
.first()};
505 int currentLineMaxLength
= wrapAtColumn
- initialIndentation
;
507 for (const QString
&word
: asConst(words
.mid(1)))
509 if (lines
.last().length() + word
.length() + 1 < currentLineMaxLength
)
511 lines
.last().append(' ' + word
);
515 lines
.append(QString(initialIndentation
, ' ') + word
);
516 currentLineMaxLength
= wrapAtColumn
;
520 return lines
.join('\n');
523 QString
makeUsage(const QString
&prgName
)
526 QTextStream
stream(&text
, QIODevice::WriteOnly
);
527 QString indentation
= QString(USAGE_INDENTATION
, ' ');
529 stream
<< QObject::tr("Usage:") << '\n';
530 stream
<< indentation
<< prgName
<< QLatin1String(" [options] [(<filename> | <url>)...]") << '\n';
532 stream
<< QObject::tr("Options:") << '\n';
533 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
534 stream
<< SHOW_VERSION_OPTION
.usage() << wrapText(QObject::tr("Display program version and exit")) << '\n';
536 stream
<< SHOW_HELP_OPTION
.usage() << wrapText(QObject::tr("Display this help message and exit")) << '\n';
537 stream
<< WEBUI_PORT_OPTION
.usage(QObject::tr("port"))
538 << wrapText(QObject::tr("Change the Web UI port"))
541 stream
<< NO_SPLASH_OPTION
.usage() << wrapText(QObject::tr("Disable splash screen")) << '\n';
542 #elif !defined(Q_OS_WIN)
543 stream
<< DAEMON_OPTION
.usage() << wrapText(QObject::tr("Run in daemon-mode (background)")) << '\n';
545 //: Use appropriate short form or abbreviation of "directory"
546 stream
<< PROFILE_OPTION
.usage(QObject::tr("dir"))
547 << wrapText(QObject::tr("Store configuration files in <dir>")) << '\n';
548 stream
<< CONFIGURATION_OPTION
.usage(QObject::tr("name"))
549 << wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) << '\n';
550 stream
<< RELATIVE_FASTRESUME
.usage()
551 << wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative "
552 "to the profile directory")) << '\n';
553 stream
<< Option::padUsageText(QObject::tr("files or URLs"))
554 << wrapText(QObject::tr("Download the torrents passed by the user")) << '\n'
557 stream
<< wrapText(QObject::tr("Options when adding new torrents:"), 0) << '\n';
558 stream
<< SAVE_PATH_OPTION
.usage(QObject::tr("path")) << wrapText(QObject::tr("Torrent save path")) << '\n';
559 stream
<< PAUSED_OPTION
.usage() << wrapText(QObject::tr("Add torrents as started or paused")) << '\n';
560 stream
<< SKIP_HASH_CHECK_OPTION
.usage() << wrapText(QObject::tr("Skip hash check")) << '\n';
561 stream
<< CATEGORY_OPTION
.usage(QObject::tr("name"))
562 << wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
563 "created.")) << '\n';
564 stream
<< SEQUENTIAL_OPTION
.usage() << wrapText(QObject::tr("Download files in sequential order")) << '\n';
565 stream
<< FIRST_AND_LAST_OPTION
.usage()
566 << wrapText(QObject::tr("Download first and last pieces first")) << '\n';
567 stream
<< SKIP_DIALOG_OPTION
.usage()
568 << wrapText(QObject::tr("Specify whether the \"Add New Torrent\" dialog opens when adding a "
569 "torrent.")) << '\n';
572 stream
<< wrapText(QObject::tr("Option values may be supplied via environment variables. For option named "
573 "'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
574 "case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
575 "'TRUE'. For example, to disable the splash screen: "), 0) << "\n"
576 << QLatin1String("QBT_NO_SPLASH=1 ") << prgName
<< '\n'
577 << wrapText(QObject::tr("Command line parameters take precedence over environment variables"), 0) << '\n';
582 void displayUsage(const QString
&prgName
)
584 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
585 QMessageBox
msgBox(QMessageBox::Information
, QObject::tr("Help"), makeUsage(prgName
), QMessageBox::Ok
);
586 msgBox
.show(); // Need to be shown or to moveToCenter does not work
587 msgBox
.move(Utils::Gui::screenCenter(&msgBox
));
590 printf("%s\n", qUtf8Printable(makeUsage(prgName
)));