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"
35 #include <QCoreApplication>
38 #include <QProcessEnvironment>
40 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
41 #include <QMessageBox>
44 #include "base/global.h"
45 #include "base/utils/fs.h"
46 #include "base/utils/misc.h"
47 #include "base/utils/string.h"
50 #include "gui/utils.h"
55 const int USAGE_INDENTATION
= 4;
56 const int USAGE_TEXT_COLUMN
= 31;
57 const int WRAP_AT_COLUMN
= 80;
59 // Base option class. Encapsulates name operations.
63 explicit constexpr Option(const char *name
, char shortcut
= 0)
65 , m_shortcut
{shortcut
}
69 QString
fullParameter() const
71 return u
"--" + QString::fromLatin1(m_name
);
74 QString
shortcutParameter() const
76 return u
"-" + QChar::fromLatin1(m_shortcut
);
79 bool hasShortcut() const
81 return m_shortcut
!= 0;
84 QString
envVarName() const
87 + QString::fromLatin1(m_name
).toUpper().replace(u
'-', u
'_');
91 static QString
padUsageText(const QString
&usage
)
93 QString res
= QString(USAGE_INDENTATION
, u
' ') + usage
;
95 if ((USAGE_TEXT_COLUMN
- usage
.length() - 4) > 0)
96 return res
+ QString((USAGE_TEXT_COLUMN
- usage
.length() - 4), u
' ');
102 const char *m_name
= nullptr;
103 const char m_shortcut
;
107 class BoolOption
: protected Option
110 explicit constexpr BoolOption(const char *name
, char shortcut
= 0)
111 : Option
{name
, shortcut
}
115 bool value(const QProcessEnvironment
&env
) const
117 QString val
= env
.value(envVarName());
118 // we accept "1" and "true" (upper or lower cased) as boolean 'true' values
119 return ((val
== u
"1") || (val
.toUpper() == u
"TRUE"));
122 QString
usage() const
126 res
+= shortcutParameter() + u
" | ";
127 res
+= fullParameter();
128 return padUsageText(res
);
131 friend bool operator==(const BoolOption
&option
, const QString
&arg
)
133 return (option
.hasShortcut() && ((arg
.size() == 2) && (option
.shortcutParameter() == arg
)))
134 || (option
.fullParameter() == arg
);
138 // Option with string value. May not have a shortcut
139 struct StringOption
: protected Option
142 explicit constexpr StringOption(const char *name
)
147 QString
value(const QString
&arg
) const
149 QStringList parts
= arg
.split(u
'=');
150 if (parts
.size() == 2)
151 return Utils::String::unquote(parts
[1], u
"'\""_s
);
152 throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
153 "e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
154 .arg(fullParameter(), u
"<value>"_s
));
157 QString
value(const QProcessEnvironment
&env
, const QString
&defaultValue
= {}) const
159 QString val
= env
.value(envVarName());
160 return val
.isEmpty() ? defaultValue
: Utils::String::unquote(val
, u
"'\""_s
);
163 QString
usage(const QString
&valueName
) const
165 return padUsageText(parameterAssignment() + u
'<' + valueName
+ u
'>');
168 friend bool operator==(const StringOption
&option
, const QString
&arg
)
170 return arg
.startsWith(option
.parameterAssignment());
174 QString
parameterAssignment() const
176 return fullParameter() + u
'=';
180 // Option with integer value. May not have a shortcut
181 class IntOption
: protected StringOption
184 explicit constexpr IntOption(const char *name
)
185 : StringOption
{name
}
189 using StringOption::usage
;
191 int value(const QString
&arg
) const
193 const QString val
= StringOption::value(arg
);
195 const int res
= val
.toInt(&ok
);
198 throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
199 "e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
200 .arg(fullParameter(), u
"<integer value>"_s
));
205 int value(const QProcessEnvironment
&env
, int defaultValue
) const
207 QString val
= env
.value(envVarName());
208 if (val
.isEmpty()) return defaultValue
;
211 int res
= val
.toInt(&ok
);
214 qDebug() << QCoreApplication::translate("CMD Options", "Expected integer number in environment variable '%1', but got '%2'")
215 .arg(envVarName(), val
);
221 friend bool operator==(const IntOption
&option
, const QString
&arg
)
223 return (static_cast<StringOption
>(option
) == arg
);
227 // Option that is explicitly set to true or false, and whose value is undefined when unspecified.
228 // May not have a shortcut.
229 class TriStateBoolOption
: protected Option
232 constexpr TriStateBoolOption(const char *name
, bool defaultValue
)
234 , m_defaultValue(defaultValue
)
238 QString
usage() const
240 return padUsageText(fullParameter() + u
"=<true|false>");
243 std::optional
<bool> value(const QString
&arg
) const
245 QStringList parts
= arg
.split(u
'=');
247 if (parts
.size() == 1)
249 return m_defaultValue
;
251 if (parts
.size() == 2)
253 QString val
= parts
[1];
255 if ((val
.toUpper() == u
"TRUE") || (val
== u
"1"))
259 if ((val
.toUpper() == u
"FALSE") || (val
== u
"0"))
265 throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
266 "e.g. Parameter '--add-stopped' must follow syntax "
267 "'--add-stopped=<true|false>'")
268 .arg(fullParameter(), u
"<true|false>"_s
));
271 std::optional
<bool> value(const QProcessEnvironment
&env
) const
273 const QString val
= env
.value(envVarName(), u
"-1"_s
);
277 return m_defaultValue
;
283 if ((val
.toUpper() == u
"TRUE") || (val
== u
"1"))
287 if ((val
.toUpper() == u
"FALSE") || (val
== u
"0"))
292 qDebug() << QCoreApplication::translate("CMD Options", "Expected %1 in environment variable '%2', but got '%3'")
293 .arg(u
"true|false"_s
, envVarName(), val
);
297 friend bool operator==(const TriStateBoolOption
&option
, const QString
&arg
)
299 return arg
.section(u
'=', 0, 0) == option
.fullParameter();
305 constexpr const BoolOption SHOW_HELP_OPTION
{"help", 'h'};
306 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
307 constexpr const BoolOption SHOW_VERSION_OPTION
{"version", 'v'};
309 constexpr const BoolOption CONFIRM_LEGAL_NOTICE
{"confirm-legal-notice"};
310 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
311 constexpr const BoolOption DAEMON_OPTION
{"daemon", 'd'};
313 constexpr const BoolOption NO_SPLASH_OPTION
{"no-splash"};
315 constexpr const IntOption WEBUI_PORT_OPTION
{"webui-port"};
316 constexpr const IntOption TORRENTING_PORT_OPTION
{"torrenting-port"};
317 constexpr const StringOption PROFILE_OPTION
{"profile"};
318 constexpr const StringOption CONFIGURATION_OPTION
{"configuration"};
319 constexpr const BoolOption RELATIVE_FASTRESUME
{"relative-fastresume"};
320 constexpr const StringOption SAVE_PATH_OPTION
{"save-path"};
321 constexpr const TriStateBoolOption STOPPED_OPTION
{"add-stopped", true};
322 constexpr const BoolOption SKIP_HASH_CHECK_OPTION
{"skip-hash-check"};
323 constexpr const StringOption CATEGORY_OPTION
{"category"};
324 constexpr const BoolOption SEQUENTIAL_OPTION
{"sequential"};
325 constexpr const BoolOption FIRST_AND_LAST_OPTION
{"first-and-last"};
326 constexpr const TriStateBoolOption SKIP_DIALOG_OPTION
{"skip-dialog", true};
329 QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment
&env
)
330 : confirmLegalNotice(CONFIRM_LEGAL_NOTICE
.value(env
))
331 , relativeFastresumePaths(RELATIVE_FASTRESUME
.value(env
))
333 , noSplash(NO_SPLASH_OPTION
.value(env
))
334 #elif !defined(Q_OS_WIN)
335 , shouldDaemonize(DAEMON_OPTION
.value(env
))
337 , webUIPort(WEBUI_PORT_OPTION
.value(env
, -1))
338 , torrentingPort(TORRENTING_PORT_OPTION
.value(env
, -1))
339 , skipDialog(SKIP_DIALOG_OPTION
.value(env
))
340 , profileDir(Utils::Fs::toAbsolutePath(Path(PROFILE_OPTION
.value(env
))))
341 , configurationName(CONFIGURATION_OPTION
.value(env
))
343 addTorrentParams
.savePath
= Path(SAVE_PATH_OPTION
.value(env
));
344 addTorrentParams
.category
= CATEGORY_OPTION
.value(env
);
345 addTorrentParams
.skipChecking
= SKIP_HASH_CHECK_OPTION
.value(env
);
346 addTorrentParams
.sequential
= SEQUENTIAL_OPTION
.value(env
);
347 addTorrentParams
.firstLastPiecePriority
= FIRST_AND_LAST_OPTION
.value(env
);
348 addTorrentParams
.addStopped
= STOPPED_OPTION
.value(env
);
351 QBtCommandLineParameters
parseCommandLine(const QStringList
&args
)
353 QBtCommandLineParameters result
{QProcessEnvironment::systemEnvironment()};
355 for (int i
= 1; i
< args
.count(); ++i
)
357 const QString
&arg
= args
[i
];
359 if ((arg
.startsWith(u
"--") && !arg
.endsWith(u
".torrent"))
360 || (arg
.startsWith(u
'-') && (arg
.size() == 2)))
362 // Parse known parameters
363 if (arg
== SHOW_HELP_OPTION
)
365 result
.showHelp
= true;
367 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
368 else if (arg
== SHOW_VERSION_OPTION
)
370 result
.showVersion
= true;
373 else if (arg
== CONFIRM_LEGAL_NOTICE
)
375 result
.confirmLegalNotice
= true;
377 else if (arg
== WEBUI_PORT_OPTION
)
379 result
.webUIPort
= WEBUI_PORT_OPTION
.value(arg
);
380 if ((result
.webUIPort
< 1) || (result
.webUIPort
> 65535))
381 throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
382 .arg(u
"--webui-port"_s
));
384 else if (arg
== TORRENTING_PORT_OPTION
)
386 result
.torrentingPort
= TORRENTING_PORT_OPTION
.value(arg
);
387 if ((result
.torrentingPort
< 1) || (result
.torrentingPort
> 65535))
389 throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
390 .arg(u
"--torrenting-port"_s
));
394 else if (arg
== NO_SPLASH_OPTION
)
396 result
.noSplash
= true;
398 #elif !defined(Q_OS_WIN)
399 else if (arg
== DAEMON_OPTION
)
401 result
.shouldDaemonize
= true;
404 else if (arg
== PROFILE_OPTION
)
406 result
.profileDir
= Utils::Fs::toAbsolutePath(Path(PROFILE_OPTION
.value(arg
)));
408 else if (arg
== RELATIVE_FASTRESUME
)
410 result
.relativeFastresumePaths
= true;
412 else if (arg
== CONFIGURATION_OPTION
)
414 result
.configurationName
= CONFIGURATION_OPTION
.value(arg
);
416 else if (arg
== SAVE_PATH_OPTION
)
418 result
.addTorrentParams
.savePath
= Path(SAVE_PATH_OPTION
.value(arg
));
420 else if (arg
== STOPPED_OPTION
)
422 result
.addTorrentParams
.addStopped
= STOPPED_OPTION
.value(arg
);
424 else if (arg
== SKIP_HASH_CHECK_OPTION
)
426 result
.addTorrentParams
.skipChecking
= true;
428 else if (arg
== CATEGORY_OPTION
)
430 result
.addTorrentParams
.category
= CATEGORY_OPTION
.value(arg
);
432 else if (arg
== SEQUENTIAL_OPTION
)
434 result
.addTorrentParams
.sequential
= true;
436 else if (arg
== FIRST_AND_LAST_OPTION
)
438 result
.addTorrentParams
.firstLastPiecePriority
= true;
440 else if (arg
== SKIP_DIALOG_OPTION
)
442 result
.skipDialog
= SKIP_DIALOG_OPTION
.value(arg
);
447 result
.unknownParameter
= arg
;
453 QFileInfo torrentPath
;
454 torrentPath
.setFile(arg
);
456 if (torrentPath
.exists())
457 result
.torrentSources
+= torrentPath
.absoluteFilePath();
459 result
.torrentSources
+= arg
;
466 QString
wrapText(const QString
&text
, int initialIndentation
= USAGE_TEXT_COLUMN
, int wrapAtColumn
= WRAP_AT_COLUMN
)
468 QStringList words
= text
.split(u
' ');
469 QStringList lines
= {words
.first()};
470 int currentLineMaxLength
= wrapAtColumn
- initialIndentation
;
472 for (const QString
&word
: asConst(words
.mid(1)))
474 if (lines
.last().length() + word
.length() + 1 < currentLineMaxLength
)
476 lines
.last().append(u
' ' + word
);
480 lines
.append(QString(initialIndentation
, u
' ') + word
);
481 currentLineMaxLength
= wrapAtColumn
;
485 return lines
.join(u
'\n');
488 QString
makeUsage(const QString
&prgName
)
490 const QString indentation
{USAGE_INDENTATION
, u
' '};
492 const QString text
= QCoreApplication::translate("CMD Options", "Usage:") + u
'\n'
493 + indentation
+ prgName
+ u
' ' + QCoreApplication::translate("CMD Options", "[options] [(<filename> | <url>)...]") + u
'\n'
495 + QCoreApplication::translate("CMD Options", "Options:") + u
'\n'
496 + SHOW_HELP_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u
'\n'
497 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
498 + SHOW_VERSION_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display program version and exit")) + u
'\n'
500 + CONFIRM_LEGAL_NOTICE
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Confirm the legal notice")) + u
'\n'
501 + WEBUI_PORT_OPTION
.usage(QCoreApplication::translate("CMD Options", "port"))
502 + wrapText(QCoreApplication::translate("CMD Options", "Change the WebUI port"))
504 + TORRENTING_PORT_OPTION
.usage(QCoreApplication::translate("CMD Options", "port"))
505 + wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
508 + NO_SPLASH_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Disable splash screen")) + u
'\n'
509 #elif !defined(Q_OS_WIN)
510 + DAEMON_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Run in daemon-mode (background)")) + u
'\n'
512 //: Use appropriate short form or abbreviation of "directory"
513 + PROFILE_OPTION
.usage(QCoreApplication::translate("CMD Options", "dir"))
514 + wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in <dir>")) + u
'\n'
515 + CONFIGURATION_OPTION
.usage(QCoreApplication::translate("CMD Options", "name"))
516 + wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in directories qBittorrent_<name>")) + u
'\n'
517 + RELATIVE_FASTRESUME
.usage()
518 + wrapText(QCoreApplication::translate("CMD Options", "Hack into libtorrent fastresume files and make file paths relative "
519 "to the profile directory")) + u
'\n'
520 + Option::padUsageText(QCoreApplication::translate("CMD Options", "files or URLs"))
521 + wrapText(QCoreApplication::translate("CMD Options", "Download the torrents passed by the user")) + u
'\n'
524 + wrapText(QCoreApplication::translate("CMD Options", "Options when adding new torrents:"), 0) + u
'\n'
525 + SAVE_PATH_OPTION
.usage(QCoreApplication::translate("CMD Options", "path")) + wrapText(QCoreApplication::translate("CMD Options", "Torrent save path")) + u
'\n'
526 + STOPPED_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as running or stopped")) + u
'\n'
527 + SKIP_HASH_CHECK_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Skip hash check")) + u
'\n'
528 + CATEGORY_OPTION
.usage(QCoreApplication::translate("CMD Options", "name"))
529 + wrapText(QCoreApplication::translate("CMD Options", "Assign torrents to category. If the category doesn't exist, it will be "
531 + SEQUENTIAL_OPTION
.usage() + wrapText(QCoreApplication::translate("CMD Options", "Download files in sequential order")) + u
'\n'
532 + FIRST_AND_LAST_OPTION
.usage()
533 + wrapText(QCoreApplication::translate("CMD Options", "Download first and last pieces first")) + u
'\n'
534 + SKIP_DIALOG_OPTION
.usage()
535 + wrapText(QCoreApplication::translate("CMD Options", "Specify whether the \"Add New Torrent\" dialog opens when adding a "
539 + wrapText(QCoreApplication::translate("CMD Options", "Option values may be supplied via environment variables. For option named "
540 "'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
541 "case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
542 "'TRUE'. For example, to disable the splash screen: "), 0) + u
'\n'
543 + u
"QBT_NO_SPLASH=1 " + prgName
+ u
'\n'
544 + wrapText(QCoreApplication::translate("CMD Options", "Command line parameters take precedence over environment variables"), 0) + u
'\n';
549 void displayUsage(const QString
&prgName
)
551 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
552 QMessageBox
msgBox(QMessageBox::Information
, QCoreApplication::translate("CMD Options", "Help"), makeUsage(prgName
), QMessageBox::Ok
);
553 msgBox
.show(); // Need to be shown or to moveToCenter does not work
554 msgBox
.move(Utils::Gui::screenCenter(&msgBox
));
557 printf("%s\n", qUtf8Printable(makeUsage(prgName
)));