Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}