feat(db): replace boltdb with sqlite

This commit is contained in:
hexxa 2022-09-03 18:40:55 +08:00 committed by Hexxa
parent 59a39efc4a
commit 791848f75c
16 changed files with 1749 additions and 543 deletions

View file

@ -23,18 +23,38 @@ const (
AdminRole = "admin"
UserRole = "user"
VisitorRole = "visitor"
VisitorID = uint64(1)
VisitorName = "visitor"
)
var (
// users related errors
ErrReachedLimit = errors.New("reached space limit")
ErrUserNotFound = errors.New("user not found")
ErrNegtiveUsedSpace = errors.New("used space can not be negative")
ErrInvalidFileInfo = errors.New("invalid fileInfo")
ErrInvalidUser = errors.New("invalid user")
ErrInvalidQuota = errors.New("invalid quota")
ErrInvalidPreferences = errors.New("invalid preferences")
ErrBucketNotFound = errors.New("bucket not found")
ErrKeyNotFound = errors.New("key not found")
ErrKeyExisting = errors.New("key is existing")
ErrCreateExisting = errors.New("create upload info which already exists")
ErrQuota = errors.New("quota limit reached")
// files related errors
ErrEmpty = errors.New("can not hash empty string")
ErrFileInfoNotFound = errors.New("file info not found")
ErrSharingNotFound = errors.New("sharing id not found")
ErrConflicted = errors.New("conflict found in hashing")
ErrVerNotFound = errors.New("file info schema version not found")
// uploadings
ErrGreaterThanSize = errors.New("uploaded is greater than file size")
ErrUploadNotFound = errors.New("upload info not found")
// site
ErrConfigNotFound = errors.New("site config not found")
ErrBucketNotFound = errors.New("bucket not found")
ErrKeyNotFound = errors.New("key not found")
ErrKeyExisting = errors.New("key is existing")
ErrCreateExisting = errors.New("create upload info which already exists")
ErrQuota = errors.New("quota limit reached")
DefaultSiteName = "Quickshare"
DefaultSiteDesc = "Quickshare"
@ -93,11 +113,11 @@ var (
)
type FileInfo struct {
IsDir bool `json:"isDir" yaml:"isDir"`
Shared bool `json:"shared" yaml:"shared"`
ShareID string `json:"shareID" yaml:"shareID"`
IsDir bool `json:"isDir" yaml:"isDir"` // deprecated
Shared bool `json:"shared" yaml:"shared"` // deprecated
ShareID string `json:"shareID" yaml:"shareID"` // deprecated
Sha1 string `json:"sha1" yaml:"sha1"`
Size int64 `json:"size" yaml:"size"`
Size int64 `json:"size" yaml:"size"` // deprecated
}
type UserCfg struct {
@ -177,6 +197,30 @@ type IUserStore interface {
ListRoles() (map[string]bool, error)
}
type IFileInfoStore interface {
AddSharing(ctx context.Context, dirPath string) error
DelSharing(ctx context.Context, dirPath string) error
GetSharing(ctx context.Context, dirPath string) (bool, bool)
ListSharings(ctx context.Context, prefix string) (map[string]string, error)
GetFileInfo(ctx context.Context, itemPath string) (*FileInfo, error)
SetFileInfo(ctx context.Context, itemPath string, info *FileInfo) error
DelFileInfo(ctx context.Context, itemPath string) error
ListFileInfos(ctx context.Context, itemPaths []string) (map[string]*FileInfo, error)
SetSha1(ctx context.Context, itemPath, sign string) error
GetSharingDir(ctx context.Context, hashID string) (string, error)
// upload info
AddUploadInfo(ctx context.Context, user, filePath, tmpPath string, fileSize int64) error
SetUploadInfo(ctx context.Context, user, filePath string, newUploaded int64) error
GetUploadInfo(ctx context.Context, user, filePath string) (string, int64, int64, error)
DelUploadInfo(ctx context.Context, user, filePath string) error
ListUploadInfo(ctx context.Context, user string) ([]*UploadInfo, error)
}
type ISiteStore interface {
SetClientCfg(ctx context.Context, cfg *ClientConfig) error
GetCfg(ctx context.Context) (*SiteConfig, error)
}
func ComparePreferences(p1, p2 *Preferences) bool {
return p1.CSSURL == p2.CSSURL &&
p1.LanPackURL == p2.LanPackURL &&

View file

@ -1,6 +1,7 @@
package fileinfostore
import (
"context"
"crypto/sha1"
"encoding/json"
"errors"
@ -21,12 +22,12 @@ const (
)
var (
ErrEmpty = errors.New("can not hash empty string")
ErrNotFound = errors.New("file info not found")
ErrSharingNotFound = errors.New("sharing id not found")
ErrConflicted = errors.New("conflict found in hashing")
ErrVerNotFound = errors.New("file info schema version not found")
maxHashingTime = 10
// 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 {
@ -77,7 +78,7 @@ func NewFileInfoStore(store kvstore.IKVStore) (*FileInfoStore, error) {
func (fi *FileInfoStore) getInfo(itemPath string) (*db.FileInfo, error) {
infoStr, ok := fi.store.GetStringIn(db.FileInfoNs, itemPath)
if !ok {
return nil, ErrNotFound
return nil, db.ErrFileInfoNotFound
}
info := &db.FileInfo{}
@ -92,16 +93,16 @@ func (fi *FileInfoStore) getInfo(itemPath string) (*db.FileInfo, error) {
return info, nil
}
func (fi *FileInfoStore) GetInfo(itemPath string) (*db.FileInfo, error) {
func (fi *FileInfoStore) GetFileInfo(ctx context.Context, itemPath string) (*db.FileInfo, error) {
return fi.getInfo(itemPath)
}
func (fi *FileInfoStore) GetInfos(itemPaths []string) (map[string]*db.FileInfo, error) {
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, ErrNotFound) {
if !errors.Is(err, db.ErrFileInfoNotFound) {
// TODO: try to make info data consistent with fs
return nil, err
}
@ -133,23 +134,23 @@ func (fi *FileInfoStore) setInfo(itemPath string, info *db.FileInfo) error {
return nil
}
func (fi *FileInfoStore) SetInfo(itemPath string, info *db.FileInfo) error {
func (fi *FileInfoStore) SetFileInfo(ctx context.Context, itemPath string, info *db.FileInfo) error {
return fi.setInfo(itemPath, info)
}
func (fi *FileInfoStore) DelInfo(itemPath string) error {
func (fi *FileInfoStore) DelFileInfo(ctx context.Context, itemPath string) error {
return fi.store.DelStringIn(db.FileInfoNs, itemPath)
}
// sharings
func (fi *FileInfoStore) SetSha1(itemPath, sign string) error {
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, ErrNotFound) {
if !errors.Is(err, db.ErrFileInfoNotFound) {
return err
}
info = &db.FileInfo{
@ -163,7 +164,7 @@ func (fi *FileInfoStore) SetSha1(itemPath, sign string) error {
func (fi *FileInfoStore) getShareID(payload string) (string, error) {
if len(payload) == 0 {
return "", ErrEmpty
return "", db.ErrEmpty
}
for i := 0; i < maxHashingTime; i++ {
@ -183,24 +184,24 @@ func (fi *FileInfoStore) getShareID(payload string) (string, error) {
}
}
return "", ErrConflicted
return "", db.ErrConflicted
}
func (fi *FileInfoStore) GetSharingDir(hashID string) (string, error) {
func (fi *FileInfoStore) GetSharingDir(ctx context.Context, hashID string) (string, error) {
dirPath, ok := fi.store.GetStringIn(db.ShareIDNs, hashID)
if !ok {
return "", ErrSharingNotFound
return "", db.ErrSharingNotFound
}
return dirPath, nil
}
func (fi *FileInfoStore) AddSharing(dirPath string) error {
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, ErrNotFound) {
if !errors.Is(err, db.ErrFileInfoNotFound) {
return err
}
info = &db.FileInfo{
@ -223,7 +224,7 @@ func (fi *FileInfoStore) AddSharing(dirPath string) error {
return fi.setInfo(dirPath, info)
}
func (fi *FileInfoStore) DelSharing(dirPath string) error {
func (fi *FileInfoStore) DelSharing(ctx context.Context, dirPath string) error {
fi.mtx.Lock()
defer fi.mtx.Unlock()
@ -257,7 +258,7 @@ func (fi *FileInfoStore) DelSharing(dirPath string) error {
return fi.setInfo(dirPath, info)
}
func (fi *FileInfoStore) GetSharing(dirPath string) (bool, bool) {
func (fi *FileInfoStore) GetSharing(ctx context.Context, dirPath string) (bool, bool) {
fi.mtx.Lock()
defer fi.mtx.Unlock()
@ -269,7 +270,7 @@ func (fi *FileInfoStore) GetSharing(dirPath string) (bool, bool) {
return info.IsDir && info.Shared, true
}
func (fi *FileInfoStore) ListSharings(prefix string) (map[string]string, error) {
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

View file

@ -1,6 +1,7 @@
package fileinfostore
import (
"context"
"encoding/json"
"errors"
@ -36,7 +37,7 @@ func (fi *FileInfoStore) setUploadInfo(user, filePath string, info *db.UploadInf
return fi.store.SetStringIn(db.UploadNS(user), filePath, string(newInfoBytes))
}
func (fi *FileInfoStore) AddUploadInfo(user, filePath, tmpPath string, fileSize int64) error {
func (fi *FileInfoStore) AddUploadInfo(ctx context.Context, user, filePath, tmpPath string, fileSize int64) error {
fi.mtx.Lock()
defer fi.mtx.Unlock()
@ -58,7 +59,7 @@ func (fi *FileInfoStore) AddUploadInfo(user, filePath, tmpPath string, fileSize
})
}
func (fi *FileInfoStore) SetUploadInfo(user, filePath string, newUploaded int64) error {
func (fi *FileInfoStore) SetUploadInfo(ctx context.Context, user, filePath string, newUploaded int64) error {
fi.mtx.Lock()
defer fi.mtx.Unlock()
@ -76,18 +77,18 @@ func (fi *FileInfoStore) SetUploadInfo(user, filePath string, newUploaded int64)
})
}
func (fi *FileInfoStore) GetUploadInfo(user, filePath string) (string, int64, int64, error) {
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(user, filePath string) error {
func (fi *FileInfoStore) DelUploadInfo(ctx context.Context, user, filePath string) error {
return fi.store.DelInt64In(db.UploadNS(user), filePath)
}
func (fi *FileInfoStore) ListUploadInfo(user string) ([]*db.UploadInfo, error) {
func (fi *FileInfoStore) ListUploadInfo(ctx context.Context, user string) ([]*db.UploadInfo, error) {
ns := db.UploadNS(user)
if !fi.store.HasNamespace(ns) {
return nil, nil

92
src/db/interface.go Normal file
View file

@ -0,0 +1,92 @@
package db
import (
"context"
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
// TODO: expose more APIs if needed
type IDB interface {
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
Close() error
PingContext(ctx context.Context) error
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
// Conn(ctx context.Context) (*Conn, error)
// Driver() driver.Driver
// SetConnMaxIdleTime(d time.Duration)
// SetConnMaxLifetime(d time.Duration)
// SetMaxIdleConns(n int)
// SetMaxOpenConns(n int)
// Stats() DBStats
}
type IDBFunctions interface {
Init(ctx context.Context, adminName, adminPwd string, config *SiteConfig) error
IDBLockable
IUserDB
IFileDB
IUploadDB
ISharingDB
IConfigDB
}
type IDBLockable interface {
Lock()
Unlock()
RLock()
RUnlock()
}
type IUserDB interface {
IsInited() bool
AddUser(ctx context.Context, user *User) error
DelUser(ctx context.Context, id uint64) error
GetUser(ctx context.Context, id uint64) (*User, error)
GetUserByName(ctx context.Context, name string) (*User, error)
SetPwd(ctx context.Context, id uint64, pwd string) error
SetInfo(ctx context.Context, id uint64, user *User) error
SetPreferences(ctx context.Context, id uint64, prefers *Preferences) error
SetUsed(ctx context.Context, id uint64, incr bool, capacity int64) error
ResetUsed(ctx context.Context, id uint64, used int64) error
ListUsers(ctx context.Context) ([]*User, error)
ListUserIDs(ctx context.Context) (map[string]string, error)
AddRole(role string) error
DelRole(role string) error
ListRoles() (map[string]bool, error)
}
type IFileDB interface {
AddFileInfo(ctx context.Context, userId uint64, itemPath string, info *FileInfo) error
// DelFileInfo(ctx context.Context, itemPath string) error
DelFileInfo(ctx context.Context, userId uint64, itemPath string) error
GetFileInfo(ctx context.Context, userId uint64, itemPath string) (*FileInfo, error)
SetSha1(ctx context.Context, userId uint64, itemPath, sign string) error
MoveFileInfos(ctx context.Context, userID uint64, oldPath, newPath string, isDir bool) error
ListFileInfos(ctx context.Context, itemPaths []string) (map[string]*FileInfo, error)
}
type IUploadDB interface {
AddUploadInfos(ctx context.Context, userId uint64, tmpPath, filePath string, info *FileInfo) error
DelUploadingInfos(ctx context.Context, userId uint64, realPath string) error
// MoveUploadingInfos(ctx context.Context, userId uint64, uploadPath, itemPath string) error
SetUploadInfo(ctx context.Context, user uint64, filePath string, newUploaded int64) error
GetUploadInfo(ctx context.Context, userId uint64, filePath string) (string, int64, int64, error)
ListUploadInfos(ctx context.Context, user uint64) ([]*UploadInfo, error)
}
type ISharingDB interface {
IsSharing(ctx context.Context, userId uint64, dirPath string) bool
GetSharingDir(ctx context.Context, hashID string) (string, error)
AddSharing(ctx context.Context, userId uint64, dirPath string) error
DelSharing(ctx context.Context, userId uint64, dirPath string) error
ListUserSharings(ctx context.Context, userId uint64) (map[string]string, error)
}
type IConfigDB interface {
SetClientCfg(ctx context.Context, cfg *ClientConfig) error
GetCfg(ctx context.Context) (*SiteConfig, error)
}

View file

@ -1,25 +0,0 @@
package rdb
import (
"context"
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
// TODO: expose more APIs if needed
type IDB interface {
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
Close() error
PingContext(ctx context.Context) error
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
// Conn(ctx context.Context) (*Conn, error)
// Driver() driver.Driver
// SetConnMaxIdleTime(d time.Duration)
// SetConnMaxLifetime(d time.Duration)
// SetMaxIdleConns(n int)
// SetMaxOpenConns(n int)
// Stats() DBStats
}

View file

@ -0,0 +1,72 @@
package sqlite
import (
"context"
"encoding/json"
"github.com/ihexxa/quickshare/src/db"
)
func (st *SQLiteStore) getCfg(ctx context.Context) (*db.SiteConfig, error) {
var configStr string
err := st.db.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 *SQLiteStore) setCfg(ctx context.Context, 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 = st.db.ExecContext(
ctx,
`update t_config
set config=?
where id=0`,
string(cfgBytes),
)
return err
}
func (st *SQLiteStore) SetClientCfg(ctx context.Context, cfg *db.ClientConfig) error {
st.Lock()
defer st.Unlock()
siteCfg, err := st.getCfg(ctx)
if err != nil {
return err
}
siteCfg.ClientCfg = cfg
return st.setCfg(ctx, siteCfg)
}
func (st *SQLiteStore) GetCfg(ctx context.Context) (*db.SiteConfig, error) {
st.RLock()
defer st.RUnlock()
return st.getCfg(ctx)
}

276
src/db/rdb/sqlite/files.go Normal file
View file

@ -0,0 +1,276 @@
package sqlite
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"path"
"strings"
"github.com/ihexxa/quickshare/src/db"
)
const (
InitNs = "Init"
InitTimeKey = "initTime"
SchemaVerKey = "SchemaVersion"
SchemaV1 = "v1"
)
var (
maxHashingTime = 10
)
func (st *SQLiteStore) getFileInfo(ctx context.Context, userId uint64, itemPath string) (*db.FileInfo, error) {
var infoStr string
fInfo := &db.FileInfo{}
var isDir bool
var size int64
var shareId string
err := st.db.QueryRowContext(
ctx,
`select is_dir, size, share_id, info
from t_file_info
where path=? and user=?
`,
itemPath,
userId,
).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 *SQLiteStore) GetFileInfo(ctx context.Context, userId uint64, itemPath string) (*db.FileInfo, error) {
st.RLock()
defer st.RUnlock()
return st.getFileInfo(ctx, userId, itemPath)
}
func (st *SQLiteStore) ListFileInfos(ctx context.Context, itemPaths []string) (map[string]*db.FileInfo, error) {
st.RLock()
defer st.RUnlock()
// TODO: add pagination
placeholders := []string{}
values := []any{}
for i := 0; i < len(itemPaths); i++ {
placeholders = append(placeholders, "?")
values = append(values, itemPaths[i])
}
rows, err := st.db.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 *SQLiteStore) addFileInfo(ctx context.Context, userId uint64, itemPath string, info *db.FileInfo) error {
infoStr, err := json.Marshal(info)
if err != nil {
return err
}
dirPath, itemName := path.Split(itemPath)
_, err = st.db.ExecContext(
ctx,
`insert into t_file_info
(path, user, parent, name, is_dir, size, share_id, info) values (?, ?, ?, ?, ?, ?, ?, ?)`,
itemPath,
userId,
dirPath,
itemName,
info.IsDir,
info.Size,
info.ShareID,
infoStr,
)
return err
}
func (st *SQLiteStore) AddFileInfo(ctx context.Context, userId uint64, itemPath string, info *db.FileInfo) error {
st.Lock()
defer st.Unlock()
err := st.addFileInfo(ctx, userId, itemPath, info)
if err != nil {
return err
}
// increase used space
return st.setUsed(ctx, userId, true, info.Size)
}
func (st *SQLiteStore) delFileInfo(ctx context.Context, userId uint64, itemPath string) error {
_, err := st.db.ExecContext(
ctx,
`delete from t_file_info
where path=? and user=?
`,
itemPath,
userId,
)
return err
}
// func (st *SQLiteStore) DelFileInfo(ctx context.Context, itemPath string) error {
// st.Lock()
// defer st.Unlock()
// return st.delFileInfo(ctx, itemPath)
// }
// sharings
func (st *SQLiteStore) SetSha1(ctx context.Context, userId uint64, itemPath, sign string) error {
st.Lock()
defer st.Unlock()
info, err := st.getFileInfo(ctx, userId, itemPath)
if err != nil {
return err
}
info.Sha1 = sign
infoStr, err := json.Marshal(info)
if err != nil {
return err
}
_, err = st.db.ExecContext(
ctx,
`update t_file_info
set info=?
where path=? and user=?`,
infoStr,
itemPath,
userId,
)
return err
}
func (st *SQLiteStore) DelFileInfo(ctx context.Context, userID uint64, itemPath string) error {
st.Lock()
defer st.Unlock()
// get all children and size
rows, err := st.db.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, userID, false, decrSize)
if err != nil {
return err
}
// delete file info entries
_, err = st.db.ExecContext(
ctx,
fmt.Sprintf(
`delete from t_file_info
where path in (%s)`,
strings.Join(placeholders, ","),
),
values...,
)
return err
}
func (st *SQLiteStore) MoveFileInfos(ctx context.Context, userId uint64, oldPath, newPath string, isDir bool) error {
st.Lock()
defer st.Unlock()
info, err := st.getFileInfo(ctx, userId, oldPath)
if err != nil {
return err
}
err = st.delFileInfo(ctx, userId, oldPath)
if err != nil {
return err
}
return st.addFileInfo(ctx, userId, newPath, info)
}

View file

@ -0,0 +1,156 @@
package sqlite
import (
"context"
"crypto/sha1"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"path"
"time"
"github.com/ihexxa/quickshare/src/db"
)
func (st *SQLiteStore) 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 *SQLiteStore) IsSharing(ctx context.Context, userId uint64, dirPath string) bool {
st.RLock()
defer st.RUnlock()
// TODO: differentiate error and not exist
info, err := st.getFileInfo(ctx, userId, dirPath)
if err != nil {
return false
}
return info.ShareID != ""
}
func (st *SQLiteStore) GetSharingDir(ctx context.Context, hashID string) (string, error) {
st.RLock()
defer st.RUnlock()
var sharedPath string
err := st.db.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 *SQLiteStore) AddSharing(ctx context.Context, userId uint64, dirPath string) error {
st.Lock()
defer st.Unlock()
shareID, err := st.generateShareID(dirPath)
if err != nil {
return err
}
_, err = st.getFileInfo(ctx, userId, 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 = st.db.ExecContext(
ctx,
`insert into t_file_info
(path, user, parent, name, is_dir, size, share_id, info) values (?, ?, ?, ?, ?, ?, ?, ?)`,
dirPath, userId, parentPath, name, true, 0, shareID, infoStr,
)
return err
}
_, err = st.db.ExecContext(
ctx,
`update t_file_info
set share_id=?
where path=? and user=?`,
shareID, dirPath, userId,
)
return err
}
func (st *SQLiteStore) DelSharing(ctx context.Context, userId uint64, dirPath string) error {
st.Lock()
defer st.Unlock()
_, err := st.db.ExecContext(
ctx,
`update t_file_info
set share_id=''
where path=? and user=?`,
dirPath,
userId,
)
return err
}
func (st *SQLiteStore) ListUserSharings(ctx context.Context, userId uint64) (map[string]string, error) {
st.RLock()
defer st.RUnlock()
rows, err := st.db.QueryContext(
ctx,
`select path, share_id
from t_file_info
where user=? and share_id <> ''`,
userId,
)
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
}

View file

@ -0,0 +1,192 @@
package sqlite
import (
"context"
"database/sql"
"errors"
"github.com/ihexxa/quickshare/src/db"
)
func (st *SQLiteStore) addUploadInfoOnly(ctx context.Context, userId uint64, filePath, tmpPath string, fileSize int64) error {
_, err := st.db.ExecContext(
ctx,
`insert into t_file_uploading
(real_path, tmp_path, user, size, uploaded) values (?, ?, ?, ?, ?)`,
filePath, tmpPath, userId, fileSize, 0,
)
return err
}
func (st *SQLiteStore) AddUploadInfos(ctx context.Context, userId uint64, tmpPath, filePath string, info *db.FileInfo) error {
st.Lock()
defer st.Unlock()
userInfo, err := st.getUser(ctx, userId)
if err != nil {
return err
} else if userInfo.UsedSpace+info.Size > int64(userInfo.Quota.SpaceLimit) {
return db.ErrQuota
}
_, _, _, err = st.getUploadInfo(ctx, 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, userInfo)
if err != nil {
return err
}
return st.addUploadInfoOnly(ctx, userId, filePath, tmpPath, info.Size)
}
func (st *SQLiteStore) DelUploadingInfos(ctx context.Context, userId uint64, realPath string) error {
st.Lock()
defer st.Unlock()
return st.delUploadingInfos(ctx, userId, realPath)
}
func (st *SQLiteStore) delUploadingInfos(ctx context.Context, userId uint64, realPath string) error {
_, size, _, err := st.getUploadInfo(ctx, userId, realPath)
if err != nil {
// info may not exist
return err
}
err = st.delUploadInfoOnly(ctx, userId, realPath)
if err != nil {
return err
}
userInfo, err := st.getUser(ctx, userId)
if err != nil {
return err
}
userInfo.UsedSpace -= size
return st.setUser(ctx, userInfo)
}
func (st *SQLiteStore) delUploadInfoOnly(ctx context.Context, userId uint64, filePath string) error {
_, err := st.db.ExecContext(
ctx,
`delete from t_file_uploading
where real_path=? and user=?`,
filePath, userId,
)
return err
}
// func (st *SQLiteStore) MoveUploadingInfos(ctx context.Context, userId uint64, uploadPath, itemPath string) error {
// st.Lock()
// defer st.Unlock()
// _, size, _, err := st.getUploadInfo(ctx, userId, itemPath)
// if err != nil {
// return err
// }
// err = st.delUploadInfoOnly(ctx, userId, itemPath)
// if err != nil {
// return err
// }
// return st.addFileInfo(ctx, userId, itemPath, &db.FileInfo{
// Size: size,
// })
// }
func (st *SQLiteStore) SetUploadInfo(ctx context.Context, userId uint64, filePath string, newUploaded int64) error {
st.Lock()
defer st.Unlock()
var size, uploaded int64
err := st.db.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 = st.db.ExecContext(
ctx,
`update t_file_uploading
set uploaded=?
where real_path=? and user=?`,
newUploaded, filePath, userId,
)
return err
}
func (st *SQLiteStore) getUploadInfo(ctx context.Context, userId uint64, filePath string) (string, int64, int64, error) {
var size, uploaded int64
err := st.db.QueryRowContext(
ctx,
`select size, uploaded
from t_file_uploading
where user=? and real_path=?`,
userId, filePath,
).Scan(&size, &uploaded)
if err != nil {
return "", 0, 0, err
}
return filePath, size, uploaded, nil
}
func (st *SQLiteStore) GetUploadInfo(ctx context.Context, userId uint64, filePath string) (string, int64, int64, error) {
st.RLock()
defer st.RUnlock()
return st.getUploadInfo(ctx, userId, filePath)
}
func (st *SQLiteStore) ListUploadInfos(ctx context.Context, userId uint64) ([]*db.UploadInfo, error) {
st.RLock()
defer st.RUnlock()
rows, err := st.db.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
}

View file

@ -1,16 +1,21 @@
package sqlite
import (
"context"
"database/sql"
"encoding/json"
"fmt"
_ "github.com/mattn/go-sqlite3"
"sync"
"time"
"github.com/ihexxa/quickshare/src/db/rdb"
"github.com/ihexxa/quickshare/src/db"
_ "github.com/mattn/go-sqlite3"
)
type SQLite struct {
rdb.IDB
db.IDB
dbPath string
mtx *sync.RWMutex
}
func NewSQLite(dbPath string) (*SQLite, error) {
@ -24,3 +29,159 @@ func NewSQLite(dbPath string) (*SQLite, error) {
dbPath: dbPath,
}, nil
}
func NewSQLiteStore(db db.IDB) (*SQLiteStore, error) {
return &SQLiteStore{
db: db,
mtx: &sync.RWMutex{},
}, nil
}
func (st *SQLiteStore) Lock() {
st.mtx.Lock()
}
func (st *SQLiteStore) Unlock() {
st.mtx.Unlock()
}
func (st *SQLiteStore) RLock() {
st.mtx.RLock()
}
func (st *SQLiteStore) RUnlock() {
st.mtx.RUnlock()
}
func (st *SQLiteStore) IsInited() bool {
// always try to init the db
return false
}
func (st *SQLiteStore) 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 *SQLiteStore) 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,
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
}
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 *SQLiteStore) 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,
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 table if not exists t_file_uploading (
real_path varchar not null,
tmp_path varchar not null,
user bigint not null,
size bigint not null,
uploaded bigint not null,
primary key(real_path)
)`,
)
return err
}
func (st *SQLiteStore) 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
}

View file

@ -5,97 +5,18 @@ import (
"database/sql"
"encoding/json"
"errors"
// "errors"
"fmt"
// "sync"
// "time"
"sync"
"github.com/ihexxa/quickshare/src/db"
"github.com/ihexxa/quickshare/src/db/rdb"
// "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 SQLiteUsers struct {
db rdb.IDB
type SQLiteStore struct {
db db.IDB
mtx *sync.RWMutex
}
func NewSQLiteUsers(db rdb.IDB) (*SQLiteUsers, error) {
return &SQLiteUsers{db: db}, nil
}
func (u *SQLiteUsers) Init(ctx context.Context, rootName, rootPwd string) error {
_, err := u.db.ExecContext(
ctx,
`create table if not exists t_user (
id bigint not null,
name varchar not null,
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
}
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 = u.AddUser(ctx, user)
if err != nil {
return err
}
}
return nil
}
func (u *SQLiteUsers) IsInited() bool {
// always try to init the db
return false
}
// t_users
// id, name, pwd, role, used_space, config
func (u *SQLiteUsers) setUser(ctx context.Context, tx *sql.Tx, user *db.User) error {
func (st *SQLiteStore) setUser(ctx context.Context, user *db.User) error {
var err error
if err = db.CheckUser(user, false); err != nil {
return err
@ -109,7 +30,7 @@ func (u *SQLiteUsers) setUser(ctx context.Context, tx *sql.Tx, user *db.User) er
if err != nil {
return err
}
_, err = tx.ExecContext(
_, err = st.db.ExecContext(
ctx,
`update t_user
set name=?, pwd=?, role=?, used_space=?, quota=?, preference=?
@ -120,16 +41,15 @@ func (u *SQLiteUsers) setUser(ctx context.Context, tx *sql.Tx, user *db.User) er
user.UsedSpace,
quotaStr,
preferencesStr,
user.ID,
)
return err
}
func (u *SQLiteUsers) getUser(ctx context.Context, tx *sql.Tx, id uint64) (*db.User, error) {
var err error
func (st *SQLiteStore) getUser(ctx context.Context, id uint64) (*db.User, error) {
user := &db.User{}
var quotaStr, preferenceStr string
err = tx.QueryRowContext(
err := st.db.QueryRowContext(
ctx,
`select id, name, pwd, role, used_space, quota, preference
from t_user
@ -146,7 +66,7 @@ func (u *SQLiteUsers) getUser(ctx context.Context, tx *sql.Tx, id uint64) (*db.U
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
return nil, db.ErrUserNotFound
}
return nil, err
}
@ -162,7 +82,10 @@ func (u *SQLiteUsers) getUser(ctx context.Context, tx *sql.Tx, id uint64) (*db.U
return user, nil
}
func (u *SQLiteUsers) AddUser(ctx context.Context, user *db.User) error {
func (st *SQLiteStore) AddUser(ctx context.Context, user *db.User) error {
st.Lock()
defer st.Unlock()
quotaStr, err := json.Marshal(user.Quota)
if err != nil {
return err
@ -171,7 +94,7 @@ func (u *SQLiteUsers) AddUser(ctx context.Context, user *db.User) error {
if err != nil {
return err
}
_, err = u.db.ExecContext(
_, err = st.db.ExecContext(
ctx,
`insert into t_user (id, name, pwd, role, used_space, quota, preference) values (?, ?, ?, ?, ?, ?, ?)`,
user.ID,
@ -185,8 +108,11 @@ func (u *SQLiteUsers) AddUser(ctx context.Context, user *db.User) error {
return err
}
func (u *SQLiteUsers) DelUser(ctx context.Context, id uint64) error {
_, err := u.db.ExecContext(
func (st *SQLiteStore) DelUser(ctx context.Context, id uint64) error {
st.Lock()
defer st.Unlock()
_, err := st.db.ExecContext(
ctx,
`delete from t_user where id=?`,
id,
@ -194,36 +120,34 @@ func (u *SQLiteUsers) DelUser(ctx context.Context, id uint64) error {
return err
}
func (u *SQLiteUsers) GetUser(ctx context.Context, id uint64) (*db.User, error) {
tx, err := u.db.BeginTx(ctx, &sql.TxOptions{})
func (st *SQLiteStore) GetUser(ctx context.Context, id uint64) (*db.User, error) {
st.RLock()
defer st.RUnlock()
user, err := st.getUser(ctx, id)
if err != nil {
return nil, err
}
user, err := u.getUser(ctx, tx, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return user, err
}
func (u *SQLiteUsers) GetUserByName(ctx context.Context, name string) (*db.User, error) {
func (st *SQLiteStore) GetUserByName(ctx context.Context, name string) (*db.User, error) {
st.RLock()
defer st.RUnlock()
user := &db.User{}
var quotaStr, preferenceStr string
err := u.db.QueryRowContext(
err := st.db.QueryRowContext(
ctx,
`select id, name, role, used_space, quota, preference
`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,
&quotaStr,
@ -231,7 +155,7 @@ func (u *SQLiteUsers) GetUserByName(ctx context.Context, name string) (*db.User,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
return nil, db.ErrUserNotFound
}
return nil, err
}
@ -247,8 +171,11 @@ func (u *SQLiteUsers) GetUserByName(ctx context.Context, name string) (*db.User,
return user, nil
}
func (u *SQLiteUsers) SetPwd(ctx context.Context, id uint64, pwd string) error {
_, err := u.db.ExecContext(
func (st *SQLiteStore) SetPwd(ctx context.Context, id uint64, pwd string) error {
st.Lock()
defer st.Unlock()
_, err := st.db.ExecContext(
ctx,
`update t_user
set pwd=?
@ -260,13 +187,16 @@ func (u *SQLiteUsers) SetPwd(ctx context.Context, id uint64, pwd string) error {
}
// role + quota
func (u *SQLiteUsers) SetInfo(ctx context.Context, id uint64, user *db.User) error {
func (st *SQLiteStore) SetInfo(ctx context.Context, id uint64, user *db.User) error {
st.Lock()
defer st.Unlock()
quotaStr, err := json.Marshal(user.Quota)
if err != nil {
return err
}
_, err = u.db.ExecContext(
_, err = st.db.ExecContext(
ctx,
`update t_user
set role=?, quota=?
@ -277,13 +207,16 @@ func (u *SQLiteUsers) SetInfo(ctx context.Context, id uint64, user *db.User) err
return err
}
func (u *SQLiteUsers) SetPreferences(ctx context.Context, id uint64, prefers *db.Preferences) error {
func (st *SQLiteStore) SetPreferences(ctx context.Context, id uint64, prefers *db.Preferences) error {
st.Lock()
defer st.Unlock()
preferenceStr, err := json.Marshal(prefers)
if err != nil {
return err
}
_, err = u.db.ExecContext(
_, err = st.db.ExecContext(
ctx,
`update t_user
set preference=?
@ -294,31 +227,32 @@ func (u *SQLiteUsers) SetPreferences(ctx context.Context, id uint64, prefers *db
return err
}
func (u *SQLiteUsers) SetUsed(ctx context.Context, id uint64, incr bool, capacity int64) error {
tx, err := u.db.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
func (st *SQLiteStore) SetUsed(ctx context.Context, id uint64, incr bool, capacity int64) error {
st.Lock()
defer st.Unlock()
return st.setUsed(ctx, id, incr, capacity)
}
gotUser, err := u.getUser(ctx, tx, id)
func (st *SQLiteStore) setUsed(ctx context.Context, id uint64, incr bool, capacity int64) error {
gotUser, err := st.getUser(ctx, id)
if err != nil {
return err
}
if incr && gotUser.UsedSpace+capacity > int64(gotUser.Quota.SpaceLimit) {
return ErrReachedLimit
return db.ErrReachedLimit
}
if incr {
gotUser.UsedSpace = gotUser.UsedSpace + capacity
} else {
if gotUser.UsedSpace-capacity < 0 {
return ErrNegtiveUsedSpace
return db.ErrNegtiveUsedSpace
}
gotUser.UsedSpace = gotUser.UsedSpace - capacity
}
_, err = tx.ExecContext(
_, err = st.db.ExecContext(
ctx,
`update t_user
set used_space=?
@ -330,11 +264,14 @@ func (u *SQLiteUsers) SetUsed(ctx context.Context, id uint64, incr bool, capacit
return err
}
return tx.Commit()
return nil
}
func (u *SQLiteUsers) ResetUsed(ctx context.Context, id uint64, used int64) error {
_, err := u.db.ExecContext(
func (st *SQLiteStore) ResetUsed(ctx context.Context, id uint64, used int64) error {
st.Lock()
defer st.Unlock()
_, err := st.db.ExecContext(
ctx,
`update t_user
set used_space=?
@ -345,16 +282,19 @@ func (u *SQLiteUsers) ResetUsed(ctx context.Context, id uint64, used int64) erro
return err
}
func (u *SQLiteUsers) ListUsers(ctx context.Context) ([]*db.User, error) {
func (st *SQLiteStore) ListUsers(ctx context.Context) ([]*db.User, error) {
st.RLock()
defer st.RUnlock()
// TODO: support pagination
rows, err := u.db.QueryContext(
rows, err := st.db.QueryContext(
ctx,
`select id, name, role, used_space, quota, preference
from t_user`,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
return nil, db.ErrUserNotFound
}
return nil, err
}
@ -389,8 +329,11 @@ func (u *SQLiteUsers) ListUsers(ctx context.Context) ([]*db.User, error) {
return users, nil
}
func (u *SQLiteUsers) ListUserIDs(ctx context.Context) (map[string]string, error) {
users, err := u.ListUsers(ctx)
func (st *SQLiteStore) ListUserIDs(ctx context.Context) (map[string]string, error) {
st.RLock()
defer st.RUnlock()
users, err := st.ListUsers(ctx)
if err != nil {
return nil, err
}
@ -402,17 +345,17 @@ func (u *SQLiteUsers) ListUserIDs(ctx context.Context) (map[string]string, error
return nameToId, nil
}
func (u *SQLiteUsers) AddRole(role string) error {
func (st *SQLiteStore) AddRole(role string) error {
// TODO: implement this after adding grant/revoke
panic("not implemented")
}
func (u *SQLiteUsers) DelRole(role string) error {
func (st *SQLiteStore) DelRole(role string) error {
// TODO: implement this after adding grant/revoke
panic("not implemented")
}
func (u *SQLiteUsers) ListRoles() (map[string]bool, error) {
func (st *SQLiteStore) ListRoles() (map[string]bool, error) {
// TODO: implement this after adding grant/revoke
panic("not implemented")
}

View file

@ -1,6 +1,7 @@
package sitestore
import (
"context"
"encoding/json"
"errors"
"sync"
@ -79,7 +80,7 @@ func (st *SiteStore) setCfg(cfg *db.SiteConfig) error {
return st.store.SetStringIn(NsSite, KeySiteCfg, string(cfgBytes))
}
func (st *SiteStore) SetClientCfg(cfg *db.ClientConfig) error {
func (st *SiteStore) SetClientCfg(ctx context.Context, cfg *db.ClientConfig) error {
st.mtx.Lock()
defer st.mtx.Unlock()
@ -92,7 +93,7 @@ func (st *SiteStore) SetClientCfg(cfg *db.ClientConfig) error {
return st.setCfg(siteCfg)
}
func (st *SiteStore) GetCfg() (*db.SiteConfig, error) {
func (st *SiteStore) GetCfg(ctx context.Context) (*db.SiteConfig, error) {
st.mtx.RLock()
defer st.mtx.RUnlock()

126
src/db/tests/config_test.go Normal file
View file

@ -0,0 +1,126 @@
package tests
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/ihexxa/quickshare/src/db"
"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{
ClientCfg: &db.ClientConfig{
SiteName: "",
SiteDesc: "",
AllowSetBg: true,
AutoTheme: false,
Bg: &db.BgConfig{
Url: "/imgs/bg.jpg",
Repeat: "repeat",
Position: "top",
Align: "scroll",
BgColor: "#000",
},
},
}
func TestSiteStore(t *testing.T) {
testConfigMethods := func(t *testing.T, store db.IConfigDB) {
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",
},
},
}
ctx := context.TODO()
err := store.SetClientCfg(ctx, siteCfg.ClientCfg)
if err != nil {
t.Fatal(err)
}
newSiteCfg, err := store.GetCfg(ctx)
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(newSiteCfg, siteCfg) {
t.Fatalf("not equal new(%v) original(%v)", newSiteCfg, siteCfg)
}
}
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) {
rootPath, err := ioutil.TempDir("./", "qs_sqlite_config_")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootPath)
dbPath := filepath.Join(rootPath, "quickshare.sqlite")
sqliteDB, err := sqlite.NewSQLite(dbPath)
if err != nil {
t.Fatal(err)
}
defer sqliteDB.Close()
store, err := sqlite.NewSQLiteStore(sqliteDB)
if err != nil {
t.Fatal("fail to new sqlite store", err)
}
err = store.Init(context.TODO(), "admin", "adminPwd", testSiteConfig)
if err != nil {
panic(err)
}
testConfigMethods(t, store)
})
}

446
src/db/tests/files_test.go Normal file
View file

@ -0,0 +1,446 @@
package tests
import (
"context"
"database/sql"
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ihexxa/quickshare/src/db"
"github.com/ihexxa/quickshare/src/db/rdb/sqlite"
)
type IFilesFunctions interface {
db.IFileDB
db.IUploadDB
db.ISharingDB
}
func TestFileStore(t *testing.T) {
testSharingMethods := func(t *testing.T, store db.IDBFunctions) {
dirPaths := []string{"admin/path1", "admin/path1/path2"}
var err error
ctx := context.TODO()
adminId := uint64(0)
// add some of paths...
err = store.AddFileInfo(ctx, adminId, "admin/path1", &db.FileInfo{
// Shared: false, // deprecated
IsDir: false,
Size: int64(0),
ShareID: "",
Sha1: "",
})
if err != nil {
t.Fatal(err)
}
// add sharings
for _, dirPath := range dirPaths {
err = store.AddSharing(ctx, adminId, dirPath)
if err != nil {
t.Fatal(err)
}
}
// list sharings
dirToID, err := store.ListUserSharings(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if len(dirToID) != len(dirPaths) {
t.Fatal("share size not match")
}
for _, sharingDir := range dirPaths {
if _, ok := dirToID[sharingDir]; !ok {
t.Fatalf("sharing(%s) not found", sharingDir)
}
mustTrue := store.IsSharing(ctx, adminId, sharingDir)
if !mustTrue {
t.Fatalf("get sharing(%t) should exist", mustTrue)
}
info, err := store.GetFileInfo(ctx, adminId, sharingDir)
if err != nil {
t.Fatal(err)
} else if len(info.ShareID) != 7 {
t.Fatalf("incorrect ShareID %s", info.ShareID)
}
gotSharingDir, err := store.GetSharingDir(ctx, 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(ctx, adminId, dirPath)
if err != nil {
t.Fatal(err)
}
}
// list sharings
dirToIDAfterDel, err := store.ListUserSharings(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if len(dirToIDAfterDel) != 0 {
t.Fatalf("share size not match (%+v)", dirToIDAfterDel)
}
for _, dirPath := range dirPaths {
if _, ok := dirToIDAfterDel[dirPath]; ok {
t.Fatalf("sharing(%s) should not exist", dirPath)
}
shared := store.IsSharing(ctx, adminId, dirPath)
if shared {
t.Fatalf("get sharing(%t) should not shared but exist", shared)
}
info, err := store.GetFileInfo(ctx, adminId, 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(ctx, originalShareID)
if !errors.Is(err, db.ErrSharingNotFound) {
t.Fatal("should return ErrSharingNotFound")
}
}
}
testFileInfoMethods := func(t *testing.T, store db.IDBFunctions) {
pathInfos := map[string]*db.FileInfo{
"admin/origin/item1": &db.FileInfo{
// Shared: false, // deprecated
IsDir: false,
Size: int64(7),
ShareID: "",
Sha1: "item1_sha",
},
"admin/origin/item2": &db.FileInfo{
// Shared: false, // deprecated
IsDir: false,
Size: int64(3),
ShareID: "",
Sha1: "item2_sha",
},
"admin/origin/dir": &db.FileInfo{
// Shared: true, // deprecated
IsDir: true,
Size: int64(0),
ShareID: "mockedShareID",
Sha1: "",
},
}
var err error
adminId := uint64(0)
ctx := context.TODO()
// add infos
usedSpace := int64(0)
itemPaths := []string{}
for itemPath, info := range pathInfos {
err = store.AddFileInfo(ctx, adminId, itemPath, info)
if err != nil {
t.Fatal(err)
}
usedSpace += info.Size
itemPaths = append(itemPaths, itemPath)
}
// get infos
for itemPath, expected := range pathInfos {
info, err := store.GetFileInfo(ctx, adminId, itemPath)
if err != nil {
t.Fatal(err)
}
if info.ShareID != expected.ShareID ||
info.IsDir != expected.IsDir ||
info.Sha1 != expected.Sha1 ||
info.Size != expected.Size {
t.Fatalf("info not equaled (%v) (%v)", expected, info)
}
}
// list infos
pathToInfo, err := store.ListFileInfos(ctx, itemPaths)
if err != nil {
t.Fatal(err)
} else if len(pathToInfo) != len(pathInfos) {
t.Fatalf("list result size not match (%v) (%d)", pathToInfo, len(pathInfos))
}
for pathname, info := range pathInfos {
gotInfo, ok := pathToInfo[pathname]
if !ok {
t.Fatalf("path not found (%s)", pathname)
}
if info.ShareID != gotInfo.ShareID ||
info.IsDir != gotInfo.IsDir ||
info.Sha1 != gotInfo.Sha1 ||
info.Size != gotInfo.Size {
t.Fatalf("info not equaled (%v) (%v)", gotInfo, info)
}
}
// set sha1
testSha1 := "sha1"
for itemPath := range pathInfos {
err := store.SetSha1(ctx, adminId, itemPath, testSha1)
if err != nil {
t.Fatal(err)
}
info, err := store.GetFileInfo(ctx, adminId, itemPath)
if err != nil {
t.Fatal(err)
}
if info.Sha1 != testSha1 {
t.Fatalf("sha not equaled (%v) (%v)", info.Sha1, testSha1)
}
}
// move paths
newPaths := []string{}
for itemPath, info := range pathInfos {
newItemPath := strings.ReplaceAll(itemPath, "origin", "new")
err = store.MoveFileInfos(ctx, adminId, itemPath, newItemPath, info.IsDir)
if err != nil {
t.Fatal(err)
}
newPaths = append(newPaths, newItemPath)
}
// list infos
pathToInfo, err = store.ListFileInfos(ctx, newPaths)
if err != nil {
t.Fatal(err)
} else if len(pathToInfo) != len(pathInfos) {
t.Fatalf("list result size not match (%v) (%d)", pathToInfo, len(pathInfos))
}
// check used space
adminInfo, err := store.GetUser(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if adminInfo.UsedSpace != usedSpace {
t.Fatalf("used space not match (%d) (%d)", adminInfo.UsedSpace, usedSpace)
}
// del info
for _, itemPath := range newPaths {
err = store.DelFileInfo(ctx, adminId, itemPath)
if err != nil {
t.Fatal(err)
}
}
// check used space
adminInfo, err = store.GetUser(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if adminInfo.UsedSpace != int64(0) {
t.Fatalf("used space not match (%d) (%d)", adminInfo.UsedSpace, int64(0))
}
// list infos
pathToInfo, err = store.ListFileInfos(ctx, itemPaths)
if err != nil {
t.Fatal(err)
} else if len(pathToInfo) != 0 {
t.Fatalf("list result should be empty (%v)", pathToInfo)
}
for itemPath := range pathInfos {
_, err := store.GetFileInfo(ctx, adminId, itemPath)
if !errors.Is(err, db.ErrFileInfoNotFound) {
t.Fatal(err)
}
}
}
testUploadingMethods := func(t *testing.T, store db.IDBFunctions) {
pathInfos := map[string]*db.FileInfo{
"admin/origin/item1": &db.FileInfo{
// Shared: false, // deprecated
IsDir: false,
Size: int64(7),
ShareID: "",
Sha1: "",
},
"admin/origin/item2": &db.FileInfo{
// Shared: false, // deprecated
IsDir: false,
Size: int64(3),
ShareID: "",
Sha1: "",
},
"admin/origin/to_delete/item3": &db.FileInfo{
// Shared: false, // deprecated
IsDir: false,
Size: int64(11),
ShareID: "",
Sha1: "",
},
}
var err error
adminId := uint64(0)
ctx := context.TODO()
// add infos
usedSpace := int64(0)
usedSpaceAfterDeleting := int64(0)
itemPaths := []string{}
pathToTmpPath := map[string]string{}
for itemPath, info := range pathInfos {
tmpPath := strings.ReplaceAll(itemPath, "origin", "uploads")
pathToTmpPath[itemPath] = tmpPath
err = store.AddUploadInfos(ctx, adminId, tmpPath, itemPath, info)
if err != nil {
t.Fatal(err)
}
usedSpace += info.Size
if !strings.Contains(itemPath, "delete") {
usedSpaceAfterDeleting += info.Size
}
itemPaths = append(itemPaths, itemPath)
}
// get infos
for itemPath, info := range pathInfos {
gotPath, size, uploaded, err := store.GetUploadInfo(ctx, adminId, itemPath)
if err != nil {
t.Fatal(err)
}
if size != info.Size ||
uploaded != int64(0) ||
gotPath != itemPath {
t.Fatalf("info not equaled (%v)", info)
}
}
// list infos
uploadInfos, err := store.ListUploadInfos(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if len(uploadInfos) != len(pathInfos) {
t.Fatalf("list result size not match (%v) (%d)", uploadInfos, len(pathInfos))
}
for _, uploadInfo := range uploadInfos {
expected, ok := pathInfos[uploadInfo.RealFilePath]
if !ok {
t.Fatalf("path not found (%s)", uploadInfo.RealFilePath)
}
if uploadInfo.Uploaded != int64(0) ||
expected.Size != uploadInfo.Size {
t.Fatalf("info not equaled (%d) (%d)", uploadInfo.Uploaded, uploadInfo.Size)
}
}
// check used space
adminInfo, err := store.GetUser(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if adminInfo.UsedSpace != usedSpace {
t.Fatalf("used space not match (%d) (%d)", adminInfo.UsedSpace, usedSpace)
}
// set uploading
for itemPath, info := range pathInfos {
err := store.SetUploadInfo(ctx, adminId, itemPath, int64(info.Size/2))
if err != nil {
t.Fatal(err)
}
gotPath, size, uploaded, err := store.GetUploadInfo(ctx, adminId, itemPath)
if err != nil {
t.Fatal(err)
}
if gotPath != itemPath || size != info.Size || uploaded != info.Size/2 {
t.Fatal("uploaded info not match")
}
}
// check used space
adminInfo, err = store.GetUser(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if adminInfo.UsedSpace != usedSpace {
t.Fatalf("used space not match (%d) (%d)", adminInfo.UsedSpace, usedSpace)
}
// del info
for itemPath := range pathInfos {
err = store.DelUploadingInfos(ctx, adminId, itemPath)
if err != nil {
t.Fatal(err)
}
}
// check used space
adminInfo, err = store.GetUser(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if adminInfo.UsedSpace != int64(0) {
t.Fatalf("used space not match (%d) (%d)", adminInfo.UsedSpace, int64(0))
}
// list infos
uploadInfos, err = store.ListUploadInfos(ctx, adminId)
if err != nil {
t.Fatal(err)
} else if len(uploadInfos) != 0 {
t.Fatalf("list result size not match (%v) (%d)", uploadInfos, 0)
}
for itemPath := range pathInfos {
_, _, _, err := store.GetUploadInfo(ctx, adminId, itemPath)
if !errors.Is(err, sql.ErrNoRows) {
t.Fatal(err)
}
}
}
t.Run("testing file info store - sqlite", func(t *testing.T) {
rootPath, err := ioutil.TempDir("./", "qs_sqlite_test_")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootPath)
dbPath := filepath.Join(rootPath, "files.sqlite")
sqliteDB, err := sqlite.NewSQLite(dbPath)
if err != nil {
t.Fatal(err)
}
defer sqliteDB.Close()
store, err := sqlite.NewSQLiteStore(sqliteDB)
if err != nil {
t.Fatal("fail to new sqlite store", err)
}
err = store.Init(context.TODO(), "admin", "1234", testSiteConfig)
if err != nil {
t.Fatal("fail to init", err)
}
testSharingMethods(t, store)
testFileInfoMethods(t, store)
testUploadingMethods(t, store)
})
}

View file

@ -1,4 +1,4 @@
package sqlite
package tests
import (
"context"
@ -7,16 +7,20 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"github.com/ihexxa/quickshare/src/db"
"github.com/ihexxa/quickshare/src/db/rdb/sqlite"
// "github.com/ihexxa/quickshare/src/db/userstore"
// "github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
)
func TestUserStores(t *testing.T) {
func TestUserStore(t *testing.T) {
rootName, rootPwd := "root", "rootPwd"
testUserMethods := func(t *testing.T, store db.IUserStore) {
testUserMethods := func(t *testing.T, store db.IUserDB) {
ctx := context.TODO()
// check root
root, err := store.GetUser(ctx, 0)
if err != nil {
t.Fatal(err)
@ -43,11 +47,12 @@ func TestUserStores(t *testing.T) {
t.Fatalf("incorrect preference %v %v", root.Preferences, db.DefaultPreferences)
}
// check visitor
visitor, err := store.GetUser(ctx, 1)
if err != nil {
t.Fatal(err)
}
if visitor.Name != VisitorName {
if visitor.Name != db.VisitorName {
t.Fatal("visitor not found")
}
if visitor.Pwd != rootPwd {
@ -69,6 +74,7 @@ func TestUserStores(t *testing.T) {
t.Fatalf("incorrect preference")
}
// add users
id, name1 := uint64(2), "test_user1"
pwd1, pwd2 := "666", "888"
role1, role2 := db.UserRole, db.AdminRole
@ -91,6 +97,7 @@ func TestUserStores(t *testing.T) {
t.Fatal("there should be no error")
}
// compare users
user, err := store.GetUser(ctx, id)
if err != nil {
t.Fatal(err)
@ -114,6 +121,7 @@ func TestUserStores(t *testing.T) {
t.Fatalf("down limit not matched %d %d", downLimit1, user.Quota.DownloadSpeedLimit)
}
// check listing
users, err := store.ListUsers(ctx)
if err != nil {
t.Fatal(err)
@ -190,7 +198,6 @@ func TestUserStores(t *testing.T) {
t.Fatalf("used space not matched %d %d", user.UsedSpace, usedIncr-usedDecr)
}
time.Sleep(5 * time.Second)
newPrefer := &db.Preferences{
Bg: &db.BgConfig{
Url: "/url",
@ -219,7 +226,7 @@ func TestUserStores(t *testing.T) {
t.Fatalf("ids not matched %d %d", id, user.ID)
}
if user.Pwd != pwd2 {
t.Fatalf("passwords not match %s", err)
t.Fatalf("passwords not match %s %s", user.Pwd, pwd2)
}
if user.Role != role2 {
t.Fatalf("roles not matched %s %s", role2, user.Role)
@ -252,7 +259,7 @@ func TestUserStores(t *testing.T) {
if user.ID == 0 && user.Name != rootName && user.Role != db.AdminRole {
t.Fatalf("incorrect root info %v", user)
}
if user.ID == VisitorID && user.Name != VisitorName && user.Role != db.VisitorRole {
if user.ID == db.VisitorID && user.Name != db.VisitorName && user.Role != db.VisitorRole {
t.Fatalf("incorrect visitor info %v", user)
}
}
@ -266,7 +273,49 @@ func TestUserStores(t *testing.T) {
}
}
t.Run("testing UserStore sqlite", func(t *testing.T) {
// TODO: role functions are disabled temporarily
// testRoleMethods := func(t *testing.T, store db.IUserDB) {
// roles := []string{"role1", "role2"}
// var err error
// for _, role := range roles {
// err = store.AddRole(role)
// if err != nil {
// t.Fatal(err)
// }
// }
// roleMap, err := store.ListRoles()
// if err != nil {
// t.Fatal(err)
// }
// for _, role := range append(roles, []string{
// db.AdminRole, db.UserRole, db.VisitorRole,
// }...) {
// if !roleMap[role] {
// t.Fatalf("role(%s) not found", role)
// }
// }
// for _, role := range roles {
// err = store.DelRole(role)
// if err != nil {
// t.Fatal(err)
// }
// }
// roleMap, err = store.ListRoles()
// if err != nil {
// t.Fatal(err)
// }
// for _, role := range roles {
// if roleMap[role] {
// t.Fatalf("role(%s) should not exist", role)
// }
// }
// }
t.Run("user store crud - sqlite", func(t *testing.T) {
rootPath, err := ioutil.TempDir("./", "quickshare_userstore_test_")
if err != nil {
t.Fatal(err)
@ -274,17 +323,17 @@ func TestUserStores(t *testing.T) {
defer os.RemoveAll(rootPath)
dbPath := filepath.Join(rootPath, "quickshare.sqlite")
sqliteDB, err := NewSQLite(dbPath)
sqliteDB, err := sqlite.NewSQLite(dbPath)
if err != nil {
t.Fatal(err)
}
defer sqliteDB.Close()
store, err := NewSQLiteUsers(sqliteDB)
store, err := sqlite.NewSQLiteStore(sqliteDB)
if err != nil {
t.Fatal("fail to new user store", err)
}
if err = store.Init(context.TODO(), rootName, rootPwd); err != nil {
if err = store.Init(context.TODO(), rootName, rootPwd, testSiteConfig); err != nil {
t.Fatal("fail to init user store", err)
}

View file

@ -1,329 +0,0 @@
package userstore
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ihexxa/quickshare/src/db"
"github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
)
func TestUserStores(t *testing.T) {
rootName, rootPwd := "root", "rootPwd"
testUserMethods := func(t *testing.T, store IUserStore) {
root, err := store.GetUser(0)
if err != nil {
t.Fatal(err)
}
if root.Name != rootName {
t.Fatal("root user not found")
}
if root.Pwd != rootPwd {
t.Fatalf("passwords not match %s", err)
}
if root.Role != db.AdminRole {
t.Fatalf("incorrect root role")
}
if root.Quota.SpaceLimit != db.DefaultSpaceLimit {
t.Fatalf("incorrect root SpaceLimit")
}
if root.Quota.UploadSpeedLimit != db.DefaultUploadSpeedLimit {
t.Fatalf("incorrect root UploadSpeedLimit")
}
if root.Quota.DownloadSpeedLimit != db.DefaultDownloadSpeedLimit {
t.Fatalf("incorrect root DownloadSpeedLimit")
}
if !db.ComparePreferences(root.Preferences, &db.DefaultPreferences) {
t.Fatalf("incorrect preference %v %v", root.Preferences, db.DefaultPreferences)
}
visitor, err := store.GetUser(1)
if err != nil {
t.Fatal(err)
}
if visitor.Name != VisitorName {
t.Fatal("visitor not found")
}
if visitor.Pwd != rootPwd {
t.Fatalf("passwords not match %s", err)
}
if visitor.Role != db.VisitorRole {
t.Fatalf("incorrect visitor role")
}
if visitor.Quota.SpaceLimit != 0 {
t.Fatalf("incorrect visitor SpaceLimit")
}
if visitor.Quota.UploadSpeedLimit != db.VisitorUploadSpeedLimit {
t.Fatalf("incorrect visitor UploadSpeedLimit")
}
if visitor.Quota.DownloadSpeedLimit != db.VisitorDownloadSpeedLimit {
t.Fatalf("incorrect visitor DownloadSpeedLimit")
}
if !db.ComparePreferences(visitor.Preferences, &db.DefaultPreferences) {
t.Fatalf("incorrect preference")
}
id, name1 := uint64(2), "test_user1"
pwd1, pwd2 := "666", "888"
role1, role2 := db.UserRole, db.AdminRole
spaceLimit1, upLimit1, downLimit1 := int64(17), 5, 7
spaceLimit2, upLimit2, downLimit2 := int64(19), 13, 17
err = store.AddUser(&db.User{
ID: id,
Name: name1,
Pwd: pwd1,
Role: role1,
Quota: &db.Quota{
SpaceLimit: spaceLimit1,
UploadSpeedLimit: upLimit1,
DownloadSpeedLimit: downLimit1,
},
Preferences: &db.DefaultPreferences,
})
if err != nil {
t.Fatal("there should be no error")
}
user, err := store.GetUser(id)
if err != nil {
t.Fatal(err)
}
if user.Name != name1 {
t.Fatalf("names not matched %s %s", name1, user.Name)
}
if user.Pwd != pwd1 {
t.Fatalf("passwords not match %s", err)
}
if user.Role != role1 {
t.Fatalf("roles not matched %s %s", role1, user.Role)
}
if user.Quota.SpaceLimit != spaceLimit1 {
t.Fatalf("space limit not matched %d %d", spaceLimit1, user.Quota.SpaceLimit)
}
if user.Quota.UploadSpeedLimit != upLimit1 {
t.Fatalf("up limit not matched %d %d", upLimit1, user.Quota.UploadSpeedLimit)
}
if user.Quota.DownloadSpeedLimit != downLimit1 {
t.Fatalf("down limit not matched %d %d", downLimit1, user.Quota.DownloadSpeedLimit)
}
users, err := store.ListUsers()
if err != nil {
t.Fatal(err)
}
if len(users) != 3 {
t.Fatalf("users size should be 3 (%d)", len(users))
}
for _, user := range users {
if user.ID == 0 {
if user.Name != rootName || user.Role != db.AdminRole {
t.Fatalf("incorrect root info %v", user)
}
}
if user.ID == id {
if user.Name != name1 || user.Role != role1 {
t.Fatalf("incorrect user info %v", user)
}
}
if user.Pwd != "" {
t.Fatalf("password must be empty")
}
}
err = store.SetPwd(id, pwd2)
if err != nil {
t.Fatal(err)
}
store.SetInfo(id, &db.User{
ID: id,
Role: role2,
Quota: &db.Quota{
SpaceLimit: spaceLimit2,
UploadSpeedLimit: upLimit2,
DownloadSpeedLimit: downLimit2,
},
})
usedIncr, usedDecr := int64(spaceLimit2), int64(7)
err = store.SetUsed(id, true, usedIncr)
if err != nil {
t.Fatal(err)
}
err = store.SetUsed(id, false, usedDecr)
if err != nil {
t.Fatal(err)
}
err = store.SetUsed(id, true, int64(spaceLimit2)-(usedIncr-usedDecr)+1)
if err == nil || !strings.Contains(err.Error(), "reached space limit") {
t.Fatal("should reject big file")
} else {
err = nil
}
user, err = store.GetUser(id)
if err != nil {
t.Fatal(err)
}
if user.Pwd != pwd2 {
t.Fatalf("passwords not match %s", err)
}
if user.Role != role2 {
t.Fatalf("roles not matched %s %s", role2, user.Role)
}
if user.Quota.SpaceLimit != spaceLimit2 {
t.Fatalf("space limit not matched %d %d", spaceLimit2, user.Quota.SpaceLimit)
}
if user.Quota.UploadSpeedLimit != upLimit2 {
t.Fatalf("up limit not matched %d %d", upLimit2, user.Quota.UploadSpeedLimit)
}
if user.Quota.DownloadSpeedLimit != downLimit2 {
t.Fatalf("down limit not matched %d %d", downLimit2, user.Quota.DownloadSpeedLimit)
}
if user.UsedSpace != usedIncr-usedDecr {
t.Fatalf("used space not matched %d %d", user.UsedSpace, usedIncr-usedDecr)
}
newPrefer := &db.Preferences{
Bg: &db.BgConfig{
Url: "/url",
Repeat: "repeat",
Position: "center",
Align: "fixed",
BgColor: "#333",
},
CSSURL: "/cssurl",
LanPackURL: "lanPackURL",
Lan: "zhCN",
Theme: "dark",
Avatar: "/avatar",
Email: "foo@gmail.com",
}
err = store.SetPreferences(id, newPrefer)
if err != nil {
t.Fatal(err)
}
user, err = store.GetUserByName(name1)
if err != nil {
t.Fatal(err)
}
if user.ID != id {
t.Fatalf("ids not matched %d %d", id, user.ID)
}
if user.Pwd != pwd2 {
t.Fatalf("passwords not match %s", err)
}
if user.Role != role2 {
t.Fatalf("roles not matched %s %s", role2, user.Role)
}
if user.Quota.SpaceLimit != spaceLimit2 {
t.Fatalf("space limit not matched %d %d", spaceLimit2, user.Quota.SpaceLimit)
}
if user.Quota.UploadSpeedLimit != upLimit2 {
t.Fatalf("up limit not matched %d %d", upLimit2, user.Quota.UploadSpeedLimit)
}
if user.Quota.DownloadSpeedLimit != downLimit2 {
t.Fatalf("down limit not matched %d %d", downLimit2, user.Quota.DownloadSpeedLimit)
}
if !db.ComparePreferences(user.Preferences, newPrefer) {
t.Fatalf("preferences not matched %v %v", user.Preferences, newPrefer)
}
err = store.DelUser(id)
if err != nil {
t.Fatal(err)
}
users, err = store.ListUsers()
if err != nil {
t.Fatal(err)
}
if len(users) != 2 {
t.Fatalf("users size should be 2 (%d)", len(users))
}
for _, user := range users {
if user.ID == 0 && user.Name != rootName && user.Role != db.AdminRole {
t.Fatalf("incorrect root info %v", user)
}
if user.ID == VisitorID && user.Name != VisitorName && user.Role != db.VisitorRole {
t.Fatalf("incorrect visitor info %v", user)
}
}
nameToID, err := store.ListUserIDs()
if err != nil {
t.Fatal(err)
}
if len(nameToID) != len(users) {
t.Fatalf("nameToID size (%d) should be same as (%d)", len(nameToID), len(users))
}
}
testRoleMethods := func(t *testing.T, store IUserStore) {
roles := []string{"role1", "role2"}
var err error
for _, role := range roles {
err = store.AddRole(role)
if err != nil {
t.Fatal(err)
}
}
roleMap, err := store.ListRoles()
if err != nil {
t.Fatal(err)
}
for _, role := range append(roles, []string{
db.AdminRole, db.UserRole, db.VisitorRole,
}...) {
if !roleMap[role] {
t.Fatalf("role(%s) not found", role)
}
}
for _, role := range roles {
err = store.DelRole(role)
if err != nil {
t.Fatal(err)
}
}
roleMap, err = store.ListRoles()
if err != nil {
t.Fatal(err)
}
for _, role := range roles {
if roleMap[role] {
t.Fatalf("role(%s) should not exist", role)
}
}
}
t.Run("testing KVUserStore", 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 := NewKVUserStore(kvstore)
if err != nil {
t.Fatal("fail to new kvstore", err)
}
if err = store.Init(rootName, rootPwd); err != nil {
t.Fatal("fail to init kvstore", err)
}
testUserMethods(t, store)
testRoleMethods(t, store)
})
}