storage.gno
3.39 Kb · 141 lines
1package boards
2
3import (
4 "errors"
5 "strings"
6
7 "gno.land/p/nt/avl/v0"
8)
9
10type (
11 // BoardIterFn defines a function type to iterate boards.
12 BoardIterFn func(*Board) bool
13
14 // Storage defines an interface for boards storage.
15 Storage interface {
16 // Get retruns a boards that matches an ID.
17 Get(ID) (_ *Board, found bool)
18
19 // GetByName retruns a boards that matches a name.
20 GetByName(name string) (_ *Board, found bool)
21
22 // Remove removes a board from the storage.
23 Remove(ID) (_ *Board, removed bool)
24
25 // Add adds a board to the storage.
26 Add(*Board) error
27
28 // Size returns the number of boards in the storage.
29 Size() int
30
31 // Iterate iterates boards.
32 // To reverse iterate boards use a negative count.
33 // If the callback returns true, the iteration is stopped.
34 Iterate(start, count int, fn BoardIterFn) bool
35 }
36)
37
38// NewStorage creates a new boards storage.
39func NewStorage() Storage {
40 return &storage{
41 byID: avl.NewTree(),
42 byName: avl.NewTree(),
43 }
44}
45
46type storage struct {
47 byID *avl.Tree // string(Board.ID) -> *Board
48 byName *avl.Tree // Board.Name -> Board.ID
49}
50
51// Get returns a board for a specific ID.
52func (s storage) Get(boardID ID) (*Board, bool) {
53 key := makeBoardKey(boardID)
54 v, found := s.byID.Get(key)
55 if !found {
56 return nil, false
57 }
58 return v.(*Board), true
59}
60
61// Get returns a board for a specific name.
62func (s storage) GetByName(name string) (*Board, bool) {
63 key := makeBoardNameKey(name)
64 v, found := s.byName.Get(key)
65 if !found {
66 return nil, false
67 }
68 return s.Get(v.(ID))
69}
70
71// Remove removes a board from the storage.
72// It returns false when board is not found.
73func (s *storage) Remove(boardID ID) (*Board, bool) {
74 board, found := s.Get(boardID)
75 if !found {
76 return nil, false
77 }
78
79 // Remove indexes for current and previous board names
80 names := append([]string{board.Name}, board.Aliases...)
81 for _, name := range names {
82 key := makeBoardNameKey(name)
83
84 // Make sure that name is indexed to the board being removed
85 v, found := s.byName.Get(key)
86 if found && v.(ID) == boardID {
87 s.byName.Remove(key)
88 }
89 }
90
91 key := makeBoardKey(board.ID)
92 _, removed := s.byID.Remove(key)
93 return board, removed
94}
95
96// Add adds a board to the storage.
97// If board already exists it updates storage by reindexing the board by ID and name.
98// When board name changes it's indexed so it can be found with the new and previous names.
99func (s *storage) Add(board *Board) error {
100 if board == nil {
101 return errors.New("adding nil boards to the storage is not allowed")
102 }
103
104 key := makeBoardKey(board.ID)
105 s.byID.Set(key, board)
106
107 // Index by name when the optional board name is not empty
108 if key = makeBoardNameKey(board.Name); key != "" {
109 s.byName.Set(key, board.ID)
110 }
111 return nil
112}
113
114// Size returns the number of boards in the storage.
115func (s storage) Size() int {
116 return s.byID.Size()
117}
118
119// Iterate iterates boards.
120// To reverse iterate boards use a negative count.
121// If the callback returns true, the iteration is stopped.
122func (s storage) Iterate(start, count int, fn BoardIterFn) bool {
123 if count < 0 {
124 return s.byID.ReverseIterateByOffset(start, -count, func(_ string, v any) bool {
125 return fn(v.(*Board))
126 })
127 }
128
129 return s.byID.IterateByOffset(start, count, func(_ string, v any) bool {
130 return fn(v.(*Board))
131 })
132}
133
134func makeBoardKey(boardID ID) string {
135 return boardID.Key()
136}
137
138func makeBoardNameKey(name string) string {
139 name = strings.TrimSpace(name)
140 return strings.ToLower(name)
141}