package object import ( "citrons.xyz/talk/proto" "citrons.xyz/talk/server/session" "citrons.xyz/talk/proto/validate" "bufio" "bytes" "log" bolt "go.etcd.io/bbolt" ) type Object interface { SendRequest(session.Request) InfoFor(uid string) proto.Object Data() proto.Object } type HasHandle interface { Handle() string } type Kind interface { Undata(o proto.Object) Object } type World struct { db *bolt.DB kinds map[string]Kind objects map[string]Object } func NewWorld(db *bolt.DB) *World { w := &World {db, make(map[string]Kind), make(map[string]Object)} w.AddObjectKind("gone", TombstoneKind{}) return w } func (w *World) AddObjectKind(name string, kind Kind) { w.kinds[name] = kind } func (w *World) getData(id string) proto.Object { var data []byte err := w.db.View(func (tx *bolt.Tx) error { bucket := tx.Bucket([]byte("world")) if bucket != nil { data = bucket.Get([]byte(id)) } return nil }) if err != nil { log.Fatal("reading database: ", err) } if len(data) == 0 { return proto.Object {} } o, err := proto.ReadObject(bufio.NewReader(bytes.NewReader(data))) if err != nil { panic(err) } return o } func (w *World) setData(id string, o proto.Object) { var buf bytes.Buffer writer := bufio.NewWriter(&buf) proto.WriteObject(writer, o) writer.Flush() err := w.db.Update(func (tx *bolt.Tx) error { bucket, _ := tx.CreateBucketIfNotExists([]byte("world")) return bucket.Put([]byte(id), buf.Bytes()) }) if err != nil { log.Fatal("updating database: ", err) } } func (w *World) GetObject(id string) Object { if w.objects[id] == nil { o := w.getData(id) if o.Kind != "" { w.objects[id] = w.kinds[o.Kind].Undata(o) } } return w.objects[id] } func (w *World) PutObject(id string, o Object) { w.objects[id] = o if id == "" { return } switch h := o.(type) { case HasHandle: err := w.db.Update(func(tx *bolt.Tx) error { kinds, _ := tx.CreateBucketIfNotExists([]byte("kinds")) kind, _ := kinds.CreateBucketIfNotExists([]byte(o.Data().Kind)) byHandle, _ := kind.CreateBucketIfNotExists([]byte("by handle")) byId, _ := kind.CreateBucketIfNotExists([]byte("by id")) existing := byId.Get([]byte(id)) if len(existing) != 0 { byHandle.Delete(existing) } handle := []byte(validate.Fold(h.Handle())) byHandle.Put(handle, []byte(id)) byId.Put([]byte(id), handle) return nil }) if err != nil { log.Fatal("updating database: ", err) } } w.setData(id, o.Data()) } func (w *World) Lookup(kind string, handle string) Object { handle = validate.Fold(handle) var id string err := w.db.View(func(tx *bolt.Tx) error { kinds := tx.Bucket([]byte("kinds")) if kinds == nil { return nil } kind := kinds.Bucket([]byte(kind)) if kind == nil { return nil } id = string(kind.Bucket([]byte("by handle")).Get([]byte(handle))) return nil }) if err != nil { log.Fatal("reading database: ", err) } if id != "" { return w.GetObject(id) } return nil } func (w *World) NewObject(o Object) string { id := proto.GenId() w.PutObject(id, o) return id } func (w *World) DB() *bolt.DB { return w.db }