File indexing completed on 2024-05-26 05:13:31
0001 /* 0002 * This file is part of the KDE Akonadi Search Project 0003 * SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 * 0007 */ 0008 0009 #include <xapian.h> 0010 0011 #include "akonadi_search_pim_debug.h" 0012 #include "indexeditems.h" 0013 0014 #include <Akonadi/ServerManager> 0015 #include <QDir> 0016 #include <QHash> 0017 #include <QStandardPaths> 0018 0019 using namespace Akonadi::Search::PIM; 0020 0021 class Akonadi::Search::PIM::IndexedItemsPrivate 0022 { 0023 public: 0024 IndexedItemsPrivate() = default; 0025 0026 [[nodiscard]] QString dbPath(const QString &dbName) const; 0027 [[nodiscard]] QString emailIndexingPath() const; 0028 [[nodiscard]] QString collectionIndexingPath() const; 0029 [[nodiscard]] QString calendarIndexingPath() const; 0030 [[nodiscard]] QString akonotesIndexingPath() const; 0031 [[nodiscard]] QString emailContactsIndexingPath() const; 0032 [[nodiscard]] QString contactIndexingPath() const; 0033 0034 mutable QHash<QString, QString> m_cachePath; 0035 QString m_overridePrefixPath; 0036 [[nodiscard]] qlonglong indexedItems(const qlonglong id); 0037 [[nodiscard]] qlonglong indexedItemsInDatabase(const std::string &term, const QString &dbPath) const; 0038 void findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath); 0039 void findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId); 0040 }; 0041 0042 QString IndexedItemsPrivate::dbPath(const QString &dbName) const 0043 { 0044 const QString cachedPath = m_cachePath.value(dbName); 0045 if (!cachedPath.isEmpty()) { 0046 return cachedPath; 0047 } 0048 if (!m_overridePrefixPath.isEmpty()) { 0049 const QString path = QStringLiteral("%1/%2/").arg(m_overridePrefixPath, dbName); 0050 m_cachePath.insert(dbName, path); 0051 return path; 0052 } 0053 0054 // First look into the old location from Baloo times in ~/.local/share/baloo, 0055 // because we don't migrate the database files automatically. 0056 QString basePath; 0057 bool hasInstanceIdentifier = Akonadi::ServerManager::hasInstanceIdentifier(); 0058 if (hasInstanceIdentifier) { 0059 basePath = QStringLiteral("baloo/instances/%1").arg(Akonadi::ServerManager::instanceIdentifier()); 0060 } else { 0061 basePath = QStringLiteral("baloo"); 0062 } 0063 QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName); 0064 if (QDir(dbPath).exists()) { 0065 m_cachePath.insert(dbName, dbPath); 0066 return dbPath; 0067 } 0068 0069 // If the database does not exist in old Baloo folders, than use the new 0070 // location in Akonadi's datadir in ~/.local/share/akonadi/search_db. 0071 if (hasInstanceIdentifier) { 0072 basePath = QStringLiteral("akonadi/instance/%1/search_db").arg(Akonadi::ServerManager::instanceIdentifier()); 0073 } else { 0074 basePath = QStringLiteral("akonadi/search_db"); 0075 } 0076 dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName); 0077 QDir().mkpath(dbPath); 0078 m_cachePath.insert(dbName, dbPath); 0079 return dbPath; 0080 } 0081 0082 QString IndexedItemsPrivate::emailIndexingPath() const 0083 { 0084 return dbPath(QStringLiteral("email")); 0085 } 0086 0087 QString IndexedItemsPrivate::contactIndexingPath() const 0088 { 0089 return dbPath(QStringLiteral("contacts")); 0090 } 0091 0092 QString IndexedItemsPrivate::emailContactsIndexingPath() const 0093 { 0094 return dbPath(QStringLiteral("emailContacts")); 0095 } 0096 0097 QString IndexedItemsPrivate::akonotesIndexingPath() const 0098 { 0099 return dbPath(QStringLiteral("notes")); 0100 } 0101 0102 QString IndexedItemsPrivate::calendarIndexingPath() const 0103 { 0104 return dbPath(QStringLiteral("calendars")); 0105 } 0106 0107 QString IndexedItemsPrivate::collectionIndexingPath() const 0108 { 0109 return dbPath(QStringLiteral("collections")); 0110 } 0111 0112 qlonglong IndexedItemsPrivate::indexedItemsInDatabase(const std::string &term, const QString &dbPath) const 0113 { 0114 Xapian::Database db; 0115 try { 0116 db = Xapian::Database(QFile::encodeName(dbPath).toStdString()); 0117 } catch (const Xapian::DatabaseError &e) { 0118 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to open database" << dbPath << ":" << QString::fromStdString(e.get_msg()); 0119 return 0; 0120 } 0121 return db.get_termfreq(term); 0122 } 0123 0124 qlonglong IndexedItemsPrivate::indexedItems(const qlonglong id) 0125 { 0126 const std::string term = QStringLiteral("C%1").arg(id).toStdString(); 0127 return indexedItemsInDatabase(term, emailIndexingPath()) + indexedItemsInDatabase(term, contactIndexingPath()) 0128 + indexedItemsInDatabase(term, akonotesIndexingPath()) + indexedItemsInDatabase(term, calendarIndexingPath()); 0129 } 0130 0131 void IndexedItemsPrivate::findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath) 0132 { 0133 Xapian::Database db; 0134 try { 0135 db = Xapian::Database(QFile::encodeName(dbPath).toStdString()); 0136 } catch (const Xapian::DatabaseError &e) { 0137 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to open database" << dbPath << ":" << QString::fromStdString(e.get_msg()); 0138 return; 0139 } 0140 const std::string term = QStringLiteral("C%1").arg(collectionId).toStdString(); 0141 const Xapian::Query query(term); 0142 Xapian::Enquire enquire(db); 0143 enquire.set_query(query); 0144 0145 auto getResults = [&enquire, &indexed]() { 0146 Xapian::MSet mset; 0147 mset = enquire.get_mset(0, UINT_MAX); 0148 Xapian::MSetIterator it = mset.begin(); 0149 for (auto result : mset) { 0150 indexed << result; 0151 } 0152 }; 0153 0154 try { 0155 getResults(); 0156 } catch (const Xapian::DatabaseModifiedError &e) { 0157 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to read database" << dbPath << ":" << QString::fromStdString(e.get_msg()); 0158 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Calling reopen() on database" << dbPath << "and trying again"; 0159 if (db.reopen()) { // only try again once 0160 try { 0161 getResults(); 0162 } catch (const Xapian::DatabaseModifiedError &e) { 0163 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to query database" << dbPath << "even after calling reopen()"; 0164 } 0165 } 0166 } catch (const Xapian::DatabaseCorruptError &e) { 0167 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to query database" << dbPath << ":" << QString::fromStdString(e.get_msg()); 0168 } 0169 } 0170 0171 void IndexedItemsPrivate::findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId) 0172 { 0173 findIndexedInDatabase(indexed, collectionId, emailIndexingPath()); 0174 findIndexedInDatabase(indexed, collectionId, contactIndexingPath()); 0175 findIndexedInDatabase(indexed, collectionId, akonotesIndexingPath()); 0176 findIndexedInDatabase(indexed, collectionId, calendarIndexingPath()); 0177 } 0178 0179 IndexedItems::IndexedItems(QObject *parent) 0180 : QObject(parent) 0181 , d(new Akonadi::Search::PIM::IndexedItemsPrivate()) 0182 { 0183 } 0184 0185 IndexedItems::~IndexedItems() = default; 0186 0187 void IndexedItems::setOverrideDbPrefixPath(const QString &path) 0188 { 0189 d->m_overridePrefixPath = path; 0190 d->m_cachePath.clear(); 0191 } 0192 0193 qlonglong IndexedItems::indexedItems(const qlonglong id) 0194 { 0195 return d->indexedItems(id); 0196 } 0197 0198 void IndexedItems::findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath) 0199 { 0200 d->findIndexedInDatabase(indexed, collectionId, dbPath); 0201 } 0202 0203 void IndexedItems::findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId) 0204 { 0205 d->findIndexed(indexed, collectionId); 0206 } 0207 0208 QString IndexedItems::emailIndexingPath() const 0209 { 0210 return d->emailIndexingPath(); 0211 } 0212 0213 QString IndexedItems::collectionIndexingPath() const 0214 { 0215 return d->collectionIndexingPath(); 0216 } 0217 0218 QString IndexedItems::calendarIndexingPath() const 0219 { 0220 return d->calendarIndexingPath(); 0221 } 0222 0223 QString IndexedItems::akonotesIndexingPath() const 0224 { 0225 return d->akonotesIndexingPath(); 0226 } 0227 0228 QString IndexedItems::emailContactsIndexingPath() const 0229 { 0230 return d->emailContactsIndexingPath(); 0231 } 0232 0233 QString IndexedItems::contactIndexingPath() const 0234 { 0235 return d->contactIndexingPath(); 0236 } 0237 0238 #include "moc_indexeditems.cpp"