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

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  *
0006  */
0007 
0008 #include "xapiandatabase.h"
0009 #include "xapiandocument.h"
0010 
0011 #include "akonadi_search_xapian_debug.h"
0012 #include <QDir>
0013 
0014 #if defined(HAVE_MALLOC_H)
0015 #include <malloc.h>
0016 #endif
0017 
0018 #include <chrono>
0019 #include <thread>
0020 
0021 using namespace Akonadi::Search;
0022 
0023 XapianDatabase::XapianDatabase(const QString &path, bool writeOnly)
0024     : m_writeOnly(writeOnly)
0025 {
0026     QDir().mkpath(path);
0027     m_path = path.toStdString();
0028 
0029     if (!writeOnly) {
0030         try {
0031             createWritableDb();
0032             m_db = new Xapian::Database(m_path);
0033         } catch (const Xapian::DatabaseError &err) {
0034             qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Serious Error: " << err.get_error_string();
0035             qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << err.get_msg().c_str() << err.get_context().c_str() << err.get_description().c_str();
0036         }
0037 
0038         // Possible errors - DatabaseLock error
0039         // Corrupt and InvalidID error
0040     } else {
0041         m_wDb = createWritableDb();
0042     }
0043 }
0044 
0045 XapianDatabase::~XapianDatabase()
0046 {
0047     delete m_db;
0048 }
0049 
0050 void XapianDatabase::replaceDocument(uint id, const XapianDocument &doc)
0051 {
0052     replaceDocument(id, doc.doc());
0053 }
0054 
0055 void XapianDatabase::replaceDocument(uint id, const Xapian::Document &doc)
0056 {
0057     if (m_writeOnly) {
0058         try {
0059             m_wDb.replace_document(id, doc);
0060         } catch (const Xapian::Error &) {
0061         }
0062         return;
0063     }
0064     m_docsToAdd << qMakePair(id, doc);
0065 }
0066 
0067 void XapianDatabase::deleteDocument(uint id)
0068 {
0069     if (id == 0) {
0070         return;
0071     }
0072 
0073     if (m_writeOnly) {
0074         try {
0075             m_wDb.delete_document(id);
0076         } catch (const Xapian::Error &) {
0077         }
0078         return;
0079     }
0080     m_docsToRemove << id;
0081 }
0082 
0083 bool XapianDatabase::haveChanges() const
0084 {
0085     return !m_docsToAdd.isEmpty() || !m_docsToRemove.isEmpty();
0086 }
0087 
0088 void XapianDatabase::commit()
0089 {
0090     if (m_writeOnly) {
0091         try {
0092             m_wDb.commit();
0093         } catch (const Xapian::Error &err) {
0094             qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << err.get_error_string();
0095         }
0096         return;
0097     }
0098 
0099     if (!haveChanges()) {
0100         return;
0101     }
0102 
0103     Xapian::WritableDatabase wdb = createWritableDb();
0104 
0105     qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << "Adding:" << m_docsToAdd.size() << "docs";
0106     for (const DocIdPair &doc : std::as_const(m_docsToAdd)) {
0107         try {
0108             wdb.replace_document(doc.first, doc.second);
0109         } catch (const Xapian::Error &) {
0110         }
0111     }
0112 
0113     qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << "Removing:" << m_docsToRemove.size() << "docs";
0114     for (Xapian::docid id : std::as_const(m_docsToRemove)) {
0115         try {
0116             wdb.delete_document(id);
0117         } catch (const Xapian::Error &) {
0118         }
0119     }
0120 
0121     try {
0122         wdb.commit();
0123         m_db->reopen();
0124     } catch (const Xapian::Error &err) {
0125         qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << err.get_error_string();
0126     }
0127     qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << "Xapian Committed";
0128 
0129     m_docsToAdd.clear();
0130     m_docsToRemove.clear();
0131 
0132 #if defined(HAVE_MALLOC_TRIM)
0133     malloc_trim(0);
0134 #endif
0135 }
0136 
0137 XapianDocument XapianDatabase::document(uint id)
0138 {
0139     try {
0140         Xapian::Document xdoc;
0141         if (m_writeOnly) {
0142             xdoc = m_wDb.get_document(id);
0143         } else {
0144             xdoc = m_db->get_document(id);
0145         }
0146         return XapianDocument(xdoc);
0147     } catch (const Xapian::DatabaseModifiedError &) {
0148         m_db->reopen();
0149         return document(id);
0150     } catch (const Xapian::Error &) {
0151         return {};
0152     }
0153 }
0154 
0155 Xapian::WritableDatabase XapianDatabase::createWritableDb()
0156 {
0157     // We need to keep sleeping for a required amount, until we reach
0158     // a threshold. That's when we decide to abort?
0159     for (int i = 1; i <= 20; ++i) {
0160         try {
0161             Xapian::WritableDatabase wdb(m_path, Xapian::DB_CREATE_OR_OPEN);
0162             return wdb;
0163         } catch (const Xapian::DatabaseLockError &) {
0164             std::this_thread::sleep_for(std::chrono::milliseconds(i * 50));
0165         } catch (const Xapian::DatabaseModifiedError &) {
0166             std::this_thread::sleep_for(std::chrono::milliseconds(i * 50));
0167         } catch (const Xapian::DatabaseCreateError &err) {
0168             qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << err.get_error_string();
0169             return {};
0170         } catch (const Xapian::DatabaseCorruptError &err) {
0171             qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Database Corrupted - What did you do?";
0172             qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << err.get_error_string();
0173             return {};
0174         } catch (...) {
0175             qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Bananana Error";
0176             return {};
0177         }
0178     }
0179 
0180     qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Could not obtain lock for Xapian Database. This is bad";
0181     return {};
0182 }