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"