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 }