100 lines
2.1 KiB
Go
100 lines
2.1 KiB
Go
package search
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"rawmark/internal/vault"
|
|
)
|
|
|
|
func insert(tx *sql.Tx, p, rev string, content []byte) (int64, error) {
|
|
metadata, err := json.Marshal(vault.Analyze(p, content))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
result, err := tx.Exec(`INSERT INTO docs(path,rev,metadata) VALUES (?,?,?)`, p, rev, string(metadata))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
_, err = tx.Exec(`INSERT INTO docs_fts(rowid,path,body) VALUES (?,?,?)`, id, p, string(content))
|
|
return id, err
|
|
}
|
|
|
|
// Rebuild 与后台同步互斥,失败时事务和已知快照均保持原状。
|
|
func (x *Index) Rebuild(ctx context.Context) error {
|
|
x.work.Lock()
|
|
defer x.work.Unlock()
|
|
x.mu.Lock()
|
|
version := x.pendingVersion
|
|
x.mu.Unlock()
|
|
entries, err := x.v.Tree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx, err := x.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
if _, err = tx.ExecContext(ctx, `DELETE FROM docs_fts; DELETE FROM docs`); err != nil {
|
|
return err
|
|
}
|
|
next := map[string]doc{}
|
|
for _, entry := range entries {
|
|
if entry.Dir || !vault.IsNote(entry.Path) {
|
|
continue
|
|
}
|
|
if err = ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
content, rev, err := x.v.Read(entry.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, err := insert(tx, entry.Path, rev, content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
next[entry.Path] = doc{id: id, rev: rev}
|
|
}
|
|
if err = tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
x.known = next
|
|
x.mu.Lock()
|
|
if version == x.pendingVersion {
|
|
x.pending = nil
|
|
}
|
|
x.ready = true
|
|
x.lastError = nil
|
|
x.mu.Unlock()
|
|
return nil
|
|
}
|
|
func (x *Index) Optimize(ctx context.Context) error {
|
|
x.work.Lock()
|
|
defer x.work.Unlock()
|
|
for _, stmt := range []string{`INSERT INTO docs_fts(docs_fts) VALUES ('optimize')`, `PRAGMA wal_checkpoint(TRUNCATE)`, `VACUUM`} {
|
|
if _, err := x.db.ExecContext(ctx, stmt); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (x *Index) syncPending() error {
|
|
x.work.Lock()
|
|
defer x.work.Unlock()
|
|
x.mu.Lock()
|
|
files := x.pending
|
|
x.pending = nil
|
|
x.mu.Unlock()
|
|
if files == nil {
|
|
return nil
|
|
}
|
|
return x.syncLocked(files)
|
|
}
|