mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-09-19 20:26:26 +08:00
feat(style): add driver icons and disk usage (#1274)
* feat(style): add driver icons and disk usage * feat(driver): add disk usage for 115_open, 123_open, aliyundrive_open and baidu_netdisk * feat(driver): add disk usage for crypt, sftp and smb * chore: clean unused variable * feat(driver): add disk usage for cloudreve_v4 Signed-off-by: MadDogOwner <xiaoran@xrgzs.top> * fix(local): disk label check when getting disk usage * feat(style): return details when accessing the manage page --------- Signed-off-by: MadDogOwner <xiaoran@xrgzs.top> Co-authored-by: MadDogOwner <xiaoran@xrgzs.top>
This commit is contained in:
@ -337,6 +337,27 @@ func (d *Open115) OfflineList(ctx context.Context) (*sdk.OfflineTaskListResp, er
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open115) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
userInfo, err := d.client.UserInfo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
total, err := userInfo.RtSpaceInfo.AllTotal.Size.Int64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
free, err := userInfo.RtSpaceInfo.AllRemain.Size.Int64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: model.DiskUsage{
|
||||||
|
TotalSpace: uint64(total),
|
||||||
|
FreeSpace: uint64(free),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func (d *Open115) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
// func (d *Open115) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
||||||
// // TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
|
// // TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
|
||||||
// return nil, errs.NotImplement
|
// return nil, errs.NotImplement
|
||||||
|
@ -214,5 +214,20 @@ func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
|||||||
return nil, fmt.Errorf("upload complete timeout")
|
return nil, fmt.Errorf("upload complete timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open123) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
userInfo, err := d.getUserInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
total := userInfo.Data.SpacePermanent + userInfo.Data.SpaceTemp
|
||||||
|
free := total - userInfo.Data.SpaceUsed
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: model.DiskUsage{
|
||||||
|
TotalSpace: total,
|
||||||
|
FreeSpace: free,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*Open123)(nil)
|
var _ driver.Driver = (*Open123)(nil)
|
||||||
var _ driver.PutResult = (*Open123)(nil)
|
var _ driver.PutResult = (*Open123)(nil)
|
||||||
|
@ -133,9 +133,9 @@ type UserInfoResp struct {
|
|||||||
// HeadImage string `json:"headImage"`
|
// HeadImage string `json:"headImage"`
|
||||||
// Passport string `json:"passport"`
|
// Passport string `json:"passport"`
|
||||||
// Mail string `json:"mail"`
|
// Mail string `json:"mail"`
|
||||||
// SpaceUsed int64 `json:"spaceUsed"`
|
SpaceUsed uint64 `json:"spaceUsed"`
|
||||||
// SpacePermanent int64 `json:"spacePermanent"`
|
SpacePermanent uint64 `json:"spacePermanent"`
|
||||||
// SpaceTemp int64 `json:"spaceTemp"`
|
SpaceTemp uint64 `json:"spaceTemp"`
|
||||||
// SpaceTempExpr int64 `json:"spaceTempExpr"`
|
// SpaceTempExpr int64 `json:"spaceTempExpr"`
|
||||||
// Vip bool `json:"vip"`
|
// Vip bool `json:"vip"`
|
||||||
// DirectTraffic int64 `json:"directTraffic"`
|
// DirectTraffic int64 `json:"directTraffic"`
|
||||||
|
@ -291,6 +291,21 @@ func (d *AliyundriveOpen) Other(ctx context.Context, args model.OtherArgs) (inte
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
res, err := d.request(ctx, limiterOther, "/adrive/v1.0/user/getSpaceInfo", http.MethodPost, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
total := utils.Json.Get(res, "personal_space_info", "total_size").ToUint64()
|
||||||
|
used := utils.Json.Get(res, "personal_space_info", "used_size").ToUint64()
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: model.DiskUsage{
|
||||||
|
TotalSpace: total,
|
||||||
|
FreeSpace: total - used,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*AliyundriveOpen)(nil)
|
var _ driver.Driver = (*AliyundriveOpen)(nil)
|
||||||
var _ driver.MkdirResult = (*AliyundriveOpen)(nil)
|
var _ driver.MkdirResult = (*AliyundriveOpen)(nil)
|
||||||
var _ driver.MoveResult = (*AliyundriveOpen)(nil)
|
var _ driver.MoveResult = (*AliyundriveOpen)(nil)
|
||||||
|
@ -364,4 +364,12 @@ func (d *BaiduNetdisk) uploadSlice(ctx context.Context, params map[string]string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *BaiduNetdisk) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
du, err := d.quota()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.StorageDetails{DiskUsage: *du}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*BaiduNetdisk)(nil)
|
var _ driver.Driver = (*BaiduNetdisk)(nil)
|
||||||
|
@ -189,3 +189,12 @@ type PrecreateResp struct {
|
|||||||
// return_type=2
|
// return_type=2
|
||||||
File File `json:"info"`
|
File File `json:"info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuotaResp struct {
|
||||||
|
Errno int `json:"errno"`
|
||||||
|
RequestId int64 `json:"request_id"`
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
Used uint64 `json:"used"`
|
||||||
|
//Free uint64 `json:"free"`
|
||||||
|
//Expire bool `json:"expire"`
|
||||||
|
}
|
||||||
|
@ -381,6 +381,18 @@ func (d *BaiduNetdisk) getSliceSize(filesize int64) int64 {
|
|||||||
return maxSliceSize
|
return maxSliceSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *BaiduNetdisk) quota() (*model.DiskUsage, error) {
|
||||||
|
var resp QuotaResp
|
||||||
|
_, err := d.request("https://pan.baidu.com/api/quota", http.MethodGet, nil, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.DiskUsage{
|
||||||
|
TotalSpace: resp.Total,
|
||||||
|
FreeSpace: resp.Total - resp.Used,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func encodeURIComponent(str string) string {
|
// func encodeURIComponent(str string) string {
|
||||||
// r := url.QueryEscape(str)
|
// r := url.QueryEscape(str)
|
||||||
// r = strings.ReplaceAll(r, "+", "%20")
|
// r = strings.ReplaceAll(r, "+", "%20")
|
||||||
|
@ -339,6 +339,21 @@ func (d *CloudreveV4) ArchiveDecompress(ctx context.Context, srcObj, dstDir mode
|
|||||||
return nil, errs.NotImplement
|
return nil, errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *CloudreveV4) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
// TODO return storage details (total space, free space, etc.)
|
||||||
|
var r CapacityResp
|
||||||
|
err := d.request(http.MethodGet, "/user/capacity", nil, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: model.DiskUsage{
|
||||||
|
TotalSpace: r.Total,
|
||||||
|
FreeSpace: r.Total - r.Used,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
//func (d *CloudreveV4) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
//func (d *CloudreveV4) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
// return nil, errs.NotSupport
|
// return nil, errs.NotSupport
|
||||||
//}
|
//}
|
||||||
|
@ -204,3 +204,9 @@ type FolderSummaryResp struct {
|
|||||||
CalculatedAt time.Time `json:"calculated_at"`
|
CalculatedAt time.Time `json:"calculated_at"`
|
||||||
} `json:"folder_summary"`
|
} `json:"folder_summary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CapacityResp struct {
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
Used uint64 `json:"used"`
|
||||||
|
// StoragePackTotal uint64 `json:"storage_pack_total"`
|
||||||
|
}
|
||||||
|
@ -411,6 +411,20 @@ func (d *Crypt) Put(ctx context.Context, dstDir model.Obj, streamer model.FileSt
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Crypt) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
wd, ok := d.remoteStorage.(driver.WithDetails)
|
||||||
|
if !ok {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
remoteDetails, err := wd.GetDetails(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: remoteDetails.DiskUsage,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
//func (d *Safe) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
//func (d *Safe) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
// return nil, errs.NotSupport
|
// return nil, errs.NotSupport
|
||||||
//}
|
//}
|
||||||
|
@ -434,4 +434,14 @@ func (d *Local) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Local) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
du, err := getDiskUsage(d.RootFolderPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: du,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*Local)(nil)
|
var _ driver.Driver = (*Local)(nil)
|
||||||
|
@ -5,8 +5,25 @@ package local
|
|||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isHidden(f fs.FileInfo, _ string) bool {
|
func isHidden(f fs.FileInfo, _ string) bool {
|
||||||
return strings.HasPrefix(f.Name(), ".")
|
return strings.HasPrefix(f.Name(), ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDiskUsage(path string) (model.DiskUsage, error) {
|
||||||
|
var stat syscall.Statfs_t
|
||||||
|
err := syscall.Statfs(path, &stat)
|
||||||
|
if err != nil {
|
||||||
|
return model.DiskUsage{}, err
|
||||||
|
}
|
||||||
|
total := stat.Blocks * uint64(stat.Bsize)
|
||||||
|
free := stat.Bfree * uint64(stat.Bsize)
|
||||||
|
return model.DiskUsage{
|
||||||
|
TotalSpace: total,
|
||||||
|
FreeSpace: free,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -1,22 +1,51 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"errors"
|
||||||
"path/filepath"
|
"io/fs"
|
||||||
"syscall"
|
"path/filepath"
|
||||||
)
|
"syscall"
|
||||||
|
|
||||||
func isHidden(f fs.FileInfo, fullPath string) bool {
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
filePath := filepath.Join(fullPath, f.Name())
|
"golang.org/x/sys/windows"
|
||||||
namePtr, err := syscall.UTF16PtrFromString(filePath)
|
)
|
||||||
if err != nil {
|
|
||||||
return false
|
func isHidden(f fs.FileInfo, fullPath string) bool {
|
||||||
}
|
filePath := filepath.Join(fullPath, f.Name())
|
||||||
attrs, err := syscall.GetFileAttributes(namePtr)
|
namePtr, err := syscall.UTF16PtrFromString(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
|
attrs, err := syscall.GetFileAttributes(namePtr)
|
||||||
}
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDiskUsage(path string) (model.DiskUsage, error) {
|
||||||
|
abs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return model.DiskUsage{}, err
|
||||||
|
}
|
||||||
|
root := filepath.VolumeName(abs)
|
||||||
|
if len(root) != 2 || root[1] != ':' {
|
||||||
|
return model.DiskUsage{}, errors.New("cannot get disk label")
|
||||||
|
}
|
||||||
|
var freeBytes, totalBytes, totalFreeBytes uint64
|
||||||
|
err = windows.GetDiskFreeSpaceEx(
|
||||||
|
windows.StringToUTF16Ptr(root),
|
||||||
|
&freeBytes,
|
||||||
|
&totalBytes,
|
||||||
|
&totalFreeBytes,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return model.DiskUsage{}, err
|
||||||
|
}
|
||||||
|
return model.DiskUsage{
|
||||||
|
TotalSpace: totalBytes,
|
||||||
|
FreeSpace: freeBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
@ -127,4 +128,22 @@ func (d *SFTP) Put(ctx context.Context, dstDir model.Obj, stream model.FileStrea
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *SFTP) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
stat, err := d.client.StatVFS(d.RootFolderPath)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "unimplemented") {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
total := stat.Blocks * stat.Bsize
|
||||||
|
free := stat.Bfree * stat.Bsize
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: model.DiskUsage{
|
||||||
|
TotalSpace: total,
|
||||||
|
FreeSpace: free,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*SFTP)(nil)
|
var _ driver.Driver = (*SFTP)(nil)
|
||||||
|
@ -205,6 +205,22 @@ func (d *SMB) Put(ctx context.Context, dstDir model.Obj, stream model.FileStream
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *SMB) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
if err := d.checkConn(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stat, err := d.fs.Statfs(d.RootFolderPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.StorageDetails{
|
||||||
|
DiskUsage: model.DiskUsage{
|
||||||
|
TotalSpace: stat.BlockSize() * stat.TotalBlockCount(),
|
||||||
|
FreeSpace: stat.BlockSize() * stat.AvailableBlockCount(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
//func (d *SMB) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
//func (d *SMB) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
// return nil, errs.NotSupport
|
// return nil, errs.NotSupport
|
||||||
//}
|
//}
|
||||||
|
@ -93,6 +93,11 @@ func (d *Template) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.O
|
|||||||
return nil, errs.NotImplement
|
return nil, errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Template) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
||||||
|
// TODO return storage details (total space, free space, etc.)
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
// return nil, errs.NotSupport
|
// return nil, errs.NotSupport
|
||||||
//}
|
//}
|
||||||
|
@ -114,6 +114,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: "share_icon", Value: "🎁", Type: conf.TypeString, Group: model.STYLE},
|
{Key: "share_icon", Value: "🎁", Type: conf.TypeString, Group: model.STYLE},
|
||||||
{Key: "home_container", Value: "max_980px", Type: conf.TypeSelect, Options: "max_980px,hope_container", Group: model.STYLE},
|
{Key: "home_container", Value: "max_980px", Type: conf.TypeSelect, Options: "max_980px,hope_container", Group: model.STYLE},
|
||||||
{Key: "settings_layout", Value: "list", Type: conf.TypeSelect, Options: "list,responsive", Group: model.STYLE},
|
{Key: "settings_layout", Value: "list", Type: conf.TypeSelect, Options: "list,responsive", Group: model.STYLE},
|
||||||
|
{Key: conf.HideStorageDetails, Value: "false", Type: conf.TypeBool, Group: model.STYLE, Flag: model.PRIVATE},
|
||||||
// preview settings
|
// preview settings
|
||||||
{Key: conf.TextTypes, Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp,tsx,vtt,srt,ass,rs,lrc,strm", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
{Key: conf.TextTypes, Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp,tsx,vtt,srt,ass,rs,lrc,strm", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||||||
{Key: conf.AudioTypes, Value: "mp3,flac,ogg,m4a,wav,opus,wma", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
{Key: conf.AudioTypes, Value: "mp3,flac,ogg,m4a,wav,opus,wma", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||||||
|
@ -17,9 +17,10 @@ const (
|
|||||||
AllowMounted = "allow_mounted"
|
AllowMounted = "allow_mounted"
|
||||||
RobotsTxt = "robots_txt"
|
RobotsTxt = "robots_txt"
|
||||||
|
|
||||||
Logo = "logo" // multi-lines text, L1: light, EOL: dark
|
Logo = "logo" // multi-lines text, L1: light, EOL: dark
|
||||||
Favicon = "favicon"
|
Favicon = "favicon"
|
||||||
MainColor = "main_color"
|
MainColor = "main_color"
|
||||||
|
HideStorageDetails = "hide_storage_details"
|
||||||
|
|
||||||
// preview
|
// preview
|
||||||
TextTypes = "text_types"
|
TextTypes = "text_types"
|
||||||
|
@ -210,6 +210,11 @@ type ArchiveDecompressResult interface {
|
|||||||
ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error)
|
ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WithDetails interface {
|
||||||
|
// GetDetails get storage details (total space, free space, etc.)
|
||||||
|
GetDetails(ctx context.Context) (*model.StorageDetails, error)
|
||||||
|
}
|
||||||
|
|
||||||
type Reference interface {
|
type Reference interface {
|
||||||
InitReference(storage Driver) error
|
InitReference(storage Driver) error
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,9 @@ import (
|
|||||||
// then pass the actual path to the op package
|
// then pass the actual path to the op package
|
||||||
|
|
||||||
type ListArgs struct {
|
type ListArgs struct {
|
||||||
Refresh bool
|
Refresh bool
|
||||||
NoLog bool
|
NoLog bool
|
||||||
|
WithStorageDetails bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func List(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
func List(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||||
@ -35,11 +36,12 @@ func List(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetArgs struct {
|
type GetArgs struct {
|
||||||
NoLog bool
|
NoLog bool
|
||||||
|
WithStorageDetails bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(ctx context.Context, path string, args *GetArgs) (model.Obj, error) {
|
func Get(ctx context.Context, path string, args *GetArgs) (model.Obj, error) {
|
||||||
res, err := get(ctx, path)
|
res, err := get(ctx, path, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !args.NoLog {
|
if !args.NoLog {
|
||||||
log.Warnf("failed get %s: %s", path, err)
|
log.Warnf("failed get %s: %s", path, err)
|
||||||
|
@ -11,11 +11,11 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func get(ctx context.Context, path string) (model.Obj, error) {
|
func get(ctx context.Context, path string, args *GetArgs) (model.Obj, error) {
|
||||||
path = utils.FixAndCleanPath(path)
|
path = utils.FixAndCleanPath(path)
|
||||||
// maybe a virtual file
|
// maybe a virtual file
|
||||||
if path != "/" {
|
if path != "/" {
|
||||||
virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(path))
|
virtualFiles := op.GetStorageVirtualFilesWithDetailsByPath(ctx, stdpath.Dir(path), !args.WithStorageDetails)
|
||||||
for _, f := range virtualFiles {
|
for _, f := range virtualFiles {
|
||||||
if f.GetName() == stdpath.Base(path) {
|
if f.GetName() == stdpath.Base(path) {
|
||||||
return f, nil
|
return f, nil
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||||
meta, _ := ctx.Value(conf.MetaKey).(*model.Meta)
|
meta, _ := ctx.Value(conf.MetaKey).(*model.Meta)
|
||||||
user, _ := ctx.Value(conf.UserKey).(*model.User)
|
user, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||||
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
virtualFiles := op.GetStorageVirtualFilesWithDetailsByPath(ctx, path, !args.WithStorageDetails)
|
||||||
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
||||||
if err != nil && len(virtualFiles) == 0 {
|
if err != nil && len(virtualFiles) == 0 {
|
||||||
return nil, errors.WithMessage(err, "failed get storage")
|
return nil, errors.WithMessage(err, "failed get storage")
|
||||||
|
@ -55,3 +55,40 @@ func (p Proxy) Webdav302() bool {
|
|||||||
func (p Proxy) WebdavProxyURL() bool {
|
func (p Proxy) WebdavProxyURL() bool {
|
||||||
return p.WebdavPolicy == "use_proxy_url"
|
return p.WebdavPolicy == "use_proxy_url"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiskUsage struct {
|
||||||
|
TotalSpace uint64 `json:"total_space"`
|
||||||
|
FreeSpace uint64 `json:"free_space"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorageDetails struct {
|
||||||
|
DiskUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorageDetailsWithName struct {
|
||||||
|
*StorageDetails
|
||||||
|
DriverName string `json:"driver_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjWithStorageDetails interface {
|
||||||
|
GetStorageDetails() *StorageDetailsWithName
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjStorageDetails struct {
|
||||||
|
Obj
|
||||||
|
StorageDetailsWithName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ObjStorageDetails) GetStorageDetails() *StorageDetailsWithName {
|
||||||
|
return &o.StorageDetailsWithName
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStorageDetails(obj Obj) (*StorageDetailsWithName, bool) {
|
||||||
|
if obj, ok := obj.(ObjWithStorageDetails); ok {
|
||||||
|
return obj.GetStorageDetails(), true
|
||||||
|
}
|
||||||
|
if unwrap, ok := obj.(ObjUnwrap); ok {
|
||||||
|
return GetStorageDetails(unwrap.Unwrap())
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/generic_sync"
|
"github.com/OpenListTeam/OpenList/v4/pkg/generic_sync"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -335,6 +334,40 @@ func getStoragesByPath(path string) []driver.Driver {
|
|||||||
// for example, there are: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
|
// for example, there are: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
|
||||||
// GetStorageVirtualFilesByPath(/a) => b,c,d
|
// GetStorageVirtualFilesByPath(/a) => b,c,d
|
||||||
func GetStorageVirtualFilesByPath(prefix string) []model.Obj {
|
func GetStorageVirtualFilesByPath(prefix string) []model.Obj {
|
||||||
|
return getStorageVirtualFilesByPath(prefix, func(_ driver.Driver, obj model.Obj) model.Obj {
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStorageVirtualFilesWithDetailsByPath(ctx context.Context, prefix string, hideDetails ...bool) []model.Obj {
|
||||||
|
if utils.IsBool(hideDetails...) {
|
||||||
|
return GetStorageVirtualFilesByPath(prefix)
|
||||||
|
}
|
||||||
|
return getStorageVirtualFilesByPath(prefix, func(d driver.Driver, obj model.Obj) model.Obj {
|
||||||
|
ret := &model.ObjStorageDetails{
|
||||||
|
Obj: obj,
|
||||||
|
StorageDetailsWithName: model.StorageDetailsWithName{
|
||||||
|
StorageDetails: nil,
|
||||||
|
DriverName: d.Config().Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
storage, ok := d.(driver.WithDetails)
|
||||||
|
if !ok {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
details, err := storage.GetDetails(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, errs.NotImplement) {
|
||||||
|
log.Errorf("failed get %s storage details: %+v", d.GetStorage().MountPath, err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
ret.StorageDetails = details
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStorageVirtualFilesByPath(prefix string, rootCallback func(driver.Driver, model.Obj) model.Obj) []model.Obj {
|
||||||
files := make([]model.Obj, 0)
|
files := make([]model.Obj, 0)
|
||||||
storages := storagesMap.Values()
|
storages := storagesMap.Values()
|
||||||
sort.Slice(storages, func(i, j int) bool {
|
sort.Slice(storages, func(i, j int) bool {
|
||||||
@ -345,21 +378,30 @@ func GetStorageVirtualFilesByPath(prefix string) []model.Obj {
|
|||||||
})
|
})
|
||||||
|
|
||||||
prefix = utils.FixAndCleanPath(prefix)
|
prefix = utils.FixAndCleanPath(prefix)
|
||||||
set := mapset.NewSet[string]()
|
set := make(map[string]int)
|
||||||
for _, v := range storages {
|
for _, v := range storages {
|
||||||
mountPath := utils.GetActualMountPath(v.GetStorage().MountPath)
|
mountPath := utils.GetActualMountPath(v.GetStorage().MountPath)
|
||||||
// Exclude prefix itself and non prefix
|
// Exclude prefix itself and non prefix
|
||||||
if len(prefix) >= len(mountPath) || !utils.IsSubPath(prefix, mountPath) {
|
if len(prefix) >= len(mountPath) || !utils.IsSubPath(prefix, mountPath) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := strings.SplitN(strings.TrimPrefix(mountPath[len(prefix):], "/"), "/", 2)[0]
|
names := strings.SplitN(strings.TrimPrefix(mountPath[len(prefix):], "/"), "/", 2)
|
||||||
if set.Add(name) {
|
idx, ok := set[names[0]]
|
||||||
files = append(files, &model.Object{
|
if !ok {
|
||||||
Name: name,
|
set[names[0]] = len(files)
|
||||||
|
obj := &model.Object{
|
||||||
|
Name: names[0],
|
||||||
Size: 0,
|
Size: 0,
|
||||||
Modified: v.GetStorage().Modified,
|
Modified: v.GetStorage().Modified,
|
||||||
IsFolder: true,
|
IsFolder: true,
|
||||||
})
|
}
|
||||||
|
if len(names) == 1 {
|
||||||
|
files = append(files, rootCallback(v, obj))
|
||||||
|
} else {
|
||||||
|
files = append(files, obj)
|
||||||
|
}
|
||||||
|
} else if len(names) == 1 {
|
||||||
|
files[idx] = rootCallback(v, files[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files
|
return files
|
||||||
|
@ -33,18 +33,19 @@ type DirReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ObjResp struct {
|
type ObjResp struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
IsDir bool `json:"is_dir"`
|
IsDir bool `json:"is_dir"`
|
||||||
Modified time.Time `json:"modified"`
|
Modified time.Time `json:"modified"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
Sign string `json:"sign"`
|
Sign string `json:"sign"`
|
||||||
Thumb string `json:"thumb"`
|
Thumb string `json:"thumb"`
|
||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
HashInfoStr string `json:"hashinfo"`
|
HashInfoStr string `json:"hashinfo"`
|
||||||
HashInfo map[*utils.HashType]string `json:"hash_info"`
|
HashInfo map[*utils.HashType]string `json:"hash_info"`
|
||||||
|
MountDetails *model.StorageDetailsWithName `json:"mount_details,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FsListResp struct {
|
type FsListResp struct {
|
||||||
@ -98,7 +99,10 @@ func FsList(c *gin.Context, req *ListReq, user *model.User) {
|
|||||||
common.ErrorStrResp(c, "Refresh without permission", 403)
|
common.ErrorStrResp(c, "Refresh without permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objs, err := fs.List(c.Request.Context(), reqPath, &fs.ListArgs{Refresh: req.Refresh})
|
objs, err := fs.List(c.Request.Context(), reqPath, &fs.ListArgs{
|
||||||
|
Refresh: req.Refresh,
|
||||||
|
WithStorageDetails: !user.IsGuest() && !setting.GetBool(conf.HideStorageDetails),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -224,19 +228,21 @@ func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
|||||||
var resp []ObjResp
|
var resp []ObjResp
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
thumb, _ := model.GetThumb(obj)
|
thumb, _ := model.GetThumb(obj)
|
||||||
|
mountDetails, _ := model.GetStorageDetails(obj)
|
||||||
resp = append(resp, ObjResp{
|
resp = append(resp, ObjResp{
|
||||||
Id: obj.GetID(),
|
Id: obj.GetID(),
|
||||||
Path: obj.GetPath(),
|
Path: obj.GetPath(),
|
||||||
Name: obj.GetName(),
|
Name: obj.GetName(),
|
||||||
Size: obj.GetSize(),
|
Size: obj.GetSize(),
|
||||||
IsDir: obj.IsDir(),
|
IsDir: obj.IsDir(),
|
||||||
Modified: obj.ModTime(),
|
Modified: obj.ModTime(),
|
||||||
Created: obj.CreateTime(),
|
Created: obj.CreateTime(),
|
||||||
HashInfoStr: obj.GetHash().String(),
|
HashInfoStr: obj.GetHash().String(),
|
||||||
HashInfo: obj.GetHash().Export(),
|
HashInfo: obj.GetHash().Export(),
|
||||||
Sign: common.Sign(obj, parent, encrypt),
|
Sign: common.Sign(obj, parent, encrypt),
|
||||||
Thumb: thumb,
|
Thumb: thumb,
|
||||||
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
||||||
|
MountDetails: mountDetails,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
@ -293,7 +299,9 @@ func FsGet(c *gin.Context, req *FsGetReq, user *model.User) {
|
|||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
obj, err := fs.Get(c.Request.Context(), reqPath, &fs.GetArgs{})
|
obj, err := fs.Get(c.Request.Context(), reqPath, &fs.GetArgs{
|
||||||
|
WithStorageDetails: !user.IsGuest() && !setting.GetBool(conf.HideStorageDetails),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -350,20 +358,22 @@ func FsGet(c *gin.Context, req *FsGetReq, user *model.User) {
|
|||||||
}
|
}
|
||||||
parentMeta, _ := op.GetNearestMeta(parentPath)
|
parentMeta, _ := op.GetNearestMeta(parentPath)
|
||||||
thumb, _ := model.GetThumb(obj)
|
thumb, _ := model.GetThumb(obj)
|
||||||
|
mountDetails, _ := model.GetStorageDetails(obj)
|
||||||
common.SuccessResp(c, FsGetResp{
|
common.SuccessResp(c, FsGetResp{
|
||||||
ObjResp: ObjResp{
|
ObjResp: ObjResp{
|
||||||
Id: obj.GetID(),
|
Id: obj.GetID(),
|
||||||
Path: obj.GetPath(),
|
Path: obj.GetPath(),
|
||||||
Name: obj.GetName(),
|
Name: obj.GetName(),
|
||||||
Size: obj.GetSize(),
|
Size: obj.GetSize(),
|
||||||
IsDir: obj.IsDir(),
|
IsDir: obj.IsDir(),
|
||||||
Modified: obj.ModTime(),
|
Modified: obj.ModTime(),
|
||||||
Created: obj.CreateTime(),
|
Created: obj.CreateTime(),
|
||||||
HashInfoStr: obj.GetHash().String(),
|
HashInfoStr: obj.GetHash().String(),
|
||||||
HashInfo: obj.GetHash().Export(),
|
HashInfo: obj.GetHash().Export(),
|
||||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
||||||
Type: utils.GetFileType(obj.GetName()),
|
Type: utils.GetFileType(obj.GetName()),
|
||||||
Thumb: thumb,
|
Thumb: thumb,
|
||||||
|
MountDetails: mountDetails,
|
||||||
},
|
},
|
||||||
RawURL: rawURL,
|
RawURL: rawURL,
|
||||||
Readme: getReadme(meta, reqPath),
|
Readme: getReadme(meta, reqPath),
|
||||||
|
@ -3,9 +3,11 @@ package handles
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/db"
|
"github.com/OpenListTeam/OpenList/v4/internal/db"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||||
@ -13,6 +15,42 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type StorageResp struct {
|
||||||
|
model.Storage
|
||||||
|
MountDetails *model.StorageDetails `json:"mount_details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeStorageResp(c *gin.Context, storages []model.Storage) []*StorageResp {
|
||||||
|
ret := make([]*StorageResp, len(storages))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, s := range storages {
|
||||||
|
ret[i] = &StorageResp{
|
||||||
|
Storage: s,
|
||||||
|
MountDetails: nil,
|
||||||
|
}
|
||||||
|
d, err := op.GetStorageByMountPath(s.MountPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wd, ok := d.(driver.WithDetails)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
details, err := wd.GetDetails(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed get %s details: %+v", s.MountPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret[i].MountDetails = details
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func ListStorages(c *gin.Context) {
|
func ListStorages(c *gin.Context) {
|
||||||
var req model.PageReq
|
var req model.PageReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
@ -27,7 +65,7 @@ func ListStorages(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.SuccessResp(c, common.PageResp{
|
common.SuccessResp(c, common.PageResp{
|
||||||
Content: storages,
|
Content: makeStorageResp(c, storages),
|
||||||
Total: total,
|
Total: total,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user