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"