File indexing completed on 2024-05-12 04:58:27

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "iconprovider.h"
0019 #include "mainapplication.h"
0020 #include "networkmanager.h"
0021 #include "sqldatabase.h"
0022 #include "autosaver.h"
0023 #include "webview.h"
0024 #include "qztools.h"
0025 
0026 #include <QTimer>
0027 #include <QBuffer>
0028 #include <QFutureWatcher>
0029 #include <QtConcurrent/QtConcurrentRun>
0030 
0031 Q_GLOBAL_STATIC(IconProvider, qz_icon_provider)
0032 
0033 static QByteArray encodeUrl(const QUrl &url)
0034 {
0035     return url.toEncoded(QUrl::RemoveFragment | QUrl::StripTrailingSlash);
0036 }
0037 
0038 IconProvider::IconProvider()
0039     : QWidget()
0040 {
0041     m_autoSaver = new AutoSaver(this);
0042     connect(m_autoSaver, &AutoSaver::save, this, &IconProvider::saveIconsToDatabase);
0043 }
0044 
0045 void IconProvider::saveIcon(WebView* view)
0046 {
0047     // Don't save icons in private mode.
0048     if (mApp->isPrivate()) {
0049         return;
0050     }
0051 
0052     const QIcon icon = view->icon(true);
0053     if (icon.isNull()) {
0054         return;
0055     }
0056 
0057     const QStringList ignoredSchemes = {
0058         QStringLiteral("falkon"),
0059         QStringLiteral("ftp"),
0060         QStringLiteral("file"),
0061         QStringLiteral("view-source"),
0062         QStringLiteral("data"),
0063         QStringLiteral("about")
0064     };
0065 
0066     if (ignoredSchemes.contains(view->url().scheme())) {
0067         return;
0068     }
0069 
0070     for (int i = 0; i < m_iconBuffer.size(); ++i) {
0071         if (m_iconBuffer[i].first == view->url()) {
0072             m_iconBuffer.removeAt(i);
0073             break;
0074         }
0075     }
0076 
0077     BufferedIcon item;
0078     item.first = view->url();
0079     item.second = icon.pixmap(16).toImage();
0080 
0081     m_autoSaver->changeOccurred();
0082     m_iconBuffer.append(item);
0083 }
0084 
0085 QIcon IconProvider::bookmarkIcon() const
0086 {
0087     return QIcon::fromTheme(QSL("bookmarks"), m_bookmarkIcon);
0088 }
0089 
0090 void IconProvider::setBookmarkIcon(const QIcon &icon)
0091 {
0092     m_bookmarkIcon = icon;
0093 }
0094 
0095 QIcon IconProvider::standardIcon(QStyle::StandardPixmap icon)
0096 {
0097     switch (icon) {
0098     case QStyle::SP_MessageBoxCritical:
0099         return QIcon::fromTheme(QSL("dialog-error"), QApplication::style()->standardIcon(icon));
0100 
0101     case QStyle::SP_MessageBoxInformation:
0102         return QIcon::fromTheme(QSL("dialog-information"), QApplication::style()->standardIcon(icon));
0103 
0104     case QStyle::SP_MessageBoxQuestion:
0105         return QIcon::fromTheme(QSL("dialog-question"), QApplication::style()->standardIcon(icon));
0106 
0107     case QStyle::SP_MessageBoxWarning:
0108         return QIcon::fromTheme(QSL("dialog-warning"), QApplication::style()->standardIcon(icon));
0109 
0110     case QStyle::SP_DialogCloseButton:
0111         return QIcon::fromTheme(QSL("dialog-close"), QApplication::style()->standardIcon(icon));
0112 
0113     case QStyle::SP_BrowserStop:
0114         return QIcon::fromTheme(QSL("process-stop"), QApplication::style()->standardIcon(icon));
0115 
0116     case QStyle::SP_BrowserReload:
0117         return QIcon::fromTheme(QSL("view-refresh"), QApplication::style()->standardIcon(icon));
0118 
0119     case QStyle::SP_FileDialogToParent:
0120         return QIcon::fromTheme(QSL("go-up"), QApplication::style()->standardIcon(icon));
0121 
0122     case QStyle::SP_ArrowUp:
0123         return QIcon::fromTheme(QSL("go-up"), QApplication::style()->standardIcon(icon));
0124 
0125     case QStyle::SP_ArrowDown:
0126         return QIcon::fromTheme(QSL("go-down"), QApplication::style()->standardIcon(icon));
0127 
0128     case QStyle::SP_ArrowForward:
0129         if (QApplication::layoutDirection() == Qt::RightToLeft) {
0130             return QIcon::fromTheme(QSL("go-previous"), QApplication::style()->standardIcon(icon));
0131         }
0132         return QIcon::fromTheme(QSL("go-next"), QApplication::style()->standardIcon(icon));
0133 
0134     case QStyle::SP_ArrowBack:
0135         if (QApplication::layoutDirection() == Qt::RightToLeft) {
0136             return QIcon::fromTheme(QSL("go-next"), QApplication::style()->standardIcon(icon));
0137         }
0138         return QIcon::fromTheme(QSL("go-previous"), QApplication::style()->standardIcon(icon));
0139 
0140     default:
0141         return QApplication::style()->standardIcon(icon);
0142     }
0143 }
0144 
0145 QIcon IconProvider::newTabIcon()
0146 {
0147     return QIcon::fromTheme(QSL("tab-new"), QIcon(QSL(":/icons/menu/tab-new.svg")));
0148 }
0149 
0150 QIcon IconProvider::newWindowIcon()
0151 {
0152     return QIcon::fromTheme(QSL("window-new"), QIcon(QSL(":/icons/menu/window-new.svg")));
0153 }
0154 
0155 QIcon IconProvider::privateBrowsingIcon()
0156 {
0157     return QIcon::fromTheme(QSL("view-private-symbolic"), QIcon(QSL(":/icons/menu/privatebrowsing.png")));
0158 }
0159 
0160 QIcon IconProvider::settingsIcon()
0161 {
0162     return QIcon::fromTheme(QSL("configure"), QIcon(QSL(":/icons/menu/settings.svg")));
0163 }
0164 
0165 QIcon IconProvider::emptyWebIcon()
0166 {
0167     return QPixmap::fromImage(instance()->emptyWebImage());
0168 }
0169 
0170 QImage IconProvider::emptyWebImage()
0171 {
0172     if (instance()->m_emptyWebImage.isNull()) {
0173         instance()->m_emptyWebImage = QIcon(QSL(":icons/other/webpage.svg")).pixmap(16).toImage();
0174     }
0175 
0176     return instance()->m_emptyWebImage;
0177 }
0178 
0179 QIcon IconProvider::iconForUrl(const QUrl &url, bool allowNull)
0180 {
0181     return instance()->iconFromImage(imageForUrl(url, allowNull));
0182 }
0183 
0184 QImage IconProvider::imageForUrl(const QUrl &url, bool allowNull)
0185 {
0186     if (url.path().isEmpty()) {
0187         return allowNull ? QImage() : IconProvider::emptyWebImage();
0188     }
0189 
0190     QMutexLocker locker(&instance()->m_iconCacheMutex);
0191 
0192     const QByteArray encodedUrl = encodeUrl(url);
0193 
0194     if (QImage *img = instance()->m_urlImageCache.object(encodedUrl)) {
0195         return img->isNull() && !allowNull ? IconProvider::emptyWebImage() : *img;
0196     }
0197 
0198     const auto iconBuffer = instance()->m_iconBuffer;
0199     for (const BufferedIcon &ic : iconBuffer) {
0200         if (encodeUrl(ic.first) == encodedUrl) {
0201             return ic.second;
0202         }
0203     }
0204 
0205     QSqlQuery query(SqlDatabase::instance()->database());
0206     query.prepare(QSL("SELECT icon FROM icons WHERE url GLOB ? LIMIT 1"));
0207     query.addBindValue(QSL("%1*").arg(QzTools::escapeSqlGlobString(QString::fromUtf8(encodedUrl))));
0208     query.exec();
0209 
0210     auto *img = new QImage;
0211     if (query.next()) {
0212         img->loadFromData(query.value(0).toByteArray());
0213     }
0214     instance()->m_urlImageCache.insert(encodedUrl, img);
0215 
0216     return img->isNull() && !allowNull ? IconProvider::emptyWebImage() : *img;
0217 }
0218 
0219 QIcon IconProvider::iconForDomain(const QUrl &url, bool allowNull)
0220 {
0221     return instance()->iconFromImage(imageForDomain(url, allowNull));
0222 }
0223 
0224 QImage IconProvider::imageForDomain(const QUrl &url, bool allowNull)
0225 {
0226     if (url.host().isEmpty()) {
0227         return allowNull ? QImage() : IconProvider::emptyWebImage();
0228     }
0229 
0230     QMutexLocker locker(&instance()->m_iconCacheMutex);
0231 
0232     const auto iconBuffer = instance()->m_iconBuffer;
0233     for (const BufferedIcon &ic : iconBuffer) {
0234         if (ic.first.host() == url.host()) {
0235             return ic.second;
0236         }
0237     }
0238 
0239     QSqlQuery query(SqlDatabase::instance()->database());
0240     query.prepare(QSL("SELECT icon FROM icons WHERE url GLOB ? LIMIT 1"));
0241     query.addBindValue(QSL("*%1*").arg(QzTools::escapeSqlGlobString(url.host())));
0242     query.exec();
0243 
0244     if (query.next()) {
0245         return QImage::fromData(query.value(0).toByteArray());
0246     }
0247 
0248     return allowNull ? QImage() : IconProvider::emptyWebImage();
0249 }
0250 
0251 IconProvider* IconProvider::instance()
0252 {
0253     return qz_icon_provider();
0254 }
0255 
0256 void IconProvider::saveIconsToDatabase()
0257 {
0258     QMutexLocker locker(&instance()->m_iconCacheMutex);
0259 
0260     for (const BufferedIcon &ic : std::as_const(m_iconBuffer)) {
0261         QByteArray ba;
0262         QBuffer buffer(&ba);
0263         buffer.open(QIODevice::WriteOnly);
0264         ic.second.save(&buffer, "PNG");
0265 
0266         const QByteArray encodedUrl = encodeUrl(ic.first);
0267         m_urlImageCache.remove(encodedUrl);
0268 
0269         auto job = new SqlQueryJob(QSL("INSERT OR REPLACE INTO icons (icon, url) VALUES (?,?)"), this);
0270         job->addBindValue(buffer.data());
0271         job->addBindValue(QString::fromUtf8(encodedUrl));
0272         job->start();
0273     }
0274 
0275     m_iconBuffer.clear();
0276 }
0277 
0278 void IconProvider::clearOldIconsInDatabase()
0279 {
0280     // Delete icons for entries older than 6 months
0281     const QDateTime date = QDateTime::currentDateTime().addMonths(-6);
0282 
0283     QSqlQuery query(SqlDatabase::instance()->database());
0284     query.prepare(QSL("DELETE FROM icons WHERE url IN (SELECT url FROM history WHERE date < ?)"));
0285     query.addBindValue(date.toMSecsSinceEpoch());
0286     query.exec();
0287 
0288     query.clear();
0289     query.exec(QSL("VACUUM"));
0290 }
0291 
0292 QIcon IconProvider::iconFromImage(const QImage &image)
0293 {
0294     return QIcon(QPixmap::fromImage(image));
0295 }