aboutsummaryrefslogtreecommitdiffstats
path: root/worker/lib
diff options
context:
space:
mode:
Diffstat (limited to 'worker/lib')
-rw-r--r--worker/lib/sort.go253
1 files changed, 253 insertions, 0 deletions
diff --git a/worker/lib/sort.go b/worker/lib/sort.go
new file mode 100644
index 00000000..36c09242
--- /dev/null
+++ b/worker/lib/sort.go
@@ -0,0 +1,253 @@
+package lib
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+ "time"
+
+ "git.sr.ht/~sircmpwn/aerc/models"
+ "git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+func Sort(messageInfos []*models.MessageInfo,
+ criteria []*types.SortCriterion) ([]uint32, error) {
+ // loop through in reverse to ensure we sort by non-primary fields first
+ for i := len(criteria) - 1; i >= 0; i-- {
+ criterion := criteria[i]
+ var err error
+ switch criterion.Field {
+ case types.SortArrival:
+ err = sortDate(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) time.Time {
+ return msgInfo.InternalDate
+ })
+ case types.SortCc:
+ err = sortAddresses(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) []*models.Address {
+ return msgInfo.Envelope.Cc
+ })
+ case types.SortDate:
+ err = sortDate(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) time.Time {
+ return msgInfo.Envelope.Date
+ })
+ case types.SortFrom:
+ err = sortAddresses(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) []*models.Address {
+ return msgInfo.Envelope.From
+ })
+ case types.SortRead:
+ err = sortFlags(messageInfos, criterion, models.SeenFlag)
+ case types.SortSize:
+ err = sortInts(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) uint32 {
+ return msgInfo.Size
+ })
+ case types.SortSubject:
+ err = sortStrings(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) string {
+ subject := strings.ToLower(msgInfo.Envelope.Subject)
+ subject = strings.TrimPrefix(subject, "re: ")
+ return strings.TrimPrefix(subject, "fwd: ")
+ })
+ case types.SortTo:
+ err = sortAddresses(messageInfos, criterion,
+ func(msgInfo *models.MessageInfo) []*models.Address {
+ return msgInfo.Envelope.To
+ })
+ }
+ if err != nil {
+ return nil, err
+ }
+ }
+ var uids []uint32
+ // copy in reverse as msgList displays backwards
+ for i := len(messageInfos) - 1; i >= 0; i-- {
+ uids = append(uids, messageInfos[i].Uid)
+ }
+ return uids, nil
+}
+
+func sortDate(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
+ getValue func(*models.MessageInfo) time.Time) error {
+ var slice []*dateStore
+ for _, msgInfo := range messageInfos {
+ slice = append(slice, &dateStore{
+ Value: getValue(msgInfo),
+ MsgInfo: msgInfo,
+ })
+ }
+ sortSlice(criterion, dateSlice{slice})
+ for i := 0; i < len(messageInfos); i++ {
+ messageInfos[i] = slice[i].MsgInfo
+ }
+ return nil
+}
+
+func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
+ getValue func(*models.MessageInfo) []*models.Address) error {
+ var slice []*addressStore
+ for _, msgInfo := range messageInfos {
+ slice = append(slice, &addressStore{
+ Value: getValue(msgInfo),
+ MsgInfo: msgInfo,
+ })
+ }
+ sortSlice(criterion, addressSlice{slice})
+ for i := 0; i < len(messageInfos); i++ {
+ messageInfos[i] = slice[i].MsgInfo
+ }
+ return nil
+}
+
+func sortFlags(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
+ testFlag models.Flag) error {
+ var slice []*boolStore
+ for _, msgInfo := range messageInfos {
+ flagPresent := false
+ for _, flag := range msgInfo.Flags {
+ if flag == testFlag {
+ flagPresent = true
+ }
+ }
+ slice = append(slice, &boolStore{
+ Value: flagPresent,
+ MsgInfo: msgInfo,
+ })
+ }
+ sortSlice(criterion, boolSlice{slice})
+ for i := 0; i < len(messageInfos); i++ {
+ messageInfos[i] = slice[i].MsgInfo
+ }
+ return nil
+}
+
+func sortInts(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
+ getValue func(*models.MessageInfo) uint32) error {
+ var slice []*intStore
+ for _, msgInfo := range messageInfos {
+ slice = append(slice, &intStore{
+ Value: getValue(msgInfo),
+ MsgInfo: msgInfo,
+ })
+ }
+ sortSlice(criterion, intSlice{slice})
+ for i := 0; i < len(messageInfos); i++ {
+ messageInfos[i] = slice[i].MsgInfo
+ }
+ return nil
+}
+
+func sortStrings(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
+ getValue func(*models.MessageInfo) string) error {
+ var slice []*lexiStore
+ for _, msgInfo := range messageInfos {
+ slice = append(slice, &lexiStore{
+ Value: getValue(msgInfo),
+ MsgInfo: msgInfo,
+ })
+ }
+ sortSlice(criterion, lexiSlice{slice})
+ for i := 0; i < len(messageInfos); i++ {
+ messageInfos[i] = slice[i].MsgInfo
+ }
+ return nil
+}
+
+type lexiStore struct {
+ Value string
+ MsgInfo *models.MessageInfo
+}
+
+type lexiSlice struct{ Slice []*lexiStore }
+
+func (s lexiSlice) Len() int { return len(s.Slice) }
+func (s lexiSlice) Swap(i, j int) { s.Slice[i], s.Slice[j] = s.Slice[j], s.Slice[i] }
+func (s lexiSlice) Less(i, j int) bool {
+ return s.Slice[i].Value < s.Slice[j].Value
+}
+
+type dateStore struct {
+ Value time.Time
+ MsgInfo *models.MessageInfo
+}
+
+type dateSlice struct{ Slice []*dateStore }
+
+func (s dateSlice) Len() int { return len(s.Slice) }
+func (s dateSlice) Swap(i, j int) { s.Slice[i], s.Slice[j] = s.Slice[j], s.Slice[i] }
+func (s dateSlice) Less(i, j int) bool {
+ return s.Slice[i].Value.Before(s.Slice[j].Value)
+}
+
+type intStore struct {
+ Value uint32
+ MsgInfo *models.MessageInfo
+}
+
+type intSlice struct{ Slice []*intStore }
+
+func (s intSlice) Len() int { return len(s.Slice) }
+func (s intSlice) Swap(i, j int) { s.Slice[i], s.Slice[j] = s.Slice[j], s.Slice[i] }
+func (s intSlice) Less(i, j int) bool {
+ return s.Slice[i].Value < s.Slice[j].Value
+}
+
+type addressStore struct {
+ Value []*models.Address
+ MsgInfo *models.MessageInfo
+}
+
+type addressSlice struct{ Slice []*addressStore }
+
+func (s addressSlice) Len() int { return len(s.Slice) }
+func (s addressSlice) Swap(i, j int) { s.Slice[i], s.Slice[j] = s.Slice[j], s.Slice[i] }
+func (s addressSlice) Less(i, j int) bool {
+ addressI, addressJ := s.Slice[i].Value, s.Slice[j].Value
+ var firstI, firstJ *models.Address
+ if len(addressI) > 0 {
+ firstI = addressI[0]
+ }
+ if len(addressJ) > 0 {
+ firstJ = addressJ[0]
+ }
+ if firstI == nil && firstJ == nil {
+ return false
+ } else if firstI == nil && firstJ != nil {
+ return false
+ } else if firstI != nil && firstJ == nil {
+ return true
+ } else /* firstI != nil && firstJ != nil */ {
+ getName := func(addr *models.Address) string {
+ if addr.Name != "" {
+ return addr.Name
+ } else {
+ return fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
+ }
+ }
+ return getName(firstI) < getName(firstJ)
+ }
+}
+
+type boolStore struct {
+ Value bool
+ MsgInfo *models.MessageInfo
+}
+
+type boolSlice struct{ Slice []*boolStore }
+
+func (s boolSlice) Len() int { return len(s.Slice) }
+func (s boolSlice) Swap(i, j int) { s.Slice[i], s.Slice[j] = s.Slice[j], s.Slice[i] }
+func (s boolSlice) Less(i, j int) bool {
+ valI, valJ := s.Slice[i].Value, s.Slice[j].Value
+ return valI && !valJ
+}
+
+func sortSlice(criterion *types.SortCriterion, interfce sort.Interface) {
+ if criterion.Reverse {
+ sort.Stable(sort.Reverse(interfce))
+ } else {
+ sort.Stable(interfce)
+ }
+}