diff --git a/agent/akonotesindexer.h b/agent/akonotesindexer.h index 0fcf468..8ff1f48 100644 --- a/agent/akonotesindexer.h +++ b/agent/akonotesindexer.h @@ -1,62 +1,62 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2014-2018 Laurent Montel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef AKONOTESINDEXER_H #define AKONOTESINDEXER_H +#include + #include "abstractindexer.h" #include #include #include -#include - class AkonotesIndexer : public AbstractIndexer { public: /** * You must provide the path where the indexed information * should be stored */ explicit AkonotesIndexer(const QString &path); ~AkonotesIndexer(); QStringList mimeTypes() const override; void index(const Akonadi::Item &item) override; void commit() override; void remove(const Akonadi::Item &item) override; void remove(const Akonadi::Collection &collection) override; void move(Akonadi::Item::Id itemId, Akonadi::Collection::Id from, Akonadi::Collection::Id to) override; private: void processPart(KMime::Content *content, KMime::Content *mainContent); void process(const KMime::Message::Ptr &msg); Xapian::WritableDatabase *m_db = nullptr; Xapian::Document *m_doc = nullptr; Xapian::TermGenerator *m_termGen = nullptr; }; #endif // AKONOTESINDEXER_H diff --git a/agent/collectionindexer.cpp b/agent/collectionindexer.cpp index 79210f3..4b7ccbc 100644 --- a/agent/collectionindexer.cpp +++ b/agent/collectionindexer.cpp @@ -1,168 +1,171 @@ /* * Copyright 2014 Christian Mollekopf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ + +#include + #include "collectionindexer.h" +#include "xapiandocument.h" #include #include -#include #include #include -#include + #include "akonadi_indexer_agent_debug.h" CollectionIndexer::CollectionIndexer(const QString &path) { Akonadi::AttributeFactory::registerAttribute(); try { m_db = new Xapian::WritableDatabase(path.toUtf8().constData(), Xapian::DB_CREATE_OR_OPEN); } catch (const Xapian::DatabaseCorruptError &err) { qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Database Corrupted - What did you do?"; qCCritical(AKONADI_INDEXER_AGENT_LOG) << err.get_error_string(); m_db = nullptr; } catch (const Xapian::Error &e) { qCCritical(AKONADI_INDEXER_AGENT_LOG) << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description()); m_db = nullptr; } } CollectionIndexer::~CollectionIndexer() { commit(); delete m_db; } static QByteArray getPath(const Akonadi::Collection &collection) { QStringList pathParts; pathParts << collection.displayName(); Akonadi::Collection col = collection; while (col.parentCollection().isValid() && (col.parentCollection() != Akonadi::Collection::root())) { col = col.parentCollection(); pathParts.prepend(col.displayName()); } return "/" + pathParts.join(QLatin1Char('/')).toUtf8(); } void CollectionIndexer::index(const Akonadi::Collection &collection) { if (!m_db) { return; } //qCDebug(AKONADI_INDEXER_AGENT_LOG) << "Indexing " << collection.id() << collection.displayName() << collection.name(); try { Xapian::Document doc; Xapian::TermGenerator gen; gen.set_document(doc); gen.set_database(*m_db); gen.index_text_without_positions(collection.displayName().toUtf8().constData()); gen.index_text_without_positions(collection.displayName().toUtf8().constData(), 1, "N"); //We index with positions so we can do phrase searches (required for exact matches) { const QByteArray path = getPath(collection); gen.index_text(path.constData(), 1, "P"); const QByteArray term = "A" + path; doc.add_term(term.constData()); } Akonadi::Collection::Id colId = collection.parentCollection().id(); const QByteArray term = 'C' + QByteArray::number(colId); doc.add_boolean_term(term.constData()); QByteArray ns; if (Akonadi::CollectionIdentificationAttribute *folderAttribute = collection.attribute()) { if (!folderAttribute->collectionNamespace().isEmpty()) { ns = folderAttribute->collectionNamespace(); } if (!folderAttribute->identifier().isEmpty()) { const QByteArray term = "ID" + folderAttribute->identifier(); doc.add_boolean_term(term.constData()); } } { //We must add the term also with an empty namespace, so we can search for that as well const QByteArray term = "NS" + ns; doc.add_boolean_term(term.constData()); } for (const QString &mt : collection.contentMimeTypes()) { const QByteArray term = "M" + mt.toUtf8(); doc.add_boolean_term(term.constData()); } m_db->replace_document(collection.id(), doc); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer:" << e.get_msg().c_str(); } } void CollectionIndexer::change(const Akonadi::Collection &col) { index(col); } void CollectionIndexer::remove(const Akonadi::Collection &col) { if (!m_db) { return; } //Remove collection try { m_db->delete_document(col.id()); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer:" << e.get_msg().c_str(); } //Remove subcollections try { Xapian::Query query('C' + QString::number(col.id()).toStdString()); Xapian::Enquire enquire(*m_db); enquire.set_query(query); Xapian::MSet mset = enquire.get_mset(0, m_db->get_doccount()); Xapian::MSetIterator end = mset.end(); for (Xapian::MSetIterator it = mset.begin(); it != end; ++it) { const qint64 id = *it; remove(Akonadi::Collection(id)); } } catch (const Xapian::DocNotFoundError &) { return; } } void CollectionIndexer::move(const Akonadi::Collection &collection, const Akonadi::Collection &from, const Akonadi::Collection &to) { Q_UNUSED(from); Q_UNUSED(to); index(collection); } void CollectionIndexer::commit() { if (m_db) { m_db->commit(); } } diff --git a/agent/emailindexer.h b/agent/emailindexer.h index fbcbdd0..050355d 100644 --- a/agent/emailindexer.h +++ b/agent/emailindexer.h @@ -1,77 +1,77 @@ /* * * Copyright 2013 Vishesh Handa * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef EMAILINDEXER_H #define EMAILINDEXER_H -#include "abstractindexer.h" - #include +#include "abstractindexer.h" + #include #include class EmailIndexer: public AbstractIndexer { public: /** * You must provide the path where the indexed information * should be stored */ EmailIndexer(const QString &path, const QString &contactDbPath); ~EmailIndexer(); QStringList mimeTypes() const override; void index(const Akonadi::Item &item) override; void updateFlags(const Akonadi::Item &item, const QSet &added, const QSet &removed) override; void remove(const Akonadi::Item &item) override; void remove(const Akonadi::Collection &item) override; void move(Akonadi::Item::Id itemId, Akonadi::Collection::Id from, Akonadi::Collection::Id to) override; void commit() override; private: Xapian::WritableDatabase *m_db = nullptr; Xapian::Document *m_doc = nullptr; Xapian::TermGenerator *m_termGen = nullptr; Xapian::WritableDatabase *m_contactDb = nullptr; void toggleFlag(Xapian::Document &doc, const char *remove, const char *add); void process(const KMime::Message::Ptr &msg); void processPart(KMime::Content *content, KMime::Content *mainContent); void processMessageStatus(const Akonadi::MessageStatus &status); void insert(const QByteArray &key, KMime::Headers::Base *base); void insert(const QByteArray &key, KMime::Headers::Generics::MailboxList *mlist); void insert(const QByteArray &key, KMime::Headers::Generics::AddressList *alist); void insert(const QByteArray &key, const KMime::Types::Mailbox::List &list); void insertBool(char key, bool value); }; #endif // EMAILINDEXER_H diff --git a/agent/index.cpp b/agent/index.cpp index 215c1bd..5a922ad 100644 --- a/agent/index.cpp +++ b/agent/index.cpp @@ -1,335 +1,336 @@ /* * Copyright 2014 Christian Mollekopf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ + +#include + #include "index.h" #include "akonadi_indexer_agent_debug.h" #include "emailindexer.h" #include "contactindexer.h" #include "akonotesindexer.h" #include "calendarindexer.h" - - #include "indexeditems.h" + #include #include #include -#include using namespace Akonadi::Search::PIM; Index::Index(QObject *parent) : QObject(parent), m_collectionIndexer(nullptr) { m_indexedItems = new IndexedItems(this); m_commitTimer.setInterval(1000); m_commitTimer.setSingleShot(true); connect(&m_commitTimer, &QTimer::timeout, this, &Index::commit); } Index::~Index() { delete m_collectionIndexer; m_collectionIndexer = nullptr; qDeleteAll(m_listIndexer); } static void removeDir(const QString &dirName) { QDir dir(dirName); if (dir.exists(dirName)) { for (const QFileInfo &info : dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) { if (info.isDir()) { removeDir(info.absoluteFilePath()); } else { QFile::remove(info.absoluteFilePath()); } } dir.rmdir(dirName); } } void Index::removeDatabase() { delete m_collectionIndexer; m_collectionIndexer = nullptr; qDeleteAll(m_listIndexer); m_listIndexer.clear(); m_indexer.clear(); qCDebug(AKONADI_INDEXER_AGENT_LOG) << "Removing database"; removeDir(m_indexedItems->emailIndexingPath()); removeDir(m_indexedItems->contactIndexingPath()); removeDir(m_indexedItems->emailContactsIndexingPath()); removeDir(m_indexedItems->akonotesIndexingPath()); removeDir(m_indexedItems->calendarIndexingPath()); removeDir(m_indexedItems->collectionIndexingPath()); } AbstractIndexer *Index::indexerForItem(const Akonadi::Item &item) const { return m_indexer.value(item.mimeType()); } QList Index::indexersForMimetypes(const QStringList &mimeTypes) const { QList indexers; for (const QString &mimeType : mimeTypes) { AbstractIndexer *i = m_indexer.value(mimeType); if (i) { indexers.append(i); } } return indexers; } bool Index::haveIndexerForMimeTypes(const QStringList &mimeTypes) { return !indexersForMimetypes(mimeTypes).isEmpty(); } void Index::index(const Akonadi::Item &item) { AbstractIndexer *indexer = indexerForItem(item); if (!indexer) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << " No indexer found for item"; return; } try { indexer->index(item); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } void Index::move(const Akonadi::Item::List &items, const Akonadi::Collection &from, const Akonadi::Collection &to) { //We always get items of the same type AbstractIndexer *indexer = indexerForItem(items.first()); if (!indexer) { return; } for (const Akonadi::Item &item : items) { try { indexer->move(item.id(), from.id(), to.id()); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } } void Index::updateFlags(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags) { //We always get items of the same type AbstractIndexer *indexer = indexerForItem(items.first()); if (!indexer) { return; } for (const Akonadi::Item &item : items) { try { indexer->updateFlags(item, addedFlags, removedFlags); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } } void Index::remove(const QSet &ids, const QStringList &mimeTypes) { const QList indexers = indexersForMimetypes(mimeTypes); for (Akonadi::Item::Id id : ids) { for (AbstractIndexer *indexer : indexers) { try { indexer->remove(Akonadi::Item(id)); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } } } void Index::remove(const Akonadi::Item::List &items) { AbstractIndexer *indexer = indexerForItem(items.first()); if (!indexer) { return; } for (const Akonadi::Item &item : items) { try { indexer->remove(item); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } } void Index::index(const Akonadi::Collection &collection) { if (m_collectionIndexer) { m_collectionIndexer->index(collection); m_collectionIndexer->commit(); } qCDebug(AKONADI_INDEXER_AGENT_LOG) << "indexed " << collection.id(); } void Index::change(const Akonadi::Collection &col) { if (m_collectionIndexer) { m_collectionIndexer->change(col); m_collectionIndexer->commit(); } } void Index::remove(const Akonadi::Collection &col) { //Remove items for (AbstractIndexer *indexer : indexersForMimetypes(col.contentMimeTypes())) { try { indexer->remove(col); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } if (m_collectionIndexer) { m_collectionIndexer->remove(col); m_collectionIndexer->commit(); } } void Index::move(const Akonadi::Collection &collection, const Akonadi::Collection &from, const Akonadi::Collection &to) { if (m_collectionIndexer) { m_collectionIndexer->move(collection, from, to); m_collectionIndexer->commit(); } } void Index::addIndexer(AbstractIndexer *indexer) { m_listIndexer.append(indexer); for (const QString &mimeType : indexer->mimeTypes()) { m_indexer.insert(mimeType, indexer); } } bool Index::createIndexers() { AbstractIndexer *indexer = nullptr; try { QDir().mkpath(m_indexedItems->emailIndexingPath()); QDir().mkpath(m_indexedItems->emailContactsIndexingPath()); indexer = new EmailIndexer(m_indexedItems->emailIndexingPath(), m_indexedItems->emailContactsIndexingPath()); addIndexer(indexer); } catch (const Xapian::DatabaseError &e) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create email indexer:" << QString::fromStdString(e.get_msg()); } catch (...) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; } try { QDir().mkpath(m_indexedItems->contactIndexingPath()); indexer = new ContactIndexer(m_indexedItems->contactIndexingPath()); addIndexer(indexer); } catch (const Xapian::DatabaseError &e) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create contact indexer:" << QString::fromStdString(e.get_msg()); } catch (...) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; } try { QDir().mkpath(m_indexedItems->akonotesIndexingPath()); indexer = new AkonotesIndexer(m_indexedItems->akonotesIndexingPath()); addIndexer(indexer); } catch (const Xapian::DatabaseError &e) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create akonotes indexer:" << QString::fromStdString(e.get_msg()); } catch (...) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; } try { QDir().mkpath(m_indexedItems->calendarIndexingPath()); indexer = new CalendarIndexer(m_indexedItems->calendarIndexingPath()); addIndexer(indexer); } catch (const Xapian::DatabaseError &e) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create akonotes indexer:" << QString::fromStdString(e.get_msg()); } catch (...) { delete indexer; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; } try { QDir().mkpath(m_indexedItems->collectionIndexingPath()); m_collectionIndexer = new CollectionIndexer(m_indexedItems->collectionIndexingPath()); } catch (const Xapian::DatabaseError &e) { delete m_collectionIndexer; m_collectionIndexer = nullptr; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Failed to create akonotes indexer:" << QString::fromStdString(e.get_msg()); } catch (...) { delete m_collectionIndexer; m_collectionIndexer = nullptr; qCCritical(AKONADI_INDEXER_AGENT_LOG) << "Random exception, but we do not want to crash"; } return !m_indexer.isEmpty(); } void Index::scheduleCommit() { if (!m_commitTimer.isActive()) { m_commitTimer.start(); } } void Index::commit() { m_commitTimer.stop(); for (AbstractIndexer *indexer : qAsConst(m_listIndexer)) { try { indexer->commit(); } catch (const Xapian::Error &e) { qCWarning(AKONADI_INDEXER_AGENT_LOG) << "Xapian error in indexer" << indexer << ":" << e.get_msg().c_str(); } } } void Index::findIndexed(QSet &indexed, Akonadi::Collection::Id collectionId) { m_indexedItems->findIndexed(indexed, collectionId); } qlonglong Index::indexedItems(const qlonglong id) { return m_indexedItems->indexedItems(id); } void Index::setOverrideDbPrefixPath(const QString &path) { m_indexedItems->setOverrideDbPrefixPath(path); } diff --git a/lib/collectionquery.cpp b/lib/collectionquery.cpp index ff2d6c2..830a5a4 100644 --- a/lib/collectionquery.cpp +++ b/lib/collectionquery.cpp @@ -1,168 +1,169 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2014 Christian Mollekopf * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ + +#include + #include "collectionquery.h" #include "resultiterator_p.h" -#include "xapian.h" - #include #include #include using namespace Akonadi::Search::PIM; struct CollectionQuery::Private { QStringList ns; QStringList mimeType; QString nameString; QString identifierString; QString pathString; QString databaseDir; int limit; }; CollectionQuery::CollectionQuery() : Query(), d(new Private) { d->databaseDir = defaultLocation(QStringLiteral("collections")); d->limit = 0; } CollectionQuery::~CollectionQuery() { delete d; } void CollectionQuery::setDatabaseDir(const QString &dir) { d->databaseDir = dir; } void CollectionQuery::nameMatches(const QString &match) { d->nameString = match; } void CollectionQuery::identifierMatches(const QString &match) { d->identifierString = match; } void CollectionQuery::pathMatches(const QString &match) { d->pathString = match; } void CollectionQuery::setNamespace(const QStringList &ns) { d->ns = ns; } void CollectionQuery::setMimetype(const QStringList &mt) { d->mimeType = mt; } void CollectionQuery::setLimit(int limit) { d->limit = limit; } ResultIterator CollectionQuery::exec() { Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(d->databaseDir).constData()); } catch (const Xapian::DatabaseError &e) { qWarning() << "Failed to open Xapian database:" << d->databaseDir << "; error:" << QString::fromStdString(e.get_error_string()); return ResultIterator(); } QList queries; if (!d->nameString.isEmpty()) { //qDebug() << "searching by name"; Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "N"); parser.set_default_op(Xapian::Query::OP_AND); queries << parser.parse_query(d->nameString.toUtf8().constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->identifierString.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "I"); parser.set_default_op(Xapian::Query::OP_AND); queries << parser.parse_query(d->identifierString.toUtf8().constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->pathString.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "P"); parser.set_default_op(Xapian::Query::OP_AND); queries << parser.parse_query(d->pathString.toUtf8().constData(), Xapian::QueryParser::FLAG_PARTIAL | Xapian::QueryParser::FLAG_PHRASE); } if (!d->ns.isEmpty()) { QList queryList; queryList.reserve(d->ns.count()); for (const QString &n : qAsConst(d->ns)) { const QByteArray term = "NS" + n.toUtf8(); queryList << Xapian::Query(term.constData()); } queries << Xapian::Query(Xapian::Query::OP_OR, queryList.begin(), queryList.end()); } if (!d->mimeType.isEmpty()) { QList queryList; queryList.reserve(d->mimeType.count()); for (const QString &n : qAsConst(d->mimeType)) { const QByteArray term = "M" + n.toUtf8(); queryList << Xapian::Query(term.constData()); } queries << Xapian::Query(Xapian::Query::OP_OR, queryList.begin(), queryList.end()); } Xapian::Query query = Xapian::Query(Xapian::Query::OP_AND, queries.begin(), queries.end()); if (d->limit == 0) { d->limit = 1000000; } Xapian::Enquire enquire(db); enquire.set_query(query); Xapian::MSet mset = enquire.get_mset(0, d->limit); ResultIterator iter; iter.d->init(mset); return iter; } diff --git a/lib/contactcompleter.cpp b/lib/contactcompleter.cpp index e247ad2..121e72c 100644 --- a/lib/contactcompleter.cpp +++ b/lib/contactcompleter.cpp @@ -1,93 +1,93 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ +#include + #include "contactcompleter.h" #include "query.h" -#include #include #include - #include using namespace Akonadi::Search::PIM; ContactCompleter::ContactCompleter(const QString &prefix, int limit) : m_prefix(prefix.toLower()) , m_limit(limit) { } QStringList ContactCompleter::complete() { const QString dir = Query::defaultLocation(QStringLiteral("emailContacts")); Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(dir).constData()); } catch (const Xapian::DatabaseOpeningError &) { qWarning() << "Xapian Database does not exist at " << dir; return QStringList(); } catch (const Xapian::DatabaseCorruptError &) { qWarning() << "Xapian Database corrupted"; return QStringList(); } catch (const Xapian::DatabaseError &e) { qWarning() << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description()); return QStringList(); } catch (...) { qWarning() << "Random exception, but we do not want to crash"; return QStringList(); } Xapian::QueryParser parser; parser.set_database(db); std::string prefix(m_prefix.toUtf8().constData()); int flags = Xapian::QueryParser::FLAG_DEFAULT | Xapian::QueryParser::FLAG_PARTIAL; Xapian::Query q = parser.parse_query(prefix, flags); Xapian::Enquire enq(db); enq.set_query(q); Xapian::MSet mset = enq.get_mset(0, m_limit); Xapian::MSetIterator mit = mset.begin(); QStringList list; Xapian::MSetIterator end = mset.end(); list.reserve(mset.size()); Q_FOREVER { try { for (; mit != end; ++mit) { std::string str = mit.get_document().get_data(); const QString entry = QString::fromUtf8(str.c_str(), str.length()); list << entry; } return list; } catch (const Xapian::DatabaseCorruptError &e) { qWarning() << "The emailContacts Xapian database is corrupted:" << QString::fromStdString(e.get_description()); return QStringList(); } catch (const Xapian::DatabaseModifiedError &e) { db.reopen(); continue; // try again } } } diff --git a/lib/contactquery.cpp b/lib/contactquery.cpp index 90f8cc1..8a929d3 100644 --- a/lib/contactquery.cpp +++ b/lib/contactquery.cpp @@ -1,215 +1,214 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ +#include + #include "contactquery.h" #include "resultiterator_p.h" -#include "xapian.h" #include "akonadi_search_pim_debug.h" - #include #include #include - #include using namespace Akonadi::Search::PIM; class Q_DECL_HIDDEN ContactQuery::Private { public: QString name; QString nick; QString email; QString uid; QString any; int limit; MatchCriteria criteria; }; ContactQuery::ContactQuery() : Query() , d(new Private) { d->criteria = StartsWithMatch; } ContactQuery::~ContactQuery() { delete d; } void ContactQuery::matchName(const QString &name) { d->name = name; } void ContactQuery::matchNickname(const QString &nick) { d->nick = nick; } void ContactQuery::matchEmail(const QString &email) { d->email = email; } void ContactQuery::matchUID(const QString &uid) { d->uid = uid; } void ContactQuery::match(const QString &str) { d->any = str; } int ContactQuery::limit() const { return d->limit; } void ContactQuery::setLimit(int limit) { d->limit = limit; } ContactQuery::MatchCriteria ContactQuery::matchCriteria() const { return d->criteria; } void ContactQuery::setMatchCriteria(ContactQuery::MatchCriteria m) { d->criteria = m; } ResultIterator ContactQuery::exec() { const QString dir = defaultLocation(QStringLiteral("contacts")); Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(dir).constData()); } catch (const Xapian::DatabaseOpeningError &) { qWarning() << "Xapian Database does not exist at " << dir; return ResultIterator(); } catch (const Xapian::DatabaseCorruptError &) { qWarning() << "Xapian Database corrupted"; return ResultIterator(); } catch (const Xapian::DatabaseError &e) { qWarning() << "Failed to open Xapian database:" << QString::fromStdString(e.get_error_string()); return ResultIterator(); } catch (...) { qWarning() << "Random exception, but we do not want to crash"; return ResultIterator(); } QList m_queries; if (d->criteria == ExactMatch) { if (!d->any.isEmpty()) { const QByteArray ba = d->any.toUtf8(); m_queries << Xapian::Query(ba.constData()); } if (!d->name.isEmpty()) { const QByteArray ba = "NA" + d->name.toUtf8(); m_queries << Xapian::Query(ba.constData()); } if (!d->nick.isEmpty()) { const QByteArray ba = "NI" + d->nick.toUtf8(); m_queries << Xapian::Query(ba.constData()); } if (!d->email.isEmpty()) { const QByteArray ba = d->email.toUtf8(); m_queries << Xapian::Query(ba.constData()); } if (!d->uid.isEmpty()) { const QByteArray ba = "UID" + d->uid.toUtf8(); m_queries << Xapian::Query(ba.constData()); } } else if (d->criteria == StartsWithMatch) { if (!d->any.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); const QByteArray ba = d->any.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->name.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "NA"); const QByteArray ba = d->name.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->nick.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "NI"); const QByteArray ba = d->nick.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } // FIXME: Check for exact match? if (!d->email.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); const QByteArray ba = d->email.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->uid.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "UID"); const QByteArray ba = d->uid.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } try { Xapian::Query query(Xapian::Query::OP_OR, m_queries.begin(), m_queries.end()); qCDebug(AKONADI_SEARCH_PIM_LOG) << query.get_description().c_str(); Xapian::Enquire enquire(db); enquire.set_query(query); if (d->limit == 0) { d->limit = 10000; } Xapian::MSet matches = enquire.get_mset(0, d->limit); ResultIterator iter; iter.d->init(matches); return iter; } catch (const Xapian::Error &e) { qWarning() << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description()); return ResultIterator(); } } diff --git a/lib/contactquery.h b/lib/contactquery.h index c86b68c..827f8ec 100644 --- a/lib/contactquery.h +++ b/lib/contactquery.h @@ -1,74 +1,75 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef AKONADI_SEARCH_PIM_CONTACTQUERY_H #define AKONADI_SEARCH_PIM_CONTACTQUERY_H #include "search_pim_export.h" -#include #include "query.h" +#include + namespace Akonadi { namespace Search { namespace PIM { /** * Query for a list of contacts matching a criteria */ class AKONADI_SEARCH_PIM_EXPORT ContactQuery : public Query { public: ContactQuery(); ~ContactQuery(); void matchName(const QString &name); void matchNickname(const QString &nick); void matchEmail(const QString &email); void matchUID(const QString &uid); void match(const QString &str); enum MatchCriteria { ExactMatch, StartsWithMatch }; void setMatchCriteria(MatchCriteria m); MatchCriteria matchCriteria() const; ResultIterator exec() override; int limit() const; void setLimit(int limit); private: class Private; Private *d; }; } } } #endif // AKONADI_SEARCH_PIM_CONTACTQUERY_H diff --git a/lib/emailquery.cpp b/lib/emailquery.cpp index 8cb2460..34fc8d9 100644 --- a/lib/emailquery.cpp +++ b/lib/emailquery.cpp @@ -1,366 +1,366 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ +#include + #include "emailquery.h" #include "resultiterator_p.h" - -#include "xapian.h" #include "../search/email/agepostingsource.h" #include #include #include using namespace Akonadi::Search::PIM; class Q_DECL_HIDDEN EmailQuery::Private { public: Private(); QStringList involves; QStringList to; QStringList cc; QStringList bcc; QString from; QList collections; char important; char read; char attachment; QString matchString; QString subjectMatchString; QString bodyMatchString; EmailQuery::OpType opType; int limit; bool splitSearchMatchString; }; EmailQuery::Private::Private(): important('0'), read('0'), attachment('0'), opType(OpAnd), limit(0), splitSearchMatchString(true) { } EmailQuery::EmailQuery(): Query(), d(new Private) { } EmailQuery::~EmailQuery() { delete d; } void EmailQuery::setSplitSearchMatchString(bool split) { d->splitSearchMatchString = split; } void EmailQuery::setSearchType(EmailQuery::OpType op) { d->opType = op; } void EmailQuery::addInvolves(const QString &email) { d->involves << email; } void EmailQuery::setInvolves(const QStringList &involves) { d->involves = involves; } void EmailQuery::addBcc(const QString &bcc) { d->bcc << bcc; } void EmailQuery::setBcc(const QStringList &bcc) { d->bcc = bcc; } void EmailQuery::setCc(const QStringList &cc) { d->cc = cc; } void EmailQuery::setFrom(const QString &from) { d->from = from; } void EmailQuery::addTo(const QString &to) { d->to << to; } void EmailQuery::setTo(const QStringList &to) { d->to = to; } void EmailQuery::addCc(const QString &cc) { d->cc << cc; } void EmailQuery::addFrom(const QString &from) { d->from = from; } void EmailQuery::addCollection(Akonadi::Collection::Id id) { d->collections << id; } void EmailQuery::setCollection(const QList &collections) { d->collections = collections; } int EmailQuery::limit() const { return d->limit; } void EmailQuery::setLimit(int limit) { d->limit = limit; } void EmailQuery::matches(const QString &match) { d->matchString = match; } void EmailQuery::subjectMatches(const QString &subjectMatch) { d->subjectMatchString = subjectMatch; } void EmailQuery::bodyMatches(const QString &bodyMatch) { d->bodyMatchString = bodyMatch; } void EmailQuery::setAttachment(bool hasAttachment) { d->attachment = hasAttachment ? 'T' : 'F'; } void EmailQuery::setImportant(bool important) { d->important = important ? 'T' : 'F'; } void EmailQuery::setRead(bool read) { d->read = read ? 'T' : 'F'; } ResultIterator EmailQuery::exec() { const QString dir = defaultLocation(QStringLiteral("email")); Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(dir).constData()); } catch (const Xapian::DatabaseOpeningError &) { qWarning() << "Xapian Database does not exist at " << dir; return ResultIterator(); } catch (const Xapian::DatabaseCorruptError &) { qWarning() << "Xapian Database corrupted"; return ResultIterator(); } catch (const Xapian::DatabaseError &e) { qWarning() << "Failed to open Xapian database:" << QString::fromStdString(e.get_error_string()); return ResultIterator(); } catch (...) { qWarning() << "Random exception, but we do not want to crash"; return ResultIterator(); } QList m_queries; if (!d->involves.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "F"); parser.add_prefix("", "T"); parser.add_prefix("", "CC"); parser.add_prefix("", "BCC"); // vHanda: Do we really need the query parser over here? for (const QString &str : qAsConst(d->involves)) { const QByteArray ba = str.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } if (!d->from.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "F"); const QByteArray ba = d->from.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->to.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "T"); for (const QString &str : qAsConst(d->to)) { const QByteArray ba = str.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } if (!d->cc.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "CC"); for (const QString &str : qAsConst(d->cc)) { const QByteArray ba = str.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } if (!d->bcc.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "BC"); for (const QString &str : qAsConst(d->bcc)) { const QByteArray ba = str.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } if (!d->subjectMatchString.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "SU"); parser.set_default_op(Xapian::Query::OP_AND); const QByteArray ba = d->subjectMatchString.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->collections.isEmpty()) { Xapian::Query query; for (Akonadi::Collection::Id id : qAsConst(d->collections)) { QString c = QString::number(id); Xapian::Query q = Xapian::Query('C' + c.toStdString()); query = Xapian::Query(Xapian::Query::OP_OR, query, q); } m_queries << query; } if (!d->bodyMatchString.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "BO"); parser.set_default_op(Xapian::Query::OP_AND); const QByteArray ba = d->bodyMatchString.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (d->important == 'T') { m_queries << Xapian::Query("BI"); } else if (d->important == 'F') { m_queries << Xapian::Query("BNI"); } if (d->read == 'T') { m_queries << Xapian::Query("BR"); } else if (d->read == 'F') { m_queries << Xapian::Query("BNR"); } if (d->attachment == 'T') { m_queries << Xapian::Query("BA"); } else if (d->attachment == 'F') { m_queries << Xapian::Query("BNA"); } if (!d->matchString.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.set_default_op(Xapian::Query::OP_AND); if (d->splitSearchMatchString) { const QStringList list = d->matchString.split(QRegularExpression(QStringLiteral("\\s")), QString::SkipEmptyParts); for (const QString &s : list) { const QByteArray ba = s.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } else { const QByteArray ba = d->matchString.toUtf8(); m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); } } Xapian::Query query; switch (d->opType) { case OpAnd: query = Xapian::Query(Xapian::Query::OP_AND, m_queries.begin(), m_queries.end()); break; case OpOr: query = Xapian::Query(Xapian::Query::OP_OR, m_queries.begin(), m_queries.end()); break; } AgePostingSource ps(0); query = Xapian::Query(Xapian::Query::OP_AND_MAYBE, query, Xapian::Query(&ps)); try { Xapian::Enquire enquire(db); enquire.set_query(query); if (d->limit == 0) { d->limit = 1000000; } Xapian::MSet mset = enquire.get_mset(0, d->limit); ResultIterator iter; iter.d->init(mset); return iter; } catch (const Xapian::Error &e) { qWarning() << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description()); return ResultIterator(); } } diff --git a/lib/indexeditems.cpp b/lib/indexeditems.cpp index 92fe230..ad96937 100644 --- a/lib/indexeditems.cpp +++ b/lib/indexeditems.cpp @@ -1,236 +1,239 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2016-2018 Laurent Montel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ + +#include + #include "indexeditems.h" #include "akonadi_search_pim_debug.h" + #include #include #include #include -#include using namespace Akonadi::Search::PIM; class Akonadi::Search::PIM::IndexedItemsPrivate { public: IndexedItemsPrivate() { } QString dbPath(const QString &dbName) const; QString emailIndexingPath() const; QString collectionIndexingPath() const; QString calendarIndexingPath() const; QString akonotesIndexingPath() const; QString emailContactsIndexingPath() const; QString contactIndexingPath() const; mutable QHash m_cachePath; QString m_overridePrefixPath; qlonglong indexedItems(const qlonglong id); qlonglong indexedItemsInDatabase(const std::string &term, const QString &dbPath) const; void findIndexedInDatabase(QSet &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath); void findIndexed(QSet &indexed, Akonadi::Collection::Id collectionId); }; QString IndexedItemsPrivate::dbPath(const QString &dbName) const { const QString cachedPath = m_cachePath.value(dbName); if (!cachedPath.isEmpty()) { return cachedPath; } if (!m_overridePrefixPath.isEmpty()) { const QString path = QString::fromLatin1("%1/%2/").arg(m_overridePrefixPath, dbName); m_cachePath.insert(dbName, path); return path; } // First look into the old location from Baloo times in ~/.local/share/baloo, // because we don't migrate the database files automatically. QString basePath; bool hasInstanceIdentifier = Akonadi::ServerManager::hasInstanceIdentifier(); if (hasInstanceIdentifier) { basePath = QStringLiteral("baloo/instances/%1").arg(Akonadi::ServerManager::instanceIdentifier()); } else { basePath = QStringLiteral("baloo"); } QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName); if (QDir(dbPath).exists()) { m_cachePath.insert(dbName, dbPath); return dbPath; } // If the database does not exist in old Baloo folders, than use the new // location in Akonadi's datadir in ~/.local/share/akonadi/search_db. if (hasInstanceIdentifier) { basePath = QStringLiteral("akonadi/instance/%1/search_db").arg(Akonadi::ServerManager::instanceIdentifier()); } else { basePath = QStringLiteral("akonadi/search_db"); } dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName); QDir().mkpath(dbPath); m_cachePath.insert(dbName, dbPath); return dbPath; } QString IndexedItemsPrivate::emailIndexingPath() const { return dbPath(QStringLiteral("email")); } QString IndexedItemsPrivate::contactIndexingPath() const { return dbPath(QStringLiteral("contacts")); } QString IndexedItemsPrivate::emailContactsIndexingPath() const { return dbPath(QStringLiteral("emailContacts")); } QString IndexedItemsPrivate::akonotesIndexingPath() const { return dbPath(QStringLiteral("notes")); } QString IndexedItemsPrivate::calendarIndexingPath() const { return dbPath(QStringLiteral("calendars")); } QString IndexedItemsPrivate::collectionIndexingPath() const { return dbPath(QStringLiteral("collections")); } qlonglong IndexedItemsPrivate::indexedItemsInDatabase(const std::string &term, const QString &dbPath) const { Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(dbPath).constData()); } catch (const Xapian::DatabaseError &e) { qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to open database" << dbPath << ":" << QString::fromStdString(e.get_msg()); return 0; } return db.get_termfreq(term); } qlonglong IndexedItemsPrivate::indexedItems(const qlonglong id) { const std::string term = QString::fromLatin1("C%1").arg(id).toStdString(); return indexedItemsInDatabase(term, emailIndexingPath()) + indexedItemsInDatabase(term, contactIndexingPath()) + indexedItemsInDatabase(term, akonotesIndexingPath()) + indexedItemsInDatabase(term, calendarIndexingPath()); } void IndexedItemsPrivate::findIndexedInDatabase(QSet &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath) { Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(dbPath).constData()); } catch (const Xapian::DatabaseError &e) { qCCritical(AKONADI_SEARCH_PIM_LOG) << "Failed to open database" << dbPath << ":" << QString::fromStdString(e.get_msg()); return; } const std::string term = QString::fromLatin1("C%1").arg(collectionId).toStdString(); Xapian::Query query(term); Xapian::Enquire enquire(db); enquire.set_query(query); Xapian::MSet mset = enquire.get_mset(0, UINT_MAX); Xapian::MSetIterator it = mset.begin(); for (; it != mset.end(); it++) { indexed << *it; } } void IndexedItemsPrivate::findIndexed(QSet &indexed, Akonadi::Collection::Id collectionId) { findIndexedInDatabase(indexed, collectionId, emailIndexingPath()); findIndexedInDatabase(indexed, collectionId, contactIndexingPath()); findIndexedInDatabase(indexed, collectionId, akonotesIndexingPath()); findIndexedInDatabase(indexed, collectionId, calendarIndexingPath()); } IndexedItems::IndexedItems(QObject *parent) : QObject(parent), d(new Akonadi::Search::PIM::IndexedItemsPrivate()) { } IndexedItems::~IndexedItems() { delete d; } void IndexedItems::setOverrideDbPrefixPath(const QString &path) { d->m_overridePrefixPath = path; d->m_cachePath.clear(); } qlonglong IndexedItems::indexedItems(const qlonglong id) { return d->indexedItems(id); } void IndexedItems::findIndexedInDatabase(QSet &indexed, Akonadi::Collection::Id collectionId, const QString &dbPath) { d->findIndexedInDatabase(indexed, collectionId, dbPath); } void IndexedItems::findIndexed(QSet &indexed, Akonadi::Collection::Id collectionId) { d->findIndexed(indexed, collectionId); } QString IndexedItems::emailIndexingPath() const { return d->emailIndexingPath(); } QString IndexedItems::collectionIndexingPath() const { return d->collectionIndexingPath(); } QString IndexedItems::calendarIndexingPath() const { return d->calendarIndexingPath(); } QString IndexedItems::akonotesIndexingPath() const { return d->akonotesIndexingPath(); } QString IndexedItems::emailContactsIndexingPath() const { return d->emailContactsIndexingPath(); } QString IndexedItems::contactIndexingPath() const { return d->contactIndexingPath(); } diff --git a/lib/notequery.cpp b/lib/notequery.cpp index 62be2dd..41df677 100644 --- a/lib/notequery.cpp +++ b/lib/notequery.cpp @@ -1,140 +1,141 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2014-2018 Laurent Montel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ +#include + #include "notequery.h" #include "resultiterator_p.h" -#include "xapian.h" #include #include #include #include using namespace Akonadi::Search::PIM; class Q_DECL_HIDDEN NoteQuery::Private { public: Private() : limit(0) { } QString title; QString note; int limit; }; NoteQuery::NoteQuery() : Query() , d(new Private) { } NoteQuery::~NoteQuery() { delete d; } void NoteQuery::matchTitle(const QString &title) { d->title = title; } void NoteQuery::matchNote(const QString ¬e) { d->note = note; } void NoteQuery::setLimit(int limit) { d->limit = limit; } int NoteQuery::limit() const { return d->limit; } ResultIterator NoteQuery::exec() { const QString dir = defaultLocation(QStringLiteral("notes")); Xapian::Database db; try { db = Xapian::Database(QFile::encodeName(dir).constData()); } catch (const Xapian::DatabaseOpeningError &) { qWarning() << "Xapian Database does not exist at " << dir; return ResultIterator(); } catch (const Xapian::DatabaseCorruptError &) { qWarning() << "Xapian Database corrupted"; return ResultIterator(); } catch (const Xapian::DatabaseError &e) { qWarning() << "Failed to open Xapian database:" << QString::fromStdString(e.get_error_string()); return ResultIterator(); } catch (...) { qWarning() << "Random exception, but we do not want to crash"; return ResultIterator(); } QList m_queries; if (!d->note.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "BO"); const QByteArray baNote = d->note.toUtf8(); m_queries << parser.parse_query(baNote.constData(), Xapian::QueryParser::FLAG_PARTIAL); } if (!d->title.isEmpty()) { Xapian::QueryParser parser; parser.set_database(db); parser.add_prefix("", "SU"); parser.set_default_op(Xapian::Query::OP_AND); const QByteArray baTitle = d->title.toUtf8(); m_queries << parser.parse_query(baTitle.constData(), Xapian::QueryParser::FLAG_PARTIAL); } try { Xapian::Query query(Xapian::Query::OP_OR, m_queries.begin(), m_queries.end()); //qDebug() << query.get_description().c_str(); Xapian::Enquire enquire(db); enquire.set_query(query); if (d->limit == 0) { d->limit = 10000; } Xapian::MSet matches = enquire.get_mset(0, d->limit); ResultIterator iter; iter.d->init(matches); return iter; } catch (const Xapian::Error &e) { qWarning() << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description()); return ResultIterator(); } } diff --git a/lib/query.cpp b/lib/query.cpp index 1b730f5..5023067 100644 --- a/lib/query.cpp +++ b/lib/query.cpp @@ -1,108 +1,107 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "query.h" #include "contactquery.h" -#include #include #include #include #include #include #include using namespace Akonadi::Search::PIM; Query::Query() { } Query::~Query() { } Query *Query::fromJSON(const QByteArray &json) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(json, &error); if (doc.isNull()) { qWarning() << "Could not parse json query" << error.errorString(); return nullptr; } QVariantMap result = doc.toVariant().toMap(); const QString type = result[QStringLiteral("type")].toString().toLower(); if (type != QLatin1String("contact")) { qWarning() << "Can only handle contact queries"; return nullptr; } ContactQuery *cq = new ContactQuery(); cq->matchName(result[QStringLiteral("name")].toString()); cq->matchNickname(result[QStringLiteral("nick")].toString()); cq->matchEmail(result[QStringLiteral("email")].toString()); cq->matchUID(result[QStringLiteral("uid")].toString()); cq->match(result[QStringLiteral("$")].toString()); const QString criteria = result[QStringLiteral("matchCriteria")].toString().toLower(); if (criteria == QLatin1String("exact")) { cq->setMatchCriteria(ContactQuery::ExactMatch); } else if (criteria == QLatin1String("startswith")) { cq->setMatchCriteria(ContactQuery::StartsWithMatch); } cq->setLimit(result[QStringLiteral("limit")].toInt()); return cq; } QString Query::defaultLocation(const QString &dbName) { // First look into the old location from Baloo times in ~/.local/share/baloo, // because we don't migrate the database files automatically. QString basePath; bool hasInstanceIdentifier = Akonadi::ServerManager::hasInstanceIdentifier(); if (hasInstanceIdentifier) { basePath = QStringLiteral("baloo/instances/%1").arg(Akonadi::ServerManager::instanceIdentifier()); } else { basePath = QStringLiteral("baloo"); } QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName); if (QDir(dbPath).exists()) { return dbPath; } // If the database does not exist in old Baloo folders, than use the new // location in Akonadi's datadir in ~/.local/share/akonadi/search_db. if (hasInstanceIdentifier) { basePath = QStringLiteral("akonadi/instance/%1/search_db").arg(Akonadi::ServerManager::instanceIdentifier()); } else { basePath = QStringLiteral("akonadi/search_db"); } dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/%1/%2/").arg(basePath, dbName); QDir().mkpath(dbPath); return dbPath; } diff --git a/lib/resultiterator_p.h b/lib/resultiterator_p.h index 16b9dea..fc26875 100644 --- a/lib/resultiterator_p.h +++ b/lib/resultiterator_p.h @@ -1,58 +1,59 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef AKONADI_SEARCH_PIM_RESULTITERATOR_P_H #define AKONADI_SEARCH_PIM_RESULTITERATOR_P_H +#include + #include "resultiterator.h" -#include "xapian.h" namespace Akonadi { namespace Search { namespace PIM { class Q_DECL_HIDDEN ResultIterator::Private { public: void init(const Xapian::MSet &mset) { m_mset = mset; m_end = m_mset.end(); m_iter = m_mset.begin(); m_firstElement = true; } Xapian::MSet m_mset; Xapian::MSetIterator m_iter; Xapian::MSetIterator m_end; bool m_firstElement; }; } } } #endif // AKONADI_SEARCH_PIM_RESULTITERATOR_P_H diff --git a/xapian/xapianqueryparser.h b/xapian/xapianqueryparser.h index 0e0c2b1..59a0732 100644 --- a/xapian/xapianqueryparser.h +++ b/xapian/xapianqueryparser.h @@ -1,60 +1,61 @@ /* * * Copyright (C) 2014 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef AKONADI_SEARCH_XAPIAN_QUERYPARSER_H #define AKONADI_SEARCH_XAPIAN_QUERYPARSER_H -#include #include + +#include #include "search_xapian_export.h" namespace Akonadi { namespace Search { class AKONADI_SEARCH_XAPIAN_EXPORT XapianQueryParser { public: XapianQueryParser(); void setDatabase(Xapian::Database *db); Xapian::Query parseQuery(const QString &str, const QString &prefix = QString()); /** * Expands word to every possible option which it can be expanded to. */ Xapian::Query expandWord(const QString &word, const QString &prefix = QString()); /** * Set if each word in the string should be treated as a partial word * and should be expanded to every possible word. */ void setAutoExapand(bool autoexpand); private: Xapian::Database *m_db = nullptr; bool m_autoExpand; }; } } #endif // AKONADI_SEARCH_XAPIAN_QUERYPARSER_H diff --git a/xapian/xapiansearchstore.h b/xapian/xapiansearchstore.h index 93aa43e..4dc94cb 100644 --- a/xapian/xapiansearchstore.h +++ b/xapian/xapiansearchstore.h @@ -1,136 +1,138 @@ /* * This file is part of the KDE Akonadi Search Project * Copyright (C) 2013 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef AKONADI_SEARCH_XAPIANSEARCHSTORE_H #define AKONADI_SEARCH_XAPIANSEARCHSTORE_H +#include + #include "searchstore.h" #include "term.h" #include "search_xapian_export.h" -#include + #include namespace Akonadi { namespace Search { /** * Implements a search store using Xapian */ class AKONADI_SEARCH_XAPIAN_EXPORT XapianSearchStore : public SearchStore { Q_OBJECT public: explicit XapianSearchStore(QObject *parent = nullptr); ~XapianSearchStore() override; int exec(const Query &query) override; void close(int queryId) override; bool next(int queryId) override; QByteArray id(int queryId) override; QUrl url(int queryId) override; /** * Set the path of the xapian database */ virtual void setDbPath(const QString &path); virtual QString dbPath(); protected: /** * The derived class should implement the logic for constructing the appropriate * Xapian::Query class from the given values. */ virtual Xapian::Query constructQuery(const QString &property, const QVariant &value, Term::Comparator com) = 0; virtual Xapian::Query constructFilterQuery(int year, int month, int day); /** * Apply any final touches to the query */ virtual Xapian::Query finalizeQuery(const Xapian::Query &query); /** * Create a query for any custom options. */ virtual Xapian::Query applyCustomOptions(const Xapian::Query &q, const QVariantMap &options); /** * Returns the url for the document with id \p docid. */ virtual QUrl constructUrl(const Xapian::docid &docid) = 0; /** * Gives a list of types which have been provided with the query. * This must return the appropriate query which will be ANDed with * the final query */ virtual Xapian::Query convertTypes(const QStringList &types) = 0; /** * The prefix that should be used when converting an integer * id to a byte array */ virtual QByteArray idPrefix() = 0; Xapian::Document docForQuery(int queryId); /** * Convenience function to AND two Xapian queries together. */ Xapian::Query andQuery(const Xapian::Query &a, const Xapian::Query &b); Xapian::Database *xapianDb(); protected: QMutex m_mutex; private: Xapian::Query toXapianQuery(const Term &term); Xapian::Query toXapianQuery(Xapian::Query::op op, const QList &terms); Xapian::Query constructSearchQuery(const QString &str); struct Result { Xapian::MSet mset; Xapian::MSetIterator it; uint lastId; QUrl lastUrl; }; QHash m_queryMap; int m_nextId; QString m_dbPath; Xapian::Database *m_db = nullptr; }; } } #endif // AKONADI_SEARCH_XAPIANSEARCHSTORE_H diff --git a/xapian/xapiantermgenerator.h b/xapian/xapiantermgenerator.h index 3e6c9da..3ff6bc6 100644 --- a/xapian/xapiantermgenerator.h +++ b/xapian/xapiantermgenerator.h @@ -1,56 +1,57 @@ /* * * Copyright (C) 2014 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef AKONADI_SEARCH_XAPIAN_TERMGENERATOR_H #define AKONADI_SEARCH_XAPIAN_TERMGENERATOR_H -#include #include + +#include #include "search_xapian_export.h" namespace Akonadi { namespace Search { class AKONADI_SEARCH_XAPIAN_EXPORT XapianTermGenerator { public: explicit XapianTermGenerator(Xapian::Document *doc); void indexText(const QString &text); void indexText(const QString &text, const QString &prefix, int wdfInc = 1); void setPosition(int position); int position() const; void setDocument(Xapian::Document *doc); static QStringList termList(const QString &text); private: Xapian::Document *m_doc = nullptr; Xapian::TermGenerator m_termGen; int m_position; }; } } #endif // AKONADI_SEARCH_XAPIAN_TERMGENERATOR_H