mox/store/train.go
Mechiel Lukkien cb229cb6cf
mox!
2023-01-30 14:27:06 +01:00

136 lines
3.5 KiB
Go

package store
import (
"errors"
"os"
"path/filepath"
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/junk"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
)
// ErrNoJunkFilter indicates user did not configure/enable a junk filter.
var ErrNoJunkFilter = errors.New("junkfilter: not configured")
// OpenJunkFilter returns an opened junk filter for the account.
// If the account does not have a junk filter enabled, ErrNotConfigured is returned.
// Do not forget to save the filter after modifying, and to always close the filter when done.
// An empty filter is initialized on first access of the filter.
func (a *Account) OpenJunkFilter(log *mlog.Log) (*junk.Filter, *config.JunkFilter, error) {
conf, ok := mox.Conf.Account(a.Name)
if !ok {
return nil, nil, ErrAccountUnknown
}
jf := conf.JunkFilter
if jf == nil {
return nil, jf, ErrNoJunkFilter
}
basePath := mox.DataDirPath("accounts")
dbPath := filepath.Join(basePath, a.Name, "junkfilter.db")
bloomPath := filepath.Join(basePath, a.Name, "junkfilter.bloom")
if _, xerr := os.Stat(dbPath); xerr != nil && os.IsNotExist(xerr) {
f, err := junk.NewFilter(log, jf.Params, dbPath, bloomPath)
return f, jf, err
}
f, err := junk.OpenFilter(log, jf.Params, dbPath, bloomPath, false)
return f, jf, err
}
// Train new messages, if relevant given their flags.
func (a *Account) Train(log *mlog.Log, msgs []Message) error {
return a.xtrain(log, msgs, false, true)
}
// Untrain removed messages, if relevant given their flags.
func (a *Account) Untrain(log *mlog.Log, msgs []Message) error {
return a.xtrain(log, msgs, true, false)
}
// train or untrain messages, if relevant given their flags.
func (a *Account) xtrain(log *mlog.Log, msgs []Message, untrain, train bool) (rerr error) {
if len(msgs) == 0 {
return nil
}
var jf *junk.Filter
for _, m := range msgs {
if !m.Seen && !m.Junk {
continue
}
// Lazy open the junk filter.
if jf == nil {
var err error
jf, _, err = a.OpenJunkFilter(log)
if err != nil && errors.Is(err, ErrNoJunkFilter) {
// No junk filter configured. Nothing more to do.
return nil
}
defer func() {
if jf != nil {
err := jf.Close()
if rerr == nil {
rerr = err
}
}
}()
}
ham := !m.Junk
err := xtrainMessage(log, a, jf, m, untrain, ham, train, ham)
if err != nil {
return err
}
}
return nil
}
// Retrain message, if relevant given old flags and the new flags in m.
func (a *Account) Retrain(log *mlog.Log, jf *junk.Filter, old Flags, m Message) error {
untrain := old.Seen || old.Junk
train := m.Seen || m.Junk
untrainHam := !old.Junk
trainHam := !m.Junk
if !untrain && !train || (untrain && train && trainHam == untrainHam) {
return nil
}
return xtrainMessage(log, a, jf, m, untrain, untrainHam, train, trainHam)
}
func xtrainMessage(log *mlog.Log, a *Account, jf *junk.Filter, m Message, untrain, untrainHam, train, trainHam bool) error {
log.Info("updating junk filter", mlog.Field("untrain", untrain), mlog.Field("untrainHam", untrainHam), mlog.Field("train", train), mlog.Field("trainHam", trainHam))
mr := a.MessageReader(m)
defer mr.Close()
p, err := m.LoadPart(mr)
if err != nil {
log.Errorx("loading part for message", err)
return nil
}
words, err := jf.ParseMessage(p)
if err != nil {
log.Errorx("parsing message for updating junk filter", err, mlog.Field("parse", ""))
return nil
}
if untrain {
err := jf.Untrain(untrainHam, words)
if err != nil {
return err
}
}
if train {
err := jf.Train(trainHam, words)
if err != nil {
return err
}
}
return nil
}