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>
39 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
40 #include <QMessageBox>
43 #include "base/global.h"
44 #include "base/utils/misc.h"
45 #include "base/utils/string.h"
48 #include "gui/utils.h"
53 const int USAGE_INDENTATION
= 4;
54 const int USAGE_TEXT_COLUMN
= 31;
55 const int WRAP_AT_COLUMN
= 80;
57 // Base option class. Encapsulates name operations.
61 explicit constexpr Option(const char *name
, char shortcut
= 0)
63 , m_shortcut
{shortcut
}
67 QString
fullParameter() const
69 return u
"--" + QString::fromLatin1(m_name
);
72 QString
shortcutParameter() const
74 return u
"-" + QChar::fromLatin1(m_shortcut
);
77 bool hasShortcut() const
79 return m_shortcut
!= 0;
82 QString
envVarName() const
85 + QString::fromLatin1(m_name
).toUpper().replace(u
'-', u
'_');
89 static QString
padUsageText(const QString
&usage
)
91 QString res
= QString(USAGE_INDENTATION
, u
' ') + usage
;
93 if ((USAGE_TEXT_COLUMN
- usage
.length() - 4) > 0)
94 return res
+ QString((USAGE_TEXT_COLUMN
- usage
.length() - 4), u
' ');
100 const char *m_name
= nullptr;
101 const char m_shortcut
;
105 class BoolOption
: protected Option
108 explicit constexpr BoolOption(const char *name
, char shortcut
= 0)
109 : Option
{name
, shortcut
}
113 bool value(const QProcessEnvironment
&env
) const
115 QString val
= env
.value(envVarName());
116 // we accept "1" and "true" (upper or lower cased) as boolean 'true' values
117 return ((val
== u
"1") || (val
.toUpper() == u
"TRUE"));
120 QString
usage() const
124 res
+= shortcutParameter() + u
" | ";
125 res
+= fullParameter();
126 return padUsageText(res
);
129 friend bool operator==(const BoolOption
&option
, const QString
&arg
)
131 return (option
.hasShortcut() && ((arg
.size() == 2) && (option
.shortcutParameter() == arg
)))
132 || (option
.fullParameter() == arg
);
136 bool operator==(const QString
&arg
, const BoolOption
&option
)
138 return (option
== arg
);
141 // Option with string value. May not have a shortcut
142 struct StringOption
: protected Option
145 explicit constexpr StringOption(const char *name
)
150 QString
value(const QString
&arg
) const
152 QStringList parts
= arg
.split(u
'=');
153 if (parts
.size() == 2)
154 return Utils::String::unquote(parts
[1], u
"'\""_qs
);
155 throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
156 "e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
157 .arg(fullParameter(), u
"<value>"_qs
));
160 QString
value(const QProcessEnvironment
&env
, const QString
&defaultValue
= {}) const
162 QString val
= env
.value(envVarName());
163 return val
.isEmpty() ? defaultValue
: Utils::String::unquote(val
, u
"'\""_qs
);
166 QString
usage(const QString
&valueName
) const
168 return padUsageText(parameterAssignment() + u
'<' + valueName
+ u
'>');
171 friend bool operator==(const StringOption
&option
, const QString
&arg
)
173 return arg
.startsWith(option
.parameterAssignment());
177 QString
parameterAssignment() const
179 return fullParameter() + u
'=';
183 bool operator==(const QString
&arg
, const StringOption
&option
)
185 return (option
== arg
);
188 // Option with integer value. May not have a shortcut
189 class IntOption
: protected StringOption
192 explicit constexpr IntOption(const char *name
)
193 : StringOption
{name
}
197 using StringOption::usage
;
199 int value(const QString
&arg
) const
201 QString val
= StringOption::value(arg
);
203 int res
= val
.toInt(&ok
);
205 throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
206 "e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
207 .arg(fullParameter(), u
"<integer value>"_qs
));
211 int value(const QProcessEnvironment
&env
, int defaultValue
) const
213 QString val
= env
.value(envVarName());
214 if (val
.isEmpty()) return defaultValue
;
217 int res
= val
.toInt(&ok
);
220 qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
221 .arg(envVarName(), val
);
227 friend bool operator==(const IntOption
&option
, const QString
&arg
)
229 return (static_cast<StringOption
>(option
) == arg
);
233 bool operator==(const QString
&arg
, const IntOption
&option
)
235 return (option
== arg
);
238 // Option that is explicitly set to true or false, and whose value is undefined when unspecified.
239 // May not have a shortcut.
240 class TriStateBoolOption
: protected Option
243 constexpr TriStateBoolOption(const char *name
, bool defaultValue
)
245 , m_defaultValue(defaultValue
)
249 QString
usage() const
251 return padUsageText(fullParameter() + u
"=<true|false>");
254 std::optional
<bool> value(const QString
&arg
) const
256 QStringList parts
= arg
.split(u
'=');
258 if (parts
.size() == 1)
260 return m_defaultValue
;
262 if (parts
.size() == 2)
264 QString val
= parts
[1];
266 if ((val
.toUpper() == u
"TRUE") || (val
== u
"1"))
270 if ((val
.toUpper() == u
"FALSE") || (val
== u
"0"))
276 throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
277 "e.g. Parameter '--add-paused' must follow syntax "
278 "'--add-paused=<true|false>'")
279 .arg(fullParameter(), u
"<true|false>"_qs
));
282 std::optional
<bool> value(const QProcessEnvironment
&env
) const
284 const QString val
= env
.value(envVarName(), u
"-1"_qs
);
288 return m_defaultValue
;
294 if ((val
.toUpper() == u
"TRUE") || (val
== u
"1"))
298 if ((val
.toUpper() == u
"FALSE") || (val
== u
"0"))
303 qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
304 .arg(u
"true|false"_qs
, envVarName(), val
);
308 friend bool operator==(const TriStateBoolOption
&option
, const QString
&arg
)
310 return arg
.section(u
'=', 0, 0) == option
.fullParameter();
316 bool operator==(const QString
&arg
, const TriStateBoolOption
&option
)
318 return (option
== arg
);
321 constexpr const BoolOption SHOW_HELP_OPTION
{"help", 'h'};
322 constexpr const BoolOption SHOW_VERSION_OPTION
{"version", 'v'};
323 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
324 constexpr const BoolOption DAEMON_OPTION
{"daemon", 'd'};
326 constexpr const BoolOption NO_SPLASH_OPTION
{"no-splash"};
328 constexpr const IntOption WEBUI_PORT_OPTION
{"webui-port"};
329 constexpr const IntOption TORRENTING_PORT_OPTION
{"torrenting-port"};
330 constexpr const StringOption PROFILE_OPTION
{"profile"};
331 constexpr const StringOption CONFIGURATION_OPTION
{"configuration"};
332 constexpr const BoolOption RELATIVE_FASTRESUME
{"relative-fastresume"};
333 constexpr const StringOption SAVE_PATH_OPTION
{"save-path"};
334 constexpr const TriStateBoolOption PAUSED_OPTION
{"add-paused", true};
335 constexpr const BoolOption SKIP_HASH_CHECK_OPTION
{"skip-hash-check"};
336 constexpr const StringOption CATEGORY_OPTION
{"category"};
337 constexpr const BoolOption SEQUENTIAL_OPTION
{"sequential"};
338 constexpr const BoolOption FIRST_AND_LAST_OPTION
{"first-and-last"};
339 constexpr const TriStateBoolOption SKIP_DIALOG_OPTION
{"skip-dialog", true};
342 QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment
&env
)
343 : relativeFastresumePaths(RELATIVE_FASTRESUME
.value(env
))
345 , noSplash(NO_SPLASH_OPTION
.value(env
))
346 #elif !defined(Q_OS_WIN)
347 , shouldDaemonize(DAEMON_OPTION
.value(env
))
349 , webUiPort(WEBUI_PORT_OPTION
.value(env
, -1))
350 , torrentingPort(TORRENTING_PORT_OPTION
.value(env
, -1))
351 , skipDialog(SKIP_DIALOG_OPTION
.value(env
))
352 , profileDir(PROFILE_OPTION
.value(env
))
353 , configurationName(CONFIGURATION_OPTION
.value(env
))
355 addTorrentParams
.savePath
= Path(SAVE_PATH_OPTION
.value(env
));
356 addTorrentParams
.category
= CATEGORY_OPTION
.value(env
);
357 addTorrentParams
.skipChecking
= SKIP_HASH_CHECK_OPTION
.value(env
);
358 addTorrentParams
.sequential
= SEQUENTIAL_OPTION
.value(env
);
359 addTorrentParams
.firstLastPiecePriority
= FIRST_AND_LAST_OPTION
.value(env
);
360 addTorrentParams
.addPaused
= PAUSED_OPTION
.value(env
);
363 QBtCommandLineParameters
parseCommandLine(const QStringList
&args
)
365 QBtCommandLineParameters result
{QProcessEnvironment::systemEnvironment()};
367 for (int i
= 1; i
< args
.count(); ++i
)
369 const QString
&arg
= args
[i
];
371 if ((arg
.startsWith(u
"--") && !arg
.endsWith(u
".torrent"))
372 || (arg
.startsWith(u
'-') && (arg
.size() == 2)))
374 // Parse known parameters
375 if (arg
== SHOW_HELP_OPTION
)
377 result
.showHelp
= true;
379 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
380 else if (arg
== SHOW_VERSION_OPTION
)
382 result
.showVersion
= true;
385 else if (arg
== WEBUI_PORT_OPTION
)
387 result
.webUiPort
= WEBUI_PORT_OPTION
.value(arg
);
388 if ((result
.webUiPort
< 1) || (result
.webUiPort
> 65535))
389 throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
390 .arg(u
"--webui-port"_qs
));
392 else if (arg
== TORRENTING_PORT_OPTION
)
394 result
.torrentingPort
= TORRENTING_PORT_OPTION
.value(arg
);
395 if ((result
.torrentingPort
< 1) || (result
.torrentingPort
> 65535))
397 throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
398 .arg(u
"--torrenting-port"_qs
));
402 else if (arg
== NO_SPLASH_OPTION
)
404 result
.noSplash
= true;
406 #elif !defined(Q_OS_WIN)
407 else if (arg
== DAEMON_OPTION
)
409 result
.shouldDaemonize
= true;
412 else if (arg
== PROFILE_OPTION
)
414 result
.profileDir
= Path(PROFILE_OPTION
.value(arg
));
416 else if (arg
== RELATIVE_FASTRESUME
)
418 result
.relativeFastresumePaths
= true;
420 else if (arg
== CONFIGURATION_OPTION
)
422 result
.configurationName
= CONFIGURATION_OPTION
.value(arg
);
424 else if (arg
== SAVE_PATH_OPTION
)
426 result
.addTorrentParams
.savePath
= Path(SAVE_PATH_OPTION
.value(arg
));
428 else if (arg
== PAUSED_OPTION
)
430 result
.addTorrentParams
.addPaused
= PAUSED_OPTION
.value(arg
);
432 else if (arg
== SKIP_HASH_CHECK_OPTION
)
434 result
.addTorrentParams
.skipChecking
= true;
436 else if (arg
== CATEGORY_OPTION
)
438 result
.addTorrentParams
.category
= CATEGORY_OPTION
.value(arg
);
440 else if (arg
== SEQUENTIAL_OPTION
)
442 result
.addTorrentParams
.sequential
= true;
444 else if (arg
== FIRST_AND_LAST_OPTION
)
446 result
.addTorrentParams
.firstLastPiecePriority
= true;
448 else if (arg
== SKIP_DIALOG_OPTION
)
450 result
.skipDialog
= SKIP_DIALOG_OPTION
.value(arg
);
455 result
.unknownParameter
= arg
;
461 QFileInfo torrentPath
;
462 torrentPath
.setFile(arg
);
464 if (torrentPath
.exists())
465 result
.torrentSources
+= torrentPath
.absoluteFilePath();
467 result
.torrentSources
+= arg
;
474 QString
wrapText(const QString
&text
, int initialIndentation
= USAGE_TEXT_COLUMN
, int wrapAtColumn
= WRAP_AT_COLUMN
)
476 QStringList words
= text
.split(u
' ');
477 QStringList lines
= {words
.first()};
478 int currentLineMaxLength
= wrapAtColumn
- initialIndentation
;
480 for (const QString
&word
: asConst(words
.mid(1)))
482 if (lines
.last().length() + word
.length() + 1 < currentLineMaxLength
)
484 lines
.last().append(u
' ' + word
);
488 lines
.append(QString(initialIndentation
, u
' ') + word
);
489 currentLineMaxLength
= wrapAtColumn
;
493 return lines
.join(u
'\n');
496 QString
makeUsage(const QString
&prgName
)
498 const QString indentation
{USAGE_INDENTATION
, u
' '};
500 const QString text
= QObject::tr("Usage:") + u
'\n'
501 + indentation
+ prgName
+ u
' ' + QObject::tr("[options] [(<filename> | <url>)...]") + u
'\n'
503 + QObject::tr("Options:") + u
'\n'
504 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
505 + SHOW_VERSION_OPTION
.usage() + wrapText(QObject::tr("Display program version and exit")) + u
'\n'
507 + SHOW_HELP_OPTION
.usage() + wrapText(QObject::tr("Display this help message and exit")) + u
'\n'
508 + WEBUI_PORT_OPTION
.usage(QObject::tr("port"))
509 + wrapText(QObject::tr("Change the Web UI port"))
511 + TORRENTING_PORT_OPTION
.usage(QObject::tr("port"))
512 + wrapText(QObject::tr("Change the torrenting port"))
515 + NO_SPLASH_OPTION
.usage() + wrapText(QObject::tr("Disable splash screen")) + u
'\n'
516 #elif !defined(Q_OS_WIN)
517 + DAEMON_OPTION
.usage() + wrapText(QObject::tr("Run in daemon-mode (background)")) + u
'\n'
519 //: Use appropriate short form or abbreviation of "directory"
520 + PROFILE_OPTION
.usage(QObject::tr("dir"))
521 + wrapText(QObject::tr("Store configuration files in <dir>")) + u
'\n'
522 + CONFIGURATION_OPTION
.usage(QObject::tr("name"))
523 + wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) + u
'\n'
524 + RELATIVE_FASTRESUME
.usage()
525 + wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative "
526 "to the profile directory")) + u
'\n'
527 + Option::padUsageText(QObject::tr("files or URLs"))
528 + wrapText(QObject::tr("Download the torrents passed by the user")) + u
'\n'
531 + wrapText(QObject::tr("Options when adding new torrents:"), 0) + u
'\n'
532 + SAVE_PATH_OPTION
.usage(QObject::tr("path")) + wrapText(QObject::tr("Torrent save path")) + u
'\n'
533 + PAUSED_OPTION
.usage() + wrapText(QObject::tr("Add torrents as started or paused")) + u
'\n'
534 + SKIP_HASH_CHECK_OPTION
.usage() + wrapText(QObject::tr("Skip hash check")) + u
'\n'
535 + CATEGORY_OPTION
.usage(QObject::tr("name"))
536 + wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
538 + SEQUENTIAL_OPTION
.usage() + wrapText(QObject::tr("Download files in sequential order")) + u
'\n'
539 + FIRST_AND_LAST_OPTION
.usage()
540 + wrapText(QObject::tr("Download first and last pieces first")) + u
'\n'
541 + SKIP_DIALOG_OPTION
.usage()
542 + wrapText(QObject::tr("Specify whether the \"Add New Torrent\" dialog opens when adding a "
546 + wrapText(QObject::tr("Option values may be supplied via environment variables. For option named "
547 "'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
548 "case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
549 "'TRUE'. For example, to disable the splash screen: "), 0) + u
'\n'
550 + u
"QBT_NO_SPLASH=1 " + prgName
+ u
'\n'
551 + wrapText(QObject::tr("Command line parameters take precedence over environment variables"), 0) + u
'\n';
556 void displayUsage(const QString
&prgName
)
558 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
559 QMessageBox
msgBox(QMessageBox::Information
, QObject::tr("Help"), makeUsage(prgName
), QMessageBox::Ok
);
560 msgBox
.show(); // Need to be shown or to moveToCenter does not work
561 msgBox
.move(Utils::Gui::screenCenter(&msgBox
));
564 printf("%s\n", qUtf8Printable(makeUsage(prgName
)));