Akonadi Search

indexeditems.cpp
1 /*
2  * This file is part of the KDE Akonadi Search Project
3  * SPDX-FileCopyrightText: 2016-2023 Laurent Montel <[email protected]>
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 
19 using namespace Akonadi::Search::PIM;
20 
21 class Akonadi::Search::PIM::IndexedItemsPrivate
22 {
23 public:
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 
42 QString 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 
82 QString IndexedItemsPrivate::emailIndexingPath() const
83 {
84  return dbPath(QStringLiteral("email"));
85 }
86 
87 QString IndexedItemsPrivate::contactIndexingPath() const
88 {
89  return dbPath(QStringLiteral("contacts"));
90 }
91 
92 QString IndexedItemsPrivate::emailContactsIndexingPath() const
93 {
94  return dbPath(QStringLiteral("emailContacts"));
95 }
96 
97 QString IndexedItemsPrivate::akonotesIndexingPath() const
98 {
99  return dbPath(QStringLiteral("notes"));
100 }
101 
102 QString IndexedItemsPrivate::calendarIndexingPath() const
103 {
104  return dbPath(QStringLiteral("calendars"));
105 }
106 
107 QString IndexedItemsPrivate::collectionIndexingPath() const
108 {
109  return dbPath(QStringLiteral("collections"));
110 }
111 
112 qlonglong 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 
124 qlonglong 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 
131 void 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 
171 void 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 
179 IndexedItems::IndexedItems(QObject *parent)
180  : QObject(parent)
181  , d(new Akonadi::Search::PIM::IndexedItemsPrivate())
182 {
183 }
184 
185 IndexedItems::~IndexedItems() = default;
186 
187 void IndexedItems::setOverrideDbPrefixPath(const QString &path)
188 {
189  d->m_overridePrefixPath = path;
190  d->m_cachePath.clear();
191 }
192 
193 qlonglong IndexedItems::indexedItems(const qlonglong id)
194 {
195  return d->indexedItems(id);
196 }
197 
198 void IndexedItems::findIndexedInDatabase(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath)
199 {
200  d->findIndexedInDatabase(indexed, collectionId, dbPath);
201 }
202 
203 void IndexedItems::findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId)
204 {
205  d->findIndexed(indexed, collectionId);
206 }
207 
208 QString IndexedItems::emailIndexingPath() const
209 {
210  return d->emailIndexingPath();
211 }
212 
213 QString IndexedItems::collectionIndexingPath() const
214 {
215  return d->collectionIndexingPath();
216 }
217 
218 QString IndexedItems::calendarIndexingPath() const
219 {
220  return d->calendarIndexingPath();
221 }
222 
223 QString IndexedItems::akonotesIndexingPath() const
224 {
225  return d->akonotesIndexingPath();
226 }
227 
228 QString IndexedItems::emailContactsIndexingPath() const
229 {
230  return d->emailContactsIndexingPath();
231 }
232 
233 QString IndexedItems::contactIndexingPath() const
234 {
235  return d->contactIndexingPath();
236 }
237 
238 #include "moc_indexeditems.cpp"
std::optional< QSqlQuery > query(const QString &queryStatement)
QByteArray encodeName(const QString &fileName)
QString writableLocation(QStandardPaths::StandardLocation type)
QString fromStdString(const std::string &str)
bool isEmpty() const const
bool mkpath(const QString &dirPath) const const
static QString instanceIdentifier()
PIM specific search API.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString path(const QString &relativePath)
QString & insert(int position, QChar ch)
static bool hasInstanceIdentifier()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Dec 3 2023 04:09:27 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.