2019-03-16 02:36:06 +01:00
|
|
|
package lib
|
|
|
|
|
|
|
|
import (
|
2019-03-31 17:29:57 +02:00
|
|
|
"io"
|
2022-07-26 15:41:14 +02:00
|
|
|
"sync"
|
2019-03-31 17:10:10 +02:00
|
|
|
"time"
|
|
|
|
|
2022-08-08 22:21:41 +02:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/marker"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/sort"
|
2022-07-26 15:41:13 +02:00
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
2019-03-16 02:36:06 +01:00
|
|
|
)
|
|
|
|
|
2019-04-28 15:26:38 +02:00
|
|
|
// Accesses to fields must be guarded by MessageStore.Lock/Unlock
|
2019-03-16 02:36:06 +01:00
|
|
|
type MessageStore struct {
|
2019-10-10 01:57:53 +02:00
|
|
|
Deleted map[uint32]interface{}
|
|
|
|
DirInfo models.DirectoryInfo
|
|
|
|
Messages map[uint32]*models.MessageInfo
|
2020-02-29 03:50:11 +01:00
|
|
|
Sorting bool
|
|
|
|
|
2019-09-18 16:21:45 +02:00
|
|
|
// Ordered list of known UIDs
|
2021-11-12 18:12:02 +01:00
|
|
|
uids []uint32
|
2022-07-26 15:41:14 +02:00
|
|
|
threads []*types.Thread
|
2019-03-30 03:35:53 +01:00
|
|
|
|
2022-08-08 22:21:42 +02:00
|
|
|
selectedUid uint32
|
|
|
|
bodyCallbacks map[uint32][]func(*types.FullMessage)
|
2019-03-30 03:35:53 +01:00
|
|
|
|
2022-07-31 22:16:40 +02:00
|
|
|
// marking
|
2022-08-08 22:21:41 +02:00
|
|
|
marker marker.Marker
|
2019-12-18 06:33:59 +01:00
|
|
|
|
2019-06-27 02:50:27 +02:00
|
|
|
// Search/filter results
|
|
|
|
results []uint32
|
|
|
|
resultIndex int
|
2022-07-05 21:48:40 +02:00
|
|
|
filter []string
|
2019-06-27 02:50:27 +02:00
|
|
|
|
2022-03-24 23:12:14 +01:00
|
|
|
sortCriteria []*types.SortCriterion
|
2020-02-19 08:37:20 +01:00
|
|
|
|
2022-07-05 21:48:38 +02:00
|
|
|
threadedView bool
|
2022-02-24 00:41:13 +01:00
|
|
|
buildThreads bool
|
|
|
|
builder *ThreadBuilder
|
2021-11-12 18:12:02 +01:00
|
|
|
|
2019-03-16 02:36:06 +01:00
|
|
|
// Map of uids we've asked the worker to fetch
|
2019-03-16 02:43:33 +01:00
|
|
|
onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers
|
2022-03-20 09:47:52 +01:00
|
|
|
onFilterChange func(store *MessageStore)
|
2019-09-11 18:37:21 +02:00
|
|
|
onUpdateDirs func()
|
2019-03-16 02:36:06 +01:00
|
|
|
pendingBodies map[uint32]interface{}
|
|
|
|
pendingHeaders map[uint32]interface{}
|
|
|
|
worker *types.Worker
|
2019-07-21 22:01:51 +02:00
|
|
|
|
2019-07-29 16:50:02 +02:00
|
|
|
triggerNewEmail func(*models.MessageInfo)
|
|
|
|
triggerDirectoryChange func()
|
2020-02-24 20:18:31 +01:00
|
|
|
|
2022-07-26 15:41:13 +02:00
|
|
|
threadBuilderDebounce *time.Timer
|
|
|
|
threadBuilderDelay time.Duration
|
2022-08-12 23:15:42 +02:00
|
|
|
threadCallback func()
|
2022-07-26 15:41:14 +02:00
|
|
|
|
2022-08-12 23:15:42 +02:00
|
|
|
// threads mutex protects the store.threads and store.threadCallback
|
2022-07-26 15:41:14 +02:00
|
|
|
threadsMutex sync.Mutex
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
|
|
|
|
2022-07-26 11:30:26 +02:00
|
|
|
const MagicUid = 0xFFFFFFFF
|
|
|
|
|
2019-03-16 02:36:06 +01:00
|
|
|
func NewMessageStore(worker *types.Worker,
|
2019-07-21 22:01:51 +02:00
|
|
|
dirInfo *models.DirectoryInfo,
|
2020-02-19 08:37:20 +01:00
|
|
|
defaultSortCriteria []*types.SortCriterion,
|
2022-07-26 15:41:13 +02:00
|
|
|
thread bool, clientThreads bool, clientThreadsDelay time.Duration,
|
2019-07-29 16:50:02 +02:00
|
|
|
triggerNewEmail func(*models.MessageInfo),
|
2022-07-31 22:16:40 +02:00
|
|
|
triggerDirectoryChange func(),
|
|
|
|
) *MessageStore {
|
2022-07-05 21:48:38 +02:00
|
|
|
if !dirInfo.Caps.Thread {
|
|
|
|
clientThreads = true
|
|
|
|
}
|
|
|
|
|
2019-03-16 02:36:06 +01:00
|
|
|
return &MessageStore{
|
2019-12-04 23:46:34 +01:00
|
|
|
Deleted: make(map[uint32]interface{}),
|
|
|
|
DirInfo: *dirInfo,
|
|
|
|
Messages: make(map[uint32]*models.MessageInfo),
|
2019-03-16 02:36:06 +01:00
|
|
|
|
2022-08-08 22:21:41 +02:00
|
|
|
selectedUid: MagicUid,
|
|
|
|
|
2022-08-08 22:21:42 +02:00
|
|
|
bodyCallbacks: make(map[uint32][]func(*types.FullMessage)),
|
2019-03-30 03:35:53 +01:00
|
|
|
|
2022-07-05 21:48:38 +02:00
|
|
|
threadedView: thread,
|
|
|
|
buildThreads: clientThreads,
|
2021-11-12 18:12:02 +01:00
|
|
|
|
2022-07-05 21:48:40 +02:00
|
|
|
filter: []string{"filter"},
|
2022-03-24 23:12:14 +01:00
|
|
|
sortCriteria: defaultSortCriteria,
|
2020-02-19 08:37:20 +01:00
|
|
|
|
2019-03-16 02:36:06 +01:00
|
|
|
pendingBodies: make(map[uint32]interface{}),
|
|
|
|
pendingHeaders: make(map[uint32]interface{}),
|
|
|
|
worker: worker,
|
2019-07-21 22:01:51 +02:00
|
|
|
|
2019-07-29 16:50:02 +02:00
|
|
|
triggerNewEmail: triggerNewEmail,
|
|
|
|
triggerDirectoryChange: triggerDirectoryChange,
|
2020-02-24 20:18:31 +01:00
|
|
|
|
2022-07-26 15:41:13 +02:00
|
|
|
threadBuilderDelay: clientThreadsDelay,
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-30 03:35:53 +01:00
|
|
|
func (store *MessageStore) FetchHeaders(uids []uint32,
|
2022-08-08 22:21:42 +02:00
|
|
|
cb func(types.WorkerMessage),
|
2022-07-31 22:16:40 +02:00
|
|
|
) {
|
2019-03-16 02:36:06 +01:00
|
|
|
// TODO: this could be optimized by pre-allocating toFetch and trimming it
|
|
|
|
// at the end. In practice we expect to get most messages back in one frame.
|
2019-07-08 04:43:57 +02:00
|
|
|
var toFetch []uint32
|
2019-03-16 02:36:06 +01:00
|
|
|
for _, uid := range uids {
|
|
|
|
if _, ok := store.pendingHeaders[uid]; !ok {
|
2019-07-08 04:43:57 +02:00
|
|
|
toFetch = append(toFetch, uid)
|
2019-03-16 02:36:06 +01:00
|
|
|
store.pendingHeaders[uid] = nil
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
|
|
|
}
|
2019-07-08 04:43:57 +02:00
|
|
|
if len(toFetch) > 0 {
|
2022-05-05 03:28:41 +02:00
|
|
|
store.worker.PostAction(&types.FetchMessageHeaders{Uids: toFetch}, func(msg types.WorkerMessage) {
|
2022-09-16 21:41:07 +02:00
|
|
|
if msg, ok := msg.(*types.Error); ok {
|
2022-05-05 03:28:41 +02:00
|
|
|
for _, uid := range toFetch {
|
2022-09-16 21:41:07 +02:00
|
|
|
store.postInvalidMessageInfo(uid, msg.Error)
|
2022-05-05 03:28:41 +02:00
|
|
|
delete(store.pendingHeaders, uid)
|
|
|
|
}
|
|
|
|
}
|
2022-08-08 22:21:42 +02:00
|
|
|
if cb != nil {
|
|
|
|
cb(msg)
|
|
|
|
}
|
2022-05-05 03:28:41 +02:00
|
|
|
})
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-16 21:41:07 +02:00
|
|
|
func (store *MessageStore) postInvalidMessageInfo(uid uint32, err error) {
|
|
|
|
logging.Errorf("Unable to fetch header %d: %w", uid, err)
|
|
|
|
info := &models.MessageInfo{
|
|
|
|
Envelope: &models.Envelope{},
|
|
|
|
Flags: []models.Flag{models.SeenFlag},
|
|
|
|
Uid: uid,
|
|
|
|
Error: err,
|
|
|
|
}
|
|
|
|
store.Update(&types.MessageInfo{Info: info})
|
|
|
|
}
|
|
|
|
|
2020-04-24 22:31:39 +02:00
|
|
|
func (store *MessageStore) FetchFull(uids []uint32, cb func(*types.FullMessage)) {
|
2019-03-30 03:35:53 +01:00
|
|
|
// TODO: this could be optimized by pre-allocating toFetch and trimming it
|
|
|
|
// at the end. In practice we expect to get most messages back in one frame.
|
2019-07-08 04:43:57 +02:00
|
|
|
var toFetch []uint32
|
2019-03-30 03:35:53 +01:00
|
|
|
for _, uid := range uids {
|
|
|
|
if _, ok := store.pendingBodies[uid]; !ok {
|
2019-07-08 04:43:57 +02:00
|
|
|
toFetch = append(toFetch, uid)
|
2019-03-30 03:35:53 +01:00
|
|
|
store.pendingBodies[uid] = nil
|
|
|
|
if cb != nil {
|
|
|
|
if list, ok := store.bodyCallbacks[uid]; ok {
|
|
|
|
store.bodyCallbacks[uid] = append(list, cb)
|
|
|
|
} else {
|
2020-04-24 22:31:39 +02:00
|
|
|
store.bodyCallbacks[uid] = []func(*types.FullMessage){cb}
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
|
|
|
}
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
|
|
|
}
|
2019-07-08 04:43:57 +02:00
|
|
|
if len(toFetch) > 0 {
|
2019-07-09 00:32:31 +02:00
|
|
|
store.worker.PostAction(&types.FetchFullMessages{
|
|
|
|
Uids: toFetch,
|
|
|
|
}, func(msg types.WorkerMessage) {
|
2022-07-31 14:32:48 +02:00
|
|
|
if _, ok := msg.(*types.Error); ok {
|
2019-07-09 00:32:31 +02:00
|
|
|
for _, uid := range toFetch {
|
2022-05-05 03:28:41 +02:00
|
|
|
delete(store.pendingBodies, uid)
|
2019-09-03 21:34:07 +02:00
|
|
|
delete(store.bodyCallbacks, uid)
|
2019-07-09 00:32:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-17 11:44:38 +02:00
|
|
|
func (store *MessageStore) FetchBodyPart(uid uint32, part []int, cb func(io.Reader)) {
|
2019-03-31 18:14:37 +02:00
|
|
|
store.worker.PostAction(&types.FetchMessageBodyPart{
|
2020-05-16 20:03:42 +02:00
|
|
|
Uid: uid,
|
|
|
|
Part: part,
|
2019-03-31 18:14:37 +02:00
|
|
|
}, func(resp types.WorkerMessage) {
|
|
|
|
msg, ok := resp.(*types.MessageBodyPart)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2019-07-08 04:43:56 +02:00
|
|
|
cb(msg.Part.Reader)
|
2019-03-31 18:14:37 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-08 04:43:56 +02:00
|
|
|
func merge(to *models.MessageInfo, from *models.MessageInfo) {
|
2019-03-31 17:10:10 +02:00
|
|
|
if from.BodyStructure != nil {
|
|
|
|
to.BodyStructure = from.BodyStructure
|
|
|
|
}
|
2019-03-30 03:35:53 +01:00
|
|
|
if from.Envelope != nil {
|
|
|
|
to.Envelope = from.Envelope
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
2019-06-09 20:55:04 +02:00
|
|
|
to.Flags = from.Flags
|
2019-12-23 12:51:58 +01:00
|
|
|
to.Labels = from.Labels
|
2019-03-31 17:10:10 +02:00
|
|
|
if from.Size != 0 {
|
|
|
|
to.Size = from.Size
|
|
|
|
}
|
|
|
|
var zero time.Time
|
|
|
|
if from.InternalDate != zero {
|
|
|
|
to.InternalDate = from.InternalDate
|
|
|
|
}
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) Update(msg types.WorkerMessage) {
|
|
|
|
update := false
|
2022-08-30 17:36:41 +02:00
|
|
|
updateThreads := false
|
2019-07-29 16:50:02 +02:00
|
|
|
directoryChange := false
|
2019-03-16 02:36:06 +01:00
|
|
|
switch msg := msg.(type) {
|
|
|
|
case *types.DirectoryInfo:
|
2019-07-08 04:43:56 +02:00
|
|
|
store.DirInfo = *msg.Info
|
2022-05-30 14:34:18 +02:00
|
|
|
if !msg.SkipSort {
|
|
|
|
store.Sort(store.sortCriteria, nil)
|
|
|
|
}
|
2019-03-16 02:36:06 +01:00
|
|
|
update = true
|
|
|
|
case *types.DirectoryContents:
|
2019-07-08 04:43:56 +02:00
|
|
|
newMap := make(map[uint32]*models.MessageInfo)
|
2019-03-16 02:36:06 +01:00
|
|
|
for _, uid := range msg.Uids {
|
|
|
|
if msg, ok := store.Messages[uid]; ok {
|
|
|
|
newMap[uid] = msg
|
|
|
|
} else {
|
|
|
|
newMap[uid] = nil
|
2019-07-29 16:50:02 +02:00
|
|
|
directoryChange = true
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
store.Messages = newMap
|
2019-07-17 09:35:50 +02:00
|
|
|
store.uids = msg.Uids
|
2022-08-31 01:24:59 +02:00
|
|
|
if store.threadedView {
|
|
|
|
store.runThreadBuilderNow()
|
|
|
|
}
|
2021-11-12 18:12:02 +01:00
|
|
|
case *types.DirectoryThreaded:
|
|
|
|
var uids []uint32
|
|
|
|
newMap := make(map[uint32]*models.MessageInfo)
|
|
|
|
|
|
|
|
for i := len(msg.Threads) - 1; i >= 0; i-- {
|
2022-07-29 22:31:54 +02:00
|
|
|
_ = msg.Threads[i].Walk(func(t *types.Thread, level int, currentErr error) error {
|
2021-11-12 18:12:02 +01:00
|
|
|
uid := t.Uid
|
|
|
|
uids = append([]uint32{uid}, uids...)
|
|
|
|
if msg, ok := store.Messages[uid]; ok {
|
|
|
|
newMap[uid] = msg
|
|
|
|
} else {
|
|
|
|
newMap[uid] = nil
|
|
|
|
directoryChange = true
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
store.Messages = newMap
|
|
|
|
store.uids = uids
|
2022-07-26 15:41:14 +02:00
|
|
|
store.threads = msg.Threads
|
2021-11-12 18:12:02 +01:00
|
|
|
update = true
|
2019-03-16 02:36:06 +01:00
|
|
|
case *types.MessageInfo:
|
2019-07-08 04:43:56 +02:00
|
|
|
if existing, ok := store.Messages[msg.Info.Uid]; ok && existing != nil {
|
|
|
|
merge(existing, msg.Info)
|
2022-07-31 14:32:48 +02:00
|
|
|
} else if msg.Info.Envelope != nil {
|
|
|
|
store.Messages[msg.Info.Uid] = msg.Info
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
2019-07-21 22:01:51 +02:00
|
|
|
seen := false
|
|
|
|
recent := false
|
|
|
|
for _, flag := range msg.Info.Flags {
|
|
|
|
if flag == models.RecentFlag {
|
|
|
|
recent = true
|
|
|
|
} else if flag == models.SeenFlag {
|
|
|
|
seen = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !seen && recent {
|
|
|
|
store.triggerNewEmail(msg.Info)
|
|
|
|
}
|
2019-07-08 04:43:56 +02:00
|
|
|
if _, ok := store.pendingHeaders[msg.Info.Uid]; msg.Info.Envelope != nil && ok {
|
|
|
|
delete(store.pendingHeaders, msg.Info.Uid)
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
2022-02-24 00:41:13 +01:00
|
|
|
if store.builder != nil {
|
|
|
|
store.builder.Update(msg.Info)
|
|
|
|
}
|
2019-03-16 02:36:06 +01:00
|
|
|
update = true
|
2022-08-30 17:36:41 +02:00
|
|
|
updateThreads = true
|
2019-03-31 18:35:51 +02:00
|
|
|
case *types.FullMessage:
|
2019-07-08 04:43:56 +02:00
|
|
|
if _, ok := store.pendingBodies[msg.Content.Uid]; ok {
|
|
|
|
delete(store.pendingBodies, msg.Content.Uid)
|
|
|
|
if cbs, ok := store.bodyCallbacks[msg.Content.Uid]; ok {
|
2019-03-30 03:35:53 +01:00
|
|
|
for _, cb := range cbs {
|
2020-04-24 22:31:39 +02:00
|
|
|
cb(msg)
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
2019-07-09 00:32:31 +02:00
|
|
|
delete(store.bodyCallbacks, msg.Content.Uid)
|
2019-03-30 03:35:53 +01:00
|
|
|
}
|
|
|
|
}
|
2019-03-21 04:23:38 +01:00
|
|
|
case *types.MessagesDeleted:
|
2019-12-10 21:07:01 +01:00
|
|
|
if len(store.uids) < len(msg.Uids) {
|
|
|
|
update = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2019-03-21 04:23:38 +01:00
|
|
|
toDelete := make(map[uint32]interface{})
|
|
|
|
for _, uid := range msg.Uids {
|
|
|
|
toDelete[uid] = nil
|
|
|
|
delete(store.Messages, uid)
|
2019-09-03 21:34:07 +02:00
|
|
|
delete(store.Deleted, uid)
|
2019-03-21 04:23:38 +01:00
|
|
|
}
|
2019-07-17 09:35:50 +02:00
|
|
|
uids := make([]uint32, len(store.uids)-len(msg.Uids))
|
2019-03-21 04:23:38 +01:00
|
|
|
j := 0
|
2019-07-17 09:35:50 +02:00
|
|
|
for _, uid := range store.uids {
|
2019-05-16 21:28:33 +02:00
|
|
|
if _, deleted := toDelete[uid]; !deleted && j < len(uids) {
|
|
|
|
uids[j] = uid
|
2019-03-21 04:23:38 +01:00
|
|
|
j += 1
|
|
|
|
}
|
|
|
|
}
|
2019-07-17 09:35:50 +02:00
|
|
|
store.uids = uids
|
2020-07-09 09:46:45 +02:00
|
|
|
|
|
|
|
var newResults []uint32
|
|
|
|
for _, res := range store.results {
|
|
|
|
if _, deleted := toDelete[res]; !deleted {
|
|
|
|
newResults = append(newResults, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
store.results = newResults
|
|
|
|
|
2022-07-26 15:41:14 +02:00
|
|
|
for _, thread := range store.Threads() {
|
2022-07-29 22:31:54 +02:00
|
|
|
_ = thread.Walk(func(t *types.Thread, _ int, _ error) error {
|
2021-11-12 18:12:02 +01:00
|
|
|
if _, deleted := toDelete[t.Uid]; deleted {
|
|
|
|
t.Deleted = true
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-03-21 04:23:38 +01:00
|
|
|
update = true
|
2022-08-30 17:36:41 +02:00
|
|
|
updateThreads = true
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
2019-04-28 15:26:38 +02:00
|
|
|
|
2019-03-30 15:40:55 +01:00
|
|
|
if update {
|
2022-08-30 17:36:41 +02:00
|
|
|
store.update(updateThreads)
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
2019-07-29 16:50:02 +02:00
|
|
|
|
|
|
|
if directoryChange && store.triggerDirectoryChange != nil {
|
|
|
|
store.triggerDirectoryChange()
|
|
|
|
}
|
2019-03-16 02:36:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {
|
|
|
|
store.onUpdate = fn
|
|
|
|
}
|
2019-03-21 04:23:38 +01:00
|
|
|
|
2022-03-20 09:47:52 +01:00
|
|
|
func (store *MessageStore) OnFilterChange(fn func(store *MessageStore)) {
|
|
|
|
store.onFilterChange = fn
|
|
|
|
}
|
|
|
|
|
2019-09-11 18:37:21 +02:00
|
|
|
func (store *MessageStore) OnUpdateDirs(fn func()) {
|
|
|
|
store.onUpdateDirs = fn
|
|
|
|
}
|
|
|
|
|
2022-08-30 17:36:41 +02:00
|
|
|
func (store *MessageStore) update(threads bool) {
|
2019-03-30 15:40:55 +01:00
|
|
|
if store.onUpdate != nil {
|
|
|
|
store.onUpdate(store)
|
|
|
|
}
|
2019-09-11 18:37:21 +02:00
|
|
|
if store.onUpdateDirs != nil {
|
|
|
|
store.onUpdateDirs()
|
|
|
|
}
|
2022-08-30 17:36:41 +02:00
|
|
|
if store.BuildThreads() && store.ThreadedView() && threads {
|
2022-02-24 00:41:13 +01:00
|
|
|
store.runThreadBuilder()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-05 21:48:38 +02:00
|
|
|
func (store *MessageStore) SetThreadedView(thread bool) {
|
|
|
|
store.threadedView = thread
|
|
|
|
if store.buildThreads {
|
|
|
|
if store.threadedView {
|
|
|
|
store.runThreadBuilder()
|
2022-08-12 23:15:42 +02:00
|
|
|
} else if store.threadBuilderDebounce != nil {
|
|
|
|
store.threadBuilderDebounce.Stop()
|
2022-07-05 21:48:38 +02:00
|
|
|
}
|
2022-02-24 00:41:13 +01:00
|
|
|
return
|
|
|
|
}
|
2022-07-05 21:48:38 +02:00
|
|
|
store.Sort(store.sortCriteria, nil)
|
|
|
|
}
|
|
|
|
|
2022-07-26 15:41:14 +02:00
|
|
|
func (store *MessageStore) Threads() []*types.Thread {
|
|
|
|
store.threadsMutex.Lock()
|
|
|
|
defer store.threadsMutex.Unlock()
|
|
|
|
return store.threads
|
|
|
|
}
|
|
|
|
|
2022-07-05 21:48:38 +02:00
|
|
|
func (store *MessageStore) ThreadedView() bool {
|
|
|
|
return store.threadedView
|
2022-02-24 00:41:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) BuildThreads() bool {
|
|
|
|
return store.buildThreads
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) runThreadBuilder() {
|
2022-07-26 15:41:13 +02:00
|
|
|
if store.threadBuilderDebounce != nil {
|
|
|
|
if store.threadBuilderDebounce.Stop() {
|
|
|
|
logging.Infof("thread builder debounced")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
store.threadBuilderDebounce = time.AfterFunc(store.threadBuilderDelay, func() {
|
2022-08-30 17:36:40 +02:00
|
|
|
store.runThreadBuilderNow()
|
|
|
|
})
|
|
|
|
}
|
2022-07-26 15:41:14 +02:00
|
|
|
|
2022-08-30 17:36:40 +02:00
|
|
|
// runThreadBuilderNow runs the threadbuilder without any debounce logic
|
|
|
|
func (store *MessageStore) runThreadBuilderNow() {
|
|
|
|
if store.builder == nil {
|
|
|
|
store.builder = NewThreadBuilder()
|
|
|
|
for _, msg := range store.Messages {
|
|
|
|
store.builder.Update(msg)
|
2022-07-26 15:41:13 +02:00
|
|
|
}
|
2022-08-30 17:36:40 +02:00
|
|
|
}
|
|
|
|
// build new threads
|
|
|
|
th := store.builder.Threads(store.uids)
|
|
|
|
|
|
|
|
// save local threads to the message store variable and
|
|
|
|
// run callback if defined (callback should reposition cursor)
|
|
|
|
store.threadsMutex.Lock()
|
|
|
|
store.threads = th
|
|
|
|
if store.threadCallback != nil {
|
|
|
|
store.threadCallback()
|
|
|
|
}
|
|
|
|
store.threadsMutex.Unlock()
|
|
|
|
|
|
|
|
// invalidate message list
|
|
|
|
if store.onUpdate != nil {
|
|
|
|
store.onUpdate(store)
|
|
|
|
}
|
2019-03-30 15:40:55 +01:00
|
|
|
}
|
|
|
|
|
2022-08-08 22:21:45 +02:00
|
|
|
// SelectedThread returns the thread with the UID from the selected message
|
|
|
|
func (store *MessageStore) SelectedThread() *types.Thread {
|
|
|
|
var thread *types.Thread
|
|
|
|
for _, root := range store.Threads() {
|
|
|
|
found := false
|
|
|
|
err := root.Walk(func(t *types.Thread, _ int, _ error) error {
|
|
|
|
if t.Uid == store.SelectedUid() {
|
|
|
|
thread = t
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
logging.Errorf("SelectedThread failed: %w", err)
|
|
|
|
}
|
|
|
|
if found {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return thread
|
|
|
|
}
|
|
|
|
|
2019-05-14 22:34:42 +02:00
|
|
|
func (store *MessageStore) Delete(uids []uint32,
|
2022-07-31 22:16:40 +02:00
|
|
|
cb func(msg types.WorkerMessage),
|
|
|
|
) {
|
2019-03-21 04:23:38 +01:00
|
|
|
for _, uid := range uids {
|
2019-03-30 15:40:55 +01:00
|
|
|
store.Deleted[uid] = nil
|
2019-03-21 04:23:38 +01:00
|
|
|
}
|
2019-04-28 15:26:38 +02:00
|
|
|
|
2022-05-05 03:28:41 +02:00
|
|
|
store.worker.PostAction(&types.DeleteMessages{Uids: uids},
|
|
|
|
func(msg types.WorkerMessage) {
|
2022-07-31 14:32:48 +02:00
|
|
|
if _, ok := msg.(*types.Error); ok {
|
2022-05-05 03:28:41 +02:00
|
|
|
store.revertDeleted(uids)
|
|
|
|
}
|
2022-08-05 04:43:48 +02:00
|
|
|
if _, ok := msg.(*types.Unsupported); ok {
|
|
|
|
store.revertDeleted(uids)
|
|
|
|
}
|
2022-05-05 03:28:41 +02:00
|
|
|
cb(msg)
|
|
|
|
})
|
2019-03-21 04:23:38 +01:00
|
|
|
}
|
2019-05-14 22:34:42 +02:00
|
|
|
|
2022-05-05 03:28:41 +02:00
|
|
|
func (store *MessageStore) revertDeleted(uids []uint32) {
|
|
|
|
for _, uid := range uids {
|
2022-07-29 21:42:02 +02:00
|
|
|
delete(store.Deleted, uid)
|
2022-05-05 03:28:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-08 19:41:56 +02:00
|
|
|
func (store *MessageStore) Copy(uids []uint32, dest string, createDest bool,
|
2022-07-31 22:16:40 +02:00
|
|
|
cb func(msg types.WorkerMessage),
|
|
|
|
) {
|
2019-06-08 19:41:56 +02:00
|
|
|
if createDest {
|
|
|
|
store.worker.PostAction(&types.CreateDirectory{
|
|
|
|
Directory: dest,
|
2019-07-11 06:49:09 +02:00
|
|
|
Quiet: true,
|
2019-06-08 19:41:56 +02:00
|
|
|
}, cb)
|
|
|
|
}
|
|
|
|
|
2019-05-14 22:34:42 +02:00
|
|
|
store.worker.PostAction(&types.CopyMessages{
|
|
|
|
Destination: dest,
|
2019-07-08 04:43:57 +02:00
|
|
|
Uids: uids,
|
2019-05-14 22:34:42 +02:00
|
|
|
}, cb)
|
|
|
|
}
|
2019-05-14 22:55:50 +02:00
|
|
|
|
2019-06-08 19:41:56 +02:00
|
|
|
func (store *MessageStore) Move(uids []uint32, dest string, createDest bool,
|
2022-07-31 22:16:40 +02:00
|
|
|
cb func(msg types.WorkerMessage),
|
|
|
|
) {
|
2019-05-14 22:55:50 +02:00
|
|
|
for _, uid := range uids {
|
|
|
|
store.Deleted[uid] = nil
|
|
|
|
}
|
|
|
|
|
2019-06-08 19:41:56 +02:00
|
|
|
if createDest {
|
|
|
|
store.worker.PostAction(&types.CreateDirectory{
|
|
|
|
Directory: dest,
|
2019-07-11 06:49:09 +02:00
|
|
|
Quiet: true,
|
2019-10-09 09:46:01 +02:00
|
|
|
}, nil) // quiet doesn't return an error, don't want the done cb here
|
2019-06-08 19:41:56 +02:00
|
|
|
}
|
|
|
|
|
2022-08-16 23:23:41 +02:00
|
|
|
store.worker.PostAction(&types.MoveMessages{
|
2019-05-14 22:55:50 +02:00
|
|
|
Destination: dest,
|
2019-07-08 04:43:57 +02:00
|
|
|
Uids: uids,
|
2019-05-14 22:55:50 +02:00
|
|
|
}, func(msg types.WorkerMessage) {
|
|
|
|
switch msg.(type) {
|
|
|
|
case *types.Error:
|
2022-05-05 03:28:41 +02:00
|
|
|
store.revertDeleted(uids)
|
2019-05-14 22:55:50 +02:00
|
|
|
cb(msg)
|
|
|
|
case *types.Done:
|
2022-08-16 23:23:41 +02:00
|
|
|
cb(msg)
|
2019-05-14 22:55:50 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-06-09 20:55:34 +02:00
|
|
|
|
2020-07-05 16:29:52 +02:00
|
|
|
func (store *MessageStore) Flag(uids []uint32, flag models.Flag,
|
2022-07-31 22:16:40 +02:00
|
|
|
enable bool, cb func(msg types.WorkerMessage),
|
|
|
|
) {
|
2020-07-05 16:29:52 +02:00
|
|
|
store.worker.PostAction(&types.FlagMessages{
|
|
|
|
Enable: enable,
|
2020-07-17 17:50:24 +02:00
|
|
|
Flag: flag,
|
|
|
|
Uids: uids,
|
2019-06-09 20:55:34 +02:00
|
|
|
}, cb)
|
|
|
|
}
|
2019-06-11 07:05:55 +02:00
|
|
|
|
2020-05-25 16:59:48 +02:00
|
|
|
func (store *MessageStore) Answered(uids []uint32, answered bool,
|
2022-07-31 22:16:40 +02:00
|
|
|
cb func(msg types.WorkerMessage),
|
|
|
|
) {
|
2020-05-25 16:59:48 +02:00
|
|
|
store.worker.PostAction(&types.AnsweredMessages{
|
|
|
|
Answered: answered,
|
|
|
|
Uids: uids,
|
|
|
|
}, cb)
|
|
|
|
}
|
|
|
|
|
2019-07-17 09:35:50 +02:00
|
|
|
func (store *MessageStore) Uids() []uint32 {
|
2022-07-05 21:48:38 +02:00
|
|
|
if store.ThreadedView() && store.builder != nil {
|
2022-03-08 18:13:39 +01:00
|
|
|
if uids := store.builder.Uids(); len(uids) > 0 {
|
|
|
|
return uids
|
|
|
|
}
|
|
|
|
}
|
2019-07-17 09:35:50 +02:00
|
|
|
return store.uids
|
|
|
|
}
|
|
|
|
|
2019-07-08 04:43:56 +02:00
|
|
|
func (store *MessageStore) Selected() *models.MessageInfo {
|
2022-07-26 11:30:26 +02:00
|
|
|
return store.Messages[store.selectedUid]
|
2019-06-11 07:05:55 +02:00
|
|
|
}
|
|
|
|
|
2022-07-26 11:30:26 +02:00
|
|
|
func (store *MessageStore) SelectedUid() uint32 {
|
|
|
|
if store.selectedUid == MagicUid && len(store.Uids()) > 0 {
|
|
|
|
uids := store.Uids()
|
|
|
|
store.selectedUid = uids[len(uids)-1]
|
2019-06-11 07:05:55 +02:00
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
return store.selectedUid
|
2019-12-18 06:33:59 +01:00
|
|
|
}
|
|
|
|
|
2022-07-26 11:30:26 +02:00
|
|
|
func (store *MessageStore) Select(uid uint32) {
|
2022-08-12 23:15:42 +02:00
|
|
|
store.threadsMutex.Lock()
|
|
|
|
if store.threadCallback != nil {
|
|
|
|
store.threadCallback = nil
|
|
|
|
}
|
|
|
|
store.threadsMutex.Unlock()
|
2022-07-26 11:30:26 +02:00
|
|
|
store.selectedUid = uid
|
2022-08-08 22:21:41 +02:00
|
|
|
if store.marker != nil {
|
|
|
|
store.marker.UpdateVisualMark()
|
2019-12-18 06:33:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-04 16:05:06 +02:00
|
|
|
func (store *MessageStore) NextPrev(delta int) {
|
2019-07-29 01:41:44 +02:00
|
|
|
uids := store.Uids()
|
|
|
|
if len(uids) == 0 {
|
2019-06-11 07:05:55 +02:00
|
|
|
return
|
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
|
|
|
|
uid := store.SelectedUid()
|
|
|
|
|
|
|
|
newIdx := store.FindIndexByUid(uid)
|
|
|
|
if newIdx < 0 {
|
|
|
|
store.Select(uids[len(uids)-1])
|
2022-07-31 22:20:24 +02:00
|
|
|
return
|
2022-07-26 11:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
newIdx -= delta
|
|
|
|
if newIdx >= len(uids) {
|
|
|
|
newIdx = len(uids) - 1
|
|
|
|
} else if newIdx < 0 {
|
|
|
|
newIdx = 0
|
2022-07-24 04:03:44 +02:00
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
|
|
|
|
store.Select(uids[newIdx])
|
|
|
|
|
2022-08-12 23:15:42 +02:00
|
|
|
if store.BuildThreads() && store.ThreadedView() {
|
|
|
|
store.threadsMutex.Lock()
|
|
|
|
store.threadCallback = func() {
|
|
|
|
if uids := store.Uids(); len(uids) > newIdx {
|
|
|
|
store.selectedUid = uids[newIdx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
store.threadsMutex.Unlock()
|
|
|
|
}
|
|
|
|
|
2022-08-08 22:21:41 +02:00
|
|
|
if store.marker != nil {
|
|
|
|
store.marker.UpdateVisualMark()
|
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
|
2019-07-22 03:26:37 +02:00
|
|
|
nextResultIndex := len(store.results) - store.resultIndex - 2*delta
|
|
|
|
if nextResultIndex < 0 || nextResultIndex >= len(store.results) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
nextResultUid := store.results[nextResultIndex]
|
2022-07-26 11:30:26 +02:00
|
|
|
if nextResultUid == store.SelectedUid() {
|
2019-07-22 03:26:37 +02:00
|
|
|
store.resultIndex += delta
|
|
|
|
}
|
2019-06-11 07:05:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) Next() {
|
2019-08-04 16:05:06 +02:00
|
|
|
store.NextPrev(1)
|
2019-06-11 07:05:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) Prev() {
|
2019-08-04 16:05:06 +02:00
|
|
|
store.NextPrev(-1)
|
2019-06-11 07:05:55 +02:00
|
|
|
}
|
2019-06-27 02:50:27 +02:00
|
|
|
|
2019-08-28 06:39:07 +02:00
|
|
|
func (store *MessageStore) Search(args []string, cb func([]uint32)) {
|
2019-06-27 02:50:27 +02:00
|
|
|
store.worker.PostAction(&types.SearchDirectory{
|
2019-08-28 06:39:07 +02:00
|
|
|
Argv: args,
|
2019-06-27 02:50:27 +02:00
|
|
|
}, func(msg types.WorkerMessage) {
|
2022-07-31 14:32:48 +02:00
|
|
|
if msg, ok := msg.(*types.SearchResults); ok {
|
2022-03-14 11:49:37 +01:00
|
|
|
allowedUids := store.Uids()
|
|
|
|
uids := make([]uint32, 0, len(msg.Uids))
|
|
|
|
for _, uid := range msg.Uids {
|
|
|
|
for _, uidCheck := range allowedUids {
|
|
|
|
if uid == uidCheck {
|
|
|
|
uids = append(uids, uid)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.SortBy(uids, allowedUids)
|
|
|
|
cb(uids)
|
2019-06-27 02:50:27 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) ApplySearch(results []uint32) {
|
|
|
|
store.results = results
|
|
|
|
store.resultIndex = -1
|
|
|
|
store.NextResult()
|
|
|
|
}
|
|
|
|
|
2022-07-05 21:48:40 +02:00
|
|
|
func (store *MessageStore) SetFilter(args []string) {
|
|
|
|
store.filter = append(store.filter, args...)
|
2019-07-17 09:35:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) ApplyClear() {
|
2022-07-05 21:48:40 +02:00
|
|
|
store.filter = []string{"filter"}
|
2022-07-05 21:48:41 +02:00
|
|
|
store.results = nil
|
2022-03-20 09:47:52 +01:00
|
|
|
if store.onFilterChange != nil {
|
|
|
|
store.onFilterChange(store)
|
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
store.Sort(nil, nil)
|
2019-07-17 09:35:50 +02:00
|
|
|
}
|
|
|
|
|
2019-06-27 02:50:27 +02:00
|
|
|
func (store *MessageStore) nextPrevResult(delta int) {
|
|
|
|
if len(store.results) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
store.resultIndex += delta
|
|
|
|
if store.resultIndex >= len(store.results) {
|
|
|
|
store.resultIndex = 0
|
|
|
|
}
|
|
|
|
if store.resultIndex < 0 {
|
|
|
|
store.resultIndex = len(store.results) - 1
|
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
store.Select(store.results[len(store.results)-store.resultIndex-1])
|
2022-08-30 17:36:41 +02:00
|
|
|
store.update(false)
|
2019-06-27 02:50:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) NextResult() {
|
|
|
|
store.nextPrevResult(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) PrevResult() {
|
|
|
|
store.nextPrevResult(-1)
|
|
|
|
}
|
2019-09-11 21:01:05 +02:00
|
|
|
|
|
|
|
func (store *MessageStore) ModifyLabels(uids []uint32, add, remove []string,
|
2022-07-31 22:16:40 +02:00
|
|
|
cb func(msg types.WorkerMessage),
|
|
|
|
) {
|
2019-09-11 21:01:05 +02:00
|
|
|
store.worker.PostAction(&types.ModifyLabels{
|
|
|
|
Uids: uids,
|
|
|
|
Add: add,
|
|
|
|
Remove: remove,
|
|
|
|
}, cb)
|
|
|
|
}
|
2019-09-20 00:37:44 +02:00
|
|
|
|
2022-07-05 21:48:40 +02:00
|
|
|
func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func(types.WorkerMessage)) {
|
2022-07-26 15:13:21 +02:00
|
|
|
store.sortCriteria = criteria
|
2020-02-29 03:50:11 +01:00
|
|
|
store.Sorting = true
|
2021-11-12 18:12:02 +01:00
|
|
|
|
|
|
|
handle_return := func(msg types.WorkerMessage) {
|
2022-08-12 23:15:42 +02:00
|
|
|
store.Select(store.SelectedUid())
|
2020-02-29 03:50:11 +01:00
|
|
|
store.Sorting = false
|
|
|
|
if cb != nil {
|
2022-07-05 21:48:40 +02:00
|
|
|
cb(msg)
|
2020-02-29 03:50:11 +01:00
|
|
|
}
|
2021-11-12 18:12:02 +01:00
|
|
|
}
|
|
|
|
|
2022-07-05 21:48:38 +02:00
|
|
|
if store.threadedView && !store.buildThreads {
|
2021-11-12 18:12:02 +01:00
|
|
|
store.worker.PostAction(&types.FetchDirectoryThreaded{
|
2022-07-05 21:48:40 +02:00
|
|
|
SortCriteria: criteria,
|
|
|
|
FilterCriteria: store.filter,
|
2021-11-12 18:12:02 +01:00
|
|
|
}, handle_return)
|
|
|
|
} else {
|
|
|
|
store.worker.PostAction(&types.FetchDirectoryContents{
|
2022-07-05 21:48:40 +02:00
|
|
|
SortCriteria: criteria,
|
|
|
|
FilterCriteria: store.filter,
|
2021-11-12 18:12:02 +01:00
|
|
|
}, handle_return)
|
|
|
|
}
|
2019-09-20 00:37:44 +02:00
|
|
|
}
|
2019-12-18 06:33:59 +01:00
|
|
|
|
2022-07-26 15:13:21 +02:00
|
|
|
func (store *MessageStore) GetCurrentSortCriteria() []*types.SortCriterion {
|
|
|
|
return store.sortCriteria
|
|
|
|
}
|
|
|
|
|
2022-08-08 22:21:41 +02:00
|
|
|
func (store *MessageStore) SetMarker(m marker.Marker) {
|
|
|
|
store.marker = m
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *MessageStore) Marker() marker.Marker {
|
|
|
|
if store.marker == nil {
|
|
|
|
store.marker = marker.New(store)
|
2019-12-18 06:33:59 +01:00
|
|
|
}
|
2022-08-08 22:21:41 +02:00
|
|
|
return store.marker
|
2019-12-18 06:33:59 +01:00
|
|
|
}
|
2022-07-26 11:30:26 +02:00
|
|
|
|
|
|
|
// FindIndexByUid returns the index in store.Uids() or -1 if not found
|
|
|
|
func (store *MessageStore) FindIndexByUid(uid uint32) int {
|
|
|
|
for idx, u := range store.Uids() {
|
|
|
|
if u == uid {
|
|
|
|
return idx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
2022-07-26 13:48:27 +02:00
|
|
|
|
|
|
|
// Capabilities returns a models.Capabilities struct or nil if not available
|
|
|
|
func (store *MessageStore) Capabilities() *models.Capabilities {
|
|
|
|
return store.DirInfo.Caps
|
|
|
|
}
|
2022-08-08 22:21:41 +02:00
|
|
|
|
|
|
|
// SelectedIndex returns the index of the selected message in the uid list or
|
|
|
|
// -1 if not found
|
|
|
|
func (store *MessageStore) SelectedIndex() int {
|
|
|
|
return store.FindIndexByUid(store.selectedUid)
|
|
|
|
}
|