2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 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"
42 #elif defined(Q_OS_UNIX)
43 #include <sys/resource.h>
48 #include <QLibraryInfo>
49 #include <QMetaObject>
53 #include <QAbstractButton>
55 #include <QMessageBox>
56 #include <QPixmapCache>
57 #include <QProgressDialog>
59 #include <QSessionManager>
62 #include <QFileOpenEvent>
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"
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"
101 #ifndef DISABLE_WEBUI
102 #include "webui/webui.h"
104 #include "base/utils/password.h"
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
124 const int PIXMAP_CACHE_SIZE
= 64 * 1024 * 1024; // 64MiB
127 QString
serializeParams(const QBtCommandLineParameters
¶ms
)
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
.addStopped
.has_value())
144 result
.append(*addTorrentParams
.addStopped
? u
"@addStopped=1"_s
: u
"@addStopped=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));
183 if (param
.startsWith(u
"@addStopped="))
185 addTorrentParams
.addStopped
= (QStringView(param
).mid(11).toInt() != 0);
189 if (param
== u
"@skipChecking")
191 addTorrentParams
.skipChecking
= true;
195 if (param
.startsWith(u
"@category="))
197 addTorrentParams
.category
= param
.mid(10);
201 if (param
== u
"@sequential")
203 addTorrentParams
.sequential
= true;
207 if (param
== u
"@firstLastPiecePriority")
209 addTorrentParams
.firstLastPiecePriority
= true;
213 if (param
.startsWith(u
"@skipDialog="))
215 parsedParams
.skipDialog
= (QStringView(param
).mid(12).toInt() != 0);
219 parsedParams
.torrentSources
.append(param
);
226 Application::Application(int &argc
, char **argv
)
227 : BaseApplication(argc
, argv
)
228 , m_commandLineArgs(parseCommandLine(Application::arguments()))
229 , m_storeInstanceName(SETTINGS_KEY(u
"InstanceName"_s
))
230 , m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u
"Enabled"_s
))
231 , m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u
"Backup"_s
))
232 , m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u
"DeleteOld"_s
))
233 , m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY(u
"MaxSizeBytes"_s
))
234 , m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u
"Age"_s
))
235 , m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u
"AgeType"_s
))
236 , m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u
"Path"_s
))
237 , m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u
"MemoryWorkingSetLimit"_s
))
239 , m_processMemoryPriority(SETTINGS_KEY(u
"ProcessMemoryPriority"_s
))
242 , m_startUpWindowState(u
"GUI/StartUpWindowState"_s
)
243 , m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u
"TorrentAdded"_s
))
246 qRegisterMetaType
<Log::Msg
>("Log::Msg");
247 qRegisterMetaType
<Log::Peer
>("Log::Peer");
249 setApplicationName(u
"qBittorrent"_s
);
250 setOrganizationDomain(u
"qbittorrent.org"_s
);
251 #if !defined(DISABLE_GUI)
252 setDesktopFileName(u
"org.qbittorrent.qBittorrent"_s
);
253 setQuitOnLastWindowClosed(false);
254 setQuitLockEnabled(false);
255 QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE
);
258 Logger::initInstance();
260 const auto portableProfilePath
= Path(QCoreApplication::applicationDirPath()) / DEFAULT_PORTABLE_MODE_PROFILE_DIR
;
261 const bool portableModeEnabled
= m_commandLineArgs
.profileDir
.isEmpty() && Utils::Fs::isDir(portableProfilePath
);
262 const Path profileDir
= portableModeEnabled
? portableProfilePath
: m_commandLineArgs
.profileDir
;
263 Profile::initInstance(profileDir
, m_commandLineArgs
.configurationName
,
264 (m_commandLineArgs
.relativeFastresumePaths
|| portableModeEnabled
));
266 m_instanceManager
= new ApplicationInstanceManager(Profile::instance()->location(SpecialFolder::Config
), this);
268 SettingsStorage::initInstance();
269 Preferences::initInstance();
271 const bool firstTimeUser
= SettingsStorage::instance()->isEmpty();
274 setCurrentMigrationVersion();
275 handleChangedDefaults(DefaultPreferencesMode::Current
);
280 throw RuntimeError(u
"Failed migration of old settings"_s
); // Not translatable. Translation isn't configured yet.
281 handleChangedDefaults(DefaultPreferencesMode::Legacy
);
284 initializeTranslation();
286 connect(this, &QCoreApplication::aboutToQuit
, this, &Application::cleanup
);
287 connect(m_instanceManager
, &ApplicationInstanceManager::messageReceived
, this, &Application::processMessage
);
288 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
289 connect(this, &QGuiApplication::commitDataRequest
, this, &Application::shutdownCleanup
, Qt::DirectConnection
);
292 LogMsg(tr("qBittorrent %1 started. Process ID: %2", "qBittorrent v3.2.0alpha started")
293 .arg(QStringLiteral(QBT_VERSION
), QString::number(QCoreApplication::applicationPid())));
294 if (portableModeEnabled
)
296 LogMsg(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir
.toString()));
297 if (m_commandLineArgs
.relativeFastresumePaths
)
298 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
302 LogMsg(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config
).toString()));
305 if (isFileLoggerEnabled())
306 m_fileLogger
= new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType
>(fileLoggerAgeType()));
308 if (m_commandLineArgs
.webUIPort
> 0) // it will be -1 when user did not set any value
309 Preferences::instance()->setWebUIPort(m_commandLineArgs
.webUIPort
);
311 if (m_commandLineArgs
.torrentingPort
> 0) // it will be -1 when user did not set any value
313 SettingValue
<int> port
{u
"BitTorrent/Session/Port"_s
};
314 port
= m_commandLineArgs
.torrentingPort
;
318 Application::~Application()
320 // we still need to call cleanup()
321 // in case the App failed to start
326 DesktopIntegration
*Application::desktopIntegration()
328 return m_desktopIntegration
;
331 MainWindow
*Application::mainWindow()
336 WindowState
Application::startUpWindowState() const
338 return m_startUpWindowState
;
341 void Application::setStartUpWindowState(const WindowState windowState
)
343 m_startUpWindowState
= windowState
;
346 bool Application::isTorrentAddedNotificationsEnabled() const
348 return m_storeNotificationTorrentAdded
;
351 void Application::setTorrentAddedNotificationsEnabled(const bool value
)
353 m_storeNotificationTorrentAdded
= value
;
357 const QBtCommandLineParameters
&Application::commandLineArgs() const
359 return m_commandLineArgs
;
362 QString
Application::instanceName() const
364 return m_storeInstanceName
;
367 void Application::setInstanceName(const QString
&name
)
369 if (name
== instanceName())
372 m_storeInstanceName
= name
;
374 if (MainWindow
*mw
= mainWindow())
375 mw
->setTitleSuffix(name
);
379 int Application::memoryWorkingSetLimit() const
381 return m_storeMemoryWorkingSetLimit
.get(512);
384 void Application::setMemoryWorkingSetLimit(const int size
)
386 if (size
== memoryWorkingSetLimit())
389 m_storeMemoryWorkingSetLimit
= size
;
390 #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
391 applyMemoryWorkingSetLimit();
395 bool Application::isFileLoggerEnabled() const
397 return m_storeFileLoggerEnabled
.get(true);
400 void Application::setFileLoggerEnabled(const bool value
)
402 if (value
&& !m_fileLogger
)
403 m_fileLogger
= new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType
>(fileLoggerAgeType()));
406 m_storeFileLoggerEnabled
= value
;
409 Path
Application::fileLoggerPath() const
411 return m_storeFileLoggerPath
.get(specialFolderLocation(SpecialFolder::Data
) / Path(LOG_FOLDER
));
414 void Application::setFileLoggerPath(const Path
&path
)
417 m_fileLogger
->changePath(path
);
418 m_storeFileLoggerPath
= path
;
421 bool Application::isFileLoggerBackup() const
423 return m_storeFileLoggerBackup
.get(true);
426 void Application::setFileLoggerBackup(const bool value
)
429 m_fileLogger
->setBackup(value
);
430 m_storeFileLoggerBackup
= value
;
433 bool Application::isFileLoggerDeleteOld() const
435 return m_storeFileLoggerDeleteOld
.get(true);
438 void Application::setFileLoggerDeleteOld(const bool value
)
440 if (value
&& m_fileLogger
)
441 m_fileLogger
->deleteOld(fileLoggerAge(), static_cast<FileLogger::FileLogAgeType
>(fileLoggerAgeType()));
442 m_storeFileLoggerDeleteOld
= value
;
445 int Application::fileLoggerMaxSize() const
447 const int val
= m_storeFileLoggerMaxSize
.get(DEFAULT_FILELOG_SIZE
);
448 return std::clamp(val
, MIN_FILELOG_SIZE
, MAX_FILELOG_SIZE
);
451 void Application::setFileLoggerMaxSize(const int bytes
)
453 const int clampedValue
= std::clamp(bytes
, MIN_FILELOG_SIZE
, MAX_FILELOG_SIZE
);
455 m_fileLogger
->setMaxSize(clampedValue
);
456 m_storeFileLoggerMaxSize
= clampedValue
;
459 int Application::fileLoggerAge() const
461 const int val
= m_storeFileLoggerAge
.get(1);
462 return std::clamp(val
, 1, 365);
465 void Application::setFileLoggerAge(const int value
)
467 m_storeFileLoggerAge
= std::clamp(value
, 1, 365);
470 int Application::fileLoggerAgeType() const
472 const int val
= m_storeFileLoggerAgeType
.get(1);
473 return ((val
< 0) || (val
> 2)) ? 1 : val
;
476 void Application::setFileLoggerAgeType(const int value
)
478 m_storeFileLoggerAgeType
= ((value
< 0) || (value
> 2)) ? 1 : value
;
481 void Application::processMessage(const QString
&message
)
484 if (message
.isEmpty())
486 if (BitTorrent::Session::instance()->isRestored()) [[likely
]]
488 m_window
->activate(); // show UI
490 else if (m_startupProgressDialog
)
492 m_startupProgressDialog
->show();
493 m_startupProgressDialog
->activateWindow();
494 m_startupProgressDialog
->raise();
498 createStartupProgressDialog();
505 const QBtCommandLineParameters params
= parseParams(message
);
506 // If Application is not allowed to process params immediately
507 // (i.e., other components are not ready) store params
508 if (m_isProcessingParamsAllowed
)
509 processParams(params
);
511 m_paramsQueue
.append(params
);
514 void Application::runExternalProgram(const QString
&programTemplate
, const BitTorrent::Torrent
*torrent
) const
516 // Cannot give users shell environment by default, as doing so could
517 // enable command injection via torrent name and other arguments
518 // (especially when some automated download mechanism has been setup).
519 // See: https://github.com/qbittorrent/qBittorrent/issues/10925
521 const auto replaceVariables
= [torrent
](QString str
) -> QString
523 for (int i
= (str
.length() - 2); i
>= 0; --i
)
528 const ushort specifier
= str
[i
+ 1].unicode();
532 str
.replace(i
, 2, QString::number(torrent
->filesCount()));
535 str
.replace(i
, 2, torrent
->savePath().toString());
538 str
.replace(i
, 2, torrent
->contentPath().toString());
541 str
.replace(i
, 2, Utils::String::joinIntoString(torrent
->tags(), u
","_s
));
544 str
.replace(i
, 2, (torrent
->infoHash().v1().isValid() ? torrent
->infoHash().v1().toString() : u
"-"_s
));
547 str
.replace(i
, 2, (torrent
->infoHash().v2().isValid() ? torrent
->infoHash().v2().toString() : u
"-"_s
));
550 str
.replace(i
, 2, torrent
->id().toString());
553 str
.replace(i
, 2, torrent
->category());
556 str
.replace(i
, 2, torrent
->name());
559 str
.replace(i
, 2, torrent
->rootPath().toString());
562 str
.replace(i
, 2, torrent
->currentTracker());
565 str
.replace(i
, 2, QString::number(torrent
->totalSize()));
572 // decrement `i` to avoid unwanted replacement, example pattern: "%%N"
579 const QString logMsg
= tr("Running external program. Torrent: \"%1\". Command: `%2`");
580 const QString logMsgError
= tr("Failed to run external program. Torrent: \"%1\". Command: `%2`");
582 // The processing sequenece is different for Windows and other OS, this is intentional
583 #if defined(Q_OS_WIN)
584 const QString program
= replaceVariables(programTemplate
);
585 const std::wstring programWStr
= program
.toStdWString();
587 // Need to split arguments manually because QProcess::startDetached(QString)
588 // will strip off empty parameters.
589 // E.g. `python.exe "1" "" "3"` will become `python.exe "1" "3"`
591 std::unique_ptr
<LPWSTR
[], decltype(&::LocalFree
)> args
{::CommandLineToArgvW(programWStr
.c_str(), &argCount
), ::LocalFree
};
597 for (int i
= 1; i
< argCount
; ++i
)
598 argList
+= QString::fromWCharArray(args
[i
]);
601 proc
.setProgram(QString::fromWCharArray(args
[0]));
602 proc
.setArguments(argList
);
603 proc
.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments
*args
)
605 if (Preferences::instance()->isAutoRunConsoleEnabled())
607 args
->flags
|= CREATE_NEW_CONSOLE
;
608 args
->flags
&= ~(CREATE_NO_WINDOW
| DETACHED_PROCESS
);
612 args
->flags
|= CREATE_NO_WINDOW
;
613 args
->flags
&= ~(CREATE_NEW_CONSOLE
| DETACHED_PROCESS
);
615 args
->inheritHandles
= false;
616 args
->startupInfo
->dwFlags
&= ~STARTF_USESTDHANDLES
;
617 ::CloseHandle(args
->startupInfo
->hStdInput
);
618 ::CloseHandle(args
->startupInfo
->hStdOutput
);
619 ::CloseHandle(args
->startupInfo
->hStdError
);
620 args
->startupInfo
->hStdInput
= nullptr;
621 args
->startupInfo
->hStdOutput
= nullptr;
622 args
->startupInfo
->hStdError
= nullptr;
625 if (proc
.startDetached())
626 LogMsg(logMsg
.arg(torrent
->name(), program
));
628 LogMsg(logMsgError
.arg(torrent
->name(), program
));
630 QStringList args
= Utils::String::splitCommand(programTemplate
);
635 for (QString
&arg
: args
)
637 // strip redundant quotes
638 if (arg
.startsWith(u
'"') && arg
.endsWith(u
'"'))
639 arg
= arg
.mid(1, (arg
.size() - 2));
641 arg
= replaceVariables(arg
);
644 const QString command
= args
.takeFirst();
646 proc
.setProgram(command
);
647 proc
.setArguments(args
);
649 if (proc
.startDetached())
651 // show intended command in log
652 LogMsg(logMsg
.arg(torrent
->name(), replaceVariables(programTemplate
)));
656 // show intended command in log
657 LogMsg(logMsgError
.arg(torrent
->name(), replaceVariables(programTemplate
)));
662 void Application::sendNotificationEmail(const BitTorrent::Torrent
*torrent
)
664 // Prepare mail content
665 const QString content
= tr("Torrent name: %1").arg(torrent
->name()) + u
'\n'
666 + tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent
->wantedSize())) + u
'\n'
667 + tr("Save path: %1").arg(torrent
->savePath().toString()) + u
"\n\n"
668 + tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds")
669 .arg(Utils::Misc::userFriendlyDuration(torrent
->activeTime())) + u
"\n\n\n"
670 + tr("Thank you for using qBittorrent.") + u
'\n';
672 // Send the notification email
673 const Preferences
*pref
= Preferences::instance();
674 auto *smtp
= new Net::Smtp(this);
675 smtp
->sendMail(pref
->getMailNotificationSender(),
676 pref
->getMailNotificationEmail(),
677 tr("Torrent \"%1\" has finished downloading").arg(torrent
->name()),
681 void Application::sendTestEmail() const
683 const Preferences
*pref
= Preferences::instance();
684 if (pref
->isMailNotificationEnabled())
686 // Prepare mail content
687 const QString content
= tr("This is a test email.") + u
'\n'
688 + tr("Thank you for using qBittorrent.") + u
'\n';
690 // Send the notification email
691 auto *smtp
= new Net::Smtp();
692 smtp
->sendMail(pref
->getMailNotificationSender(),
693 pref
->getMailNotificationEmail(),
699 void Application::torrentAdded(const BitTorrent::Torrent
*torrent
) const
701 const Preferences
*pref
= Preferences::instance();
704 if (pref
->isAutoRunOnTorrentAddedEnabled())
705 runExternalProgram(pref
->getAutoRunOnTorrentAddedProgram().trimmed(), torrent
);
708 void Application::torrentFinished(const BitTorrent::Torrent
*torrent
)
710 const Preferences
*pref
= Preferences::instance();
713 if (pref
->isAutoRunOnTorrentFinishedEnabled())
714 runExternalProgram(pref
->getAutoRunOnTorrentFinishedProgram().trimmed(), torrent
);
717 if (pref
->isMailNotificationEnabled())
719 LogMsg(tr("Torrent: %1, sending mail notification").arg(torrent
->name()));
720 sendNotificationEmail(torrent
);
724 if (Preferences::instance()->isRecursiveDownloadEnabled())
726 // Check whether it contains .torrent files
727 for (const Path
&torrentRelpath
: asConst(torrent
->filePaths()))
729 if (torrentRelpath
.hasExtension(u
".torrent"_s
))
731 askRecursiveTorrentDownloadConfirmation(torrent
);
739 void Application::allTorrentsFinished()
741 Preferences
*const pref
= Preferences::instance();
742 bool isExit
= pref
->shutdownqBTWhenDownloadsComplete();
743 bool isShutdown
= pref
->shutdownWhenDownloadsComplete();
744 bool isSuspend
= pref
->suspendWhenDownloadsComplete();
745 bool isHibernate
= pref
->hibernateWhenDownloadsComplete();
747 bool haveAction
= isExit
|| isShutdown
|| isSuspend
|| isHibernate
;
748 if (!haveAction
) return;
750 ShutdownDialogAction action
= ShutdownDialogAction::Exit
;
752 action
= ShutdownDialogAction::Suspend
;
753 else if (isHibernate
)
754 action
= ShutdownDialogAction::Hibernate
;
756 action
= ShutdownDialogAction::Shutdown
;
760 if ((action
== ShutdownDialogAction::Exit
) && (pref
->dontConfirmAutoExit()))
762 // do nothing & skip confirm
766 if (!ShutdownConfirmDialog::askForConfirmation(m_window
, action
)) return;
768 #endif // DISABLE_GUI
770 // Actually shut down
771 if (action
!= ShutdownDialogAction::Exit
)
773 qDebug("Preparing for auto-shutdown because all downloads are complete!");
774 // Disabling it for next time
775 pref
->setShutdownWhenDownloadsComplete(false);
776 pref
->setSuspendWhenDownloadsComplete(false);
777 pref
->setHibernateWhenDownloadsComplete(false);
778 // Make sure preferences are synced before exiting
779 m_shutdownAct
= action
;
782 qDebug("Exiting the application");
786 bool Application::callMainInstance()
788 return m_instanceManager
->sendMessage(serializeParams(commandLineArgs()));
791 void Application::processParams(const QBtCommandLineParameters
¶ms
)
794 // There are two circumstances in which we want to show the torrent
795 // dialog. One is when the application settings specify that it should
796 // be shown and skipTorrentDialog is undefined. The other is when
797 // skipTorrentDialog is false, meaning that the application setting
798 // should be overridden.
799 AddTorrentOption addTorrentOption
= AddTorrentOption::Default
;
800 if (params
.skipDialog
.has_value())
801 addTorrentOption
= params
.skipDialog
.value() ? AddTorrentOption::SkipDialog
: AddTorrentOption::ShowDialog
;
802 for (const QString
&torrentSource
: params
.torrentSources
)
803 m_addTorrentManager
->addTorrent(torrentSource
, params
.addTorrentParams
, addTorrentOption
);
805 for (const QString
&torrentSource
: params
.torrentSources
)
806 m_addTorrentManager
->addTorrent(torrentSource
, params
.addTorrentParams
);
810 int Application::exec()
812 #if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI)
813 const QString loadingStr
= tr("WebUI will be started shortly after internal preparations. Please wait...");
814 printf("%s\n", qUtf8Printable(loadingStr
));
817 #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
818 applyMemoryWorkingSetLimit();
822 applyMemoryPriority();
823 adjustThreadPriority();
826 Net::ProxyConfigurationManager::initInstance();
827 Net::DownloadManager::initInstance();
829 BitTorrent::Session::initInstance();
831 UIThemeManager::initInstance();
833 m_desktopIntegration
= new DesktopIntegration
;
834 m_desktopIntegration
->setToolTip(tr("Loading torrents..."));
836 auto *desktopIntegrationMenu
= m_desktopIntegration
->menu();
837 auto *actionExit
= new QAction(tr("E&xit"), desktopIntegrationMenu
);
838 actionExit
->setIcon(UIThemeManager::instance()->getIcon(u
"application-exit"_s
));
839 actionExit
->setMenuRole(QAction::QuitRole
);
840 actionExit
->setShortcut(Qt::CTRL
| Qt::Key_Q
);
841 connect(actionExit
, &QAction::triggered
, this, []
843 QApplication::exit();
845 desktopIntegrationMenu
->addAction(actionExit
);
847 const bool isHidden
= m_desktopIntegration
->isActive() && (startUpWindowState() == WindowState::Hidden
);
849 const bool isHidden
= false;
854 createStartupProgressDialog();
855 // Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore.
856 m_startupProgressDialog
->setMinimumDuration(1000);
857 if (startUpWindowState() != WindowState::Normal
)
858 m_startupProgressDialog
->setWindowState(Qt::WindowMinimized
);
862 connect(m_desktopIntegration
, &DesktopIntegration::activationRequested
, this, &Application::createStartupProgressDialog
);
865 connect(BitTorrent::Session::instance(), &BitTorrent::Session::restored
, this, [this]()
867 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAdded
, this, &Application::torrentAdded
);
868 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished
, this, &Application::torrentFinished
);
869 connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished
, this, &Application::allTorrentsFinished
, Qt::QueuedConnection
);
871 m_addTorrentManager
= new AddTorrentManagerImpl(this, BitTorrent::Session::instance(), this);
873 Net::GeoIPManager::initInstance();
874 TorrentFilesWatcher::initInstance();
876 new RSS::Session
; // create RSS::Session singleton
877 new RSS::AutoDownloader(this); // create RSS::AutoDownloader singleton
880 const auto *btSession
= BitTorrent::Session::instance();
881 connect(btSession
, &BitTorrent::Session::fullDiskError
, this
882 , [this](const BitTorrent::Torrent
*torrent
, const QString
&msg
)
884 m_desktopIntegration
->showNotification(tr("I/O Error", "i.e: Input/Output Error")
885 , tr("An I/O error occurred for torrent '%1'.\n Reason: %2"
886 , "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent
->name(), msg
));
888 connect(btSession
, &BitTorrent::Session::torrentFinished
, this
889 , [this](const BitTorrent::Torrent
*torrent
)
891 m_desktopIntegration
->showNotification(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent
->name()));
893 connect(m_addTorrentManager
, &AddTorrentManager::torrentAdded
, this
894 , [this]([[maybe_unused
]] const QString
&source
, const BitTorrent::Torrent
*torrent
)
896 if (isTorrentAddedNotificationsEnabled())
897 m_desktopIntegration
->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent
->name()));
899 connect(m_addTorrentManager
, &AddTorrentManager::addTorrentFailed
, this
900 , [this](const QString
&source
, const QString
&reason
)
902 m_desktopIntegration
->showNotification(tr("Add torrent failed")
903 , tr("Couldn't add torrent '%1', reason: %2.").arg(source
, reason
));
906 disconnect(m_desktopIntegration
, &DesktopIntegration::activationRequested
, this, &Application::createStartupProgressDialog
);
908 const WindowState windowState
= !m_startupProgressDialog
? WindowState::Hidden
909 : (m_startupProgressDialog
->windowState() & Qt::WindowMinimized
) ? WindowState::Minimized
910 : WindowState::Normal
;
912 const WindowState windowState
= (m_startupProgressDialog
->windowState() & Qt::WindowMinimized
)
913 ? WindowState::Minimized
: WindowState::Normal
;
915 m_window
= new MainWindow(this, windowState
, instanceName());
917 delete m_startupProgressDialog
;
918 #endif // DISABLE_GUI
920 #ifndef DISABLE_WEBUI
922 m_webui
= new WebUI(this);
924 const auto *pref
= Preferences::instance();
926 const QString tempPassword
= pref
->getWebUIPassword().isEmpty()
927 ? Utils::Password::generate() : QString();
928 m_webui
= new WebUI(this, (!tempPassword
.isEmpty() ? Utils::Password::PBKDF2::generate(tempPassword
) : QByteArray()));
929 connect(m_webui
, &WebUI::error
, this, [](const QString
&message
)
931 fprintf(stderr
, "WebUI configuration failed. Reason: %s\n", qUtf8Printable(message
));
934 printf("%s", qUtf8Printable(u
"\n******** %1 ********\n"_s
.arg(tr("Information"))));
936 if (m_webui
->isErrored())
938 const QString error
= m_webui
->errorMessage() + u
'\n'
939 + tr("To fix the error, you may need to edit the config file manually.");
940 fprintf(stderr
, "%s\n", qUtf8Printable(error
));
942 else if (m_webui
->isEnabled())
944 const QHostAddress address
= m_webui
->hostAddress();
945 const QString url
= u
"%1://%2:%3"_s
.arg((m_webui
->isHttps() ? u
"https"_s
: u
"http"_s
)
946 , (address
.isEqual(QHostAddress::Any
, QHostAddress::ConvertUnspecifiedAddress
) ? u
"localhost"_s
: address
.toString())
947 , QString::number(m_webui
->port()));
948 printf("%s\n", qUtf8Printable(tr("To control qBittorrent, access the WebUI at: %1").arg(url
)));
950 if (!tempPassword
.isEmpty())
952 const QString warning
= tr("The WebUI administrator username is: %1").arg(pref
->getWebUIUsername()) + u
'\n'
953 + tr("The WebUI administrator password was not set. A temporary password is provided for this session: %1").arg(tempPassword
) + u
'\n'
954 + tr("You should set your own password in program preferences.") + u
'\n';
955 printf("%s", qUtf8Printable(warning
));
960 printf("%s\n", qUtf8Printable(tr("The WebUI is disabled! To enable the WebUI, edit the config file manually.")));
962 #endif // DISABLE_GUI
963 #endif // DISABLE_WEBUI
965 m_isProcessingParamsAllowed
= true;
966 for (const QBtCommandLineParameters
¶ms
: m_paramsQueue
)
967 processParams(params
);
968 m_paramsQueue
.clear();
971 const QBtCommandLineParameters params
= commandLineArgs();
972 if (!params
.torrentSources
.isEmpty())
973 m_paramsQueue
.append(params
);
975 return BaseApplication::exec();
978 bool Application::hasAnotherInstance() const
980 return !m_instanceManager
->isFirstInstance();
984 void Application::createStartupProgressDialog()
986 Q_ASSERT(!m_startupProgressDialog
);
987 Q_ASSERT(m_desktopIntegration
);
989 disconnect(m_desktopIntegration
, &DesktopIntegration::activationRequested
, this, &Application::createStartupProgressDialog
);
991 m_startupProgressDialog
= new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100);
992 m_startupProgressDialog
->setAttribute(Qt::WA_DeleteOnClose
);
993 m_startupProgressDialog
->setWindowFlag(Qt::WindowMinimizeButtonHint
);
994 m_startupProgressDialog
->setMinimumDuration(0); // Show dialog immediately by default
995 m_startupProgressDialog
->setAutoReset(false);
996 m_startupProgressDialog
->setAutoClose(false);
998 connect(m_startupProgressDialog
, &QProgressDialog::canceled
, this, []()
1000 QApplication::exit();
1003 connect(BitTorrent::Session::instance(), &BitTorrent::Session::startupProgressUpdated
, m_startupProgressDialog
, &QProgressDialog::setValue
);
1005 connect(m_desktopIntegration
, &DesktopIntegration::activationRequested
, m_startupProgressDialog
, [this]()
1008 if (!m_startupProgressDialog
->isVisible())
1010 m_startupProgressDialog
->show();
1011 m_startupProgressDialog
->activateWindow();
1012 m_startupProgressDialog
->raise();
1015 if (m_startupProgressDialog
->isHidden())
1017 // Make sure the window is not minimized
1018 m_startupProgressDialog
->setWindowState((m_startupProgressDialog
->windowState() & ~Qt::WindowMinimized
) | Qt::WindowActive
);
1021 m_startupProgressDialog
->show();
1022 m_startupProgressDialog
->raise();
1023 m_startupProgressDialog
->activateWindow();
1027 m_startupProgressDialog
->hide();
1033 void Application::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent
*torrent
)
1035 const auto torrentID
= torrent
->id();
1037 QMessageBox
*confirmBox
= new QMessageBox(QMessageBox::Question
, tr("Recursive download confirmation")
1038 , tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent
->name())
1039 , (QMessageBox::Yes
| QMessageBox::No
| QMessageBox::NoToAll
), mainWindow());
1040 confirmBox
->setAttribute(Qt::WA_DeleteOnClose
);
1042 const QAbstractButton
*yesButton
= confirmBox
->button(QMessageBox::Yes
);
1043 QAbstractButton
*neverButton
= confirmBox
->button(QMessageBox::NoToAll
);
1044 neverButton
->setText(tr("Never"));
1046 connect(confirmBox
, &QMessageBox::buttonClicked
, this
1047 , [this, torrentID
, yesButton
, neverButton
](const QAbstractButton
*button
)
1049 if (button
== yesButton
)
1051 recursiveTorrentDownload(torrentID
);
1053 else if (button
== neverButton
)
1055 Preferences::instance()->setRecursiveDownloadEnabled(false);
1061 void Application::recursiveTorrentDownload(const BitTorrent::TorrentID
&torrentID
)
1063 const BitTorrent::Torrent
*torrent
= BitTorrent::Session::instance()->getTorrent(torrentID
);
1067 for (const Path
&torrentRelpath
: asConst(torrent
->filePaths()))
1069 if (torrentRelpath
.hasExtension(u
".torrent"_s
))
1071 const Path torrentFullpath
= torrent
->savePath() / torrentRelpath
;
1073 LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
1074 .arg(torrent
->name(), torrentFullpath
.toString()));
1076 BitTorrent::AddTorrentParams params
;
1077 // Passing the save path along to the sub torrent file
1078 params
.savePath
= torrent
->savePath();
1079 addTorrentManager()->addTorrent(torrentFullpath
.data(), params
, AddTorrentOption::SkipDialog
);
1085 bool Application::event(QEvent
*ev
)
1087 if (ev
->type() == QEvent::FileOpen
)
1089 QString path
= static_cast<QFileOpenEvent
*>(ev
)->file();
1091 // Get the url instead
1092 path
= static_cast<QFileOpenEvent
*>(ev
)->url().toString();
1093 qDebug("Received a mac file open event: %s", qUtf8Printable(path
));
1095 QBtCommandLineParameters params
;
1096 params
.torrentSources
.append(path
);
1097 // If Application is not allowed to process params immediately
1098 // (i.e., other components are not ready) store params
1099 if (m_isProcessingParamsAllowed
)
1100 processParams(params
);
1102 m_paramsQueue
.append(params
);
1107 return BaseApplication::event(ev
);
1109 #endif // Q_OS_MACOS
1110 #endif // DISABLE_GUI
1112 void Application::initializeTranslation()
1114 Preferences
*const pref
= Preferences::instance();
1116 const QString localeStr
= pref
->getLocale();
1118 if (m_qtTranslator
.load((u
"qtbase_" + localeStr
), QLibraryInfo::path(QLibraryInfo::TranslationsPath
))
1119 || m_qtTranslator
.load((u
"qt_" + localeStr
), QLibraryInfo::path(QLibraryInfo::TranslationsPath
)))
1121 qDebug("Qt %s locale recognized, using translation.", qUtf8Printable(localeStr
));
1125 qDebug("Qt %s locale unrecognized, using default (en).", qUtf8Printable(localeStr
));
1128 installTranslator(&m_qtTranslator
);
1130 if (m_translator
.load(u
":/lang/qbittorrent_" + localeStr
))
1131 qDebug("%s locale recognized, using translation.", qUtf8Printable(localeStr
));
1133 qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(localeStr
));
1134 installTranslator(&m_translator
);
1137 if (localeStr
.startsWith(u
"ar") || localeStr
.startsWith(u
"he"))
1139 qDebug("Right to Left mode");
1140 setLayoutDirection(Qt::RightToLeft
);
1144 setLayoutDirection(Qt::LeftToRight
);
1149 #if (!defined(DISABLE_GUI) && defined(Q_OS_WIN))
1150 void Application::shutdownCleanup([[maybe_unused
]] QSessionManager
&manager
)
1152 // This is only needed for a special case on Windows XP.
1153 // (but is called for every Windows version)
1154 // If a process takes too much time to exit during OS
1155 // shutdown, the OS presents a dialog to the user.
1156 // That dialog tells the user that qbt is blocking the
1157 // shutdown, it shows a progress bar and it offers
1158 // a "Terminate Now" button for the user. However,
1159 // after the progress bar has reached 100% another button
1160 // is offered to the user reading "Cancel". With this the
1161 // user can cancel the **OS** shutdown. If we don't do
1162 // the cleanup by handling the commitDataRequest() signal
1163 // and the user clicks "Cancel", it will result in qbt being
1164 // killed and the shutdown proceeding instead. Apparently
1165 // aboutToQuit() is emitted too late in the shutdown process.
1168 // According to the qt docs we shouldn't call quit() inside a slot.
1169 // aboutToQuit() is never emitted if the user hits "Cancel" in
1170 // the above dialog.
1171 QMetaObject::invokeMethod(qApp
, &QCoreApplication::quit
, Qt::QueuedConnection
);
1175 #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
1176 void Application::applyMemoryWorkingSetLimit() const
1178 const size_t MiB
= 1024 * 1024;
1179 const QString logMessage
= tr("Failed to set physical memory (RAM) usage limit. Error code: %1. Error message: \"%2\"");
1182 const SIZE_T maxSize
= memoryWorkingSetLimit() * MiB
;
1183 const auto minSize
= std::min
<SIZE_T
>((64 * MiB
), (maxSize
/ 2));
1184 if (!::SetProcessWorkingSetSizeEx(::GetCurrentProcess(), minSize
, maxSize
, QUOTA_LIMITS_HARDWS_MAX_ENABLE
))
1186 const DWORD errorCode
= ::GetLastError();
1188 LPVOID lpMsgBuf
= nullptr;
1189 const DWORD msgLength
= ::FormatMessageW((FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS
)
1190 , nullptr, errorCode
, LANG_USER_DEFAULT
, reinterpret_cast<LPWSTR
>(&lpMsgBuf
), 0, nullptr);
1193 message
= QString::fromWCharArray(reinterpret_cast<LPWSTR
>(lpMsgBuf
)).trimmed();
1194 ::LocalFree(lpMsgBuf
);
1196 LogMsg(logMessage
.arg(QString::number(errorCode
), message
), Log::WARNING
);
1198 #elif defined(Q_OS_UNIX)
1199 // has no effect on linux but it might be meaningful for other OS
1202 if (::getrlimit(RLIMIT_RSS
, &limit
) != 0)
1205 const size_t newSize
= memoryWorkingSetLimit() * MiB
;
1206 if (newSize
> limit
.rlim_max
)
1208 // try to raise the hard limit
1209 rlimit newLimit
= limit
;
1210 newLimit
.rlim_max
= newSize
;
1211 if (::setrlimit(RLIMIT_RSS
, &newLimit
) != 0)
1213 const auto message
= QString::fromLocal8Bit(strerror(errno
));
1214 LogMsg(tr("Failed to set physical memory (RAM) usage hard limit. Requested size: %1. System hard limit: %2. Error code: %3. Error message: \"%4\"")
1215 .arg(QString::number(newSize
), QString::number(limit
.rlim_max
), QString::number(errno
), message
), Log::WARNING
);
1220 limit
.rlim_cur
= newSize
;
1221 if (::setrlimit(RLIMIT_RSS
, &limit
) != 0)
1223 const auto message
= QString::fromLocal8Bit(strerror(errno
));
1224 LogMsg(logMessage
.arg(QString::number(errno
), message
), Log::WARNING
);
1231 MemoryPriority
Application::processMemoryPriority() const
1233 return m_processMemoryPriority
.get(MemoryPriority::BelowNormal
);
1236 void Application::setProcessMemoryPriority(const MemoryPriority priority
)
1238 if (processMemoryPriority() == priority
)
1241 m_processMemoryPriority
= priority
;
1242 applyMemoryPriority();
1245 void Application::applyMemoryPriority() const
1247 using SETPROCESSINFORMATION
= BOOL (WINAPI
*)(HANDLE
, PROCESS_INFORMATION_CLASS
, LPVOID
, DWORD
);
1248 const auto setProcessInformation
= Utils::OS::loadWinAPI
<SETPROCESSINFORMATION
>(u
"Kernel32.dll"_s
, "SetProcessInformation");
1249 if (!setProcessInformation
) // only available on Windows >= 8
1252 using SETTHREADINFORMATION
= BOOL (WINAPI
*)(HANDLE
, THREAD_INFORMATION_CLASS
, LPVOID
, DWORD
);
1253 const auto setThreadInformation
= Utils::OS::loadWinAPI
<SETTHREADINFORMATION
>(u
"Kernel32.dll"_s
, "SetThreadInformation");
1254 if (!setThreadInformation
) // only available on Windows >= 8
1257 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
1258 // this dummy struct is required to compile successfully when targeting older Windows version
1259 struct MEMORY_PRIORITY_INFORMATION
1261 ULONG MemoryPriority
;
1264 #define MEMORY_PRIORITY_LOWEST 0
1265 #define MEMORY_PRIORITY_VERY_LOW 1
1266 #define MEMORY_PRIORITY_LOW 2
1267 #define MEMORY_PRIORITY_MEDIUM 3
1268 #define MEMORY_PRIORITY_BELOW_NORMAL 4
1269 #define MEMORY_PRIORITY_NORMAL 5
1272 MEMORY_PRIORITY_INFORMATION prioInfo
{};
1273 switch (processMemoryPriority())
1275 case MemoryPriority::Normal
:
1277 prioInfo
.MemoryPriority
= MEMORY_PRIORITY_NORMAL
;
1279 case MemoryPriority::BelowNormal
:
1280 prioInfo
.MemoryPriority
= MEMORY_PRIORITY_BELOW_NORMAL
;
1282 case MemoryPriority::Medium
:
1283 prioInfo
.MemoryPriority
= MEMORY_PRIORITY_MEDIUM
;
1285 case MemoryPriority::Low
:
1286 prioInfo
.MemoryPriority
= MEMORY_PRIORITY_LOW
;
1288 case MemoryPriority::VeryLow
:
1289 prioInfo
.MemoryPriority
= MEMORY_PRIORITY_VERY_LOW
;
1292 setProcessInformation(::GetCurrentProcess(), ProcessMemoryPriority
, &prioInfo
, sizeof(prioInfo
));
1294 // To avoid thrashing/sluggishness of the app, set "main event loop" thread to normal memory priority
1295 // which is higher/equal than other threads
1296 prioInfo
.MemoryPriority
= MEMORY_PRIORITY_NORMAL
;
1297 setThreadInformation(::GetCurrentThread(), ThreadMemoryPriority
, &prioInfo
, sizeof(prioInfo
));
1300 void Application::adjustThreadPriority() const
1302 // Workaround for improving responsiveness of qbt when CPU resources are scarce.
1303 // Raise main event loop thread to be just one level higher than libtorrent threads.
1304 // Also note that on *nix platforms there is no easy way to achieve it,
1305 // so implementation is omitted.
1307 ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL
);
1311 void Application::cleanup()
1313 // cleanup() can be called multiple times during shutdown. We only need it once.
1314 if (m_isCleanupRun
.exchange(true, std::memory_order_acquire
))
1317 LogMsg(tr("qBittorrent termination initiated"));
1320 if (m_desktopIntegration
)
1322 m_desktopIntegration
->disconnect();
1323 m_desktopIntegration
->setToolTip(tr("qBittorrent is shutting down..."));
1324 if (m_desktopIntegration
->menu())
1325 m_desktopIntegration
->menu()->setEnabled(false);
1330 // Hide the window and don't leave it on screen as
1331 // unresponsive. Also for Windows take the WinId
1332 // after it's hidden, because hide() may cause a
1337 const std::wstring msg
= tr("Saving torrent progress...").toStdWString();
1338 ::ShutdownBlockReasonCreate(reinterpret_cast<HWND
>(m_window
->effectiveWinId())
1342 // Do manual cleanup in MainWindow to force widgets
1343 // to save their Preferences, stop all timers and
1344 // delete as many widgets as possible to leave only
1345 // a 'shell' MainWindow.
1346 // We need a valid window handle for Windows Vista+
1347 // otherwise the system shutdown will continue even
1348 // though we created a ShutdownBlockReason
1349 m_window
->cleanup();
1351 #endif // DISABLE_GUI
1353 #ifndef DISABLE_WEBUI
1357 delete RSS::AutoDownloader::instance();
1358 delete RSS::Session::instance();
1360 TorrentFilesWatcher::freeInstance();
1361 delete m_addTorrentManager
;
1362 BitTorrent::Session::freeInstance();
1363 Net::GeoIPManager::freeInstance();
1364 Net::DownloadManager::freeInstance();
1365 Net::ProxyConfigurationManager::freeInstance();
1366 Preferences::freeInstance();
1367 SettingsStorage::freeInstance();
1368 SearchPluginManager::freeInstance();
1369 Utils::Fs::removeDirRecursively(Utils::Fs::tempPath());
1371 LogMsg(tr("qBittorrent is now ready to exit"));
1372 Logger::freeInstance();
1373 delete m_fileLogger
;
1379 ::ShutdownBlockReasonDestroy(reinterpret_cast<HWND
>(m_window
->effectiveWinId()));
1382 delete m_desktopIntegration
;
1383 UIThemeManager::freeInstance();
1385 #endif // DISABLE_GUI
1387 Profile::freeInstance();
1389 if (m_shutdownAct
!= ShutdownDialogAction::Exit
)
1391 qDebug() << "Sending computer shutdown/suspend/hibernate signal...";
1392 Utils::OS::shutdownComputer(m_shutdownAct
);
1396 AddTorrentManagerImpl
*Application::addTorrentManager() const
1398 return m_addTorrentManager
;
1401 #ifndef DISABLE_WEBUI
1402 WebUI
*Application::webUI() const