When retraining ham/spam messages, don't make existence of the messages optional.

If messages that should exist don't, that's a real error we don't want to hide.
Part of larger changes.
This commit is contained in:
Mechiel Lukkien
2025-03-01 10:48:36 +01:00
parent 3b731b7afe
commit 5ba51adb14
7 changed files with 39 additions and 33 deletions

View File

@ -654,11 +654,17 @@ func (m Message) LoadPart(r io.ReaderAt) (message.Part, error) {
// NeedsTraining returns whether message needs a training update, based on
// TrainedJunk (current training status) and new Junk/Notjunk flags.
func (m Message) NeedsTraining() bool {
untrain := m.TrainedJunk != nil
untrainJunk := untrain && *m.TrainedJunk
train := m.Junk != m.Notjunk
trainJunk := m.Junk
return untrain != train || untrain && train && untrainJunk != trainJunk
needs, _, _, _, _ := m.needsTraining()
return needs
}
func (m Message) needsTraining() (needs, untrain, untrainJunk, train, trainJunk bool) {
untrain = m.TrainedJunk != nil
untrainJunk = untrain && *m.TrainedJunk
train = m.Junk != m.Notjunk
trainJunk = m.Junk
needs = untrain != train || untrain && train && untrainJunk != trainJunk
return
}
// JunkFlagsForMailbox sets Junk and Notjunk flags based on mailbox name if configured. Often
@ -1850,7 +1856,7 @@ func (a *Account) DeliverMessage(log mlog.Log, tx *bstore.Tx, m *Message, msgFil
if !notrain && m.NeedsTraining() {
l := []Message{*m}
if err := a.RetrainMessages(context.TODO(), log, tx, l, false); err != nil {
if err := a.RetrainMessages(context.TODO(), log, tx, l); err != nil {
return fmt.Errorf("training junkfilter: %w", err)
}
*m = l[0]
@ -2422,7 +2428,7 @@ func (a *Account) rejectsRemoveMessages(ctx context.Context, log mlog.Log, tx *b
expunged[i].Junk = false
expunged[i].Notjunk = false
}
if err := a.RetrainMessages(ctx, log, tx, expunged, true); err != nil {
if err := a.RetrainMessages(ctx, log, tx, expunged); err != nil {
return nil, fmt.Errorf("retraining expunged messages: %w", err)
}
@ -3134,7 +3140,7 @@ func (a *Account) MailboxDelete(ctx context.Context, log mlog.Log, tx *bstore.Tx
}
}
remove = remove[:n]
if err := a.RetrainMessages(ctx, log, tx, remove, true); err != nil {
if err := a.RetrainMessages(ctx, log, tx, remove); err != nil {
return nil, nil, false, fmt.Errorf("untraining deleted messages: %v", err)
}
}

View File

@ -127,7 +127,7 @@ func TestMailbox(t *testing.T) {
err = acc.DB.Write(ctxbg, func(tx *bstore.Tx) error {
m.Junk = true
l := []Message{m}
err = acc.RetrainMessages(ctxbg, log, tx, l, false)
err = acc.RetrainMessages(ctxbg, log, tx, l)
tcheck(t, err, "train as junk")
m = l[0]
return nil
@ -140,7 +140,7 @@ func TestMailbox(t *testing.T) {
jf, _, err := acc.OpenJunkFilter(ctxbg, log)
tcheck(t, err, "open junk filter")
err = acc.DB.Write(ctxbg, func(tx *bstore.Tx) error {
return acc.RetrainMessage(ctxbg, log, tx, jf, &m, false)
return acc.RetrainMessage(ctxbg, log, tx, jf, &m)
})
tcheck(t, err, "retraining as non-junk")
err = jf.Close()
@ -148,7 +148,7 @@ func TestMailbox(t *testing.T) {
m.Notjunk = false
err = acc.DB.Write(ctxbg, func(tx *bstore.Tx) error {
return acc.RetrainMessages(ctxbg, log, tx, []Message{m}, false)
return acc.RetrainMessages(ctxbg, log, tx, []Message{m})
})
tcheck(t, err, "untraining non-junk")

View File

@ -19,6 +19,11 @@ import (
// ErrNoJunkFilter indicates user did not configure/enable a junk filter.
var ErrNoJunkFilter = errors.New("junkfilter: not configured")
func (a *Account) HasJunkFilter() bool {
conf, _ := a.Conf()
return conf.JunkFilter != nil
}
// 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.
@ -47,7 +52,7 @@ func (a *Account) OpenJunkFilter(ctx context.Context, log mlog.Log) (*junk.Filte
// RetrainMessages (un)trains messages, if relevant given their flags. Updates
// m.TrainedJunk after retraining.
func (a *Account) RetrainMessages(ctx context.Context, log mlog.Log, tx *bstore.Tx, msgs []Message, absentOK bool) (rerr error) {
func (a *Account) RetrainMessages(ctx context.Context, log mlog.Log, tx *bstore.Tx, msgs []Message) (rerr error) {
if len(msgs) == 0 {
return nil
}
@ -78,7 +83,7 @@ func (a *Account) RetrainMessages(ctx context.Context, log mlog.Log, tx *bstore.
}
}()
}
if err := a.RetrainMessage(ctx, log, tx, jf, &msgs[i], absentOK); err != nil {
if err := a.RetrainMessage(ctx, log, tx, jf, &msgs[i]); err != nil {
return err
}
}
@ -87,16 +92,11 @@ func (a *Account) RetrainMessages(ctx context.Context, log mlog.Log, tx *bstore.
// RetrainMessage untrains and/or trains a message, if relevant given m.TrainedJunk
// and m.Junk/m.Notjunk. Updates m.TrainedJunk after retraining.
func (a *Account) RetrainMessage(ctx context.Context, log mlog.Log, tx *bstore.Tx, jf *junk.Filter, m *Message, absentOK bool) error {
untrain := m.TrainedJunk != nil
untrainJunk := untrain && *m.TrainedJunk
train := m.Junk != m.Notjunk
trainJunk := m.Junk
if !untrain && !train || (untrain && train && untrainJunk == trainJunk) {
func (a *Account) RetrainMessage(ctx context.Context, log mlog.Log, tx *bstore.Tx, jf *junk.Filter, m *Message) error {
need, untrain, untrainJunk, train, trainJunk := m.needsTraining()
if !need {
return nil
}
log.Debug("updating junk filter",
slog.Bool("untrain", untrain),
slog.Bool("untrainjunk", untrainJunk),
@ -135,7 +135,7 @@ func (a *Account) RetrainMessage(ctx context.Context, log mlog.Log, tx *bstore.T
}
m.TrainedJunk = &trainJunk
}
if err := tx.Update(m); err != nil && (!absentOK || err != bstore.ErrAbsent) {
if err := tx.Update(m); err != nil {
return err
}
return nil