#include "xapian_wrapper.h" #include #include #include #include #include #include struct XapianDB { Xapian::WritableDatabase* wdb; Xapian::Database* rdb; bool writable; }; static char* dup_error(const std::string& msg) { char* s = (char*)malloc(msg.size() + 1); if (s) memcpy(s, msg.c_str(), msg.size() + 1); return s; } extern "C" { XapianDB* xapian_open(const char* path, int writable, char** err) { try { XapianDB* db = new XapianDB{nullptr, nullptr, (bool)writable}; if (writable) { db->wdb = new Xapian::WritableDatabase(path, Xapian::DB_CREATE_OR_OPEN); } else { db->rdb = new Xapian::Database(path); } return db; } catch (const std::exception& e) { if (err) *err = dup_error(e.what()); return nullptr; } } void xapian_close(XapianDB* db) { if (!db) return; if (db->wdb) { db->wdb->close(); delete db->wdb; } if (db->rdb) { db->rdb->close(); delete db->rdb; } delete db; } int xapian_index(XapianDB* db, const char* id, const char* from, const char* to, const char* subject, const char* body, long long timestamp, char** err) { try { Xapian::Document doc; Xapian::TermGenerator gen; gen.set_document(doc); gen.set_stemmer(Xapian::Stem("en")); // Prefix-indexed fields for filtering gen.index_text(from, 1, "XF"); gen.index_text(to, 1, "XT"); gen.index_text(subject, 1, "XS"); // Free-text indexed fields gen.index_text(subject); gen.increase_termpos(); gen.index_text(body); gen.increase_termpos(); gen.index_text(from); gen.increase_termpos(); gen.index_text(to); // Store timestamp for date range queries (value slot 0) doc.add_value(0, Xapian::sortable_serialise((double)timestamp)); // Store ID as document data doc.set_data(id); doc.add_boolean_term(std::string("Q") + id); db->wdb->replace_document(std::string("Q") + id, doc); db->wdb->commit(); return 0; } catch (const std::exception& e) { if (err) *err = dup_error(e.what()); return -1; } } int xapian_delete(XapianDB* db, const char* id, char** err) { try { db->wdb->delete_document(std::string("Q") + id); db->wdb->commit(); return 0; } catch (const std::exception& e) { if (err) *err = dup_error(e.what()); return -1; } } char* xapian_search(XapianDB* db, const char* query_str, const char* from_filter, const char* own_email, const char* to_filter, long long date_from, long long date_to, int offset, int limit, char** err) { try { Xapian::Database& xdb = db->wdb ? (Xapian::Database&)*db->wdb : *db->rdb; Xapian::Enquire enquire(xdb); Xapian::Query main_query; // Full-text query if (query_str && query_str[0] != '\0') { Xapian::QueryParser qp; qp.set_database(xdb); qp.set_stemmer(Xapian::Stem("en")); qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME); qp.add_prefix("from", "XF"); qp.add_prefix("to", "XT"); qp.add_prefix("subject", "XS"); main_query = qp.parse_query(query_str, Xapian::QueryParser::FLAG_DEFAULT | Xapian::QueryParser::FLAG_PARTIAL); } else { main_query = Xapian::Query::MatchAll; } // From filter if (from_filter && from_filter[0] != '\0') { Xapian::QueryParser qp; qp.set_database(xdb); Xapian::Query fq = qp.parse_query(from_filter, Xapian::QueryParser::FLAG_DEFAULT, "XF"); main_query = Xapian::Query(Xapian::Query::OP_AND, main_query, fq); } // OwnEmail filter: (from=own OR to=own) if (own_email && own_email[0] != '\0') { Xapian::QueryParser qp; qp.set_database(xdb); Xapian::Query fq = qp.parse_query(own_email, Xapian::QueryParser::FLAG_DEFAULT, "XF"); Xapian::Query tq = qp.parse_query(own_email, Xapian::QueryParser::FLAG_DEFAULT, "XT"); Xapian::Query owq(Xapian::Query::OP_OR, fq, tq); main_query = Xapian::Query(Xapian::Query::OP_AND, main_query, owq); } // To filter if (to_filter && to_filter[0] != '\0') { Xapian::QueryParser qp; qp.set_database(xdb); Xapian::Query tq = qp.parse_query(to_filter, Xapian::QueryParser::FLAG_DEFAULT, "XT"); main_query = Xapian::Query(Xapian::Query::OP_AND, main_query, tq); } // Date range if (date_from > 0 || date_to > 0) { double lo = date_from > 0 ? (double)date_from : 0.0; double hi = date_to > 0 ? (double)date_to : 1e18; Xapian::Query drq(Xapian::Query::OP_VALUE_RANGE, 0, Xapian::sortable_serialise(lo), Xapian::sortable_serialise(hi)); main_query = Xapian::Query(Xapian::Query::OP_AND, main_query, drq); } enquire.set_query(main_query); enquire.set_sort_by_value(0, true); // sort by date desc // Get total count Xapian::MSet all = enquire.get_mset(0, xdb.get_doccount()); int total = (int)all.get_matches_estimated(); // Get page Xapian::MSet mset = enquire.get_mset(offset, limit); std::ostringstream json; json << "{\"total\":" << total << ",\"hits\":["; bool first = true; for (auto it = mset.begin(); it != mset.end(); ++it) { if (!first) json << ","; first = false; std::string id = it.get_document().get_data(); double score = it.get_weight(); json << "{\"id\":\"" << id << "\",\"score\":" << score << "}"; } json << "]}"; std::string result = json.str(); char* out = (char*)malloc(result.size() + 1); memcpy(out, result.c_str(), result.size() + 1); return out; } catch (const std::exception& e) { if (err) *err = dup_error(e.what()); return nullptr; } } void xapian_free_string(char* s) { free(s); } } // extern "C"