// package store -- хранилище на диске package store import ( "fmt" "log" "os" "strings" "sync" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/util" "git.p78su.freemyip.com/svi/gostore/pkg/types" ) // StoreDisk -- хранилище на диске type Store struct { serv types.IServCtx db *leveldb.DB isWork bool block sync.RWMutex } var IsBad_ = false // Для тестов // NewStore -- возвращает новое хранилище на диске func NewStore(serv types.IServCtx) (types.IStore, error) { if IsBad_ { return nil, fmt.Errorf("NewStore(): IsBad_==true") } if serv == nil { return nil, fmt.Errorf("NewStore(): IServCtx==nil") } err := os.MkdirAll("./store", 0750) if err != nil { return nil, fmt.Errorf("NewStore(): in create dir, err=\n\t%w", err) } db, err := leveldb.OpenFile("./store/db", nil) if err != nil { if !strings.Contains(err.Error(), "leveldb: manifest corrupted") { return nil, fmt.Errorf("NewStore(): in create IStoreDisk, err=\n\t%w", err) } db, err = leveldb.RecoverFile("./store/db", nil) if err != nil { return nil, fmt.Errorf("NewStore(): in recovery DB, err=\n\t%w", err) } } sf := &Store{ serv: serv, db: db, } sf.serv.Wg().Add(1) sf.isWork = true go sf.close() return sf, nil } // Проверка на нерабочую схему func (sf *Store) isClosed() bool { sf.block.RLock() defer sf.block.RUnlock() return !sf.isWork } // Put -- размещает в хранилище ключ и значение func (sf *Store) Put(key string, val []byte) error { if sf.isClosed() { return fmt.Errorf("Store.Put(): store is closed") } if key == "" { return fmt.Errorf("Store.Put(): key is empty") } err := sf.db.Put([]byte(key), val, nil) if err != nil { return fmt.Errorf("Store.Put(): key=%q\terr=\n\t%w", key, err) } return nil } // Get -- возвращает значение ключа func (sf *Store) Get(key string) ([]byte, error) { if sf.isClosed() { return nil, fmt.Errorf("Store.Get(): store is closed") } val, err := sf.db.Get([]byte(key), nil) if err != nil { return nil, fmt.Errorf("Store.Get(): key=%q\terr=\n\t%w", key, err) } return val, nil } // Find -- ищет ключи по префиксу func (sf *Store) Find(prefixKey string) ([]string, error) { if sf.isClosed() { return nil, fmt.Errorf("Store.Find(): store is closed") } lstKey := []string{} iter := sf.db.NewIterator(util.BytesPrefix([]byte(prefixKey)), nil) for iter.Next() { key := iter.Key() lstKey = append(lstKey, string(key)) } iter.Release() err := iter.Error() if err != nil { return nil, fmt.Errorf("Store.Find(): preefixKey=%q\terr=\n\t%w", prefixKey, err) } return lstKey, nil } // Del -- удаление ключа из базы func (sf *Store) Del(key string) error { if sf.isClosed() { return fmt.Errorf("Store.Del(): store is closed") } err := sf.db.Delete([]byte(key), nil) if err != nil { return fmt.Errorf("Store.Del(): key=%q\terr=\n\t%w", key, err) } return nil } // Ожидание закрытия приложения func (sf *Store) close() { <-sf.serv.Ctx().Done() sf.block.Lock() defer sf.block.Unlock() if !sf.isWork { return } sf.isWork = false err := sf.db.Close() if err != nil { log.Printf("Store.close(): err=\n\t%v\n", err) } sf.serv.Wg().Done() }