File indexing completed on 2024-12-22 04:41:07

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2014-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 "locationcompleterrefreshjob.h"
0019 #include "locationcompletermodel.h"
0020 #include "mainapplication.h"
0021 #include "bookmarkitem.h"
0022 #include "iconprovider.h"
0023 #include "sqldatabase.h"
0024 #include "qzsettings.h"
0025 #include "bookmarks.h"
0026 #include "qztools.h"
0027 
0028 #include <algorithm>
0029 
0030 #include <QDateTime>
0031 
0032 #include <QtConcurrent/QtConcurrentRun>
0033 
0034 LocationCompleterRefreshJob::LocationCompleterRefreshJob(const QString &searchString)
0035     : QObject()
0036     , m_timestamp(QDateTime::currentMSecsSinceEpoch())
0037     , m_searchString(searchString)
0038     , m_jobCancelled(false)
0039 {
0040     m_watcher = new QFutureWatcher<void>(this);
0041     connect(m_watcher, &QFutureWatcherBase::finished, this, &LocationCompleterRefreshJob::slotFinished);
0042 
0043     QFuture<void> future = QtConcurrent::run(&LocationCompleterRefreshJob::runJob, this);
0044     m_watcher->setFuture(future);
0045 }
0046 
0047 qint64 LocationCompleterRefreshJob::timestamp() const
0048 {
0049     return m_timestamp;
0050 }
0051 
0052 QString LocationCompleterRefreshJob::searchString() const
0053 {
0054     return m_searchString;
0055 }
0056 
0057 bool LocationCompleterRefreshJob::isCanceled() const
0058 {
0059     return m_jobCancelled;
0060 }
0061 
0062 QList<QStandardItem*> LocationCompleterRefreshJob::completions() const
0063 {
0064     return m_items;
0065 }
0066 
0067 QString LocationCompleterRefreshJob::domainCompletion() const
0068 {
0069     return m_domainCompletion;
0070 }
0071 
0072 void LocationCompleterRefreshJob::jobCancelled()
0073 {
0074     m_jobCancelled = true;
0075 }
0076 
0077 void LocationCompleterRefreshJob::slotFinished()
0078 {
0079     Q_EMIT finished();
0080 }
0081 
0082 static bool countBiggerThan(const QStandardItem* i1, const QStandardItem* i2)
0083 {
0084     int i1Count = i1->data(LocationCompleterModel::CountRole).toInt();
0085     int i2Count = i2->data(LocationCompleterModel::CountRole).toInt();
0086     return i1Count > i2Count;
0087 }
0088 
0089 void LocationCompleterRefreshJob::runJob()
0090 {
0091     if (m_jobCancelled || mApp->isClosing() || !mApp) {
0092         return;
0093     }
0094 
0095     if (m_searchString.isEmpty()) {
0096         completeMostVisited();
0097     }
0098     else {
0099         completeFromHistory();
0100     }
0101 
0102     // Load all icons into QImage
0103     for (QStandardItem* item : std::as_const(m_items)) {
0104         if (m_jobCancelled) {
0105             return;
0106         }
0107 
0108         const QUrl url = item->data(LocationCompleterModel::UrlRole).toUrl();
0109         item->setData(IconProvider::imageForUrl(url), LocationCompleterModel::ImageRole);
0110     }
0111 
0112     if (m_jobCancelled) {
0113         return;
0114     }
0115 
0116     // Get domain completion
0117     if (!m_searchString.isEmpty() && qzSettings->useInlineCompletion) {
0118         QSqlQuery domainQuery = LocationCompleterModel::createDomainQuery(m_searchString);
0119         if (!domainQuery.lastQuery().isEmpty()) {
0120             domainQuery.exec();
0121             if (domainQuery.next()) {
0122                 m_domainCompletion = createDomainCompletion(domainQuery.value(0).toUrl().host());
0123             }
0124         }
0125     }
0126 
0127     if (m_jobCancelled) {
0128         return;
0129     }
0130 
0131     // Add search/visit item
0132     if (!m_searchString.isEmpty()) {
0133         auto* item = new QStandardItem();
0134         item->setText(m_searchString);
0135         item->setData(m_searchString, LocationCompleterModel::UrlRole);
0136         item->setData(m_searchString, LocationCompleterModel::SearchStringRole);
0137         item->setData(true, LocationCompleterModel::VisitSearchItemRole);
0138         if (!m_domainCompletion.isEmpty()) {
0139             const QUrl url = QUrl(QSL("http://%1").arg(m_domainCompletion));
0140             item->setData(IconProvider::imageForDomain(url), LocationCompleterModel::ImageRole);
0141         }
0142         m_items.prepend(item);
0143     }
0144 }
0145 
0146 void LocationCompleterRefreshJob::completeFromHistory()
0147 {
0148     QList<QUrl> urlList;
0149     Type showType = (Type) qzSettings->showLocationSuggestions;
0150 
0151     // Search in bookmarks
0152     if (showType == HistoryAndBookmarks || showType == Bookmarks) {
0153         const int bookmarksLimit = 10;
0154         const QList<BookmarkItem*> bookmarks = mApp->bookmarks()->searchBookmarks(m_searchString, bookmarksLimit);
0155 
0156         for (BookmarkItem* bookmark : bookmarks) {
0157             Q_ASSERT(bookmark->isUrl());
0158 
0159             // Keyword bookmark replaces visit/search item
0160             if (bookmark->keyword() == m_searchString) {
0161                 continue;
0162             }
0163 
0164             auto* item = new QStandardItem();
0165             item->setText(QString::fromUtf8(bookmark->url().toEncoded()));
0166             item->setData(-1, LocationCompleterModel::IdRole);
0167             item->setData(bookmark->title(), LocationCompleterModel::TitleRole);
0168             item->setData(bookmark->url(), LocationCompleterModel::UrlRole);
0169             item->setData(bookmark->visitCount(), LocationCompleterModel::CountRole);
0170             item->setData(true, LocationCompleterModel::BookmarkRole);
0171             item->setData(QVariant::fromValue<void*>(static_cast<void*>(bookmark)), LocationCompleterModel::BookmarkItemRole);
0172             item->setData(m_searchString, LocationCompleterModel::SearchStringRole);
0173 
0174             urlList.append(bookmark->url());
0175             m_items.append(item);
0176         }
0177     }
0178 
0179     // Sort by count
0180     std::sort(m_items.begin(), m_items.end(), countBiggerThan);
0181 
0182     // Search in history
0183     if (showType == HistoryAndBookmarks || showType == History) {
0184         const int historyLimit = 20;
0185         QSqlQuery query = LocationCompleterModel::createHistoryQuery(m_searchString, historyLimit);
0186         query.exec();
0187 
0188         while (query.next()) {
0189             const QUrl url = query.value(1).toUrl();
0190 
0191             if (urlList.contains(url)) {
0192                 continue;
0193             }
0194 
0195             auto* item = new QStandardItem();
0196             item->setText(QString::fromUtf8(url.toEncoded()));
0197             item->setData(query.value(0), LocationCompleterModel::IdRole);
0198             item->setData(query.value(2), LocationCompleterModel::TitleRole);
0199             item->setData(url, LocationCompleterModel::UrlRole);
0200             item->setData(query.value(3), LocationCompleterModel::CountRole);
0201             item->setData(true, LocationCompleterModel::HistoryRole);
0202             item->setData(m_searchString, LocationCompleterModel::SearchStringRole);
0203 
0204             m_items.append(item);
0205         }
0206     }
0207 }
0208 
0209 void LocationCompleterRefreshJob::completeMostVisited()
0210 {
0211     QSqlQuery query(SqlDatabase::instance()->database());
0212     query.exec(QSL("SELECT id, url, title FROM history ORDER BY count DESC LIMIT 15"));
0213 
0214     while (query.next()) {
0215         auto* item = new QStandardItem();
0216         const QUrl url = query.value(1).toUrl();
0217 
0218         item->setText(QString::fromUtf8(url.toEncoded()));
0219         item->setData(query.value(0), LocationCompleterModel::IdRole);
0220         item->setData(query.value(2), LocationCompleterModel::TitleRole);
0221         item->setData(url, LocationCompleterModel::UrlRole);
0222         item->setData(true, LocationCompleterModel::HistoryRole);
0223 
0224         m_items.append(item);
0225     }
0226 }
0227 
0228 QString LocationCompleterRefreshJob::createDomainCompletion(const QString &completion) const
0229 {
0230     // Make sure search string and completion matches
0231 
0232     if (m_searchString.startsWith(QL1S("www.")) && !completion.startsWith(QL1S("www."))) {
0233         return QL1S("www.") + completion;
0234     }
0235 
0236     if (!m_searchString.startsWith(QL1S("www.")) && completion.startsWith(QL1S("www."))) {
0237         return completion.mid(4);
0238     }
0239 
0240     return completion;
0241 }