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"
36 #include <QLibraryInfo>
47 #include <QMessageBox>
49 #include <QSessionManager>
50 #include <QSharedMemory>
53 #include <QFileOpenEvent>
55 #include "addnewtorrentdialog.h"
56 #include "gui/guiiconprovider.h"
57 #include "mainwindow.h"
58 #include "shutdownconfirmdialog.h"
63 #include "base/bittorrent/session.h"
64 #include "base/bittorrent/torrenthandle.h"
65 #include "base/exceptions.h"
66 #include "base/iconprovider.h"
67 #include "base/logger.h"
68 #include "base/net/downloadmanager.h"
69 #include "base/net/geoipmanager.h"
70 #include "base/net/proxyconfigurationmanager.h"
71 #include "base/net/smtp.h"
72 #include "base/preferences.h"
73 #include "base/profile.h"
74 #include "base/rss/rss_autodownloader.h"
75 #include "base/rss/rss_session.h"
76 #include "base/scanfoldersmodel.h"
77 #include "base/search/searchpluginmanager.h"
78 #include "base/settingsstorage.h"
79 #include "base/utils/fs.h"
80 #include "base/utils/misc.h"
81 #include "base/utils/string.h"
82 #include "filelogger.h"
85 #include "webui/webui.h"
90 #define SETTINGS_KEY(name) "Application/" name
92 // FileLogger properties keys
93 #define FILELOGGER_SETTINGS_KEY(name) QStringLiteral(SETTINGS_KEY("FileLogger/") name)
94 const QString KEY_FILELOGGER_ENABLED
= FILELOGGER_SETTINGS_KEY("Enabled");
95 const QString KEY_FILELOGGER_PATH
= FILELOGGER_SETTINGS_KEY("Path");
96 const QString KEY_FILELOGGER_BACKUP
= FILELOGGER_SETTINGS_KEY("Backup");
97 const QString KEY_FILELOGGER_DELETEOLD
= FILELOGGER_SETTINGS_KEY("DeleteOld");
98 const QString KEY_FILELOGGER_MAXSIZEBYTES
= FILELOGGER_SETTINGS_KEY("MaxSizeBytes");
99 const QString KEY_FILELOGGER_AGE
= FILELOGGER_SETTINGS_KEY("Age");
100 const QString KEY_FILELOGGER_AGETYPE
= FILELOGGER_SETTINGS_KEY("AgeType");
103 inline SettingsStorage
*settings() { return SettingsStorage::instance(); }
105 const QString LOG_FOLDER
= QStringLiteral("logs");
106 const QChar PARAMS_SEPARATOR
= '|';
108 const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR
= QStringLiteral("profile");
110 const int MIN_FILELOG_SIZE
= 1024; // 1KiB
111 const int MAX_FILELOG_SIZE
= 1000 * 1024 * 1024; // 1000MiB
112 const int DEFAULT_FILELOG_SIZE
= 65 * 1024; // 65KiB
115 Application::Application(const QString
&id
, int &argc
, char **argv
)
116 : BaseApplication(id
, argc
, argv
)
118 , m_shutdownAct(ShutdownDialogAction::Exit
)
119 , m_commandLineArgs(parseCommandLine(this->arguments()))
120 #ifndef DISABLE_WEBUI
124 qRegisterMetaType
<Log::Msg
>("Log::Msg");
126 setApplicationName("qBittorrent");
127 validateCommandLineParameters();
129 QString profileDir
= m_commandLineArgs
.portableMode
130 ? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR
)
131 : m_commandLineArgs
.profileDir
;
133 Profile::initialize(profileDir
, m_commandLineArgs
.configurationName
,
134 m_commandLineArgs
.relativeFastresumePaths
|| m_commandLineArgs
.portableMode
);
136 Logger::initInstance();
137 SettingsStorage::initInstance();
138 Preferences::initInstance();
140 if (m_commandLineArgs
.webUiPort
> 0) // it will be -1 when user did not set any value
141 Preferences::instance()->setWebUiPort(m_commandLineArgs
.webUiPort
);
143 initializeTranslation();
145 #if !defined(DISABLE_GUI)
146 setAttribute(Qt::AA_UseHighDpiPixmaps
, true); // opt-in to the high DPI pixmap support
147 setQuitOnLastWindowClosed(false);
150 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
151 connect(this, &QGuiApplication::commitDataRequest
, this, &Application::shutdownCleanup
, Qt::DirectConnection
);
154 connect(this, &Application::messageReceived
, this, &Application::processMessage
);
155 connect(this, &QCoreApplication::aboutToQuit
, this, &Application::cleanup
);
157 if (isFileLoggerEnabled())
158 m_fileLogger
= new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType
>(fileLoggerAgeType()));
160 Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION
));
163 Application::~Application()
165 // we still need to call cleanup()
166 // in case the App failed to start
171 QPointer
<MainWindow
> Application::mainWindow()
177 const QBtCommandLineParameters
&Application::commandLineArgs() const
179 return m_commandLineArgs
;
182 bool Application::isFileLoggerEnabled() const
184 return settings()->loadValue(KEY_FILELOGGER_ENABLED
, true).toBool();
187 void Application::setFileLoggerEnabled(bool value
)
189 if (value
&& !m_fileLogger
)
190 m_fileLogger
= new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType
>(fileLoggerAgeType()));
193 settings()->storeValue(KEY_FILELOGGER_ENABLED
, value
);
196 QString
Application::fileLoggerPath() const
198 return settings()->loadValue(KEY_FILELOGGER_PATH
,
199 QVariant(specialFolderLocation(SpecialFolder::Data
) + LOG_FOLDER
)).toString();
202 void Application::setFileLoggerPath(const QString
&path
)
205 m_fileLogger
->changePath(path
);
206 settings()->storeValue(KEY_FILELOGGER_PATH
, path
);
209 bool Application::isFileLoggerBackup() const
211 return settings()->loadValue(KEY_FILELOGGER_BACKUP
, true).toBool();
214 void Application::setFileLoggerBackup(bool value
)
217 m_fileLogger
->setBackup(value
);
218 settings()->storeValue(KEY_FILELOGGER_BACKUP
, value
);
221 bool Application::isFileLoggerDeleteOld() const
223 return settings()->loadValue(KEY_FILELOGGER_DELETEOLD
, true).toBool();
226 void Application::setFileLoggerDeleteOld(bool value
)
228 if (value
&& m_fileLogger
)
229 m_fileLogger
->deleteOld(fileLoggerAge(), static_cast<FileLogger::FileLogAgeType
>(fileLoggerAgeType()));
230 settings()->storeValue(KEY_FILELOGGER_DELETEOLD
, value
);
233 int Application::fileLoggerMaxSize() const
235 int val
= settings()->loadValue(KEY_FILELOGGER_MAXSIZEBYTES
, DEFAULT_FILELOG_SIZE
).toInt();
236 return std::min(std::max(val
, MIN_FILELOG_SIZE
), MAX_FILELOG_SIZE
);
239 void Application::setFileLoggerMaxSize(const int bytes
)
241 int clampedValue
= std::min(std::max(bytes
, MIN_FILELOG_SIZE
), MAX_FILELOG_SIZE
);
243 m_fileLogger
->setMaxSize(clampedValue
);
244 settings()->storeValue(KEY_FILELOGGER_MAXSIZEBYTES
, clampedValue
);
247 int Application::fileLoggerAge() const
249 int val
= settings()->loadValue(KEY_FILELOGGER_AGE
, 1).toInt();
250 return std::min(std::max(val
, 1), 365);
253 void Application::setFileLoggerAge(const int value
)
255 settings()->storeValue(KEY_FILELOGGER_AGE
, std::min(std::max(value
, 1), 365));
258 int Application::fileLoggerAgeType() const
260 int val
= settings()->loadValue(KEY_FILELOGGER_AGETYPE
, 1).toInt();
261 return ((val
< 0) || (val
> 2)) ? 1 : val
;
264 void Application::setFileLoggerAgeType(const int value
)
266 settings()->storeValue(KEY_FILELOGGER_AGETYPE
, ((value
< 0) || (value
> 2)) ? 1 : value
);
269 void Application::processMessage(const QString
&message
)
271 QStringList params
= message
.split(PARAMS_SEPARATOR
, QString::SkipEmptyParts
);
272 // If Application is not running (i.e., other
273 // components are not ready) store params
275 processParams(params
);
277 m_paramsQueue
.append(params
);
280 void Application::runExternalProgram(const BitTorrent::TorrentHandle
*torrent
) const
282 QString program
= Preferences::instance()->getAutoRunProgram().trimmed();
283 program
.replace("%N", torrent
->name());
284 program
.replace("%L", torrent
->category());
286 QStringList tags
= torrent
->tags().toList();
287 std::sort(tags
.begin(), tags
.end(), Utils::String::naturalLessThan
<Qt::CaseInsensitive
>);
288 program
.replace("%G", tags
.join(','));
290 #if defined(Q_OS_WIN)
291 const auto chopPathSep
= [](const QString
&str
) -> QString
293 if (str
.endsWith('\\'))
294 return str
.mid(0, (str
.length() -1));
297 program
.replace("%F", chopPathSep(Utils::Fs::toNativePath(torrent
->contentPath())));
298 program
.replace("%R", chopPathSep(Utils::Fs::toNativePath(torrent
->rootPath())));
299 program
.replace("%D", chopPathSep(Utils::Fs::toNativePath(torrent
->savePath())));
301 program
.replace("%F", Utils::Fs::toNativePath(torrent
->contentPath()));
302 program
.replace("%R", Utils::Fs::toNativePath(torrent
->rootPath()));
303 program
.replace("%D", Utils::Fs::toNativePath(torrent
->savePath()));
305 program
.replace("%C", QString::number(torrent
->filesCount()));
306 program
.replace("%Z", QString::number(torrent
->totalSize()));
307 program
.replace("%T", torrent
->currentTracker());
308 program
.replace("%I", torrent
->hash());
310 Logger
*logger
= Logger::instance();
311 logger
->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent
->name(), program
));
313 #if defined(Q_OS_WIN)
314 std::unique_ptr
<wchar_t[]> programWchar(new wchar_t[program
.length() + 1] {});
315 program
.toWCharArray(programWchar
.get());
317 // Need to split arguments manually because QProcess::startDetached(QString)
318 // will strip off empty parameters.
319 // E.g. `python.exe "1" "" "3"` will become `python.exe "1" "3"`
321 LPWSTR
*args
= ::CommandLineToArgvW(programWchar
.get(), &argCount
);
324 for (int i
= 1; i
< argCount
; ++i
)
325 argList
+= QString::fromWCharArray(args
[i
]);
327 QProcess::startDetached(QString::fromWCharArray(args
[0]), argList
);
331 QProcess::startDetached(QLatin1String("/bin/sh"), {QLatin1String("-c"), program
});
335 void Application::sendNotificationEmail(const BitTorrent::TorrentHandle
*torrent
)
337 // Prepare mail content
338 const QString content
= tr("Torrent name: %1").arg(torrent
->name()) + '\n'
339 + tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent
->wantedSize())) + '\n'
340 + tr("Save path: %1").arg(torrent
->savePath()) + "\n\n"
341 + tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds")
342 .arg(Utils::Misc::userFriendlyDuration(torrent
->activeTime())) + "\n\n\n"
343 + tr("Thank you for using qBittorrent.") + '\n';
345 // Send the notification email
346 const Preferences
*pref
= Preferences::instance();
347 Net::Smtp
*smtp
= new Net::Smtp(this);
348 smtp
->sendMail(pref
->getMailNotificationSender(),
349 pref
->getMailNotificationEmail(),
350 tr("[qBittorrent] '%1' has finished downloading").arg(torrent
->name()),
354 void Application::torrentFinished(BitTorrent::TorrentHandle
*const torrent
)
356 Preferences
*const pref
= Preferences::instance();
359 if (pref
->isAutoRunEnabled())
360 runExternalProgram(torrent
);
363 if (pref
->isMailNotificationEnabled()) {
364 Logger::instance()->addMessage(tr("Torrent: %1, sending mail notification").arg(torrent
->name()));
365 sendNotificationEmail(torrent
);
369 void Application::allTorrentsFinished()
371 Preferences
*const pref
= Preferences::instance();
372 bool isExit
= pref
->shutdownqBTWhenDownloadsComplete();
373 bool isShutdown
= pref
->shutdownWhenDownloadsComplete();
374 bool isSuspend
= pref
->suspendWhenDownloadsComplete();
375 bool isHibernate
= pref
->hibernateWhenDownloadsComplete();
377 bool haveAction
= isExit
|| isShutdown
|| isSuspend
|| isHibernate
;
378 if (!haveAction
) return;
380 ShutdownDialogAction action
= ShutdownDialogAction::Exit
;
382 action
= ShutdownDialogAction::Suspend
;
383 else if (isHibernate
)
384 action
= ShutdownDialogAction::Hibernate
;
386 action
= ShutdownDialogAction::Shutdown
;
390 if ((action
== ShutdownDialogAction::Exit
) && (pref
->dontConfirmAutoExit())) {
391 // do nothing & skip confirm
394 if (!ShutdownConfirmDialog::askForConfirmation(m_window
, action
)) return;
396 #endif // DISABLE_GUI
398 // Actually shut down
399 if (action
!= ShutdownDialogAction::Exit
) {
400 qDebug("Preparing for auto-shutdown because all downloads are complete!");
401 // Disabling it for next time
402 pref
->setShutdownWhenDownloadsComplete(false);
403 pref
->setSuspendWhenDownloadsComplete(false);
404 pref
->setHibernateWhenDownloadsComplete(false);
405 // Make sure preferences are synced before exiting
406 m_shutdownAct
= action
;
409 qDebug("Exiting the application");
413 bool Application::sendParams(const QStringList
¶ms
)
415 return sendMessage(params
.join(PARAMS_SEPARATOR
));
418 // As program parameters, we can get paths or urls.
419 // This function parse the parameters and call
420 // the right addTorrent function, considering
421 // the parameter type.
422 void Application::processParams(const QStringList
¶ms
)
425 if (params
.isEmpty()) {
426 m_window
->activate(); // show UI
430 BitTorrent::AddTorrentParams torrentParams
;
431 TriStateBool skipTorrentDialog
;
433 for (QString param
: params
) {
434 param
= param
.trimmed();
436 // Process strings indicating options specified by the user.
438 if (param
.startsWith(QLatin1String("@savePath="))) {
439 torrentParams
.savePath
= param
.mid(10);
443 if (param
.startsWith(QLatin1String("@addPaused="))) {
444 torrentParams
.addPaused
= param
.midRef(11).toInt() ? TriStateBool::True
: TriStateBool::False
;
448 if (param
== QLatin1String("@skipChecking")) {
449 torrentParams
.skipChecking
= true;
453 if (param
.startsWith(QLatin1String("@category="))) {
454 torrentParams
.category
= param
.mid(10);
458 if (param
== QLatin1String("@sequential")) {
459 torrentParams
.sequential
= true;
463 if (param
== QLatin1String("@firstLastPiecePriority")) {
464 torrentParams
.firstLastPiecePriority
= true;
468 if (param
.startsWith(QLatin1String("@skipDialog="))) {
469 skipTorrentDialog
= param
.midRef(12).toInt() ? TriStateBool::True
: TriStateBool::False
;
474 // There are two circumstances in which we want to show the torrent
475 // dialog. One is when the application settings specify that it should
476 // be shown and skipTorrentDialog is undefined. The other is when
477 // skipTorrentDialog is false, meaning that the application setting
478 // should be overridden.
479 const bool showDialogForThisTorrent
=
480 ((AddNewTorrentDialog::isEnabled() && skipTorrentDialog
== TriStateBool::Undefined
)
481 || skipTorrentDialog
== TriStateBool::False
);
482 if (showDialogForThisTorrent
)
483 AddNewTorrentDialog::show(param
, torrentParams
, m_window
);
486 BitTorrent::Session::instance()->addTorrent(param
, torrentParams
);
490 int Application::exec(const QStringList
¶ms
)
492 Net::ProxyConfigurationManager::initInstance();
493 Net::DownloadManager::initInstance();
495 IconProvider::initInstance();
497 GuiIconProvider::initInstance();
501 BitTorrent::Session::initInstance();
502 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished
, this, &Application::torrentFinished
);
503 connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished
, this, &Application::allTorrentsFinished
, Qt::QueuedConnection
);
505 #ifndef DISABLE_COUNTRIES_RESOLUTION
506 Net::GeoIPManager::initInstance();
508 ScanFoldersModel::initInstance(this);
510 #ifndef DISABLE_WEBUI
513 if (m_webui
->isErrored())
515 connect(m_webui
, &WebUI::fatalError
, this, []() { QCoreApplication::exit(1); });
516 #endif // DISABLE_GUI
517 #endif // DISABLE_WEBUI
519 new RSS::Session
; // create RSS::Session singleton
520 new RSS::AutoDownloader
; // create RSS::AutoDownloader singleton
522 catch (const RuntimeError
&err
) {
524 fprintf(stderr
, "%s", err
.what());
527 msgBox
.setIcon(QMessageBox::Critical
);
528 msgBox
.setText(tr("Application failed to start."));
529 msgBox
.setInformativeText(err
.message());
530 msgBox
.show(); // Need to be shown or to moveToCenter does not work
531 msgBox
.move(Utils::Misc::screenCenter(&msgBox
));
538 #ifndef DISABLE_WEBUI
539 Preferences
*const pref
= Preferences::instance();
540 // Display some information to the user
541 const QString mesg
= QString("\n******** %1 ********\n").arg(tr("Information"))
542 + tr("To control qBittorrent, access the Web UI at %1")
543 .arg(QString("http://localhost:") + QString::number(pref
->getWebUiPort())) + '\n';
544 printf("%s", qUtf8Printable(mesg
));
546 if (pref
->getWebUIPassword() == "ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==") {
547 const QString warning
= tr("The Web UI administrator username is: %1").arg(pref
->getWebUiUsername()) + '\n'
548 + tr("The Web UI administrator password is still the default one: %1").arg("adminadmin") + '\n'
549 + tr("This is a security risk, please consider changing your password from program preferences.") + '\n';
550 printf("%s", qUtf8Printable(warning
));
552 #endif // DISABLE_WEBUI
554 m_window
= new MainWindow
;
555 #endif // DISABLE_GUI
559 // Now UI is ready to process signals from Session
560 BitTorrent::Session::instance()->startUpTorrents();
562 m_paramsQueue
= params
+ m_paramsQueue
;
563 if (!m_paramsQueue
.isEmpty()) {
564 processParams(m_paramsQueue
);
565 m_paramsQueue
.clear();
567 return BaseApplication::exec();
572 bool Application::isRunning()
574 bool running
= BaseApplication::isRunning();
575 QSharedMemory
*sharedMem
= new QSharedMemory(id() + QLatin1String("-shared-memory-key"), this);
577 // First instance creates shared memory and store PID
578 if (sharedMem
->create(sizeof(DWORD
)) && sharedMem
->lock()) {
579 *(static_cast<DWORD
*>(sharedMem
->data())) = ::GetCurrentProcessId();
584 // Later instances attach to shared memory and retrieve PID
585 if (sharedMem
->attach() && sharedMem
->lock()) {
586 ::AllowSetForegroundWindow(*(static_cast<DWORD
*>(sharedMem
->data())));
591 if (!sharedMem
->isAttached())
592 qWarning() << "Failed to initialize shared memory: " << sharedMem
->errorString();
599 bool Application::event(QEvent
*ev
)
601 if (ev
->type() == QEvent::FileOpen
) {
602 QString path
= static_cast<QFileOpenEvent
*>(ev
)->file();
604 // Get the url instead
605 path
= static_cast<QFileOpenEvent
*>(ev
)->url().toString();
606 qDebug("Received a mac file open event: %s", qUtf8Printable(path
));
608 processParams(QStringList(path
));
610 m_paramsQueue
.append(path
);
614 return BaseApplication::event(ev
);
619 bool Application::notify(QObject
*receiver
, QEvent
*event
)
622 return QApplication::notify(receiver
, event
);
624 catch (const std::exception
&e
) {
625 qCritical() << "Exception thrown:" << e
.what() << ", receiver: " << receiver
->objectName();
626 receiver
->dumpObjectInfo();
631 #endif // DISABLE_GUI
633 void Application::initializeTranslation()
635 Preferences
*const pref
= Preferences::instance();
637 QString localeStr
= pref
->getLocale();
639 if (m_qtTranslator
.load(QLatin1String("qtbase_") + localeStr
, QLibraryInfo::location(QLibraryInfo::TranslationsPath
)) ||
640 m_qtTranslator
.load(QLatin1String("qt_") + localeStr
, QLibraryInfo::location(QLibraryInfo::TranslationsPath
)))
641 qDebug("Qt %s locale recognized, using translation.", qUtf8Printable(localeStr
));
643 qDebug("Qt %s locale unrecognized, using default (en).", qUtf8Printable(localeStr
));
645 installTranslator(&m_qtTranslator
);
647 if (m_translator
.load(QLatin1String(":/lang/qbittorrent_") + localeStr
))
648 qDebug("%s locale recognized, using translation.", qUtf8Printable(localeStr
));
650 qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(localeStr
));
651 installTranslator(&m_translator
);
654 if (localeStr
.startsWith("ar") || localeStr
.startsWith("he")) {
655 qDebug("Right to Left mode");
656 setLayoutDirection(Qt::RightToLeft
);
659 setLayoutDirection(Qt::LeftToRight
);
664 #if (!defined(DISABLE_GUI) && defined(Q_OS_WIN))
665 void Application::shutdownCleanup(QSessionManager
&manager
)
669 // This is only needed for a special case on Windows XP.
670 // (but is called for every Windows version)
671 // If a process takes too much time to exit during OS
672 // shutdown, the OS presents a dialog to the user.
673 // That dialog tells the user that qbt is blocking the
674 // shutdown, it shows a progress bar and it offers
675 // a "Terminate Now" button for the user. However,
676 // after the progress bar has reached 100% another button
677 // is offered to the user reading "Cancel". With this the
678 // user can cancel the **OS** shutdown. If we don't do
679 // the cleanup by handling the commitDataRequest() signal
680 // and the user clicks "Cancel", it will result in qbt being
681 // killed and the shutdown proceeding instead. Apparently
682 // aboutToQuit() is emitted too late in the shutdown process.
685 // According to the qt docs we shouldn't call quit() inside a slot.
686 // aboutToQuit() is never emitted if the user hits "Cancel" in
688 QTimer::singleShot(0, qApp
, &QCoreApplication::quit
);
692 void Application::cleanup()
694 // cleanup() can be called multiple times during shutdown. We only need it once.
695 static QAtomicInt alreadyDone
;
696 if (!alreadyDone
.testAndSetAcquire(0, 1))
701 // Hide the window and don't leave it on screen as
702 // unresponsive. Also for Windows take the WinId
703 // after it's hidden, because hide() may cause a
708 typedef BOOL (WINAPI
*PSHUTDOWNBRCREATE
)(HWND
, LPCWSTR
);
709 const auto shutdownBRCreate
= Utils::Misc::loadWinAPI
<PSHUTDOWNBRCREATE
>("User32.dll", "ShutdownBlockReasonCreate");
710 // Only available on Vista+
711 if (shutdownBRCreate
)
712 shutdownBRCreate((HWND
)m_window
->effectiveWinId(), tr("Saving torrent progress...").toStdWString().c_str());
715 // Do manual cleanup in MainWindow to force widgets
716 // to save their Preferences, stop all timers and
717 // delete as many widgets as possible to leave only
718 // a 'shell' MainWindow.
719 // We need a valid window handle for Windows Vista+
720 // otherwise the system shutdown will continue even
721 // though we created a ShutdownBlockReason
724 #endif // DISABLE_GUI
726 #ifndef DISABLE_WEBUI
730 delete RSS::AutoDownloader::instance();
731 delete RSS::Session::instance();
733 ScanFoldersModel::freeInstance();
734 BitTorrent::Session::freeInstance();
735 #ifndef DISABLE_COUNTRIES_RESOLUTION
736 Net::GeoIPManager::freeInstance();
738 Net::DownloadManager::freeInstance();
739 Net::ProxyConfigurationManager::freeInstance();
740 Preferences::freeInstance();
741 SettingsStorage::freeInstance();
743 Logger::freeInstance();
744 IconProvider::freeInstance();
745 SearchPluginManager::freeInstance();
746 Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
751 typedef BOOL (WINAPI
*PSHUTDOWNBRDESTROY
)(HWND
);
752 const auto shutdownBRDestroy
= Utils::Misc::loadWinAPI
<PSHUTDOWNBRDESTROY
>("User32.dll", "ShutdownBlockReasonDestroy");
753 // Only available on Vista+
754 if (shutdownBRDestroy
)
755 shutdownBRDestroy((HWND
)m_window
->effectiveWinId());
759 #endif // DISABLE_GUI
761 if (m_shutdownAct
!= ShutdownDialogAction::Exit
) {
762 qDebug() << "Sending computer shutdown/suspend/hibernate signal...";
763 Utils::Misc::shutdownComputer(m_shutdownAct
);
767 void Application::validateCommandLineParameters()
769 if (m_commandLineArgs
.portableMode
&& !m_commandLineArgs
.profileDir
.isEmpty())
770 throw CommandLineParameterError(tr("Portable mode and explicit profile directory options are mutually exclusive"));
772 if (m_commandLineArgs
.portableMode
&& m_commandLineArgs
.relativeFastresumePaths
)
773 Logger::instance()->addMessage(tr("Portable mode implies relative fastresume"), Log::WARNING
);