Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / app / cmdoptions.cpp
blobcebfcbbf717b9e0d70cbc5ac346f919fed539355
1 /*
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"
33 #include <cstdio>
35 #include <QDebug>
36 #include <QFileInfo>
37 #include <QProcessEnvironment>
39 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
40 #include <QMessageBox>
41 #endif
43 #include "base/global.h"
44 #include "base/utils/misc.h"
45 #include "base/utils/string.h"
47 #ifndef DISABLE_GUI
48 #include "gui/utils.h"
49 #endif
51 namespace
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.
58 class Option
60 protected:
61 explicit constexpr Option(const char *name, char shortcut = 0)
62 : m_name {name}
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
84 return u"QBT_"
85 + QString::fromLatin1(m_name).toUpper().replace(u'-', u'_');
88 public:
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' ');
96 return res;
99 private:
100 const char *m_name = nullptr;
101 const char m_shortcut;
104 // Boolean option.
105 class BoolOption : protected Option
107 public:
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
122 QString res;
123 if (hasShortcut())
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
144 public:
145 explicit constexpr StringOption(const char *name)
146 : Option {name, 0}
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());
176 private:
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
191 public:
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);
202 bool ok = false;
203 int res = val.toInt(&ok);
204 if (!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));
208 return res;
211 int value(const QProcessEnvironment &env, int defaultValue) const
213 QString val = env.value(envVarName());
214 if (val.isEmpty()) return defaultValue;
216 bool ok;
217 int res = val.toInt(&ok);
218 if (!ok)
220 qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
221 .arg(envVarName(), val);
222 return defaultValue;
224 return res;
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
242 public:
243 constexpr TriStateBoolOption(const char *name, bool defaultValue)
244 : Option {name, 0}
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"))
268 return true;
270 if ((val.toUpper() == u"FALSE") || (val == u"0"))
272 return false;
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);
286 if (val.isEmpty())
288 return m_defaultValue;
290 if (val == u"-1")
292 return std::nullopt;
294 if ((val.toUpper() == u"TRUE") || (val == u"1"))
296 return true;
298 if ((val.toUpper() == u"FALSE") || (val == u"0"))
300 return false;
303 qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
304 .arg(u"true|false"_qs, envVarName(), val);
305 return std::nullopt;
308 friend bool operator==(const TriStateBoolOption &option, const QString &arg)
310 return arg.section(u'=', 0, 0) == option.fullParameter();
313 bool m_defaultValue;
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'};
325 #else
326 constexpr const BoolOption NO_SPLASH_OPTION {"no-splash"};
327 #endif
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))
344 #ifndef DISABLE_GUI
345 , noSplash(NO_SPLASH_OPTION.value(env))
346 #elif !defined(Q_OS_WIN)
347 , shouldDaemonize(DAEMON_OPTION.value(env))
348 #endif
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;
384 #endif
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));
401 #ifndef DISABLE_GUI
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;
411 #endif
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);
452 else
454 // Unknown argument
455 result.unknownParameter = arg;
456 break;
459 else
461 QFileInfo torrentPath;
462 torrentPath.setFile(arg);
464 if (torrentPath.exists())
465 result.torrentSources += torrentPath.absoluteFilePath();
466 else
467 result.torrentSources += arg;
471 return result;
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);
486 else
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'
506 #endif
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"))
510 + u'\n'
511 + TORRENTING_PORT_OPTION.usage(QObject::tr("port"))
512 + wrapText(QObject::tr("Change the torrenting port"))
513 + u'\n'
514 #ifndef DISABLE_GUI
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'
518 #endif
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'
529 + 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 "
537 "created.")) + u'\n'
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 "
543 "torrent.")) + u'\n'
544 + u'\n'
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';
553 return text;
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));
562 msgBox.exec();
563 #else
564 printf("%s\n", qUtf8Printable(makeUsage(prgName)));
565 #endif