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