member_storage.gno
5.01 Kb · 164 lines
1package storage
2
3import (
4 "gno.land/p/jeronimoalbi/message"
5 "gno.land/p/nt/avl/v0"
6 "gno.land/p/nt/commondao/v0"
7)
8
9const (
10 msgMemberAdd message.Topic = "MemberAdd"
11 msgMemberRemove = "MemberRemove"
12)
13
14// GetMemberGroups returns the groups that a member belongs to.
15// It returns no groups if member storage was not created using this package.
16func GetMemberGroups(s commondao.MemberStorage, member address) []string {
17 storage, ok := s.(*memberStorage)
18 if !ok {
19 return nil
20 }
21
22 v, found := storage.memberGroups.Get(member.String())
23 if !found {
24 return nil
25 }
26
27 tree := v.(*avl.Tree)
28 groups := make([]string, 0, tree.Size())
29 tree.Iterate("", "", func(group string, _ any) bool {
30 groups = append(groups, group)
31 return false
32 })
33 return groups
34}
35
36// NewMemberStorage creates a new CommonDAO member storage with grouping support.
37//
38// This is a custom storage that automatically adds or removes members that are added
39// or removed from any of the member groups. This allows for quick and inexpensive
40// checks for the number of total unique storage users, including users added to groups,
41// and also to iterate all of them without needing to iterate individual groups.
42func NewMemberStorage() commondao.MemberStorage {
43 // Create a new broker to allow storages to publish and subscribe to messages
44 // It is used to add/remove users from the storage each time one or more groups change.
45 broker := message.NewBroker()
46
47 // Define a factory for creating custom member storages when groups are created.
48 // Custom storage publishes when a member is added or removed from a group.
49 innerFactory := func(group string) commondao.MemberStorage {
50 return &groupMemberStorage{
51 MemberStorage: commondao.NewMemberStorage(),
52 messages: broker,
53 group: group,
54 }
55 }
56
57 // Create a member storage that automatically adds or removes members each time
58 // a member group changes. This allows the storage to keep all members within
59 // the same "root" storage for easier iteration.
60 storage := &memberStorage{
61 MemberStorage: commondao.NewMemberStorageWithGrouping(
62 commondao.UseStorageFactory(innerFactory),
63 ),
64 messages: broker,
65 }
66
67 // Subscribe to messages published by member groups
68 storage.messages.Subscribe(msgMemberAdd, storage.handleMemberAddMsg)
69 storage.messages.Subscribe(msgMemberRemove, storage.handleMemberRemoveMsg)
70 return storage
71}
72
73type memberStorage struct {
74 commondao.MemberStorage
75
76 memberGroups avl.Tree // string(address) -> *avl.Tree(group -> struct{})
77 messages message.Subscriber
78}
79
80func (s *memberStorage) handleMemberAddMsg(msg message.Message) {
81 data := msg.Data.(groupMemberUpdateData)
82 key := data.Member.String()
83 v, _ := s.memberGroups.Get(key)
84 groups, ok := v.(*avl.Tree)
85 if !ok {
86 // Create a new tree to track member's groups
87 groups = avl.NewTree()
88 s.memberGroups.Set(key, groups)
89
90 // Add the new member to the storage
91 s.MemberStorage.Add(data.Member)
92 }
93
94 // Keep track of the new member group
95 groups.Set(data.Group, struct{}{})
96}
97
98func (s *memberStorage) handleMemberRemoveMsg(msg message.Message) {
99 data := msg.Data.(groupMemberUpdateData)
100 key := data.Member.String()
101 v, found := s.memberGroups.Get(key)
102 if !found {
103 // Member should always be found
104 return
105 }
106
107 // Remove the group from the list of groups member belongs
108 groups := v.(*avl.Tree)
109 groups.Remove(data.Group)
110
111 // Remove the member from the storage when it doesn't belong to any group
112 if groups.Size() == 0 {
113 s.memberGroups.Remove(key)
114 s.MemberStorage.Remove(data.Member)
115 }
116}
117
118// Size returns the number of members in the storage.
119// It also includes unique members that belong to any number of member groups.
120func (s memberStorage) Size() int {
121 return s.MemberStorage.Size()
122}
123
124// IterateByOffset iterates members starting at the given offset.
125// The callback can return true to stop iteration.
126// It also iterates unique members that belong to any of the member groups.
127func (s memberStorage) IterateByOffset(offset, count int, fn commondao.MemberIterFn) bool {
128 return s.MemberStorage.IterateByOffset(offset, count, fn)
129}
130
131// groupMemberUpdateData defines a data type for group member updates.
132type groupMemberUpdateData struct {
133 Group string
134 Member address
135}
136
137// groupMemberStorage defines a member storage for member groups.
138// This type of storage publishes messages when a member is added or removed from a group.
139type groupMemberStorage struct {
140 commondao.MemberStorage
141
142 group string
143 messages message.Publisher
144}
145
146// Add adds a member to the storage.
147// Returns true if the member is added, or false if it already existed.
148func (s *groupMemberStorage) Add(member address) bool {
149 s.messages.Publish(msgMemberAdd, groupMemberUpdateData{
150 Group: s.group,
151 Member: member,
152 })
153 return s.MemberStorage.Add(member)
154}
155
156// Remove removes a member from the storage.
157// Returns true if member was removed, or false if it was not found.
158func (s *groupMemberStorage) Remove(member address) bool {
159 s.messages.Publish(msgMemberRemove, groupMemberUpdateData{
160 Group: s.group,
161 Member: member,
162 })
163 return s.MemberStorage.Remove(member)
164}