diff options
Diffstat (limited to 'server/channel/channel.go')
| -rw-r--r-- | server/channel/channel.go | 242 |
1 files changed, 160 insertions, 82 deletions
diff --git a/server/channel/channel.go b/server/channel/channel.go index 3cb6f60..fdd4243 100644 --- a/server/channel/channel.go +++ b/server/channel/channel.go @@ -7,34 +7,35 @@ import ( "citrons.xyz/talk/server/validate" "citrons.xyz/talk/server/user" "strings" + "slices" "sort" + "maps" + "bytes" + "bufio" + bolt "go.etcd.io/bbolt" + "log" ) -type ChannelStore struct { +type ChannelKind struct { world *object.World - byName map[string]*Channel - directChannels map[string]*Channel + db *bolt.DB } type Channel struct { - store *ChannelStore + kind *ChannelKind id string name string isDirect bool - members map[string]Membership - messages []proto.Object - byId map[string]int - defaultMembership Membership Stream session.Stream + byId map[string]int + messages []proto.Object } -func NewStore(world *object.World) *ChannelStore { - return &ChannelStore { - world, make(map[string]*Channel), make(map[string]*Channel), - } +func Kind(world *object.World) *ChannelKind { + return &ChannelKind {world, world.DB()} } -func (cs *ChannelStore) CreateChannel(name string) (*Channel, *proto.Fail) { +func (cs *ChannelKind) CreateChannel(name string) (*Channel, *proto.Fail) { if cs.ByName(name) != nil { return nil, &proto.Fail { "name-taken", "", map[string]string {"": name}, @@ -46,39 +47,65 @@ func (cs *ChannelStore) CreateChannel(name string) (*Channel, *proto.Fail) { } } var c Channel - c.store = cs + c.kind = cs c.name = name - c.members = make(map[string]Membership) + c.id = proto.GenId() c.byId = make(map[string]int) - c.defaultMembership = DefaultMembership - cs.byName[validate.Fold(name)] = &c - c.id = cs.world.NewObject(&c) + err := cs.db.Update(func(tx *bolt.Tx) error { + chm, _ := tx.CreateBucketIfNotExists([]byte("channel membership")) + chm.CreateBucket([]byte(c.id)) + return nil + }) + if err != nil { + log.Fatal("error updating database: ", err) + } + c.SetDefaultMembership(DefaultMembership) + + c.Save() return &c, nil } -func (cs *ChannelStore) GetDirect(among []string) *Channel { - sort.Strings(among) - key := strings.Join(among, "\x00") - if cs.directChannels[key] == nil { - var c Channel - c.isDirect = true - c.store = cs - c.byId = make(map[string]int) - c.defaultMembership = DefaultMembership - c.members = make(map[string]Membership) - for _, member := range among { - c.members[member] = c.defaultMembership - } +func DirectHandle(among map[string]bool) string { + return strings.Join(slices.Sorted(maps.Keys(among)), "\x00") +} + +func (cs *ChannelKind) GetDirect(among map[string]bool) *Channel { + handle := DirectHandle(among) + switch ch := cs.world.Lookup("direct-channel", handle).(type) { + case *Channel: + return ch + } + var c Channel + c.isDirect = true + c.kind = cs + c.id = proto.GenId() + c.byId = make(map[string]int) + for member, _ := range among { + c.SetMembership(member, DefaultMembership) + } + c.Save() + return &c +} - cs.directChannels[key] = &c - c.id = cs.world.NewObject(&c) +func (cs *ChannelKind) ByName(name string) *Channel { + switch ch := cs.world.Lookup("channel", name).(type) { + case *Channel: + return ch + default: + return nil } - return cs.directChannels[key] } -func (cs *ChannelStore) ByName(name string) *Channel { - return cs.byName[validate.Fold(name)] +func (cs *ChannelKind) Undata(o proto.Object) object.Object { + log.Println("load: ", o) + var c Channel + c.kind = cs + c.id = o.Id + c.name = o.Fields[""] + c.isDirect = o.Kind == "direct-channel" + c.byId = make(map[string]int) + return &c } func (c *Channel) Name() string { @@ -90,11 +117,11 @@ func (c *Channel) NameFor(uid string) string { return c.name } else { var members []string - for member := range c.members { - if member == uid && len(c.members) > 1 { + for member, _ := range c.Members() { + if member == uid && len(c.Members()) > 1 { continue } - u := c.store.world.GetObject(member) + u := c.kind.world.GetObject(member) if u != nil { members = append(members, u.InfoFor(uid).Fields[""]) } @@ -104,6 +131,27 @@ func (c *Channel) NameFor(uid string) string { } } +func (c *Channel) Handle() string { + if !c.isDirect { + return c.name + } else { + members := make(map[string]bool) + for member := range c.Members() { + members[member] = true + } + return DirectHandle(members) + } +} + +func (c *Channel) Data() proto.Object { + log.Println("save: ", c.InfoFor("")) + return c.InfoFor("") +} + +func (c *Channel) Save() { + c.kind.world.PutObject(c.id, c) +} + func (c *Channel) Id() string { return c.id } @@ -114,18 +162,13 @@ func (c *Channel) Rename(name string) *proto.Fail { "invalid-name", "", map[string]string {"": name}, } } - if validate.Fold(name) == validate.Fold(c.name) { - c.name = name - return nil - } - if c.store.ByName(name) != nil { + if c.kind.ByName(name) != nil { return &proto.Fail { "name-taken", "", map[string]string {"": name}, } } - c.store.byName[validate.Fold(c.name)] = nil - c.store.byName[validate.Fold(name)] = c c.name = name + c.Save() return nil } @@ -138,75 +181,110 @@ func (c *Channel) Put(m proto.Object) proto.Object { if m.Fields["f"] == s.UserId { continue } - if c.members[s.UserId].See { + if c.GetMembership(s.UserId).See { s.Event(proto.NewCmd("p", c.id, m)) } } return m } -func (c *Channel) prune() { - if c.isDirect { - return - } - for m, _ := range c.members { - switch c.store.world.GetObject(m).(type) { - case *user.User: - default: - delete(c.members, m) - } - } -} - func (c *Channel) Join(u *user.User) *proto.Fail { - if c.members[u.Id()].Yes { + if c.GetMembership(u.Id()).Yes { return nil } - c.members[u.Id()] = c.defaultMembership - u.Channels[c.id] = true + c.SetMembership(u.Id(), c.GetDefaultMembership()) + // u.Channels[c.id] = true c.Put(proto.Object{"join", "", map[string]string {"f": u.Id()}}) return nil } func (c *Channel) Leave(u *user.User) *proto.Fail { - if !c.members[u.Id()].Yes { + if !c.GetMembership(u.Id()).Yes { return nil } - delete(c.members, u.Id()) - delete(u.Channels, c.id) + c.SetMembership(u.Id(), Membership {Yes: false}) + // delete(u.Channels, c.id) c.Put(proto.Object{"leave", "", map[string]string {"f": u.Id()}}) return nil } func (c *Channel) Members() map[string]Membership { - c.prune() - return c.members + result := make(map[string]Membership) + err := c.kind.db.View(func(tx *bolt.Tx) error { + channels := tx.Bucket([]byte("channel membership")) + members := channels.Bucket([]byte(c.id)) + return members.ForEach(func(k, v []byte) error { + var mship Membership + o, _ := proto.ReadObject(bufio.NewReader(bytes.NewReader(v))) + result[string(k)], _ = mship.Change(o) + return nil + }) + }) + if err != nil { + log.Fatal("error updating database: ", err) + } + return result } -func (c *Channel) SetMembership(u *user.User, m Membership) { - if c.members[u.Id()].Yes { - c.members[u.Id()] = m +func (c *Channel) GetMembership(uid string) Membership { + var mship Membership + err := c.kind.db.View(func(tx *bolt.Tx) error { + channels := tx.Bucket([]byte("channel membership")) + members := channels.Bucket([]byte(c.id)) + data := members.Get([]byte(uid)) + if data != nil { + o, _ := proto.ReadObject(bufio.NewReader(bytes.NewReader(data))) + mship.Undata(o) + } + return nil + }) + if err != nil { + log.Fatal("error updating database: ", err) } + return mship +} + +func (c *Channel) SetMembership(uid string, m Membership) { + err := c.kind.db.Update(func(tx *bolt.Tx) error { + channels := tx.Bucket([]byte("channel membership")) + members, _ := channels.CreateBucketIfNotExists([]byte(c.id)) + if m.Yes { + var buf bytes.Buffer + writer := bufio.NewWriter(&buf) + proto.WriteObject(writer, m.GetInfo()) + writer.Flush() + return members.Put([]byte(uid), buf.Bytes()) + } else { + return members.Delete([]byte(uid)) + } + }) + if err != nil { + log.Fatal("error updating database: ", err) + } +} + +func (c *Channel) GetDefaultMembership() Membership { + return DefaultMembership +} + +func (c *Channel) SetDefaultMembership(m Membership) { } func (c *Channel) Delete() { c.Stream.Event(proto.NewCmd("delete", c.id)) c.Stream.UnsubscribeAll() - for m, _ := range c.members { - switch u := c.store.world.GetObject(m).(type) { - case *user.User: - u.Channels[c.id] = false - default: - } - } - delete(c.store.byName, validate.Fold(c.name)) - c.store.world.RemoveObject(c.id) - deleted := object.Tombstone { c.id, map[string]string {"": c.name, "kind": c.Kind()}, } - c.store.world.PutObject(c.id, deleted) + c.kind.world.PutObject(c.id, deleted) + err := c.kind.db.Update(func(tx *bolt.Tx) error { + channels := tx.Bucket([]byte("channel membership")) + return channels.DeleteBucket([]byte(c.id)) + }) + if err != nil { + log.Fatal("error updating database: ", err) + } } func (c *Channel) Kind() string { |
