mox retrain: make the parameter, for account, optional and retrain all accounts when absent

for more easily retraining all accounts. users should be retraining their
accounts with the next release, due to the fix in the previous commit.
This commit is contained in:
Mechiel Lukkien 2024-12-07 17:00:00 +01:00
parent 17baf9a883
commit 94fb48c2dc
No known key found for this signature in database
3 changed files with 74 additions and 59 deletions

115
ctl.go
View file

@ -1335,67 +1335,78 @@ func servectlcmd(ctx context.Context, ctl *ctl, shutdown func()) {
case "retrain": case "retrain":
/* protocol: /* protocol:
> "retrain" > "retrain"
> account > account or empty
< "ok" or error < "ok" or error
*/ */
account := ctl.xread() account := ctl.xread()
acc, err := store.OpenAccount(log, account)
ctl.xcheck(err, "open account")
defer func() {
if acc != nil {
err := acc.Close()
log.Check(err, "closing account after retraining")
}
}()
// todo: can we retrain an account without holding a write lock? perhaps by writing a junkfilter to a new location, and staying informed of message changes while we go through all messages in the account? xretrain := func(name string) {
acc, err := store.OpenAccount(log, name)
acc.WithWLock(func() { ctl.xcheck(err, "open account")
conf, _ := acc.Conf()
if conf.JunkFilter == nil {
ctl.xcheck(store.ErrNoJunkFilter, "looking for junk filter")
}
// Remove existing junk filter files.
basePath := mox.DataDirPath("accounts")
dbPath := filepath.Join(basePath, acc.Name, "junkfilter.db")
bloomPath := filepath.Join(basePath, acc.Name, "junkfilter.bloom")
err := os.Remove(dbPath)
log.Check(err, "removing old junkfilter database file", slog.String("path", dbPath))
err = os.Remove(bloomPath)
log.Check(err, "removing old junkfilter bloom filter file", slog.String("path", bloomPath))
// Open junk filter, this creates new files.
jf, _, err := acc.OpenJunkFilter(ctx, log)
ctl.xcheck(err, "open new junk filter")
defer func() { defer func() {
if jf == nil { if acc != nil {
return err := acc.Close()
log.Check(err, "closing account after retraining")
} }
err := jf.Close()
log.Check(err, "closing junk filter during cleanup")
}() }()
// Read through messages with junk or nonjunk flag set, and train them. // todo: can we retrain an account without holding a write lock? perhaps by writing a junkfilter to a new location, and staying informed of message changes while we go through all messages in the account?
var total, trained int
q := bstore.QueryDB[store.Message](ctx, acc.DB)
q.FilterEqual("Expunged", false)
err = q.ForEach(func(m store.Message) error {
total++
ok, err := acc.TrainMessage(ctx, log, jf, m)
if ok {
trained++
}
return err
})
ctl.xcheck(err, "training messages")
log.Info("retrained messages", slog.Int("total", total), slog.Int("trained", trained))
// Close junk filter, marking success. acc.WithWLock(func() {
err = jf.Close() conf, _ := acc.Conf()
jf = nil if conf.JunkFilter == nil {
ctl.xcheck(err, "closing junk filter") ctl.xcheck(store.ErrNoJunkFilter, "looking for junk filter")
}) }
// Remove existing junk filter files.
basePath := mox.DataDirPath("accounts")
dbPath := filepath.Join(basePath, acc.Name, "junkfilter.db")
bloomPath := filepath.Join(basePath, acc.Name, "junkfilter.bloom")
err := os.Remove(dbPath)
log.Check(err, "removing old junkfilter database file", slog.String("path", dbPath))
err = os.Remove(bloomPath)
log.Check(err, "removing old junkfilter bloom filter file", slog.String("path", bloomPath))
// Open junk filter, this creates new files.
jf, _, err := acc.OpenJunkFilter(ctx, log)
ctl.xcheck(err, "open new junk filter")
defer func() {
if jf == nil {
return
}
err := jf.Close()
log.Check(err, "closing junk filter during cleanup")
}()
// Read through messages with junk or nonjunk flag set, and train them.
var total, trained int
q := bstore.QueryDB[store.Message](ctx, acc.DB)
q.FilterEqual("Expunged", false)
err = q.ForEach(func(m store.Message) error {
total++
ok, err := acc.TrainMessage(ctx, log, jf, m)
if ok {
trained++
}
return err
})
ctl.xcheck(err, "training messages")
log.Info("retrained messages", slog.Int("total", total), slog.Int("trained", trained))
// Close junk filter, marking success.
err = jf.Close()
jf = nil
ctl.xcheck(err, "closing junk filter")
})
}
if account == "" {
for _, name := range mox.Conf.Accounts() {
xretrain(name)
}
} else {
xretrain(account)
}
ctl.xwriteok() ctl.xwriteok()
case "recalculatemailboxcounts": case "recalculatemailboxcounts":

6
doc.go
View file

@ -105,7 +105,7 @@ any parameters. Followed by the help and usage information for each command.
mox dnsbl check zone ip mox dnsbl check zone ip
mox dnsbl checkhealth zone mox dnsbl checkhealth zone
mox mtasts lookup domain mox mtasts lookup domain
mox retrain accountname mox retrain [accountname]
mox sendmail [-Fname] [ignoredflags] [-t] [<message] mox sendmail [-Fname] [ignoredflags] [-t] [<message]
mox spf check domain ip mox spf check domain ip
mox spf lookup domain mox spf lookup domain
@ -1390,12 +1390,12 @@ should be used, and how long the policy can be cached.
# mox retrain # mox retrain
Recreate and retrain the junk filter for the account. Recreate and retrain the junk filter for the account or all accounts.
Useful after having made changes to the junk filter configuration, or if the Useful after having made changes to the junk filter configuration, or if the
implementation has changed. implementation has changed.
usage: mox retrain accountname usage: mox retrain [accountname]
# mox sendmail # mox sendmail

12
main.go
View file

@ -2874,19 +2874,23 @@ should be used, and how long the policy can be cached.
} }
func cmdRetrain(c *cmd) { func cmdRetrain(c *cmd) {
c.params = "accountname" c.params = "[accountname]"
c.help = `Recreate and retrain the junk filter for the account. c.help = `Recreate and retrain the junk filter for the account or all accounts.
Useful after having made changes to the junk filter configuration, or if the Useful after having made changes to the junk filter configuration, or if the
implementation has changed. implementation has changed.
` `
args := c.Parse() args := c.Parse()
if len(args) != 1 { if len(args) > 1 {
c.Usage() c.Usage()
} }
var account string
if len(args) == 1 {
account = args[0]
}
mustLoadConfig() mustLoadConfig()
ctlcmdRetrain(xctl(), args[0]) ctlcmdRetrain(xctl(), account)
} }
func ctlcmdRetrain(ctl *ctl, account string) { func ctlcmdRetrain(ctl *ctl, account string) {