Documentation
¶
Overview ¶
Package worker runs one supervisor per mail account: it connects, watches the account's folder and feeds each arrival to the pipeline, strictly in UID order.
Index ¶
- Constants
- Variables
- type Check
- type CheckFunc
- type CheckState
- type CheckStore
- type Cleanup
- type Manager
- func (m *Manager) Checks() *CheckStore
- func (m *Manager) Lock(accountID int64) (unlock func())
- func (m *Manager) Mailbox(accountID int64) (mail.Mailbox, error)
- func (m *Manager) Sort(ctx context.Context, sr SortRun) (store.Batch, error)
- func (m *Manager) SortAll(ctx context.Context, runs []SortRun) ([]store.Batch, error)
- func (m *Manager) Start(ctx context.Context, s *Supervisor)
- func (m *Manager) StartCheck(c Cleanup, fingerprint string, run CheckFunc) (*Check, error)
- func (m *Manager) Stop(accountID int64)
- func (m *Manager) Wait()
- type Retention
- type SortRun
- type Supervisor
Constants ¶
const ( CheckRunning = "running" // the real run-through is still going CheckReady = "ready" // it is through; the rows are kept, waiting for Sort or Discard CheckFailed = "failed" // the mail server or the model failed CheckStale = "stale" // the rules changed since the check: Sort is refused until a new one )
Check statuses, as the API reports them.
const ( StatusLive = "live" StatusReconnecting = "reconnecting" StatusAuthFailed = "auth_failed" StatusError = "error" StatusPaused = "paused" // set by the user; Run is not started for a paused account )
Account statuses the supervisor sets, as stored in accounts.status.
const QueueSize = 1000
QueueSize is how many arrivals may wait between the watcher and the processor.
Variables ¶
var ErrCleanupRunning = errors.New("a cleanup is already running for this account")
ErrCleanupRunning means the account is already being sorted.
var ErrNotConnected = errors.New("account is not connected")
ErrNotConnected means the account has no open connection right now.
Functions ¶
This section is empty.
Types ¶
type Check ¶
type Check struct {
ID string
AccountID int64
Folder string
Since int64 // 0 = all mail
Limit int // 0 = no limit
RuleIDs []int64 // the rules the check was limited to; nil = every enabled rule (MAI-43)
Fingerprint string
StartedAt int64
// contains filtered or unexported fields
}
Check is one account's current cleanup check and its progress. Read it with State.
func (*Check) MarkStale ¶
func (c *Check) MarkStale()
MarkStale moves a ready check to stale: the rules changed since it ran, so Sort must wait for a new check. It leaves a running or failed check as it is.
func (*Check) SetExclude ¶
SetExclude saves the user's selection: the unticked row indices, stored sorted and without repeats. It reports false when the check is not ready, since a running, failed or stale check has no table to tick. Whether the indices name selectable rows is the caller's to check.
func (*Check) Started ¶ added in v0.1.3
func (c *Check) Started() CheckState
Started returns the check as it was when it began: running, with no progress. The answer to the request that started it uses this, so it never depends on how far the run in the background got in the meantime.
func (*Check) State ¶
func (c *Check) State() CheckState
State returns the whole check, rows included.
type CheckFunc ¶
type CheckFunc func(ctx context.Context, report func(composer.CheckProgress)) ([]composer.CheckRow, error)
CheckFunc runs the read-only check and returns its rows. The daemon calls it on the account's background context, so a check outlives the browser request that began it.
type CheckState ¶
type CheckState struct {
ID string
AccountID int64
Folder string
Since int64
Limit int
RuleIDs []int64 // nil = every enabled rule
Fingerprint string
Status string
Done int
Total int
Matched int // emails in the chosen range before the limit; more than Total when it cut the range
ModelCalls int
Tokens int
CostUSD float64
Error string
Rows []composer.CheckRow
// Exclude is the saved selection: the unticked selectable row indices, ascending. It
// lives only as long as the check (MAI-44) and is never written anywhere else.
Exclude []int
}
CheckState is a check read back: a copy safe to hand around and render. Rows are present only when the check is ready or stale.
type CheckStore ¶
type CheckStore struct {
// contains filtered or unexported fields
}
CheckStore keeps the current cleanup check of each account in the daemon's memory, one per account, guarded by one mutex.
MAI-44 knowingly overrides the working agreement that no state lives in process memory a restart or a second instance would break. Tilak decided (2026-10-06) that a cleanup check — the real, paid run-through of a mailbox that Sort then applies without asking the model again — is held here, not in the database: one check per account, a new check replacing (and cancelling) the previous one, deleted once Sort has used it or the user discards it, and lost on a daemon restart (accepted, no time-based expiry). Its rows carry display fields (sender, subject) that must never be logged or written to the database.
func (*CheckStore) All ¶ added in v0.1.2
func (s *CheckStore) All() []*Check
All returns every account's current check, by account id.
func (*CheckStore) Discard ¶
func (s *CheckStore) Discard(accountID int64) *Check
Discard removes and cancels the account's current check and returns it, or nil.
func (*CheckStore) Get ¶
func (s *CheckStore) Get(accountID int64) *Check
Get returns the account's current check, or nil.
func (*CheckStore) Take ¶
func (s *CheckStore) Take(accountID int64, id string) *Check
Take removes the account's current check, but only when it is the one with id, so a newer check that replaced it since is left alone. It returns the removed check, or nil. Sort calls it once it has used a check's rows.
type Cleanup ¶
type Cleanup struct {
AccountID int64
Folder string
Since time.Time // only mail received from this time on; zero = all of it
Limit int // only the newest Limit emails; 0 = no limit
// RuleIDs limits the check to these rules, checked in their usual order; nil = every
// enabled rule. Sender rules apply either way (MAI-43).
RuleIDs []int64
}
Cleanup describes which mail a cleanup check, and the Sort that follows it, covers.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager runs the supervisors and is how the action executor reaches an account (it implements actions.Accounts).
func (*Manager) Checks ¶
func (m *Manager) Checks() *CheckStore
Checks is the account cleanup checks held in the daemon's memory (MAI-44). It is created on first use, so a Manager needs no constructor.
func (*Manager) Lock ¶
Lock waits until the account's processor is between messages and holds it there until unlock is called. Undo and corrections move mail back into the watched folder; holding the lock until that is on record keeps the processor from sorting it again as new mail.
func (*Manager) Sort ¶
Sort starts applying a check's kept rows in the background and returns the run's batch at once; the run goes on until it is through, the account's supervisor stops or the daemon does. Each email is verified still where the check found it (BODY.PEEK, never marking it read) and then its saved decision is handed to the executor, exactly as a live email's is, so dry-run is honoured and every action is on record; all of them belong to the one batch, which makes the run undoable as a whole. One Sort per account at a time. ctx only covers opening the batch.
func (*Manager) SortAll ¶ added in v0.1.2
SortAll starts the Sort of several mailboxes together, one batch each, and returns the batches in the order of runs. They are all started or none is: when a mailbox is not connected or already being sorted, nothing starts and the error says so. More than one batch makes a manual run over several mailboxes: they share a store.Batch.RunID, so they read back as one run, and each is undone on its own like any cleanup batch (MAI-43). A mailbox may be named only once.
func (*Manager) Start ¶
func (m *Manager) Start(ctx context.Context, s *Supervisor)
Start runs a supervisor until ctx is done or Stop is called for its account. An account that is already running is stopped first.
func (*Manager) StartCheck ¶
StartCheck begins a cleanup check of an account in the background, keyed in the store as the account's current check (cancelling any previous one). It returns the check at once, running; progress and the finished rows arrive as check.progress events and through CheckStore.Get. fingerprint is the signature of the rules the check was run against, which Sort compares with the rules in force to refuse a stale check.
type Retention ¶
type Retention struct {
Store *store.Store
Days func(ctx context.Context) int // the retention_days setting in force
Now func() time.Time // nil = time.Now
Every time.Duration // 0 = hourly
}
Retention is the job that forgets: it blanks the snippets of messages older than the retention setting, and deletes the rows of messages older than store.KeepMessagesDays that have nothing left to undo.
type SortRun ¶
type SortRun struct {
AccountID int64
Folder string
Since int64 // only mail received from this time on; 0 = all of it. Recorded on the batch.
// Limit and Matched are the check's: the most emails it covered and how many the range
// held before that cut. Recorded on the batch, so it can say what it covered (MAI-48).
Limit, Matched int
Rows []composer.CheckRow
}
SortRun is the Sort that follows a finished check: the rows the user kept, applied with the answers the check already settled, as one undoable cleanup batch. It makes no model calls — the decisions are replayed, never asked again.
type Supervisor ¶
type Supervisor struct {
Account store.Account
Store *store.Store
Hub *events.Hub
// Open connects to the account's server. It is called again after a failure.
Open func(ctx context.Context) (mail.Mailbox, error)
// Pipeline is the template for the account's pipeline; the supervisor fills in
// Mailbox, Account and Rediscover.
Pipeline pipeline.Pipeline
// Timers. Zero means the default; tests shrink them.
RetryEvery time.Duration // the retry job (pipeline.RetryEvery)
ContactsEvery time.Duration // the Sent-folder rescan (6 h)
DrainTimeout time.Duration // how long queued mail may take to finish at shutdown (10 s)
BackoffMin time.Duration // first reconnect delay (1 s), doubling up to BackoffMax (5 min)
BackoffMax time.Duration
// contains filtered or unexported fields
}
Supervisor owns one account's connection, watcher and processor goroutine.
func (*Supervisor) Mailbox ¶
func (s *Supervisor) Mailbox() mail.Mailbox
Mailbox returns the account's open connection, or nil while it is not connected.
func (*Supervisor) Run ¶
func (s *Supervisor) Run(ctx context.Context)
Run supervises the account until ctx is done or the account needs the user: wrong credentials, a TLS failure or a watch folder that does not exist. Anything else is retried with a growing delay.