diff options
author | Robin Jarry <robin@jarry.cc> | 2024-08-14 16:59:11 +0200 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2024-08-28 12:06:01 +0200 |
commit | 73dc39c6ee0827fc68b93af8dc438b0e1c14e929 (patch) | |
tree | aff067600ea6326ff179447ed968b6712013b889 | |
parent | 2950d919a5c5a55bd0eb53d6c41f989d8b70bd55 (diff) | |
download | aerc-73dc39c6ee0827fc68b93af8dc438b0e1c14e929.tar.gz |
treewide: replace uint32 uids with opaque strings
Add a new models.UID type (an alias to string). Replace all occurrences
of uint32 being used as message UID or thread UID with models.UID.
Update all workers to only expose models.UID values and deal with the
conversion internally. Only IMAP needs to convert these to uint32. All
other backends already use plain strings as message identifiers, in
which case no conversion is even needed.
The directory tree implementation needed to be heavily refactored in
order to accommodate thread UID not being usable as a list index.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Inwit <inwit@sindominio.net>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
64 files changed, 541 insertions, 649 deletions
diff --git a/app/account.go b/app/account.go index 5577e461..6d690142 100644 --- a/app/account.go +++ b/app/account.go @@ -240,7 +240,7 @@ func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) { return msg, nil } -func (acct *AccountView) MarkedMessages() ([]uint32, error) { +func (acct *AccountView) MarkedMessages() ([]models.UID, error) { if store := acct.Store(); store != nil { return store.Marker().Marked(), nil } @@ -495,7 +495,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) { acct.setTitle() } -func (acct *AccountView) updateDirCounts(destination string, uids []uint32) { +func (acct *AccountView) updateDirCounts(destination string, uids []models.UID) { // Only update the destination destDir if it is initialized if destDir := acct.dirlist.Directory(destination); destDir != nil { var recent, unseen int diff --git a/app/dirtree.go b/app/dirtree.go index a7d0a5e7..dc80fa7f 100644 --- a/app/dirtree.go +++ b/app/dirtree.go @@ -3,7 +3,6 @@ package app import ( "fmt" "sort" - "strconv" "strings" "time" @@ -23,8 +22,6 @@ type DirectoryTree struct { listIdx int list []*types.Thread - treeDirs []string - virtual bool virtualCb func() } @@ -33,7 +30,6 @@ func NewDirectoryTree(dirlist *DirectoryList) DirectoryLister { dt := &DirectoryTree{ DirectoryList: dirlist, listIdx: -1, - list: make([]*types.Thread, 0), virtualCb: func() {}, } return dt @@ -48,13 +44,12 @@ func (dt *DirectoryTree) Selected() string { return dt.DirectoryList.Selected() } node := dt.list[dt.listIdx] - sep := dt.DirectoryList.worker.PathSeparator() - elems := strings.Split(dt.treeDirs[getAnyUid(node)], sep) + elems := dt.nodeElems(node) n := countLevels(node) if n < 0 || n >= len(elems) { return "" } - return strings.Join(elems[:(n+1)], sep) + return strings.Join(elems[:(n+1)], dt.DirectoryList.worker.PathSeparator()) } func (dt *DirectoryTree) SelectedDirectory() *models.Directory { @@ -211,27 +206,24 @@ func (dt *DirectoryTree) SelectedMsgStore() (*lib.MessageStore, bool) { if dt.virtual { return nil, false } - if findString(dt.treeDirs, dt.selected) < 0 { + + selected := models.UID(dt.selected) + if _, node := dt.getTreeNode(selected); node == nil { dt.buildTree() - if idx := findString(dt.treeDirs, dt.selected); idx >= 0 { - selIdx, node := dt.getTreeNode(uint32(idx)) - if node != nil { - makeVisible(node) - dt.listIdx = selIdx - } + selIdx, node := dt.getTreeNode(selected) + if node != nil { + makeVisible(node) + dt.listIdx = selIdx } } return dt.DirectoryList.SelectedMsgStore() } func (dt *DirectoryTree) reindex(name string) { - idx := findString(dt.treeDirs, name) - if idx >= 0 { - selIdx, node := dt.getTreeNode(uint32(idx)) - if node != nil { - makeVisible(node) - dt.listIdx = selIdx - } + selIdx, node := dt.getTreeNode(models.UID(name)) + if node != nil { + makeVisible(node) + dt.listIdx = selIdx } } @@ -247,7 +239,8 @@ func (dt *DirectoryTree) Open(name string, query string, delay time.Duration, cb return } again := false - if findString(dt.dirs, name) < 0 { + uid := models.UID(name) + if _, node := dt.getTreeNode(uid); node == nil { again = true } else { dt.reindex(name) @@ -300,13 +293,14 @@ func (dt *DirectoryTree) NextPrev(delta int) { func (dt *DirectoryTree) selectIndex(i int) { dt.listIdx = i - if path := dt.getDirectory(dt.list[dt.listIdx]); path != "" { - dt.virtual = false - dt.Select(path) - } else { + node := dt.list[dt.listIdx] + if node.Dummy { dt.virtual = true dt.NewContext() dt.virtualCb() + } else { + dt.virtual = false + dt.Select(dt.getDirectory(node)) } } @@ -345,37 +339,43 @@ func (dt *DirectoryTree) countVisible(list []*types.Thread) (n int) { return } +func (dt *DirectoryTree) nodeElems(node *types.Thread) []string { + dir := string(node.Uid) + sep := dt.DirectoryList.worker.PathSeparator() + return strings.Split(dir, sep) +} + +func (dt *DirectoryTree) nodeName(node *types.Thread) string { + if elems := dt.nodeElems(node); len(elems) > 0 { + return elems[len(elems)-1] + } + return "" +} + func (dt *DirectoryTree) displayText(node *types.Thread) string { - elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.DirectoryList.worker.PathSeparator()) return fmt.Sprintf("%s%s%s", threadPrefix(node, false, false), - getFlag(node), elems[countLevels(node)]) + getFlag(node), dt.nodeName(node)) } func (dt *DirectoryTree) getDirectory(node *types.Thread) string { - if uid := node.Uid; int(uid) < len(dt.treeDirs) { - return dt.treeDirs[uid] - } - return "" + return string(node.Uid) } -func (dt *DirectoryTree) getTreeNode(uid uint32) (int, *types.Thread) { - var found *types.Thread - var idx int +func (dt *DirectoryTree) getTreeNode(uid models.UID) (int, *types.Thread) { for i, node := range dt.list { if node.Uid == uid { - found = node - idx = i + return i, node } } - return idx, found + return -1, nil } func (dt *DirectoryTree) hiddenDirectories() map[string]bool { hidden := make(map[string]bool, 0) for _, node := range dt.list { if node.Hidden != 0 && node.FirstChild != nil { - elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.DirectoryList.worker.PathSeparator()) + elems := dt.nodeElems(node) if levels := countLevels(node); levels < len(elems) { if node.FirstChild != nil && (levels+1) < len(elems) { levels += 1 @@ -390,8 +390,9 @@ func (dt *DirectoryTree) hiddenDirectories() map[string]bool { } func (dt *DirectoryTree) setHiddenDirectories(hiddenDirs map[string]bool) { + log.Tracef("setHiddenDirectories: %#v", hiddenDirs) for _, node := range dt.list { - elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.DirectoryList.worker.PathSeparator()) + elems := dt.nodeElems(node) if levels := countLevels(node); levels < len(elems) { if node.FirstChild != nil && (levels+1) < len(elems) { levels += 1 @@ -399,6 +400,7 @@ func (dt *DirectoryTree) setHiddenDirectories(hiddenDirs map[string]bool) { strDir := strings.Join(elems[:levels], dt.DirectoryList.worker.PathSeparator()) if hidden, ok := hiddenDirs[strDir]; hidden && ok { node.Hidden = 1 + log.Tracef("setHiddenDirectories: %q -> %#v", strDir, node) } } } @@ -407,29 +409,15 @@ func (dt *DirectoryTree) setHiddenDirectories(hiddenDirs map[string]bool) { func (dt *DirectoryTree) buildTree() { if len(dt.list) != 0 { hiddenDirs := dt.hiddenDirectories() - defer func() { - dt.setHiddenDirectories(hiddenDirs) - }() - } - - sTree := make([][]string, 0) - for i, dir := range dt.dirs { - elems := strings.Split(dir, dt.DirectoryList.worker.PathSeparator()) - if len(elems) == 0 { - continue - } - elems = append(elems, fmt.Sprintf("%d", i)) - sTree = append(sTree, elems) + defer dt.setHiddenDirectories(hiddenDirs) } - dt.treeDirs = make([]string, len(dt.dirs)) - copy(dt.treeDirs, dt.dirs) - - root := &types.Thread{Uid: 0} - dt.buildTreeNode(root, sTree, 0xFFFFFF, 1) - - threads := make([]*types.Thread, 0) + dirs := make([]string, len(dt.dirs)) + copy(dirs, dt.dirs) + root := &types.Thread{} + dt.buildTreeNode(root, dirs, 1) + var threads []*types.Thread for iter := root.FirstChild; iter != nil; iter = iter.NextSibling { iter.Parent = nil threads = append(threads, iter) @@ -437,16 +425,10 @@ func (dt *DirectoryTree) buildTree() { // folders-sort if dt.DirectoryList.acctConf.EnableFoldersSort { - toStr := func(t *types.Thread) string { - if elems := strings.Split(dt.treeDirs[getAnyUid(t)], dt.DirectoryList.worker.PathSeparator()); len(elems) > 0 { - return elems[0] - } - return "" - } sort.Slice(threads, func(i, j int) bool { foldersSort := dt.DirectoryList.acctConf.FoldersSort - iInFoldersSort := findString(foldersSort, toStr(threads[i])) - jInFoldersSort := findString(foldersSort, toStr(threads[j])) + iInFoldersSort := findString(foldersSort, dt.getDirectory(threads[i])) + jInFoldersSort := findString(foldersSort, dt.getDirectory(threads[j])) if iInFoldersSort >= 0 && jInFoldersSort >= 0 { return iInFoldersSort < jInFoldersSort } @@ -456,7 +438,7 @@ func (dt *DirectoryTree) buildTree() { if jInFoldersSort >= 0 { return false } - return toStr(threads[i]) < toStr(threads[j]) + return dt.getDirectory(threads[i]) < dt.getDirectory(threads[j]) }) } @@ -472,41 +454,51 @@ func (dt *DirectoryTree) buildTree() { } } -func (dt *DirectoryTree) buildTreeNode(node *types.Thread, stree [][]string, defaultUid uint32, depth int) { - m := make(map[string][][]string) - for _, branch := range stree { - if len(branch) > 1 { - next := append(m[branch[0]], branch[1:]) //nolint:gocritic // intentional append to different slice - m[branch[0]] = next - } - } - keys := make([]string, 0) - for key := range m { - keys = append(keys, key) - } - sort.Strings(keys) - path := dt.getDirectory(node) - for _, key := range keys { - next := m[key] - var uid uint32 = defaultUid - for _, testStr := range next { - if len(testStr) == 1 { - if uidI, err := strconv.Atoi(next[0][0]); err == nil { - uid = uint32(uidI) - } +func (dt *DirectoryTree) buildTreeNode(node *types.Thread, dirs []string, depth int) { + dirmap := make(map[string][]string) + for _, dir := range dirs { + base, dir, cut := strings.Cut( + dir, dt.DirectoryList.worker.PathSeparator()) + if _, found := dirmap[base]; found { + if cut { + dirmap[base] = append(dirmap[base], dir) } + } else if cut { + dirmap[base] = append(dirmap[base], dir) + } else { + dirmap[base] = []string{} } - nextNode := &types.Thread{Uid: uid} + } + bases := make([]string, 0, len(dirmap)) + for base, dirs := range dirmap { + bases = append(bases, base) + sort.Strings(dirs) + } + sort.Strings(bases) + + basePath := dt.getDirectory(node) + if depth > dt.UiConfig(basePath).DirListCollapse { + node.Hidden = 1 + } else { + node.Hidden = 0 + } + + for _, base := range bases { + path := dt.childPath(basePath, base) + nextNode := &types.Thread{Uid: models.UID(path)} + + nextNode.Dummy = findString(dt.dirs, path) == -1 + node.AddChild(nextNode) - if dt.UiConfig(path).DirListCollapse != 0 && dt.listIdx < 0 { - if depth > dt.UiConfig(path).DirListCollapse { - node.Hidden = 1 - } else { - node.Hidden = 0 - } - } - dt.buildTreeNode(nextNode, next, defaultUid, depth+1) + dt.buildTreeNode(nextNode, dirmap[base], depth+1) + } +} + +func (dt *DirectoryTree) childPath(base, relpath string) string { + if base == "" { + return relpath } + return base + dt.DirectoryList.worker.PathSeparator() + relpath } func makeVisible(node *types.Thread) { @@ -519,27 +511,12 @@ func makeVisible(node *types.Thread) { } func isVisible(node *types.Thread) bool { - isVisible := true for iter := node.Parent; iter != nil; iter = iter.Parent { if iter.Hidden != 0 { - isVisible = false - break + return false } } - return isVisible -} - -func getAnyUid(node *types.Thread) (uid uint32) { - err := node.Walk(func(t *types.Thread, l int, err error) error { - if t.FirstChild == nil { - uid = t.Uid - } - return nil - }) - if err != nil { - log.Warnf("failed to get uid: %v", err) - } - return + return true } func countLevels(node *types.Thread) (level int) { @@ -550,7 +527,7 @@ func countLevels(node *types.Thread) (level int) { } func getFlag(node *types.Thread) string { - if node == nil && node.FirstChild == nil { + if node == nil || node.FirstChild == nil { return "" } if node.Hidden != 0 { diff --git a/app/msglist.go b/app/msglist.go index 9e6f3289..4db54f4d 100644 --- a/app/msglist.go +++ b/app/msglist.go @@ -44,7 +44,7 @@ func (ml *MessageList) Invalidate() { } type messageRowParams struct { - uid uint32 + uid models.UID needsHeaders bool err error uiConfig *config.UIConfig @@ -61,7 +61,7 @@ func (ml *MessageList) AlignMessage(pos AlignPosition) { idx := 0 iter := store.UidsIterator() for i := 0; iter.Next(); i++ { - if store.SelectedUid() == iter.Value().(uint32) { + if store.SelectedUid() == iter.Value().(models.UID) { idx = i break } @@ -92,7 +92,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { ml.UpdateScroller(ml.height, len(store.Uids())) iter := store.UidsIterator() for i := 0; iter.Next(); i++ { - if store.SelectedUid() == iter.Value().(uint32) { + if store.SelectedUid() == iter.Value().(models.UID) { ml.EnsureScroll(i) break } @@ -108,7 +108,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { return } - var needsHeaders []uint32 + var needsHeaders []models.UID data := state.NewDataSetter() data.SetAccount(acct.acct) @@ -166,7 +166,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { if i < ml.Scroll() { continue } - uid := iter.Value().(uint32) + uid := iter.Value().(models.UID) if showThreads { threadView.Update(data, uid) } @@ -201,7 +201,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { } func addMessage( - store *lib.MessageStore, uid uint32, + store *lib.MessageStore, uid models.UID, table *ui.Table, data state.DataSetter, uiConfig *config.UIConfig, ) bool { @@ -406,14 +406,14 @@ func (ml *MessageList) Select(index int) { iter := store.UidsIterator() - var uid uint32 + var uid models.UID if index < 0 { uid = uids[iter.EndIndex()] } else { uid = uids[iter.StartIndex()] for i := 0; iter.Next(); i++ { if i >= index { - uid = iter.Value().(uint32) + uid = iter.Value().(models.UID) break } } @@ -579,7 +579,7 @@ func newThreadView(store *lib.MessageStore) *threadView { } } -func (t *threadView) Update(data state.DataSetter, uid uint32) { +func (t *threadView) Update(data state.DataSetter, uid models.UID) { thread, err := t.store.Thread(uid) info := state.ThreadInfo{} if thread != nil && err == nil { diff --git a/app/msgviewer.go b/app/msgviewer.go index 8760bf74..d4dbd73e 100644 --- a/app/msgviewer.go +++ b/app/msgviewer.go @@ -333,7 +333,7 @@ func (mv *MessageViewer) SelectedMessage() (*models.MessageInfo, error) { return mv.msg.MessageInfo(), nil } -func (mv *MessageViewer) MarkedMessages() ([]uint32, error) { +func (mv *MessageViewer) MarkedMessages() ([]models.UID, error) { return mv.acct.MarkedMessages() } diff --git a/app/providesmessage.go b/app/providesmessage.go index c89c811f..4572f65b 100644 --- a/app/providesmessage.go +++ b/app/providesmessage.go @@ -26,5 +26,5 @@ type ProvidesMessages interface { Store() *lib.MessageStore SelectedAccount() *AccountView SelectedMessage() (*models.MessageInfo, error) - MarkedMessages() ([]uint32, error) + MarkedMessages() ([]models.UID, error) } diff --git a/commands/account/clear.go b/commands/account/clear.go index 1c13ddf5..ec033c46 100644 --- a/commands/account/clear.go +++ b/commands/account/clear.go @@ -35,7 +35,7 @@ func (c Clear) Execute(args []string) error { } if c.Selected { - defer store.Select(0) + defer store.Select("") } store.ApplyClear() acct.SetStatus(state.SearchFilterClear()) diff --git a/commands/account/export-mbox.go b/commands/account/export-mbox.go index 6422eae0..619c24a2 100644 --- a/commands/account/export-mbox.go +++ b/commands/account/export-mbox.go @@ -13,6 +13,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/lib/log" "git.sr.ht/~rjarry/aerc/lib/xdg" + "git.sr.ht/~rjarry/aerc/models" mboxer "git.sr.ht/~rjarry/aerc/worker/mbox" "git.sr.ht/~rjarry/aerc/worker/types" ) @@ -61,7 +62,7 @@ func (e ExportMbox) Execute(args []string) error { app.PushStatus("Exporting to "+e.Filename, 10*time.Second) // uids of messages to export - var uids []uint32 + var uids []models.UID // check if something is marked - we export that then msgProvider, ok := app.SelectedTabContent().(app.ProvidesMessages) @@ -98,7 +99,7 @@ func (e ExportMbox) Execute(args []string) error { defer file.Close() var mu sync.Mutex - var ctr uint32 + var ctr uint var retries int done := make(chan bool) @@ -159,15 +160,15 @@ func (e ExportMbox) Execute(args []string) error { return nil } -func sortMarkedUids(marked []uint32, store *lib.MessageStore) ([]uint32, error) { - lookup := map[uint32]bool{} +func sortMarkedUids(marked []models.UID, store *lib.MessageStore) ([]models.UID, error) { + lookup := map[models.UID]bool{} for _, uid := range marked { lookup[uid] = true } - uids := []uint32{} + uids := []models.UID{} iter := store.UidsIterator() for iter.Next() { - uid, ok := iter.Value().(uint32) + uid, ok := iter.Value().(models.UID) if !ok { return nil, errors.New("Invalid message UID value") } @@ -179,11 +180,11 @@ func sortMarkedUids(marked []uint32, store *lib.MessageStore) ([]uint32, error) return uids, nil } -func sortAllUids(store *lib.MessageStore) ([]uint32, error) { - uids := []uint32{} +func sortAllUids(store *lib.MessageStore) ([]models.UID, error) { + uids := []models.UID{} iter := store.UidsIterator() for iter.Next() { - uid, ok := iter.Value().(uint32) + uid, ok := iter.Value().(models.UID) if !ok { return nil, errors.New("Invalid message UID value") } diff --git a/commands/account/next.go b/commands/account/next.go index e14b14fb..b54ed0c1 100644 --- a/commands/account/next.go +++ b/commands/account/next.go @@ -85,7 +85,7 @@ func (np NextPrevMsg) Execute(args []string) error { if nextMsg := store.Selected(); nextMsg != nil { reloadViewer(nextMsg) } else { - store.FetchHeaders([]uint32{store.SelectedUid()}, + store.FetchHeaders([]models.UID{store.SelectedUid()}, func(msg types.WorkerMessage) { if m, ok := msg.(*types.MessageInfo); ok { reloadViewer(m.Info) diff --git a/commands/account/search.go b/commands/account/search.go index 5c2eaec7..14aa367b 100644 --- a/commands/account/search.go +++ b/commands/account/search.go @@ -191,7 +191,7 @@ func (s SearchFilter) Execute(args []string) error { store.Sort(store.GetCurrentSortCriteria(), cb) } else { acct.SetStatus(state.Search("Searching...")) - cb := func(uids []uint32) { + cb := func(uids []models.UID) { acct.SetStatus(state.Search(strings.Join(args, " "))) log.Tracef("Search results: %v", uids) store.ApplySearch(uids) diff --git a/commands/msg/archive.go b/commands/msg/archive.go index 8c5f12b9..c262de41 100644 --- a/commands/msg/archive.go +++ b/commands/msg/archive.go @@ -89,7 +89,7 @@ func archive(msgs []*models.MessageInfo, mfs *types.MultiFileStrategy, if err != nil { return err } - var uids []uint32 + var uids []models.UID for _, msg := range msgs { uids = append(uids, msg.Uid) } @@ -98,7 +98,7 @@ func archive(msgs []*models.MessageInfo, mfs *types.MultiFileStrategy, marker.ClearVisualMark() next := findNextNonDeleted(uids, store) - var uidMap map[string][]uint32 + var uidMap map[string][]models.UID switch archiveType { case ARCHIVE_MONTH: uidMap = groupBy(msgs, func(msg *models.MessageInfo) string { @@ -120,7 +120,7 @@ func archive(msgs []*models.MessageInfo, mfs *types.MultiFileStrategy, return dir }) case ARCHIVE_FLAT: - uidMap = make(map[string][]uint32) + uidMap = make(map[string][]models.UID) uidMap[archiveDir] = commands.UidsFromMessageInfos(msgs) } @@ -164,8 +164,8 @@ func archive(msgs []*models.MessageInfo, mfs *types.MultiFileStrategy, func groupBy(msgs []*models.MessageInfo, grouper func(*models.MessageInfo) string, -) map[string][]uint32 { - m := make(map[string][]uint32) +) map[string][]models.UID { + m := make(map[string][]models.UID) for _, msg := range msgs { group := grouper(msg) m[group] = append(m[group], msg.Uid) diff --git a/commands/msg/copy.go b/commands/msg/copy.go index d9c40ee2..52a6ea6c 100644 --- a/commands/msg/copy.go +++ b/commands/msg/copy.go @@ -175,7 +175,7 @@ func (c Copy) Execute(args []string) error { return nil } -func (c Copy) CallBack(msg types.WorkerMessage, uids []uint32, store *lib.MessageStore) { +func (c Copy) CallBack(msg types.WorkerMessage, uids []models.UID, store *lib.MessageStore) { dest := c.Folder if len(c.Account) != 0 { dest = fmt.Sprintf("%s in %s", c.Folder, c.Account) diff --git a/commands/msg/delete.go b/commands/msg/delete.go index 0d269eab..d58b14ad 100644 --- a/commands/msg/delete.go +++ b/commands/msg/delete.go @@ -117,7 +117,7 @@ func (d Delete) Execute(args []string) error { return nil } -func findNextNonDeleted(deleted []uint32, store *lib.MessageStore) *models.MessageInfo { +func findNextNonDeleted(deleted []models.UID, store *lib.MessageStore) *models.MessageInfo { var next, previous *models.MessageInfo stepper := []func(){store.Next, store.Prev} for _, stepFn := range stepper { @@ -146,7 +146,7 @@ func findNextNonDeleted(deleted []uint32, store *lib.MessageStore) *models.Messa return next } -func contains(uids []uint32, uid uint32) bool { +func contains(uids []models.UID, uid models.UID) bool { for _, item := range uids { if item == uid { return true diff --git a/commands/msg/forward.go b/commands/msg/forward.go index 7c491a06..d030636e 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -131,7 +131,7 @@ func (f forward) Execute(args []string) error { fetchFull = mv.MessageView().FetchFull } else { fetchFull = func(cb func(io.Reader)) { - store.FetchFull([]uint32{msg.Uid}, func(fm *types.FullMessage) { + store.FetchFull([]models.UID{msg.Uid}, func(fm *types.FullMessage) { if fm == nil || (fm != nil && fm.Content == nil) { return } @@ -164,7 +164,7 @@ func (f forward) Execute(args []string) error { composer.AddAttachment(tmpFileName) composer.OnClose(func(c *app.Composer) { if c.Sent() { - store.Forwarded([]uint32{msg.Uid}, true, nil) + store.Forwarded([]models.UID{msg.Uid}, true, nil) } os.RemoveAll(tmpDir) }) @@ -216,7 +216,7 @@ func (f forward) Execute(args []string) error { composer.OnClose(func(c *app.Composer) { if c.Sent() { - store.Forwarded([]uint32{msg.Uid}, true, nil) + store.Forwarded([]models.UID{msg.Uid}, true, nil) } }) diff --git a/commands/msg/invite.go b/commands/msg/invite.go index c62397c5..63ad2d3a 100644 --- a/commands/msg/invite.go +++ b/commands/msg/invite.go @@ -141,7 +141,7 @@ func (i invite) Execute(args []string) error { composer.OnClose(func(c *app.Composer) { if c.Sent() { - store.Answered([]uint32{msg.Uid}, true, nil) + store.Answered([]models.UID{msg.Uid}, true, nil) } }) diff --git a/commands/msg/mark.go b/commands/msg/mark.go index 5717e33d..42110038 100644 --- a/commands/msg/mark.go +++ b/commands/msg/mark.go @@ -4,6 +4,7 @@ import ( "fmt" "git.sr.ht/~rjarry/aerc/commands" + "git.sr.ht/~rjarry/aerc/models" ) type Mark struct { @@ -28,7 +29,7 @@ func (Mark) Aliases() []string { func (m Mark) Execute(args []string) error { h := newHelper() - OnSelectedMessage := func(fn func(uint32)) error { + OnSelectedMessage := func(fn func(models.UID)) error { if fn == nil { return fmt.Errorf("no operation selected") } @@ -58,7 +59,7 @@ func (m Mark) Execute(args []string) error { switch args[0] { case "mark": - var modFunc func(uint32) + var modFunc func(models.UID) if m.Toggle { modFunc = marker.ToggleMark } else { diff --git a/commands/msg/move.go b/commands/msg/move.go index 96e7c3da..1365b060 100644 --- a/commands/msg/move.go +++ b/commands/msg/move.go @@ -119,7 +119,7 @@ func (m Move) Execute(args []string) error { // something is happening app.PushStatus("Moving messages...", 10*time.Second) - var appended []uint32 + var appended []models.UID var timeout bool go func() { defer log.PanicHandler() @@ -187,7 +187,7 @@ func (m Move) Execute(args []string) error { func (m Move) CallBack( msg types.WorkerMessage, acct *app.AccountView, - uids []uint32, + uids []models.UID, next *models.MessageInfo, marker marker.Marker, timeout bool, diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go index 6ba57089..9764de82 100644 --- a/commands/msg/pipe.go +++ b/commands/msg/pipe.go @@ -15,6 +15,7 @@ import ( "git.sr.ht/~rjarry/aerc/commands" cryptoutil "git.sr.ht/~rjarry/aerc/lib/crypto/util" "git.sr.ht/~rjarry/aerc/lib/log" + "git.sr.ht/~rjarry/aerc/models" mboxer "git.sr.ht/~rjarry/aerc/worker/mbox" "git.sr.ht/~rjarry/aerc/worker/types" ) @@ -122,7 +123,7 @@ func (p Pipe) Run(cb func()) error { app.PushStatus("Fetching messages ...", 10*time.Second) if p.Full { - var uids []uint32 + var uids []models.UID var title string h := newHelper() diff --git a/commands/msg/read.go b/commands/msg/read.go index ab84d51c..686c1a9e 100644 --- a/commands/msg/read.go +++ b/commands/msg/read.go @@ -97,8 +97,8 @@ func (f FlagMsg) Execute(args []string) error { } // UIDs of messages to enable or disable the flag for. - var toEnable []uint32 - var toDisable []uint32 + var toEnable []models.UID + var toDisable []models.UID if f.Toggle { // If toggling, split messages into those that need to diff --git a/commands/msg/recall.go b/commands/msg/recall.go index 7c59ac85..15566414 100644 --- a/commands/msg/recall.go +++ b/commands/msg/recall.go @@ -70,7 +70,7 @@ func (r Recall) Execute(args []string) error { } composer.Tab = app.NewTab(composer, subject) composer.OnClose(func(composer *app.Composer) { - uids := []uint32{msgInfo.Uid} + uids := []models.UID{msgInfo.Uid} deleteMessage := func() { store.Delete( diff --git a/commands/msg/reply.go b/commands/msg/reply.go index e55b5d4b..2654514e 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -195,13 +195,13 @@ func (r reply) Execute(args []string) error { composer.OnClose(func(c *app.Composer) { switch { case c.Sent() && c.Archive() != "" && !noStore: - store.Answered([]uint32{msg.Uid}, true, nil) + store.Answered([]models.UID{msg.Uid}, true, nil) err := archive([]*models.MessageInfo{msg}, nil, c.Archive()) if err != nil { app.PushStatus("Archive failed", 10*time.Second) } case c.Sent() && !noStore: - store.Answered([]uint32{msg.Uid}, true, nil) + store.Answered([]models.UID{msg.Uid}, true, nil) case mv != nil && r.Close: view := account.ViewMessage{Peek: true} //nolint:errcheck // who cares? diff --git a/commands/msg/utils.go b/commands/msg/utils.go index d6dffd50..f6acb10d 100644 --- a/commands/msg/utils.go +++ b/commands/msg/utils.go @@ -29,7 +29,7 @@ func newHelper() *helper { } } -func (h *helper) markedOrSelectedUids() ([]uint32, error) { +func (h *helper) markedOrSelectedUids() ([]models.UID, error) { return commands.MarkedOrSelected(h.msgProvider) } diff --git a/commands/util.go b/commands/util.go index b6a458fd..bb20a204 100644 --- a/commands/util.go +++ b/commands/util.go @@ -148,7 +148,7 @@ func listDir(path string, hidden bool) []string { // MarkedOrSelected returns either all marked messages if any are marked or the // selected message instead -func MarkedOrSelected(pm app.ProvidesMessages) ([]uint32, error) { +func MarkedOrSelected(pm app.ProvidesMessages) ([]models.UID, error) { // marked has priority over the selected message marked, err := pm.MarkedMessages() if err != nil { @@ -162,15 +162,15 @@ func MarkedOrSelected(pm app.ProvidesMessages) ([]uint32, error) { if err != nil { return nil, err } - return expandFoldedThreads(pm, []uint32{msg.Uid}), nil + return expandFoldedThreads(pm, []models.UID{msg.Uid}), nil } -func expandFoldedThreads(pm app.ProvidesMessages, uids []uint32) []uint32 { +func expandFoldedThreads(pm app.ProvidesMessages, uids []models.UID) []models.UID { store := pm.Store() if store == nil { return uids } - expanded := make([]uint32, len(uids)) + expanded := make([]models.UID, len(uids)) copy(expanded, uids) for _, uid := range uids { thread, err := store.Thread(uid) @@ -194,8 +194,8 @@ func expandFoldedThreads(pm app.ProvidesMessages, uids []uint32) []uint32 { } // UidsFromMessageInfos extracts a uid slice from a slice of MessageInfos -func UidsFromMessageInfos(msgs []*models.MessageInfo) []uint32 { - uids := make([]uint32, len(msgs)) +func UidsFromMessageInfos(msgs []*models.MessageInfo) []models.UID { + uids := make([]models.UID, len(msgs)) i := 0 for _, msg := range msgs { uids[i] = msg.Uid @@ -204,9 +204,9 @@ func UidsFromMessageInfos(msgs []*models.MessageInfo) []uint32 { return uids } -func MsgInfoFromUids(store *lib.MessageStore, uids []uint32, statusInfo func(string)) ([]*models.MessageInfo, error) { +func MsgInfoFromUids(store *lib.MessageStore, uids []models.UID, statusInfo func(string)) ([]*models.MessageInfo, error) { infos := make([]*models.MessageInfo, len(uids)) - needHeaders := make([]uint32, 0) + needHeaders := make([]models.UID, 0) for i, uid := range uids { var ok bool infos[i], ok = store.Messages[uid] diff --git a/lib/emlview.go b/lib/emlview.go index e27edcb0..3f9c3f39 100644 --- a/lib/emlview.go +++ b/lib/emlview.go @@ -19,8 +19,8 @@ func (fm *EmlMessage) NewReader() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(*fm)), nil } -func (fm *EmlMessage) UID() uint32 { - return 0xFFFFFFF +func (fm *EmlMessage) UID() models.UID { + return "" } func (fm *EmlMessage) Labels() ([]string, error) { diff --git a/lib/iterator/impl.go b/lib/iterator/impl.go index 5e685166..be8c382e 100644 --- a/lib/iterator/impl.go +++ b/lib/iterator/impl.go @@ -3,6 +3,7 @@ package iterator import ( "errors" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) @@ -11,7 +12,7 @@ type defaultFactory struct{} func (df *defaultFactory) NewIterator(a interface{}) Iterator { switch data := a.(type) { - case []uint32: + case []models.UID: return &defaultUid{data: data, index: len(data)} case []*types.Thread: return &defaultThread{data: data, index: len(data)} @@ -21,7 +22,7 @@ func (df *defaultFactory) NewIterator(a interface{}) Iterator { // defaultUid type defaultUid struct { - data []uint32 + data []models.UID index int } @@ -70,7 +71,7 @@ type reverseFactory struct{} func (rf *reverseFactory) NewIterator(a interface{}) Iterator { switch data := a.(type) { - case []uint32: + case []models.UID: return &reverseUid{data: data, index: -1} case []*types.Thread: return &reverseThread{data: data, index: -1} @@ -80,7 +81,7 @@ func (rf *reverseFactory) NewIterator(a interface{}) Iterator { // reverseUid type reverseUid struct { - data []uint32 + data []models.UID index int } diff --git a/lib/iterator/iterator_test.go b/lib/iterator/iterator_test.go index 6a8d3f6e..01ab9ff9 100644 --- a/lib/iterator/iterator_test.go +++ b/lib/iterator/iterator_test.go @@ -4,10 +4,11 @@ import ( "testing" "git.sr.ht/~rjarry/aerc/lib/iterator" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) -func toThreads(uids []uint32) []*types.Thread { +func toThreads(uids []models.UID) []*types.Thread { threads := make([]*types.Thread, len(uids)) for i, u := range uids { threads[i] = &types.Thread{Uid: u} @@ -16,8 +17,8 @@ func toThreads(uids []uint32) []*types.Thread { } func TestIterator_DefaultFactory(t *testing.T) { - input := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9} - want := []uint32{9, 8, 7, 6, 5, 4, 3, 2, 1} + input := []models.UID{"1", "2", "3", "4", "5", "6", "7", "8", "9"} + want := []models.UID{"9", "8", "7", "6", "5", "4", "3", "2", "1"} factory := iterator.NewFactory(false) if factory == nil { @@ -30,8 +31,8 @@ func TestIterator_DefaultFactory(t *testing.T) { } func TestIterator_ReverseFactory(t *testing.T) { - input := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9} - want := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9} + input := []models.UID{"1", "2", "3", "4", "5", "6", "7", "8", "9"} + want := []models.UID{"1", "2", "3", "4", "5", "6", "7", "8", "9"} factory := iterator.NewFactory(true) if factory == nil { @@ -45,13 +46,13 @@ func TestIterator_ReverseFactory(t *testing.T) { } func checkUids(t *testing.T, factory iterator.Factory, - input []uint32, want []uint32, start, end int, + input []models.UID, want []models.UID, start, end int, ) { label := "uids" - got := make([]uint32, 0) + got := make([]models.UID, 0) iter := factory.NewIterator(input) for iter.Next() { - got = append(got, iter.Value().(uint32)) + got = append(got, iter.Value().(models.UID)) } if len(got) != len(want) { t.Errorf(label + "number of elements not correct") diff --git a/lib/marker/marker.go b/lib/marker/marker.go index a0860be9..55f70f42 100644 --- a/lib/marker/marker.go +++ b/lib/marker/marker.go @@ -1,13 +1,15 @@ package marker +import "git.sr.ht/~rjarry/aerc/models" + // Marker provides the interface for the marking behavior of messages type Marker interface { - Mark(uint32) - Unmark(uint32) - ToggleMark(uint32) + Mark(models.UID) + Unmark(models.UID) + ToggleMark(models.UID) Remark() - Marked() []uint32 - IsMarked(uint32) bool + Marked() []models.UID + IsMarked(models.UID) bool IsVisualMark() bool ToggleVisualMark(bool) UpdateVisualMark() @@ -16,30 +18,30 @@ type Marker interface { // UIDProvider provides the underlying uids and the selected message index type UIDProvider interface { - Uids() []uint32 + Uids() []models.UID SelectedIndex() int } type controller struct { uidProvider UIDProvider - marked map[uint32]struct{} - lastMarked map[uint32]struct{} - visualStartUID uint32 + marked map[models.UID]struct{} + lastMarked map[models.UID]struct{} + visualStartUID models.UID visualMarkMode bool - visualBase map[uint32]struct{} + visualBase map[models.UID]struct{} } // New returns a new Marker func New(up UIDProvider) Marker { return &controller{ uidProvider: up, - marked: make(map[uint32]struct{}), - lastMarked: make(map[uint32]struct{}), + marked: make(map[models.UID]struct{}), + lastMarked: make(map[models.UID]struct{}), } } // Mark markes the uid as marked -func (mc *controller) Mark(uid uint32) { +func (mc *controller) Mark(uid models.UID) { if mc.visualMarkMode { // visual mode has override, bogus input from user return @@ -48,7 +50,7 @@ func (mc *controller) Mark(uid uint32) { } // Unmark unmarks the uid -func (mc *controller) Unmark(uid uint32) { +func (mc *controller) Unmark(uid models.UID) { if mc.visualMarkMode { // user probably wanted to clear the visual marking mc.ClearVisualMark() @@ -63,7 +65,7 @@ func (mc *controller) Remark() { } // ToggleMark toggles the marked state for the given uid -func (mc *controller) ToggleMark(uid uint32) { +func (mc *controller) ToggleMark(uid models.UID) { if mc.visualMarkMode { // visual mode has override, bogus input from user return @@ -78,7 +80,7 @@ func (mc *controller) ToggleMark(uid uint32) { // resetMark removes the marking from all messages func (mc *controller) resetMark() { mc.lastMarked = mc.marked - mc.marked = make(map[uint32]struct{}) + mc.marked = make(map[models.UID]struct{}) } // removeStaleUID removes uids that are no longer presents in the UIDProvider @@ -98,15 +100,15 @@ func (mc *controller) removeStaleUID() { } // IsMarked checks whether the given uid has been marked -func (mc *controller) IsMarked(uid uint32) bool { +func (mc *controller) IsMarked(uid models.UID) bool { _, marked := mc.marked[uid] return marked } // Marked returns the uids of all marked messages -func (mc *controller) Marked() []uint32 { +func (mc *controller) Marked() []models.UID { mc.removeStaleUID() - marked := make([]uint32, len(mc.marked)) + marked := make([]models.UID, len(mc.marked)) i := 0 for uid := range mc.marked { marked[i] = uid @@ -132,7 +134,7 @@ func (mc *controller) ToggleVisualMark(clear bool) { if idx := mc.uidProvider.SelectedIndex(); idx >= 0 && idx < len(uids) { mc.visualStartUID = uids[idx] mc.marked[mc.visualStartUID] = struct{}{} - mc.visualBase = make(map[uint32]struct{}) + mc.visualBase = make(map[models.UID]struct{}) for key, value := range mc.marked { mc.visualBase[key] = value } @@ -144,7 +146,7 @@ func (mc *controller) ToggleVisualMark(clear bool) { func (mc *controller) ClearVisualMark() { mc.resetMark() mc.visualMarkMode = false - mc.visualStartUID = 0 + mc.visualStartUID = "" } // UpdateVisualMark updates the index with the currently selected message @@ -167,13 +169,13 @@ func (mc *controller) UpdateVisualMark() { uids := mc.uidProvider.Uids() - var visUids []uint32 + var visUids []models.UID if selectedIdx > startIdx { visUids = uids[startIdx : selectedIdx+1] } else { visUids = uids[selectedIdx : startIdx+1] } - mc.marked = make(map[uint32]struct{}) + mc.marked = make(map[models.UID]struct{}) for uid := range mc.visualBase { mc.marked[uid] = struct{}{} } diff --git a/lib/marker/marker_test.go b/lib/marker/marker_test.go index df9eb2a3..04d3fedc 100644 --- a/lib/marker/marker_test.go +++ b/lib/marker/marker_test.go @@ -4,16 +4,17 @@ import ( "testing" "git.sr.ht/~rjarry/aerc/lib/marker" + "git.sr.ht/~rjarry/aerc/models" ) // mockUidProvider implements the UidProvider interface and mocks the message // store for testing type mockUidProvider struct { - uids []uint32 + uids []models.UID idx int } -func (mock *mockUidProvider) Uids() []uint32 { +func (mock *mockUidProvider) Uids() []models.UID { return mock.uids } @@ -23,7 +24,7 @@ func (mock *mockUidProvider) SelectedIndex() int { func createMarker() (marker.Marker, *mockUidProvider) { uidProvider := &mockUidProvider{ - uids: []uint32{1, 2, 3, 4}, + uids: []models.UID{"1", "2", "3", "4"}, idx: 1, } m := marker.New(uidProvider) @@ -32,7 +33,7 @@ func createMarker() (marker.Marker, *mockUidProvider) { func TestMarker_MarkUnmark(t *testing.T) { m, _ := createMarker() - uid := uint32(4) + uid := models.UID("4") m.Mark(uid) if !m.IsMarked(uid) { @@ -47,7 +48,7 @@ func TestMarker_MarkUnmark(t *testing.T) { func TestMarker_ToggleMark(t *testing.T) { m, _ := createMarker() - uid := uint32(4) + uid := models.UID("4") if m.IsMarked(uid) { t.Errorf("ToggleMark: uid should not be marked") @@ -66,9 +67,9 @@ func TestMarker_ToggleMark(t *testing.T) { func TestMarker_Marked(t *testing.T) { m, _ := createMarker() - expected := map[uint32]struct{}{ - uint32(1): {}, - uint32(4): {}, + expected := map[models.UID]struct{}{ + "1": {}, + "4": {}, } for uid := range expected { m.Mark(uid) @@ -81,7 +82,7 @@ func TestMarker_Marked(t *testing.T) { for _, uid := range got { if _, ok := expected[uid]; !ok { - t.Errorf("Marked: received uid %d as marked but it should not be", uid) + t.Errorf("Marked: received uid %q as marked but it should not be", uid) } } } @@ -93,15 +94,15 @@ func TestMarker_VisualMode(t *testing.T) { m.ToggleVisualMark(false) // marking should now fail silently because we're in visual mode - m.Mark(1) - if m.IsMarked(1) { + m.Mark("1") + if m.IsMarked("1") { t.Errorf("marking in visual mode should not work") } // move selection index to last item up.idx = len(up.uids) - 1 m.UpdateVisualMark() - expectedMarked := []uint32{2, 3, 4} + expectedMarked := []models.UID{"2", "3", "4"} for _, uidMarked := range expectedMarked { if !m.IsMarked(uidMarked) { @@ -128,7 +129,7 @@ func TestMarker_VisualMode(t *testing.T) { func TestMarker_MarkOutOfBound(t *testing.T) { m, _ := createMarker() - outOfBoundUid := uint32(100) + outOfBoundUid := models.UID("100") m.Mark(outOfBoundUid) for _, markedUid := range m.Marked() { diff --git a/lib/messageview.go b/lib/messageview.go index 34549023..b849148a 100644 --- a/lib/messageview.go +++ b/lib/messageview.go @@ -75,7 +75,7 @@ func NewMessageStoreView(messageInfo *models.MessageInfo, setSeen bool, cb := func(msv MessageView, err error) { if msv != nil && setSeen && err == nil && !messageInfo.Flags.Has(models.SeenFlag) { - store.Flag([]uint32{messageInfo.Uid}, models.SeenFlag, true, nil) + store.Flag([]models.UID{messageInfo.Uid}, models.SeenFlag, true, nil) } innerCb(msv, err) } @@ -147,7 +147,7 @@ func (msv *MessageStoreView) MessageDetails() *models.MessageDetails { func (msv *MessageStoreView) FetchFull(cb func(io.Reader)) { if msv.message == nil && msv.messageStore != nil { - msv.messageStore.FetchFull([]uint32{msv.messageInfo.Uid}, + msv.messageStore.FetchFull([]models.UID{msv.messageInfo.Uid}, func(fm *types.FullMessage) { cb(fm.Content.Reader) }) diff --git a/lib/msgstore.go b/lib/msgstore.go index 5c8b3ef1..1250a9c0 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -20,8 +20,8 @@ import ( type MessageStore struct { sync.Mutex Name string - Deleted map[uint32]interface{} - Messages map[uint32]*models.MessageInfo + Deleted map[models.UID]interface{} + Messages map[models.UID]*models.MessageInfo Sorting bool ui func() *config.UIConfig @@ -30,21 +30,21 @@ type MessageStore struct { ctx context.Context // Ordered list of known UIDs - uids []uint32 + uids []models.UID threads []*types.Thread // Visible UIDs scrollOffset int scrollLen int - selectedUid uint32 - bodyCallbacks map[uint32][]func(*types.FullMessage) + selectedUid models.UID + bodyCallbacks map[models.UID][]func(*types.FullMessage) // marking marker marker.Marker // Search/filter results - results []uint32 + results []models.UID resultIndex int filter *types.SearchCriteria @@ -60,11 +60,11 @@ type MessageStore struct { onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers onFilterChange func(store *MessageStore) onUpdateDirs func() - pendingBodies map[uint32]interface{} - pendingHeaders map[uint32]interface{} + pendingBodies map[models.UID]interface{} + pendingHeaders map[models.UID]interface{} worker *types.Worker - needsFlags []uint32 + needsFlags []models.UID fetchFlagsDebounce *time.Timer fetchFlagsDelay time.Duration @@ -85,7 +85,7 @@ type MessageStore struct { onSelect func(*models.MessageInfo) } -const MagicUid = 0xFFFFFFFF +const MagicUid = models.UID("") func NewMessageStore(worker *types.Worker, name string, ui func() *config.UIConfig, @@ -97,8 +97,8 @@ func NewMessageStore(worker *types.Worker, name string, ) *MessageStore { return &MessageStore{ Name: name, - Deleted: make(map[uint32]interface{}), - Messages: make(map[uint32]*models.MessageInfo), + Deleted: make(map[models.UID]interface{}), + Messages: make(map[models.UID]*models.MessageInfo), ui: ui, @@ -108,13 +108,12 @@ func NewMessageStore(worker *types.Worker, name string, // default window height until account is drawn once scrollLen: 25, - bodyCallbacks: make(map[uint32][]func(*types.FullMessage)), - - pendingBodies: make(map[uint32]interface{}), - pendingHeaders: make(map[uint32]interface{}), + bodyCallbacks: make(map[models.UID][]func(*types.FullMessage)), + pendingBodies: make(map[models.UID]interface{}), + pendingHeaders: make(map[models.UID]interface{}), worker: worker, - needsFlags: []uint32{}, + needsFlags: []models.UID{}, fetchFlagsDelay: 50 * time.Millisecond, triggerNewEmail: triggerNewEmail, @@ -158,12 +157,12 @@ func (store *MessageStore) UpdateScroll(offset, length int) { store.scrollLen = length } -func (store *MessageStore) FetchHeaders(uids []uint32, +func (store *MessageStore) FetchHeaders(uids []models.UID, cb func(types.WorkerMessage), ) { // 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. - var toFetch []uint32 + var toFetch []models.UID for _, uid := range uids { if _, ok := store.pendingHeaders[uid]; !ok { toFetch = append(toFetch, uid) @@ -189,10 +188,10 @@ func (store *MessageStore) FetchHeaders(uids []uint32, } } -func (store *MessageStore) FetchFull(uids []uint32, cb func(*types.FullMessage)) { +func (store *MessageStore) FetchFull(uids []models.UID, cb func(*types.FullMessage)) { // 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. - var toFetch []uint32 + var toFetch []models.UID for _, uid := range uids { if _, ok := store.pendingBodies[uid]; !ok { toFetch = append(toFetch, uid) @@ -220,7 +219,7 @@ func (store *MessageStore) FetchFull(uids []uint32, cb func(*types.FullMessage)) } } -func (store *MessageStore) FetchBodyPart(uid uint32, part []int, cb func(io.Reader)) { +func (store *MessageStore) FetchBodyPart(uid models.UID, part []int, cb func(io.Reader)) { store.worker.PostAction(&types.FetchMessageBodyPart{ Uid: uid, Part: part, @@ -253,7 +252,7 @@ func merge(to *models.MessageInfo, from *models.MessageInfo) { } func (store *MessageStore) Update(msg types.WorkerMessage) { - var newUids []uint32 + var newUids []models.UID update := false updateThreads := false directoryChange := false @@ -265,7 +264,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { store.Sort(store.sortCriteria, nil) update = true case *types.DirectoryContents: - newMap := make(map[uint32]*models.MessageInfo, len(msg.Uids)) + newMap := make(map[models.UID]*models.MessageInfo, len(msg.Uids)) for i, uid := range msg.Uids { if msg, ok := store.Messages[uid]; ok { newMap[uid] = msg @@ -291,7 +290,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { store.uids = store.builder.Uids() store.threads = msg.Threads - newMap := make(map[uint32]*models.MessageInfo, len(store.uids)) + newMap := make(map[models.UID]*models.MessageInfo, len(store.uids)) for i, uid := range store.uids { if msg, ok := store.Messages[uid]; ok { newMap[uid] = msg @@ -351,13 +350,13 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { break } - toDelete := make(map[uint32]interface{}) + toDelete := make(map[models.UID]interface{}) for _, uid := range msg.Uids { toDelete[uid] = nil delete(store.Messages, uid) delete(store.Deleted, uid) } - uids := make([]uint32, 0, len(store.uids)-len(msg.Uids)) + uids := make([]models.UID, 0, len(store.uids)-len(msg.Uids)) for _, uid := range store.uids { if _, deleted := toDelete[uid]; deleted { continue @@ -369,7 +368,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { store.Select(MagicUid) } - var newResults []uint32 + var newResults []models.UID for _, res := range store.results { if _, deleted := toDelete[res]; !deleted { newResults = append(newResults, res) @@ -528,7 +527,7 @@ func (store *MessageStore) runThreadBuilderNow() { } // Thread returns the thread for the given UId -func (store *MessageStore) Thread(uid uint32) (*types.Thread, error) { +func (store *MessageStore) Thread(uid models.UID) (*types.Thread, error) { if store.builder == nil { return nil, errors.New("no threads found") } @@ -540,15 +539,15 @@ func (store *MessageStore) SelectedThread() (*types.Thread, error) { return store.Thread(store.SelectedUid()) } -func (store *MessageStore) Fold(uid uint32, toggle bool) error { +func (store *MessageStore) Fold(uid models.UID, toggle bool) error { return store.doThreadFolding(uid, true, toggle) } -func (store *MessageStore) Unfold(uid uint32, toggle bool) error { +func (store *MessageStore) Unfold(uid models.UID, toggle bool) error { return store.doThreadFolding(uid, false, toggle) } -func (store *MessageStore) doThreadFolding(uid uint32, hide bool, toggle bool) error { +func (store *MessageStore) doThreadFolding(uid models.UID, hide bool, toggle bool) error { thread, err := store.Thread(uid) if err != nil { return err @@ -596,7 +595,7 @@ func (store *MessageStore) doThreadFolding(uid uint32, hide bool, toggle bool) e return nil } -func (store *MessageStore) Delete(uids []uint32, mfs *types.MultiFileStrategy, +func (store *MessageStore) Delete(uids []models.UID, mfs *types.MultiFileStrategy, cb func(msg types.WorkerMessage), ) { for _, uid := range uids { @@ -618,13 +617,13 @@ func (store *MessageStore) Delete(uids []uint32, mfs *types.MultiFileStrategy, }) } -func (store *MessageStore) revertDeleted(uids []uint32) { +func (store *MessageStore) revertDeleted(uids []models.UID) { for _, uid := range uids { delete(store.Deleted, uid) } } -func (store *MessageStore) Copy(uids []uint32, dest string, createDest bool, +func (store *MessageStore) Copy(uids []models.UID, dest string, createDest bool, mfs *types.MultiFileStrategy, cb func(msg types.WorkerMessage), ) { if createDest { @@ -646,7 +645,7 @@ func (store *MessageStore) Copy(uids []uint32, dest string, createDest bool, }) } -func (store *MessageStore) Move(uids []uint32, dest string, createDest bool, +func (store *MessageStore) Move(uids []models.UID, dest string, createDest bool, mfs *types.MultiFileStrategy, cb func(msg types.WorkerMessage), ) { for _, uid := range uids { @@ -699,7 +698,7 @@ func (store *MessageStore) Append(dest string, flags models.Flags, date time.Tim }) } -func (store *MessageStore) Flag(uids []uint32, flags models.Flags, +func (store *MessageStore) Flag(uids []models.UID, flags models.Flags, enable bool, cb func(msg types.WorkerMessage), ) { store.worker.PostAction(&types.FlagMessages{ @@ -729,7 +728,7 @@ func (store *MessageStore) Flag(uids []uint32, flags models.Flags, }) } -func (store *MessageStore) Answered(uids []uint32, answered bool, +func (store *MessageStore) Answered(uids []models.UID, answered bool, cb func(msg types.WorkerMessage), ) { store.worker.PostAction(&types.AnsweredMessages{ @@ -738,7 +737,7 @@ func (store *MessageStore) Answered(uids []uint32, answered bool, }, cb) } -func (store *MessageStore) Forwarded(uids []uint32, forwarded bool, +func (store *MessageStore) Forwarded(uids []models.UID, forwarded bool, cb func(msg types.WorkerMessage), ) { store.worker.PostAction(&types.ForwardedMessages{ @@ -747,7 +746,7 @@ func (store *MessageStore) Forwarded(uids []uint32, forwarded bool, }, cb) } -func (store *MessageStore) Uids() []uint32 { +func (store *MessageStore) Uids() []models.UID { if store.ThreadedView() && store.builder != nil { if uids := store.builder.Uids(); len(uids) > 0 { return uids @@ -764,7 +763,7 @@ func (store *MessageStore) Selected() *models.MessageInfo { return store.Messages[store.selectedUid] } -func (store *MessageStore) SelectedUid() uint32 { +func (store *MessageStore) SelectedUid() models.UID { if store.selectedUid == MagicUid && len(store.Uids()) > 0 { iter := store.UidsIterator() idx := iter.StartIndex() @@ -776,14 +775,14 @@ func (store *MessageStore) SelectedUid() uint32 { return store.selectedUid } -func (store *MessageStore) Select(uid uint32) { +func (store *MessageStore) Select(uid models.UID) { store.selectPriv(uid, false) if store.onSelect != nil { store.onSelect(store.Selected()) } } -func (store *MessageStore) selectPriv(uid uint32, lockHeld bool) { +func (store *MessageStore) selectPriv(uid models.UID, lockHeld bool) { if !lockHeld { store.threadsMutex.Lock() } @@ -844,14 +843,14 @@ func (store *MessageStore) Prev() { store.NextPrev(-1) } -func (store *MessageStore) Search(terms *types.SearchCriteria, cb func([]uint32)) { +func (store *MessageStore) Search(terms *types.SearchCriteria, cb func([]models.UID)) { store.worker.PostAction(&types.SearchDirectory{ Context: store.ctx, Criteria: terms, }, func(msg types.WorkerMessage) { if msg, ok := msg.(*types.SearchResults); ok { allowedUids := store.Uids() - uids := make([]uint32, 0, len(msg.Uids)) + uids := make([]models.UID, 0, len(msg.Uids)) for _, uid := range msg.Uids { for _, uidCheck := range allowedUids { if uid == uidCheck { @@ -866,14 +865,14 @@ func (store *MessageStore) Search(terms *types.SearchCriteria, cb func([]uint32) }) } -func (store *MessageStore) ApplySearch(results []uint32) { +func (store *MessageStore) ApplySearch(results []models.UID) { store.results = results store.resultIndex = -1 store.NextResult() } // IsResult returns true if uid is a search result -func (store *MessageStore) IsResult(uid uint32) bool { +func (store *MessageStore) IsResult(uid models.UID) bool { for _, hit := range store.results { if hit == uid { return true @@ -935,7 +934,7 @@ func (store *MessageStore) PrevResult() { store.nextPrevResult(-1) } -func (store *MessageStore) ModifyLabels(uids []uint32, add, remove []string, +func (store *MessageStore) ModifyLabels(uids []models.UID, add, remove []string, cb func(msg types.WorkerMessage), ) { store.worker.PostAction(&types.ModifyLabels{ @@ -999,7 +998,7 @@ func (store *MessageStore) Marker() marker.Marker { } // FindIndexByUid returns the index in store.Uids() or -1 if not found -func (store *MessageStore) FindIndexByUid(uid uint32) int { +func (store *MessageStore) FindIndexByUid(uid models.UID) int { for idx, u := range store.Uids() { if u == uid { return idx @@ -1029,7 +1028,7 @@ func (store *MessageStore) fetchFlags() { Context: store.ctx, Uids: store.needsFlags, }, nil) - store.needsFlags = []uint32{} + store.needsFlags = []models.UID{} store.Unlock() }) } diff --git a/lib/rfc822/message.go b/lib/rfc822/message.go index 3f0c447e..653cb2fe 100644 --- a/lib/rfc822/message.go +++ b/lib/rfc822/message.go @@ -291,7 +291,7 @@ type RawMessage interface { NewReader() (io.ReadCloser, error) ModelFlags() (models.Flags, error) Labels() ([]string, error) - UID() uint32 + UID() models.UID } // MessageInfo populates a models.MessageInfo struct for the message. diff --git a/lib/rfc822/message_test.go b/lib/rfc822/message_test.go index f5f222e4..ca5ae014 100644 --- a/lib/rfc822/message_test.go +++ b/lib/rfc822/message_test.go @@ -185,7 +185,7 @@ func (m *mockRawMessage) NewReader() (io.ReadCloser, error) { } func (m *mockRawMessage) ModelFlags() (models.Flags, error) { return 0, nil } func (m *mockRawMessage) Labels() ([]string, error) { return nil, nil } -func (m *mockRawMessage) UID() uint32 { return 0 } +func (m *mockRawMessage) UID() models.UID { return "" } func die(err error) { if err != nil { diff --git a/lib/sort/sort.go b/lib/sort/sort.go index 082ba48b..b62b8d04 100644 --- a/lib/sort/sort.go +++ b/lib/sort/sort.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) @@ -61,9 +62,9 @@ func parseSortField(arg string) (types.SortField, error) { // Sorts toSort by sortBy so that toSort becomes a permutation following the // order of sortBy. // toSort should be a subset of sortBy -func SortBy(toSort []uint32, sortBy []uint32) { +func SortBy(toSort []models.UID, sortBy []models.UID) { // build a map from sortBy - uidMap := make(map[uint32]int) + uidMap := make(map[models.UID]int) for i, uid := range sortBy { uidMap[uid] = i } diff --git a/lib/threadbuilder.go b/lib/threadbuilder.go index abfbadb7..d7373b10 100644 --- a/lib/threadbuilder.go +++ b/lib/threadbuilder.go @@ -15,41 +15,41 @@ import ( type ThreadBuilder struct { sync.Mutex - threadBlocks map[uint32]jwz.Threadable - threadedUids []uint32 - threadMap map[uint32]*types.Thread + threadBlocks map[models.UID]jwz.Threadable + threadedUids []models.UID + threadMap map[models.UID]*types.Thread iterFactory iterator.Factory bySubject bool } func NewThreadBuilder(i iterator.Factory, bySubject bool) *ThreadBuilder { tb := &ThreadBuilder{ - threadBlocks: make(map[uint32]jwz.Threadable), + threadBlocks: make(map[models.UID]jwz.Threadable), iterFactory: i, - threadMap: make(map[uint32]*types.Thread), + threadMap: make(map[models.UID]*types.Thread), bySubject: bySubject, } return tb } -func (builder *ThreadBuilder) ThreadForUid(uid uint32) (*types.Thread, error) { +func (builder *ThreadBuilder) ThreadForUid(uid models.UID) (*types.Thread, error) { builder.Lock() defer builder.Unlock() t, ok := builder.threadMap[uid] var err error if !ok { - err = fmt.Errorf("no thread found for uid '%d'", uid) + err = fmt.Errorf("no thread found for uid '%s'", uid) } return t, err } // Uids returns the uids in threading order -func (builder *ThreadBuilder) Uids() []uint32 { +func (builder *ThreadBuilder) Uids() []models.UID { builder.Lock() defer builder.Unlock() if builder.threadedUids == nil { - return []uint32{} + return []models.UID{} } return builder.threadedUids } @@ -68,7 +68,7 @@ func (builder *ThreadBuilder) Update(msg *models.MessageInfo) { } // Threads returns a slice of threads for the given list of uids -func (builder *ThreadBuilder) Threads(uids []uint32, inverse bool, sort bool, +func (builder *ThreadBuilder) Threads(uids []models.UID, inverse bool, sort bool, ) []*types.Thread { builder.Lock() defer builder.Unlock() @@ -91,7 +91,7 @@ func (builder *ThreadBuilder) Threads(uids []uint32, inverse bool, sort bool, return threads } -func (builder *ThreadBuilder) generateStructure(uids []uint32) jwz.Threadable { +func (builder *ThreadBuilder) generateStructure(uids []models.UID) jwz.Threadable { jwzThreads := make([]jwz.Threadable, 0, len(builder.threadBlocks)) for _, uid := range uids { if thr, ok := builder.threadBlocks[uid]; ok { @@ -108,7 +108,7 @@ func (builder *ThreadBuilder) generateStructure(uids []uint32) jwz.Threadable { } func (builder *ThreadBuilder) buildAercThreads(structure jwz.Threadable, - uids []uint32, sort bool, + uids []models.UID, sort bool, ) []*types.Thread { threads := make([]*types.Thread, 0, len(builder.threadBlocks)) @@ -121,7 +121,7 @@ func (builder *ThreadBuilder) buildAercThreads(structure jwz.Threadable, // prepare bigger function var bigger func(l, r *types.Thread) bool if sort { - sortMap := make(map[uint32]int) + sortMap := make(map[models.UID]int) for i, uid := range uids { sortMap[uid] = i } @@ -148,7 +148,7 @@ func (builder *ThreadBuilder) buildAercThreads(structure jwz.Threadable, } // build thread tree - root := &types.Thread{Uid: 0} + root := &types.Thread{} builder.buildTree(structure, root, bigger, true) // copy top-level threads to thread slice @@ -197,16 +197,16 @@ func (builder *ThreadBuilder) newThread(c jwz.Threadable, parent *types.Thread, return nil } -func (builder *ThreadBuilder) sortThreads(threads []*types.Thread, orderedUids []uint32) { +func (builder *ThreadBuilder) sortThreads(threads []*types.Thread, orderedUids []models.UID) { types.SortThreadsBy(threads, orderedUids) } // RebuildUids rebuilds the uids from the given slice of threads func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread, inverse bool) { - uids := make([]uint32, 0, len(threads)) + uids := make([]models.UID, 0, len(threads)) iterT := builder.iterFactory.NewIterator(threads) for iterT.Next() { - var threaduids []uint32 + var threaduids []models.UID _ = iterT.Value().(*types.Thread).Walk( func(t *types.Thread, level int, currentErr error) error { stored, ok := builder.threadMap[t.Uid] @@ -231,10 +231,10 @@ func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread, inverse bool) } } - result := make([]uint32, 0, len(uids)) + result := make([]models.UID, 0, len(uids)) iterU := builder.iterFactory.NewIterator(uids) for iterU.Next() { - result = append(result, iterU.Value().(uint32)) + result = append(result, iterU.Value().(models.UID)) } builder.threadedUids = result } @@ -310,9 +310,9 @@ func cleanRefs(m, irp string, refs []string) []string { return cleanRefs } -func (t *threadable) UID() uint32 { +func (t *threadable) UID() models.UID { if t.MsgInfo == nil { - return 0 + return "" } return t.MsgInfo.Uid } diff --git a/lib/uidstore/uidstore.go b/lib/uidstore/uidstore.go deleted file mode 100644 index 11c5e47c..00000000 --- a/lib/uidstore/uidstore.go +++ /dev/null @@ -1,62 +0,0 @@ -// Package uidstore provides a concurrency-safe two-way mapping between UIDs -// used by the UI and arbitrary string keys as used by different mail backends. -// -// Multiple Store instances can safely be created and the UIDs that they -// generate will be globally unique. -package uidstore - -import ( - "sync" - "sync/atomic" -) - -var nextUID uint32 = 1 - -// Store holds a mapping between application keys and globally-unique UIDs. -type Store struct { - keyByUID map[uint32]string - uidByKey map[string]uint32 - m sync.Mutex -} - -// NewStore creates a new, empty Store. -func NewStore() *Store { - return &Store{ - keyByUID: make(map[uint32]string), - uidByKey: make(map[string]uint32), - } -} - -// GetOrInsert returns the UID for the provided key. If the key was already -// present in the store, the same UID value is returned. Otherwise, the key is -// inserted and the newly generated UID is returned. -func (s *Store) GetOrInsert(key string) uint32 { - s.m.Lock() - defer s.m.Unlock() - if uid, ok := s.uidByKey[key]; ok { - return uid - } - uid := atomic.AddUint32(&nextUID, 1) - s.keyByUID[uid] = key - s.uidByKey[key] = uid - return uid -} - -// GetKey returns the key for the provided UID, if available. -func (s *Store) GetKey(uid uint32) (string, bool) { - s.m.Lock() - defer s.m.Unlock() - key, ok := s.keyByUID[uid] - return key, ok -} - -// RemoveUID removes the specified UID from the store. -func (s *Store) RemoveUID(uid uint32) { - s.m.Lock() - defer s.m.Unlock() - key, ok := s.keyByUID[uid] - if ok { - delete(s.uidByKey, key) - } - delete(s.keyByUID, uid) -} diff --git a/models/models.go b/models/models.go index 749d14d9..0f7c8445 100644 --- a/models/models.go +++ b/models/models.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "strconv" "strings" "time" @@ -107,6 +108,33 @@ func (c *Capabilities) Has(s string) bool { return false } +type UID string + +func UidToUint32(uid UID) uint32 { + u, _ := strconv.ParseUint(string(uid), 10, 32) + return uint32(u) +} + +func Uint32ToUid(u uint32) UID { + return UID(strconv.FormatUint(uint64(u), 10)) +} + +func UidToUint32List(uids []UID) []uint32 { + ulist := make([]uint32, 0, len(uids)) + for _, uid := range uids { + ulist = append(ulist, UidToUint32(uid)) + } + return ulist +} + +func Uint32ToUidList(ulist []uint32) []UID { + uids := make([]UID, 0, len(ulist)) + for _, u := range ulist { + uids = append(uids, Uint32ToUid(u)) + } + return uids +} + // A MessageInfo holds information about the structure of a message type MessageInfo struct { BodyStructure *BodyStructure @@ -118,7 +146,7 @@ type MessageInfo struct { RFC822Headers *mail.Header Refs []string Size uint32 - Uid uint32 + Uid UID Error error } @@ -169,13 +197,13 @@ func (mi *MessageInfo) References() ([]string, error) { // A MessageBodyPart can be displayed in the message viewer type MessageBodyPart struct { Reader io.Reader - Uid uint32 + Uid UID } // A FullMessage is the entire message type FullMessage struct { Reader io.Reader - Uid uint32 + Uid UID } type BodyStructure struct { diff --git a/worker/imap/cache.go b/worker/imap/cache.go index 8ae60f03..0eba7b9d 100644 --- a/worker/imap/cache.go +++ b/worker/imap/cache.go @@ -24,7 +24,7 @@ type CachedHeader struct { BodyStructure models.BodyStructure Envelope models.Envelope InternalDate time.Time - Uid uint32 + Uid models.UID Size uint32 Header []byte Created time.Time @@ -34,7 +34,7 @@ var ( // cacheTag should be updated when changing the cache // structure; this will ensure that the user's cache is cleared and // reloaded when the underlying cache structure changes - cacheTag = []byte("0002") + cacheTag = []byte("0003") cacheTagKey = []byte("cache.tag") ) @@ -112,9 +112,9 @@ func (w *IMAPWorker) cacheHeader(mi *models.MessageInfo) { } } -func (w *IMAPWorker) getCachedHeaders(msg *types.FetchMessageHeaders) []uint32 { +func (w *IMAPWorker) getCachedHeaders(msg *types.FetchMessageHeaders) []models.UID { w.worker.Tracef("Retrieving headers from cache: %v", msg.Uids) - var need []uint32 + var need []models.UID for _, uid := range msg.Uids { key := w.headerKey(uid) data, err := w.cache.Get(key, nil) @@ -157,8 +157,8 @@ func (w *IMAPWorker) getCachedHeaders(msg *types.FetchMessageHeaders) []uint32 { return need } -func (w *IMAPWorker) headerKey(uid uint32) []byte { - key := fmt.Sprintf("header.%s.%d.%d", +func (w *IMAPWorker) headerKey(uid models.UID) []byte { + key := fmt.Sprintf("header.%s.%d.%s", w.selected.Name, w.selected.UidValidity, uid) return []byte(key) } diff --git a/worker/imap/extensions/xgmext/client.go b/worker/imap/extensions/xgmext/client.go index 65f11e74..e6e34a44 100644 --- a/worker/imap/extensions/xgmext/client.go +++ b/worker/imap/extensions/xgmext/client.go @@ -5,6 +5,7 @@ import ( "fmt" "git.sr.ht/~rjarry/aerc/lib/log" + "git.sr.ht/~rjarry/aerc/models" "github.com/emersion/go-imap" "github.com/emersion/go-imap/client" "github.com/emersion/go-imap/commands" @@ -19,7 +20,7 @@ func NewHandler(c *client.Client) *handler { return &handler{client: c} } -func (h handler) FetchEntireThreads(requested []uint32) ([]uint32, error) { +func (h handler) FetchEntireThreads(requested []models.UID) ([]models.UID, error) { threadIds, err := h.fetchThreadIds(requested) if err != nil { return nil, @@ -33,7 +34,7 @@ func (h handler) FetchEntireThreads(requested []uint32) ([]uint32, error) { return uids, nil } -func (h handler) fetchThreadIds(uids []uint32) ([]string, error) { +func (h handler) fetchThreadIds(uids []models.UID) ([]string, error) { messages := make(chan *imap.Message) done := make(chan error) @@ -58,7 +59,9 @@ func (h handler) fetchThreadIds(uids []uint32) ([]string, error) { }() var set imap.SeqSet - set.AddNum(uids...) + for _, uid := range uids { + set.AddNum(models.UidToUint32(uid)) + } err := h.client.UidFetch(&set, items, messages) <-done @@ -69,18 +72,18 @@ func (h handler) fetchThreadIds(uids []uint32) ([]string, error) { return thrid, err } -func (h handler) searchUids(thrid []string) ([]uint32, error) { +func (h handler) searchUids(thrid []string) ([]models.UID, error) { if len(thrid) == 0 { return nil, errors.New("no thread IDs provided") } return h.runSearch(NewThreadIDSearch(thrid)) } -func (h handler) RawSearch(rawSearch string) ([]uint32, error) { +func (h handler) RawSearch(rawSearch string) ([]models.UID, error) { return h.runSearch(NewRawSearch(rawSearch)) } -func (h handler) runSearch(cmd imap.Commander) ([]uint32, error) { +func (h handler) runSearch(cmd imap.Commander) ([]models.UID, error) { if h.client.State() != imap.SelectedState { return nil, errors.New("no mailbox selected") } @@ -90,5 +93,9 @@ func (h handler) runSearch(cmd imap.Commander) ([]uint32, error) { if err != nil { return nil, fmt.Errorf("imap execute failed: %w", err) } - return res.Ids, status.Err() + var uids []models.UID + for _, i := range res.Ids { + uids = append(uids, models.Uint32ToUid(i)) + } + return uids, status.Err() } diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go index 202038fe..9b77c777 100644 --- a/worker/imap/fetch.go +++ b/worker/imap/fetch.go @@ -82,7 +82,7 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders( RFC822Headers: header, Refs: parse.MsgIDList(header, "references"), Size: _msg.Size, - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), } imapw.worker.PostMessage(&types.MessageInfo{ Message: types.RespondTo(msg), @@ -126,7 +126,7 @@ func (imapw *IMAPWorker) handleFetchMessageBodyPart( partHeaderSection.FetchItem(), partBodySection.FetchItem(), } - imapw.handleFetchMessages(msg, []uint32{msg.Uid}, items, + imapw.handleFetchMessages(msg, []models.UID{msg.Uid}, items, func(_msg *imap.Message) error { if len(_msg.Body) == 0 { // ignore duplicate messages with only flag updates @@ -154,7 +154,7 @@ func (imapw *IMAPWorker) handleFetchMessageBodyPart( Message: types.RespondTo(msg), Part: &models.MessageBodyPart{ Reader: part.Body, - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) // Update flags (to mark message as read) @@ -162,7 +162,7 @@ func (imapw *IMAPWorker) handleFetchMessageBodyPart( Message: types.RespondTo(msg), Info: &models.MessageInfo{ Flags: translateImapFlags(_msg.Flags), - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) return nil @@ -196,7 +196,7 @@ func (imapw *IMAPWorker) handleFetchFullMessages( Message: types.RespondTo(msg), Content: &models.FullMessage{ Reader: bufio.NewReader(r), - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) // Update flags (to mark message as read) @@ -204,7 +204,7 @@ func (imapw *IMAPWorker) handleFetchFullMessages( Message: types.RespondTo(msg), Info: &models.MessageInfo{ Flags: translateImapFlags(_msg.Flags), - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) return nil @@ -228,7 +228,7 @@ func (imapw *IMAPWorker) handleFetchMessageFlags(msg *types.FetchMessageFlags) { Message: types.RespondTo(msg), Info: &models.MessageInfo{ Flags: translateImapFlags(_msg.Flags), - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) return nil @@ -236,13 +236,13 @@ func (imapw *IMAPWorker) handleFetchMessageFlags(msg *types.FetchMessageFlags) { } func (imapw *IMAPWorker) handleFetchMessages( - msg types.WorkerMessage, uids []uint32, items []imap.FetchItem, + msg types.WorkerMessage, uids []models.UID, items []imap.FetchItem, procFunc func(*imap.Message) error, ) { messages := make(chan *imap.Message) done := make(chan struct{}) - missingUids := make(map[uint32]bool) + missingUids := make(map[models.UID]bool) for _, uid := range uids { missingUids[uid] = true } @@ -251,14 +251,14 @@ func (imapw *IMAPWorker) handleFetchMessages( defer log.PanicHandler() for _msg := range messages { - delete(missingUids, _msg.Uid) + delete(missingUids, models.Uint32ToUid(_msg.Uid)) err := procFunc(_msg) if err != nil { log.Errorf("failed to process message <%d>: %v", _msg.Uid, err) imapw.worker.PostMessage(&types.MessageInfo{ Message: types.RespondTo(msg), Info: &models.MessageInfo{ - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), Error: err, }, }, nil) diff --git a/worker/imap/flags.go b/worker/imap/flags.go index 60137bd3..31d3bea3 100644 --- a/worker/imap/flags.go +++ b/worker/imap/flags.go @@ -81,7 +81,7 @@ func (imapw *IMAPWorker) handleAnsweredMessages(msg *types.AnsweredMessages) { Message: types.RespondTo(msg), Info: &models.MessageInfo{ Flags: translateImapFlags(_msg.Flags), - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) return nil @@ -100,7 +100,7 @@ func (imapw *IMAPWorker) handleFlagMessages(msg *types.FlagMessages) { Message: types.RespondTo(msg), Info: &models.MessageInfo{ Flags: translateImapFlags(_msg.Flags), - Uid: _msg.Uid, + Uid: models.Uint32ToUid(_msg.Uid), }, }, nil) return nil @@ -108,7 +108,7 @@ func (imapw *IMAPWorker) handleFlagMessages(msg *types.FlagMessages) { } func (imapw *IMAPWorker) handleStoreOps( - msg types.WorkerMessage, uids []uint32, item imap.StoreItem, flag interface{}, + msg types.WorkerMessage, uids []models.UID, item imap.StoreItem, flag interface{}, procFunc func(*imap.Message) error, ) { messages := make(chan *imap.Message) diff --git a/worker/imap/imap.go b/worker/imap/imap.go index fbdcb2b9..67d56264 100644 --- a/worker/imap/imap.go +++ b/worker/imap/imap.go @@ -14,12 +14,12 @@ func init() { imap.CharsetReader = charset.Reader } -func toSeqSet(uids []uint32) *imap.SeqSet { - var set imap.SeqSet +func toSeqSet(uids []models.UID) *imap.SeqSet { + set := new(imap.SeqSet) for _, uid := range uids { - set.AddNum(uid) + set.AddNum(models.UidToUint32(uid)) } - return &set + return set } func translateBodyStructure(bs *imap.BodyStructure) *models.BodyStructure { diff --git a/worker/imap/list.go b/worker/imap/list.go index d12ae4bd..e3c9db3e 100644 --- a/worker/imap/list.go +++ b/worker/imap/list.go @@ -139,6 +139,6 @@ func (imapw *IMAPWorker) handleSearchDirectory(msg *types.SearchDirectory) { imapw.worker.PostMessage(&types.SearchResults{ Message: types.RespondTo(msg), - Uids: uids, + Uids: models.Uint32ToUidList(uids), }, nil) } diff --git a/worker/imap/open.go b/worker/imap/open.go index 355709a7..b1314a4b 100644 --- a/worker/imap/open.go +++ b/worker/imap/open.go @@ -5,6 +5,7 @@ import ( sortthread "github.com/emersion/go-imap-sortthread" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) @@ -85,9 +86,10 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents( // Only initialize if we are not filtering imapw.seqMap.Initialize(uids) } + imapw.worker.PostMessage(&types.DirectoryContents{ Message: types.RespondTo(msg), - Uids: uids, + Uids: models.Uint32ToUidList(uids), }, nil) imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil) } @@ -146,7 +148,7 @@ func (imapw *IMAPWorker) handleDirectoryThreaded( var uids []uint32 for i := len(aercThreads) - 1; i >= 0; i-- { aercThreads[i].Walk(func(t *types.Thread, level int, currentErr error) error { //nolint:errcheck // error indicates skipped threads - uids = append(uids, t.Uid) + uids = append(uids, models.UidToUint32(t.Uid)) return nil }) } @@ -175,7 +177,7 @@ func convertThreads(threads []*sortthread.Thread, parent *types.Thread) ([]*type for i := 0; i < len(threads); i++ { t := threads[i] conv[i] = &types.Thread{ - Uid: t.Id, + Uid: models.Uint32ToUid(t.Id), } // Set the first child node diff --git a/worker/imap/worker.go b/worker/imap/worker.go index edb68ec4..28eb907c 100644 --- a/worker/imap/worker.go +++ b/worker/imap/worker.go @@ -289,7 +289,7 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) { Envelope: translateEnvelope(msg.Envelope), Flags: translateImapFlags(msg.Flags), InternalDate: msg.InternalDate, - Uid: msg.Uid, + Uid: models.Uint32ToUid(msg.Uid), }, }, nil) case *client.ExpungeUpdate: @@ -297,7 +297,7 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) { w.worker.Errorf("ExpungeUpdate unknown seqnum: %d", update.SeqNum) } else { w.worker.PostMessage(&types.MessagesDeleted{ - Uids: []uint32{uid}, + Uids: []models.UID{models.Uint32ToUid(uid)}, }, nil) } } diff --git a/worker/jmap/directories.go b/worker/jmap/directories.go index 07bb0762..7ae79ead 100644 --- a/worker/jmap/directories.go +++ b/worker/jmap/directories.go @@ -186,9 +186,9 @@ func (w *JMAPWorker) handleFetchDirectoryContents(msg *types.FetchDirectoryConte } } - uids := make([]uint32, 0, len(contents.MessageIDs)) + uids := make([]models.UID, 0, len(contents.MessageIDs)) for _, id := range contents.MessageIDs { - uids = append(uids, w.uidStore.GetOrInsert(string(id))) + uids = append(uids, models.UID(id)) } w.w.PostMessage(&types.DirectoryContents{ Message: types.RespondTo(msg), @@ -214,9 +214,9 @@ func (w *JMAPWorker) handleSearchDirectory(msg *types.SearchDirectory) error { for _, inv := range resp.Responses { switch r := inv.Args.(type) { case *email.QueryResponse: - var uids []uint32 + var uids []models.UID for _, id := range r.IDs { - uids = append(uids, w.uidStore.GetOrInsert(string(id))) + uids = append(uids, models.UID(id)) } w.w.PostMessage(&types.SearchResults{ Message: types.RespondTo(msg), diff --git a/worker/jmap/fetch.go b/worker/jmap/fetch.go index 07579b99..3b3a8460 100644 --- a/worker/jmap/fetch.go +++ b/worker/jmap/fetch.go @@ -38,11 +38,7 @@ func (w *JMAPWorker) handleFetchMessageHeaders(msg *types.FetchMessageHeaders) e emailIdsToFetch := make([]jmap.ID, 0, len(msg.Uids)) currentEmails := make([]*email.Email, 0, len(msg.Uids)) for _, uid := range msg.Uids { - id, ok := w.uidStore.GetKey(uid) - if !ok { - return fmt.Errorf("bug: no jmap id for message uid: %v", uid) - } - jid := jmap.ID(id) + jid := jmap.ID(uid) m, err := w.cache.GetEmail(jid) if err != nil { // Message wasn't in cache; fetch it @@ -103,13 +99,9 @@ func (w *JMAPWorker) handleFetchMessageHeaders(msg *types.FetchMessageHeaders) e } func (w *JMAPWorker) handleFetchMessageBodyPart(msg *types.FetchMessageBodyPart) error { - id, ok := w.uidStore.GetKey(msg.Uid) - if !ok { - return fmt.Errorf("bug: unknown message uid %d", msg.Uid) - } - mail, err := w.cache.GetEmail(jmap.ID(id)) + mail, err := w.cache.GetEmail(jmap.ID(msg.Uid)) if err != nil { - return fmt.Errorf("bug: unknown message id %s: %w", id, err) + return fmt.Errorf("bug: unknown message id %s: %w", msg.Uid, err) } part := mail.BodyStructure @@ -159,13 +151,9 @@ func (w *JMAPWorker) handleFetchMessageBodyPart(msg *types.FetchMessageBodyPart) func (w *JMAPWorker) handleFetchFullMessages(msg *types.FetchFullMessages) error { for _, uid := range msg.Uids { - id, ok := w.uidStore.GetKey(uid) - if !ok { - return fmt.Errorf("bug: unknown message uid %d", uid) - } - mail, err := w.cache.GetEmail(jmap.ID(id)) + mail, err := w.cache.GetEmail(jmap.ID(uid)) if err != nil { - return fmt.Errorf("bug: unknown message id %s: %w", id, err) + return fmt.Errorf("bug: unknown message id %s: %w", uid, err) } buf, err := w.cache.GetBlob(mail.BlobID) if err != nil { diff --git a/worker/jmap/jmap.go b/worker/jmap/jmap.go index bb0f75f9..7320ec04 100644 --- a/worker/jmap/jmap.go +++ b/worker/jmap/jmap.go @@ -37,7 +37,7 @@ func (w *JMAPWorker) translateMsgInfo(m *email.Email) *models.MessageInfo { return &models.MessageInfo{ Envelope: env, Flags: keywordsToFlags(m.Keywords), - Uid: w.uidStore.GetOrInsert(string(m.ID)), + Uid: models.UID(m.ID), BodyStructure: translateBodyStructure(m.BodyStructure), RFC822Headers: translateJMAPHeader(m.Headers), Refs: m.References, diff --git a/worker/jmap/push.go b/worker/jmap/push.go index bc90dd77..f2a8ad45 100644 --- a/worker/jmap/push.go +++ b/worker/jmap/push.go @@ -247,10 +247,9 @@ func (w *JMAPWorker) refresh(newState jmap.TypeState) error { } if w.selectedMbox == mboxId { - uids := make([]uint32, 0, len(ids)) + uids := make([]models.UID, 0, len(ids)) for _, id := range ids { - uid := w.uidStore.GetOrInsert(string(id)) - uids = append(uids, uid) + uids = append(uids, models.UID(id)) } w.w.PostMessage(&types.DirectoryContents{ Uids: uids, diff --git a/worker/jmap/set.go b/worker/jmap/set.go index 36b23688..302314c1 100644 --- a/worker/jmap/set.go +++ b/worker/jmap/set.go @@ -10,15 +10,11 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail/mailbox" ) -func (w *JMAPWorker) updateFlags(uids []uint32, flags models.Flags, enable bool) error { +func (w *JMAPWorker) updateFlags(uids []models.UID, flags models.Flags, enable bool) error { var req jmap.Request patches := make(map[jmap.ID]jmap.Patch) for _, uid := range uids { - id, ok := w.uidStore.GetKey(uid) - if !ok { - return fmt.Errorf("bug: unknown uid %d", uid) - } patch := jmap.Patch{} for kw := range flagsToKeywords(flags) { path := fmt.Sprintf("keywords/%s", kw) @@ -28,7 +24,7 @@ func (w *JMAPWorker) updateFlags(uids []uint32, flags models.Flags, enable bool) patch[path] = nil } } - patches[jmap.ID(id)] = patch + patches[jmap.ID(uid)] = patch } req.Invoke(&email.Set{ @@ -44,7 +40,7 @@ func (w *JMAPWorker) updateFlags(uids []uint32, flags models.Flags, enable bool) return checkNotUpdated(resp) } -func (w *JMAPWorker) moveCopy(uids []uint32, destDir string, deleteSrc bool) error { +func (w *JMAPWorker) moveCopy(uids []models.UID, destDir string, deleteSrc bool) error { var req jmap.Request var destMbox jmap.ID var destroy []jmap.ID @@ -62,13 +58,9 @@ func (w *JMAPWorker) moveCopy(uids []uint32, destDir string, deleteSrc bool) err for _, uid := range uids { dest := destMbox - id, ok := w.uidStore.GetKey(uid) - if !ok { - return fmt.Errorf("bug: unknown uid %d", uid) - } - mail, err := w.cache.GetEmail(jmap.ID(id)) + mail, err := w.cache.GetEmail(jmap.ID(uid)) if err != nil { - return fmt.Errorf("bug: unknown message id %s: %w", id, err) + return fmt.Errorf("bug: unknown message id %s: %w", uid, err) } patch := w.moveCopyPatch(mail, dest, deleteSrc) @@ -76,7 +68,7 @@ func (w *JMAPWorker) moveCopy(uids []uint32, destDir string, deleteSrc bool) err destroy = append(destroy, mail.ID) w.w.Debugf("destroying <%s>", mail.MessageID[0]) } else { - patches[jmap.ID(id)] = patch + patches[jmap.ID(uid)] = patch } } @@ -161,11 +153,7 @@ func (w *JMAPWorker) handleModifyLabels(msg *types.ModifyLabels) error { patches := make(map[jmap.ID]jmap.Patch) for _, uid := range msg.Uids { - id, ok := w.uidStore.GetKey(uid) - if !ok { - return fmt.Errorf("bug: unknown uid %d", uid) - } - patches[jmap.ID(id)] = patch + patches[jmap.ID(uid)] = patch } req.Invoke(&email.Set{ diff --git a/worker/jmap/worker.go b/worker/jmap/worker.go index 67553272..58883e1e 100644 --- a/worker/jmap/worker.go +++ b/worker/jmap/worker.go @@ -6,7 +6,6 @@ import ( "time" "git.sr.ht/~rjarry/aerc/config" - "git.sr.ht/~rjarry/aerc/lib/uidstore" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/handlers" "git.sr.ht/~rjarry/aerc/worker/jmap/cache" @@ -47,7 +46,6 @@ type JMAPWorker struct { mbox2dir map[jmap.ID]string roles map[mailbox.Role]jmap.ID identities map[string]*identity.Identity - uidStore *uidstore.Store changes chan jmap.TypeState stop chan struct{} @@ -56,7 +54,6 @@ type JMAPWorker struct { func NewJMAPWorker(worker *types.Worker) (types.Backend, error) { return &JMAPWorker{ w: worker, - uidStore: uidstore.NewStore(), roles: make(map[mailbox.Role]jmap.ID), dir2mbox: make(map[string]jmap.ID), mbox2dir: make(map[jmap.ID]string), diff --git a/worker/lib/search.go b/worker/lib/search.go index d44e2ce1..b98e2bbb 100644 --- a/worker/lib/search.go +++ b/worker/lib/search.go @@ -13,11 +13,11 @@ import ( "git.sr.ht/~rjarry/go-opt" ) -func Search(messages []rfc822.RawMessage, criteria *types.SearchCriteria) ([]uint32, error) { +func Search(messages []rfc822.RawMessage, criteria *types.SearchCriteria) ([]models.UID, error) { criteria.PrepareHeader() requiredParts := GetRequiredParts(criteria) - matchedUids := []uint32{} + var matchedUids []models.UID for _, m := range messages { success, err := SearchMessage(m, criteria, requiredParts) if err != nil { diff --git a/worker/lib/sort.go b/worker/lib/sort.go index 7af2c6fa..70a64c7b 100644 --- a/worker/lib/sort.go +++ b/worker/lib/sort.go @@ -11,7 +11,7 @@ import ( func Sort(messageInfos []*models.MessageInfo, criteria []*types.SortCriterion, -) ([]uint32, error) { +) ([]models.UID, 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] @@ -56,7 +56,7 @@ func Sort(messageInfos []*models.MessageInfo, }) } } - var uids []uint32 + var uids []models.UID // copy in reverse as msgList displays backwards for i := len(messageInfos) - 1; i >= 0; i-- { uids = append(uids, messageInfos[i].Uid) diff --git a/worker/maildir/container.go b/worker/maildir/container.go index dea1ded6..c23825da 100644 --- a/worker/maildir/container.go +++ b/worker/maildir/container.go @@ -9,7 +9,7 @@ import ( "github.com/emersion/go-maildir" "git.sr.ht/~rjarry/aerc/lib/log" - "git.sr.ht/~rjarry/aerc/lib/uidstore" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/lib" ) @@ -17,8 +17,7 @@ import ( // the Maildir spec type Container struct { Store *lib.MaildirStore - uids *uidstore.Store - recentUIDS map[uint32]struct{} // used to set the recent flag + recentUIDS map[models.UID]struct{} // used to set the recent flag } // NewContainer creates a new container at the specified directory @@ -28,8 +27,8 @@ func NewContainer(dir string, maildirpp bool) (*Container, error) { return nil, err } return &Container{ - Store: store, uids: uidstore.NewStore(), - recentUIDS: make(map[uint32]struct{}), + Store: store, + recentUIDS: make(map[models.UID]struct{}), }, nil } @@ -40,8 +39,7 @@ func (c *Container) SyncNewMail(dir maildir.Dir) error { return err } for _, key := range keys { - uid := c.uids.GetOrInsert(key) - c.recentUIDS[uid] = struct{}{} + c.recentUIDS[models.UID(key)] = struct{}{} } return nil } @@ -57,18 +55,18 @@ func (c *Container) OpenDirectory(name string) (maildir.Dir, error) { } // IsRecent returns if a uid has the Recent flag set -func (c *Container) IsRecent(uid uint32) bool { +func (c *Container) IsRecent(uid models.UID) bool { _, ok := c.recentUIDS[uid] return ok } // ClearRecentFlag removes the Recent flag from the message with the given uid -func (c *Container) ClearRecentFlag(uid uint32) { +func (c *Container) ClearRecentFlag(uid models.UID) { delete(c.recentUIDS, uid) } // UIDs fetches the unique message identifiers for the maildir -func (c *Container) UIDs(d maildir.Dir) ([]uint32, error) { +func (c *Container) UIDs(d maildir.Dir) ([]models.UID, error) { keys, err := d.Keys() if err != nil && len(keys) == 0 { return nil, fmt.Errorf("could not get keys for %s: %w", d, err) @@ -77,39 +75,26 @@ func (c *Container) UIDs(d maildir.Dir) ([]uint32, error) { log.Errorf("could not get all keys for %s: %s", d, err.Error()) } sort.Strings(keys) - var uids []uint32 + var uids []models.UID for _, key := range keys { - uids = append(uids, c.uids.GetOrInsert(key)) + uids = append(uids, models.UID(key)) } return uids, err } // Message returns a Message struct for the given UID and maildir -func (c *Container) Message(d maildir.Dir, uid uint32) (*Message, error) { - if key, ok := c.uids.GetKey(uid); ok { - return &Message{ - dir: d, - uid: uid, - key: key, - }, nil - } - return nil, fmt.Errorf("could not find message with uid %d in maildir %s", - uid, d) -} - -func (c *Container) MessageFromKey(d maildir.Dir, key string) *Message { - uid := c.uids.GetOrInsert(key) +func (c *Container) Message(d maildir.Dir, uid models.UID) (*Message, error) { return &Message{ dir: d, uid: uid, - key: key, - } + key: string(uid), + }, nil } // DeleteAll deletes a set of messages by UID and returns the subset of UIDs // which were successfully deleted, stopping upon the first error. -func (c *Container) DeleteAll(d maildir.Dir, uids []uint32) ([]uint32, error) { - var success []uint32 +func (c *Container) DeleteAll(d maildir.Dir, uids []models.UID) ([]models.UID, error) { + var success []models.UID for _, uid := range uids { msg, err := c.Message(d, uid) if err != nil { @@ -124,46 +109,38 @@ func (c *Container) DeleteAll(d maildir.Dir, uids []uint32) ([]uint32, error) { } func (c *Container) CopyAll( - dest maildir.Dir, src maildir.Dir, uids []uint32, + dest maildir.Dir, src maildir.Dir, uids []models.UID, ) error { for _, uid := range uids { if err := c.copyMessage(dest, src, uid); err != nil { - return fmt.Errorf("could not copy message %d: %w", uid, err) + return fmt.Errorf("could not copy message %s: %w", uid, err) } } return nil } func (c *Container) copyMessage( - dest maildir.Dir, src maildir.Dir, uid uint32, + dest maildir.Dir, src maildir.Dir, uid models.UID, ) error { - key, ok := c.uids.GetKey(uid) - if !ok { - return fmt.Errorf("could not find key for message id %d", uid) - } - _, err := src.Copy(dest, key) + _, err := src.Copy(dest, string(uid)) return err } -func (c *Container) MoveAll(dest maildir.Dir, src maildir.Dir, uids []uint32) ([]uint32, error) { - var success []uint32 +func (c *Container) MoveAll(dest maildir.Dir, src maildir.Dir, uids []models.UID) ([]models.UID, error) { + var success []models.UID for _, uid := range uids { if err := c.moveMessage(dest, src, uid); err != nil { - return success, fmt.Errorf("could not move message %d: %w", uid, err) + return success, fmt.Errorf("could not move message %s: %w", uid, err) } success = append(success, uid) } return success, nil } -func (c *Container) moveMessage(dest maildir.Dir, src maildir.Dir, uid uint32) error { - key, ok := c.uids.GetKey(uid) - if !ok { - return fmt.Errorf("could not find key for message id %d", uid) - } - path, err := src.Filename(key) +func (c *Container) moveMessage(dest maildir.Dir, src maildir.Dir, uid models.UID) error { + path, err := src.Filename(string(uid)) if err != nil { - return fmt.Errorf("could not find path for message id %d", uid) + return fmt.Errorf("could not find path for message id %s: %w", uid, err) } // Remove encoded UID information from the key to prevent sync issues name := lib.StripUIDFromMessageFilename(filepath.Base(path)) diff --git a/worker/maildir/message.go b/worker/maildir/message.go index 1d8d26b9..14c83721 100644 --- a/worker/maildir/message.go +++ b/worker/maildir/message.go @@ -15,7 +15,7 @@ import ( // A Message is an individual email inside of a maildir.Dir. type Message struct { dir maildir.Dir - uid uint32 + uid models.UID key string } @@ -135,7 +135,7 @@ func (m Message) NewBodyPartReader(requestedParts []int) (io.Reader, error) { return rfc822.FetchEntityPartReader(msg, requestedParts) } -func (m Message) UID() uint32 { +func (m Message) UID() models.UID { return m.uid } diff --git a/worker/maildir/search.go b/worker/maildir/search.go index 90c84087..de2d0530 100644 --- a/worker/maildir/search.go +++ b/worker/maildir/search.go @@ -6,11 +6,12 @@ import ( "sync" "git.sr.ht/~rjarry/aerc/lib/log" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/lib" "git.sr.ht/~rjarry/aerc/worker/types" ) -func (w *Worker) search(ctx context.Context, criteria *types.SearchCriteria) ([]uint32, error) { +func (w *Worker) search(ctx context.Context, criteria *types.SearchCriteria) ([]models.UID, error) { criteria.PrepareHeader() requiredParts := lib.GetRequiredParts(criteria) w.worker.Debugf("Required parts bitmask for search: %b", requiredParts) @@ -20,7 +21,7 @@ func (w *Worker) search(ctx context.Context, criteria *types.SearchCriteria) ([] return nil, err } - matchedUids := []uint32{} + var matchedUids []models.UID mu := sync.Mutex{} wg := sync.WaitGroup{} // Hard limit at 2x CPU cores @@ -33,7 +34,7 @@ func (w *Worker) search(ctx context.Context, criteria *types.SearchCriteria) ([] default: limit <- struct{}{} wg.Add(1) - go func(key uint32) { + go func(key models.UID) { defer log.PanicHandler() defer wg.Done() success, err := w.searchKey(key, criteria, requiredParts) @@ -55,7 +56,7 @@ func (w *Worker) search(ctx context.Context, criteria *types.SearchCriteria) ([] } // Execute the search criteria for the given key, returns true if search succeeded -func (w *Worker) searchKey(key uint32, criteria *types.SearchCriteria, +func (w *Worker) searchKey(key models.UID, criteria *types.SearchCriteria, parts lib.MsgParts, ) (bool, error) { message, err := w.c.Message(*w.selected, key) diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go index 2c5bc893..74efdc3c 100644 --- a/worker/maildir/worker.go +++ b/worker/maildir/worker.go @@ -460,7 +460,7 @@ func (w *Worker) handleFetchDirectoryContents( msg *types.FetchDirectoryContents, ) error { var ( - uids []uint32 + uids []models.UID err error ) if msg.Filter != nil { @@ -494,7 +494,7 @@ func (w *Worker) handleFetchDirectoryContents( return nil } -func (w *Worker) sort(ctx context.Context, uids []uint32, criteria []*types.SortCriterion) ([]uint32, error) { +func (w *Worker) sort(ctx context.Context, uids []models.UID, criteria []*types.SortCriterion) ([]models.UID, error) { if len(criteria) == 0 { // At least sort by uid, parallel searching can create random // order @@ -516,7 +516,7 @@ func (w *Worker) sort(ctx context.Context, uids []uint32, criteria []*types.Sort default: limit <- struct{}{} wg.Add(1) - go func(uid uint32) { + go func(uid models.UID) { defer log.PanicHandler() defer wg.Done() info, err := w.msgHeadersFromUid(uid) @@ -546,7 +546,7 @@ func (w *Worker) handleFetchDirectoryThreaded( msg *types.FetchDirectoryThreaded, ) error { var ( - uids []uint32 + uids []models.UID err error ) if msg.Filter != nil { @@ -574,7 +574,7 @@ func (w *Worker) handleFetchDirectoryThreaded( return nil } -func (w *Worker) threads(ctx context.Context, uids []uint32, +func (w *Worker) threads(ctx context.Context, uids []models.UID, criteria []*types.SortCriterion, ) ([]*types.Thread, error) { builder := aercLib.NewThreadBuilder(iterator.NewFactory(false), false) @@ -590,7 +590,7 @@ func (w *Worker) threads(ctx context.Context, uids []uint32, default: limit <- struct{}{} wg.Add(1) - go func(uid uint32) { + go func(uid models.UID) { defer log.PanicHandler() defer wg.Done() info, err := w.msgHeadersFromUid(uid) @@ -903,7 +903,7 @@ func (w *Worker) handleSearchDirectory(msg *types.SearchDirectory) error { return nil } -func (w *Worker) msgInfoFromUid(uid uint32) (*models.MessageInfo, error) { +func (w *Worker) msgInfoFromUid(uid models.UID) (*models.MessageInfo, error) { m, err := w.c.Message(*w.selected, uid) if err != nil { return nil, err @@ -923,7 +923,7 @@ func (w *Worker) msgInfoFromUid(uid uint32) (*models.MessageInfo, error) { return info, nil } -func (w *Worker) msgHeadersFromUid(uid uint32) (*models.MessageInfo, error) { +func (w *Worker) msgHeadersFromUid(uid models.UID) (*models.MessageInfo, error) { m, err := w.c.Message(*w.selected, uid) if err != nil { return nil, err diff --git a/worker/mbox/io.go b/worker/mbox/io.go index f5fbc596..22d0d023 100644 --- a/worker/mbox/io.go +++ b/worker/mbox/io.go @@ -12,7 +12,6 @@ import ( func Read(r io.Reader) ([]rfc822.RawMessage, error) { mbr := mbox.NewReader(r) - uid := uint32(0) messages := make([]rfc822.RawMessage, 0) for { msg, err := mbr.NextMessage() @@ -28,10 +27,10 @@ func Read(r io.Reader) ([]rfc822.RawMessage, error) { } messages = append(messages, &message{ - uid: uid, flags: models.SeenFlag, content: content, + uid: uidFromContents(content), + flags: models.SeenFlag, + content: content, }) - - uid++ } return messages, nil } diff --git a/worker/mbox/models.go b/worker/mbox/models.go index 5acd6f5a..ebfe5d32 100644 --- a/worker/mbox/models.go +++ b/worker/mbox/models.go @@ -2,6 +2,8 @@ package mboxer import ( "bytes" + "crypto/sha256" + "encoding/hex" "fmt" "io" @@ -49,7 +51,7 @@ func (md *mailboxContainer) DirectoryInfo(file string) *models.DirectoryInfo { } } -func (md *mailboxContainer) Copy(dest, src string, uids []uint32) error { +func (md *mailboxContainer) Copy(dest, src string, uids []models.UID) error { srcmbox, ok := md.Mailbox(src) if !ok { return fmt.Errorf("source %s not found", src) @@ -69,15 +71,15 @@ func (md *mailboxContainer) Copy(dest, src string, uids []uint32) error { if found { msg, err := srcmbox.Message(uidSrc) if err != nil { - return fmt.Errorf("could not get message with uid %d from folder %s", uidSrc, src) + return fmt.Errorf("could not get message with uid %s from folder %s", uidSrc, src) } r, err := msg.NewReader() if err != nil { - return fmt.Errorf("could not get reader for message with uid %d", uidSrc) + return fmt.Errorf("could not get reader for message with uid %s", uidSrc) } flags, err := msg.ModelFlags() if err != nil { - return fmt.Errorf("could not get flags for message with uid %d", uidSrc) + return fmt.Errorf("could not get flags for message with uid %s", uidSrc) } err = destmbox.Append(r, flags) if err != nil { @@ -94,24 +96,24 @@ type container struct { messages []rfc822.RawMessage } -func (f *container) Uids() []uint32 { - uids := make([]uint32, len(f.messages)) +func (f *container) Uids() []models.UID { + uids := make([]models.UID, len(f.messages)) for i, m := range f.messages { uids[i] = m.UID() } return uids } -func (f *container) Message(uid uint32) (rfc822.RawMessage, error) { +func (f *container) Message(uid models.UID) (rfc822.RawMessage, error) { for _, m := range f.messages { if uid == m.UID() { return m, nil } } - return &message{}, fmt.Errorf("uid [%d] not found", uid) + return &message{}, fmt.Errorf("uid [%s] not found", uid) } -func (f *container) Delete(uids []uint32) (deleted []uint32) { +func (f *container) Delete(uids []models.UID) (deleted []models.UID) { newMessages := make([]rfc822.RawMessage, 0) for _, m := range f.messages { del := false @@ -131,32 +133,28 @@ func (f *container) Delete(uids []uint32) (deleted []uint32) { return } -func (f *container) newUid() (next uint32) { - for _, m := range f.messages { - if uid := m.UID(); uid > next { - next = uid - } - } - next++ - return -} - func (f *container) Append(r io.Reader, flags models.Flags) error { data, err := io.ReadAll(r) if err != nil { return err } f.messages = append(f.messages, &message{ - uid: f.newUid(), + uid: uidFromContents(data), flags: flags, content: data, }) return nil } +func uidFromContents(data []byte) models.UID { + sum := sha256.New() + sum.Write(data) + return models.UID(hex.EncodeToString(sum.Sum(nil))) +} + // message implements the lib.RawMessage interface type message struct { - uid uint32 + uid models.UID flags models.Flags content []byte } @@ -173,7 +171,7 @@ func (m *message) Labels() ([]string, error) { return nil, nil } -func (m *message) UID() uint32 { +func (m *message) UID() models.UID { return m.uid } diff --git a/worker/mbox/worker.go b/worker/mbox/worker.go index 1f4a4965..72a44368 100644 --- a/worker/mbox/worker.go +++ b/worker/mbox/worker.go @@ -405,7 +405,7 @@ func (w *mboxWorker) PathSeparator() string { return "/" } -func filterUids(folder *container, uids []uint32, criteria *types.SearchCriteria) ([]uint32, error) { +func filterUids(folder *container, uids []models.UID, criteria *types.SearchCriteria) ([]models.UID, error) { log.Debugf("Search with parsed criteria: %#v", criteria) m := make([]rfc822.RawMessage, 0, len(uids)) for _, uid := range uids { @@ -419,9 +419,9 @@ func filterUids(folder *container, uids []uint32, criteria *types.SearchCriteria return lib.Search(m, criteria) } -func sortUids(folder *container, uids []uint32, +func sortUids(folder *container, uids []models.UID, criteria []*types.SortCriterion, -) ([]uint32, error) { +) ([]models.UID, error) { var infos []*models.MessageInfo needSize := false for _, item := range criteria { diff --git a/worker/notmuch/lib/database.go b/worker/notmuch/lib/database.go index 9a8a9a60..2915605d 100644 --- a/worker/notmuch/lib/database.go +++ b/worker/notmuch/lib/database.go @@ -10,7 +10,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/log" "git.sr.ht/~rjarry/aerc/lib/notmuch" - "git.sr.ht/~rjarry/aerc/lib/uidstore" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) @@ -18,7 +18,6 @@ type DB struct { path string excludedTags []string db *notmuch.Database - uidStore *uidstore.Store } func NewDB(path string, excludedTags []string) *DB { @@ -28,7 +27,6 @@ func NewDB(path string, excludedTags []string) *DB { db := &DB{ path: path, excludedTags: excludedTags, - uidStore: uidstore.NewStore(), db: nm, } return db @@ -312,14 +310,6 @@ func (db *DB) MsgModifyTags(key string, add, remove []string) error { return msg.SyncTagsToMaildirFlags() } -func (db *DB) UidFromKey(key string) uint32 { - return db.uidStore.GetOrInsert(key) -} - -func (db *DB) KeyFromUid(uid uint32) (string, bool) { - return db.uidStore.GetKey(uid) -} - func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages, threadContext bool) []*types.Thread { var siblings []*types.Thread for msgs.Next() { @@ -338,7 +328,7 @@ func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages, threadCon continue } node := &types.Thread{ - Uid: db.uidStore.GetOrInsert(msgID), + Uid: models.UID(msgID), Parent: parent, } switch threadContext { diff --git a/worker/notmuch/message.go b/worker/notmuch/message.go index 539d85ae..81a4da54 100644 --- a/worker/notmuch/message.go +++ b/worker/notmuch/message.go @@ -21,7 +21,7 @@ import ( ) type Message struct { - uid uint32 + uid models.UID key string db *notmuch.DB } @@ -152,7 +152,7 @@ func (m *Message) ModelFlags() (models.Flags, error) { return flags, nil } -func (m *Message) UID() uint32 { +func (m *Message) UID() models.UID { return m.uid } diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go index 06c0cde6..8c954a61 100644 --- a/worker/notmuch/worker.go +++ b/worker/notmuch/worker.go @@ -457,27 +457,21 @@ func (w *worker) handleFetchMessageHeaders( return nil } -func (w *worker) uidsFromQuery(ctx context.Context, query string) ([]uint32, error) { +func (w *worker) uidsFromQuery(ctx context.Context, query string) ([]models.UID, error) { msgIDs, err := w.db.MsgIDsFromQuery(ctx, query) if err != nil { return nil, err } - var uids []uint32 + var uids []models.UID for _, id := range msgIDs { - uid := w.db.UidFromKey(id) - uids = append(uids, uid) - + uids = append(uids, models.UID(id)) } return uids, nil } -func (w *worker) msgFromUid(uid uint32) (*Message, error) { - key, ok := w.db.KeyFromUid(uid) - if !ok { - return nil, fmt.Errorf("Invalid uid: %v", uid) - } +func (w *worker) msgFromUid(uid models.UID) (*Message, error) { msg := &Message{ - key: key, + key: string(uid), uid: uid, db: w.db, } @@ -613,7 +607,7 @@ func (w *worker) handleModifyLabels(msg *types.ModifyLabels) error { for _, uid := range msg.Uids { m, err := w.msgFromUid(uid) if err != nil { - return fmt.Errorf("could not get message from uid %d: %w", uid, err) + return fmt.Errorf("could not get message from uid %s: %w", uid, err) } err = m.ModifyTags(msg.Add, msg.Remove) if err != nil { @@ -699,7 +693,7 @@ func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error { return nil } -func (w *worker) emitMessageInfoError(msg types.WorkerMessage, uid uint32, err error) { +func (w *worker) emitMessageInfoError(msg types.WorkerMessage, uid models.UID, err error) { w.w.PostMessage(&types.MessageInfo{ Info: &models.MessageInfo{ Envelope: &models.Envelope{}, @@ -743,9 +737,9 @@ func (w *worker) emitLabelList() { w.w.PostMessage(&types.LabelList{Labels: tags}, nil) } -func (w *worker) sort(uids []uint32, +func (w *worker) sort(uids []models.UID, criteria []*types.SortCriterion, -) ([]uint32, error) { +) ([]models.UID, error) { if len(criteria) == 0 { return uids, nil } @@ -796,7 +790,7 @@ func (w *worker) handleDeleteMessages(msg *types.DeleteMessages) error { return errUnsupported } - var deleted []uint32 + var deleted []models.UID folders, _ := w.store.FolderMap() curDir := folders[w.currentQueryName] @@ -874,7 +868,7 @@ func (w *worker) handleMoveMessages(msg *types.MoveMessages) error { return errUnsupported } - var moved []uint32 + var moved []models.UID folders, _ := w.store.FolderMap() diff --git a/worker/types/messages.go b/worker/types/messages.go index b91adbb7..0d0a187a 100644 --- a/worker/types/messages.go +++ b/worker/types/messages.go @@ -146,29 +146,29 @@ type RemoveDirectory struct { type FetchMessageHeaders struct { Message Context context.Context - Uids []uint32 + Uids []models.UID } type FetchFullMessages struct { Message - Uids []uint32 + Uids []models.UID } type FetchMessageBodyPart struct { Message - Uid uint32 + Uid models.UID Part []int } type FetchMessageFlags struct { Message Context context.Context - Uids []uint32 + Uids []models.UID } type DeleteMessages struct { Message - Uids []uint32 + Uids []models.UID MultiFileStrategy *MultiFileStrategy } @@ -177,32 +177,32 @@ type FlagMessages struct { Message Enable bool Flags models.Flags - Uids []uint32 + Uids []models.UID } type AnsweredMessages struct { Message Answered bool - Uids []uint32 + Uids []models.UID } type ForwardedMessages struct { Message Forwarded bool - Uids []uint32 + Uids []models.UID } type CopyMessages struct { Message Destination string - Uids []uint32 + Uids []models.UID MultiFileStrategy *MultiFileStrategy } type MoveMessages struct { Message Destination string - Uids []uint32 + Uids []models.UID MultiFileStrategy *MultiFileStrategy } @@ -244,12 +244,12 @@ type DirectoryInfo struct { type DirectoryContents struct { Message - Uids []uint32 + Uids []models.UID } type SearchResults struct { Message - Uids []uint32 + Uids []models.UID } type MessageInfo struct { @@ -270,24 +270,24 @@ type MessageBodyPart struct { type MessagesDeleted struct { Message - Uids []uint32 + Uids []models.UID } type MessagesCopied struct { Message Destination string - Uids []uint32 + Uids []models.UID } type MessagesMoved struct { Message Destination string - Uids []uint32 + Uids []models.UID } type ModifyLabels struct { Message - Uids []uint32 + Uids []models.UID Add []string Remove []string } diff --git a/worker/types/thread.go b/worker/types/thread.go index 42565964..fe6c56bf 100644 --- a/worker/types/thread.go +++ b/worker/types/thread.go @@ -6,10 +6,11 @@ import ( "sort" "git.sr.ht/~rjarry/aerc/lib/log" + "git.sr.ht/~rjarry/aerc/models" ) type Thread struct { - Uid uint32 + Uid models.UID Parent *Thread PrevSibling *Thread NextSibling *Thread @@ -77,11 +78,11 @@ func (t *Thread) Root() *Thread { } // Uids returns all associated uids for the given thread and its children -func (t *Thread) Uids() []uint32 { +func (t *Thread) Uids() []models.UID { if t == nil { return nil } - uids := make([]uint32, 0) + uids := make([]models.UID, 0) err := t.Walk(func(node *Thread, _ int, _ error) error { uids = append(uids, node.Uid) return nil @@ -96,20 +97,20 @@ func (t *Thread) String() string { if t == nil { return "<nil>" } - parent := -1 + var parent models.UID if t.Parent != nil { - parent = int(t.Parent.Uid) + parent = t.Parent.Uid } - next := -1 + var next models.UID if t.NextSibling != nil { - next = int(t.NextSibling.Uid) + next = t.NextSibling.Uid } - child := -1 + var child models.UID if t.FirstChild != nil { - child = int(t.FirstChild.Uid) + child = t.FirstChild.Uid } return fmt.Sprintf( - "[%d] (parent:%v, next:%v, child:%v)", + "[%s] (parent:%s, next:%s, child:%s)", t.Uid, parent, next, child, ) } @@ -141,9 +142,9 @@ type NewThreadWalkFn func(t *Thread, level int, currentErr error) error // Implement interface to be able to sort threads by newest (max UID) type ByUID []*Thread -func getMaxUID(thread *Thread) uint32 { +func getMaxUID(thread *Thread) models.UID { // TODO: should we make this part of the Thread type to avoid recomputation? - var Uid uint32 + var Uid models.UID _ = thread.Walk(func(t *Thread, _ int, currentErr error) error { if t.Deleted || t.Hidden > 0 { @@ -171,9 +172,9 @@ func (s ByUID) Less(i, j int) bool { return maxUID_i < maxUID_j } -func SortThreadsBy(toSort []*Thread, sortBy []uint32) { +func SortThreadsBy(toSort []*Thread, sortBy []models.UID) { // build a map from sortBy - uidMap := make(map[uint32]int) + uidMap := make(map[models.UID]int) for i, uid := range sortBy { uidMap[uid] = i } diff --git a/worker/types/thread_test.go b/worker/types/thread_test.go index 669803d8..a6a0f327 100644 --- a/worker/types/thread_test.go +++ b/worker/types/thread_test.go @@ -4,16 +4,16 @@ import ( "fmt" "strings" "testing" + + "git.sr.ht/~rjarry/aerc/models" ) func genFakeTree() *Thread { - tree := &Thread{ - Uid: 0, - } + tree := new(Thread) var prevChild *Thread - for i := 1; i < 3; i++ { + for i := uint32(1); i < uint32(3); i++ { child := &Thread{ - Uid: uint32(i * 10), + Uid: models.Uint32ToUid(i * 10), Parent: tree, PrevSibling: prevChild, } @@ -26,9 +26,9 @@ func genFakeTree() *Thread { } prevChild = child var prevSecond *Thread - for j := 1; j < 3; j++ { + for j := uint32(1); j < uint32(3); j++ { second := &Thread{ - Uid: child.Uid + uint32(j), + Uid: models.Uint32ToUid(models.UidToUint32(child.Uid) + j), Parent: child, PrevSibling: prevSecond, } @@ -41,13 +41,13 @@ func genFakeTree() *Thread { } prevSecond = second var prevThird *Thread - limit := 3 + limit := uint32(3) if j == 2 { limit = 8 } - for k := 1; k < limit; k++ { + for k := uint32(1); k < limit; k++ { third := &Thread{ - Uid: second.Uid*10 + uint32(k), + Uid: models.Uint32ToUid(models.UidToUint32(second.Uid)*10 + j), Parent: second, PrevSibling: prevThird, } @@ -107,7 +107,7 @@ func TestNewWalk(t *testing.T) { func uidSeq(tree *Thread) string { var seq []string tree.Walk(func(t *Thread, _ int, _ error) error { - seq = append(seq, fmt.Sprintf("%d", t.Uid)) + seq = append(seq, string(t.Uid)) return nil }) return strings.Join(seq, ".") @@ -116,25 +116,25 @@ func uidSeq(tree *Thread) string { func TestThread_AddChild(t *testing.T) { tests := []struct { name string - seq []int + seq []models.UID want string }{ { name: "ascending", - seq: []int{1, 2, 3, 4, 5, 6}, - want: "0.1.2.3.4.5.6", + seq: []models.UID{"1", "2", "3", "4", "5", "6"}, + want: ".1.2.3.4.5.6", }, { name: "descending", - seq: []int{6, 5, 4, 3, 2, 1}, - want: "0.6.5.4.3.2.1", + seq: []models.UID{"6", "5", "4", "3", "2", "1"}, + want: ".6.5.4.3.2.1", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := &Thread{Uid: 0} + tree := new(Thread) for _, i := range test.seq { - tree.AddChild(&Thread{Uid: uint32(i)}) + tree.AddChild(&Thread{Uid: i}) } if got := uidSeq(tree); got != test.want { t.Errorf("got: %s, but wanted: %s", got, @@ -147,30 +147,30 @@ func TestThread_AddChild(t *testing.T) { func TestThread_OrderedInsert(t *testing.T) { tests := []struct { name string - seq []int + seq []models.UID want string }{ { name: "ascending", - seq: []int{1, 2, 3, 4, 5, 6}, - want: "0.1.2.3.4.5.6", + seq: []models.UID{"1", "2", "3", "4", "5", "6"}, + want: ".1.2.3.4.5.6", }, { name: "descending", - seq: []int{6, 5, 4, 3, 2, 1}, - want: "0.1.2.3.4.5.6", + seq: []models.UID{"6", "5", "4", "3", "2", "1"}, + want: ".1.2.3.4.5.6", }, { name: "mixed", - seq: []int{2, 1, 6, 3, 4, 5}, - want: "0.1.2.3.4.5.6", + seq: []models.UID{"2", "1", "6", "3", "4", "5"}, + want: ".1.2.3.4.5.6", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := &Thread{Uid: 0} + tree := new(Thread) for _, i := range test.seq { - tree.OrderedInsert(&Thread{Uid: uint32(i)}) + tree.OrderedInsert(&Thread{Uid: i}) } if got := uidSeq(tree); got != test.want { t.Errorf("got: %s, but wanted: %s", got, @@ -183,32 +183,32 @@ func TestThread_OrderedInsert(t *testing.T) { func TestThread_InsertCmd(t *testing.T) { tests := []struct { name string - seq []int + seq []models.UID want string }{ { name: "ascending", - seq: []int{1, 2, 3, 4, 5, 6}, - want: "0.6.4.2.1.3.5", + seq: []models.UID{"1", "2", "3", "4", "5", "6"}, + want: ".6.4.2.1.3.5", }, { name: "descending", - seq: []int{6, 5, 4, 3, 2, 1}, - want: "0.6.4.2.1.3.5", + seq: []models.UID{"6", "5", "4", "3", "2", "1"}, + want: ".6.4.2.1.3.5", }, { name: "mixed", - seq: []int{2, 1, 6, 3, 4, 5}, - want: "0.6.4.2.1.3.5", + seq: []models.UID{"2", "1", "6", "3", "4", "5"}, + want: ".6.4.2.1.3.5", }, } - sortMap := map[uint32]int{ - uint32(6): 1, - uint32(4): 2, - uint32(2): 3, - uint32(1): 4, - uint32(3): 5, - uint32(5): 6, + sortMap := map[models.UID]int{ + "6": 1, + "4": 2, + "2": 3, + "1": 4, + "3": 5, + "5": 6, } // bigger compares the new child with the next node and returns true if @@ -219,9 +219,9 @@ func TestThread_InsertCmd(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := &Thread{Uid: 0} + tree := new(Thread) for _, i := range test.seq { - tree.InsertCmp(&Thread{Uid: uint32(i)}, bigger) + tree.InsertCmp(&Thread{Uid: i}, bigger) } if got := uidSeq(tree); got != test.want { t.Errorf("got: %s, but wanted: %s", got, |