2021-11-12 18:12:02 +01:00
|
|
|
package types
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-03-08 18:13:39 +01:00
|
|
|
"sort"
|
2022-08-08 22:21:45 +02:00
|
|
|
|
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
2021-11-12 18:12:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Thread struct {
|
|
|
|
Uid uint32
|
|
|
|
Parent *Thread
|
|
|
|
PrevSibling *Thread
|
|
|
|
NextSibling *Thread
|
|
|
|
FirstChild *Thread
|
|
|
|
|
|
|
|
Hidden bool // if this flag is set the message isn't rendered in the UI
|
|
|
|
Deleted bool // if this flag is set the message was deleted
|
|
|
|
}
|
|
|
|
|
2022-02-21 00:18:41 +01:00
|
|
|
func (t *Thread) AddChild(child *Thread) {
|
2022-02-24 00:41:13 +01:00
|
|
|
t.insertCmp(child, func(child, iter *Thread) bool { return true })
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Thread) OrderedInsert(child *Thread) {
|
|
|
|
t.insertCmp(child, func(child, iter *Thread) bool { return child.Uid > iter.Uid })
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Thread) insertCmp(child *Thread, cmp func(*Thread, *Thread) bool) {
|
2022-02-21 00:18:41 +01:00
|
|
|
if t.FirstChild == nil {
|
|
|
|
t.FirstChild = child
|
|
|
|
} else {
|
2022-02-24 00:41:13 +01:00
|
|
|
start := &Thread{Uid: t.FirstChild.Uid, NextSibling: t.FirstChild}
|
2022-02-21 00:18:41 +01:00
|
|
|
var iter *Thread
|
2022-02-24 00:41:13 +01:00
|
|
|
for iter = start; iter.NextSibling != nil && cmp(child, iter); iter = iter.NextSibling {
|
2022-02-21 00:18:41 +01:00
|
|
|
}
|
2022-02-24 00:41:13 +01:00
|
|
|
child.NextSibling = iter.NextSibling
|
2022-02-21 00:18:41 +01:00
|
|
|
iter.NextSibling = child
|
2022-02-24 00:41:13 +01:00
|
|
|
t.FirstChild = start.NextSibling
|
2022-02-21 00:18:41 +01:00
|
|
|
}
|
|
|
|
child.Parent = t
|
|
|
|
}
|
|
|
|
|
2021-11-12 18:12:02 +01:00
|
|
|
func (t *Thread) Walk(walkFn NewThreadWalkFn) error {
|
|
|
|
err := newWalk(t, walkFn, 0, nil)
|
2022-07-31 15:15:27 +02:00
|
|
|
if errors.Is(err, ErrSkipThread) {
|
2021-11-12 18:12:02 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-08-08 22:21:45 +02:00
|
|
|
// Root returns the root thread of the thread tree
|
|
|
|
func (t *Thread) Root() *Thread {
|
|
|
|
if t == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var iter *Thread
|
|
|
|
for iter = t; iter.Parent != nil; iter = iter.Parent {
|
|
|
|
}
|
|
|
|
return iter
|
|
|
|
}
|
|
|
|
|
|
|
|
// Uids returns all associated uids for the given thread and its children
|
|
|
|
func (t *Thread) Uids() []uint32 {
|
|
|
|
if t == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
uids := make([]uint32, 0)
|
|
|
|
err := t.Walk(func(node *Thread, _ int, _ error) error {
|
|
|
|
uids = append(uids, node.Uid)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2022-10-02 11:36:05 +02:00
|
|
|
logging.Errorf("walk to collect uids failed: %v", err)
|
2022-08-08 22:21:45 +02:00
|
|
|
}
|
|
|
|
return uids
|
|
|
|
}
|
|
|
|
|
2021-11-12 18:12:02 +01:00
|
|
|
func (t *Thread) String() string {
|
|
|
|
if t == nil {
|
|
|
|
return "<nil>"
|
|
|
|
}
|
|
|
|
parent := -1
|
|
|
|
if t.Parent != nil {
|
|
|
|
parent = int(t.Parent.Uid)
|
|
|
|
}
|
|
|
|
next := -1
|
|
|
|
if t.NextSibling != nil {
|
|
|
|
next = int(t.NextSibling.Uid)
|
|
|
|
}
|
|
|
|
child := -1
|
|
|
|
if t.FirstChild != nil {
|
|
|
|
child = int(t.FirstChild.Uid)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"[%d] (parent:%v, next:%v, child:%v)",
|
|
|
|
t.Uid, parent, next, child,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newWalk(node *Thread, walkFn NewThreadWalkFn, lvl int, ce error) error {
|
|
|
|
if node == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
err := walkFn(node, lvl, ce)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
|
|
|
err = newWalk(child, walkFn, lvl+1, err)
|
2022-07-31 15:15:27 +02:00
|
|
|
if errors.Is(err, ErrSkipThread) {
|
2021-11-12 18:12:02 +01:00
|
|
|
err = nil
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var ErrSkipThread = errors.New("skip this Thread")
|
|
|
|
|
|
|
|
type NewThreadWalkFn func(t *Thread, level int, currentErr error) error
|
|
|
|
|
2022-07-31 22:16:40 +02:00
|
|
|
// Implement interface to be able to sort threads by newest (max UID)
|
2021-11-12 18:12:02 +01:00
|
|
|
type ByUID []*Thread
|
|
|
|
|
|
|
|
func getMaxUID(thread *Thread) uint32 {
|
|
|
|
// TODO: should we make this part of the Thread type to avoid recomputation?
|
|
|
|
var Uid uint32
|
|
|
|
|
2022-07-29 22:31:54 +02:00
|
|
|
_ = thread.Walk(func(t *Thread, _ int, currentErr error) error {
|
2021-11-12 18:12:02 +01:00
|
|
|
if t.Uid > Uid {
|
|
|
|
Uid = t.Uid
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return Uid
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s ByUID) Len() int {
|
|
|
|
return len(s)
|
|
|
|
}
|
2022-07-31 22:16:40 +02:00
|
|
|
|
2021-11-12 18:12:02 +01:00
|
|
|
func (s ByUID) Swap(i, j int) {
|
|
|
|
s[i], s[j] = s[j], s[i]
|
|
|
|
}
|
2022-07-31 22:16:40 +02:00
|
|
|
|
2021-11-12 18:12:02 +01:00
|
|
|
func (s ByUID) Less(i, j int) bool {
|
|
|
|
maxUID_i := getMaxUID(s[i])
|
|
|
|
maxUID_j := getMaxUID(s[j])
|
|
|
|
return maxUID_i < maxUID_j
|
|
|
|
}
|
2022-03-08 18:13:39 +01:00
|
|
|
|
|
|
|
func SortThreadsBy(toSort []*Thread, sortBy []uint32) {
|
|
|
|
// build a map from sortBy
|
|
|
|
uidMap := make(map[uint32]int)
|
|
|
|
for i, uid := range sortBy {
|
|
|
|
uidMap[uid] = i
|
|
|
|
}
|
|
|
|
// sortslice of toSort with less function of indexing the map sortBy
|
|
|
|
sort.Slice(toSort, func(i, j int) bool {
|
|
|
|
return uidMap[getMaxUID(toSort[i])] < uidMap[getMaxUID(toSort[j])]
|
|
|
|
})
|
|
|
|
}
|