package localserver import ( "encoding/binary" "encoding/json" "os" "path/filepath" "sync" "github.com/dgraph-io/badger/v3" ) type ChatFile struct { ChatId string `json:"chatId"` Owner string `json:"owner"` Name string `json:"name"` Size int `json:"size"` UploadTime string `json:"uploadTime"` } type ChatMessage struct { ID uint64 `json:"id"` From string `json:"from"` IsResponse bool `json:"isResponse"` ResponseOf *ChatMessage `json:"responseOf"` Tags []string `json:"tags"` Content string `json:"content"` Date string `json:"date"` File *ChatFile `json:"file"` } type ZoneChatDBHandler struct { ChatID string ZoneID string PreviousId uint64 ItemCount uint64 db func(func(*badger.DB) (err error)) (err error) lock *sync.RWMutex } const bufferSize = 8 func NewZoneChatDBHandler(zoneId string, chatID string) (zoneChatDBHandler *ZoneChatDBHandler, err error) { zoneChatDBHandler = &ZoneChatDBHandler{ db: func(f func(*badger.DB) (err error)) (err error) { db, err := badger.Open(badger.DefaultOptions(filepath.Join(dataPath, "data", "zones", zoneId, "chats", chatID)).WithLogger(dbLogger)) if err != nil { return } defer db.Close() err = f(db) return }, ChatID: chatID, ZoneID: zoneId, lock: new(sync.RWMutex), } err = zoneChatDBHandler.db(func(d *badger.DB) (err error) { err = d.View(func(txn *badger.Txn) error { opt := badger.DefaultIteratorOptions it := txn.NewIterator(opt) defer it.Close() var counter uint64 = 1 var itemCounter uint64 = 0 for it.Rewind(); it.Valid(); it.Next() { item := it.Item() if err = item.Value(func(val []byte) (err error) { var chatMessage *ChatMessage if err = json.Unmarshal(val, &chatMessage); err != nil { return err } counter = chatMessage.ID itemCounter++ return }); err != nil { return err } } zoneChatDBHandler.PreviousId = counter zoneChatDBHandler.ItemCount = itemCounter return nil }) return }) return } func (zcdbh *ZoneChatDBHandler) calculateNewChatCount(previousId uint64) (count uint, err error) { err = zcdbh.db(func(d *badger.DB) (err error) { err = d.View(func(txn *badger.Txn) error { opt := badger.DefaultIteratorOptions it := txn.NewIterator(opt) defer it.Close() count = 0 b := make([]byte, bufferSize) binary.BigEndian.PutUint64(b, previousId) if previousId == 0 || previousId == 1 { count++ for it.Rewind(); it.Valid(); it.Next() { count++ } } else { for it.Seek(b); it.Valid(); it.Next() { count++ } } if count > 0 { count-- } return nil }) return }) return } func (zcdbh *ZoneChatDBHandler) revertPreviousId() (err error) { err = zcdbh.db(func(d *badger.DB) (err error) { err = d.View(func(txn *badger.Txn) error { opt := badger.DefaultIteratorOptions opt.Reverse = true it := txn.NewIterator(opt) defer it.Close() it.Rewind() if it.Valid() { item := it.Item() if err = item.Value(func(val []byte) (err error) { var chatMessage *ChatMessage if err = json.Unmarshal(val, &chatMessage); err != nil { return err } zcdbh.PreviousId = chatMessage.ID return }); err != nil { return err } } else { zcdbh.PreviousId = 1 } return nil }) return }) return } func (zcdbh *ZoneChatDBHandler) updateDbCallbackFolder(newChatId string) { zcdbh.db = func(f func(*badger.DB) (err error)) (err error) { db, err := badger.Open(badger.DefaultOptions(filepath.Join(dataPath, "data", "zones", zcdbh.ZoneID, "chats", newChatId)).WithLogger(dbLogger)) if err != nil { return } defer db.Close() err = f(db) return } } func (zcdbh *ZoneChatDBHandler) AddNewChatMessage(chatMessage *ChatMessage) (err error) { b := make([]byte, bufferSize) zcdbh.PreviousId++ binary.BigEndian.PutUint64(b, zcdbh.PreviousId) chatMessage.ID = zcdbh.PreviousId bs, err := json.Marshal(chatMessage) if err != nil { return } if err = zcdbh.db(func(d *badger.DB) (err error) { err = d.Update(func(txn *badger.Txn) (err error) { if updateErr := txn.Set(b, bs); updateErr != nil { return updateErr } return nil }) return }); err != nil { return } return } func (zcdbh *ZoneChatDBHandler) DeleteChatMessage(key uint64) (err error) { if err = zcdbh.db(func(d *badger.DB) (err error) { err = d.Update(func(txn *badger.Txn) (err error) { b := make([]byte, bufferSize) binary.BigEndian.PutUint64(b, key) if err = txn.Delete(b); err != nil { return } zcdbh.ItemCount-- return }) return }); err != nil { return } return } func (zcdbh *ZoneChatDBHandler) DeleteChatFile(filename string, key uint64) (err error) { if err = zcdbh.DeleteChatMessage(key); err != nil { return } err = os.Remove(filepath.Join(dataPath, "data", "zones", zcdbh.ZoneID, "chats", zcdbh.ChatID, "__files__", filename)) return } func (zcdbh *ZoneChatDBHandler) ListChatMessages(lastIndex int, limit int) (chatMessages []*ChatMessage, l int, done bool, err error) { err = zcdbh.db(func(d *badger.DB) (err error) { err = d.View(func(txn *badger.Txn) (err error) { opt := badger.DefaultIteratorOptions opt.Reverse = true it := txn.NewIterator(opt) defer it.Close() b := make([]byte, bufferSize) if lastIndex <= 0 { binary.BigEndian.PutUint64(b, uint64(zcdbh.PreviousId)) } else { binary.BigEndian.PutUint64(b, uint64(lastIndex)) } x := 0 defer func() { if x < limit { done = true } }() chatMessages = make([]*ChatMessage, 0) for it.Seek(b); it.Valid(); it.Next() { if x >= limit { break } item := it.Item() if err = item.Value(func(val []byte) (err error) { var chatMessage *ChatMessage if err = json.Unmarshal(val, &chatMessage); err != nil { return err } l = int(chatMessage.ID) chatMessages = append(chatMessages, chatMessage) return }); err != nil { return err } x++ } return }) return }) return } func (zcdbh *ZoneChatDBHandler) ListChatFiles(lastIndex int, limit int) (chatMessages []*ChatFile, l int, err error) { err = zcdbh.db(func(d *badger.DB) (err error) { err = d.View(func(txn *badger.Txn) (err error) { opt := badger.DefaultIteratorOptions opt.Reverse = true it := txn.NewIterator(opt) defer it.Close() b := make([]byte, bufferSize) var li int if lastIndex <= 0 { binary.BigEndian.PutUint64(b, uint64(zcdbh.PreviousId)) li = int(zcdbh.PreviousId) } else { binary.BigEndian.PutUint64(b, uint64(lastIndex)) li = lastIndex } x := 0 y := 0 defer func() { if li > x { l = li - x } else if li == 0 { if zcdbh.PreviousId > uint64(x) { l = int(zcdbh.PreviousId) - x } else { l = 0 } } else { l = 0 } }() defer it.Close() chatMessages = make([]*ChatFile, 0) for it.Seek(b); it.Valid(); it.Next() { if y >= limit || x >= int(zcdbh.PreviousId) { break } item := it.Item() if err = item.Value(func(val []byte) (err error) { var chatMessage *ChatMessage if err = json.Unmarshal(val, &chatMessage); err != nil { return err } if chatMessage.File != nil { chatMessages = append(chatMessages, chatMessage.File) l = lastIndex - x y++ } return }); err != nil { return err } x++ } return }) return }) return } func (zcdbh *ZoneChatDBHandler) GetChatMessage(index uint64) (chatMessage *ChatMessage, err error) { err = zcdbh.db(func(d *badger.DB) (err error) { err = d.View(func(txn *badger.Txn) (err error) { b := make([]byte, bufferSize) binary.BigEndian.PutUint64(b, uint64(index)) item, err := txn.Get(b) if err != nil { return } err = item.Value(func(val []byte) error { return json.Unmarshal(val, &chatMessage) }) return }) return }) return } func (zcdbh *ZoneChatDBHandler) ModifyChatMessage(key uint64, newContent string) (err error) { b := make([]byte, bufferSize) binary.BigEndian.PutUint64(b, key) chatMessage, err := zcdbh.GetChatMessage(key) if err != nil { return } chatMessage.Content = newContent bs, err := json.Marshal(chatMessage) if err != nil { return } if err = zcdbh.db(func(d *badger.DB) (err error) { err = d.Update(func(txn *badger.Txn) (err error) { if updateErr := txn.Set(b, bs); updateErr != nil { return updateErr } return nil }) return }); err != nil { return } return }