Files
warmbly/internal/models/sync.go
T

111 lines
5.0 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// SyncPolicy is the fair-use budget a mailbox syncs under. The control plane
// resolves it when the mailbox is loaded onto a worker (instance settings,
// see internal/app/instancesettings) and ships it inside ADD_EMAIL, so the
// worker never needs to read configuration of its own.
type SyncPolicy struct {
// BackfillDays is how far back the initial import reaches.
BackfillDays int `json:"backfill_days" avro:"backfill_days"`
// BackfillMessages caps how many messages the initial import stores.
BackfillMessages int `json:"backfill_messages" avro:"backfill_messages"`
// DailyMessages caps new (live) messages stored per UTC day. Replies to
// the mailbox's own sends have a separate budget of the same size.
DailyMessages int `json:"daily_messages" avro:"daily_messages"`
// OrgDailyMessages caps new plus backfilled messages stored across the
// whole organization per UTC day.
OrgDailyMessages int `json:"org_daily_messages" avro:"org_daily_messages"`
}
// SyncBackfillStatus is where the initial import stands.
type SyncBackfillStatus string
const (
// SyncBackfillPending: the mailbox is loaded but the import has not run yet.
SyncBackfillPending SyncBackfillStatus = "pending"
// SyncBackfillRunning: the import is walking history and may be paced.
SyncBackfillRunning SyncBackfillStatus = "running"
// SyncBackfillComplete: the window is exhausted or the cap was reached.
SyncBackfillComplete SyncBackfillStatus = "complete"
)
// SyncFolderCursor is the resumable position inside one folder of a backfill.
// IMAP keys folders by UIDVALIDITY and walks UIDs downward; Graph keys by
// well-known folder name and follows @odata.nextLink; Gmail has no folders and
// uses SyncCursor.PageToken.
type SyncFolderCursor struct {
// Next is an opaque continuation (Graph nextLink).
Next string `json:"next,omitempty" avro:"next"`
// UID is the lowest UID already imported; the walk continues below it.
UID uint32 `json:"uid,omitempty" avro:"uid"`
// Done marks the folder exhausted for this window.
Done bool `json:"done,omitempty" avro:"done"`
}
// SyncCursor is the resumable position of a mailbox's backfill. It is stored
// as jsonb: read-then-execute state that is never filtered in SQL.
type SyncCursor struct {
// PageToken is Gmail's messages.list continuation.
PageToken string `json:"page_token,omitempty" avro:"page_token"`
// Folders is the per-folder position for IMAP and Graph.
Folders map[string]SyncFolderCursor `json:"folders,omitempty" avro:"folders"`
}
// SyncState is what the platform knows about a mailbox's sync: backfill
// progress, whether fair use is holding it, and when it last ran. The worker
// owns the live copy and relays every change as SYNC_STATE; the consumer
// persists it and the loader hands it back on the next (re)assignment.
type SyncState struct {
BackfillStatus SyncBackfillStatus `json:"backfill_status" avro:"backfill_status"`
BackfillCursor SyncCursor `json:"backfill_cursor" avro:"backfill_cursor"`
// BackfillSynced counts messages the import has stored so far.
BackfillSynced int `json:"backfill_synced" avro:"backfill_synced"`
// BackfillSince is the cutoff the running import uses; fixed at start so
// a later settings change does not move the goalposts mid-walk.
BackfillSince *time.Time `json:"backfill_since,omitempty" avro:"backfill_since"`
BackfillStartedAt *time.Time `json:"backfill_started_at,omitempty" avro:"backfill_started_at"`
BackfillCompletedAt *time.Time `json:"backfill_completed_at,omitempty" avro:"backfill_completed_at"`
// ThrottledUntil is set while fair use is deferring live mail; nil when
// the mailbox is within budget.
ThrottledUntil *time.Time `json:"throttled_until,omitempty" avro:"throttled_until"`
// ThrottleReason names the exhausted budget (see SyncThrottle* constants).
ThrottleReason string `json:"throttle_reason,omitempty" avro:"throttle_reason"`
// Deferred counts live messages currently waiting on budget: seen on the
// server but not yet stored. Drops back to zero once they are admitted.
Deferred int `json:"deferred" avro:"deferred"`
LastSyncedAt *time.Time `json:"last_synced_at,omitempty" avro:"last_synced_at"`
}
// Throttle reasons, stable strings the dashboard maps to copy.
const (
SyncThrottleBurst = "burst"
SyncThrottleHourly = "hourly"
SyncThrottleDaily = "daily"
SyncThrottleOrgDaily = "org_daily"
SyncThrottlePriorityFull = "priority_daily"
)
// AddWorkerEmailSyncData seeds a mailbox's sync on the worker: the budget it
// runs under and where a previous worker left off. State is nil on first
// connect.
type AddWorkerEmailSyncData struct {
Policy SyncPolicy `json:"policy" avro:"policy"`
State *SyncState `json:"state" avro:"state"`
}
// JobEventSyncState is the worker's SYNC_STATE relay: the full current state,
// not a delta, so a lost event is repaired by the next one.
type JobEventSyncState struct {
UserID uuid.UUID `json:"user_id"`
EmailID uuid.UUID `json:"email_id"`
State SyncState `json:"state"`
}