fix: remove bolt db stores
This commit is contained in:
parent
f637781d4d
commit
02740bae37
16 changed files with 2 additions and 3142 deletions
|
@ -1,398 +0,0 @@
|
||||||
package boltstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BoltStore struct {
|
|
||||||
boltdb *bolt.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBoltStore(boltdb *bolt.DB) (*BoltStore, error) {
|
|
||||||
bs := &BoltStore{
|
|
||||||
boltdb: boltdb,
|
|
||||||
}
|
|
||||||
return bs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) getUserInfo(tx *bolt.Tx, userID uint64) (*db.User, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
usersBucket := tx.Bucket([]byte(db.UsersNs))
|
|
||||||
if usersBucket == nil {
|
|
||||||
return nil, db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uidStr := fmt.Sprint(userID)
|
|
||||||
infoBytes := usersBucket.Get([]byte(uidStr))
|
|
||||||
if infoBytes == nil {
|
|
||||||
return nil, db.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo := &db.User{}
|
|
||||||
err = json.Unmarshal(infoBytes, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if userInfo.ID != userID {
|
|
||||||
return nil, fmt.Errorf("user id key(%d) info(%d) does match", userID, userInfo.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckUser(userInfo, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) setUserInfo(tx *bolt.Tx, userID uint64, userInfo *db.User) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = db.CheckUser(userInfo, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
usersBucket := tx.Bucket([]byte(db.UsersNs))
|
|
||||||
if usersBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfoBytes, err := json.Marshal(userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uidStr := fmt.Sprint(userID)
|
|
||||||
return usersBucket.Put([]byte(uidStr), userInfoBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) getUploadInfo(tx *bolt.Tx, userID uint64, itemPath string) (*db.UploadInfo, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
uidStr := fmt.Sprint(userID)
|
|
||||||
userUploadNS := db.UploadNS(uidStr)
|
|
||||||
|
|
||||||
uploadInfoBucket := tx.Bucket([]byte(userUploadNS))
|
|
||||||
if uploadInfoBucket == nil {
|
|
||||||
return nil, db.ErrBucketNotFound
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadInfoBytes := uploadInfoBucket.Get([]byte(itemPath))
|
|
||||||
if uploadInfoBytes == nil {
|
|
||||||
return nil, db.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadInfo := &db.UploadInfo{}
|
|
||||||
err = json.Unmarshal(uploadInfoBytes, uploadInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return uploadInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) setUploadInfo(tx *bolt.Tx, userID uint64, uploadPath string, uploadInfo *db.UploadInfo, overWrite bool) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
uidStr := fmt.Sprint(userID)
|
|
||||||
userUploadNS := db.UploadNS(uidStr)
|
|
||||||
uploadInfoBucket := tx.Bucket([]byte(userUploadNS))
|
|
||||||
if uploadInfoBucket == nil {
|
|
||||||
uploadInfoBucket, err = tx.CreateBucket([]byte(userUploadNS))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
existingInfoBytes := uploadInfoBucket.Get([]byte(uploadPath))
|
|
||||||
if existingInfoBytes != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
uploadInfoBytes, err := json.Marshal(uploadInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadInfoBucket.Put([]byte(uploadPath), uploadInfoBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) getFileInfo(tx *bolt.Tx, userID uint64, itemPath string) (*db.FileInfo, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
fileInfoBucket := tx.Bucket([]byte(db.FileInfoNs))
|
|
||||||
if fileInfoBucket == nil {
|
|
||||||
return nil, db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfoBytes := fileInfoBucket.Get([]byte(itemPath))
|
|
||||||
if fileInfoBytes == nil {
|
|
||||||
return nil, db.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfo := &db.FileInfo{}
|
|
||||||
err = json.Unmarshal(fileInfoBytes, fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckFileInfo(fileInfo, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fileInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) setFileInfo(tx *bolt.Tx, userID uint64, itemPath string, fileInfo *db.FileInfo) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = db.CheckFileInfo(fileInfo, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfoBucket := tx.Bucket([]byte(db.FileInfoNs))
|
|
||||||
if fileInfoBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfoBytes, err := json.Marshal(fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileInfoBucket.Put([]byte(itemPath), fileInfoBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) AddUploadInfos(userID uint64, tmpPath, filePath string, info *db.FileInfo) error {
|
|
||||||
return bs.boltdb.Update(func(tx *bolt.Tx) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
_, err = bs.getUploadInfo(tx, userID, tmpPath)
|
|
||||||
if err == nil {
|
|
||||||
return db.ErrKeyExisting
|
|
||||||
} else if !errors.Is(err, db.ErrBucketNotFound) && !errors.Is(err, db.ErrKeyNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check space quota
|
|
||||||
userInfo, err := bs.getUserInfo(tx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if userInfo.UsedSpace+info.Size > int64(userInfo.Quota.SpaceLimit) {
|
|
||||||
return db.ErrQuota
|
|
||||||
}
|
|
||||||
|
|
||||||
// update used space
|
|
||||||
userInfo.UsedSpace += info.Size
|
|
||||||
err = bs.setUserInfo(tx, userID, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add upload info
|
|
||||||
uploadInfo := &db.UploadInfo{
|
|
||||||
RealFilePath: filePath,
|
|
||||||
Size: info.Size,
|
|
||||||
Uploaded: 0,
|
|
||||||
}
|
|
||||||
return bs.setUploadInfo(tx, userID, tmpPath, uploadInfo, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) DelUploadingInfos(userID uint64, uploadPath string) error {
|
|
||||||
return bs.boltdb.Update(func(tx *bolt.Tx) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// delete upload info
|
|
||||||
uidStr := fmt.Sprint(userID)
|
|
||||||
userUploadNS := db.UploadNS(uidStr)
|
|
||||||
|
|
||||||
uploadInfoBucket := tx.Bucket([]byte(userUploadNS))
|
|
||||||
if uploadInfoBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadInfoBytes := uploadInfoBucket.Get([]byte(uploadPath))
|
|
||||||
if uploadInfoBytes == nil {
|
|
||||||
return db.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadInfo := &db.UploadInfo{}
|
|
||||||
err = json.Unmarshal(uploadInfoBytes, uploadInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = uploadInfoBucket.Delete([]byte(uploadPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// decr used space
|
|
||||||
userInfo, err := bs.getUserInfo(tx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo.UsedSpace -= uploadInfo.Size
|
|
||||||
return bs.setUserInfo(tx, userID, userInfo)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) MoveUploadingInfos(userID uint64, uploadPath, itemPath string) error {
|
|
||||||
return bs.boltdb.Update(func(tx *bolt.Tx) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// delete upload info
|
|
||||||
uidStr := fmt.Sprint(userID)
|
|
||||||
userUploadNS := db.UploadNS(uidStr)
|
|
||||||
|
|
||||||
uploadInfoBucket := tx.Bucket([]byte(userUploadNS))
|
|
||||||
if uploadInfoBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadInfoBytes := uploadInfoBucket.Get([]byte(uploadPath))
|
|
||||||
if uploadInfoBytes == nil {
|
|
||||||
return db.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadInfo := &db.UploadInfo{}
|
|
||||||
err = json.Unmarshal(uploadInfoBytes, uploadInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = uploadInfoBucket.Delete([]byte(uploadPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create file info
|
|
||||||
fileInfo := &db.FileInfo{
|
|
||||||
IsDir: false,
|
|
||||||
Size: uploadInfo.Size,
|
|
||||||
}
|
|
||||||
return bs.setFileInfo(tx, userID, itemPath, fileInfo)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) delShareID(tx *bolt.Tx, itemPath string) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
shareIDBucket := tx.Bucket([]byte(db.ShareIDNs))
|
|
||||||
if shareIDBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
shareIDtoDir := map[string]string{}
|
|
||||||
shareIDBucket.ForEach(func(k, v []byte) error {
|
|
||||||
shareIDtoDir[string(k)] = string(v)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// because before this version, shareIDs are not removed correctly
|
|
||||||
// so it iterates all shareIDs and cleans remaining entries
|
|
||||||
for shareID, shareDir := range shareIDtoDir {
|
|
||||||
if shareDir == itemPath {
|
|
||||||
err = shareIDBucket.Delete([]byte(shareID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) DelInfos(userID uint64, itemPath string) error {
|
|
||||||
return bs.boltdb.Update(func(tx *bolt.Tx) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
fileInfoBucket := tx.Bucket([]byte(db.FileInfoNs))
|
|
||||||
if fileInfoBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete children
|
|
||||||
prefixBytes := []byte(itemPath)
|
|
||||||
cur := fileInfoBucket.Cursor()
|
|
||||||
usedSpaceDecr := int64(0)
|
|
||||||
for k, v := cur.Seek(prefixBytes); k != nil && bytes.HasPrefix(k, prefixBytes); k, v = cur.Next() {
|
|
||||||
fileInfo := &db.FileInfo{}
|
|
||||||
err = json.Unmarshal(v, fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
usedSpaceDecr += fileInfo.Size
|
|
||||||
childPath := string(k)
|
|
||||||
err = fileInfoBucket.Delete([]byte(childPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bs.delShareID(tx, childPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decr used space
|
|
||||||
userInfo, err := bs.getUserInfo(tx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userInfo.UsedSpace -= usedSpaceDecr
|
|
||||||
err = bs.setUserInfo(tx, userID, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *BoltStore) MoveInfos(userID uint64, oldPath, newPath string, isDir bool) error {
|
|
||||||
return bs.boltdb.Update(func(tx *bolt.Tx) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
fileInfoBucket := tx.Bucket([]byte(db.FileInfoNs))
|
|
||||||
if fileInfoBucket == nil {
|
|
||||||
return db.ErrBucketNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfo, err := bs.getFileInfo(tx, userID, oldPath)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, db.ErrKeyNotFound) && isDir {
|
|
||||||
// the file info for the dir does not exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete old shareID
|
|
||||||
if isDir {
|
|
||||||
fileInfo.Shared = false
|
|
||||||
fileInfo.ShareID = ""
|
|
||||||
err = bs.delShareID(tx, oldPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete old info
|
|
||||||
err = fileInfoBucket.Delete([]byte(oldPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new info
|
|
||||||
return bs.setFileInfo(tx, userID, newPath, fileInfo)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,293 +0,0 @@
|
||||||
package fileinfostore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
"github.com/ihexxa/quickshare/src/kvstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
InitNs = "Init"
|
|
||||||
InitTimeKey = "initTime"
|
|
||||||
SchemaVerKey = "SchemaVersion"
|
|
||||||
SchemaV1 = "v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// db.ErrEmpty = errors.New("can not hash empty string")
|
|
||||||
// db.ErrFileInfoNotFound = errors.New("file info not found")
|
|
||||||
// db.ErrSharingNotFound = errors.New("sharing id not found")
|
|
||||||
// db.ErrConflicted = errors.New("conflict found in hashing")
|
|
||||||
// db.ErrVerNotFound = errors.New("file info schema version not found")
|
|
||||||
maxHashingTime = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type IFileInfoStore interface {
|
|
||||||
AddSharing(dirPath string) error
|
|
||||||
DelSharing(dirPath string) error
|
|
||||||
GetSharing(dirPath string) (bool, bool)
|
|
||||||
ListSharings(prefix string) (map[string]string, error)
|
|
||||||
GetInfo(itemPath string) (*db.FileInfo, error)
|
|
||||||
SetInfo(itemPath string, info *db.FileInfo) error
|
|
||||||
DelInfo(itemPath string) error
|
|
||||||
SetSha1(itemPath, sign string) error
|
|
||||||
GetInfos(itemPaths []string) (map[string]*db.FileInfo, error)
|
|
||||||
GetSharingDir(hashID string) (string, error)
|
|
||||||
// upload info
|
|
||||||
AddUploadInfo(user, filePath, tmpPath string, fileSize int64) error
|
|
||||||
SetUploadInfo(user, filePath string, newUploaded int64) error
|
|
||||||
GetUploadInfo(user, filePath string) (string, int64, int64, error)
|
|
||||||
DelUploadInfo(user, filePath string) error
|
|
||||||
ListUploadInfo(user string) ([]*db.UploadInfo, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileInfoStore struct {
|
|
||||||
mtx *sync.RWMutex
|
|
||||||
store kvstore.IKVStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFileInfoStore(store kvstore.IKVStore) (*FileInfoStore, error) {
|
|
||||||
var err error
|
|
||||||
for _, nsName := range []string{
|
|
||||||
db.FileSchemaNs,
|
|
||||||
db.FileInfoNs,
|
|
||||||
db.ShareIDNs,
|
|
||||||
} {
|
|
||||||
if !store.HasNamespace(nsName) {
|
|
||||||
if err = store.AddNamespace(nsName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fi := &FileInfoStore{
|
|
||||||
store: store,
|
|
||||||
mtx: &sync.RWMutex{},
|
|
||||||
}
|
|
||||||
return fi, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) getInfo(itemPath string) (*db.FileInfo, error) {
|
|
||||||
infoStr, ok := fi.store.GetStringIn(db.FileInfoNs, itemPath)
|
|
||||||
if !ok {
|
|
||||||
return nil, db.ErrFileInfoNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
info := &db.FileInfo{}
|
|
||||||
err := json.Unmarshal([]byte(infoStr), info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get file info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckFileInfo(info, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) GetFileInfo(ctx context.Context, itemPath string) (*db.FileInfo, error) {
|
|
||||||
return fi.getInfo(itemPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) ListFileInfos(ctx context.Context, itemPaths []string) (map[string]*db.FileInfo, error) {
|
|
||||||
infos := map[string]*db.FileInfo{}
|
|
||||||
for _, itemPath := range itemPaths {
|
|
||||||
info, err := fi.getInfo(itemPath)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, db.ErrFileInfoNotFound) {
|
|
||||||
// TODO: try to make info data consistent with fs
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = db.CheckFileInfo(info, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
infos[itemPath] = info
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) setInfo(itemPath string, info *db.FileInfo) error {
|
|
||||||
if err := db.CheckFileInfo(info, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
infoStr, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("set file info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fi.store.SetStringIn(db.FileInfoNs, itemPath, string(infoStr))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("set file info: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) SetFileInfo(ctx context.Context, itemPath string, info *db.FileInfo) error {
|
|
||||||
return fi.setInfo(itemPath, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) DelFileInfo(ctx context.Context, itemPath string) error {
|
|
||||||
return fi.store.DelStringIn(db.FileInfoNs, itemPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sharings
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) SetSha1(ctx context.Context, itemPath, sign string) error {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
info, err := fi.getInfo(itemPath)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, db.ErrFileInfoNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info = &db.FileInfo{
|
|
||||||
IsDir: false,
|
|
||||||
Shared: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.Sha1 = sign
|
|
||||||
return fi.setInfo(itemPath, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) getShareID(payload string) (string, error) {
|
|
||||||
if len(payload) == 0 {
|
|
||||||
return "", db.ErrEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < maxHashingTime; i++ {
|
|
||||||
msg := strings.Repeat(payload, i+1)
|
|
||||||
h := sha1.New()
|
|
||||||
_, err := io.WriteString(h, msg)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
shareID := fmt.Sprintf("%x", h.Sum(nil))[:7]
|
|
||||||
shareDir, ok := fi.store.GetStringIn(db.ShareIDNs, shareID)
|
|
||||||
if !ok {
|
|
||||||
return shareID, nil
|
|
||||||
} else if ok && shareDir == payload {
|
|
||||||
return shareID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", db.ErrConflicted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) GetSharingDir(ctx context.Context, hashID string) (string, error) {
|
|
||||||
dirPath, ok := fi.store.GetStringIn(db.ShareIDNs, hashID)
|
|
||||||
if !ok {
|
|
||||||
return "", db.ErrSharingNotFound
|
|
||||||
}
|
|
||||||
return dirPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) AddSharing(ctx context.Context, dirPath string) error {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
info, err := fi.getInfo(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, db.ErrFileInfoNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info = &db.FileInfo{
|
|
||||||
IsDir: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: ensure Atomicity
|
|
||||||
shareID, err := fi.getShareID(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = fi.store.SetStringIn(db.ShareIDNs, shareID, dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
info.Shared = true
|
|
||||||
info.ShareID = shareID
|
|
||||||
return fi.setInfo(dirPath, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) DelSharing(ctx context.Context, dirPath string) error {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
info, err := fi.getInfo(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: ensure Atomicity
|
|
||||||
// In the bolt, if the key does not exist
|
|
||||||
// then nothing is done and a nil error is returned
|
|
||||||
|
|
||||||
// because before this version, shareIDs are not removed correctly
|
|
||||||
// so it iterates all shareIDs and cleans remaining entries
|
|
||||||
shareIDtoDir, err := fi.store.ListStringsIn(db.ShareIDNs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for shareID, shareDir := range shareIDtoDir {
|
|
||||||
if shareDir == dirPath {
|
|
||||||
err = fi.store.DelStringIn(db.ShareIDNs, shareID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info.ShareID = ""
|
|
||||||
info.Shared = false
|
|
||||||
return fi.setInfo(dirPath, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) GetSharing(ctx context.Context, dirPath string) (bool, bool) {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
// TODO: differentiate error and not exist
|
|
||||||
info, err := fi.getInfo(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
return info.IsDir && info.Shared, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) ListSharings(ctx context.Context, prefix string) (map[string]string, error) {
|
|
||||||
infoStrs, err := fi.store.ListStringsByPrefixIn(prefix, db.FileInfoNs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := &db.FileInfo{}
|
|
||||||
sharings := map[string]string{}
|
|
||||||
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] = info.ShareID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sharings, nil
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
package fileinfostore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
"github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list sharings
|
|
||||||
prefix := "admin"
|
|
||||||
dirToID, err := store.ListSharings(prefix)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, sharingDir := range dirPaths {
|
|
||||||
if _, ok := dirToID[sharingDir]; !ok {
|
|
||||||
t.Fatalf("sharing(%s) not found", sharingDir)
|
|
||||||
}
|
|
||||||
mustTrue, exist := store.GetSharing(sharingDir)
|
|
||||||
if !mustTrue || !exist {
|
|
||||||
t.Fatalf("get sharing(%t %t) should exist", mustTrue, exist)
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := store.GetInfo(sharingDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if len(info.ShareID) != 7 {
|
|
||||||
t.Fatalf("incorrect ShareID %s", info.ShareID)
|
|
||||||
}
|
|
||||||
|
|
||||||
gotSharingDir, err := store.GetSharingDir(info.ShareID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if sharingDir != gotSharingDir {
|
|
||||||
t.Fatalf("sharing dir not consist: (%s) (%s)", sharingDir, gotSharingDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// del sharings
|
|
||||||
for _, dirPath := range dirPaths {
|
|
||||||
err = store.DelSharing(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dirToIDAfterDel, err := store.ListSharings(prefix)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, dirPath := range dirPaths {
|
|
||||||
if _, ok := dirToIDAfterDel[dirPath]; ok {
|
|
||||||
t.Fatalf("sharing(%s) should not exist", dirPath)
|
|
||||||
}
|
|
||||||
shared, exist := store.GetSharing(dirPath)
|
|
||||||
if shared {
|
|
||||||
t.Fatalf("get sharing(%t, %t) should not shared but exist", shared, exist)
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := store.GetInfo(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if len(info.ShareID) != 0 {
|
|
||||||
t.Fatalf("ShareID should be empty %s", info.ShareID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shareIDs are removed, use original dirToID to get shareID
|
|
||||||
originalShareID, ok := dirToID[dirPath]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("dir (%s) should exist in originalShareID", dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = store.GetSharingDir(originalShareID)
|
|
||||||
if err != ErrSharingNotFound {
|
|
||||||
t.Fatal("should return ErrSharingNotFound")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testInfoMethods := func(t *testing.T, store IFileInfoStore) {
|
|
||||||
pathInfos := map[string]*db.FileInfo{
|
|
||||||
"admin/item": &db.FileInfo{
|
|
||||||
Shared: false,
|
|
||||||
ShareID: "",
|
|
||||||
IsDir: false,
|
|
||||||
Sha1: "file",
|
|
||||||
},
|
|
||||||
"admin/dir": &db.FileInfo{
|
|
||||||
Shared: true,
|
|
||||||
ShareID: "mockedShareID",
|
|
||||||
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 !errors.Is(err, ErrNotFound) {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("testing FileInfoStore", func(t *testing.T) {
|
|
||||||
rootPath, err := ioutil.TempDir("./", "quickshare_userstore_test_")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rootPath)
|
|
||||||
|
|
||||||
dbPath := filepath.Join(rootPath, "quickshare.db")
|
|
||||||
kvstore := boltdbpvd.New(dbPath, 1024)
|
|
||||||
defer kvstore.Close()
|
|
||||||
|
|
||||||
store, err := NewFileInfoStore(kvstore)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("fail to new kvstore", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testSharingMethods(t, store)
|
|
||||||
testInfoMethods(t, store)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
package fileinfostore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrGreaterThanSize = errors.New("uploaded is greater than file size")
|
|
||||||
ErrUploadNotFound = errors.New("upload info not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) getUploadInfo(user, filePath string) (string, int64, int64, error) {
|
|
||||||
ns := db.UploadNS(user)
|
|
||||||
infoBytes, ok := fi.store.GetStringIn(ns, filePath)
|
|
||||||
if !ok {
|
|
||||||
return "", 0, 0, ErrUploadNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
info := &db.UploadInfo{}
|
|
||||||
err := json.Unmarshal([]byte(infoBytes), info)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return info.RealFilePath, info.Size, info.Uploaded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) setUploadInfo(user, filePath string, info *db.UploadInfo) error {
|
|
||||||
newInfoBytes, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fi.store.SetStringIn(db.UploadNS(user), filePath, string(newInfoBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) AddUploadInfo(ctx context.Context, user, filePath, tmpPath string, fileSize int64) error {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
ns := db.UploadNS(user)
|
|
||||||
err := fi.store.AddNamespace(ns)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err = fi.getUploadInfo(user, tmpPath)
|
|
||||||
if err == nil {
|
|
||||||
return db.ErrCreateExisting
|
|
||||||
}
|
|
||||||
|
|
||||||
return fi.setUploadInfo(user, filePath, &db.UploadInfo{
|
|
||||||
RealFilePath: filePath,
|
|
||||||
Size: fileSize,
|
|
||||||
Uploaded: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) SetUploadInfo(ctx context.Context, user, filePath string, newUploaded int64) error {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
realFilePath, fileSize, _, err := fi.getUploadInfo(user, filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if newUploaded > fileSize {
|
|
||||||
return ErrGreaterThanSize
|
|
||||||
}
|
|
||||||
|
|
||||||
return fi.setUploadInfo(user, filePath, &db.UploadInfo{
|
|
||||||
RealFilePath: realFilePath,
|
|
||||||
Size: fileSize,
|
|
||||||
Uploaded: newUploaded,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) GetUploadInfo(ctx context.Context, user, filePath string) (string, int64, int64, error) {
|
|
||||||
fi.mtx.Lock()
|
|
||||||
defer fi.mtx.Unlock()
|
|
||||||
|
|
||||||
return fi.getUploadInfo(user, filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) DelUploadInfo(ctx context.Context, user, filePath string) error {
|
|
||||||
return fi.store.DelInt64In(db.UploadNS(user), filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FileInfoStore) ListUploadInfo(ctx context.Context, user string) ([]*db.UploadInfo, error) {
|
|
||||||
ns := db.UploadNS(user)
|
|
||||||
if !fi.store.HasNamespace(ns) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
infoMap, err := fi.store.ListStringsIn(ns)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
infos := []*db.UploadInfo{}
|
|
||||||
for _, infoStr := range infoMap {
|
|
||||||
info := &db.UploadInfo{}
|
|
||||||
err = json.Unmarshal([]byte(infoStr), info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
infos = append(infos, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos, nil
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package default
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (st *DefaultStore) getCfg(ctx context.Context, tx *sql.Tx) (*db.SiteConfig, error) {
|
|
||||||
var configStr string
|
|
||||||
err := tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select config
|
|
||||||
from t_config
|
|
||||||
where id=0`,
|
|
||||||
).Scan(&configStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &db.SiteConfig{}
|
|
||||||
err = json.Unmarshal([]byte(configStr), config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckSiteCfg(config, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) setCfg(ctx context.Context, tx *sql.Tx, cfg *db.SiteConfig) error {
|
|
||||||
if err := db.CheckSiteCfg(cfg, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfgBytes, err := json.Marshal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_config
|
|
||||||
set config=?
|
|
||||||
where id=0`,
|
|
||||||
string(cfgBytes),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) SetClientCfg(ctx context.Context, cfg *db.ClientConfig) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
siteCfg, err := st.getCfg(ctx, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
siteCfg.ClientCfg = cfg
|
|
||||||
|
|
||||||
err = st.setCfg(ctx, tx, siteCfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) GetCfg(ctx context.Context) (*db.SiteConfig, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
return st.getCfg(ctx, tx)
|
|
||||||
}
|
|
|
@ -1,307 +0,0 @@
|
||||||
package default
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (st *DefaultStore) getFileInfo(ctx context.Context, tx *sql.Tx, itemPath string) (*db.FileInfo, error) {
|
|
||||||
var infoStr string
|
|
||||||
fInfo := &db.FileInfo{}
|
|
||||||
var isDir bool
|
|
||||||
var size int64
|
|
||||||
var shareId string
|
|
||||||
err := tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select is_dir, size, share_id, info
|
|
||||||
from t_file_info
|
|
||||||
where path=?`,
|
|
||||||
itemPath,
|
|
||||||
).Scan(
|
|
||||||
&isDir,
|
|
||||||
&size,
|
|
||||||
&shareId,
|
|
||||||
&infoStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, db.ErrFileInfoNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(infoStr), &fInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fInfo.IsDir = isDir
|
|
||||||
fInfo.Size = size
|
|
||||||
fInfo.ShareID = shareId
|
|
||||||
fInfo.Shared = shareId != ""
|
|
||||||
return fInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) GetFileInfo(ctx context.Context, itemPath string) (*db.FileInfo, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
return st.getFileInfo(ctx, tx, itemPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ListFileInfos(ctx context.Context, itemPaths []string) (map[string]*db.FileInfo, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// TODO: add pagination
|
|
||||||
placeholders := []string{}
|
|
||||||
values := []any{}
|
|
||||||
for i := 0; i < len(itemPaths); i++ {
|
|
||||||
placeholders = append(placeholders, "?")
|
|
||||||
values = append(values, itemPaths[i])
|
|
||||||
}
|
|
||||||
rows, err := tx.QueryContext(
|
|
||||||
ctx,
|
|
||||||
fmt.Sprintf(
|
|
||||||
`select path, is_dir, size, share_id, info
|
|
||||||
from t_file_info
|
|
||||||
where path in (%s)
|
|
||||||
`,
|
|
||||||
strings.Join(placeholders, ","),
|
|
||||||
),
|
|
||||||
values...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var fInfoStr, itemPath, shareId string
|
|
||||||
var isDir bool
|
|
||||||
var size int64
|
|
||||||
fInfos := map[string]*db.FileInfo{}
|
|
||||||
for rows.Next() {
|
|
||||||
fInfo := &db.FileInfo{}
|
|
||||||
err = rows.Scan(&itemPath, &isDir, &size, &shareId, &fInfoStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(fInfoStr), fInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fInfo.IsDir = isDir
|
|
||||||
fInfo.Size = size
|
|
||||||
fInfo.ShareID = shareId
|
|
||||||
fInfo.Shared = shareId != ""
|
|
||||||
fInfos[itemPath] = fInfo
|
|
||||||
}
|
|
||||||
if rows.Err() != nil {
|
|
||||||
return nil, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return fInfos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) addFileInfo(ctx context.Context, tx *sql.Tx, userId uint64, itemPath string, info *db.FileInfo) error {
|
|
||||||
infoStr, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
location, err := getLocation(itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dirPath, itemName := path.Split(itemPath)
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`insert into t_file_info (
|
|
||||||
path, user, location, parent, name,
|
|
||||||
is_dir, size, share_id, info
|
|
||||||
)
|
|
||||||
values (
|
|
||||||
?, ?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?
|
|
||||||
)`,
|
|
||||||
itemPath, userId, location, dirPath, itemName,
|
|
||||||
info.IsDir, info.Size, info.ShareID, infoStr,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) AddFileInfo(ctx context.Context, userId uint64, itemPath string, info *db.FileInfo) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
err = st.addFileInfo(ctx, tx, userId, itemPath, info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// increase used space
|
|
||||||
err = st.setUsed(ctx, tx, userId, true, info.Size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) delFileInfo(ctx context.Context, tx *sql.Tx, itemPath string) error {
|
|
||||||
_, err := tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`delete from t_file_info
|
|
||||||
where path=?
|
|
||||||
`,
|
|
||||||
itemPath,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) SetSha1(ctx context.Context, itemPath, sign string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
info, err := st.getFileInfo(ctx, tx, itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.Sha1 = sign
|
|
||||||
|
|
||||||
infoStr, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_file_info
|
|
||||||
set info=?
|
|
||||||
where path=?`,
|
|
||||||
infoStr,
|
|
||||||
itemPath,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) DelFileInfo(ctx context.Context, userID uint64, itemPath string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// get all children and size
|
|
||||||
rows, err := tx.QueryContext(
|
|
||||||
ctx,
|
|
||||||
`select path, size
|
|
||||||
from t_file_info
|
|
||||||
where path = ? or path like ?
|
|
||||||
`,
|
|
||||||
itemPath,
|
|
||||||
fmt.Sprintf("%s/%%", itemPath),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var childrenPath string
|
|
||||||
var itemSize int64
|
|
||||||
placeholders := []string{}
|
|
||||||
values := []any{}
|
|
||||||
decrSize := int64(0)
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&childrenPath, &itemSize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
placeholders = append(placeholders, "?")
|
|
||||||
values = append(values, childrenPath)
|
|
||||||
decrSize += itemSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrease used space
|
|
||||||
err = st.setUsed(ctx, tx, userID, false, decrSize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete file info entries
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
fmt.Sprintf(
|
|
||||||
`delete from t_file_info
|
|
||||||
where path in (%s)`,
|
|
||||||
strings.Join(placeholders, ","),
|
|
||||||
),
|
|
||||||
values...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) MoveFileInfo(ctx context.Context, userId uint64, oldPath, newPath string, isDir bool) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
info, err := st.getFileInfo(ctx, tx, oldPath)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, db.ErrFileInfoNotFound) {
|
|
||||||
// info for file does not exist so no need to move it
|
|
||||||
// e.g. folder info is not created before
|
|
||||||
// TODO: but sometimes it could be a bug
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = st.delFileInfo(ctx, tx, oldPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = st.addFileInfo(ctx, tx, userId, newPath, info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLocation(itemPath string) (string, error) {
|
|
||||||
// location is taken from item path
|
|
||||||
itemPathParts := strings.Split(itemPath, "/")
|
|
||||||
if len(itemPathParts) == 0 {
|
|
||||||
return "", fmt.Errorf("invalid item path '%s'", itemPath)
|
|
||||||
}
|
|
||||||
return itemPathParts[0], nil
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
package default
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (st *DefaultStore) generateShareID(payload string) (string, error) {
|
|
||||||
if len(payload) == 0 {
|
|
||||||
return "", db.ErrEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("%s-%d", payload, time.Now().Unix())
|
|
||||||
h := sha1.New()
|
|
||||||
_, err := io.WriteString(h, msg)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))[:7], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) IsSharing(ctx context.Context, dirPath string) (bool, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
var shareId string
|
|
||||||
err = tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select share_id
|
|
||||||
from t_file_info
|
|
||||||
where path=?`,
|
|
||||||
dirPath,
|
|
||||||
).Scan(
|
|
||||||
&shareId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return false, db.ErrFileInfoNotFound
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return shareId != "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) GetSharingDir(ctx context.Context, hashID string) (string, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
var sharedPath string
|
|
||||||
err = tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select path
|
|
||||||
from t_file_info
|
|
||||||
where share_id=?
|
|
||||||
`,
|
|
||||||
hashID,
|
|
||||||
).Scan(
|
|
||||||
&sharedPath,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return "", db.ErrSharingNotFound
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sharedPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) AddSharing(ctx context.Context, userId uint64, dirPath string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
shareID, err := st.generateShareID(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
location, err := getLocation(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = st.getFileInfo(ctx, tx, dirPath)
|
|
||||||
if err != nil && !errors.Is(err, db.ErrFileInfoNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, db.ErrFileInfoNotFound) {
|
|
||||||
// insert new
|
|
||||||
parentPath, name := path.Split(dirPath)
|
|
||||||
info := &db.FileInfo{Shared: true} // TODO: deprecate shared in info
|
|
||||||
infoStr, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`insert into t_file_info (
|
|
||||||
path, user, location, parent, name,
|
|
||||||
is_dir, size, share_id, info
|
|
||||||
)
|
|
||||||
values (
|
|
||||||
?, ?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?
|
|
||||||
)`,
|
|
||||||
dirPath, userId, location, parentPath, name,
|
|
||||||
true, 0, shareID, infoStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_file_info
|
|
||||||
set share_id=?
|
|
||||||
where path=?`,
|
|
||||||
shareID, dirPath,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) DelSharing(ctx context.Context, userId uint64, dirPath string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_file_info
|
|
||||||
set share_id=''
|
|
||||||
where path=?`,
|
|
||||||
dirPath,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ListSharingsByLocation(ctx context.Context, location string) (map[string]string, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
rows, err := tx.QueryContext(
|
|
||||||
ctx,
|
|
||||||
`select path, share_id
|
|
||||||
from t_file_info
|
|
||||||
where share_id<>'' and location=?`,
|
|
||||||
location,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var pathname, shareId string
|
|
||||||
pathToShareId := map[string]string{}
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&pathname, &shareId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pathToShareId[pathname] = shareId
|
|
||||||
}
|
|
||||||
if rows.Err() != nil {
|
|
||||||
return nil, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathToShareId, nil
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
package default
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (st *DefaultStore) addUploadInfoOnly(ctx context.Context, tx *sql.Tx, userId uint64, tmpPath, filePath string, fileSize int64) error {
|
|
||||||
_, err := tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`insert into t_file_uploading (
|
|
||||||
real_path, tmp_path, user, size, uploaded
|
|
||||||
)
|
|
||||||
values (
|
|
||||||
?, ?, ?, ?, ?
|
|
||||||
)`,
|
|
||||||
filePath, tmpPath, userId, fileSize, 0,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) AddUploadInfos(ctx context.Context, userId uint64, tmpPath, filePath string, info *db.FileInfo) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
userInfo, err := st.getUser(ctx, tx, userId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if userInfo.UsedSpace+info.Size > int64(userInfo.Quota.SpaceLimit) {
|
|
||||||
return db.ErrQuota
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err = st.getUploadInfo(ctx, tx, userId, filePath)
|
|
||||||
if err == nil {
|
|
||||||
return db.ErrKeyExisting
|
|
||||||
} else if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo.UsedSpace += info.Size
|
|
||||||
err = st.setUser(ctx, tx, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = st.addUploadInfoOnly(ctx, tx, userId, tmpPath, filePath, info.Size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) DelUploadingInfos(ctx context.Context, userId uint64, realPath string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
err = st.delUploadingInfos(ctx, tx, userId, realPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) delUploadingInfos(ctx context.Context, tx *sql.Tx, userId uint64, realPath string) error {
|
|
||||||
_, size, _, err := st.getUploadInfo(ctx, tx, userId, realPath)
|
|
||||||
if err != nil {
|
|
||||||
// info may not exist
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = st.delUploadInfoOnly(ctx, tx, userId, realPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo, err := st.getUser(ctx, tx, userId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userInfo.UsedSpace -= size
|
|
||||||
return st.setUser(ctx, tx, userInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) delUploadInfoOnly(ctx context.Context, tx *sql.Tx, userId uint64, filePath string) error {
|
|
||||||
_, err := tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`delete from t_file_uploading
|
|
||||||
where real_path=? and user=?`,
|
|
||||||
filePath, userId,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) MoveUploadingInfos(ctx context.Context, userId uint64, uploadPath, itemPath string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
_, size, _, err := st.getUploadInfo(ctx, tx, userId, itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = st.delUploadInfoOnly(ctx, tx, userId, itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = st.addFileInfo(ctx, tx, userId, itemPath, &db.FileInfo{
|
|
||||||
Size: size,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) SetUploadInfo(ctx context.Context, userId uint64, filePath string, newUploaded int64) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
var size, uploaded int64
|
|
||||||
err = tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select size, uploaded
|
|
||||||
from t_file_uploading
|
|
||||||
where real_path=? and user=?`,
|
|
||||||
filePath, userId,
|
|
||||||
).Scan(&size, &uploaded)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if newUploaded > size {
|
|
||||||
return db.ErrGreaterThanSize
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_file_uploading
|
|
||||||
set uploaded=?
|
|
||||||
where real_path=? and user=?`,
|
|
||||||
newUploaded, filePath, userId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) getUploadInfo(
|
|
||||||
ctx context.Context, tx *sql.Tx, userId uint64, filePath string,
|
|
||||||
) (string, int64, int64, error) {
|
|
||||||
var size, uploaded int64
|
|
||||||
err := tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select size, uploaded
|
|
||||||
from t_file_uploading
|
|
||||||
where real_path=? and user=?`,
|
|
||||||
filePath, userId,
|
|
||||||
).Scan(&size, &uploaded)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filePath, size, uploaded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) GetUploadInfo(ctx context.Context, userId uint64, filePath string) (string, int64, int64, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, 0, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
return st.getUploadInfo(ctx, tx, userId, filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ListUploadInfos(ctx context.Context, userId uint64) ([]*db.UploadInfo, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
rows, err := tx.QueryContext(
|
|
||||||
ctx,
|
|
||||||
`select real_path, size, uploaded
|
|
||||||
from t_file_uploading
|
|
||||||
where user=?`,
|
|
||||||
userId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var pathname string
|
|
||||||
var size, uploaded int64
|
|
||||||
infos := []*db.UploadInfo{}
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(
|
|
||||||
&pathname,
|
|
||||||
&size,
|
|
||||||
&uploaded,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
infos = append(infos, &db.UploadInfo{
|
|
||||||
RealFilePath: pathname,
|
|
||||||
Size: size,
|
|
||||||
Uploaded: uploaded,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if rows.Err() != nil {
|
|
||||||
return nil, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos, nil
|
|
||||||
}
|
|
|
@ -1,226 +0,0 @@
|
||||||
package default
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DefaultDB struct {
|
|
||||||
db.IDB
|
|
||||||
dbPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultDB(driverName, dbPath string) (*DefaultDB, error) {
|
|
||||||
db, err := sql.Open(driverName, dbPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DefaultDB{
|
|
||||||
IDB: db,
|
|
||||||
dbPath: dbPath,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultStore struct {
|
|
||||||
db db.IDB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultStore(db db.IDB) (*DefaultStore, error) {
|
|
||||||
return &DefaultStore{
|
|
||||||
db: db,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) Close() error {
|
|
||||||
return st.db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) Lock() {
|
|
||||||
st.mtx.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) Unlock() {
|
|
||||||
st.mtx.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) RLock() {
|
|
||||||
st.mtx.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) RUnlock() {
|
|
||||||
st.mtx.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) IsInited() bool {
|
|
||||||
// always try to init the db
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) Init(ctx context.Context, rootName, rootPwd string, cfg *db.SiteConfig) error {
|
|
||||||
err := st.InitUserTable(ctx, rootName, rootPwd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = st.InitFileTables(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return st.InitConfigTable(ctx, cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) InitUserTable(ctx context.Context, rootName, rootPwd string) error {
|
|
||||||
_, err := st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create table if not exists t_user (
|
|
||||||
id bigint not null,
|
|
||||||
name varchar not null unique,
|
|
||||||
pwd varchar not null,
|
|
||||||
role integer not null,
|
|
||||||
used_space bigint not null,
|
|
||||||
quota varchar not null,
|
|
||||||
preference varchar not null,
|
|
||||||
primary key(id)
|
|
||||||
)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create index if not exists i_user_name on t_user (name)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
admin := &db.User{
|
|
||||||
ID: 0,
|
|
||||||
Name: rootName,
|
|
||||||
Pwd: rootPwd,
|
|
||||||
Role: db.AdminRole,
|
|
||||||
Quota: &db.Quota{
|
|
||||||
SpaceLimit: db.DefaultSpaceLimit,
|
|
||||||
UploadSpeedLimit: db.DefaultUploadSpeedLimit,
|
|
||||||
DownloadSpeedLimit: db.DefaultDownloadSpeedLimit,
|
|
||||||
},
|
|
||||||
Preferences: &db.DefaultPreferences,
|
|
||||||
}
|
|
||||||
visitor := &db.User{
|
|
||||||
ID: db.VisitorID,
|
|
||||||
Name: db.VisitorName,
|
|
||||||
Pwd: rootPwd,
|
|
||||||
Role: db.VisitorRole,
|
|
||||||
Quota: &db.Quota{
|
|
||||||
SpaceLimit: 0,
|
|
||||||
UploadSpeedLimit: db.VisitorUploadSpeedLimit,
|
|
||||||
DownloadSpeedLimit: db.VisitorDownloadSpeedLimit,
|
|
||||||
},
|
|
||||||
Preferences: &db.DefaultPreferences,
|
|
||||||
}
|
|
||||||
for _, user := range []*db.User{admin, visitor} {
|
|
||||||
err = st.AddUser(ctx, user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) InitFileTables(ctx context.Context) error {
|
|
||||||
_, err := st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create table if not exists t_file_info (
|
|
||||||
path varchar not null,
|
|
||||||
user bigint not null,
|
|
||||||
location varchar not null,
|
|
||||||
parent varchar not null,
|
|
||||||
name varchar not null,
|
|
||||||
is_dir boolean not null,
|
|
||||||
size bigint not null,
|
|
||||||
share_id varchar not null,
|
|
||||||
info varchar not null,
|
|
||||||
primary key(path)
|
|
||||||
)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create index if not exists t_file_share on t_file_info (share_id, location)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create table if not exists t_file_uploading (
|
|
||||||
real_path varchar not null,
|
|
||||||
tmp_path varchar not null unique,
|
|
||||||
user bigint not null,
|
|
||||||
size bigint not null,
|
|
||||||
uploaded bigint not null,
|
|
||||||
primary key(real_path)
|
|
||||||
)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create index if not exists t_file_uploading_path on t_file_uploading (real_path, user)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create index if not exists t_file_uploading_user on t_file_uploading (user)`,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) InitConfigTable(ctx context.Context, cfg *db.SiteConfig) error {
|
|
||||||
st.Lock()
|
|
||||||
defer st.Unlock()
|
|
||||||
|
|
||||||
_, err := st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`create table if not exists t_config (
|
|
||||||
id bigint not null,
|
|
||||||
config varchar not null,
|
|
||||||
modified datetime not null,
|
|
||||||
primary key(id)
|
|
||||||
)`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfgStr, err := json.Marshal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = st.db.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`insert into t_config
|
|
||||||
(id, config, modified) values (?, ?, ?)`,
|
|
||||||
0, cfgStr, time.Now(),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,418 +0,0 @@
|
||||||
package default
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (st *DefaultStore) setUser(ctx context.Context, tx *sql.Tx, user *db.User) error {
|
|
||||||
var err error
|
|
||||||
if err = db.CheckUser(user, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
quotaStr, err := json.Marshal(user.Quota)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
preferencesStr, err := json.Marshal(user.Preferences)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_user
|
|
||||||
set name=?, pwd=?, role=?, used_space=?, quota=?, preference=?
|
|
||||||
where id=?`,
|
|
||||||
user.Name,
|
|
||||||
user.Pwd,
|
|
||||||
user.Role,
|
|
||||||
user.UsedSpace,
|
|
||||||
quotaStr,
|
|
||||||
preferencesStr,
|
|
||||||
user.ID,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) getUser(ctx context.Context, tx *sql.Tx, id uint64) (*db.User, error) {
|
|
||||||
user := &db.User{}
|
|
||||||
var quotaStr, preferenceStr string
|
|
||||||
err := tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select id, name, pwd, role, used_space, quota, preference
|
|
||||||
from t_user
|
|
||||||
where id=?`,
|
|
||||||
id,
|
|
||||||
).Scan(
|
|
||||||
&user.ID,
|
|
||||||
&user.Name,
|
|
||||||
&user.Pwd,
|
|
||||||
&user.Role,
|
|
||||||
&user.UsedSpace,
|
|
||||||
"aStr,
|
|
||||||
&preferenceStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, db.ErrUserNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(quotaStr), &user.Quota)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(preferenceStr), &user.Preferences)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) AddUser(ctx context.Context, user *db.User) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
quotaStr, err := json.Marshal(user.Quota)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
preferenceStr, err := json.Marshal(user.Preferences)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`insert into t_user (id, name, pwd, role, used_space, quota, preference) values (?, ?, ?, ?, ?, ?, ?)`,
|
|
||||||
user.ID,
|
|
||||||
user.Name,
|
|
||||||
user.Pwd,
|
|
||||||
user.Role,
|
|
||||||
user.UsedSpace,
|
|
||||||
quotaStr,
|
|
||||||
preferenceStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) DelUser(ctx context.Context, id uint64) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`delete from t_user where id=?`,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) GetUser(ctx context.Context, id uint64) (*db.User, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
user, err := st.getUser(ctx, tx, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) GetUserByName(ctx context.Context, name string) (*db.User, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
user := &db.User{}
|
|
||||||
var quotaStr, preferenceStr string
|
|
||||||
err = tx.QueryRowContext(
|
|
||||||
ctx,
|
|
||||||
`select id, name, pwd, role, used_space, quota, preference
|
|
||||||
from t_user
|
|
||||||
where name=?`,
|
|
||||||
name,
|
|
||||||
).Scan(
|
|
||||||
&user.ID,
|
|
||||||
&user.Name,
|
|
||||||
&user.Pwd,
|
|
||||||
&user.Role,
|
|
||||||
&user.UsedSpace,
|
|
||||||
"aStr,
|
|
||||||
&preferenceStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, db.ErrUserNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(quotaStr), &user.Quota)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(preferenceStr), &user.Preferences)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) SetPwd(ctx context.Context, id uint64, pwd string) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_user
|
|
||||||
set pwd=?
|
|
||||||
where id=?`,
|
|
||||||
pwd,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// role + quota
|
|
||||||
func (st *DefaultStore) SetInfo(ctx context.Context, id uint64, user *db.User) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
quotaStr, err := json.Marshal(user.Quota)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_user
|
|
||||||
set role=?, quota=?
|
|
||||||
where id=?`,
|
|
||||||
user.Role, quotaStr,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) SetPreferences(ctx context.Context, id uint64, prefers *db.Preferences) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
preferenceStr, err := json.Marshal(prefers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_user
|
|
||||||
set preference=?
|
|
||||||
where id=?`,
|
|
||||||
preferenceStr,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) SetUsed(ctx context.Context, id uint64, incr bool, capacity int64) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
err = st.setUsed(ctx, tx, id, incr, capacity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) setUsed(ctx context.Context, tx *sql.Tx, id uint64, incr bool, capacity int64) error {
|
|
||||||
gotUser, err := st.getUser(ctx, tx, id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if incr && gotUser.UsedSpace+capacity > int64(gotUser.Quota.SpaceLimit) {
|
|
||||||
return db.ErrReachedLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
if incr {
|
|
||||||
gotUser.UsedSpace = gotUser.UsedSpace + capacity
|
|
||||||
} else {
|
|
||||||
if gotUser.UsedSpace-capacity < 0 {
|
|
||||||
return db.ErrNegtiveUsedSpace
|
|
||||||
}
|
|
||||||
gotUser.UsedSpace = gotUser.UsedSpace - capacity
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_user
|
|
||||||
set used_space=?
|
|
||||||
where id=?`,
|
|
||||||
gotUser.UsedSpace,
|
|
||||||
gotUser.ID,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ResetUsed(ctx context.Context, id uint64, used int64) error {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(
|
|
||||||
ctx,
|
|
||||||
`update t_user
|
|
||||||
set used_space=?
|
|
||||||
where id=?`,
|
|
||||||
used,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) listUsers(ctx context.Context, tx *sql.Tx) ([]*db.User, error) {
|
|
||||||
// TODO: support pagination
|
|
||||||
rows, err := tx.QueryContext(
|
|
||||||
ctx,
|
|
||||||
`select id, name, role, used_space, quota, preference
|
|
||||||
from t_user`,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, db.ErrUserNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close() // TODO: check error
|
|
||||||
|
|
||||||
users := []*db.User{}
|
|
||||||
for rows.Next() {
|
|
||||||
user := &db.User{}
|
|
||||||
var quotaStr, preferenceStr string
|
|
||||||
err = rows.Scan(
|
|
||||||
&user.ID,
|
|
||||||
&user.Name,
|
|
||||||
&user.Role,
|
|
||||||
&user.UsedSpace,
|
|
||||||
"aStr,
|
|
||||||
&preferenceStr,
|
|
||||||
)
|
|
||||||
err = json.Unmarshal([]byte(quotaStr), &user.Quota)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(preferenceStr), &user.Preferences)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
if rows.Err() != nil {
|
|
||||||
return nil, rows.Err()
|
|
||||||
}
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ListUsers(ctx context.Context) ([]*db.User, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
return st.listUsers(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ListUserIDs(ctx context.Context) (map[string]string, error) {
|
|
||||||
tx, err := st.db.BeginTx(ctx, &sql.TxOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
users, err := st.listUsers(ctx, tx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nameToId := map[string]string{}
|
|
||||||
for _, user := range users {
|
|
||||||
nameToId[user.Name] = fmt.Sprint(user.ID)
|
|
||||||
}
|
|
||||||
return nameToId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) AddRole(role string) error {
|
|
||||||
// TODO: implement this after adding grant/revoke
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) DelRole(role string) error {
|
|
||||||
// TODO: implement this after adding grant/revoke
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *DefaultStore) ListRoles() (map[string]bool, error) {
|
|
||||||
// TODO: implement this after adding grant/revoke
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
package sitestore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
"github.com/ihexxa/quickshare/src/kvstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NsSite = "NsSite"
|
|
||||||
KeySiteCfg = "KeySiteCfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotFound = errors.New("site config not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ISiteStore interface {
|
|
||||||
SetClientCfg(cfg *db.ClientConfig) error
|
|
||||||
GetCfg() (*db.SiteConfig, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SiteStore struct {
|
|
||||||
mtx *sync.RWMutex
|
|
||||||
store kvstore.IKVStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSiteStore(store kvstore.IKVStore) (*SiteStore, error) {
|
|
||||||
return &SiteStore{
|
|
||||||
store: store,
|
|
||||||
mtx: &sync.RWMutex{},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *SiteStore) Init(cfg *db.SiteConfig) error {
|
|
||||||
_, ok := st.store.GetStringIn(NsSite, KeySiteCfg)
|
|
||||||
if !ok {
|
|
||||||
var err error
|
|
||||||
if err = st.store.AddNamespace(NsSite); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return st.setCfg(cfg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *SiteStore) getCfg() (*db.SiteConfig, error) {
|
|
||||||
cfgStr, ok := st.store.GetStringIn(NsSite, KeySiteCfg)
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &db.SiteConfig{}
|
|
||||||
err := json.Unmarshal([]byte(cfgStr), cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckSiteCfg(cfg, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *SiteStore) setCfg(cfg *db.SiteConfig) error {
|
|
||||||
if err := db.CheckSiteCfg(cfg, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfgBytes, err := json.Marshal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return st.store.SetStringIn(NsSite, KeySiteCfg, string(cfgBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *SiteStore) SetClientCfg(ctx context.Context, cfg *db.ClientConfig) error {
|
|
||||||
st.mtx.Lock()
|
|
||||||
defer st.mtx.Unlock()
|
|
||||||
|
|
||||||
siteCfg, err := st.getCfg()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
siteCfg.ClientCfg = cfg
|
|
||||||
|
|
||||||
return st.setCfg(siteCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *SiteStore) GetCfg(ctx context.Context) (*db.SiteConfig, error) {
|
|
||||||
st.mtx.RLock()
|
|
||||||
defer st.mtx.RUnlock()
|
|
||||||
|
|
||||||
return st.getCfg()
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
package sitestore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
"github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSiteStore(t *testing.T) {
|
|
||||||
|
|
||||||
testSiteMethods := func(t *testing.T, store ISiteStore) {
|
|
||||||
siteCfg := &db.SiteConfig{
|
|
||||||
ClientCfg: &db.ClientConfig{
|
|
||||||
SiteName: "quickshare",
|
|
||||||
SiteDesc: "simpel file sharing",
|
|
||||||
AllowSetBg: true,
|
|
||||||
AutoTheme: true,
|
|
||||||
Bg: &db.BgConfig{
|
|
||||||
Url: "/imgs/bg.jpg",
|
|
||||||
Repeat: "no-repeat",
|
|
||||||
Position: "center",
|
|
||||||
Align: "fixed",
|
|
||||||
BgColor: "#ccc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := store.SetClientCfg(siteCfg.ClientCfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
newSiteCfg, err := store.GetCfg()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if !reflect.DeepEqual(newSiteCfg, siteCfg) {
|
|
||||||
t.Fatalf("not equal new(%v) original(%v)", newSiteCfg, siteCfg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get/Set config", func(t *testing.T) {
|
|
||||||
rootPath, err := ioutil.TempDir("./", "quickshare_sitestore_test_")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rootPath)
|
|
||||||
|
|
||||||
dbPath := filepath.Join(rootPath, "quickshare.db")
|
|
||||||
kvstore := boltdbpvd.New(dbPath, 1024)
|
|
||||||
defer kvstore.Close()
|
|
||||||
|
|
||||||
store, err := NewSiteStore(kvstore)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("fail to new kvstore", err)
|
|
||||||
}
|
|
||||||
err = store.Init(&db.SiteConfig{
|
|
||||||
ClientCfg: &db.ClientConfig{
|
|
||||||
SiteName: "",
|
|
||||||
SiteDesc: "",
|
|
||||||
AllowSetBg: true,
|
|
||||||
AutoTheme: false,
|
|
||||||
Bg: &db.BgConfig{
|
|
||||||
Url: "/imgs/bg.jpg",
|
|
||||||
Repeat: "repeat",
|
|
||||||
Position: "top",
|
|
||||||
Align: "scroll",
|
|
||||||
BgColor: "#000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testSiteMethods(t, store)
|
|
||||||
})
|
|
||||||
|
|
||||||
testMigrations := func(t *testing.T, store ISiteStore) {
|
|
||||||
autoFilledCfg := &db.SiteConfig{
|
|
||||||
ClientCfg: &db.ClientConfig{
|
|
||||||
SiteName: "Quickshare",
|
|
||||||
SiteDesc: "Quickshare",
|
|
||||||
AllowSetBg: false,
|
|
||||||
AutoTheme: false,
|
|
||||||
Bg: &db.BgConfig{
|
|
||||||
Url: "/imgs/bg.jpg",
|
|
||||||
Repeat: "repeat",
|
|
||||||
Position: "top",
|
|
||||||
Align: "scroll",
|
|
||||||
BgColor: "#000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
siteCfg := &db.SiteConfig{
|
|
||||||
ClientCfg: &db.ClientConfig{
|
|
||||||
SiteName: "quickshare",
|
|
||||||
SiteDesc: "simpel file sharing",
|
|
||||||
AllowSetBg: true,
|
|
||||||
AutoTheme: true,
|
|
||||||
Bg: &db.BgConfig{
|
|
||||||
Url: "/imgs/bg.jpg",
|
|
||||||
Repeat: "no-repeat",
|
|
||||||
Position: "center",
|
|
||||||
Align: "fixed",
|
|
||||||
BgColor: "#ccc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
oldSiteCfg, err := store.GetCfg()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if !reflect.DeepEqual(oldSiteCfg, autoFilledCfg) {
|
|
||||||
oldSiteCfgBytes, _ := json.Marshal(oldSiteCfg)
|
|
||||||
autoFilledCfgBytes, _ := json.Marshal(autoFilledCfg)
|
|
||||||
t.Fatalf("not equal old \n%s\n filled\n%s\n", oldSiteCfgBytes, autoFilledCfgBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.SetClientCfg(siteCfg.ClientCfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
newSiteCfg, err := store.GetCfg()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if !reflect.DeepEqual(newSiteCfg, siteCfg) {
|
|
||||||
t.Fatalf("not equal new(%v) original(%v)", newSiteCfg, siteCfg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Test Migrations", func(t *testing.T) {
|
|
||||||
rootPath, err := ioutil.TempDir("./", "quickshare_sitestore_test_")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rootPath)
|
|
||||||
|
|
||||||
dbPath := filepath.Join(rootPath, "quickshare.db")
|
|
||||||
kvstore := boltdbpvd.New(dbPath, 1024)
|
|
||||||
defer kvstore.Close()
|
|
||||||
|
|
||||||
store, err := NewSiteStore(kvstore)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("fail to new kvstore", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this config does not contain some fields
|
|
||||||
err = store.Init(&db.SiteConfig{
|
|
||||||
ClientCfg: &db.ClientConfig{
|
|
||||||
SiteName: "",
|
|
||||||
SiteDesc: "",
|
|
||||||
// AllowSetBg: true,
|
|
||||||
// AutoTheme: false,
|
|
||||||
Bg: &db.BgConfig{
|
|
||||||
Url: "/imgs/bg.jpg",
|
|
||||||
Repeat: "repeat",
|
|
||||||
Position: "top",
|
|
||||||
Align: "scroll",
|
|
||||||
BgColor: "#000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testMigrations(t, store)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -10,8 +10,6 @@ import (
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
"github.com/ihexxa/quickshare/src/db"
|
||||||
"github.com/ihexxa/quickshare/src/db/rdb/sqlite"
|
"github.com/ihexxa/quickshare/src/db/rdb/sqlite"
|
||||||
"github.com/ihexxa/quickshare/src/db/sitestore"
|
|
||||||
"github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var testSiteConfig = &db.SiteConfig{
|
var testSiteConfig = &db.SiteConfig{
|
||||||
|
@ -61,43 +59,6 @@ func TestSiteStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("config methods basic tests - boltdb", func(t *testing.T) {
|
|
||||||
rootPath, err := ioutil.TempDir("./", "quickshare_sitestore_test_")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rootPath)
|
|
||||||
|
|
||||||
dbPath := filepath.Join(rootPath, "quickshare.db")
|
|
||||||
kvstore := boltdbpvd.New(dbPath, 1024)
|
|
||||||
defer kvstore.Close()
|
|
||||||
|
|
||||||
store, err := sitestore.NewSiteStore(kvstore)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("fail to new kvstore", err)
|
|
||||||
}
|
|
||||||
err = store.Init(&db.SiteConfig{
|
|
||||||
ClientCfg: &db.ClientConfig{
|
|
||||||
SiteName: "",
|
|
||||||
SiteDesc: "",
|
|
||||||
AllowSetBg: true,
|
|
||||||
AutoTheme: false,
|
|
||||||
Bg: &db.BgConfig{
|
|
||||||
Url: "/imgs/bg.jpg",
|
|
||||||
Repeat: "repeat",
|
|
||||||
Position: "top",
|
|
||||||
Align: "scroll",
|
|
||||||
BgColor: "#000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testConfigMethods(t, store)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("config methods basic tests - sqlite", func(t *testing.T) {
|
t.Run("config methods basic tests - sqlite", func(t *testing.T) {
|
||||||
rootPath, err := ioutil.TempDir("./", "qs_sqlite_config_")
|
rootPath, err := ioutil.TempDir("./", "qs_sqlite_config_")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,375 +0,0 @@
|
||||||
package userstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
|
||||||
"github.com/ihexxa/quickshare/src/kvstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: use sync.Pool instead
|
|
||||||
|
|
||||||
const (
|
|
||||||
VisitorID = uint64(1)
|
|
||||||
VisitorName = "visitor"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrReachedLimit = errors.New("reached space limit")
|
|
||||||
ErrUserNotFound = errors.New("user not found")
|
|
||||||
ErrNegtiveUsedSpace = errors.New("used space can not be negative")
|
|
||||||
)
|
|
||||||
|
|
||||||
type KVUserStore struct {
|
|
||||||
store kvstore.IKVStore
|
|
||||||
mtx *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKVUserStore(store kvstore.IKVStore) (*KVUserStore, error) {
|
|
||||||
return &KVUserStore{
|
|
||||||
store: store,
|
|
||||||
mtx: &sync.RWMutex{},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) Init(ctx context.Context, rootName, rootPwd string) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, namespace := range []string{
|
|
||||||
db.UserSchemaNs,
|
|
||||||
db.UserIDsNs,
|
|
||||||
db.UsersNs,
|
|
||||||
db.RolesNs,
|
|
||||||
} {
|
|
||||||
_, ok := us.store.GetStringIn(namespace, db.KeyInitTime)
|
|
||||||
if !ok {
|
|
||||||
if err = us.store.AddNamespace(namespace); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
admin := &db.User{
|
|
||||||
ID: 0,
|
|
||||||
Name: rootName,
|
|
||||||
Pwd: rootPwd,
|
|
||||||
Role: db.AdminRole,
|
|
||||||
Quota: &db.Quota{
|
|
||||||
SpaceLimit: db.DefaultSpaceLimit,
|
|
||||||
UploadSpeedLimit: db.DefaultUploadSpeedLimit,
|
|
||||||
DownloadSpeedLimit: db.DefaultDownloadSpeedLimit,
|
|
||||||
},
|
|
||||||
Preferences: &db.DefaultPreferences,
|
|
||||||
}
|
|
||||||
|
|
||||||
visitor := &db.User{
|
|
||||||
ID: VisitorID,
|
|
||||||
Name: VisitorName,
|
|
||||||
Pwd: rootPwd,
|
|
||||||
Role: db.VisitorRole,
|
|
||||||
Quota: &db.Quota{
|
|
||||||
SpaceLimit: 0,
|
|
||||||
UploadSpeedLimit: db.VisitorUploadSpeedLimit,
|
|
||||||
DownloadSpeedLimit: db.VisitorDownloadSpeedLimit,
|
|
||||||
},
|
|
||||||
Preferences: &db.DefaultPreferences,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range []*db.User{admin, visitor} {
|
|
||||||
err = us.AddUser(context.TODO(), user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, role := range []string{db.AdminRole, db.UserRole, db.VisitorRole} {
|
|
||||||
err = us.AddRole(role)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return us.store.SetStringIn(db.UserSchemaNs, db.KeyInitTime, fmt.Sprintf("%d", time.Now().Unix()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) IsInited() bool {
|
|
||||||
_, ok := us.store.GetStringIn(db.UserSchemaNs, db.KeyInitTime)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) setUser(user *db.User) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = db.CheckUser(user, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userID := fmt.Sprint(user.ID)
|
|
||||||
err = us.store.SetStringIn(db.UserIDsNs, user.Name, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userBytes, err := json.Marshal(user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return us.store.SetStringIn(db.UsersNs, userID, string(userBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) getUser(id uint64) (*db.User, error) {
|
|
||||||
userID := fmt.Sprint(id)
|
|
||||||
userBytes, ok := us.store.GetStringIn(db.UsersNs, userID)
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrUserNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &db.User{}
|
|
||||||
err := json.Unmarshal([]byte(userBytes), user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckUser(user, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) getUserByName(name string) (*db.User, error) {
|
|
||||||
userID, ok := us.store.GetStringIn(db.UserIDsNs, name)
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrUserNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
userBytes, ok := us.store.GetStringIn(db.UsersNs, userID)
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrUserNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &db.User{}
|
|
||||||
err := json.Unmarshal([]byte(userBytes), user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.CheckUser(user, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) AddUser(ctx context.Context, user *db.User) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
return us.setUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) DelUser(ctx context.Context, id uint64) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
user, err := us.getUser(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add complement operations if part of the actions fails
|
|
||||||
err1 := us.store.DelStringIn(db.UserIDsNs, user.Name)
|
|
||||||
err2 := us.store.DelStringIn(db.UsersNs, fmt.Sprint(user.ID))
|
|
||||||
if err1 != nil || err2 != nil {
|
|
||||||
return fmt.Errorf("DelUser: err1(%s) err2(%s)", err1, err2)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) GetUser(ctx context.Context, id uint64) (*db.User, error) {
|
|
||||||
us.mtx.RLock()
|
|
||||||
defer us.mtx.RUnlock()
|
|
||||||
|
|
||||||
return us.getUser(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) GetUserByName(ctx context.Context, name string) (*db.User, error) {
|
|
||||||
us.mtx.RLock()
|
|
||||||
defer us.mtx.RUnlock()
|
|
||||||
|
|
||||||
return us.getUserByName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) SetPwd(ctx context.Context, id uint64, pwd string) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
user, err := us.getUser(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Pwd = pwd
|
|
||||||
return us.setUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) SetInfo(ctx context.Context, id uint64, user *db.User) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
gotUser, err := us.getUser(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gotUser.Role = user.Role
|
|
||||||
gotUser.Quota = user.Quota
|
|
||||||
gotUser.UsedSpace = user.UsedSpace
|
|
||||||
return us.setUser(gotUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) SetPreferences(ctx context.Context, id uint64, prefers *db.Preferences) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
user, err := us.getUser(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Preferences = prefers
|
|
||||||
return us.setUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) SetUsed(ctx context.Context, id uint64, incr bool, capacity int64) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
gotUser, err := us.getUser(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if incr && gotUser.UsedSpace+capacity > int64(gotUser.Quota.SpaceLimit) {
|
|
||||||
return ErrReachedLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
if incr {
|
|
||||||
gotUser.UsedSpace = gotUser.UsedSpace + capacity
|
|
||||||
} else {
|
|
||||||
if gotUser.UsedSpace-capacity < 0 {
|
|
||||||
return ErrNegtiveUsedSpace
|
|
||||||
}
|
|
||||||
gotUser.UsedSpace = gotUser.UsedSpace - capacity
|
|
||||||
}
|
|
||||||
|
|
||||||
return us.setUser(gotUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) ResetUsed(ctx context.Context, id uint64, used int64) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
gotUser, err := us.getUser(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gotUser.UsedSpace = used
|
|
||||||
return us.setUser(gotUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) ListUsers(ctx context.Context) ([]*db.User, error) {
|
|
||||||
us.mtx.RLock()
|
|
||||||
defer us.mtx.RUnlock()
|
|
||||||
|
|
||||||
idToInfo, err := us.store.ListStringsIn(db.UsersNs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
nameToID, err := us.store.ListStringsIn(db.UserIDsNs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
users := []*db.User{}
|
|
||||||
for _, infoStr := range idToInfo {
|
|
||||||
user := &db.User{}
|
|
||||||
err = json.Unmarshal([]byte(infoStr), user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user.Pwd = ""
|
|
||||||
|
|
||||||
if err = db.CheckUser(user, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// redundant check
|
|
||||||
if len(idToInfo) != len(nameToID) {
|
|
||||||
if len(idToInfo) > len(nameToID) {
|
|
||||||
for _, user := range users {
|
|
||||||
_, ok := nameToID[user.Name]
|
|
||||||
if !ok {
|
|
||||||
err = us.store.DelStringIn(db.UsersNs, fmt.Sprint(user.ID))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for name, id := range nameToID {
|
|
||||||
_, ok := idToInfo[id]
|
|
||||||
if !ok {
|
|
||||||
err = us.store.DelStringIn(db.UserIDsNs, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) ListUserIDs(ctx context.Context) (map[string]string, error) {
|
|
||||||
us.mtx.RLock()
|
|
||||||
defer us.mtx.RUnlock()
|
|
||||||
|
|
||||||
return us.store.ListStringsIn(db.UserIDsNs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) AddRole(role string) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
_, ok := us.store.GetBoolIn(db.RolesNs, role)
|
|
||||||
if ok {
|
|
||||||
return fmt.Errorf("role (%s) exists", role)
|
|
||||||
}
|
|
||||||
|
|
||||||
return us.store.SetBoolIn(db.RolesNs, role, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) DelRole(role string) error {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
if role == db.AdminRole || role == db.UserRole || role == db.VisitorRole {
|
|
||||||
return errors.New("predefined roles can not be deleted")
|
|
||||||
}
|
|
||||||
|
|
||||||
return us.store.DelBoolIn(db.RolesNs, role)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *KVUserStore) ListRoles() (map[string]bool, error) {
|
|
||||||
us.mtx.Lock()
|
|
||||||
defer us.mtx.Unlock()
|
|
||||||
|
|
||||||
return us.store.ListBoolsIn(db.RolesNs)
|
|
||||||
}
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
|
|
||||||
"github.com/ihexxa/fsearch"
|
"github.com/ihexxa/fsearch"
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
"github.com/ihexxa/quickshare/src/db"
|
||||||
"github.com/ihexxa/quickshare/src/db/userstore"
|
|
||||||
"github.com/ihexxa/quickshare/src/depidx"
|
"github.com/ihexxa/quickshare/src/depidx"
|
||||||
q "github.com/ihexxa/quickshare/src/handlers"
|
q "github.com/ihexxa/quickshare/src/handlers"
|
||||||
"github.com/ihexxa/quickshare/src/worker/localworker"
|
"github.com/ihexxa/quickshare/src/worker/localworker"
|
||||||
|
@ -679,7 +678,7 @@ func (h *FileHandlers) Download(c *gin.Context) {
|
||||||
dirPath := filepath.Dir(filePath)
|
dirPath := filepath.Dir(filePath)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
userId := userstore.VisitorID
|
userId := db.VisitorID
|
||||||
if role != db.VisitorRole {
|
if role != db.VisitorRole {
|
||||||
userId, err = q.GetUserId(c)
|
userId, err = q.GetUserId(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/db"
|
"github.com/ihexxa/quickshare/src/db"
|
||||||
"github.com/ihexxa/quickshare/src/db/userstore"
|
|
||||||
"github.com/ihexxa/quickshare/src/depidx"
|
"github.com/ihexxa/quickshare/src/depidx"
|
||||||
q "github.com/ihexxa/quickshare/src/handlers"
|
q "github.com/ihexxa/quickshare/src/handlers"
|
||||||
"github.com/ihexxa/quickshare/src/worker/localworker"
|
"github.com/ihexxa/quickshare/src/worker/localworker"
|
||||||
|
@ -248,7 +247,7 @@ func (h *MultiUsersSvc) Login(c *gin.Context) {
|
||||||
|
|
||||||
user, err := h.deps.Users().GetUserByName(c, req.User)
|
user, err := h.deps.Users().GetUserByName(c, req.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, userstore.ErrUserNotFound) {
|
if errors.Is(err, db.ErrUserNotFound) {
|
||||||
c.JSON(q.ErrResp(c, 403, err))
|
c.JSON(q.ErrResp(c, 403, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue