bb963a796f
- seedDefaultUsers: generiert kryptographisch zufällige Passwörter (crypto/rand) statt hartkodiertes "archivmailrockz" — Passwörter werden einmalig im Terminal angezeigt und können danach nicht wiederhergestellt werden - generateJTI: verwendet crypto/rand (16 Byte, hex) statt time.UnixNano XOR deadbeef Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
150 lines
3.4 KiB
Go
150 lines
3.4 KiB
Go
//go:build xapian
|
|
|
|
package index
|
|
|
|
/*
|
|
#cgo pkg-config: xapian-core
|
|
#cgo LDFLAGS: -lstdc++
|
|
#include "xapian_wrapper.h"
|
|
#include <stdlib.h>
|
|
*/
|
|
import "C"
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"unsafe"
|
|
)
|
|
|
|
type xapianIndex struct {
|
|
db *C.XapianDB
|
|
}
|
|
|
|
func newXapian(dir string) (Indexer, error) {
|
|
cdir := C.CString(dir)
|
|
defer C.free(unsafe.Pointer(cdir))
|
|
var cerr *C.char
|
|
db := C.xapian_open(cdir, 1, &cerr)
|
|
if db == nil {
|
|
msg := C.GoString(cerr)
|
|
C.xapian_free_string(cerr)
|
|
return nil, fmt.Errorf("xapian open: %s", msg)
|
|
}
|
|
return &xapianIndex{db: db}, nil
|
|
}
|
|
|
|
func (x *xapianIndex) IndexSync(doc MailDocument) error {
|
|
cid := C.CString(doc.ID)
|
|
defer C.free(unsafe.Pointer(cid))
|
|
cfrom := C.CString(doc.From)
|
|
defer C.free(unsafe.Pointer(cfrom))
|
|
cto := C.CString(doc.To)
|
|
defer C.free(unsafe.Pointer(cto))
|
|
csubj := C.CString(doc.Subject)
|
|
defer C.free(unsafe.Pointer(csubj))
|
|
cbody := C.CString(doc.Body)
|
|
defer C.free(unsafe.Pointer(cbody))
|
|
hasAttach := C.int(0)
|
|
if doc.HasAttachment {
|
|
hasAttach = C.int(1)
|
|
}
|
|
var cerr *C.char
|
|
rc := C.xapian_index(x.db, cid, cfrom, cto, csubj, cbody, C.longlong(doc.Date.Unix()), hasAttach, &cerr)
|
|
if rc != 0 {
|
|
msg := C.GoString(cerr)
|
|
C.xapian_free_string(cerr)
|
|
return fmt.Errorf("xapian index: %s", msg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (x *xapianIndex) Delete(id string) error {
|
|
cid := C.CString(id)
|
|
defer C.free(unsafe.Pointer(cid))
|
|
var cerr *C.char
|
|
rc := C.xapian_delete(x.db, cid, &cerr)
|
|
if rc != 0 {
|
|
msg := C.GoString(cerr)
|
|
C.xapian_free_string(cerr)
|
|
return fmt.Errorf("xapian delete: %s", msg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (x *xapianIndex) Search(req SearchRequest) (*SearchResult, error) {
|
|
cquery := C.CString(req.Query)
|
|
defer C.free(unsafe.Pointer(cquery))
|
|
cfrom := C.CString(req.From)
|
|
defer C.free(unsafe.Pointer(cfrom))
|
|
cown := C.CString(req.OwnEmail)
|
|
defer C.free(unsafe.Pointer(cown))
|
|
cto := C.CString(req.To)
|
|
defer C.free(unsafe.Pointer(cto))
|
|
|
|
var dateFrom, dateTo C.longlong
|
|
if req.DateFrom != nil {
|
|
dateFrom = C.longlong(req.DateFrom.Unix())
|
|
}
|
|
if req.DateTo != nil {
|
|
dateTo = C.longlong(req.DateTo.Unix())
|
|
}
|
|
page := req.Page
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
offset := C.int((page - 1) * req.PageSize)
|
|
limit := C.int(req.PageSize)
|
|
if limit <= 0 {
|
|
limit = 25
|
|
}
|
|
|
|
// Sort mode: 0=relevance, 1=date_desc (default), 2=date_asc
|
|
sortMode := C.int(1)
|
|
switch req.Sort {
|
|
case "relevance":
|
|
sortMode = C.int(0)
|
|
case "date_asc":
|
|
sortMode = C.int(2)
|
|
}
|
|
|
|
// Attachment filter: 0=all, 1=only with, -1=only without
|
|
attachFilter := C.int(0)
|
|
if req.HasAttachment != nil {
|
|
if *req.HasAttachment {
|
|
attachFilter = C.int(1)
|
|
} else {
|
|
attachFilter = C.int(-1)
|
|
}
|
|
}
|
|
|
|
var cerr *C.char
|
|
cresult := C.xapian_search(x.db, cquery, cfrom, cown, cto, dateFrom, dateTo, offset, limit, sortMode, attachFilter, &cerr)
|
|
if cresult == nil {
|
|
msg := C.GoString(cerr)
|
|
C.xapian_free_string(cerr)
|
|
return nil, fmt.Errorf("xapian search: %s", msg)
|
|
}
|
|
defer C.xapian_free_string(cresult)
|
|
jsonStr := C.GoString(cresult)
|
|
|
|
var raw struct {
|
|
Total int `json:"total"`
|
|
Hits []struct {
|
|
ID string `json:"id"`
|
|
Score float64 `json:"score"`
|
|
} `json:"hits"`
|
|
}
|
|
if err := json.Unmarshal([]byte(jsonStr), &raw); err != nil {
|
|
return nil, fmt.Errorf("xapian parse result: %w", err)
|
|
}
|
|
hits := make([]Hit, len(raw.Hits))
|
|
for i, h := range raw.Hits {
|
|
hits[i] = Hit{ID: h.ID, Score: h.Score}
|
|
}
|
|
return &SearchResult{Total: raw.Total, Hits: hits}, nil
|
|
}
|
|
|
|
func (x *xapianIndex) Close() error {
|
|
C.xapian_close(x.db)
|
|
return nil
|
|
}
|