keepassxc/src/gui/IconDownloaderDialog.cpp

218 lines
6.7 KiB
C++

/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "IconDownloaderDialog.h"
#include "ui_IconDownloaderDialog.h"
#include "core/Config.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "gui/IconDownloader.h"
#include "gui/IconModels.h"
#include "gui/Icons.h"
#include "osutils/OSUtils.h"
#ifdef Q_OS_MACOS
#include "gui/osutils/macutils/MacUtils.h"
#endif
#include <QStandardItemModel>
IconDownloaderDialog::IconDownloaderDialog(QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::IconDownloaderDialog())
, m_dataModel(new QStandardItemModel(this))
{
setWindowFlags(Qt::Window);
setAttribute(Qt::WA_DeleteOnClose);
m_ui->setupUi(this);
showFallbackMessage(false);
m_dataModel->clear();
m_dataModel->setHorizontalHeaderLabels({tr("URL"), tr("Status")});
m_ui->tableView->setModel(m_dataModel);
m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(abortDownloads()));
connect(m_ui->closeButton, SIGNAL(clicked()), SLOT(close()));
}
IconDownloaderDialog::~IconDownloaderDialog()
{
abortDownloads();
}
void IconDownloaderDialog::downloadFavicons(const QSharedPointer<Database>& database,
const QList<Entry*>& entries,
bool force)
{
m_db = database;
m_urlToEntries.clear();
abortDownloads();
for (const auto& e : entries) {
// 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);
}
}
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<QStandardItem*>()
<< 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();
}
}
}
void IconDownloaderDialog::downloadFaviconInBackground(const QSharedPointer<Database>& database, Entry* entry)
{
m_db = database;
m_urlToEntries.clear();
abortDownloads();
auto webUrl = entry->webUrl();
if (!webUrl.isEmpty()) {
m_urlToEntries.insert(webUrl, entry);
}
if (m_urlToEntries.count() > 0) {
m_activeDownloaders.append(createDownloader(webUrl));
m_activeDownloaders.first()->download();
}
}
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::downloadFinished(const QString& url, const QImage& icon)
{
// Prevent re-entrance from multiple calls finishing at the same time
QMutexLocker locker(&m_mutex);
// Cleanup the icon downloader that sent this signal
auto downloader = qobject_cast<IconDownloader*>(sender());
if (downloader) {
downloader->deleteLater();
m_activeDownloaders.removeAll(downloader);
}
updateProgressBar();
updateCancelButton();
if (m_db && !icon.isNull()) {
// Don't add an icon larger than 128x128, but retain original size if smaller
constexpr auto maxIconSize = 128;
auto scaledIcon = icon;
if (icon.width() > maxIconSize || icon.height() > maxIconSize) {
scaledIcon = icon.scaled(maxIconSize, maxIconSize);
}
QByteArray serializedIcon = Icons::saveToBytes(scaledIcon);
QUuid uuid = m_db->metadata()->findCustomIcon(serializedIcon);
if (uuid.isNull()) {
uuid = QUuid::createUuid();
m_db->metadata()->addCustomIcon(uuid, serializedIcon);
updateTable(url, tr("Ok"));
} else {
updateTable(url, tr("Already Exists"));
}
// Set the icon on all the entries associated with this url
for (const auto entry : m_urlToEntries.values(url)) {
entry->setIcon(uuid);
}
} else {
showFallbackMessage(true);
updateTable(url, tr("Download Failed"));
return;
}
}
void IconDownloaderDialog::showFallbackMessage(bool state)
{
// Show fallback message if the option is not active
bool show = state && !config()->get(Config::Security_IconDownloadFallback).toBool();
m_ui->fallbackLabel->setVisible(show);
}
void IconDownloaderDialog::updateProgressBar()
{
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::updateCancelButton()
{
m_ui->cancelButton->setEnabled(!m_activeDownloaders.isEmpty());
}
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() == url) {
m_dataModel->item(i, 1)->setText(message);
}
}
}
void IconDownloaderDialog::abortDownloads()
{
for (auto* downloader : m_activeDownloaders) {
downloader->deleteLater();
}
m_activeDownloaders.clear();
updateProgressBar();
updateCancelButton();
}