WebUI: Show 'Rename...' context menu item only when one torrent is selected
[qBittorrent.git] / src / app / cmdoptions.cpp
blob12492359d1ec7accbfc63cf1352c6901f687b9cc
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 <QCoreApplication>
36 #include <QDebug>
37 #include <QFileInfo>
38 #include <QProcessEnvironment>
40 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
41 #include <QMessageBox>
42 #endif
44 #include "base/global.h"
45 #include "base/utils/fs.h"
46 #include "base/utils/misc.h"
47 #include "base/utils/string.h"
49 #ifndef DISABLE_GUI
50 #include "gui/utils.h"
51 #endif
53 namespace
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.
60 class Option
62 protected:
63 explicit constexpr Option(const char *name, char shortcut = 0)
64 : m_name {name}
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
86 return u"QBT_"
87 + QString::fromLatin1(m_name).toUpper().replace(u'-', u'_');
90 public:
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' ');
98 return res;
101 private:
102 const char *m_name = nullptr;
103 const char m_shortcut;
106 // Boolean option.
107 class BoolOption : protected Option
109 public:
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
124 QString res;
125 if (hasShortcut())
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
141 public:
142 explicit constexpr StringOption(const char *name)
143 : Option {name, 0}
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());
173 private:
174 QString parameterAssignment() const
176 return fullParameter() + u'=';
180 // Option with integer value. May not have a shortcut
181 class IntOption : protected StringOption
183 public:
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);
194 bool ok = false;
195 const int res = val.toInt(&ok);
196 if (!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));
202 return res;
205 int value(const QProcessEnvironment &env, int defaultValue) const
207 QString val = env.value(envVarName());
208 if (val.isEmpty()) return defaultValue;
210 bool ok;
211 int res = val.toInt(&ok);
212 if (!ok)
214 qDebug() << QCoreApplication::translate("CMD Options", "Expected integer number in environment variable '%1', but got '%2'")
215 .arg(envVarName(), val);
216 return defaultValue;
218 return res;
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
231 public:
232 constexpr TriStateBoolOption(const char *name, bool defaultValue)
233 : Option {name, 0}
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"))
257 return true;
259 if ((val.toUpper() == u"FALSE") || (val == u"0"))
261 return false;
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);
275 if (val.isEmpty())
277 return m_defaultValue;
279 if (val == u"-1")
281 return std::nullopt;
283 if ((val.toUpper() == u"TRUE") || (val == u"1"))
285 return true;
287 if ((val.toUpper() == u"FALSE") || (val == u"0"))
289 return false;
292 qDebug() << QCoreApplication::translate("CMD Options", "Expected %1 in environment variable '%2', but got '%3'")
293 .arg(u"true|false"_s, envVarName(), val);
294 return std::nullopt;
297 friend bool operator==(const TriStateBoolOption &option, const QString &arg)
299 return arg.section(u'=', 0, 0) == option.fullParameter();
302 bool m_defaultValue;
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'};
308 #endif
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'};
312 #else
313 constexpr const BoolOption NO_SPLASH_OPTION {"no-splash"};
314 #endif
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))
332 #ifndef DISABLE_GUI
333 , noSplash(NO_SPLASH_OPTION.value(env))
334 #elif !defined(Q_OS_WIN)
335 , shouldDaemonize(DAEMON_OPTION.value(env))
336 #endif
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;
372 #endif
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));
393 #ifndef DISABLE_GUI
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;
403 #endif
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);
444 else
446 // Unknown argument
447 result.unknownParameter = arg;
448 break;
451 else
453 QFileInfo torrentPath;
454 torrentPath.setFile(arg);
456 if (torrentPath.exists())
457 result.torrentSources += torrentPath.absoluteFilePath();
458 else
459 result.torrentSources += arg;
463 return result;
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);
478 else
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'
499 #endif
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"))
503 + u'\n'
504 + TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
505 + wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
506 + u'\n'
507 #ifndef DISABLE_GUI
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'
511 #endif
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'
522 + 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 "
530 "created.")) + u'\n'
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 "
536 "torrent.")) + u'\n'
537 + u'\n'
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';
546 return text;
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));
555 msgBox.exec();
556 #else
557 printf("%s\n", qUtf8Printable(makeUsage(prgName)));
558 #endif