0
Files
aleesa-telegram-go/internal/tg/settings-db-util.go

223 lines
7.5 KiB
Go

package tg
import (
"crypto/sha256"
"errors"
"fmt"
"io/fs"
"slices"
"strings"
"aleesa-telegram-go/internal/log"
"github.com/cockroachdb/errors/oserror"
"github.com/cockroachdb/pebble"
)
// StoreKV сохраняет в указанной бд ключ и значение.
func StoreKV(db *pebble.DB, key string, value string) error {
var (
kArray = []byte(key)
vArray = []byte(value)
)
err := db.Set(kArray, vArray, pebble.Sync)
return err
}
// FetchV достаёт значение по ключу.
func FetchV(db *pebble.DB, key string) (string, error) {
var (
kArray = []byte(key)
vArray []byte
valueString = ""
)
vArray, closer, err := db.Get(kArray)
if err != nil {
return valueString, err
}
valueString = string(vArray)
err = closer.Close()
return valueString, err
}
// InitChatListDB открывает для пользования базу со списком чатов.
// Вторым слоем этой базы являются слайсы chatList и chatGroupList, по которым можно в дальнейшем определять, известен
// ли даденный чат боту. По сути, слайс chatList активно пользуется в функции AppendChatListDB, а chatGroupList - в
// SendGoodMorning.
func InitChatListDB() error {
var (
err error
options pebble.Options
dbPath = Config.DataDir + "/" + chatListDBName
)
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений.
options.L0CompactionFileThreshold = 8
chatListDB, err = pebble.Open(dbPath, &options)
if err != nil {
return fmt.Errorf("unable to open settings db, %s: %w", dbPath, err)
}
// Заполним слайс со списком чатов. Операция нудная.
iterator, err := chatListDB.NewIter(&pebble.IterOptions{})
if !iterator.First() {
return errors.New("unable to iterate slice with chats")
}
// Попробуем извлечь список ключей и заполнить слайс с чатами.
for !iterator.Valid() {
// Битая база?
if err := iterator.Error(); err != nil {
chatList = []string{}
return fmt.Errorf("unable to get chat list: %w", err)
}
chatName := iterator.Key()
if len(chatName) == 0 {
return errors.New("got empty chat name when query " + chatListDBName)
}
chatID := string(chatName)
chatList = append(chatList, chatID)
slices.Sort(chatList)
if strings.HasPrefix(chatID, "-") {
chatGroupList = append(chatGroupList, chatID)
slices.Sort(chatGroupList)
}
iterator.Next()
}
return err
}
// AppendChatListDB добавляет чат в список известных боту. Можно добавлять чаты не глядя, дубликатов не добавится.
// Bot API нам не даёт информацию о том, на что подписан бот.
func AppendChatListDB(chatID string) error {
var err error
if slices.Contains(chatList, chatID) {
return err
}
if err = StoreKV(chatListDB, chatID, "1"); err != nil {
return fmt.Errorf("unable to append chat %s to chatListDB %s: %w", chatID, chatListDBName, err)
}
// Не забываем актуализировать список чатов.
chatList = append(chatList, chatID)
slices.Sort(chatList)
chatList = slices.Compact(chatList)
// Если СhatID начинается с - это значит, что мы имеем дело именно с группой/супергруппой/каналом.
if strings.HasPrefix(chatID, "-") {
chatGroupList = append(chatGroupList, chatID)
slices.Sort(chatGroupList)
chatGroupList = slices.Compact(chatGroupList)
}
return err
}
// GetSetting достаёт настройку из БД с настройками.
func GetSetting(chatID string, setting string) string {
var err error
chatHash := sha256.Sum256([]byte(chatID))
database := fmt.Sprintf("settings_db/%x", chatHash)
// Если БД не открыта, откроем её.
if _, ok := settingsDB[database]; !ok {
var options pebble.Options
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений.
options.L0CompactionFileThreshold = 8
settingsDB[database], err = pebble.Open(Config.DataDir+"/"+database, &options)
if err != nil {
log.Errorf("Unable to open settings db, %s: %s\n", database, err)
return ""
}
}
value, err := FetchV(settingsDB[database], setting)
// Если из базы ничего не вынулось, по каким-то причинам, то просто вернём пустую строку.
if err != nil {
switch {
case errors.Is(err, pebble.ErrNotFound):
log.Debugf("Unable to get value for %s: no record found in db %s", setting, database)
case errors.Is(err, fs.ErrNotExist):
log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database)
case errors.Is(err, oserror.ErrNotExist):
log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database)
default:
log.Errorf("Unable to get value for %s in db dir %s: %s", setting, database, err)
}
return ""
}
return value
}
// SaveSetting сохраняет настройку в БД с настройками.
func SaveSetting(chatID string, setting string, value string) error {
var (
chatHash = sha256.Sum256([]byte(chatID))
database = fmt.Sprintf("settings_db/%x", chatHash)
err error
)
// Если БД не открыта, откроем её.
if _, ok := settingsDB[database]; !ok {
var options pebble.Options
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений.
options.L0CompactionFileThreshold = 8
settingsDB[database], err = pebble.Open(Config.DataDir+"/"+database, &options)
if err != nil {
log.Errorf("Unable to open settings db, %s: %s\n", database, err)
return err
}
}
if err = StoreKV(settingsDB[database], setting, value); err != nil {
log.Errorf("Unable to save setting %s in database %s: %s", setting, database, err)
return err
}
if err = settingsDB[database].Flush(); err != nil {
log.Errorf("Unable to flush settings db for %s: %s", chatID, err)
}
return err
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */