aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

msgstore.go (6175B)


      1 package lib
      2 
      3 import (
      4 	"io"
      5 	"time"
      6 
      7 	"github.com/emersion/go-imap"
      8 
      9 	"git.sr.ht/~sircmpwn/aerc/worker/types"
     10 )
     11 
     12 // Accesses to fields must be guarded by MessageStore.Lock/Unlock
     13 type MessageStore struct {
     14 	Deleted  map[uint32]interface{}
     15 	DirInfo  types.DirectoryInfo
     16 	Messages map[uint32]*types.MessageInfo
     17 	// Ordered list of known UIDs
     18 	Uids []uint32
     19 
     20 	bodyCallbacks   map[uint32][]func(io.Reader)
     21 	headerCallbacks map[uint32][]func(*types.MessageInfo)
     22 
     23 	// Map of uids we've asked the worker to fetch
     24 	onUpdate       func(store *MessageStore) // TODO: multiple onUpdate handlers
     25 	pendingBodies  map[uint32]interface{}
     26 	pendingHeaders map[uint32]interface{}
     27 	worker         *types.Worker
     28 }
     29 
     30 func NewMessageStore(worker *types.Worker,
     31 	dirInfo *types.DirectoryInfo) *MessageStore {
     32 
     33 	return &MessageStore{
     34 		Deleted: make(map[uint32]interface{}),
     35 		DirInfo: *dirInfo,
     36 
     37 		bodyCallbacks:   make(map[uint32][]func(io.Reader)),
     38 		headerCallbacks: make(map[uint32][]func(*types.MessageInfo)),
     39 
     40 		pendingBodies:  make(map[uint32]interface{}),
     41 		pendingHeaders: make(map[uint32]interface{}),
     42 		worker:         worker,
     43 	}
     44 }
     45 
     46 func (store *MessageStore) FetchHeaders(uids []uint32,
     47 	cb func(*types.MessageInfo)) {
     48 
     49 	// TODO: this could be optimized by pre-allocating toFetch and trimming it
     50 	// at the end. In practice we expect to get most messages back in one frame.
     51 	var toFetch imap.SeqSet
     52 	for _, uid := range uids {
     53 		if _, ok := store.pendingHeaders[uid]; !ok {
     54 			toFetch.AddNum(uint32(uid))
     55 			store.pendingHeaders[uid] = nil
     56 			if cb != nil {
     57 				if list, ok := store.headerCallbacks[uid]; ok {
     58 					store.headerCallbacks[uid] = append(list, cb)
     59 				} else {
     60 					store.headerCallbacks[uid] = []func(*types.MessageInfo){cb}
     61 				}
     62 			}
     63 		}
     64 	}
     65 	if !toFetch.Empty() {
     66 		store.worker.PostAction(&types.FetchMessageHeaders{Uids: toFetch}, nil)
     67 	}
     68 }
     69 
     70 func (store *MessageStore) FetchFull(uids []uint32, cb func(io.Reader)) {
     71 	// TODO: this could be optimized by pre-allocating toFetch and trimming it
     72 	// at the end. In practice we expect to get most messages back in one frame.
     73 	var toFetch imap.SeqSet
     74 	for _, uid := range uids {
     75 		if _, ok := store.pendingBodies[uid]; !ok {
     76 			toFetch.AddNum(uint32(uid))
     77 			store.pendingBodies[uid] = nil
     78 			if cb != nil {
     79 				if list, ok := store.bodyCallbacks[uid]; ok {
     80 					store.bodyCallbacks[uid] = append(list, cb)
     81 				} else {
     82 					store.bodyCallbacks[uid] = []func(io.Reader){cb}
     83 				}
     84 			}
     85 		}
     86 	}
     87 	if !toFetch.Empty() {
     88 		store.worker.PostAction(&types.FetchFullMessages{Uids: toFetch}, nil)
     89 	}
     90 }
     91 
     92 func (store *MessageStore) FetchBodyPart(
     93 	uid uint32, part []int, cb func(io.Reader)) {
     94 
     95 	store.worker.PostAction(&types.FetchMessageBodyPart{
     96 		Uid:  uid,
     97 		Part: part,
     98 	}, func(resp types.WorkerMessage) {
     99 		msg, ok := resp.(*types.MessageBodyPart)
    100 		if !ok {
    101 			return
    102 		}
    103 		cb(msg.Reader)
    104 	})
    105 }
    106 
    107 func merge(to *types.MessageInfo, from *types.MessageInfo) {
    108 
    109 	if from.BodyStructure != nil {
    110 		to.BodyStructure = from.BodyStructure
    111 	}
    112 	if from.Envelope != nil {
    113 		to.Envelope = from.Envelope
    114 	}
    115 	if len(from.Flags) != 0 {
    116 		to.Flags = from.Flags
    117 	}
    118 	if from.Size != 0 {
    119 		to.Size = from.Size
    120 	}
    121 	var zero time.Time
    122 	if from.InternalDate != zero {
    123 		to.InternalDate = from.InternalDate
    124 	}
    125 }
    126 
    127 func (store *MessageStore) Update(msg types.WorkerMessage) {
    128 	update := false
    129 	switch msg := msg.(type) {
    130 	case *types.DirectoryInfo:
    131 		store.DirInfo = *msg
    132 		if store.DirInfo.Exists != len(store.Uids) {
    133 			store.worker.PostAction(&types.FetchDirectoryContents{}, nil)
    134 		}
    135 		update = true
    136 	case *types.DirectoryContents:
    137 		newMap := make(map[uint32]*types.MessageInfo)
    138 		for _, uid := range msg.Uids {
    139 			if msg, ok := store.Messages[uid]; ok {
    140 				newMap[uid] = msg
    141 			} else {
    142 				newMap[uid] = nil
    143 			}
    144 		}
    145 		store.Messages = newMap
    146 		store.Uids = msg.Uids
    147 		update = true
    148 	case *types.MessageInfo:
    149 		if existing, ok := store.Messages[msg.Uid]; ok && existing != nil {
    150 			merge(existing, msg)
    151 		} else {
    152 			store.Messages[msg.Uid] = msg
    153 		}
    154 		if _, ok := store.pendingHeaders[msg.Uid]; msg.Envelope != nil && ok {
    155 			delete(store.pendingHeaders, msg.Uid)
    156 			if cbs, ok := store.headerCallbacks[msg.Uid]; ok {
    157 				for _, cb := range cbs {
    158 					cb(msg)
    159 				}
    160 			}
    161 		}
    162 		update = true
    163 	case *types.FullMessage:
    164 		if _, ok := store.pendingBodies[msg.Uid]; ok {
    165 			delete(store.pendingBodies, msg.Uid)
    166 			if cbs, ok := store.bodyCallbacks[msg.Uid]; ok {
    167 				for _, cb := range cbs {
    168 					cb(msg.Reader)
    169 				}
    170 			}
    171 		}
    172 	case *types.MessagesDeleted:
    173 		toDelete := make(map[uint32]interface{})
    174 		for _, uid := range msg.Uids {
    175 			toDelete[uid] = nil
    176 			delete(store.Messages, uid)
    177 			if _, ok := store.Deleted[uid]; ok {
    178 				delete(store.Deleted, uid)
    179 			}
    180 		}
    181 		uids := make([]uint32, len(store.Uids)-len(msg.Uids))
    182 		j := 0
    183 		for _, uid := range store.Uids {
    184 			if _, deleted := toDelete[uid]; !deleted && j < len(uids) {
    185 				uids[j] = uid
    186 				j += 1
    187 			}
    188 		}
    189 		store.Uids = uids
    190 		update = true
    191 	}
    192 
    193 	if update {
    194 		store.update()
    195 	}
    196 }
    197 
    198 func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {
    199 	store.onUpdate = fn
    200 }
    201 
    202 func (store *MessageStore) update() {
    203 	if store.onUpdate != nil {
    204 		store.onUpdate(store)
    205 	}
    206 }
    207 
    208 func (store *MessageStore) Delete(uids []uint32,
    209 	cb func(msg types.WorkerMessage)) {
    210 
    211 	var set imap.SeqSet
    212 	for _, uid := range uids {
    213 		set.AddNum(uid)
    214 		store.Deleted[uid] = nil
    215 	}
    216 
    217 	store.worker.PostAction(&types.DeleteMessages{Uids: set}, cb)
    218 	store.update()
    219 }
    220 
    221 func (store *MessageStore) Copy(uids []uint32, dest string,
    222 	cb func(msg types.WorkerMessage)) {
    223 	var set imap.SeqSet
    224 	for _, uid := range uids {
    225 		set.AddNum(uid)
    226 	}
    227 
    228 	store.worker.PostAction(&types.CopyMessages{
    229 		Destination: dest,
    230 		Uids:        set,
    231 	}, cb)
    232 }
    233 
    234 func (store *MessageStore) Move(uids []uint32, dest string,
    235 	cb func(msg types.WorkerMessage)) {
    236 
    237 	var set imap.SeqSet
    238 	for _, uid := range uids {
    239 		set.AddNum(uid)
    240 		store.Deleted[uid] = nil
    241 	}
    242 
    243 	store.worker.PostAction(&types.CopyMessages{
    244 		Destination: dest,
    245 		Uids:        set,
    246 	}, func(msg types.WorkerMessage) {
    247 		switch msg.(type) {
    248 		case *types.Error:
    249 			cb(msg)
    250 		case *types.Done:
    251 			store.worker.PostAction(&types.DeleteMessages{Uids: set}, cb)
    252 		}
    253 	})
    254 
    255 	store.update()
    256 }