Specify Unicode for resource block (#15370)
[qBittorrent.git] / src / app / cmdoptions.cpp
blob15cb4a85a01352f8319eb9603e86dcda2fcfdc34
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>
38 #include <QTextStream>
40 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
41 #include <QMessageBox>
42 #endif
44 #include "base/global.h"
45 #include "base/utils/misc.h"
46 #include "base/utils/string.h"
48 #ifndef DISABLE_GUI
49 #include "gui/utils.h"
50 #endif
52 namespace
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.
59 class Option
61 protected:
62 explicit constexpr Option(const char *name, char shortcut = 0)
63 : m_name {name}
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('_'));
89 public:
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, ' ');
97 return res;
100 private:
101 const char *m_name;
102 const char m_shortcut;
105 // Boolean option.
106 class BoolOption : protected Option
108 public:
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
129 QString res;
130 if (hasShortcut())
131 res += shortcutParameter() + QLatin1String(" | ");
132 res += fullParameter();
133 return padUsageText(res);
137 bool operator==(const QString &s, const BoolOption &o)
139 return o == s;
142 // Option with string value. May not have a shortcut
143 struct StringOption : protected Option
145 public:
146 explicit constexpr StringOption(const char *name)
147 : Option {name, 0}
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('>'));
177 private:
178 QString parameterAssignment() const
180 return fullParameter() + QLatin1Char('=');
184 bool operator==(const QString &s, const StringOption &o)
186 return o == s;
189 // Option with integer value. May not have a shortcut
190 class IntOption : protected StringOption
192 public:
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);
204 bool ok = false;
205 int res = val.toInt(&ok);
206 if (!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>")));
210 return res;
213 int value(const QProcessEnvironment &env, int defaultValue) const
215 QString val = env.value(envVarName());
216 if (val.isEmpty()) return defaultValue;
218 bool ok;
219 int res = val.toInt(&ok);
220 if (!ok)
222 qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
223 .arg(envVarName(), val);
224 return defaultValue;
226 return res;
230 bool operator==(const QString &s, const IntOption &o)
232 return o == s;
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
239 public:
240 constexpr TriStateBoolOption(const char *name, bool defaultValue)
241 : Option {name, 0}
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")))
271 return true;
273 if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0")))
275 return false;
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");
289 if (val.isEmpty())
291 return m_defaultValue;
293 if (val == QLatin1String("-1"))
295 return std::nullopt;
297 if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1")))
299 return true;
301 if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0")))
303 return false;
306 qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
307 .arg(QLatin1String("true|false"), envVarName(), val);
308 return std::nullopt;
311 bool m_defaultValue;
314 bool operator==(const QString &s, const TriStateBoolOption &o)
316 return o == s;
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'};
323 #else
324 constexpr const BoolOption NO_SPLASH_OPTION {"no-splash"};
325 #endif
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)
340 : showHelp(false)
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)
346 , showVersion(false)
347 #endif
348 #ifndef DISABLE_GUI
349 , noSplash(NO_SPLASH_OPTION.value(env))
350 #elif !defined(Q_OS_WIN)
351 , shouldDaemonize(DAEMON_OPTION.value(env))
352 #endif
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
365 QStringList result;
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"});
380 if (skipChecking)
381 result.append(QLatin1String("@skipChecking"));
383 if (!category.isEmpty())
384 result.append(QLatin1String("@category=") + category);
386 if (sequential)
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"});
395 result += torrents;
396 return result;
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;
420 #endif
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")));
428 #ifndef DISABLE_GUI
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;
438 #endif
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);
479 else
481 // Unknown argument
482 result.unknownParameter = arg;
483 break;
486 else
488 QFileInfo torrentPath;
489 torrentPath.setFile(arg);
491 if (torrentPath.exists())
492 result.torrents += torrentPath.absoluteFilePath();
493 else
494 result.torrents += arg;
498 return result;
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);
513 else
515 lines.append(QString(initialIndentation, ' ') + word);
516 currentLineMaxLength = wrapAtColumn;
520 return lines.join('\n');
523 QString makeUsage(const QString &prgName)
525 QString text;
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';
535 #endif
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"))
539 << '\n';
540 #ifndef DISABLE_GUI
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';
544 #endif
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'
555 << '\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';
570 stream << '\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';
579 return text;
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));
588 msgBox.exec();
589 #else
590 printf("%s\n", qUtf8Printable(makeUsage(prgName)));
591 #endif