From bf25a0b52a3876822e2309c39b1d9f2a8474ae2c Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 27 Jun 2019 17:35:11 -0400 Subject: [PATCH] Significant updates to original PR * Selecting one or more entries to download icons always forces the download (ie, if a new URL exists the new icon will be downloaded and set) * Instead of downloading for each entry, the web url's are scraped from the provided entries and only those urls are downloaded. The icon is set for all entries that share a URL. This is useful if a group contains many entries that point to the same url, only 1 download call will occur. * The icon download dialog displays whether you are doing one entry, many entries, or an entire group. It is also modal so you have to dismiss it to use KeePassXC again. * The timeout setting is always enabled. * Moved DuckDuckGo fallback notice into the download dialog. --- src/CMakeLists.txt | 15 +- src/cli/Analyze.cpp | 3 +- src/core/Config.cpp | 1 + src/{gui => core}/IconDownloader.cpp | 137 +++++------ src/{gui => core}/IconDownloader.h | 39 ++- src/core/NetworkManager.cpp | 33 +++ src/core/NetworkManager.h | 34 +++ src/gui/ApplicationSettingsWidget.cpp | 10 +- src/gui/ApplicationSettingsWidgetGeneral.ui | 95 ++++---- src/gui/DatabaseWidget.cpp | 49 ++-- src/gui/DatabaseWidget.h | 12 +- src/gui/EditWidgetIcons.cpp | 111 +++++---- src/gui/EditWidgetIcons.h | 12 +- src/gui/IconDownloaderDialog.cpp | 251 ++++++++------------ src/gui/IconDownloaderDialog.h | 48 ++-- src/gui/IconDownloaderDialog.ui | 207 +++++++++------- src/gui/MainWindow.cpp | 14 +- src/updatecheck/UpdateChecker.cpp | 11 +- src/updatecheck/UpdateChecker.h | 2 - 19 files changed, 565 insertions(+), 519 deletions(-) rename src/{gui => core}/IconDownloader.cpp (71%) rename src/{gui => core}/IconDownloader.h (67%) create mode 100644 src/core/NetworkManager.cpp create mode 100644 src/core/NetworkManager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5a5527fd48..52b709c85b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,9 @@ set(keepassx_SOURCES core/Alloc.cpp core/AutoTypeAssociations.cpp core/AutoTypeMatch.cpp + core/Base32.cpp + core/Bootstrap.cpp + core/Clock.cpp core/Compare.cpp core/Config.cpp core/CsvParser.cpp @@ -39,7 +42,6 @@ set(keepassx_SOURCES core/EntrySearcher.cpp core/FilePath.cpp core/FileWatcher.cpp - core/Bootstrap.cpp core/Group.cpp core/HibpOffline.cpp core/InactivityTimer.cpp @@ -52,10 +54,8 @@ set(keepassx_SOURCES core/ScreenLockListenerPrivate.cpp core/TimeDelta.cpp core/TimeInfo.cpp - core/Clock.cpp core/Tools.cpp core/Translator.cpp - core/Base32.cpp cli/Utils.cpp cli/TextStream.cpp crypto/Crypto.cpp @@ -102,8 +102,6 @@ set(keepassx_SOURCES gui/EditWidgetProperties.cpp gui/FileDialog.cpp gui/Font.cpp - gui/IconDownloader.cpp - gui/IconDownloaderDialog.cpp gui/IconModels.cpp gui/KeePass1OpenWidget.cpp gui/KMessageWidget.cpp @@ -265,7 +263,12 @@ else() endif() if(WITH_XC_NETWORKING) - list(APPEND keepassx_SOURCES updatecheck/UpdateChecker.cpp gui/UpdateCheckDialog.cpp) + list(APPEND keepassx_SOURCES + core/IconDownloader.cpp + core/NetworkManager.cpp + gui/UpdateCheckDialog.cpp + gui/IconDownloaderDialog.cpp + updatecheck/UpdateChecker.cpp) endif() if(WITH_XC_TOUCHID) diff --git a/src/cli/Analyze.cpp b/src/cli/Analyze.cpp index 41f9910c51..b600c3c3f0 100644 --- a/src/cli/Analyze.cpp +++ b/src/cli/Analyze.cpp @@ -50,7 +50,8 @@ int Analyze::executeWithDatabase(QSharedPointer database, QSharedPoint QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption); QFile hibpFile(hibpDatabase); if (!hibpFile.open(QFile::ReadOnly)) { - errorTextStream << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl; + errorTextStream << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) + << endl; return EXIT_FAILURE; } diff --git a/src/core/Config.cpp b/src/core/Config.cpp index caf41c5d99..36c544296f 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -185,6 +185,7 @@ void Config::init(const QString& fileName) m_defaults.insert("AutoTypeStartDelay", 500); m_defaults.insert("UseGroupIconOnEntryCreation", true); m_defaults.insert("IgnoreGroupExpansion", true); + m_defaults.insert("FaviconDownloadTimeout", 10); m_defaults.insert("security/clearclipboard", true); m_defaults.insert("security/clearclipboardtimeout", 10); m_defaults.insert("security/lockdatabaseidle", false); diff --git a/src/gui/IconDownloader.cpp b/src/core/IconDownloader.cpp similarity index 71% rename from src/gui/IconDownloader.cpp rename to src/core/IconDownloader.cpp index 818c7c626f..36047ce2a7 100644 --- a/src/gui/IconDownloader.cpp +++ b/src/core/IconDownloader.cpp @@ -17,27 +17,27 @@ #include "IconDownloader.h" #include "core/Config.h" +#include "core/NetworkManager.h" -#ifdef WITH_XC_NETWORKING #include -#include #include -#endif + +#define MAX_REDIRECTS 5 IconDownloader::IconDownloader(QObject* parent) : QObject(parent) -#ifdef WITH_XC_NETWORKING - , m_netMgr(new QNetworkAccessManager(this)) , m_reply(nullptr) -#endif + , m_redirects(0) { + m_timeout.setSingleShot(true); + connect(&m_timeout, SIGNAL(timeout()), SLOT(abortDownload())); } IconDownloader::~IconDownloader() { + abortDownload(); } -#ifdef WITH_XC_NETWORKING namespace { // Try to get the 2nd level domain of the host part of a QUrl. For example, @@ -55,8 +55,9 @@ namespace QUrl convertVariantToUrl(const QVariant& var) { QUrl url; - if (var.canConvert()) + if (var.canConvert()) { url = var.toUrl(); + } return url; } @@ -67,21 +68,23 @@ namespace return url; } } // namespace -#endif -void IconDownloader::downloadFavicon(Entry* entry) +void IconDownloader::setUrl(const QString& entryUrl) { -#ifdef WITH_XC_NETWORKING - m_entry = entry; - m_url = entry->url(); + m_url = entryUrl; + QUrl url(m_url); + if (!url.isValid()) { + return; + } + m_redirects = 0; m_urlsToTry.clear(); - if (m_url.scheme().isEmpty()) { - m_url.setUrl(QString("https://%1").arg(m_url.toString())); + if (url.scheme().isEmpty()) { + url.setUrl(QString("https://%1").arg(url.toString())); } - QString fullyQualifiedDomain = m_url.host(); + QString fullyQualifiedDomain = url.host(); // Determine if host portion of URL is an IP address by resolving it and // searching for a match with the returned address(es). @@ -113,38 +116,56 @@ void IconDownloader::downloadFavicon(Entry* entry) } // Add a direct pull of the website's own favicon.ico file - m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico")); + m_urlsToTry.append(QUrl(url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico")); // Also try a direct pull of the second-level domain (if possible) if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { - m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico")); + m_urlsToTry.append(QUrl(url.scheme() + "://" + secondLevelDomain + "/favicon.ico")); + } +} + +void IconDownloader::download() +{ + if (!m_timeout.isActive()) { + int timeout = config()->get("FaviconDownloadTimeout", 10).toInt(); + m_timeout.start(timeout * 1000); + + // Use the first URL to start the download process + // If a favicon is not found, the next URL will be tried + fetchFavicon(m_urlsToTry.takeFirst()); + } +} + +void IconDownloader::abortDownload() +{ + if (m_reply) { + m_reply->abort(); } +} + +void IconDownloader::fetchFavicon(const QUrl& url) +{ + m_bytesReceived.clear(); + m_fetchUrl = url; - // Use the first URL to start the download process - // If a favicon is not found, the next URL will be tried - startFetchFavicon(m_urlsToTry.takeFirst()); -#else - Q_UNUSED(entry); -#endif + QNetworkRequest request(url); + m_reply = getNetMgr()->get(request); + + connect(m_reply, &QNetworkReply::finished, this, &IconDownloader::fetchFinished); + connect(m_reply, &QIODevice::readyRead, this, &IconDownloader::fetchReadyRead); } void IconDownloader::fetchReadyRead() { -#ifdef WITH_XC_NETWORKING m_bytesReceived += m_reply->readAll(); -#endif } void IconDownloader::fetchFinished() { -#ifdef WITH_XC_NETWORKING QImage image; - bool fallbackEnabled = config()->get("security/IconDownloadFallback", false).toBool(); + QString url = m_url; + bool error = (m_reply->error() != QNetworkReply::NoError); - if (m_reply->error() == QNetworkReply::HostNotFoundError || m_reply->error() == QNetworkReply::TimeoutError) { - emit iconReceived(image, m_entry); - return; - } QUrl redirectTarget = getRedirectTarget(m_reply); m_reply->deleteLater(); @@ -154,12 +175,12 @@ void IconDownloader::fetchFinished() if (redirectTarget.isValid()) { // Redirected, we need to follow it, or fall through if we have // done too many redirects already. - if (m_redirects < 5) { + if (m_redirects < MAX_REDIRECTS) { m_redirects++; - if (redirectTarget.isRelative()) + if (redirectTarget.isRelative()) { redirectTarget = m_fetchUrl.resolved(redirectTarget); - startFetchFavicon(redirectTarget); - return; + } + m_urlsToTry.prepend(redirectTarget); } } else { // No redirect, and we theoretically have some icon data now. @@ -168,44 +189,16 @@ void IconDownloader::fetchFinished() } if (!image.isNull()) { - emit iconReceived(image, m_entry); - return; + // Valid icon received + m_timeout.stop(); + emit finished(url, image); } else if (!m_urlsToTry.empty()) { + // Try the next url m_redirects = 0; - startFetchFavicon(m_urlsToTry.takeFirst()); - return; + fetchFavicon(m_urlsToTry.takeFirst()); } else { - if (!fallbackEnabled) { - emit fallbackNotEnabled(); - } else { - emit iconError(m_entry); - } - } - emit iconReceived(image, m_entry); -#endif -} - -void IconDownloader::abortRequest() -{ -#ifdef WITH_XC_NETWORKING - if (m_reply) { - m_reply->abort(); + // No icon found + m_timeout.stop(); + emit finished(url, image); } -#endif -} - -void IconDownloader::startFetchFavicon(const QUrl& url) -{ -#ifdef WITH_XC_NETWORKING - m_bytesReceived.clear(); - m_fetchUrl = url; - - QNetworkRequest request(url); - m_reply = m_netMgr->get(request); - - connect(m_reply, &QNetworkReply::finished, this, &IconDownloader::fetchFinished); - connect(m_reply, &QIODevice::readyRead, this, &IconDownloader::fetchReadyRead); -#else - Q_UNUSED(url); -#endif } diff --git a/src/gui/IconDownloader.h b/src/core/IconDownloader.h similarity index 67% rename from src/gui/IconDownloader.h rename to src/core/IconDownloader.h index 6b071a5f74..e2b8c4f2df 100644 --- a/src/gui/IconDownloader.h +++ b/src/core/IconDownloader.h @@ -15,21 +15,17 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_ICONDOWNLOADER_H -#define KEEPASSX_ICONDOWNLOADER_H +#ifndef KEEPASSXC_ICONDOWNLOADER_H +#define KEEPASSXC_ICONDOWNLOADER_H +#include #include +#include #include -#include -#include "config-keepassx.h" -#include "core/Entry.h" #include "core/Global.h" -#ifdef WITH_XC_NETWORKING -class QNetworkAccessManager; class QNetworkReply; -#endif class IconDownloader : public QObject { @@ -37,34 +33,31 @@ class IconDownloader : public QObject public: explicit IconDownloader(QObject* parent = nullptr); - ~IconDownloader(); + ~IconDownloader() override; - void downloadFavicon(Entry* entry); - -public slots: - void startFetchFavicon(const QUrl& url); - void abortRequest(); + void setUrl(const QString& entryUrl); + void download(); signals: - void iconReceived(const QImage&, Entry*); - void iconError(Entry*); - void fallbackNotEnabled(); + void finished(const QString& entryUrl, const QImage& image); + +public slots: + void abortDownload(); private slots: void fetchFinished(); void fetchReadyRead(); private: -#ifdef WITH_XC_NETWORKING - QUrl m_url; - Entry* m_entry; + void fetchFavicon(const QUrl& url); + + QString m_url; QUrl m_fetchUrl; QList m_urlsToTry; QByteArray m_bytesReceived; - QNetworkAccessManager* m_netMgr; QNetworkReply* m_reply; + QTimer m_timeout; int m_redirects; -#endif }; -#endif // KEEPASSX_ICONDOWNLOADER_H \ No newline at end of file +#endif // KEEPASSXC_ICONDOWNLOADER_H \ No newline at end of file diff --git a/src/core/NetworkManager.cpp b/src/core/NetworkManager.cpp new file mode 100644 index 0000000000..52b54609fe --- /dev/null +++ b/src/core/NetworkManager.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config-keepassx.h" + +#ifdef WITH_XC_NETWORKING +#include "NetworkManager.h" + +#include + +QNetworkAccessManager* g_netMgr = nullptr; +QNetworkAccessManager* getNetMgr() +{ + if (!g_netMgr) { + g_netMgr = new QNetworkAccessManager(QCoreApplication::instance()); + } + return g_netMgr; +} +#endif diff --git a/src/core/NetworkManager.h b/src/core/NetworkManager.h new file mode 100644 index 0000000000..5616218749 --- /dev/null +++ b/src/core/NetworkManager.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_NETWORKMANAGER_H +#define KEEPASSXC_NETWORKMANAGER_H + +#include "config-keepassx.h" +#include + +#ifdef WITH_XC_NETWORKING +#include +#include +#include + +QNetworkAccessManager* getNetMgr(); +#else +Q_STATIC_ASSERT_X(false, "Qt Networking used when WITH_XC_NETWORKING is disabled!"); +#endif + +#endif // KEEPASSXC_NETWORKMANAGER_H diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index 30bb33f8b9..acfba0c8d3 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -84,8 +84,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) connect(m_generalUi->toolbarHideCheckBox, SIGNAL(toggled(bool)), SLOT(toolbarSettingsToggled(bool))); connect(m_generalUi->rememberLastDatabasesCheckBox, SIGNAL(toggled(bool)), SLOT(rememberDatabasesToggled(bool))); - connect(m_generalUi->downloadFaviconCheckBox, SIGNAL(toggled(bool)), - m_generalUi->downloadFaviconSpinBox, SLOT(setEnabled(bool))); connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)), m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool))); connect(m_secUi->lockDatabaseIdleCheckBox, SIGNAL(toggled(bool)), @@ -104,6 +102,8 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) #ifndef WITH_XC_NETWORKING m_secUi->privacy->setVisible(false); + m_generalUi->faviconTimeoutLabel->setVisible(false); + m_generalUi->faviconTimeoutSpinBox->setVisible(false); #endif #ifndef WITH_XC_TOUCHID @@ -155,8 +155,7 @@ void ApplicationSettingsWidget::loadSettings() m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); m_generalUi->autoTypeEntryURLMatchCheckBox->setChecked(config()->get("AutoTypeEntryURLMatch").toBool()); m_generalUi->ignoreGroupExpansionCheckBox->setChecked(config()->get("IgnoreGroupExpansion").toBool()); - m_generalUi->downloadFaviconCheckBox->setChecked(config()->get("DownloadFavicon").toBool()); - m_generalUi->downloadFaviconSpinBox->setValue(config()->get("DownloadFaviconTimeout").toInt()); + m_generalUi->faviconTimeoutSpinBox->setValue(config()->get("FaviconDownloadTimeout").toInt()); m_generalUi->languageComboBox->clear(); QList> languages = Translator::availableLanguages(); @@ -258,8 +257,7 @@ void ApplicationSettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); config()->set("AutoTypeEntryURLMatch", m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); - config()->set("DownloadFavicon", m_generalUi->downloadFaviconCheckBox->isChecked()); - config()->set("DownloadFaviconTimeout", m_generalUi->downloadFaviconSpinBox->value()); + config()->set("FaviconDownloadTimeout", m_generalUi->faviconTimeoutSpinBox->value()); config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); diff --git a/src/gui/ApplicationSettingsWidgetGeneral.ui b/src/gui/ApplicationSettingsWidgetGeneral.ui index 7ad9329066..1855e4a4f5 100644 --- a/src/gui/ApplicationSettingsWidgetGeneral.ui +++ b/src/gui/ApplicationSettingsWidgetGeneral.ui @@ -7,7 +7,7 @@ 0 0 684 - 1030 + 860 @@ -269,6 +269,55 @@ + + + + + + Favicon download timeout: + + + + + + + true + + + + 0 + 0 + + + + sec + + + 1 + + + 60 + + + 10 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -532,50 +581,6 @@ - - - - Timeouts - - - - Qt::AlignLeft|Qt::AlignTop - - - - - Favicon download timeout - - - - - - - false - - - - 0 - 0 - - - - sec - - - 1 - - - 60 - - - 10 - - - - - - diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index a3cbd7215f..079043f43f 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -95,7 +95,6 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) , m_groupView(new GroupView(m_db.data(), m_mainSplitter)) , m_saveAttempts(0) , m_fileWatcher(new DelayingFileWatcher(this)) - , m_iconDownloaderState(IconDownloaderState::Idle) { m_messageWidget->setHidden(true); @@ -654,49 +653,35 @@ void DatabaseWidget::openUrl() void DatabaseWidget::downloadSelectedFavicons() { #ifdef WITH_XC_NETWORKING - const QModelIndexList selected = m_entryView->selectionModel()->selectedRows(); - if (selected.isEmpty()) { - return; - } - QList selectedEntries; - for (const QModelIndex& index : selected) { + for (const auto& index : m_entryView->selectionModel()->selectedRows()) { selectedEntries.append(m_entryView->entryFromIndex(index)); } - if (selectedEntries.isEmpty()) { - return; - } - - if (m_iconDownloaderState == IconDownloaderState::Downloading) { - return; - } - m_iconDownloaderState = IconDownloaderState::Downloading; - - auto* iconDownloader = new IconDownloaderDialog(this); - iconDownloader->downloadFavicons(m_db, selectedEntries); - m_iconDownloaderState = IconDownloaderState::Idle; + // Force download even if icon already exists + performIconDownloads(selectedEntries, true); #endif } void DatabaseWidget::downloadAllFavicons() { #ifdef WITH_XC_NETWORKING - if (m_iconDownloaderState == IconDownloaderState::Downloading) { - return; - } - m_iconDownloaderState = IconDownloaderState::Downloading; - - auto* iconDownloader = new IconDownloaderDialog(this); - - Group* currentGroup = m_groupView->currentGroup(); - Q_ASSERT(currentGroup); + auto currentGroup = m_groupView->currentGroup(); if (currentGroup) { - auto entries = currentGroup->entries(); - iconDownloader->downloadFavicons(m_db, entries); + performIconDownloads(currentGroup->entries()); } +#endif +} - m_iconDownloaderState = IconDownloaderState::Idle; +void DatabaseWidget::performIconDownloads(const QList& entries, bool force) +{ +#ifdef WITH_XC_NETWORKING + auto* iconDownloaderDialog = new IconDownloaderDialog(this); + connect(this, SIGNAL(databaseLockRequested()), iconDownloaderDialog, SLOT(close())); + iconDownloaderDialog->downloadFavicons(m_db, entries, force); +#else + Q_UNUSED(entries); + Q_UNUSED(force); #endif } @@ -1325,6 +1310,8 @@ bool DatabaseWidget::lock() return true; } + emit databaseLockRequested(); + clipboard()->clearCopiedText(); if (isEditWidgetModified()) { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index cfe6e08023..aeb6a02e1b 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -25,10 +25,10 @@ #include #include "DatabaseOpenDialog.h" +#include "config-keepassx.h" #include "gui/MessageWidget.h" #include "gui/csvImport/CsvImportWizard.h" #include "gui/entry/EntryModel.h" -#include "config-keepassx.h" class DatabaseOpenWidget; class KeePass1OpenWidget; @@ -123,6 +123,7 @@ class DatabaseWidget : public QStackedWidget void databaseModified(); void databaseSaved(); void databaseUnlocked(); + void databaseLockRequested(); void databaseLocked(); // Emitted in replaceDatabase, may be caused by lock, reload, unlock, load. @@ -230,16 +231,11 @@ private slots: void restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& EntryUuid); private: - enum IconDownloaderState - { - Idle, - Downloading - }; - int addChildWidget(QWidget* w); void setClipboardTextAndMinimize(const QString& text); void processAutoOpen(); bool confirmDeleteEntries(QList entries, bool permanent); + void performIconDownloads(const QList& entries, bool force = false); QSharedPointer m_db; @@ -278,8 +274,6 @@ private slots: // Autoreload QPointer m_fileWatcher; bool m_blockAutoSave; - - IconDownloaderState m_iconDownloaderState; }; #endif // KEEPASSX_DATABASEWIDGET_H diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 31af5a050f..83bc0fc350 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -26,9 +26,11 @@ #include "core/Group.h" #include "core/Metadata.h" #include "core/Tools.h" -#include "gui/IconDownloader.h" #include "gui/IconModels.h" #include "gui/MessageBox.h" +#ifdef WITH_XC_NETWORKING +#include "core/IconDownloader.h" +#endif IconStruct::IconStruct() : uuid(QUuid()) @@ -69,6 +71,11 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) this, SIGNAL(widgetUpdated())); connect(m_ui->customIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(widgetUpdated())); +#ifdef WITH_XC_NETWORKING + connect(m_downloader.data(), + SIGNAL(finished(const QString&, const QImage&)), + SLOT(iconReceived(const QString&, const QImage&))); +#endif // clang-format on m_ui->faviconButton->setVisible(false); @@ -171,7 +178,7 @@ QMenu* EditWidgetIcons::createApplyIconToMenu() void EditWidgetIcons::setUrl(const QString& url) { #ifdef WITH_XC_NETWORKING - m_url = QUrl(url); + m_url = url; m_ui->faviconButton->setVisible(!url.isEmpty()); #else Q_UNUSED(url); @@ -182,29 +189,33 @@ void EditWidgetIcons::setUrl(const QString& url) void EditWidgetIcons::downloadFavicon() { #ifdef WITH_XC_NETWORKING - connect(m_downloader.data(), SIGNAL(iconReceived(const QImage&, Entry*)), this, SLOT(iconReceived(const QImage&, Entry*))); - Entry* entry = m_db->rootGroup()->findEntryByUuid(m_currentUuid); - if (entry) { - m_downloader->downloadFavicon(entry); + if (!m_url.isEmpty()) { + m_downloader->setUrl(m_url); + m_downloader->download(); } #endif } -void EditWidgetIcons::iconReceived(const QImage& icon, Entry* entry) +void EditWidgetIcons::iconReceived(const QString& url, const QImage& icon) { #ifdef WITH_XC_NETWORKING - Q_UNUSED(entry); + Q_UNUSED(url); if (icon.isNull()) { - emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); + QString message(tr("Unable to fetch favicon.")); + if (!config()->get("security/IconDownloadFallback", false).toBool()) { + message.append("\n").append( + tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security")); + } + emit messageEditEntry(message, MessageWidget::Error); return; } if (!addCustomIcon(icon)) { - emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); + emit messageEditEntry(tr("Existing icon selected."), MessageWidget::Information); } #else + Q_UNUSED(url); Q_UNUSED(icon); - Q_UNUSED(entry); #endif } @@ -212,56 +223,58 @@ void EditWidgetIcons::abortRequests() { #ifdef WITH_XC_NETWORKING if (m_downloader) { - m_downloader->abortRequest(); + m_downloader->abortDownload(); } #endif } void EditWidgetIcons::addCustomIconFromFile() { - if (m_db) { - QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files")); - - auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter); - if (!filenames.empty()) { - QStringList errornames; - int numexisting = 0; - for (const auto& filename : filenames) { - if (!filename.isEmpty()) { - auto icon = QImage(filename); - if (icon.isNull()) { - errornames << filename; - } else if (!addCustomIcon(icon)) { - // Icon already exists in database - ++numexisting; - } + if (!m_db) { + return; + } + + QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files")); + + auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter); + if (!filenames.empty()) { + QStringList errornames; + int numexisting = 0; + for (const auto& filename : filenames) { + if (!filename.isEmpty()) { + auto icon = QImage(filename); + if (icon.isNull()) { + errornames << filename; + } else if (!addCustomIcon(icon)) { + // Icon already exists in database + ++numexisting; } } + } - int numloaded = filenames.size() - errornames.size() - numexisting; - QString msg; + int numloaded = filenames.size() - errornames.size() - numexisting; + QString msg; - if (numloaded > 0) { - msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded); - } else { - msg = tr("No icons were loaded"); - } + if (numloaded > 0) { + msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded); + } else { + msg = tr("No icons were loaded"); + } - if (numexisting > 0) { - msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting); - } + if (numexisting > 0) { + msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting); + } - if (!errornames.empty()) { - // Show the first 8 icons that failed to load - errornames = errornames.mid(0, 8); - emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + "\n" - + errornames.join("\n"), - MessageWidget::Error); - } else if (numloaded > 0) { - emit messageEditEntry(msg, MessageWidget::Positive); - } else { - emit messageEditEntry(msg, MessageWidget::Information); - } + if (!errornames.empty()) { + // Show the first 8 icons that failed to load + errornames = errornames.mid(0, 8); + emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + "\n" + + errornames.join("\n"), + MessageWidget::Error); + } else if (numloaded > 0) { + emit messageEditEntry(msg, MessageWidget::Positive); + } else { + emit messageEditEntry(msg, MessageWidget::Information); } } } diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 2a7bd7b376..2a95445f97 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -25,16 +25,16 @@ #include #include "config-keepassx.h" -#include "core/Global.h" #include "core/Entry.h" +#include "core/Global.h" #include "gui/MessageWidget.h" -#ifdef WITH_XC_NETWORKING -#include "gui/IconDownloader.h" -#endif class Database; class DefaultIconModel; class CustomIconModel; +#ifdef WITH_XC_NETWORKING +class IconDownloader; +#endif namespace Ui { @@ -87,7 +87,7 @@ public slots: private slots: void downloadFavicon(); - void iconReceived(const QImage& icon, Entry* entry); + void iconReceived(const QString& url, const QImage& icon); void addCustomIconFromFile(); bool addCustomIcon(const QImage& icon); void removeCustomIcon(); @@ -108,7 +108,7 @@ private slots: CustomIconModel* const m_customIconModel; #ifdef WITH_XC_NETWORKING QScopedPointer m_downloader; - QUrl m_url; + QString m_url; #endif Q_DISABLE_COPY(EditWidgetIcons) diff --git a/src/gui/IconDownloaderDialog.cpp b/src/gui/IconDownloaderDialog.cpp index 2b84a52f8d..ebe6980a2d 100644 --- a/src/gui/IconDownloaderDialog.cpp +++ b/src/gui/IconDownloaderDialog.cpp @@ -16,164 +16,119 @@ */ #include "IconDownloaderDialog.h" - -#include -#include -#include +#include "ui_IconDownloaderDialog.h" #include "core/AsyncTask.h" #include "core/Config.h" +#include "core/Entry.h" +#include "core/Global.h" #include "core/Group.h" +#include "core/IconDownloader.h" #include "core/Metadata.h" #include "core/Tools.h" #include "gui/IconModels.h" -#include "ui_IconDownloaderDialog.h" - -#ifdef WITH_XC_NETWORKING -#include -#include -#include -#endif - #ifdef Q_OS_MACOS #include "gui/macutils/MacUtils.h" #endif +#include + IconDownloaderDialog::IconDownloaderDialog(QWidget* parent) : QDialog(parent) , m_ui(new Ui::IconDownloaderDialog()) , m_dataModel(new QStandardItemModel(this)) - , m_db(nullptr) - , m_customIconModel(new CustomIconModel(this)) - , m_parent(parent) { - m_ui->setupUi(this); setWindowFlags(Qt::Window); setAttribute(Qt::WA_DeleteOnClose); - m_ui->tableView->setModel(m_dataModel); - m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - m_ui->progressBar->setMinimumHeight(20); - m_dataModel->clear(); - m_dataModel->setHorizontalHeaderLabels({"URL", "Status"}); - - connect(this, SIGNAL(entryUpdated()), parent, SIGNAL(databaseModified())); -} - -IconDownloaderDialog::~IconDownloaderDialog() -{ - m_futureList.waitForFinished(); -} -void IconDownloaderDialog::raiseWindow() -{ -#ifdef Q_OS_MACOS - macUtils()->raiseOwnWindow(); - Tools::wait(500); -#endif - show(); - activateWindow(); - raise(); -} + m_ui->setupUi(this); + showFallbackMessage(false); -void IconDownloaderDialog::downloadFavicon(const QSharedPointer& database, Entry* entry) -{ - m_db = database; -#ifdef WITH_XC_NETWORKING - QScopedPointer downloader(new IconDownloader()); - connect(downloader.data(), SIGNAL(iconError(Entry*)), this, SLOT(iconError(Entry*))); - connect(downloader.data(), SIGNAL(fallbackNotEnabled()), this, SLOT(fallbackNotEnabled())); - connect(downloader.data(), SIGNAL(iconReceived(const QImage&, Entry*)), - this, SLOT(iconReceived(const QImage&, Entry*))); - connect(m_ui->abortButton, SIGNAL(clicked()), downloader.data(), SLOT(abortRequest())); - connect(m_parent, SIGNAL(databaseLocked()), downloader.data(), SLOT(abortRequest())); - connect(m_parent, SIGNAL(databaseLocked()), this, SLOT(close())); - - m_mutex.lock(); - m_dataModel->appendRow(QList() << new QStandardItem(entry->url()) - << new QStandardItem(tr("Loading"))); - m_mutex.unlock(); - - QTimer timer; - connect(&timer, SIGNAL(timeout()), downloader.data(), SLOT(abortRequest())); - if (config()->get("DownloadFavicon", false).toBool()) { - timer.start(config()->get("DownloadFaviconTimeout").toInt() * 1000); - } + m_dataModel->clear(); + m_dataModel->setHorizontalHeaderLabels({tr("URL"), tr("Status")}); - downloader->downloadFavicon(entry); + m_ui->tableView->setModel(m_dataModel); + m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); - QEventLoop loop; - connect(downloader.data(), SIGNAL(iconReceived(const QImage&, Entry*)), &loop, SLOT(quit())); - loop.exec(); - timer.stop(); -#endif + connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(abortDownloads())); + connect(m_ui->closeButton, SIGNAL(clicked()), SLOT(close())); } -void IconDownloaderDialog::downloadFavicons(const QSharedPointer& database, const QList& entries) +IconDownloaderDialog::~IconDownloaderDialog() { - if (entries.count() == 1) { - connect(this, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), - m_parent, SLOT(showMessage(QString, MessageWidget::MessageType))); - downloadFavicon(database, entries.first()); - return; - } - - raiseWindow(); - auto result = AsyncTask::runAndWaitForFuture( - [&]() { return downloadAllFavicons(database, entries); }); - Q_UNUSED(result); + abortDownloads(); } -bool IconDownloaderDialog::downloadAllFavicons(const QSharedPointer& database, const QList& entries) +void IconDownloaderDialog::downloadFavicons(const QSharedPointer& database, + const QList& entries, + bool force) { m_db = database; - - // Set progress bar - int maximum = 0; + m_urlToEntries.clear(); + abortDownloads(); for (const auto& e : entries) { - if (!e->url().isEmpty() && e->iconUuid().isNull()) { - ++maximum; + // Only consider entries with a valid URL and without a custom icon + auto webUrl = e->webUrl(); + if (!webUrl.isEmpty() && (force || e->iconUuid().isNull())) { + m_urlToEntries.insert(webUrl, e); } } - m_ui->progressBar->setMinimum(0); - m_ui->progressBar->setMaximum(maximum); - for (const auto& e : entries) { - if (!e->url().isEmpty() && e->iconUuid().isNull()) { - QFuture fut = QtConcurrent::run(this, &IconDownloaderDialog::downloadFavicon, database, e); - m_futureList.addFuture(fut); + if (m_urlToEntries.count() > 0) { +#ifdef Q_OS_MACOS + macUtils()->raiseOwnWindow(); + Tools::wait(100); +#endif + showFallbackMessage(false); + m_ui->progressLabel->setText(tr("Please wait, processing entry list...")); + open(); + QApplication::processEvents(); + + for (const auto& url : m_urlToEntries.uniqueKeys()) { + m_dataModel->appendRow(QList() + << new QStandardItem(url) << new QStandardItem(tr("Downloading..."))); + m_activeDownloaders.append(createDownloader(url)); + } + + // Setup the dialog + updateProgressBar(); + updateCancelButton(); + QApplication::processEvents(); + + // Start the downloads + for (auto downloader : m_activeDownloaders) { + downloader->download(); } } +} - m_futureList.waitForFinished(); - m_ui->abortButton->setEnabled(false); - return true; +IconDownloader* IconDownloaderDialog::createDownloader(const QString& url) +{ + auto downloader = new IconDownloader(); + connect(downloader, + SIGNAL(finished(const QString&, const QImage&)), + this, + SLOT(downloadFinished(const QString&, const QImage&))); + + downloader->setUrl(url); + return downloader; } -void IconDownloaderDialog::iconReceived(const QImage& icon, Entry* entry) +void IconDownloaderDialog::downloadFinished(const QString& url, const QImage& icon) { - m_mutex.lock(); - m_ui->progressBar->setValue(m_ui->progressBar->value() + 1); - m_ui->label->setText(tr("Downloading favicon %1 of %n…", 0, - m_ui->progressBar->maximum()).arg(m_ui->progressBar->value())); - m_mutex.unlock(); - - if (icon.isNull() || !entry) { - updateTable(entry, tr("Error")); - emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); - return; - } + // Prevent re-entrance from multiple calls finishing at the same time + QMutexLocker locker(&m_mutex); - if (!addCustomIcon(icon, entry)) { - updateTable(entry, tr("Custom icon already exists")); - return; + // Cleanup the icon downloader that sent this signal + auto downloader = qobject_cast(sender()); + if (downloader) { + downloader->deleteLater(); + m_activeDownloaders.removeAll(downloader); } - updateTable(entry, tr("Ok")); -} + updateProgressBar(); + updateCancelButton(); -bool IconDownloaderDialog::addCustomIcon(const QImage& icon, Entry* entry) -{ - bool added = false; if (m_db && !icon.isNull()) { // Don't add an icon larger than 128x128, but retain original size if smaller auto scaledicon = icon; @@ -185,61 +140,59 @@ bool IconDownloaderDialog::addCustomIcon(const QImage& icon, Entry* entry) if (uuid.isNull()) { uuid = QUuid::createUuid(); m_db->metadata()->addCustomIcon(uuid, scaledicon); - m_customIconModel->setIcons(m_db->metadata()->customIconsScaledPixmaps(), - m_db->metadata()->customIconsOrder()); - added = true; + updateTable(url, tr("Ok")); } else { - emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); + updateTable(url, tr("Already Exists")); } - if (entry) { + // Set the icon on all the entries associated with this url + for (const auto entry : m_urlToEntries.values(url)) { entry->setIcon(uuid); } - - emit entryUpdated(); + } else { + showFallbackMessage(true); + updateTable(url, tr("Download Failed")); + return; } - - return added; } -void IconDownloaderDialog::fallbackNotEnabled() +void IconDownloaderDialog::showFallbackMessage(bool state) { -#ifdef Q_OS_MACOS - const QString settingsPath = tr("Preferences -> Security"); -#else - const QString settingsPath = tr("Tools -> Settings -> Security"); -#endif - - emit messageEditEntry( - tr("Unable to fetch favicon.") + "\n" - + tr("You can enable the DuckDuckGo website icon service under %1").arg(settingsPath), - MessageWidget::Error); + // Show fallback message if the option is not active + bool show = state && !config()->get("security/IconDownloadFallback").toBool(); + m_ui->fallbackLabel->setVisible(show); } -void IconDownloaderDialog::iconError(Entry* entry) +void IconDownloaderDialog::updateProgressBar() { - updateTable(entry, tr("Unable to fetch favicon.")); - emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); + int total = m_urlToEntries.uniqueKeys().count(); + int value = total - m_activeDownloaders.count(); + m_ui->progressBar->setValue(value); + m_ui->progressBar->setMaximum(total); + m_ui->progressLabel->setText( + tr("Downloading favicons (%1/%2)...").arg(QString::number(value), QString::number(total))); } -void IconDownloaderDialog::updateTable(Entry* entry, const QString& message) +void IconDownloaderDialog::updateCancelButton() { - if (!entry) { - return; - } + m_ui->cancelButton->setEnabled(!m_activeDownloaders.isEmpty()); +} - QMutexLocker locker(&m_mutex); +void IconDownloaderDialog::updateTable(const QString& url, const QString& message) +{ for (int i = 0; i < m_dataModel->rowCount(); ++i) { - if (m_dataModel->item(i, 0)->text() == entry->url()) { + if (m_dataModel->item(i, 0)->text() == url) { m_dataModel->item(i, 1)->setText(message); } } } -void IconDownloaderDialog::closeEvent(QCloseEvent* event) +void IconDownloaderDialog::abortDownloads() { - emit m_ui->abortButton->clicked(); - m_ui->abortButton->setEnabled(false); - m_futureList.waitForFinished(); - event->accept(); + for (auto* downloader : m_activeDownloaders) { + delete downloader; + } + m_activeDownloaders.clear(); + updateProgressBar(); + updateCancelButton(); } diff --git a/src/gui/IconDownloaderDialog.h b/src/gui/IconDownloaderDialog.h index c7300317e7..955e85a98a 100644 --- a/src/gui/IconDownloaderDialog.h +++ b/src/gui/IconDownloaderDialog.h @@ -19,25 +19,15 @@ #define KEEPASSX_ICONDOWNLOADERDIALOG_H #include -#include -#include -#include -#include -#include -#include -#include #include +#include -#include "config-keepassx.h" -#include "core/Entry.h" -#include "core/Global.h" #include "gui/MessageWidget.h" -#ifdef WITH_XC_NETWORKING -#include "gui/IconDownloader.h" -#endif class Database; +class Entry; class CustomIconModel; +class IconDownloader; namespace Ui { @@ -52,33 +42,25 @@ class IconDownloaderDialog : public QDialog explicit IconDownloaderDialog(QWidget* parent = nullptr); ~IconDownloaderDialog() override; - void raiseWindow(); - void downloadFavicon(const QSharedPointer& database, Entry* entry); - void downloadFavicons(const QSharedPointer& database, const QList& entries); - -signals: - void messageEditEntry(QString, MessageWidget::MessageType); - void entryUpdated(); + void downloadFavicons(const QSharedPointer& database, const QList& entries, bool force = false); private slots: - void iconReceived(const QImage& icon, Entry* entry); - bool addCustomIcon(const QImage& icon, Entry* entry); - void fallbackNotEnabled(); - void iconError(Entry* entry); - -protected: - bool downloadAllFavicons(const QSharedPointer& database, const QList& entries); - void updateTable(Entry* entry, const QString& message); - void closeEvent(QCloseEvent* event) override; + void downloadFinished(const QString& url, const QImage& icon); + void abortDownloads(); private: + IconDownloader* createDownloader(const QString& url); + + void showFallbackMessage(bool state); + void updateTable(const QString& url, const QString& message); + void updateProgressBar(); + void updateCancelButton(); + QScopedPointer m_ui; QStandardItemModel* m_dataModel; QSharedPointer m_db; - QUuid m_currentUuid; - CustomIconModel* const m_customIconModel; - QWidget* m_parent; - QFutureSynchronizer m_futureList; + QMultiMap m_urlToEntries; + QList m_activeDownloaders; QMutex m_mutex; Q_DISABLE_COPY(IconDownloaderDialog) diff --git a/src/gui/IconDownloaderDialog.ui b/src/gui/IconDownloaderDialog.ui index eb1ece20a8..a657f7acbe 100644 --- a/src/gui/IconDownloaderDialog.ui +++ b/src/gui/IconDownloaderDialog.ui @@ -6,8 +6,8 @@ 0 0 - 641 - 452 + 453 + 339 @@ -17,88 +17,135 @@ - Download all favicons + Download Favicons - - - - - - - - - Downloading favicon 0/0... - - - - - - - QLayout::SetDefaultConstraint - - - - - 0 - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - Abort - - - - - - - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::NoSelection - - - QAbstractItemView::SelectColumns - - - 500 - - - 20 - - - true - - - false - - - - + + + + + + 75 + true + + + + Downloading favicon 0/0... + + + + + + + QLayout::SetDefaultConstraint + + + + + 0 + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + Cancel + + + + + + + 75 + false + true + + + + Having trouble downloading icons? +You can enable the DuckDuckGo website icon service in the security section of the application settings. + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideNone + + + true + + + false + + + 20 + + + 20 + + + true + + + true + + + false + + + + + + + Close + + + true + + + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 1556eca439..11cd402865 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -306,11 +306,13 @@ MainWindow::MainWindow() m_ui->actionEntryCopyUsername->setIcon(filePath()->icon("actions", "username-copy")); m_ui->actionEntryCopyPassword->setIcon(filePath()->icon("actions", "password-copy")); m_ui->actionEntryCopyURL->setIcon(filePath()->icon("actions", "url-copy")); + m_ui->actionEntryDownloadIcon->setIcon(filePath()->icon("actions", "favicon-download")); m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new")); m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit")); m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete")); m_ui->actionGroupEmptyRecycleBin->setIcon(filePath()->icon("actions", "group-empty-trash")); + m_ui->actionDownloadAllFavicons->setIcon(filePath()->icon("actions", "favicon-download")); m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure")); m_ui->actionPasswordGenerator->setIcon(filePath()->icon("actions", "password-generator")); @@ -422,6 +424,11 @@ MainWindow::MainWindow() m_ui->actionCheckForUpdates->setVisible(false); #endif +#ifndef WITH_XC_NETWORKING + m_ui->actionDownloadAllFavicons->setVisible(false); + m_ui->actionEntryDownloadIcon->setVisible(false); +#endif + // clang-format off connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), @@ -580,6 +587,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && hasFocus; bool groupSelected = dbWidget->isGroupSelected(); bool currentGroupHasChildren = dbWidget->currentGroup()->hasChildren(); + bool currentGroupHasEntries = !dbWidget->currentGroup()->entries().isEmpty(); bool recycleBinSelected = dbWidget->isRecycleBinSelected(); m_ui->actionEntryNew->setEnabled(true); @@ -599,8 +607,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected); m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntryDownloadIcon->setEnabled((entriesSelected && !singleEntrySelected) || - (singleEntrySelected && dbWidget->currentEntryHasUrl())); + m_ui->actionEntryDownloadIcon->setEnabled((entriesSelected && !singleEntrySelected) + || (singleEntrySelected && dbWidget->currentEntryHasUrl())); m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); @@ -608,7 +616,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupSortDesc->setEnabled(groupSelected && currentGroupHasChildren); m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected); m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected); - m_ui->actionDownloadAllFavicons->setEnabled(groupSelected); + m_ui->actionDownloadAllFavicons->setEnabled(groupSelected && currentGroupHasEntries); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave()); diff --git a/src/updatecheck/UpdateChecker.cpp b/src/updatecheck/UpdateChecker.cpp index 1453129074..c36879707d 100644 --- a/src/updatecheck/UpdateChecker.cpp +++ b/src/updatecheck/UpdateChecker.cpp @@ -16,18 +16,21 @@ */ #include "UpdateChecker.h" + #include "config-keepassx.h" #include "core/Clock.h" #include "core/Config.h" +#include "core/NetworkManager.h" + +#include +#include #include -#include -#include +#include UpdateChecker* UpdateChecker::m_instance(nullptr); UpdateChecker::UpdateChecker(QObject* parent) : QObject(parent) - , m_netMgr(new QNetworkAccessManager(this)) , m_reply(nullptr) , m_isManuallyRequested(false) { @@ -56,7 +59,7 @@ void UpdateChecker::checkForUpdates(bool manuallyRequested) QNetworkRequest request(apiUrl); request.setRawHeader("Accept", "application/json"); - m_reply = m_netMgr->get(request); + m_reply = getNetMgr()->get(request); connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished); connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead); diff --git a/src/updatecheck/UpdateChecker.h b/src/updatecheck/UpdateChecker.h index 64430bda3b..9e804b2740 100644 --- a/src/updatecheck/UpdateChecker.h +++ b/src/updatecheck/UpdateChecker.h @@ -20,7 +20,6 @@ #include #include -class QNetworkAccessManager; class QNetworkReply; class UpdateChecker : public QObject @@ -42,7 +41,6 @@ private slots: void fetchReadyRead(); private: - QNetworkAccessManager* m_netMgr; QNetworkReply* m_reply; QByteArray m_bytesReceived; bool m_isManuallyRequested;