Documentation
¶
Index ¶
- Constants
- func AttrKey(scope AttrScope, folderGUID [16]byte, attrName string) string
- func AttrPrefix(scope AttrScope, folderGUID [16]byte) string
- func ExpandVars(template, username string) string
- func FormatAttrEntry(scope AttrScope, attrName string) string
- func IsValidRight(r rune) bool
- func ParseMailLocationMods(loc string) map[string]string
- func ServerAttrKey(scope AttrScope, inboxGUID [16]byte, attrName string) string
- func ServerAttrPrefix(scope AttrScope, inboxGUID [16]byte) string
- func SharedAttrKey(scope AttrScope, folderGUID [16]byte, accessingUser, attrName string) string
- func SharedAttrPrefix(scope AttrScope, folderGUID [16]byte, accessingUser string) string
- func TrimAttrPrefix(key, prefix string) string
- type ACL
- type AttrScope
- type Entry
- type FlagsUpdate
- type Folder
- type Identifier
- type IdentifierType
- type IndexBackend
- type Location
- type MailboxBackend
- type MessageMeta
- type Resolver
- type Rights
- type ScanRecord
- type SeqRange
- type SeqSet
- type UserIndex
- type UserInfo
- type UserMailbox
Constants ¶
const ( RightLookup = 'l' // visible to LIST/LSUB/SELECT result RightRead = 'r' // SELECT/EXAMINE/FETCH; \Seen excluded RightWriteSeen = 's' // STORE of \Seen RightWrite = 'w' // STORE of other flags except \Seen, \Deleted RightInsert = 'i' // APPEND, COPY destination (private namespaces) RightPost = 'p' // delivery via MTA (public / shared namespaces) RightCreate = 'k' // CREATE / RENAME of children — was obsolete 'c' RightDeleteMailbox = 'x' // DELETE / RENAME of the mailbox itself RightDeleteMessage = 't' // STORE \Deleted on a message — was part of 'd' RightExpunge = 'e' // EXPUNGE — was part of 'd' RightAdminister = 'a' // SETACL / DELETEACL / GETACL / LISTRIGHTS )
RFC 4314 §2.1 single-letter ACL rights. The 11 codes below are the canonical set used in this package. Obsolete codes 'c' / 'd' are accepted only on input by ParseRights and immediately expanded — they never appear in canonical output.
Letter map mirrors dovecot-2.4/src/plugins/acl/acl-rights.c:15-28.
const FullRights = Rights("lrswipkxtea")
FullRights is the union of every RFC 4314 right.
Variables ¶
This section is empty.
Functions ¶
func AttrKey ¶ added in v1.19.0
AttrKey returns the dict key for a per-folder attribute on a personal-namespace mailbox.
scope chooses the priv/ or shared/ namespace; folderGUID is the rename-stable identifier of the target folder (Folder.GUID); attrName is the path-component (without leading slash) returned by ParseAttrEntry.
The resulting key is of the form:
priv/box/<hex(guid)>/<attrName>
The "box/" segment exists so a future extension (e.g. per-message or per-user-scoped attributes) can co-exist under priv/<other>/... without colliding with mailbox attributes.
For shared / public namespaces use SharedAttrKey instead — it adds a per-accessing-user dimension to the priv/ scope so users do not see each other's private annotations on a shared folder.
func AttrPrefix ¶ added in v1.19.0
AttrPrefix returns the dict-iteration path for "all attributes of this personal-namespace folder under this scope". Useful for METADATA GETMETADATA with DEPTH 1 / DEPTH INFINITY.
func ExpandVars ¶ added in v0.3.10
ExpandVars rewrites Dovecot %-variables against a username.
%u → full username (alice@example.com) %n → local part (before @) (alice) %d → domain (after @) (example.com) %% → literal %
Unknown sequences pass through unchanged. A bare trailing % is preserved.
func FormatAttrEntry ¶ added in v1.19.0
FormatAttrEntry returns the wire-format entry name for a (scope, attrName) pair: the inverse of ParseAttrEntry. Used when iterating dict keys back into a GETMETADATA response map.
func IsValidRight ¶ added in v1.39.0
IsValidRight reports whether r is one of the 11 RFC 4314 rights. The obsolete 'c' / 'd' codes are NOT valid Right runes — they are only tolerated on input and expanded by ParseRights.
func ParseMailLocationMods ¶ added in v1.59.0
ParseMailLocationMods parses the modifier section of a Dovecot mail location string of the form "driver:path:KEY1=v1:KEY2=v2". Returns a map of uppercase modifier keys to their raw (unexpanded) values. Returns nil when the string has fewer than three colon-separated segments (i.e. no modifiers present).
func ServerAttrKey ¶ added in v1.19.0
ServerAttrKey returns the dict key for a server-scope attribute.
Server-scope attributes (those whose IMAP mailbox name is empty) are stored under INBOX's GUID with the serverScopeVendorPrefix added so they cannot collide with same-named INBOX mailbox attributes.
func ServerAttrPrefix ¶ added in v1.19.0
ServerAttrPrefix returns the iteration path for server-scope attributes — the equivalent of AttrPrefix for "" (empty) mailbox.
func SharedAttrKey ¶ added in v1.21.0
SharedAttrKey returns the dict key for a per-folder attribute on a shared or public namespace mailbox. For the priv/ scope the key embeds a per-accessing-user dimension (SHA-256 hash of the username, first 16 hex chars) so users cannot see each other's private annotations on the same shared folder. The shared/ scope remains a single value visible to everyone.
Hashing avoids embedding raw usernames (which may contain '/' or '%' that would clash with dict path semantics) and gives a stable, case-sensitive identifier without escaping.
func SharedAttrPrefix ¶ added in v1.21.0
SharedAttrPrefix returns the iteration path for a shared/public namespace folder. Mirrors SharedAttrKey: priv/ scope is per-user (key includes the accessingUser hash subdir), shared/ scope is global to the folder.
func TrimAttrPrefix ¶ added in v1.19.0
TrimAttrPrefix extracts the attrName from a full dict key, given the scope+guid prefix the iterator used. Returns "" if key does not lie under prefix (defensive — callers should pre-filter).
Types ¶
type ACL ¶ added in v1.39.0
type ACL []Entry
ACL is the ordered set of entries from a dovecot-acl file. Order is preserved on read but is semantically insignificant — Negative=true entries subtract from any matching positive regardless of position. Sorted() returns a deterministic ordering for stable on-disk writes.
func ParseACL ¶ added in v1.39.0
ParseACL reads a full dovecot-acl file body. Comment and blank lines are skipped; the first malformed entry aborts parsing with the 1-based line number annotated.
Scanner buffer is bumped to 1 MiB so an oversized line surfaces as a scanner error rather than silent truncation.
func ParseACLString ¶ added in v1.39.0
ParseACLString is a convenience wrapper for tests and CLI input.
func (ACL) Effective ¶ added in v1.39.0
Effective resolves the effective rights an authenticated user has on this mailbox's stored ACL.
Semantics (RFC 4314 §3.5):
- isOwner == true: returns FullRights regardless of stored entries (Dovecot personal-namespace auto-grant).
- else: union of positive entries whose Identifier matches the accessing user — anyone, authenticated, user=<user>, group=<g> (when <g> is in groups), group-override=<g> — minus negative entries matching the same identifiers.
groups is the list of supplementary groups the user belongs to, sourced from the userdb `groups=` extra field. Pass nil or empty when no group membership is configured — group= entries have no effect.
Identifier type priority (higher tier replaces lower when matched):
- anyone / authenticated / group= → base tier
- user= → replaces if any user= entry matches
- group-override= → replaces if any group-override= matches
A nil ACL with isOwner == false yields the empty rights set — no implicit grant for non-owners is the RFC 4314 default.
type AttrScope ¶ added in v1.19.0
type AttrScope int
AttrScope distinguishes per-user-private from per-resource-shared attributes.
func ParseAttrEntry ¶ added in v1.19.0
ParseAttrEntry splits a wire-format METADATA entry name like "/private/comment" into (scope, attrName). The attrName is everything after the scope prefix, with the leading slash dropped:
/private/comment → (AttrPrivate, "comment") /shared/admin → (AttrShared, "admin") /private/vendor/yarilo/abc → (AttrPrivate, "vendor/yarilo/abc")
Returns an error if the entry does not start with /private/ or /shared/, or is otherwise malformed. The fork-side ValidateMetadataEntry already catches most of this on parse; we re-check here so callers that bypass the wire layer (tests, scripted scaffolding) still get the same guarantees.
type Entry ¶ added in v1.39.0
type Entry struct {
Identifier Identifier
Rights Rights
Negative bool
}
Entry is one parsed line of a dovecot-acl file. Negative=true entries strip Rights at evaluation time rather than granting them (RFC 4314 §3.5). Negatives are kept as separate entries — fold-in happens in the evaluator, not here.
func ParseEntry ¶ added in v1.39.0
ParseEntry parses one line of a dovecot-acl file:
[-]<identifier><WS><letters>
Whitespace-only and '#'-prefixed lines return (Entry{}, false, nil) — the caller skips them. Trailing comments are not supported, to match dovecot-2.4/src/plugins/acl/acl-backend-vfile.c:187-231.
type FlagsUpdate ¶ added in v1.94.0
ScanRecord is one entry produced by UserMailbox.Scan — the raw per-message info a storage driver can reconstruct from disk alone (no help from the index). Used by admin rebuild flows to regenerate the fileindex after corruption or operator request.
Fields populated per driver:
maildir: Filename, Size (from "S=" or stat), VSize (from "W="),
InternalDate (from stat mtime), Flags (parsed from
the ":2,FLAGS" trailer); GUID stays zero — Maildir
filenames carry no GUID.
dbox: Filename, GUID (from "G<hex>\n" trailer line),
Size+VSize (from "Z<hex>" / "V<hex>"), InternalDate
(from "R<hex>" Unix epoch); Flags empty — dbox
delegates flag storage to the index.
mdbox: not implemented in this phase — driver returns
"not yet implemented" until Phase MDBOX-PROD-READY.
FlagsUpdate carries the new flag and keyword sets for one message in a batch UpdateFlagsMulti call.
type Folder ¶
type Folder struct {
ID uint64
Name string
UIDValidity uint32
NextUID uint32
Messages uint32
Unseen uint32
HighestModSeq uint64
// GUID is a stable 16-byte identifier stamped at folder creation
// time. Survives RENAME (unlike Name). Used as the key namespace
// for per-folder metadata in pkg/dict (RFC 5464 METADATA) and as
// the rename-stable handle for ACL state, quota counters, etc.
GUID [16]byte
}
Folder holds index-level folder metadata.
type Identifier ¶ added in v1.39.0
type Identifier struct {
Type IdentifierType
Name string // user=/group=/group-override= only
}
Identifier is a parsed ACL identifier — the left side of a dovecot-acl line, without the optional '-' negativity marker.
func ParseIdentifier ¶ added in v1.39.0
func ParseIdentifier(s string) (Identifier, error)
ParseIdentifier parses the wire/disk form into an Identifier. Rejects empty Name for user=/group=/group-override=. The '-' negativity marker is handled by ParseEntry, not here.
func (Identifier) String ¶ added in v1.39.0
func (id Identifier) String() string
String returns the wire/disk form. Returns "" for IDInvalid so callers serialising an unvalidated Identifier produce a clearly malformed line rather than silent data.
type IdentifierType ¶ added in v1.39.0
type IdentifierType int
IdentifierType enumerates the six identifier kinds RFC 4314 + Dovecot recognise. Name is meaningful only for IDUser / IDGroup / IDGroupOverride. The zero value IDInvalid is never produced by ParseIdentifier — callers can use it as a sentinel.
const ( IDInvalid IdentifierType = iota IDAnyone IDAuthenticated IDOwner IDUser IDGroup IDGroupOverride )
type IndexBackend ¶
IndexBackend is the per-process factory for user-scoped index handles.
Phase 5 — multi-namespace stub:
OpenNamespace(user *UserInfo, namespace string) (UserIndex, error)
Returns an index handle for a non-private namespace.
Implementation: separate Backend instance rooted at the namespace index dir.
type Location ¶ added in v1.21.0
type Location struct {
Driver string // "maildir", "sdbox" (alias: "dbox"), "mdbox"
Path string // expanded absolute path (varexpand applied)
}
Location is a parsed namespace storage URL: "driver:path". Currently only the "maildir" driver is supported; other drivers (dbox, mdbox) are accepted by the parser for forward-compat but must match the globally configured cfg.Storage.Mailbox driver — per-namespace driver mixing is deferred until backends gain a shared OpenNamespace dispatch.
func ParseLocation ¶ added in v1.21.0
ParseLocation parses "driver:path" into Location. When loc is empty returns (Location{}, false) so callers can detect "namespace not configured for storage" and fall through to NS-1a wire-only mode. Path is %u/%n/%d/%h-expanded against ui — when ui is nil expansion is a no-op (callers without a user context, e.g. system-wide shared/public namespaces, pass nil and ship a literal absolute path).
type MailboxBackend ¶
type MailboxBackend interface {
OpenUser(*UserInfo) UserMailbox
}
MailboxBackend is the per-process factory for user-scoped storage handles. It holds no per-user state; all per-user state lives in UserMailbox.
Phase 5 — multi-namespace stub:
OpenNamespace(user *UserInfo, namespace string) (UserMailbox, error)
Returns a handle for a shared or public namespace (e.g. "shared/" or "Public").
Implementation: separate Backend instance rooted at the namespace storage dir,
wrapped with the same UserMailbox contract. Namespace list comes from
config.Namespaces (private/shared/public, each with its own location template).
type MessageMeta ¶
type MessageMeta struct {
UID uint32
Filename string // backend-specific filename returned by UserMailbox.Save
Flags []string
Keywords []string
ModSeq uint64
Size uint32
VSize uint32
InternalDate time.Time
GUID [16]byte
CacheOffset uint32
// AltTier is true when the message body resides in alt (cold) storage.
// Stored as FlagBackend (0x40) in the on-disk index record so Fetch()
// can open the correct tier without a wasted primary-tier syscall.
// Only meaningful for mdbox; other drivers ignore it.
AltTier bool
}
MessageMeta holds per-message metadata stored in the index.
type Resolver ¶ added in v0.3.10
type Resolver struct {
// Root is prepended to template-derived paths. Absolute override paths
// from userdb skip Root entirely.
Root string
// HomeTemplate is the Dovecot-style template for users with no userdb
// override. Default: "%d/%u" (virtual hosting layout).
HomeTemplate string
// DefaultQuotaRules are applied to every UserInfo produced by this
// Resolver when the userdb lookup provides no per-user override.
// Mirrors Dovecot's quota_rule setting at the global level.
DefaultQuotaRules []string
// DefaultVolatileDir is the cluster-wide VOLATILEDIR template applied
// when no per-user override arrives from userdb. Supports the same
// %u/%n/%d/%h variables as HomeTemplate. Empty disables volatile dir
// (default). Mirrors Dovecot's VOLATILEDIR mail-location modifier at
// the global config level.
DefaultVolatileDir string
// DefaultIndexDir is the cluster-wide INDEX= template applied when no
// per-user override arrives from userdb. Supports %u/%n/%d/%h. Empty
// keeps index files co-located with the mailbox (default). Mirrors
// Dovecot's INDEX= mail-location modifier at the global config level.
DefaultIndexDir string
// DefaultControlDir is the cluster-wide CONTROL= template applied when
// no per-user override arrives from userdb. Supports %u/%n/%d/%h.
// Empty keeps control files co-located with the mailbox (default).
// Mirrors Dovecot's CONTROL= mail-location modifier at the global
// config level.
DefaultControlDir string
// DefaultAltDir is the cluster-wide ALT= template applied when no
// per-user override arrives from userdb. Supports %u/%n/%d/%h.
// Empty disables two-tier storage (default). Mirrors Dovecot's
// ALT= mail-location modifier at the global config level.
DefaultAltDir string
}
Resolver maps a username (+ optional userdb override) to an absolute home directory using a Dovecot-style template (%u / %n / %d expansion).
Dovecot reference:
mail_home = /var/mail/vhosts/%d/%n (defaults shipped here) mail_location = maildir:~/Maildir (consumed by storage layer)
When userdb returns a non-empty home value it overrides the template outright; otherwise the template is expanded and joined with Root.
func (*Resolver) Resolve ¶ added in v0.3.10
Resolve returns the absolute home directory for a user. An empty homeOverride falls back to expanding HomeTemplate against the username and joining with Root.
func (*Resolver) UserInfo ¶ added in v0.3.10
UserInfo builds a fully-resolved UserInfo by running Resolve against the supplied username + userdb override. DefaultVolatileDir (if set) is expanded with %u/%n/%d/%h variables and stored in VolatileDir; per-user overrides can overwrite this field after the call returns.
type Rights ¶ added in v1.39.0
type Rights string
Rights is a set of RFC 4314 right letters in canonical order. Always sorted, deduped, free of obsolete codes; equality is byte-equality on the underlying string.
func MustParseRights ¶ added in v1.39.0
MustParseRights panics on error. Test helper.
func ParseRights ¶ added in v1.39.0
ParseRights normalises a wire/disk letter string into Rights:
- dedupes letters
- sorts to canonical order
- expands obsolete 'c' → 'k' and 'd' → 'te' (RFC 4314 §2.1.1)
- rejects any other character with a descriptive error
Empty input returns the empty Rights set without error.
type ScanRecord ¶ added in v1.25.0
type SeqSet ¶
type SeqSet []SeqRange
SeqSet is a set of UIDs or sequence numbers (use UID=0 for seq).
type UserIndex ¶ added in v0.3.10
type UserIndex interface {
OpenFolder(folder string, uidValidity uint32) (*Folder, error)
SaveFolder(f *Folder) error
AppendMessage(folderID uint64, m *MessageMeta) error
// AllocateUID atomically reserves and persists the folder's next UID
// under the cross-process mailbox lock. The caller passes the UID to
// UserMailbox.Save, then records the full MessageMeta via AppendMessage.
//
// If the caller fails between AllocateUID and AppendMessage the UID
// is burnt — matches Dovecot's "uid hole" tolerance. Periodic rebuild
// reconciles state by scanning the on-disk tree.
AllocateUID(folderID uint64) (uint32, error)
// AllocateUIDWithModSeq atomically reserves the folder's next UID and
// pre-allocates the next modseq value in one lock/reload/flush cycle,
// replacing the separate AllocateUID + NextModSeq calls in Append.
AllocateUIDWithModSeq(folderID uint64) (uid uint32, modseq uint64, err error)
// AllocateAndAppend assigns a UID and records the message in a single
// lock/reload/flush cycle — the go-imap appendBytes pattern applied to
// persistent storage. m.UID and m.ModSeq are filled in by the call;
// all other fields must be set by the caller. m.Filename must already
// be known (box.Save completes before this call). Replaces the two-step
// AllocateUID(WithModSeq) + AppendMessage pattern that caused APPEND stalls.
AllocateAndAppend(folderID uint64, m *MessageMeta) error
UpdateFlags(folderID uint64, uid uint32, flags, keywords []string) error
// UpdateFlagsMulti replaces flags+keywords for a batch of UIDs in a
// single lock/reload/flush cycle. Returns the new modseq per UID.
UpdateFlagsMulti(folderID uint64, updates map[uint32]FlagsUpdate) (map[uint32]uint64, error)
// SetAltTier sets or clears the AltTier marker (FlagBackend) for every
// message in folderID whose Filename matches one of the supplied names.
// Called by the altmove API after physically relocating mdbox m.<N> files
// so subsequent Fetch calls can skip the primary-tier open() attempt.
// Operates under the cross-process mailbox lock for the folder.
SetAltTier(folderID uint64, filenames []string, altTier bool) error
GetMessages(folderID uint64, uids SeqSet) ([]*MessageMeta, error)
ExpungeMessage(folderID uint64, uid uint32) error
NextModSeq(folderID uint64) (uint64, error)
// Vanished returns UIDs expunged from folderID with modseq strictly
// greater than sinceModSeq. Drives QRESYNC (RFC 7162) SELECT and
// UID FETCH (CHANGEDSINCE N VANISHED) responses.
Vanished(folderID uint64, sinceModSeq uint64) ([]uint32, error)
Keywords(folderID uint64) ([]string, error)
// RenameFolder renames oldName to newName in the index.
// Called by IMAP RENAME immediately after UserMailbox.Rename succeeds.
RenameFolder(oldName, newName string) error
// GetPOP3UIDLs loads saved POP3 UIDLs for a folder (uid → uidl string).
// Returns an empty map when no saved UIDLs exist yet.
GetPOP3UIDLs(folderID uint64) (map[uint32]string, error)
// SavePOP3UIDLs persists POP3 UIDLs so subsequent sessions use stable values.
SavePOP3UIDLs(folderID uint64, uidls map[uint32]string) error
// ResetFolder atomically replaces the on-disk record set for
// folderID with the supplied messages. Preserves UIDVALIDITY
// and the folder GUID; bumps NextUID past max(records.UID);
// HighestModSeq advances by one so QRESYNC clients invalidate
// their caches. Drives the admin rebuild flow. Caller has
// already taken the cross-process mailbox lock and made a
// .bak of the old base file.
ResetFolder(folderID uint64, records []*MessageMeta) error
// OptimizeIndex compacts the .index.log overlay into the base
// .index file. Returns a no-op nil when there is nothing to
// compact. Takes the same X lock as a normal write.
OptimizeIndex(folderID uint64) error
Close() error
}
UserIndex is a per-session, per-user index handle. All folder IDs are local to this handle — they must not be shared across handles.
type UserInfo ¶ added in v0.3.10
type UserInfo struct {
// Username is the canonical login (typically full email). Used for
// per-user concurrency counters, log lines, and message-ID Received
// headers — never as a storage path on its own.
Username string
// Home is the absolute filesystem root for the user's mailbox tree.
// Resolved from either userdb.home (override) or the storage.mail_home
// template expanded against the username. See Resolver.
Home string
// VolatileDir, when non-empty, redirects volatile index artefacts
// (Recreate tmp files) to a local path instead of the NFS-backed
// index directory. Mirrors Dovecot's VOLATILEDIR mail-location
// modifier. Template vars (%u/%n/%d/%h) are already expanded by the
// time this field is populated.
VolatileDir string
// IndexDir, when non-empty, redirects all per-folder index files
// (yarilo.index*, yarilo-acl) to a separate directory tree. The
// mailbox data (Maildir cur/new/tmp) stays under Home; only index
// state moves. Mirrors Dovecot's INDEX= mail-location modifier.
// Template vars (%u/%n/%d/%h) are already expanded.
IndexDir string
// ControlDir, when non-empty, redirects per-folder control files
// (yarilo-uidlist, subscriptions) to a separate directory tree.
// The mailbox data and index files are unaffected. Mirrors Dovecot's
// CONTROL= mail-location modifier.
// Template vars (%u/%n/%d/%h) are already expanded.
ControlDir string
// AltDir, when non-empty, enables two-tier maildir storage. Messages
// that have been cold-tiered (via altmove) live under AltDir instead
// of Home. Reads check both primary (Home) and alt; writes always go
// to the primary tier. Mirrors Dovecot's ALT= mail-location modifier.
// Template vars (%u/%n/%d/%h) are already expanded.
AltDir string
// Groups is the list of supplementary groups the user belongs to,
// sourced from the userdb `groups=` extra field (comma-separated).
// ACL evaluation matches these against `group=<name>` and
// `group-override=<name>` entries. Empty when not configured —
// group= ACL entries have no effect in that case.
Groups []string
// QuotaRules is the list of per-user quota rules sourced from the
// userdb `quota_rule=` extra field. Format: `*:storage=5G` or
// `*:messages=100000`. Empty means no quota limit.
QuotaRules []string
// SessionID is the IMAP/POP3 session identifier assigned by the login
// proxy. Included in the yarilo-locks owner string for BUSY diagnostics.
// Empty for LMTP and other non-session contexts.
SessionID string
}
UserInfo carries the per-session storage identity for a user. The fields are resolved once at session start (after passdb/userdb lookup) and passed to MailboxBackend.OpenUser / IndexBackend.OpenUser. Backends MUST use Home directly and not re-derive a path from Username — UserInfo is the contract for "where this user's data lives".
Only Username and Home are required for the current phase. The commented fields below are placeholders — adding a field here is backward-compatible (all callers pass *UserInfo by pointer; OpenUser signatures do not change).
type UserMailbox ¶ added in v0.3.10
type UserMailbox interface {
Init() error
Create(folder string) error
Delete(folder string) error
Rename(oldName, newName string) error
Save(folder string, r io.Reader, uid uint32, size int64, flags []string) (string, error)
// Fetch returns a reader for the message body. altTier hints that the
// message lives in alt (cold) storage so the driver can open it directly
// without trying the primary path first. The hint is set from
// MessageMeta.AltTier which is persisted in the index as FlagBackend.
Fetch(folder, filename string, altTier bool) (io.ReadCloser, error)
Remove(folder, filename string) error
List(folder string) ([]*MessageMeta, error)
FolderExists(folder string) (bool, error)
ListFolders() ([]string, error)
// Scan walks the on-disk representation of folder and yields
// every visible message as a ScanRecord. Used by the admin
// rebuild flow to regenerate the fileindex independently of
// whatever the index currently believes. Returns
// (nil, fmt.Errorf("driver/scan: not yet implemented"))
// from drivers that have not implemented disk-scan yet.
Scan(folder string) ([]ScanRecord, error)
Close() error
}
UserMailbox is a per-session, per-user storage handle bound to a single UserInfo at creation time. Mirrors Dovecot's struct mail_storage (mail-storage-private.h:138).
Init MUST be called before any other method — it creates the on-disk directory structure. Callers that open a handle but never call Init will see errors from the underlying filesystem operations.
Save takes the assigned UID as a parameter. Drivers that encode the UID in the on-disk filename (sdbox: u.<uid>; mdbox: map_uid bookkeeping) use it directly; Maildir ignores the UID for its filename but writes the uid→filename mapping into the dovecot-uidlist sidecar inline. The canonical caller flow is:
uid := idx.AllocateUID(folderID)
filename := box.Save(folder, r, uid, size, flags)
idx.AppendMessage(folderID, &MessageMeta{UID: uid, Filename: filename, ...})
If Save fails after AllocateUID, the UID is burnt (matches Dovecot's behaviour — the index simply skips the hole on next scan).
Close releases any open file descriptors held by the handle.