File indexing completed on 2024-05-19 16:41:36

0001 /*
0002     SPDX-FileCopyrightText: 2007 Glenn Ergeerts <glenn.ergeerts@telenet.be>
0003     SPDX-FileCopyrightText: 2012 Marco Gulino <marco.gulino@xpeppers.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "firefox.h"
0009 #include "bookmarkmatch.h"
0010 #include "bookmarks_debug.h"
0011 #include "favicon.h"
0012 #include "faviconfromblob.h"
0013 #include "fetchsqlite.h"
0014 #include <KConfigGroup>
0015 #include <KSharedConfig>
0016 #include <QFile>
0017 #include <QRegularExpression>
0018 
0019 Firefox::Firefox(const QString &firefoxConfigDir, QObject *parent)
0020     : QObject(parent)
0021     , m_dbCacheFile(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)
0022                     + QStringLiteral("/bookmarksrunner/bookmarkrunnerfirefoxdbfile.sqlite"))
0023     , m_dbCacheFile_fav(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)
0024                         + QStringLiteral("/bookmarksrunner/bookmarkrunnerfirefoxfavdbfile.sqlite"))
0025     , m_favicon(new FallbackFavicon(this))
0026     , m_fetchsqlite(nullptr)
0027     , m_fetchsqlite_fav(nullptr)
0028 {
0029     if (!QSqlDatabase::isDriverAvailable(QStringLiteral("QSQLITE"))) {
0030         qCWarning(RUNNER_BOOKMARKS) << "SQLITE driver isn't available";
0031         return;
0032     }
0033 
0034     KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("General"));
0035     /* This allows the user to specify a profile database */
0036     m_dbFile = grp.readEntry("dbfile", QString());
0037     if (m_dbFile.isEmpty() || !QFile::exists(m_dbFile)) {
0038         // Try to get the right database file, the default profile is used
0039         KConfig firefoxProfile(firefoxConfigDir + "/profiles.ini", KConfig::SimpleConfig);
0040         QStringList profilesList = firefoxProfile.groupList();
0041         profilesList = profilesList.filter(QRegularExpression(QStringLiteral("^Profile\\d+$")));
0042 
0043         QString profilePath;
0044         if (profilesList.size() == 1) {
0045             // There is only 1 profile so we select it
0046             KConfigGroup fGrp = firefoxProfile.group(profilesList.first());
0047             profilePath = fGrp.readEntry("Path");
0048         } else {
0049             const QStringList installConfig = firefoxProfile.groupList().filter(QRegularExpression("^Install.*"));
0050             // The profile with Default=1 is not always the default profile, see BUG: 418526
0051             // If there is only one Install* group it contains the default profile
0052             if (installConfig.size() == 1) {
0053                 profilePath = firefoxProfile.group(installConfig.first()).readEntry("Default");
0054             } else {
0055                 // There are multiple profiles, find the default one
0056                 for (const QString &profileName : qAsConst(profilesList)) {
0057                     KConfigGroup fGrp = firefoxProfile.group(profileName);
0058                     if (fGrp.readEntry<int>("Default", 0)) {
0059                         profilePath = fGrp.readEntry("Path");
0060                         break;
0061                     }
0062                 }
0063             }
0064         }
0065 
0066         if (profilePath.isEmpty()) {
0067             qCWarning(RUNNER_BOOKMARKS) << "No default firefox profile found";
0068             return;
0069         }
0070         profilePath.prepend(firefoxConfigDir + "/");
0071         m_dbFile = profilePath + "/places.sqlite";
0072         m_dbFile_fav = profilePath + "/favicons.sqlite";
0073     } else {
0074         auto dir = QDir(m_dbFile);
0075         if (dir.cdUp()) {
0076             QString profilePath = dir.absolutePath();
0077             m_dbFile_fav = profilePath + "/favicons.sqlite";
0078         }
0079     }
0080     // We can reuse the favicon instance over the lifetime of the plugin consequently the
0081     // icons that are already written to disk can be reused in multiple match sessions
0082     updateCacheFile(m_dbFile_fav, m_dbCacheFile_fav);
0083     m_fetchsqlite_fav = new FetchSqlite(m_dbCacheFile_fav, this);
0084     delete m_favicon;
0085     m_favicon = FaviconFromBlob::firefox(m_fetchsqlite_fav, this);
0086 }
0087 
0088 Firefox::~Firefox()
0089 {
0090     // Delete the cached databases
0091     if (!m_dbFile.isEmpty()) {
0092         QFile db_CacheFile(m_dbCacheFile);
0093         if (db_CacheFile.exists()) {
0094             db_CacheFile.remove();
0095         }
0096     }
0097     if (!m_dbFile_fav.isEmpty()) {
0098         QFile db_CacheFileFav(m_dbCacheFile_fav);
0099         if (db_CacheFileFav.exists()) {
0100             db_CacheFileFav.remove();
0101         }
0102     }
0103 }
0104 
0105 void Firefox::prepare()
0106 {
0107     if (updateCacheFile(m_dbFile, m_dbCacheFile) != Error) {
0108         m_fetchsqlite = new FetchSqlite(m_dbCacheFile);
0109         m_fetchsqlite->prepare();
0110     }
0111     updateCacheFile(m_dbFile_fav, m_dbCacheFile_fav);
0112     m_favicon->prepare();
0113 }
0114 
0115 QList<BookmarkMatch> Firefox::match(const QString &term, bool addEverything)
0116 {
0117     QList<BookmarkMatch> matches;
0118     if (!m_fetchsqlite) {
0119         return matches;
0120     }
0121 
0122     QString query;
0123     if (addEverything) {
0124         query = QStringLiteral(
0125             "SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url "
0126             "FROM moz_bookmarks, moz_places WHERE "
0127             "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id");
0128     } else {
0129         query = QStringLiteral(
0130             "SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url "
0131             "FROM moz_bookmarks, moz_places WHERE "
0132             "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id AND "
0133             "(moz_bookmarks.title LIKE :term OR moz_places.url LIKE :term)");
0134     }
0135     const QMap<QString, QVariant> bindVariables{
0136         {QStringLiteral(":term"), QStringLiteral("%%%1%%").arg(term)},
0137     };
0138     const QList<QVariantMap> results = m_fetchsqlite->query(query, bindVariables);
0139     QMultiMap<QString, QString> uniqueResults;
0140     for (const QVariantMap &result : results) {
0141         const QString title = result.value(QStringLiteral("title")).toString();
0142         const QUrl url = result.value(QStringLiteral("url")).toUrl();
0143         if (url.isEmpty() || url.scheme() == QLatin1String("place")) {
0144             // Don't use bookmarks with empty url or Firefox's "place:" scheme,
0145             // e.g. used for "Most Visited" or "Recent Tags"
0146             // qDebug() << "element " << url << " was not added";
0147             continue;
0148         }
0149 
0150         auto urlString = url.toString();
0151         // After joining we may have multiple results for each URL:
0152         // 1) one for each bookmark folder (same or different titles)
0153         // 2) one for each tag (no title for all but the first entry)
0154         auto keyRange = uniqueResults.equal_range(urlString);
0155         auto it = keyRange.first;
0156         if (!title.isEmpty()) {
0157             while (it != keyRange.second) {
0158                 if (*it == title) {
0159                     // same URL and title in multiple bookmark folders
0160                     break;
0161                 }
0162                 if (it->isEmpty()) {
0163                     // add a title if there was none for the URL
0164                     *it = title;
0165                     break;
0166                 }
0167                 ++it;
0168             }
0169         }
0170         if (it == keyRange.second) {
0171             // first or unique entry
0172             uniqueResults.insert(urlString, title);
0173         }
0174     }
0175 
0176     for (auto result = uniqueResults.constKeyValueBegin(); result != uniqueResults.constKeyValueEnd(); ++result) {
0177         const QString url = (*result).first;
0178         BookmarkMatch bookmarkMatch(m_favicon->iconFor(url), term, (*result).second, url);
0179         bookmarkMatch.addTo(matches, addEverything);
0180     }
0181 
0182     return matches;
0183 }
0184 
0185 void Firefox::teardown()
0186 {
0187     if (m_fetchsqlite) {
0188         m_fetchsqlite->teardown();
0189         delete m_fetchsqlite;
0190         m_fetchsqlite = nullptr;
0191     }
0192     m_favicon->teardown();
0193 }