File indexing completed on 2024-05-12 05:11:15

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 "collectionindexer.h"
0011 #include "xapiandocument.h"
0012 
0013 #include <Akonadi/AttributeFactory>
0014 #include <Akonadi/CollectionIdentificationAttribute>
0015 #include <QStringList>
0016 
0017 #include "akonadi_indexer_agent_debug.h"
0018 
0019 CollectionIndexer::CollectionIndexer(const QString &path)
0020 {
0021     Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionIdentificationAttribute>();
0022 
0023     try {
0024         m_db = new Xapian::WritableDatabase(path.toStdString(), Xapian::DB_CREATE_OR_OPEN);
0025     } catch (const Xapian::DatabaseCorruptError &err) {
0026         qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Database Corrupted - What did you do?";
0027         qCCritical(AKONADI_INDEXER_AGENT_LOG) << err.get_error_string();
0028         m_db = nullptr;
0029     } catch (const Xapian::Error &e) {
0030         qCCritical(AKONADI_INDEXER_AGENT_LOG) << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description());
0031         m_db = nullptr;
0032     }
0033 }
0034 
0035 CollectionIndexer::~CollectionIndexer()
0036 {
0037     commit();
0038     delete m_db;
0039 }
0040 
0041 static QByteArray getPath(const Akonadi::Collection &collection)
0042 {
0043     QStringList pathParts = {collection.displayName()};
0044     Akonadi::Collection col = collection;
0045     while (col.parentCollection().isValid() && (col.parentCollection() != Akonadi::Collection::root())) {
0046         col = col.parentCollection();
0047         pathParts.prepend(col.displayName());
0048     }
0049     return "/" + pathParts.join(QLatin1Char('/')).toUtf8();
0050 }
0051 
0052 void CollectionIndexer::index(const Akonadi::Collection &collection)
0053 {
0054     if (!m_db) {
0055         return;
0056     }
0057     // qCDebug(AKONADI_INDEXER_AGENT_LOG) << "Indexing " << collection.id() << collection.displayName() << collection.name();
0058 
0059     try {
0060         Xapian::Document doc;
0061         Xapian::TermGenerator gen;
0062         gen.set_document(doc);
0063         gen.set_database(*m_db);
0064 
0065         gen.index_text_without_positions(collection.displayName().toStdString());
0066         gen.index_text_without_positions(collection.displayName().toStdString(), 1, "N");
0067 
0068         // We index with positions so we can do phrase searches (required for exact matches)
0069         {
0070             const QByteArray path = getPath(collection);
0071             gen.index_text(path.constData(), 1, "P");
0072             const QByteArray term = "A" + path;
0073             doc.add_term(term.constData());
0074         }
0075 
0076         const Akonadi::Collection::Id colId = collection.parentCollection().id();
0077         const QByteArray term = 'C' + QByteArray::number(colId);
0078         doc.add_boolean_term(term.constData());
0079 
0080         QByteArray ns;
0081         if (const auto folderAttribute = collection.attribute<Akonadi::CollectionIdentificationAttribute>()) {
0082             if (!folderAttribute->collectionNamespace().isEmpty()) {
0083                 ns = folderAttribute->collectionNamespace();
0084             }
0085             if (!folderAttribute->identifier().isEmpty()) {
0086                 const QByteArray term = "ID" + folderAttribute->identifier();
0087                 doc.add_boolean_term(term.constData());
0088             }
0089         }
0090         {
0091             // We must add the term also with an empty namespace, so we can search for that as well
0092             const QByteArray term = "NS" + ns;
0093             doc.add_boolean_term(term.constData());
0094         }
0095         const QStringList contentMimeTypes = collection.contentMimeTypes();
0096         for (const QString &mt : contentMimeTypes) {
0097             const QByteArray term = "M" + mt.toUtf8();
0098             doc.add_boolean_term(term.constData());
0099         }
0100 
0101         m_db->replace_document(collection.id(), doc);
0102     } catch (const Xapian::Error &e) {
0103         qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer:" << e.get_msg().c_str();
0104     }
0105 }
0106 
0107 void CollectionIndexer::change(const Akonadi::Collection &col)
0108 {
0109     index(col);
0110 }
0111 
0112 void CollectionIndexer::remove(const Akonadi::Collection &col)
0113 {
0114     if (!m_db) {
0115         return;
0116     }
0117 
0118     // Remove collection
0119     try {
0120         m_db->delete_document(col.id());
0121     } catch (const Xapian::Error &e) {
0122         qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer:" << e.get_msg().c_str();
0123     }
0124 
0125     // Remove subcollections
0126     try {
0127         Xapian::Query query('C' + QString::number(col.id()).toStdString());
0128         Xapian::Enquire enquire(*m_db);
0129         enquire.set_query(query);
0130 
0131         Xapian::MSet mset = enquire.get_mset(0, m_db->get_doccount());
0132         Xapian::MSetIterator end = mset.end();
0133         for (Xapian::MSetIterator it = mset.begin(); it != end; ++it) {
0134             const qint64 id = *it;
0135             remove(Akonadi::Collection(id));
0136         }
0137     } catch (const Xapian::DocNotFoundError &) {
0138         return;
0139     }
0140 }
0141 
0142 void CollectionIndexer::move(const Akonadi::Collection &collection, const Akonadi::Collection &from, const Akonadi::Collection &to)
0143 {
0144     Q_UNUSED(from)
0145     Q_UNUSED(to)
0146     index(collection);
0147 }
0148 
0149 void CollectionIndexer::commit()
0150 {
0151     if (!m_db) {
0152         return;
0153     }
0154 
0155     try {
0156         m_db->commit();
0157     } catch (const Xapian::Error &err) {
0158         qCWarning(AKONADI_INDEXER_AGENT_LOG) << err.get_error_string();
0159     }
0160     qCDebug(AKONADI_INDEXER_AGENT_LOG) << "Xapian Committed";
0161 }
0162 
0163 #include "moc_collectionindexer.cpp"