Akonadi Search

indexeditems.cpp
1/*
2 * This file is part of the KDE Akonadi Search Project
3 * SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 *
7 */
8
9#include <xapian.h>
10
11#include "akonadi_search_pim_debug.h"
12#include "indexeditems.h"
13
14#include <Akonadi/ServerManager>
15#include <QDir>
16#include <QHash>
17#include <QStandardPaths>
18
19using namespace Akonadi::Search::PIM;
20
21class Akonadi::Search::PIM::IndexedItemsPrivate
22{
23public:
24 IndexedItemsPrivate() = default;
25
26 [[nodiscard]] QString dbPath(const QString &dbName) const;
27 [[nodiscard]] QString emailIndexingPath() const;
28 [[nodiscard]] QString collectionIndexingPath() const;
29 [[nodiscard]] QString calendarIndexingPath() const;
30 [[nodiscard]] QString akonotesIndexingPath() const;
31 [[nodiscard]] QString emailContactsIndexingPath() const;
32 [[nodiscard]] QString contactIndexingPath() const;
33
34 mutable QHash<QString, QString> m_cachePath;
35 QString m_overridePrefixPath;
36 [[nodiscard]] qlonglong indexedItems(const qlonglong id);
37 [[nodiscard]] qlonglong indexedItemsInDatabase(const std::string &term, const QString &dbPath) const;
38 void findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath);
39 void findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId);
40};
41
42QString IndexedItemsPrivate::dbPath(const QString &dbName) const
43{
44 const QString cachedPath = m_cachePath.value(dbName);
45 if (!cachedPath.isEmpty()) {
46 return cachedPath;
47 }
48 if (!m_overridePrefixPath.isEmpty()) {
49 const QString path = QStringLiteral("%1/%2/").arg(m_overridePrefixPath, dbName);
50 m_cachePath.insert(dbName, path);
51 return path;
52 }
53
54 // First look into the old location from Baloo times in ~/.local/share/baloo,
55 // because we don't migrate the database files automatically.
56 QString basePath;
57 bool hasInstanceIdentifier = Akonadi::ServerManager::hasInstanceIdentifier();
58 if (hasInstanceIdentifier) {
59 basePath = QStringLiteral("baloo/instances/%1").arg(Akonadi::ServerManager::instanceIdentifier());
60 } else {
61 basePath = QStringLiteral("baloo");
62 }
63 QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName);
64 if (QDir(dbPath).exists()) {
65 m_cachePath.insert(dbName, dbPath);
66 return dbPath;
67 }
68
69 // If the database does not exist in old Baloo folders, than use the new
70 // location in Akonadi's datadir in ~/.local/share/akonadi/search_db.
71 if (hasInstanceIdentifier) {
72 basePath = QStringLiteral("akonadi/instance/%1/search_db").arg(Akonadi::ServerManager::instanceIdentifier());
73 } else {
74 basePath = QStringLiteral("akonadi/search_db");
75 }
76 dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName);
77 QDir().mkpath(dbPath);
78 m_cachePath.insert(dbName, dbPath);
79 return dbPath;
80}
81
82QString IndexedItemsPrivate::emailIndexingPath() const
83{
84 return dbPath(QStringLiteral("email"));
85}
86
87QString IndexedItemsPrivate::contactIndexingPath() const
88{
89 return dbPath(QStringLiteral("contacts"));
90}
91
92QString IndexedItemsPrivate::emailContactsIndexingPath() const
93{
94 return dbPath(QStringLiteral("emailContacts"));
95}
96
97QString IndexedItemsPrivate::akonotesIndexingPath() const
98{
99 return dbPath(QStringLiteral("notes"));
100}
101
102QString IndexedItemsPrivate::calendarIndexingPath() const
103{
104 return dbPath(QStringLiteral("calendars"));
105}
106
107QString IndexedItemsPrivate::collectionIndexingPath() const
108{
109 return dbPath(QStringLiteral("collections"));
110}
111
112qlonglong IndexedItemsPrivate::indexedItemsInDatabase(const std::string &term, const QString &dbPath) const
113{
114 Xapian::Database db;
115 try {
116 db = Xapian::Database(QFile::encodeName(dbPath).toStdString());
117 } catch (const Xapian::DatabaseError &e) {
118 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to open database" << dbPath << ":" << QString::fromStdString(e.get_msg());
119 return 0;
120 }
121 return db.get_termfreq(term);
122}
123
124qlonglong IndexedItemsPrivate::indexedItems(const qlonglong id)
125{
126 const std::string term = QStringLiteral("C%1").arg(id).toStdString();
127 return indexedItemsInDatabase(term, emailIndexingPath()) + indexedItemsInDatabase(term, contactIndexingPath())
128 + indexedItemsInDatabase(term, akonotesIndexingPath()) + indexedItemsInDatabase(term, calendarIndexingPath());
129}
130
131void IndexedItemsPrivate::findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath)
132{
133 Xapian::Database db;
134 try {
135 db = Xapian::Database(QFile::encodeName(dbPath).toStdString());
136 } catch (const Xapian::DatabaseError &e) {
137 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to open database" << dbPath << ":" << QString::fromStdString(e.get_msg());
138 return;
139 }
140 const std::string term = QStringLiteral("C%1").arg(collectionId).toStdString();
141 const Xapian::Query query(term);
142 Xapian::Enquire enquire(db);
143 enquire.set_query(query);
144
145 auto getResults = [&enquire, &indexed]() {
146 Xapian::MSet mset;
147 mset = enquire.get_mset(0, UINT_MAX);
148 Xapian::MSetIterator it = mset.begin();
149 for (auto result : mset) {
150 indexed << result;
151 }
152 };
153
154 try {
155 getResults();
156 } catch (const Xapian::DatabaseModifiedError &e) {
157 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to read database" << dbPath << ":" << QString::fromStdString(e.get_msg());
158 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Calling reopen() on database" << dbPath << "and trying again";
159 if (db.reopen()) { // only try again once
160 try {
161 getResults();
162 } catch (const Xapian::DatabaseModifiedError &e) {
163 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to query database" << dbPath << "even after calling reopen()";
164 }
165 }
166 } catch (const Xapian::DatabaseCorruptError &e) {
167 qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to query database" << dbPath << ":" << QString::fromStdString(e.get_msg());
168 }
169}
170
171void IndexedItemsPrivate::findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId)
172{
173 findIndexedInDatabase(indexed, collectionId, emailIndexingPath());
174 findIndexedInDatabase(indexed, collectionId, contactIndexingPath());
175 findIndexedInDatabase(indexed, collectionId, akonotesIndexingPath());
176 findIndexedInDatabase(indexed, collectionId, calendarIndexingPath());
177}
178
179IndexedItems::IndexedItems(QObject *parent)
180 : QObject(parent)
181 , d(new Akonadi::Search::PIM::IndexedItemsPrivate())
182{
183}
184
185IndexedItems::~IndexedItems() = default;
186
187void IndexedItems::setOverrideDbPrefixPath(const QString &path)
188{
189 d->m_overridePrefixPath = path;
190 d->m_cachePath.clear();
191}
192
193qlonglong IndexedItems::indexedItems(const qlonglong id)
194{
195 return d->indexedItems(id);
196}
197
198void IndexedItems::findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath)
199{
200 d->findIndexedInDatabase(indexed, collectionId, dbPath);
201}
202
203void IndexedItems::findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId)
204{
205 d->findIndexed(indexed, collectionId);
206}
207
208QString IndexedItems::emailIndexingPath() const
209{
210 return d->emailIndexingPath();
211}
212
213QString IndexedItems::collectionIndexingPath() const
214{
215 return d->collectionIndexingPath();
216}
217
218QString IndexedItems::calendarIndexingPath() const
219{
220 return d->calendarIndexingPath();
221}
222
223QString IndexedItems::akonotesIndexingPath() const
224{
225 return d->akonotesIndexingPath();
226}
227
228QString IndexedItems::emailContactsIndexingPath() const
229{
230 return d->emailContactsIndexingPath();
231}
232
233QString IndexedItems::contactIndexingPath() const
234{
235 return d->contactIndexingPath();
236}
237
238#include "moc_indexeditems.cpp"
static bool hasInstanceIdentifier()
static QString instanceIdentifier()
PIM specific search API.
std::optional< QSqlQuery > query(const QString &queryStatement)
QString path(const QString &relativePath)
bool mkpath(const QString &dirPath) const const
QByteArray encodeName(const QString &fileName)
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
T qobject_cast(QObject *object)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
QString fromStdString(const std::string &str)
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:56:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.