Show URL seeds for torrents that have no metadata
[qBittorrent.git] / src / app / application.cpp
blob26669d8e9ffb4b099db8c99117cfaf004d19d9e9
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "application.h"
32 #include <algorithm>
34 #ifdef DISABLE_GUI
35 #include <cstdio>
36 #endif
38 #ifdef Q_OS_WIN
39 #include <memory>
40 #include <windows.h>
41 #include <shellapi.h>
42 #elif defined(Q_OS_UNIX)
43 #include <sys/resource.h>
44 #endif
46 #include <QByteArray>
47 #include <QDebug>
48 #include <QLibraryInfo>
49 #include <QMetaObject>
50 #include <QProcess>
52 #ifndef DISABLE_GUI
53 #include <QAbstractButton>
54 #include <QMenu>
55 #include <QMessageBox>
56 #include <QPixmapCache>
57 #include <QProgressDialog>
58 #ifdef Q_OS_WIN
59 #include <QSessionManager>
60 #endif // Q_OS_WIN
61 #ifdef Q_OS_MACOS
62 #include <QFileOpenEvent>
63 #endif // Q_OS_MACOS
64 #endif
66 #include "base/addtorrentmanager.h"
67 #include "base/bittorrent/infohash.h"
68 #include "base/bittorrent/session.h"
69 #include "base/bittorrent/torrent.h"
70 #include "base/exceptions.h"
71 #include "base/global.h"
72 #include "base/logger.h"
73 #include "base/net/downloadmanager.h"
74 #include "base/net/geoipmanager.h"
75 #include "base/net/proxyconfigurationmanager.h"
76 #include "base/net/smtp.h"
77 #include "base/preferences.h"
78 #include "base/profile.h"
79 #include "base/rss/rss_autodownloader.h"
80 #include "base/rss/rss_session.h"
81 #include "base/search/searchpluginmanager.h"
82 #include "base/settingsstorage.h"
83 #include "base/torrentfileswatcher.h"
84 #include "base/utils/fs.h"
85 #include "base/utils/misc.h"
86 #include "base/utils/os.h"
87 #include "base/utils/string.h"
88 #include "base/version.h"
89 #include "applicationinstancemanager.h"
90 #include "filelogger.h"
91 #include "upgrade.h"
93 #ifndef DISABLE_GUI
94 #include "gui/desktopintegration.h"
95 #include "gui/mainwindow.h"
96 #include "gui/shutdownconfirmdialog.h"
97 #include "gui/uithememanager.h"
98 #include "gui/windowstate.h"
99 #endif // DISABLE_GUI
101 #ifndef DISABLE_WEBUI
102 #include "webui/webui.h"
103 #ifdef DISABLE_GUI
104 #include "base/utils/password.h"
105 #endif
106 #endif
108 namespace
110 #define SETTINGS_KEY(name) u"Application/" name
111 #define FILELOGGER_SETTINGS_KEY(name) (SETTINGS_KEY(u"FileLogger/") name)
112 #define NOTIFICATIONS_SETTINGS_KEY(name) (SETTINGS_KEY(u"GUI/Notifications/"_s) name)
114 const QString LOG_FOLDER = u"logs"_s;
115 const QChar PARAMS_SEPARATOR = u'|';
117 const Path DEFAULT_PORTABLE_MODE_PROFILE_DIR {u"profile"_s};
119 const int MIN_FILELOG_SIZE = 1024; // 1KiB
120 const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
121 const int DEFAULT_FILELOG_SIZE = 65 * 1024; // 65KiB
123 #ifndef DISABLE_GUI
124 const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
125 #endif
127 QString serializeParams(const QBtCommandLineParameters &params)
129 QStringList result;
130 // Because we're passing a string list to the currently running
131 // qBittorrent process, we need some way of passing along the options
132 // the user has specified. Here we place special strings that are
133 // almost certainly not going to collide with a file path or URL
134 // specified by the user, and placing them at the beginning of the
135 // string list so that they will be processed before the list of
136 // torrent paths or URLs.
138 const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
140 if (!addTorrentParams.savePath.isEmpty())
141 result.append(u"@savePath=" + addTorrentParams.savePath.data());
143 if (addTorrentParams.addPaused.has_value())
144 result.append(*addTorrentParams.addPaused ? u"@addPaused=1"_s : u"@addPaused=0"_s);
146 if (addTorrentParams.skipChecking)
147 result.append(u"@skipChecking"_s);
149 if (!addTorrentParams.category.isEmpty())
150 result.append(u"@category=" + addTorrentParams.category);
152 if (addTorrentParams.sequential)
153 result.append(u"@sequential"_s);
155 if (addTorrentParams.firstLastPiecePriority)
156 result.append(u"@firstLastPiecePriority"_s);
158 if (params.skipDialog.has_value())
159 result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
161 result += params.torrentSources;
163 return result.join(PARAMS_SEPARATOR);
166 QBtCommandLineParameters parseParams(const QString &str)
168 QBtCommandLineParameters parsedParams;
169 BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
171 for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
173 param = param.trimmed();
175 // Process strings indicating options specified by the user.
177 if (param.startsWith(u"@savePath="))
179 addTorrentParams.savePath = Path(param.mid(10));
180 continue;
183 if (param.startsWith(u"@addPaused="))
185 addTorrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
186 continue;
189 if (param == u"@skipChecking")
191 addTorrentParams.skipChecking = true;
192 continue;
195 if (param.startsWith(u"@category="))
197 addTorrentParams.category = param.mid(10);
198 continue;
201 if (param == u"@sequential")
203 addTorrentParams.sequential = true;
204 continue;
207 if (param == u"@firstLastPiecePriority")
209 addTorrentParams.firstLastPiecePriority = true;
210 continue;
213 if (param.startsWith(u"@skipDialog="))
215 parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
216 continue;
219 parsedParams.torrentSources.append(param);
222 return parsedParams;
226 Application::Application(int &argc, char **argv)
227 : BaseApplication(argc, argv)
228 , m_commandLineArgs(parseCommandLine(Application::arguments()))
229 , m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_s))
230 , m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_s))
231 , m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_s))
232 , m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY(u"MaxSizeBytes"_s))
233 , m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u"Age"_s))
234 , m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u"AgeType"_s))
235 , m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u"Path"_s))
236 , m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u"MemoryWorkingSetLimit"_s))
237 #ifdef Q_OS_WIN
238 , m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_s))
239 #endif
240 #ifndef DISABLE_GUI
241 , m_startUpWindowState(u"GUI/StartUpWindowState"_s)
242 , m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_s))
243 #endif
245 qRegisterMetaType<Log::Msg>("Log::Msg");
246 qRegisterMetaType<Log::Peer>("Log::Peer");
248 setApplicationName(u"qBittorrent"_s);
249 setOrganizationDomain(u"qbittorrent.org"_s);
250 #if !defined(DISABLE_GUI)
251 setDesktopFileName(u"org.qbittorrent.qBittorrent"_s);
252 setQuitOnLastWindowClosed(false);
253 QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE);
254 #endif
256 Logger::initInstance();
258 const auto portableProfilePath = Path(QCoreApplication::applicationDirPath()) / DEFAULT_PORTABLE_MODE_PROFILE_DIR;
259 const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() && portableProfilePath.exists();
261 const Path profileDir = portableModeEnabled
262 ? portableProfilePath
263 : m_commandLineArgs.profileDir;
264 Profile::initInstance(profileDir, m_commandLineArgs.configurationName,
265 (m_commandLineArgs.relativeFastresumePaths || portableModeEnabled));
267 m_instanceManager = new ApplicationInstanceManager(Profile::instance()->location(SpecialFolder::Config), this);
269 SettingsStorage::initInstance();
270 Preferences::initInstance();
272 const bool firstTimeUser = SettingsStorage::instance()->isEmpty();
273 if (firstTimeUser)
275 setCurrentMigrationVersion();
276 handleChangedDefaults(DefaultPreferencesMode::Current);
278 else
280 if (!upgrade())
281 throw RuntimeError(u"Failed migration of old settings"_s); // Not translatable. Translation isn't configured yet.
282 handleChangedDefaults(DefaultPreferencesMode::Legacy);
285 initializeTranslation();
287 connect(this, &QCoreApplication::aboutToQuit, this, &Application::cleanup);
288 connect(m_instanceManager, &ApplicationInstanceManager::messageReceived, this, &Application::processMessage);
289 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
290 connect(this, &QGuiApplication::commitDataRequest, this, &Application::shutdownCleanup, Qt::DirectConnection);
291 #endif
293 LogMsg(tr("qBittorrent %1 started. Process ID: %2", "qBittorrent v3.2.0alpha started")
294 .arg(QStringLiteral(QBT_VERSION), QString::number(QCoreApplication::applicationPid())));
295 if (portableModeEnabled)
297 LogMsg(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir.toString()));
298 if (m_commandLineArgs.relativeFastresumePaths)
299 LogMsg(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg(u"--relative-fastresume"_s), Log::WARNING); // to avoid translating the `--relative-fastresume` string
301 else
303 LogMsg(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config).toString()));
306 if (isFileLoggerEnabled())
307 m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
309 if (m_commandLineArgs.webUIPort > 0) // it will be -1 when user did not set any value
310 Preferences::instance()->setWebUIPort(m_commandLineArgs.webUIPort);
312 if (m_commandLineArgs.torrentingPort > 0) // it will be -1 when user did not set any value
314 SettingValue<int> port {u"BitTorrent/Session/Port"_s};
315 port = m_commandLineArgs.torrentingPort;
319 Application::~Application()
321 // we still need to call cleanup()
322 // in case the App failed to start
323 cleanup();
326 #ifndef DISABLE_GUI
327 DesktopIntegration *Application::desktopIntegration()
329 return m_desktopIntegration;
332 MainWindow *Application::mainWindow()
334 return m_window;
337 WindowState Application::startUpWindowState() const
339 return m_startUpWindowState;
342 void Application::setStartUpWindowState(const WindowState windowState)
344 m_startUpWindowState = windowState;
347 bool Application::isTorrentAddedNotificationsEnabled() const
349 return m_storeNotificationTorrentAdded;
352 void Application::setTorrentAddedNotificationsEnabled(const bool value)
354 m_storeNotificationTorrentAdded = value;
356 #endif
358 const QBtCommandLineParameters &Application::commandLineArgs() const
360 return m_commandLineArgs;
363 int Application::memoryWorkingSetLimit() const
365 return m_storeMemoryWorkingSetLimit.get(512);
368 void Application::setMemoryWorkingSetLimit(const int size)
370 if (size == memoryWorkingSetLimit())
371 return;
373 m_storeMemoryWorkingSetLimit = size;
374 #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
375 applyMemoryWorkingSetLimit();
376 #endif
379 bool Application::isFileLoggerEnabled() const
381 return m_storeFileLoggerEnabled.get(true);
384 void Application::setFileLoggerEnabled(const bool value)
386 if (value && !m_fileLogger)
387 m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
388 else if (!value)
389 delete m_fileLogger;
390 m_storeFileLoggerEnabled = value;
393 Path Application::fileLoggerPath() const
395 return m_storeFileLoggerPath.get(specialFolderLocation(SpecialFolder::Data) / Path(LOG_FOLDER));
398 void Application::setFileLoggerPath(const Path &path)
400 if (m_fileLogger)
401 m_fileLogger->changePath(path);
402 m_storeFileLoggerPath = path;
405 bool Application::isFileLoggerBackup() const
407 return m_storeFileLoggerBackup.get(true);
410 void Application::setFileLoggerBackup(const bool value)
412 if (m_fileLogger)
413 m_fileLogger->setBackup(value);
414 m_storeFileLoggerBackup = value;
417 bool Application::isFileLoggerDeleteOld() const
419 return m_storeFileLoggerDeleteOld.get(true);
422 void Application::setFileLoggerDeleteOld(const bool value)
424 if (value && m_fileLogger)
425 m_fileLogger->deleteOld(fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
426 m_storeFileLoggerDeleteOld = value;
429 int Application::fileLoggerMaxSize() const
431 const int val = m_storeFileLoggerMaxSize.get(DEFAULT_FILELOG_SIZE);
432 return std::clamp(val, MIN_FILELOG_SIZE, MAX_FILELOG_SIZE);
435 void Application::setFileLoggerMaxSize(const int bytes)
437 const int clampedValue = std::clamp(bytes, MIN_FILELOG_SIZE, MAX_FILELOG_SIZE);
438 if (m_fileLogger)
439 m_fileLogger->setMaxSize(clampedValue);
440 m_storeFileLoggerMaxSize = clampedValue;
443 int Application::fileLoggerAge() const
445 const int val = m_storeFileLoggerAge.get(1);
446 return std::clamp(val, 1, 365);
449 void Application::setFileLoggerAge(const int value)
451 m_storeFileLoggerAge = std::clamp(value, 1, 365);
454 int Application::fileLoggerAgeType() const
456 const int val = m_storeFileLoggerAgeType.get(1);
457 return ((val < 0) || (val > 2)) ? 1 : val;
460 void Application::setFileLoggerAgeType(const int value)
462 m_storeFileLoggerAgeType = ((value < 0) || (value > 2)) ? 1 : value;
465 void Application::processMessage(const QString &message)
467 #ifndef DISABLE_GUI
468 if (message.isEmpty())
470 if (BitTorrent::Session::instance()->isRestored()) [[likely]]
472 m_window->activate(); // show UI
474 else if (m_startupProgressDialog)
476 m_startupProgressDialog->show();
477 m_startupProgressDialog->activateWindow();
478 m_startupProgressDialog->raise();
480 else
482 createStartupProgressDialog();
485 return;
487 #endif
489 const QBtCommandLineParameters params = parseParams(message);
490 // If Application is not allowed to process params immediately
491 // (i.e., other components are not ready) store params
492 if (m_isProcessingParamsAllowed)
493 processParams(params);
494 else
495 m_paramsQueue.append(params);
498 void Application::runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const
500 // Cannot give users shell environment by default, as doing so could
501 // enable command injection via torrent name and other arguments
502 // (especially when some automated download mechanism has been setup).
503 // See: https://github.com/qbittorrent/qBittorrent/issues/10925
505 const auto replaceVariables = [torrent](QString str) -> QString
507 for (int i = (str.length() - 2); i >= 0; --i)
509 if (str[i] != u'%')
510 continue;
512 const ushort specifier = str[i + 1].unicode();
513 switch (specifier)
515 case u'C':
516 str.replace(i, 2, QString::number(torrent->filesCount()));
517 break;
518 case u'D':
519 str.replace(i, 2, torrent->savePath().toString());
520 break;
521 case u'F':
522 str.replace(i, 2, torrent->contentPath().toString());
523 break;
524 case u'G':
525 str.replace(i, 2, Utils::String::joinIntoString(torrent->tags(), u","_s));
526 break;
527 case u'I':
528 str.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : u"-"_s));
529 break;
530 case u'J':
531 str.replace(i, 2, (torrent->infoHash().v2().isValid() ? torrent->infoHash().v2().toString() : u"-"_s));
532 break;
533 case u'K':
534 str.replace(i, 2, torrent->id().toString());
535 break;
536 case u'L':
537 str.replace(i, 2, torrent->category());
538 break;
539 case u'N':
540 str.replace(i, 2, torrent->name());
541 break;
542 case u'R':
543 str.replace(i, 2, torrent->rootPath().toString());
544 break;
545 case u'T':
546 str.replace(i, 2, torrent->currentTracker());
547 break;
548 case u'Z':
549 str.replace(i, 2, QString::number(torrent->totalSize()));
550 break;
551 default:
552 // do nothing
553 break;
556 // decrement `i` to avoid unwanted replacement, example pattern: "%%N"
557 --i;
560 return str;
563 const QString logMsg = tr("Running external program. Torrent: \"%1\". Command: `%2`");
564 const QString logMsgError = tr("Failed to run external program. Torrent: \"%1\". Command: `%2`");
566 // The processing sequenece is different for Windows and other OS, this is intentional
567 #if defined(Q_OS_WIN)
568 const QString program = replaceVariables(programTemplate);
569 const std::wstring programWStr = program.toStdWString();
571 // Need to split arguments manually because QProcess::startDetached(QString)
572 // will strip off empty parameters.
573 // E.g. `python.exe "1" "" "3"` will become `python.exe "1" "3"`
574 int argCount = 0;
575 std::unique_ptr<LPWSTR[], decltype(&::LocalFree)> args {::CommandLineToArgvW(programWStr.c_str(), &argCount), ::LocalFree};
577 if (argCount <= 0)
578 return;
580 QStringList argList;
581 for (int i = 1; i < argCount; ++i)
582 argList += QString::fromWCharArray(args[i]);
584 QProcess proc;
585 proc.setProgram(QString::fromWCharArray(args[0]));
586 proc.setArguments(argList);
587 proc.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args)
589 if (Preferences::instance()->isAutoRunConsoleEnabled())
591 args->flags |= CREATE_NEW_CONSOLE;
592 args->flags &= ~(CREATE_NO_WINDOW | DETACHED_PROCESS);
594 else
596 args->flags |= CREATE_NO_WINDOW;
597 args->flags &= ~(CREATE_NEW_CONSOLE | DETACHED_PROCESS);
599 args->inheritHandles = false;
600 args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
601 ::CloseHandle(args->startupInfo->hStdInput);
602 ::CloseHandle(args->startupInfo->hStdOutput);
603 ::CloseHandle(args->startupInfo->hStdError);
604 args->startupInfo->hStdInput = nullptr;
605 args->startupInfo->hStdOutput = nullptr;
606 args->startupInfo->hStdError = nullptr;
609 if (proc.startDetached())
610 LogMsg(logMsg.arg(torrent->name(), program));
611 else
612 LogMsg(logMsgError.arg(torrent->name(), program));
613 #else // Q_OS_WIN
614 QStringList args = Utils::String::splitCommand(programTemplate);
616 if (args.isEmpty())
617 return;
619 for (QString &arg : args)
621 // strip redundant quotes
622 if (arg.startsWith(u'"') && arg.endsWith(u'"'))
623 arg = arg.mid(1, (arg.size() - 2));
625 arg = replaceVariables(arg);
628 const QString command = args.takeFirst();
629 QProcess proc;
630 proc.setProgram(command);
631 proc.setArguments(args);
633 if (proc.startDetached())
635 // show intended command in log
636 LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
638 else
640 // show intended command in log
641 LogMsg(logMsgError.arg(torrent->name(), replaceVariables(programTemplate)));
643 #endif
646 void Application::sendNotificationEmail(const BitTorrent::Torrent *torrent)
648 // Prepare mail content
649 const QString content = tr("Torrent name: %1").arg(torrent->name()) + u'\n'
650 + tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + u'\n'
651 + tr("Save path: %1").arg(torrent->savePath().toString()) + u"\n\n"
652 + tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds")
653 .arg(Utils::Misc::userFriendlyDuration(torrent->activeTime())) + u"\n\n\n"
654 + tr("Thank you for using qBittorrent.") + u'\n';
656 // Send the notification email
657 const Preferences *pref = Preferences::instance();
658 auto *smtp = new Net::Smtp(this);
659 smtp->sendMail(pref->getMailNotificationSender(),
660 pref->getMailNotificationEmail(),
661 tr("Torrent \"%1\" has finished downloading").arg(torrent->name()),
662 content);
665 void Application::torrentAdded(const BitTorrent::Torrent *torrent) const
667 const Preferences *pref = Preferences::instance();
669 // AutoRun program
670 if (pref->isAutoRunOnTorrentAddedEnabled())
671 runExternalProgram(pref->getAutoRunOnTorrentAddedProgram().trimmed(), torrent);
674 void Application::torrentFinished(const BitTorrent::Torrent *torrent)
676 const Preferences *pref = Preferences::instance();
678 // AutoRun program
679 if (pref->isAutoRunOnTorrentFinishedEnabled())
680 runExternalProgram(pref->getAutoRunOnTorrentFinishedProgram().trimmed(), torrent);
682 // Mail notification
683 if (pref->isMailNotificationEnabled())
685 LogMsg(tr("Torrent: %1, sending mail notification").arg(torrent->name()));
686 sendNotificationEmail(torrent);
689 #ifndef DISABLE_GUI
690 if (Preferences::instance()->isRecursiveDownloadEnabled())
692 // Check whether it contains .torrent files
693 for (const Path &torrentRelpath : asConst(torrent->filePaths()))
695 if (torrentRelpath.hasExtension(u".torrent"_s))
697 askRecursiveTorrentDownloadConfirmation(torrent);
698 break;
702 #endif
705 void Application::allTorrentsFinished()
707 Preferences *const pref = Preferences::instance();
708 bool isExit = pref->shutdownqBTWhenDownloadsComplete();
709 bool isShutdown = pref->shutdownWhenDownloadsComplete();
710 bool isSuspend = pref->suspendWhenDownloadsComplete();
711 bool isHibernate = pref->hibernateWhenDownloadsComplete();
713 bool haveAction = isExit || isShutdown || isSuspend || isHibernate;
714 if (!haveAction) return;
716 ShutdownDialogAction action = ShutdownDialogAction::Exit;
717 if (isSuspend)
718 action = ShutdownDialogAction::Suspend;
719 else if (isHibernate)
720 action = ShutdownDialogAction::Hibernate;
721 else if (isShutdown)
722 action = ShutdownDialogAction::Shutdown;
724 #ifndef DISABLE_GUI
725 // ask confirm
726 if ((action == ShutdownDialogAction::Exit) && (pref->dontConfirmAutoExit()))
728 // do nothing & skip confirm
730 else
732 if (!ShutdownConfirmDialog::askForConfirmation(m_window, action)) return;
734 #endif // DISABLE_GUI
736 // Actually shut down
737 if (action != ShutdownDialogAction::Exit)
739 qDebug("Preparing for auto-shutdown because all downloads are complete!");
740 // Disabling it for next time
741 pref->setShutdownWhenDownloadsComplete(false);
742 pref->setSuspendWhenDownloadsComplete(false);
743 pref->setHibernateWhenDownloadsComplete(false);
744 // Make sure preferences are synced before exiting
745 m_shutdownAct = action;
748 qDebug("Exiting the application");
749 exit();
752 bool Application::callMainInstance()
754 return m_instanceManager->sendMessage(serializeParams(commandLineArgs()));
757 void Application::processParams(const QBtCommandLineParameters &params)
759 #ifndef DISABLE_GUI
760 // There are two circumstances in which we want to show the torrent
761 // dialog. One is when the application settings specify that it should
762 // be shown and skipTorrentDialog is undefined. The other is when
763 // skipTorrentDialog is false, meaning that the application setting
764 // should be overridden.
765 AddTorrentOption addTorrentOption = AddTorrentOption::Default;
766 if (params.skipDialog.has_value())
767 addTorrentOption = params.skipDialog.value() ? AddTorrentOption::SkipDialog : AddTorrentOption::ShowDialog;
768 for (const QString &torrentSource : params.torrentSources)
769 m_addTorrentManager->addTorrent(torrentSource, params.addTorrentParams, addTorrentOption);
770 #else
771 for (const QString &torrentSource : params.torrentSources)
772 m_addTorrentManager->addTorrent(torrentSource, params.addTorrentParams);
773 #endif
776 int Application::exec()
778 #if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI)
779 const QString loadingStr = tr("WebUI will be started shortly after internal preparations. Please wait...");
780 printf("%s\n", qUtf8Printable(loadingStr));
781 #endif
783 #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
784 applyMemoryWorkingSetLimit();
785 #endif
787 #ifdef Q_OS_WIN
788 applyMemoryPriority();
789 adjustThreadPriority();
790 #endif
792 Net::ProxyConfigurationManager::initInstance();
793 Net::DownloadManager::initInstance();
795 BitTorrent::Session::initInstance();
796 #ifndef DISABLE_GUI
797 UIThemeManager::initInstance();
799 m_desktopIntegration = new DesktopIntegration;
800 m_desktopIntegration->setToolTip(tr("Loading torrents..."));
801 #ifndef Q_OS_MACOS
802 auto *desktopIntegrationMenu = new QMenu;
803 auto *actionExit = new QAction(tr("E&xit"), desktopIntegrationMenu);
804 actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
805 actionExit->setMenuRole(QAction::QuitRole);
806 actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
807 connect(actionExit, &QAction::triggered, this, []
809 QApplication::exit();
811 desktopIntegrationMenu->addAction(actionExit);
813 m_desktopIntegration->setMenu(desktopIntegrationMenu);
815 const bool isHidden = m_desktopIntegration->isActive() && (startUpWindowState() == WindowState::Hidden);
816 #else
817 const bool isHidden = false;
818 #endif
820 if (!isHidden)
822 createStartupProgressDialog();
823 // Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore.
824 m_startupProgressDialog->setMinimumDuration(1000);
825 if (startUpWindowState() != WindowState::Normal)
826 m_startupProgressDialog->setWindowState(Qt::WindowMinimized);
828 else
830 connect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
832 #endif
833 connect(BitTorrent::Session::instance(), &BitTorrent::Session::restored, this, [this]()
835 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAdded, this, &Application::torrentAdded);
836 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
837 connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
839 m_addTorrentManager = new AddTorrentManagerImpl(this, BitTorrent::Session::instance(), this);
841 Net::GeoIPManager::initInstance();
842 TorrentFilesWatcher::initInstance();
844 new RSS::Session; // create RSS::Session singleton
845 new RSS::AutoDownloader(this); // create RSS::AutoDownloader singleton
847 #ifndef DISABLE_GUI
848 const auto *btSession = BitTorrent::Session::instance();
849 connect(btSession, &BitTorrent::Session::fullDiskError, this
850 , [this](const BitTorrent::Torrent *torrent, const QString &msg)
852 m_desktopIntegration->showNotification(tr("I/O Error", "i.e: Input/Output Error")
853 , tr("An I/O error occurred for torrent '%1'.\n Reason: %2"
854 , "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg));
856 connect(btSession, &BitTorrent::Session::torrentFinished, this
857 , [this](const BitTorrent::Torrent *torrent)
859 m_desktopIntegration->showNotification(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name()));
861 connect(m_addTorrentManager, &AddTorrentManager::torrentAdded, this
862 , [this]([[maybe_unused]] const QString &source, const BitTorrent::Torrent *torrent)
864 if (isTorrentAddedNotificationsEnabled())
865 m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
867 connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
868 , [this](const QString &source, const QString &reason)
870 m_desktopIntegration->showNotification(tr("Add torrent failed")
871 , tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
874 disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
875 #ifndef Q_OS_MACOS
876 const WindowState windowState = !m_startupProgressDialog ? WindowState::Hidden
877 : (m_startupProgressDialog->windowState() & Qt::WindowMinimized) ? WindowState::Minimized
878 : WindowState::Normal;
879 #else
880 const WindowState windowState = (m_startupProgressDialog->windowState() & Qt::WindowMinimized)
881 ? WindowState::Minimized : WindowState::Normal;
882 #endif
883 m_window = new MainWindow(this, windowState);
884 delete m_startupProgressDialog;
885 #endif // DISABLE_GUI
887 #ifndef DISABLE_WEBUI
888 #ifndef DISABLE_GUI
889 m_webui = new WebUI(this);
890 #else
891 const auto *pref = Preferences::instance();
893 const QString tempPassword = pref->getWebUIPassword().isEmpty()
894 ? Utils::Password::generate() : QString();
895 m_webui = new WebUI(this, (!tempPassword.isEmpty() ? Utils::Password::PBKDF2::generate(tempPassword) : QByteArray()));
896 connect(m_webui, &WebUI::error, this, [](const QString &message)
898 fprintf(stderr, "WebUI configuration failed. Reason: %s\n", qUtf8Printable(message));
901 printf("%s", qUtf8Printable(u"\n******** %1 ********\n"_s.arg(tr("Information"))));
903 if (m_webui->isErrored())
905 const QString error = m_webui->errorMessage() + u'\n'
906 + tr("To fix the error, you may need to edit the config file manually.");
907 fprintf(stderr, "%s\n", qUtf8Printable(error));
909 else if (m_webui->isEnabled())
911 const QHostAddress address = m_webui->hostAddress();
912 const QString url = u"%1://%2:%3"_s.arg((m_webui->isHttps() ? u"https"_s : u"http"_s)
913 , (address.isEqual(QHostAddress::Any, QHostAddress::ConvertUnspecifiedAddress) ? u"localhost"_s : address.toString())
914 , QString::number(m_webui->port()));
915 printf("%s\n", qUtf8Printable(tr("To control qBittorrent, access the WebUI at: %1").arg(url)));
917 if (!tempPassword.isEmpty())
919 const QString warning = tr("The WebUI administrator username is: %1").arg(pref->getWebUIUsername()) + u'\n'
920 + tr("The WebUI administrator password was not set. A temporary password is provided for this session: %1").arg(tempPassword) + u'\n'
921 + tr("You should set your own password in program preferences.") + u'\n';
922 printf("%s", qUtf8Printable(warning));
925 else
927 printf("%s\n", qUtf8Printable(tr("The WebUI is disabled! To enable the WebUI, edit the config file manually.")));
929 #endif // DISABLE_GUI
930 #endif // DISABLE_WEBUI
932 m_isProcessingParamsAllowed = true;
933 for (const QBtCommandLineParameters &params : m_paramsQueue)
934 processParams(params);
935 m_paramsQueue.clear();
938 const QBtCommandLineParameters params = commandLineArgs();
939 if (!params.torrentSources.isEmpty())
940 m_paramsQueue.append(params);
942 return BaseApplication::exec();
945 bool Application::hasAnotherInstance() const
947 return !m_instanceManager->isFirstInstance();
950 #ifndef DISABLE_GUI
951 void Application::createStartupProgressDialog()
953 Q_ASSERT(!m_startupProgressDialog);
954 Q_ASSERT(m_desktopIntegration);
956 disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
958 m_startupProgressDialog = new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100);
959 m_startupProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
960 m_startupProgressDialog->setWindowFlag(Qt::WindowMinimizeButtonHint);
961 m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediately by default
962 m_startupProgressDialog->setAutoReset(false);
963 m_startupProgressDialog->setAutoClose(false);
965 connect(m_startupProgressDialog, &QProgressDialog::canceled, this, []()
967 QApplication::exit();
970 connect(BitTorrent::Session::instance(), &BitTorrent::Session::startupProgressUpdated, m_startupProgressDialog, &QProgressDialog::setValue);
972 connect(m_desktopIntegration, &DesktopIntegration::activationRequested, m_startupProgressDialog, [this]()
974 #ifdef Q_OS_MACOS
975 if (!m_startupProgressDialog->isVisible())
977 m_startupProgressDialog->show();
978 m_startupProgressDialog->activateWindow();
979 m_startupProgressDialog->raise();
981 #else
982 if (m_startupProgressDialog->isHidden())
984 // Make sure the window is not minimized
985 m_startupProgressDialog->setWindowState((m_startupProgressDialog->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
987 // Then show it
988 m_startupProgressDialog->show();
989 m_startupProgressDialog->raise();
990 m_startupProgressDialog->activateWindow();
992 else
994 m_startupProgressDialog->hide();
996 #endif
1000 void Application::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent)
1002 const auto torrentID = torrent->id();
1004 QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
1005 , tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent->name())
1006 , (QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll), mainWindow());
1007 confirmBox->setAttribute(Qt::WA_DeleteOnClose);
1009 const QAbstractButton *yesButton = confirmBox->button(QMessageBox::Yes);
1010 QAbstractButton *neverButton = confirmBox->button(QMessageBox::NoToAll);
1011 neverButton->setText(tr("Never"));
1013 connect(confirmBox, &QMessageBox::buttonClicked, this
1014 , [this, torrentID, yesButton, neverButton](const QAbstractButton *button)
1016 if (button == yesButton)
1018 recursiveTorrentDownload(torrentID);
1020 else if (button == neverButton)
1022 Preferences::instance()->setRecursiveDownloadEnabled(false);
1025 confirmBox->open();
1028 void Application::recursiveTorrentDownload(const BitTorrent::TorrentID &torrentID)
1030 const BitTorrent::Torrent *torrent = BitTorrent::Session::instance()->getTorrent(torrentID);
1031 if (!torrent)
1032 return;
1034 for (const Path &torrentRelpath : asConst(torrent->filePaths()))
1036 if (torrentRelpath.hasExtension(u".torrent"_s))
1038 const Path torrentFullpath = torrent->savePath() / torrentRelpath;
1040 LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
1041 .arg(torrent->name(), torrentFullpath.toString()));
1043 BitTorrent::AddTorrentParams params;
1044 // Passing the save path along to the sub torrent file
1045 params.savePath = torrent->savePath();
1046 addTorrentManager()->addTorrent(torrentFullpath.data(), params, AddTorrentOption::SkipDialog);
1051 #ifdef Q_OS_MACOS
1052 bool Application::event(QEvent *ev)
1054 if (ev->type() == QEvent::FileOpen)
1056 QString path = static_cast<QFileOpenEvent *>(ev)->file();
1057 if (path.isEmpty())
1058 // Get the url instead
1059 path = static_cast<QFileOpenEvent *>(ev)->url().toString();
1060 qDebug("Received a mac file open event: %s", qUtf8Printable(path));
1062 QBtCommandLineParameters params;
1063 params.torrentSources.append(path);
1064 // If Application is not allowed to process params immediately
1065 // (i.e., other components are not ready) store params
1066 if (m_isProcessingParamsAllowed)
1067 processParams(params);
1068 else
1069 m_paramsQueue.append(params);
1071 return true;
1074 return BaseApplication::event(ev);
1076 #endif // Q_OS_MACOS
1077 #endif // DISABLE_GUI
1079 void Application::initializeTranslation()
1081 Preferences *const pref = Preferences::instance();
1082 // Load translation
1083 const QString localeStr = pref->getLocale();
1085 if (m_qtTranslator.load((u"qtbase_" + localeStr), QLibraryInfo::path(QLibraryInfo::TranslationsPath))
1086 || m_qtTranslator.load((u"qt_" + localeStr), QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
1088 qDebug("Qt %s locale recognized, using translation.", qUtf8Printable(localeStr));
1090 else
1092 qDebug("Qt %s locale unrecognized, using default (en).", qUtf8Printable(localeStr));
1095 installTranslator(&m_qtTranslator);
1097 if (m_translator.load(u":/lang/qbittorrent_" + localeStr))
1098 qDebug("%s locale recognized, using translation.", qUtf8Printable(localeStr));
1099 else
1100 qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(localeStr));
1101 installTranslator(&m_translator);
1103 #ifndef DISABLE_GUI
1104 if (localeStr.startsWith(u"ar") || localeStr.startsWith(u"he"))
1106 qDebug("Right to Left mode");
1107 setLayoutDirection(Qt::RightToLeft);
1109 else
1111 setLayoutDirection(Qt::LeftToRight);
1113 #endif
1116 #if (!defined(DISABLE_GUI) && defined(Q_OS_WIN))
1117 void Application::shutdownCleanup([[maybe_unused]] QSessionManager &manager)
1119 // This is only needed for a special case on Windows XP.
1120 // (but is called for every Windows version)
1121 // If a process takes too much time to exit during OS
1122 // shutdown, the OS presents a dialog to the user.
1123 // That dialog tells the user that qbt is blocking the
1124 // shutdown, it shows a progress bar and it offers
1125 // a "Terminate Now" button for the user. However,
1126 // after the progress bar has reached 100% another button
1127 // is offered to the user reading "Cancel". With this the
1128 // user can cancel the **OS** shutdown. If we don't do
1129 // the cleanup by handling the commitDataRequest() signal
1130 // and the user clicks "Cancel", it will result in qbt being
1131 // killed and the shutdown proceeding instead. Apparently
1132 // aboutToQuit() is emitted too late in the shutdown process.
1133 cleanup();
1135 // According to the qt docs we shouldn't call quit() inside a slot.
1136 // aboutToQuit() is never emitted if the user hits "Cancel" in
1137 // the above dialog.
1138 QMetaObject::invokeMethod(qApp, &QCoreApplication::quit, Qt::QueuedConnection);
1140 #endif
1142 #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
1143 void Application::applyMemoryWorkingSetLimit() const
1145 const size_t MiB = 1024 * 1024;
1146 const QString logMessage = tr("Failed to set physical memory (RAM) usage limit. Error code: %1. Error message: \"%2\"");
1148 #ifdef Q_OS_WIN
1149 const SIZE_T maxSize = memoryWorkingSetLimit() * MiB;
1150 const auto minSize = std::min<SIZE_T>((64 * MiB), (maxSize / 2));
1151 if (!::SetProcessWorkingSetSizeEx(::GetCurrentProcess(), minSize, maxSize, QUOTA_LIMITS_HARDWS_MAX_ENABLE))
1153 const DWORD errorCode = ::GetLastError();
1154 QString message;
1155 LPVOID lpMsgBuf = nullptr;
1156 const DWORD msgLength = ::FormatMessageW((FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS)
1157 , nullptr, errorCode, LANG_USER_DEFAULT, reinterpret_cast<LPWSTR>(&lpMsgBuf), 0, nullptr);
1158 if (msgLength > 0)
1160 message = QString::fromWCharArray(reinterpret_cast<LPWSTR>(lpMsgBuf)).trimmed();
1161 ::LocalFree(lpMsgBuf);
1163 LogMsg(logMessage.arg(QString::number(errorCode), message), Log::WARNING);
1165 #elif defined(Q_OS_UNIX)
1166 // has no effect on linux but it might be meaningful for other OS
1167 rlimit limit {};
1169 if (::getrlimit(RLIMIT_RSS, &limit) != 0)
1170 return;
1172 const size_t newSize = memoryWorkingSetLimit() * MiB;
1173 if (newSize > limit.rlim_max)
1175 // try to raise the hard limit
1176 rlimit newLimit = limit;
1177 newLimit.rlim_max = newSize;
1178 if (::setrlimit(RLIMIT_RSS, &newLimit) != 0)
1180 const auto message = QString::fromLocal8Bit(strerror(errno));
1181 LogMsg(tr("Failed to set physical memory (RAM) usage hard limit. Requested size: %1. System hard limit: %2. Error code: %3. Error message: \"%4\"")
1182 .arg(QString::number(newSize), QString::number(limit.rlim_max), QString::number(errno), message), Log::WARNING);
1183 return;
1187 limit.rlim_cur = newSize;
1188 if (::setrlimit(RLIMIT_RSS, &limit) != 0)
1190 const auto message = QString::fromLocal8Bit(strerror(errno));
1191 LogMsg(logMessage.arg(QString::number(errno), message), Log::WARNING);
1193 #endif
1195 #endif
1197 #ifdef Q_OS_WIN
1198 MemoryPriority Application::processMemoryPriority() const
1200 return m_processMemoryPriority.get(MemoryPriority::BelowNormal);
1203 void Application::setProcessMemoryPriority(const MemoryPriority priority)
1205 if (processMemoryPriority() == priority)
1206 return;
1208 m_processMemoryPriority = priority;
1209 applyMemoryPriority();
1212 void Application::applyMemoryPriority() const
1214 using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);
1215 const auto setProcessInformation = Utils::OS::loadWinAPI<SETPROCESSINFORMATION>(u"Kernel32.dll"_s, "SetProcessInformation");
1216 if (!setProcessInformation) // only available on Windows >= 8
1217 return;
1219 using SETTHREADINFORMATION = BOOL (WINAPI *)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD);
1220 const auto setThreadInformation = Utils::OS::loadWinAPI<SETTHREADINFORMATION>(u"Kernel32.dll"_s, "SetThreadInformation");
1221 if (!setThreadInformation) // only available on Windows >= 8
1222 return;
1224 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
1225 // this dummy struct is required to compile successfully when targeting older Windows version
1226 struct MEMORY_PRIORITY_INFORMATION
1228 ULONG MemoryPriority;
1231 #define MEMORY_PRIORITY_LOWEST 0
1232 #define MEMORY_PRIORITY_VERY_LOW 1
1233 #define MEMORY_PRIORITY_LOW 2
1234 #define MEMORY_PRIORITY_MEDIUM 3
1235 #define MEMORY_PRIORITY_BELOW_NORMAL 4
1236 #define MEMORY_PRIORITY_NORMAL 5
1237 #endif
1239 MEMORY_PRIORITY_INFORMATION prioInfo {};
1240 switch (processMemoryPriority())
1242 case MemoryPriority::Normal:
1243 default:
1244 prioInfo.MemoryPriority = MEMORY_PRIORITY_NORMAL;
1245 break;
1246 case MemoryPriority::BelowNormal:
1247 prioInfo.MemoryPriority = MEMORY_PRIORITY_BELOW_NORMAL;
1248 break;
1249 case MemoryPriority::Medium:
1250 prioInfo.MemoryPriority = MEMORY_PRIORITY_MEDIUM;
1251 break;
1252 case MemoryPriority::Low:
1253 prioInfo.MemoryPriority = MEMORY_PRIORITY_LOW;
1254 break;
1255 case MemoryPriority::VeryLow:
1256 prioInfo.MemoryPriority = MEMORY_PRIORITY_VERY_LOW;
1257 break;
1259 setProcessInformation(::GetCurrentProcess(), ProcessMemoryPriority, &prioInfo, sizeof(prioInfo));
1261 // To avoid thrashing/sluggishness of the app, set "main event loop" thread to normal memory priority
1262 // which is higher/equal than other threads
1263 prioInfo.MemoryPriority = MEMORY_PRIORITY_NORMAL;
1264 setThreadInformation(::GetCurrentThread(), ThreadMemoryPriority, &prioInfo, sizeof(prioInfo));
1267 void Application::adjustThreadPriority() const
1269 // Workaround for improving responsiveness of qbt when CPU resources are scarce.
1270 // Raise main event loop thread to be just one level higher than libtorrent threads.
1271 // Also note that on *nix platforms there is no easy way to achieve it,
1272 // so implementation is omitted.
1274 ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
1276 #endif
1278 void Application::cleanup()
1280 // cleanup() can be called multiple times during shutdown. We only need it once.
1281 if (m_isCleanupRun.exchange(true, std::memory_order_acquire))
1282 return;
1284 LogMsg(tr("qBittorrent termination initiated"));
1286 #ifndef DISABLE_GUI
1287 if (m_desktopIntegration)
1289 m_desktopIntegration->disconnect();
1290 m_desktopIntegration->setToolTip(tr("qBittorrent is shutting down..."));
1291 if (m_desktopIntegration->menu())
1292 m_desktopIntegration->menu()->setEnabled(false);
1295 if (m_window)
1297 // Hide the window and don't leave it on screen as
1298 // unresponsive. Also for Windows take the WinId
1299 // after it's hidden, because hide() may cause a
1300 // WinId change.
1301 m_window->hide();
1303 #ifdef Q_OS_WIN
1304 const std::wstring msg = tr("Saving torrent progress...").toStdWString();
1305 ::ShutdownBlockReasonCreate(reinterpret_cast<HWND>(m_window->effectiveWinId())
1306 , msg.c_str());
1307 #endif // Q_OS_WIN
1309 // Do manual cleanup in MainWindow to force widgets
1310 // to save their Preferences, stop all timers and
1311 // delete as many widgets as possible to leave only
1312 // a 'shell' MainWindow.
1313 // We need a valid window handle for Windows Vista+
1314 // otherwise the system shutdown will continue even
1315 // though we created a ShutdownBlockReason
1316 m_window->cleanup();
1318 #endif // DISABLE_GUI
1320 #ifndef DISABLE_WEBUI
1321 delete m_webui;
1322 #endif
1324 delete RSS::AutoDownloader::instance();
1325 delete RSS::Session::instance();
1327 TorrentFilesWatcher::freeInstance();
1328 delete m_addTorrentManager;
1329 BitTorrent::Session::freeInstance();
1330 Net::GeoIPManager::freeInstance();
1331 Net::DownloadManager::freeInstance();
1332 Net::ProxyConfigurationManager::freeInstance();
1333 Preferences::freeInstance();
1334 SettingsStorage::freeInstance();
1335 SearchPluginManager::freeInstance();
1336 Utils::Fs::removeDirRecursively(Utils::Fs::tempPath());
1338 LogMsg(tr("qBittorrent is now ready to exit"));
1339 Logger::freeInstance();
1340 delete m_fileLogger;
1342 #ifndef DISABLE_GUI
1343 if (m_window)
1345 #ifdef Q_OS_WIN
1346 ::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId()));
1347 #endif // Q_OS_WIN
1348 delete m_window;
1349 delete m_desktopIntegration;
1350 UIThemeManager::freeInstance();
1352 #endif // DISABLE_GUI
1354 Profile::freeInstance();
1356 if (m_shutdownAct != ShutdownDialogAction::Exit)
1358 qDebug() << "Sending computer shutdown/suspend/hibernate signal...";
1359 Utils::OS::shutdownComputer(m_shutdownAct);
1363 AddTorrentManagerImpl *Application::addTorrentManager() const
1365 return m_addTorrentManager;
1368 #ifndef DISABLE_WEBUI
1369 WebUI *Application::webUI() const
1371 return m_webui;
1373 #endif