File indexing completed on 2024-05-12 05:11:16
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 * 0006 */ 0007 0008 #include <xapian.h> 0009 0010 #include "akonadi_indexer_agent_debug.h" 0011 #include "akonotesindexer.h" 0012 #include "calendarindexer.h" 0013 #include "contactindexer.h" 0014 #include "emailindexer.h" 0015 #include "index.h" 0016 #include "indexeditems.h" 0017 0018 #include <Akonadi/ServerManager> 0019 #include <QDir> 0020 #include <QStandardPaths> 0021 #include <chrono> 0022 0023 using namespace std::chrono_literals; 0024 0025 using namespace Akonadi::Search::PIM; 0026 Index::Index(QObject *parent) 0027 : QObject(parent) 0028 { 0029 m_indexedItems = new IndexedItems(this); 0030 m_commitTimer.setInterval(1s); 0031 m_commitTimer.setSingleShot(true); 0032 connect(&m_commitTimer, &QTimer::timeout, this, &Index::commit); 0033 } 0034 0035 Index::~Index() 0036 { 0037 } 0038 0039 static void removeDir(const QString &dirName) 0040 { 0041 QDir dir(dirName); 0042 if (dir.exists(dirName)) { 0043 const auto dirs = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); 0044 for (const QFileInfo &info : dirs) { 0045 if (info.isDir()) { 0046 removeDir(info.absoluteFilePath()); 0047 } else { 0048 QFile::remove(info.absoluteFilePath()); 0049 } 0050 } 0051 dir.rmdir(dirName); 0052 } 0053 } 0054 0055 void Index::removeDatabase() 0056 { 0057 m_collectionIndexer.reset(); 0058 m_listIndexer.clear(); 0059 m_indexer.clear(); 0060 0061 qCDebug(AKONADI_INDEXER_AGENT_LOG) << "Removing database"; 0062 removeDir(m_indexedItems->emailIndexingPath()); 0063 removeDir(m_indexedItems->contactIndexingPath()); 0064 removeDir(m_indexedItems->emailContactsIndexingPath()); 0065 removeDir(m_indexedItems->akonotesIndexingPath()); 0066 removeDir(m_indexedItems->calendarIndexingPath()); 0067 removeDir(m_indexedItems->collectionIndexingPath()); 0068 } 0069 0070 std::shared_ptr<AbstractIndexer> Index::indexerForItem(const Akonadi::Item &item) const 0071 { 0072 return m_indexer.value(item.mimeType()); 0073 } 0074 0075 QList<std::shared_ptr<AbstractIndexer>> Index::indexersForMimetypes(const QStringList &mimeTypes) const 0076 { 0077 QList<std::shared_ptr<AbstractIndexer>> indexers; 0078 for (const QString &mimeType : mimeTypes) { 0079 auto i = m_indexer.value(mimeType); 0080 if (i) { 0081 indexers.append(i); 0082 } 0083 } 0084 return indexers; 0085 } 0086 0087 bool Index::haveIndexerForMimeTypes(const QStringList &mimeTypes) 0088 { 0089 return !indexersForMimetypes(mimeTypes).isEmpty(); 0090 } 0091 0092 void Index::index(const Akonadi::Item &item) 0093 { 0094 auto indexer = indexerForItem(item); 0095 if (!indexer) { 0096 qCWarning(AKONADI_INDEXER_AGENT_LOG) << " No indexer found for item"; 0097 return; 0098 } 0099 0100 try { 0101 indexer->index(item); 0102 } catch (const Xapian::Error &e) { 0103 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0104 } 0105 } 0106 0107 void Index::move(const Akonadi::Item::List &items, const Akonadi::Collection &from, const Akonadi::Collection &to) 0108 { 0109 // We always get items of the same type 0110 auto indexer = indexerForItem(items.first()); 0111 if (!indexer) { 0112 return; 0113 } 0114 for (const Akonadi::Item &item : items) { 0115 try { 0116 indexer->move(item.id(), from.id(), to.id()); 0117 } catch (const Xapian::Error &e) { 0118 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0119 } 0120 } 0121 } 0122 0123 void Index::updateFlags(const Akonadi::Item::List &items, const QSet<QByteArray> &addedFlags, const QSet<QByteArray> &removedFlags) 0124 { 0125 // We always get items of the same type 0126 auto indexer = indexerForItem(items.first()); 0127 if (!indexer) { 0128 return; 0129 } 0130 for (const Akonadi::Item &item : items) { 0131 try { 0132 indexer->updateFlags(item, addedFlags, removedFlags); 0133 } catch (const Xapian::Error &e) { 0134 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0135 } 0136 } 0137 } 0138 0139 void Index::remove(const QSet<Akonadi::Item::Id> &ids, const QStringList &mimeTypes) 0140 { 0141 const auto indexers = indexersForMimetypes(mimeTypes); 0142 for (Akonadi::Item::Id id : ids) { 0143 for (const auto &indexer : indexers) { 0144 try { 0145 indexer->remove(Akonadi::Item(id)); 0146 } catch (const Xapian::Error &e) { 0147 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0148 } 0149 } 0150 } 0151 } 0152 0153 void Index::remove(const Akonadi::Item::List &items) 0154 { 0155 auto indexer = indexerForItem(items.first()); 0156 if (!indexer) { 0157 return; 0158 } 0159 for (const Akonadi::Item &item : items) { 0160 try { 0161 indexer->remove(item); 0162 } catch (const Xapian::Error &e) { 0163 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0164 } 0165 } 0166 } 0167 0168 void Index::index(const Akonadi::Collection &collection) 0169 { 0170 if (m_collectionIndexer) { 0171 m_collectionIndexer->index(collection); 0172 m_collectionIndexer->commit(); 0173 } 0174 qCDebug(AKONADI_INDEXER_AGENT_LOG) << "indexed " << collection.id(); 0175 } 0176 0177 void Index::change(const Akonadi::Collection &col) 0178 { 0179 if (m_collectionIndexer) { 0180 m_collectionIndexer->change(col); 0181 m_collectionIndexer->commit(); 0182 } 0183 } 0184 0185 void Index::remove(const Akonadi::Collection &col) 0186 { 0187 // Remove items 0188 const auto indexers = indexersForMimetypes(col.contentMimeTypes()); 0189 for (const auto &indexer : indexers) { 0190 try { 0191 indexer->remove(col); 0192 } catch (const Xapian::Error &e) { 0193 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0194 } 0195 } 0196 0197 if (m_collectionIndexer) { 0198 m_collectionIndexer->remove(col); 0199 m_collectionIndexer->commit(); 0200 } 0201 } 0202 0203 void Index::move(const Akonadi::Collection &collection, const Akonadi::Collection &from, const Akonadi::Collection &to) 0204 { 0205 if (m_collectionIndexer) { 0206 m_collectionIndexer->move(collection, from, to); 0207 m_collectionIndexer->commit(); 0208 } 0209 } 0210 0211 void Index::addIndexer(std::shared_ptr<AbstractIndexer> indexer) 0212 { 0213 m_listIndexer.append(indexer); 0214 const QStringList indexerMimetypes = indexer->mimeTypes(); 0215 for (const QString &mimeType : indexerMimetypes) { 0216 m_indexer.insert(mimeType, indexer); 0217 } 0218 } 0219 0220 bool Index::createIndexers() 0221 { 0222 std::unique_ptr<AbstractIndexer> indexer; 0223 try { 0224 QDir().mkpath(m_indexedItems->emailIndexingPath()); 0225 QDir().mkpath(m_indexedItems->emailContactsIndexingPath()); 0226 indexer = std::make_unique<EmailIndexer>(m_indexedItems->emailIndexingPath(), m_indexedItems->emailContactsIndexingPath()); 0227 indexer->setRespectDiacriticAndAccents(mRespectDiacriticAndAccents); 0228 addIndexer(std::move(indexer)); 0229 } catch (const Xapian::DatabaseError &e) { 0230 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create email indexer:" << QString::fromStdString(e.get_msg()); 0231 } catch (...) { 0232 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; 0233 } 0234 0235 try { 0236 QDir().mkpath(m_indexedItems->contactIndexingPath()); 0237 indexer = std::make_unique<ContactIndexer>(m_indexedItems->contactIndexingPath()); 0238 indexer->setRespectDiacriticAndAccents(mRespectDiacriticAndAccents); 0239 addIndexer(std::move(indexer)); 0240 } catch (const Xapian::DatabaseError &e) { 0241 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create contact indexer:" << QString::fromStdString(e.get_msg()); 0242 } catch (...) { 0243 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; 0244 } 0245 0246 try { 0247 QDir().mkpath(m_indexedItems->akonotesIndexingPath()); 0248 indexer = std::make_unique<AkonotesIndexer>(m_indexedItems->akonotesIndexingPath()); 0249 indexer->setRespectDiacriticAndAccents(mRespectDiacriticAndAccents); 0250 addIndexer(std::move(indexer)); 0251 } catch (const Xapian::DatabaseError &e) { 0252 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create akonotes indexer:" << QString::fromStdString(e.get_msg()); 0253 } catch (...) { 0254 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; 0255 } 0256 0257 try { 0258 QDir().mkpath(m_indexedItems->calendarIndexingPath()); 0259 indexer = std::make_unique<CalendarIndexer>(m_indexedItems->calendarIndexingPath()); 0260 indexer->setRespectDiacriticAndAccents(mRespectDiacriticAndAccents); 0261 addIndexer(std::move(indexer)); 0262 } catch (const Xapian::DatabaseError &e) { 0263 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create akonotes indexer:" << QString::fromStdString(e.get_msg()); 0264 } catch (...) { 0265 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; 0266 } 0267 0268 try { 0269 QDir().mkpath(m_indexedItems->collectionIndexingPath()); 0270 m_collectionIndexer = std::make_unique<CollectionIndexer>(m_indexedItems->collectionIndexingPath()); 0271 } catch (const Xapian::DatabaseError &e) { 0272 m_collectionIndexer.reset(); 0273 m_collectionIndexer = nullptr; 0274 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create akonotes indexer:" << QString::fromStdString(e.get_msg()); 0275 } catch (...) { 0276 m_collectionIndexer.reset(); 0277 m_collectionIndexer = nullptr; 0278 qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; 0279 } 0280 0281 return !m_indexer.isEmpty(); 0282 } 0283 0284 void Index::scheduleCommit() 0285 { 0286 if (!m_commitTimer.isActive()) { 0287 m_commitTimer.start(); 0288 } 0289 } 0290 0291 void Index::commit() 0292 { 0293 m_commitTimer.stop(); 0294 for (const std::shared_ptr<AbstractIndexer> &indexer : std::as_const(m_listIndexer)) { 0295 try { 0296 indexer->commit(); 0297 } catch (const Xapian::Error &e) { 0298 qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer.get() << ":" << e.get_msg().c_str(); 0299 } 0300 } 0301 } 0302 0303 void Index::findIndexed(QSet<Akonadi::Item::Id> &indexed, Akonadi::Collection::Id collectionId) 0304 { 0305 m_indexedItems->findIndexed(indexed, collectionId); 0306 } 0307 0308 qlonglong Index::indexedItems(const qlonglong id) 0309 { 0310 return m_indexedItems->indexedItems(id); 0311 } 0312 0313 void Index::setOverrideDbPrefixPath(const QString &path) 0314 { 0315 m_indexedItems->setOverrideDbPrefixPath(path); 0316 } 0317 0318 void Index::setRespectDiacriticAndAccents(bool b) 0319 { 0320 mRespectDiacriticAndAccents = b; 0321 } 0322 0323 #include "moc_index.cpp"