9 register = make(chan *Comm)
10 unregister = make(chan *Comm)
11 broadcast = make(chan changeReq)
14type changeReq struct {
16 comm *Comm // Can be nil.
21type UID uint32 // IMAP UID.
23// Change to mailboxes/subscriptions/messages in an account. One of the Change*
24// types in this package.
27// ChangeAddUID is sent for a new message in a mailbox.
28type ChangeAddUID struct {
32 Flags Flags // System flags.
33 Keywords []string // Other flags.
36// ChangeRemoveUIDs is sent for removal of one or more messages from a mailbox.
37type ChangeRemoveUIDs struct {
39 UIDs []UID // Must be in increasing UID order, for IMAP.
43// ChangeFlags is sent for an update to flags for a message, e.g. "Seen".
44type ChangeFlags struct {
48 Mask Flags // Which flags are actually modified.
49 Flags Flags // New flag values. All are set, not just mask.
50 Keywords []string // Non-system/well-known flags/keywords/labels.
53// ChangeThread is sent when muted/collapsed changes.
54type ChangeThread struct {
60// ChangeRemoveMailbox is sent for a removed mailbox.
61type ChangeRemoveMailbox struct {
66// ChangeAddMailbox is sent for a newly created mailbox.
67type ChangeAddMailbox struct {
69 Flags []string // For flags like \Subscribed.
72// ChangeRenameMailbox is sent for a rename mailbox.
73type ChangeRenameMailbox struct {
80// ChangeAddSubscription is sent for an added subscription to a mailbox.
81type ChangeAddSubscription struct {
83 Flags []string // For additional IMAP flags like \NonExistent.
86// ChangeMailboxCounts is sent when the number of total/deleted/unseen/unread messages changes.
87type ChangeMailboxCounts struct {
93// ChangeMailboxSpecialUse is sent when a special-use flag changes.
94type ChangeMailboxSpecialUse struct {
100// ChangeMailboxKeywords is sent when keywords are changed for a mailbox. For
101// example, when a message is added with a previously unseen keyword.
102type ChangeMailboxKeywords struct {
108var switchboardBusy atomic.Bool
110// Switchboard distributes changes to accounts to interested listeners. See Comm and Change.
111func Switchboard() (stop func()) {
112 regs := map[*Account]map[*Comm]struct{}{}
113 done := make(chan struct{})
115 if !switchboardBusy.CompareAndSwap(false, true) {
116 panic("switchboard already busy")
122 case c := <-register:
123 if _, ok := regs[c.acc]; !ok {
124 regs[c.acc] = map[*Comm]struct{}{}
126 regs[c.acc][c] = struct{}{}
128 case c := <-unregister:
129 delete(regs[c.acc], c)
130 if len(regs[c.acc]) == 0 {
134 case chReq := <-broadcast:
136 for c := range regs[acc] {
137 // Do not send the broadcaster back their own changes. chReq.comm is nil if not
138 // originating from a comm, so won't match in that case.
144 c.changes = append(c.changes, chReq.changes...)
148 case c.Pending <- struct{}{}:
152 chReq.done <- struct{}{}
163 if !switchboardBusy.CompareAndSwap(true, false) {
164 panic("switchboard already unregistered?")
169// Comm handles communication with the goroutine that maintains the
170// account/mailbox/message state.
172 Pending chan struct{} // Receives block until changes come in, e.g. for IMAP IDLE.
180// Register starts a Comm for the account. Unregister must be called.
181func RegisterComm(acc *Account) *Comm {
183 Pending: make(chan struct{}, 1), // Bufferend so Switchboard can just do a non-blocking send.
190// Unregister stops this Comm.
191func (c *Comm) Unregister() {
195// Broadcast ensures changes are sent to other Comms.
196func (c *Comm) Broadcast(ch []Change) {
200 done := make(chan struct{}, 1)
201 broadcast <- changeReq{c.acc, c, ch, done}
205// Get retrieves all pending changes. If no changes are pending a nil or empty list
207func (c *Comm) Get() []Change {
215// BroadcastChanges ensures changes are sent to all listeners on the accoount.
216func BroadcastChanges(acc *Account, ch []Change) {
220 done := make(chan struct{}, 1)
221 broadcast <- changeReq{acc, nil, ch, done}