summaryrefslogtreecommitdiff
path: root/server/channel/channel.go
diff options
context:
space:
mode:
Diffstat (limited to 'server/channel/channel.go')
-rw-r--r--server/channel/channel.go242
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 {