/* SPDX-FileCopyrightText: 2007 Glenn Ergeerts SPDX-FileCopyrightText: 2012 Marco Gulino SPDX-License-Identifier: LGPL-2.0-or-later */ #include "firefox.h" #include "bookmarkmatch.h" #include "bookmarks_debug.h" #include "favicon.h" #include "faviconfromblob.h" #include "fetchsqlite.h" #include #include #include #include #include Firefox::Firefox(const QString &firefoxConfigDir, QObject *parent) : QObject(parent) , m_dbCacheFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/bookmarkrunnerfirefoxdbfile.sqlite")) , m_dbCacheFile_fav(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/bookmarkrunnerfirefoxfavdbfile.sqlite")) , m_favicon(new FallbackFavicon(this)) , m_fetchsqlite(nullptr) , m_fetchsqlite_fav(nullptr) { if (!QSqlDatabase::isDriverAvailable(QStringLiteral("QSQLITE"))) { qCWarning(RUNNER_BOOKMARKS) << "SQLITE driver isn't available"; return; } KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("General")); /* This allows the user to specify a profile database */ m_dbFile = grp.readEntry("dbfile", QString()); if (m_dbFile.isEmpty() || !QFile::exists(m_dbFile)) { // Try to get the right database file, the default profile is used KConfig firefoxProfile(firefoxConfigDir + "/profiles.ini", KConfig::SimpleConfig); QStringList profilesList = firefoxProfile.groupList(); profilesList = profilesList.filter(QRegularExpression(QStringLiteral("^Profile\\d+$"))); QString profilePath; if (profilesList.size() == 1) { // There is only 1 profile so we select it KConfigGroup fGrp = firefoxProfile.group(profilesList.first()); profilePath = fGrp.readEntry("Path"); } else { const QStringList installConfig = firefoxProfile.groupList().filter(QRegularExpression("^Install.*")); // The profile with Default=1 is not always the default profile, see BUG: 418526 // If there is only one Install* group it contains the default profile if (installConfig.size() == 1) { profilePath = firefoxProfile.group(installConfig.first()).readEntry("Default"); } else { // There are multiple profiles, find the default one for (const QString &profileName : qAsConst(profilesList)) { KConfigGroup fGrp = firefoxProfile.group(profileName); if (fGrp.readEntry("Default", 0)) { profilePath = fGrp.readEntry("Path"); break; } } } } if (profilePath.isEmpty()) { qCWarning(RUNNER_BOOKMARKS) << "No default firefox profile found"; return; } profilePath.prepend(firefoxConfigDir + "/"); m_dbFile = profilePath + "/places.sqlite"; m_dbFile_fav = profilePath + "/favicons.sqlite"; } else { auto dir = QDir(m_dbFile); if (dir.cdUp()) { QString profilePath = dir.absolutePath(); m_dbFile_fav = profilePath + "/favicons.sqlite"; } } // We can reuse the favicon instance over the lifetime of the plugin consequently the // icons that are already written to disk can be reused in multiple match sessions updateCacheFile(m_dbFile_fav, m_dbCacheFile_fav); m_fetchsqlite_fav = new FetchSqlite(m_dbCacheFile_fav, this); delete m_favicon; m_favicon = FaviconFromBlob::firefox(m_fetchsqlite_fav, this); } Firefox::~Firefox() { // Delete the cached databases if (!m_dbFile.isEmpty()) { QFile db_CacheFile(m_dbCacheFile); if (db_CacheFile.exists()) { db_CacheFile.remove(); } } if (!m_dbFile_fav.isEmpty()) { QFile db_CacheFileFav(m_dbCacheFile_fav); if (db_CacheFileFav.exists()) { db_CacheFileFav.remove(); } } } void Firefox::prepare() { if (updateCacheFile(m_dbFile, m_dbCacheFile) != Error) { m_fetchsqlite = new FetchSqlite(m_dbCacheFile); m_fetchsqlite->prepare(); } updateCacheFile(m_dbFile_fav, m_dbCacheFile_fav); m_favicon->prepare(); } QList Firefox::match(const QString &term, bool addEverything) { QList matches; if (!m_fetchsqlite) { return matches; } QString query; if (addEverything) { query = QStringLiteral( "SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url " "FROM moz_bookmarks, moz_places WHERE " "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id"); } else { query = QStringLiteral( "SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url " "FROM moz_bookmarks, moz_places WHERE " "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id AND " "(moz_bookmarks.title LIKE :term OR moz_places.url LIKE :term)"); } const QMap bindVariables{ {QStringLiteral(":term"), QStringLiteral("%%%1%%").arg(term)}, }; const QList results = m_fetchsqlite->query(query, bindVariables); QMultiMap uniqueResults; for (const QVariantMap &result : results) { const QString title = result.value(QStringLiteral("title")).toString(); const QUrl url = result.value(QStringLiteral("url")).toUrl(); if (url.isEmpty() || url.scheme() == QLatin1String("place")) { // Don't use bookmarks with empty url or Firefox's "place:" scheme, // e.g. used for "Most Visited" or "Recent Tags" // qDebug() << "element " << url << " was not added"; continue; } auto urlString = url.toString(); // After joining we may have multiple results for each URL: // 1) one for each bookmark folder (same or different titles) // 2) one for each tag (no title for all but the first entry) auto keyRange = uniqueResults.equal_range(urlString); auto it = keyRange.first; if (!title.isEmpty()) { while (it != keyRange.second) { if (*it == title) { // same URL and title in multiple bookmark folders break; } if (it->isEmpty()) { // add a title if there was none for the URL *it = title; break; } ++it; } } if (it == keyRange.second) { // first or unique entry uniqueResults.insert(urlString, title); } } for (auto result = uniqueResults.constKeyValueBegin(); result != uniqueResults.constKeyValueEnd(); ++result) { const QString url = (*result).first; BookmarkMatch bookmarkMatch(m_favicon->iconFor(url), term, (*result).second, url); bookmarkMatch.addTo(matches, addEverything); } return matches; } void Firefox::teardown() { if (m_fetchsqlite) { m_fetchsqlite->teardown(); delete m_fetchsqlite; m_fetchsqlite = nullptr; } m_favicon->teardown(); }