From e24aaa4ce1bf70a8a1af6a42425e96f702d91649 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Wed, 3 Aug 2022 07:14:26 +0300 Subject: [PATCH] Show startup progress dialog PR #17389. --- src/app/application.cpp | 258 ++++++++++++++++++++++---------- src/app/application.h | 4 + src/base/bittorrent/session.cpp | 11 ++ src/base/bittorrent/session.h | 1 + src/gui/mainwindow.cpp | 13 +- src/gui/mainwindow.h | 8 +- 6 files changed, 205 insertions(+), 90 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index f777f3f27..ab0434b73 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #ifdef Q_OS_WIN #include #include @@ -644,6 +645,7 @@ void Application::processParams(const AddTorrentParams ¶ms) } int Application::exec(const QStringList ¶ms) +try { #if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI) const QString loadingStr = tr("WebUI will be started shortly after internal preparations. Please wait..."); @@ -663,41 +665,53 @@ int Application::exec(const QStringList ¶ms) Net::DownloadManager::initInstance(); IconProvider::initInstance(); - try + BitTorrent::Session::initInstance(); +#ifndef DISABLE_GUI + UIThemeManager::initInstance(); + + m_desktopIntegration = new DesktopIntegration(this); + m_desktopIntegration->setToolTip(tr("Loading torrents...")); +#ifndef Q_OS_MACOS + auto *desktopIntegrationMenu = new QMenu; + auto *actionExit = new QAction(tr("E&xit"), desktopIntegrationMenu); + actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs)); + actionExit->setMenuRole(QAction::QuitRole); + actionExit->setShortcut(Qt::CTRL | Qt::Key_Q); + connect(actionExit, &QAction::triggered, this, [this]() { - BitTorrent::Session::initInstance(); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::restored, this, [this]() - { -#ifndef DISABLE_WEBUI - m_webui = new WebUI(this); -#ifdef DISABLE_GUI - if (m_webui->isErrored()) - QCoreApplication::exit(1); - connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(1); }); + QApplication::exit(); + }); + desktopIntegrationMenu->addAction(actionExit); - const Preferences *pref = Preferences::instance(); + m_desktopIntegration->setMenu(desktopIntegrationMenu); +#endif - const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_qs : u"http"_qs; - const auto url = u"%1://localhost:%2\n"_qs.arg(scheme, QString::number(pref->getWebUiPort())); - const QString mesg = u"\n******** %1 ********\n"_qs.arg(tr("Information")) - + tr("To control qBittorrent, access the WebUI at: %1").arg(url); - printf("%s\n", qUtf8Printable(mesg)); + const auto *pref = Preferences::instance(); +#ifndef Q_OS_MACOS + const bool isHidden = m_desktopIntegration->isActive() && pref->startMinimized() && pref->minimizeToTray(); +#else + const bool isHidden = false; +#endif - if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")) - { - const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n' - + tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_qs) + u'\n' - + tr("This is a security risk, please change your password in program preferences.") + u'\n'; - printf("%s", qUtf8Printable(warning)); - } -#endif // DISABLE_GUI -#endif // DISABLE_WEBUI - - m_isProcessingParamsAllowed = true; - for (const AddTorrentParams ¶ms : m_paramsQueue) - processParams(params); - m_paramsQueue.clear(); - }); + if (!isHidden) + { + createStartupProgressDialog(); + // Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore. + m_startupProgressDialog->setMinimumDuration(1000); + if (pref->startMinimized()) + m_startupProgressDialog->setWindowState(Qt::WindowMinimized); + } + else + { +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + connect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog); +#else + connect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog, Qt::SingleShotConnection); +#endif + } +#endif + connect(BitTorrent::Session::instance(), &BitTorrent::Session::restored, this, [this]() + { connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished); connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection); @@ -706,67 +720,97 @@ int Application::exec(const QStringList ¶ms) new RSS::Session; // create RSS::Session singleton new RSS::AutoDownloader; // create RSS::AutoDownloader singleton - } - catch (const RuntimeError &err) - { -#ifdef DISABLE_GUI - fprintf(stderr, "%s", qPrintable(err.message())); -#else - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setText(tr("Application failed to start.")); - msgBox.setInformativeText(err.message()); - msgBox.show(); // Need to be shown or to moveToCenter does not work - msgBox.move(Utils::Gui::screenCenter(&msgBox)); - msgBox.exec(); -#endif - return 1; - } #ifndef DISABLE_GUI - UIThemeManager::initInstance(); + const auto *btSession = BitTorrent::Session::instance(); + connect(btSession, &BitTorrent::Session::fullDiskError, this + , [this](const BitTorrent::Torrent *torrent, const QString &msg) + { + m_desktopIntegration->showNotification(tr("I/O Error", "i.e: Input/Output Error") + , tr("An I/O error occurred for torrent '%1'.\n Reason: %2" + , "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg)); + }); + connect(btSession, &BitTorrent::Session::loadTorrentFailed, this + , [this](const QString &error) + { + m_desktopIntegration->showNotification(tr("Error"), tr("Failed to add torrent: %1").arg(error)); + }); + connect(btSession, &BitTorrent::Session::torrentAdded, this + , [this](const BitTorrent::Torrent *torrent) + { + if (isTorrentAddedNotificationsEnabled()) + m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name())); + }); + connect(btSession, &BitTorrent::Session::torrentFinished, this + , [this](const BitTorrent::Torrent *torrent) + { + m_desktopIntegration->showNotification(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name())); + }); + connect(btSession, &BitTorrent::Session::downloadFromUrlFailed, this + , [this](const QString &url, const QString &reason) + { + m_desktopIntegration->showNotification(tr("URL download error") + , tr("Couldn't download file at URL '%1', reason: %2.").arg(url, reason)); + }); - const auto *btSession = BitTorrent::Session::instance(); - - m_desktopIntegration = new DesktopIntegration(this); - connect(btSession, &BitTorrent::Session::fullDiskError, this - , [this](const BitTorrent::Torrent *torrent, const QString &msg) - { - m_desktopIntegration->showNotification(tr("I/O Error", "i.e: Input/Output Error") - , tr("An I/O error occurred for torrent '%1'.\n Reason: %2" - , "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg)); - }); - connect(btSession, &BitTorrent::Session::loadTorrentFailed, this - , [this](const QString &error) - { - m_desktopIntegration->showNotification(tr("Error"), tr("Failed to add torrent: %1").arg(error)); - }); - connect(btSession, &BitTorrent::Session::torrentAdded, this - , [this](const BitTorrent::Torrent *torrent) - { - if (isTorrentAddedNotificationsEnabled()) - m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name())); - }); - connect(btSession, &BitTorrent::Session::torrentFinished, this - , [this](const BitTorrent::Torrent *torrent) - { - m_desktopIntegration->showNotification(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name())); - }); - connect(btSession, &BitTorrent::Session::downloadFromUrlFailed, this - , [this](const QString &url, const QString &reason) - { - m_desktopIntegration->showNotification(tr("URL download error") - , tr("Couldn't download file at URL '%1', reason: %2.").arg(url, reason)); - }); - - m_window = new MainWindow(this); + disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog); + delete m_desktopIntegration->menu(); + const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized)) + ? MainWindow::Minimized : MainWindow::Normal; + m_window = new MainWindow(this, windowState); #endif // DISABLE_GUI +#ifndef DISABLE_WEBUI + m_webui = new WebUI(this); +#ifdef DISABLE_GUI + if (m_webui->isErrored()) + QCoreApplication::exit(EXIT_FAILURE); + connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(EXIT_FAILURE); }); + + const Preferences *pref = Preferences::instance(); + + const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_qs : u"http"_qs; + const auto url = u"%1://localhost:%2\n"_qs.arg(scheme, QString::number(pref->getWebUiPort())); + const QString mesg = u"\n******** %1 ********\n"_qs.arg(tr("Information")) + + tr("To control qBittorrent, access the WebUI at: %1").arg(url); + printf("%s\n", qUtf8Printable(mesg)); + + if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")) + { + const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n' + + tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_qs) + u'\n' + + tr("This is a security risk, please change your password in program preferences.") + u'\n'; + printf("%s", qUtf8Printable(warning)); + } +#endif // DISABLE_GUI +#endif // DISABLE_WEBUI + + m_isProcessingParamsAllowed = true; + for (const AddTorrentParams ¶ms : m_paramsQueue) + processParams(params); + m_paramsQueue.clear(); + }); + if (!params.isEmpty()) m_paramsQueue.append(parseParams(params)); return BaseApplication::exec(); } +catch (const RuntimeError &err) +{ +#ifdef DISABLE_GUI + fprintf(stderr, "%s", qPrintable(err.message())); +#else + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Critical); + msgBox.setText(tr("Application failed to start.")); + msgBox.setInformativeText(err.message()); + msgBox.show(); // Need to be shown or to moveToCenter does not work + msgBox.move(Utils::Gui::screenCenter(&msgBox)); + msgBox.exec(); +#endif + return EXIT_FAILURE; +} bool Application::isRunning() { @@ -774,6 +818,56 @@ bool Application::isRunning() } #ifndef DISABLE_GUI +void Application::createStartupProgressDialog() +{ + Q_ASSERT(m_desktopIntegration); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog); +#endif + + m_startupProgressDialog = new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100); + m_startupProgressDialog->setAttribute(Qt::WA_DeleteOnClose); + m_startupProgressDialog->setWindowFlag(Qt::WindowMinimizeButtonHint); + m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediatelly by default + m_startupProgressDialog->setAutoReset(false); + m_startupProgressDialog->setAutoClose(false); + + connect(m_startupProgressDialog, &QProgressDialog::canceled, this, []() + { + QApplication::exit(); + }); + + connect(BitTorrent::Session::instance(), &BitTorrent::Session::startupProgressUpdated, m_startupProgressDialog, &QProgressDialog::setValue); + connect(BitTorrent::Session::instance(), &BitTorrent::Session::restored, m_startupProgressDialog, &QObject::deleteLater); + + connect(m_desktopIntegration, &DesktopIntegration::activationRequested, m_startupProgressDialog, [this]() + { +#ifdef Q_OS_MACOS + if (!m_startupProgressDialog->isVisible()) + { + m_startupProgressDialog->show(); + m_startupProgressDialog->activateWindow(); + m_startupProgressDialog->raise(); + } +#else + if (m_startupProgressDialog->isHidden()) + { + // Make sure the window is not minimized + m_startupProgressDialog->setWindowState((m_startupProgressDialog->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + + // Then show it + m_startupProgressDialog->show(); + m_startupProgressDialog->raise(); + m_startupProgressDialog->activateWindow(); + } + else + { + m_startupProgressDialog->hide(); + } +#endif + }); +} + #ifdef Q_OS_MACOS bool Application::event(QEvent *ev) { diff --git a/src/app/application.h b/src/app/application.h index cc39684fb..364f28916 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -67,6 +67,8 @@ namespace RSS } #ifndef DISABLE_GUI +class QProgressDialog; + class DesktopIntegration; class MainWindow; @@ -166,6 +168,7 @@ private: #endif #ifndef DISABLE_GUI + void createStartupProgressDialog(); #ifdef Q_OS_MACOS bool event(QEvent *) override; #endif @@ -203,6 +206,7 @@ private: DesktopIntegration *m_desktopIntegration = nullptr; MainWindow *m_window = nullptr; + QProgressDialog *m_startupProgressDialog = nullptr; #endif #ifndef DISABLE_WEBUI diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 166011386..b334b9fe5 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -307,6 +307,8 @@ struct BitTorrent::Session::ResumeSessionContext final : public QObject ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy; QVector loadedResumeData; int processingResumeDataCount = 0; + int64_t totalResumeDataCount = 0; + int64_t finishedResumeDataCount = 0; bool isLoadFinished = false; bool isLoadedResumeDataHandlingEnqueued = false; QSet recoveredCategories; @@ -1102,6 +1104,7 @@ void Session::prepareStartup() connect(context->startupStorage, &ResumeDataStorage::loadStarted, context , [this, context](const QVector &torrents) { + context->totalResumeDataCount = torrents.size(); #ifdef QBT_USES_LIBTORRENT2 context->indexedTorrents = QSet(torrents.cbegin(), torrents.cend()); #endif @@ -1117,6 +1120,7 @@ void Session::prepareStartup() connect(this, &Session::torrentsLoaded, context, [this, context](const QVector &torrents) { context->processingResumeDataCount -= torrents.count(); + context->finishedResumeDataCount += torrents.count(); if (!context->isLoadedResumeDataHandlingEnqueued) { QMetaObject::invokeMethod(this, [this, context]() { handleLoadedResumeData(context); }, Qt::QueuedConnection); @@ -1128,6 +1132,8 @@ void Session::prepareStartup() m_nativeSession->post_torrent_updates(); m_refreshEnqueued = true; } + + emit startupProgressUpdated((context->finishedResumeDataCount * 100.) / context->totalResumeDataCount); }); context->startupStorage->loadAll(); @@ -1137,6 +1143,7 @@ void Session::handleLoadedResumeData(ResumeSessionContext *context) { context->isLoadedResumeDataHandlingEnqueued = false; + int count = context->processingResumeDataCount; while (context->processingResumeDataCount < MAX_PROCESSING_RESUMEDATA_COUNT) { if (context->loadedResumeData.isEmpty()) @@ -1161,7 +1168,10 @@ void Session::handleLoadedResumeData(ResumeSessionContext *context) } processNextResumeData(context); + ++count; } + + context->finishedResumeDataCount += (count - context->processingResumeDataCount); } void Session::processNextResumeData(ResumeSessionContext *context) @@ -1365,6 +1375,7 @@ void Session::endStartup(ResumeSessionContext *context) } m_isRestored = true; + emit startupProgressUpdated(100); emit restored(); } diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index a1ab59cca..3fc1c3be7 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -532,6 +532,7 @@ namespace BitTorrent , const Path &downloadPath, const PathList &filePaths = {}) const; signals: + void startupProgressUpdated(int progress); void allTorrentsFinished(); void categoryAdded(const QString &categoryName); void categoryRemoved(const QString &categoryName); diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 7be560e51..964fea847 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -119,9 +119,8 @@ namespace } } -MainWindow::MainWindow(IGUIApplication *app, QWidget *parent) - : QMainWindow(parent) - , GUIApplicationComponent(app) +MainWindow::MainWindow(IGUIApplication *app, const State initialState) + : GUIApplicationComponent(app) , m_ui(new Ui::MainWindow) , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_qs)) , m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_qs)) @@ -381,7 +380,7 @@ MainWindow::MainWindow(IGUIApplication *app, QWidget *parent) #ifdef Q_OS_MACOS // Make sure the Window is visible if we don't have a tray icon - if (pref->startMinimized()) + if (initialState == Minimized) { showMinimized(); } @@ -394,13 +393,13 @@ MainWindow::MainWindow(IGUIApplication *app, QWidget *parent) #else if (app->desktopIntegration()->isActive()) { - if (!(pref->startMinimized() || m_uiLocked)) + if ((initialState != Minimized) && !m_uiLocked) { show(); activateWindow(); raise(); } - else if (pref->startMinimized()) + else if (initialState == Minimized) { showMinimized(); if (pref->minimizeToTray()) @@ -417,7 +416,7 @@ MainWindow::MainWindow(IGUIApplication *app, QWidget *parent) else { // Make sure the Window is visible if we don't have a tray icon - if (pref->startMinimized()) + if (initialState == Minimized) { showMinimized(); } diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index b7a527c09..838395ad5 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -79,7 +79,13 @@ class MainWindow final : public QMainWindow, public GUIApplicationComponent Q_DISABLE_COPY_MOVE(MainWindow) public: - explicit MainWindow(IGUIApplication *app, QWidget *parent = nullptr); + enum State + { + Normal, + Minimized + }; + + explicit MainWindow(IGUIApplication *app, State initialState = Normal); ~MainWindow() override; QWidget *currentTabWidget() const;