From 0e7f39b8cc2d6c374094301b4f26fea834aa4b19 Mon Sep 17 00:00:00 2001 From: hexxa Date: Sat, 4 Sep 2021 17:49:33 +0800 Subject: [PATCH] feat(filestore): support file info methods --- src/fileinfostore/file_info_store.go | 115 ++++++++++++++++++++-- src/fileinfostore/file_info_store_test.go | 65 +++++++++++- src/kvstore/boltdbpvd/provider.go | 19 ++++ src/kvstore/kvstore_interface.go | 1 + src/kvstore/test/provider_test.go | 15 +++ 5 files changed, 200 insertions(+), 15 deletions(-) diff --git a/src/fileinfostore/file_info_store.go b/src/fileinfostore/file_info_store.go index 01f74c9..093f295 100644 --- a/src/fileinfostore/file_info_store.go +++ b/src/fileinfostore/file_info_store.go @@ -1,20 +1,42 @@ package fileinfostore import ( + "encoding/json" + "errors" + "fmt" + "time" + "github.com/ihexxa/quickshare/src/kvstore" ) const ( InitNs = "Init" - SharingNs = "sharing" + InfoNs = "sharing" InitTimeKey = "initTime" ) +var ( + ErrNotFound = errors.New("file info not found") +) + +func IsNotFound(err error) bool { + return err == ErrNotFound +} + +type FileInfo struct { + IsDir bool `json:"isDir"` + Shared bool `json:"shared"` + Sha1 string `json:"sha1"` +} + type IFileInfoStore interface { AddSharing(dirPath string) error DelSharing(dirPath string) error GetSharing(dirPath string) (bool, bool) ListSharings(prefix string) (map[string]bool, error) + GetInfo(itemPath string) (*FileInfo, error) + SetInfo(itemPath string, info *FileInfo) error + DelInfo(itemPath string) error } type FileInfoStore struct { @@ -27,7 +49,7 @@ func NewFileInfoStore(store kvstore.IKVStore) (*FileInfoStore, error) { var err error for _, nsName := range []string{ InitNs, - SharingNs, + InfoNs, } { if err = store.AddNamespace(nsName); err != nil { return nil, err @@ -35,23 +57,96 @@ func NewFileInfoStore(store kvstore.IKVStore) (*FileInfoStore, error) { } } + err := store.SetStringIn(InitNs, InitTimeKey, fmt.Sprintf("%d", time.Now().Unix())) + if err != nil { + return nil, err + } + return &FileInfoStore{ store: store, }, nil } -func (us *FileInfoStore) AddSharing(dirPath string) error { - return us.store.SetBoolIn(SharingNs, dirPath, true) +func (fi *FileInfoStore) AddSharing(dirPath string) error { + info, err := fi.GetInfo(dirPath) + if err != nil { + if !IsNotFound(err) { + return err + } + info = &FileInfo{ + IsDir: true, + } + } + info.Shared = true + return fi.SetInfo(dirPath, info) } -func (us *FileInfoStore) DelSharing(dirPath string) error { - return us.store.DelBoolIn(SharingNs, dirPath) +func (fi *FileInfoStore) DelSharing(dirPath string) error { + info, err := fi.GetInfo(dirPath) + if err != nil { + return err + } + info.Shared = false + return fi.SetInfo(dirPath, info) } -func (us *FileInfoStore) GetSharing(dirPath string) (bool, bool) { - return us.store.GetBoolIn(SharingNs, dirPath) +func (fi *FileInfoStore) GetSharing(dirPath string) (bool, bool) { + info, err := fi.GetInfo(dirPath) + if err != nil { + // TODO: error is ignored + return false, false + } + return info.IsDir && info.Shared, true } -func (us *FileInfoStore) ListSharings(prefix string) (map[string]bool, error) { - return us.store.ListBoolsByPrefixIn(prefix, SharingNs) +func (fi *FileInfoStore) ListSharings(prefix string) (map[string]bool, error) { + infoStrs, err := fi.store.ListStringsByPrefixIn(prefix, InfoNs) + if err != nil { + return nil, err + } + + info := &FileInfo{} + sharings := map[string]bool{} + for itemPath, infoStr := range infoStrs { + err = json.Unmarshal([]byte(infoStr), info) + if err != nil { + return nil, fmt.Errorf("list sharing error: %w", err) + } + if info.IsDir && info.Shared { + sharings[itemPath] = true + } + } + + return sharings, nil +} + +func (fi *FileInfoStore) GetInfo(itemPath string) (*FileInfo, error) { + infoStr, ok := fi.store.GetStringIn(InfoNs, itemPath) + if !ok { + return nil, ErrNotFound + } + + info := &FileInfo{} + err := json.Unmarshal([]byte(infoStr), info) + if err != nil { + return nil, fmt.Errorf("get file info: %w", err) + } + return info, nil +} + +func (fi *FileInfoStore) SetInfo(itemPath string, info *FileInfo) error { + infoStr, err := json.Marshal(info) + if err != nil { + return fmt.Errorf("set file info: %w", err) + } + + err = fi.store.SetStringIn(InfoNs, itemPath, string(infoStr)) + if err != nil { + return fmt.Errorf("set file info: %w", err) + } + return nil +} + +func (fi *FileInfoStore) DelInfo(itemPath string) error { + return fi.store.DelStringIn(InfoNs, itemPath) } diff --git a/src/fileinfostore/file_info_store_test.go b/src/fileinfostore/file_info_store_test.go index 3e0a8bc..8451309 100644 --- a/src/fileinfostore/file_info_store_test.go +++ b/src/fileinfostore/file_info_store_test.go @@ -13,6 +13,8 @@ func TestUserStores(t *testing.T) { testSharingMethods := func(t *testing.T, store IFileInfoStore) { dirPaths := []string{"admin/path1", "admin/path1/path2"} var err error + + // add sharings for _, dirPath := range dirPaths { err = store.AddSharing(dirPath) if err != nil { @@ -20,12 +22,12 @@ func TestUserStores(t *testing.T) { } } + // list sharings prefix := "admin" sharingMap, err := store.ListSharings(prefix) if err != nil { t.Fatal(err) } - for _, sharingDir := range dirPaths { if !sharingMap[sharingDir] { t.Fatalf("sharing(%s) not found", sharingDir) @@ -36,6 +38,7 @@ func TestUserStores(t *testing.T) { } } + // del sharings for _, dirPath := range dirPaths { err = store.DelSharing(dirPath) if err != nil { @@ -48,12 +51,63 @@ func TestUserStores(t *testing.T) { t.Fatal(err) } for _, dirPath := range dirPaths { - if sharingMap[dirPath] { + if _, ok := sharingMap[dirPath]; ok { t.Fatalf("sharing(%s) should not exist", dirPath) } - _, exist := store.GetSharing(dirPath) - if exist { - t.Fatalf("get sharing(%t) should not exit", exist) + shared, exist := store.GetSharing(dirPath) + if shared { + t.Fatalf("get sharing(%t, %t) should not shared but exist", shared, exist) + } + } + } + + testInfoMethods := func(t *testing.T, store IFileInfoStore) { + pathInfos := map[string]*FileInfo{ + "admin/item": &FileInfo{ + Shared: false, + IsDir: false, + Sha1: "file", + }, + "admin/dir": &FileInfo{ + Shared: true, + IsDir: true, + Sha1: "dir", + }, + } + var err error + + // add infos + for itemPath, info := range pathInfos { + err = store.SetInfo(itemPath, info) + if err != nil { + t.Fatal(err) + } + } + + // get infos + for itemPath, expected := range pathInfos { + info, err := store.GetInfo(itemPath) + if err != nil { + t.Fatal(err) + } + if info.Shared != expected.Shared || info.IsDir != expected.IsDir || info.Sha1 != expected.Sha1 { + t.Fatalf("info not equaled (%v) (%v)", expected, info) + } + } + + // del sharings + for itemPath := range pathInfos { + err = store.DelInfo(itemPath) + if err != nil { + t.Fatal(err) + } + } + + // get infos + for itemPath := range pathInfos { + _, err := store.GetInfo(itemPath) + if !IsNotFound(err) { + t.Fatal(err) } } } @@ -74,5 +128,6 @@ func TestUserStores(t *testing.T) { } testSharingMethods(t, store) + testInfoMethods(t, store) }) } diff --git a/src/kvstore/boltdbpvd/provider.go b/src/kvstore/boltdbpvd/provider.go index 055ced0..b19f298 100644 --- a/src/kvstore/boltdbpvd/provider.go +++ b/src/kvstore/boltdbpvd/provider.go @@ -413,3 +413,22 @@ func (bp *BoltPvd) ListStringsIn(ns string) (map[string]string, error) { return kv, err } + +func (bp *BoltPvd) ListStringsByPrefixIn(prefix, ns string) (map[string]string, error) { + results := map[string]string{} + + err := bp.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(ns)).Cursor() + if b == nil { + return ErrBucketNotFound + } + + prefixBytes := []byte(prefix) + for k, v := b.Seek(prefixBytes); k != nil && bytes.HasPrefix(k, prefixBytes); k, v = b.Next() { + results[string(k)] = string(v) + } + return nil + }) + + return results, err +} diff --git a/src/kvstore/kvstore_interface.go b/src/kvstore/kvstore_interface.go index 9de7505..f590753 100644 --- a/src/kvstore/kvstore_interface.go +++ b/src/kvstore/kvstore_interface.go @@ -38,6 +38,7 @@ type IKVStore interface { GetStringIn(ns, key string) (string, bool) SetStringIn(ns, key, val string) error ListStringsIn(ns string) (map[string]string, error) + ListStringsByPrefixIn(prefix, ns string) (map[string]string, error) TryLock(key string) error Unlock(key string) error } diff --git a/src/kvstore/test/provider_test.go b/src/kvstore/test/provider_test.go index 76010e8..44576dc 100644 --- a/src/kvstore/test/provider_test.go +++ b/src/kvstore/test/provider_test.go @@ -171,6 +171,21 @@ func TestKVStoreProviders(t *testing.T) { } else if stringVGot != stringV { t.Error(fmt.Sprintln("value not equal", stringVGot, stringV)) } + err = store.SetStringIn(ns, key2, stringV) + if err != nil { + t.Errorf("there should be no error %v", err) + } + kvs, err := store.ListStringsByPrefixIn("key", ns) + if err != nil { + t.Errorf("there should be no error %v", err) + } + if kvs[key] != stringV { + t.Errorf("list str key not found") + } + if kvs[key2] != stringV { + t.Errorf("list str key not found") + } + err = store.DelStringIn(ns, key) if err != nil { t.Errorf("there should be no error %v", err)