Add sorting functionality
There is a command and config option. The criteria are a list of the sort criterion and each can be individually reversed. This only includes support for sorting in the maildir backend currently. The other backends are not supported in this patch.
This commit is contained in:
parent
43435ba06c
commit
90d26da58a
9 changed files with 491 additions and 8 deletions
85
commands/account/sort.go
Normal file
85
commands/account/sort.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/lib/sort"
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sort struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(Sort{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Sort) Aliases() []string {
|
||||||
|
return []string{"sort"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Sort) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
|
supportedCriteria := []string{
|
||||||
|
"arrival",
|
||||||
|
"cc",
|
||||||
|
"date",
|
||||||
|
"from",
|
||||||
|
"read",
|
||||||
|
"size",
|
||||||
|
"subject",
|
||||||
|
"to",
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
return supportedCriteria
|
||||||
|
}
|
||||||
|
last := args[len(args)-1]
|
||||||
|
var completions []string
|
||||||
|
currentPrefix := strings.Join(args, " ") + " "
|
||||||
|
// if there is a completed criteria then suggest all again or an option
|
||||||
|
for _, criteria := range append(supportedCriteria, "-r") {
|
||||||
|
if criteria == last {
|
||||||
|
for _, criteria := range supportedCriteria {
|
||||||
|
completions = append(completions, currentPrefix+criteria)
|
||||||
|
}
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPrefix = strings.Join(args[:len(args)-1], " ")
|
||||||
|
if len(args) > 1 {
|
||||||
|
currentPrefix += " "
|
||||||
|
}
|
||||||
|
// last was beginning an option
|
||||||
|
if last == "-" {
|
||||||
|
return []string{currentPrefix + "-r"}
|
||||||
|
}
|
||||||
|
// the last item is not complete
|
||||||
|
for _, criteria := range supportedCriteria {
|
||||||
|
if strings.HasPrefix(criteria, last) {
|
||||||
|
completions = append(completions, currentPrefix+criteria)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Sort) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
acct := aerc.SelectedAccount()
|
||||||
|
if acct == nil {
|
||||||
|
return errors.New("No account selected.")
|
||||||
|
}
|
||||||
|
store := acct.Store()
|
||||||
|
if store == nil {
|
||||||
|
return errors.New("Messages still loading.")
|
||||||
|
}
|
||||||
|
|
||||||
|
sortCriteria, err := sort.GetSortCriteria(args[1:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
aerc.SetStatus("Sorting")
|
||||||
|
store.Sort(sortCriteria, func() {
|
||||||
|
aerc.SetStatus("Sorting complete")
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ type UIConfig struct {
|
||||||
Spinner string `ini:"spinner"`
|
Spinner string `ini:"spinner"`
|
||||||
SpinnerDelimiter string `ini:"spinner-delimiter"`
|
SpinnerDelimiter string `ini:"spinner-delimiter"`
|
||||||
DirListFormat string `ini:"dirlist-format"`
|
DirListFormat string `ini:"dirlist-format"`
|
||||||
|
Sort []string `delim:" "`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -25,6 +25,8 @@ type MessageStore struct {
|
||||||
resultIndex int
|
resultIndex int
|
||||||
filter bool
|
filter bool
|
||||||
|
|
||||||
|
defaultSortCriteria []*types.SortCriterion
|
||||||
|
|
||||||
// Map of uids we've asked the worker to fetch
|
// Map of uids we've asked the worker to fetch
|
||||||
onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers
|
onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers
|
||||||
onUpdateDirs func()
|
onUpdateDirs func()
|
||||||
|
@ -38,6 +40,7 @@ type MessageStore struct {
|
||||||
|
|
||||||
func NewMessageStore(worker *types.Worker,
|
func NewMessageStore(worker *types.Worker,
|
||||||
dirInfo *models.DirectoryInfo,
|
dirInfo *models.DirectoryInfo,
|
||||||
|
defaultSortCriteria []*types.SortCriterion,
|
||||||
triggerNewEmail func(*models.MessageInfo),
|
triggerNewEmail func(*models.MessageInfo),
|
||||||
triggerDirectoryChange func()) *MessageStore {
|
triggerDirectoryChange func()) *MessageStore {
|
||||||
|
|
||||||
|
@ -49,6 +52,8 @@ func NewMessageStore(worker *types.Worker,
|
||||||
bodyCallbacks: make(map[uint32][]func(io.Reader)),
|
bodyCallbacks: make(map[uint32][]func(io.Reader)),
|
||||||
headerCallbacks: make(map[uint32][]func(*types.MessageInfo)),
|
headerCallbacks: make(map[uint32][]func(*types.MessageInfo)),
|
||||||
|
|
||||||
|
defaultSortCriteria: defaultSortCriteria,
|
||||||
|
|
||||||
pendingBodies: make(map[uint32]interface{}),
|
pendingBodies: make(map[uint32]interface{}),
|
||||||
pendingHeaders: make(map[uint32]interface{}),
|
pendingHeaders: make(map[uint32]interface{}),
|
||||||
worker: worker,
|
worker: worker,
|
||||||
|
@ -151,7 +156,9 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *types.DirectoryInfo:
|
case *types.DirectoryInfo:
|
||||||
store.DirInfo = *msg.Info
|
store.DirInfo = *msg.Info
|
||||||
store.worker.PostAction(&types.FetchDirectoryContents{}, nil)
|
store.worker.PostAction(&types.FetchDirectoryContents{
|
||||||
|
SortCriteria: store.defaultSortCriteria,
|
||||||
|
}, nil)
|
||||||
update = true
|
update = true
|
||||||
case *types.DirectoryContents:
|
case *types.DirectoryContents:
|
||||||
newMap := make(map[uint32]*models.MessageInfo)
|
newMap := make(map[uint32]*models.MessageInfo)
|
||||||
|
@ -434,3 +441,11 @@ func (store *MessageStore) ModifyLabels(uids []uint32, add, remove []string,
|
||||||
Remove: remove,
|
Remove: remove,
|
||||||
}, cb)
|
}, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func()) {
|
||||||
|
store.worker.PostAction(&types.FetchDirectoryContents{
|
||||||
|
SortCriteria: criteria,
|
||||||
|
}, func(msg types.WorkerMessage) {
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
56
lib/sort/sort.go
Normal file
56
lib/sort/sort.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package sort
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/worker/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSortCriteria(args []string) ([]*types.SortCriterion, error) {
|
||||||
|
var sortCriteria []*types.SortCriterion
|
||||||
|
reverse := false
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg == "-r" {
|
||||||
|
reverse = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field, err := parseSortField(arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sortCriteria = append(sortCriteria, &types.SortCriterion{
|
||||||
|
Field: field,
|
||||||
|
Reverse: reverse,
|
||||||
|
})
|
||||||
|
reverse = false
|
||||||
|
}
|
||||||
|
if reverse {
|
||||||
|
return nil, errors.New("Expected argument to reverse")
|
||||||
|
}
|
||||||
|
return sortCriteria, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSortField(arg string) (types.SortField, error) {
|
||||||
|
switch strings.ToLower(arg) {
|
||||||
|
case "arrival":
|
||||||
|
return types.SortArrival, nil
|
||||||
|
case "cc":
|
||||||
|
return types.SortCc, nil
|
||||||
|
case "date":
|
||||||
|
return types.SortDate, nil
|
||||||
|
case "from":
|
||||||
|
return types.SortFrom, nil
|
||||||
|
case "read":
|
||||||
|
return types.SortRead, nil
|
||||||
|
case "size":
|
||||||
|
return types.SortSize, nil
|
||||||
|
case "subject":
|
||||||
|
return types.SortSubject, nil
|
||||||
|
case "to":
|
||||||
|
return types.SortTo, nil
|
||||||
|
default:
|
||||||
|
return types.SortArrival, fmt.Errorf("%v is not a valid sort criterion", arg)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"git.sr.ht/~sircmpwn/aerc/config"
|
"git.sr.ht/~sircmpwn/aerc/config"
|
||||||
"git.sr.ht/~sircmpwn/aerc/lib"
|
"git.sr.ht/~sircmpwn/aerc/lib"
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/lib/sort"
|
||||||
"git.sr.ht/~sircmpwn/aerc/lib/ui"
|
"git.sr.ht/~sircmpwn/aerc/lib/ui"
|
||||||
"git.sr.ht/~sircmpwn/aerc/models"
|
"git.sr.ht/~sircmpwn/aerc/models"
|
||||||
"git.sr.ht/~sircmpwn/aerc/worker"
|
"git.sr.ht/~sircmpwn/aerc/worker"
|
||||||
|
@ -218,6 +219,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
||||||
store.Update(msg)
|
store.Update(msg)
|
||||||
} else {
|
} else {
|
||||||
store = lib.NewMessageStore(acct.worker, msg.Info,
|
store = lib.NewMessageStore(acct.worker, msg.Info,
|
||||||
|
acct.getSortCriteria(),
|
||||||
func(msg *models.MessageInfo) {
|
func(msg *models.MessageInfo) {
|
||||||
acct.conf.Triggers.ExecNewEmail(acct.acct,
|
acct.conf.Triggers.ExecNewEmail(acct.acct,
|
||||||
acct.conf, msg)
|
acct.conf, msg)
|
||||||
|
@ -254,3 +256,15 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
||||||
Color(tcell.ColorDefault, tcell.ColorRed)
|
Color(tcell.ColorDefault, tcell.ColorRed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (acct *AccountView) getSortCriteria() []*types.SortCriterion {
|
||||||
|
if len(acct.conf.Ui.Sort) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
criteria, err := sort.GetSortCriteria(acct.conf.Ui.Sort)
|
||||||
|
if err != nil {
|
||||||
|
acct.aerc.PushError(" ui.sort: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return criteria
|
||||||
|
}
|
||||||
|
|
253
worker/lib/sort.go
Normal file
253
worker/lib/sort.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"git.sr.ht/~sircmpwn/aerc/models"
|
"git.sr.ht/~sircmpwn/aerc/models"
|
||||||
"git.sr.ht/~sircmpwn/aerc/worker/handlers"
|
"git.sr.ht/~sircmpwn/aerc/worker/handlers"
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/worker/lib"
|
||||||
"git.sr.ht/~sircmpwn/aerc/worker/types"
|
"git.sr.ht/~sircmpwn/aerc/worker/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ type Worker struct {
|
||||||
selectedName string
|
selectedName string
|
||||||
worker *types.Worker
|
worker *types.Worker
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
|
currentSortCriteria []*types.SortCriterion
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWorker creates a new maildir worker with the provided worker.
|
// NewWorker creates a new maildir worker with the provided worker.
|
||||||
|
@ -86,8 +88,13 @@ func (w *Worker) handleFSEvent(ev fsnotify.Event) {
|
||||||
w.worker.Logger.Printf("could not scan UIDs: %v", err)
|
w.worker.Logger.Printf("could not scan UIDs: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sortedUids, err := w.sort(uids, w.currentSortCriteria)
|
||||||
|
if err != nil {
|
||||||
|
w.worker.Logger.Printf("error sorting directory: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.worker.PostMessage(&types.DirectoryContents{
|
w.worker.PostMessage(&types.DirectoryContents{
|
||||||
Uids: uids,
|
Uids: sortedUids,
|
||||||
}, nil)
|
}, nil)
|
||||||
dirInfo := w.getDirectoryInfo()
|
dirInfo := w.getDirectoryInfo()
|
||||||
dirInfo.Recent = len(newUnseen)
|
dirInfo.Recent = len(newUnseen)
|
||||||
|
@ -271,13 +278,45 @@ func (w *Worker) handleFetchDirectoryContents(
|
||||||
w.worker.Logger.Printf("error scanning uids: %v", err)
|
w.worker.Logger.Printf("error scanning uids: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
sortedUids, err := w.sort(uids, msg.SortCriteria)
|
||||||
|
if err != nil {
|
||||||
|
w.worker.Logger.Printf("error sorting directory: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.currentSortCriteria = msg.SortCriteria
|
||||||
w.worker.PostMessage(&types.DirectoryContents{
|
w.worker.PostMessage(&types.DirectoryContents{
|
||||||
Message: types.RespondTo(msg),
|
Message: types.RespondTo(msg),
|
||||||
Uids: uids,
|
Uids: sortedUids,
|
||||||
}, nil)
|
}, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Worker) sort(uids []uint32, criteria []*types.SortCriterion) ([]uint32, error) {
|
||||||
|
if len(criteria) == 0 {
|
||||||
|
return uids, nil
|
||||||
|
}
|
||||||
|
var msgInfos []*models.MessageInfo
|
||||||
|
for _, uid := range uids {
|
||||||
|
m, err := w.c.Message(*w.selected, uid)
|
||||||
|
if err != nil {
|
||||||
|
w.worker.Logger.Printf("could not get message: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := m.MessageInfo()
|
||||||
|
if err != nil {
|
||||||
|
w.worker.Logger.Printf("could not get message info: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msgInfos = append(msgInfos, info)
|
||||||
|
}
|
||||||
|
sortedUids, err := lib.Sort(msgInfos, criteria)
|
||||||
|
if err != nil {
|
||||||
|
w.worker.Logger.Printf("could not sort the messages: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sortedUids, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *Worker) handleCreateDirectory(msg *types.CreateDirectory) error {
|
func (w *Worker) handleCreateDirectory(msg *types.CreateDirectory) error {
|
||||||
dir := w.c.Dir(msg.Directory)
|
dir := w.c.Dir(msg.Directory)
|
||||||
if err := dir.Create(); err != nil {
|
if err := dir.Create(); err != nil {
|
||||||
|
|
|
@ -78,6 +78,7 @@ type OpenDirectory struct {
|
||||||
|
|
||||||
type FetchDirectoryContents struct {
|
type FetchDirectoryContents struct {
|
||||||
Message
|
Message
|
||||||
|
SortCriteria []*SortCriterion
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchDirectory struct {
|
type SearchDirectory struct {
|
||||||
|
|
19
worker/types/sort.go
Normal file
19
worker/types/sort.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type SortField int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SortArrival SortField = iota
|
||||||
|
SortCc
|
||||||
|
SortDate
|
||||||
|
SortFrom
|
||||||
|
SortRead
|
||||||
|
SortSize
|
||||||
|
SortSubject
|
||||||
|
SortTo
|
||||||
|
)
|
||||||
|
|
||||||
|
type SortCriterion struct {
|
||||||
|
Field SortField
|
||||||
|
Reverse bool
|
||||||
|
}
|
Loading…
Reference in a new issue