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
|
||||
}
|
||||
|
||||
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) {
|
||||
// // TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
|
||||
// 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")
|
||||
}
|
||||
|
||||
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.PutResult = (*Open123)(nil)
|
||||
|
@ -133,9 +133,9 @@ type UserInfoResp struct {
|
||||
// HeadImage string `json:"headImage"`
|
||||
// Passport string `json:"passport"`
|
||||
// Mail string `json:"mail"`
|
||||
// SpaceUsed int64 `json:"spaceUsed"`
|
||||
// SpacePermanent int64 `json:"spacePermanent"`
|
||||
// SpaceTemp int64 `json:"spaceTemp"`
|
||||
SpaceUsed uint64 `json:"spaceUsed"`
|
||||
SpacePermanent uint64 `json:"spacePermanent"`
|
||||
SpaceTemp uint64 `json:"spaceTemp"`
|
||||
// SpaceTempExpr int64 `json:"spaceTempExpr"`
|
||||
// Vip bool `json:"vip"`
|
||||
// DirectTraffic int64 `json:"directTraffic"`
|
||||
|
@ -291,6 +291,21 @@ func (d *AliyundriveOpen) Other(ctx context.Context, args model.OtherArgs) (inte
|
||||
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.MkdirResult = (*AliyundriveOpen)(nil)
|
||||
var _ driver.MoveResult = (*AliyundriveOpen)(nil)
|
||||
|
@ -364,4 +364,12 @@ func (d *BaiduNetdisk) uploadSlice(ctx context.Context, params map[string]string
|
||||
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)
|
||||
|
@ -189,3 +189,12 @@ type PrecreateResp struct {
|
||||
// return_type=2
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
// r := url.QueryEscape(str)
|
||||
// r = strings.ReplaceAll(r, "+", "%20")
|
||||
|
@ -339,6 +339,21 @@ func (d *CloudreveV4) ArchiveDecompress(ctx context.Context, srcObj, dstDir mode
|
||||
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) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
@ -204,3 +204,9 @@ type FolderSummaryResp struct {
|
||||
CalculatedAt time.Time `json:"calculated_at"`
|
||||
} `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
|
||||
}
|
||||
|
||||
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) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
@ -434,4 +434,14 @@ func (d *Local) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
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)
|
||||
|
@ -5,8 +5,25 @@ package local
|
||||
import (
|
||||
"io/fs"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
)
|
||||
|
||||
func isHidden(f fs.FileInfo, _ string) bool {
|
||||
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
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func isHidden(f fs.FileInfo, fullPath string) bool {
|
||||
filePath := filepath.Join(fullPath, f.Name())
|
||||
namePtr, err := syscall.UTF16PtrFromString(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
attrs, err := syscall.GetFileAttributes(namePtr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
|
||||
}
|
||||
//go:build windows
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func isHidden(f fs.FileInfo, fullPath string) bool {
|
||||
filePath := filepath.Join(fullPath, f.Name())
|
||||
namePtr, err := syscall.UTF16PtrFromString(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
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"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"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
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -205,6 +205,22 @@ func (d *SMB) Put(ctx context.Context, dstDir model.Obj, stream model.FileStream
|
||||
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) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
@ -93,6 +93,11 @@ func (d *Template) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.O
|
||||
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) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
@ -114,6 +114,7 @@ func InitialSettings() []model.SettingItem {
|
||||
{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: "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
|
||||
{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},
|
||||
|
@ -17,9 +17,10 @@ const (
|
||||
AllowMounted = "allow_mounted"
|
||||
RobotsTxt = "robots_txt"
|
||||
|
||||
Logo = "logo" // multi-lines text, L1: light, EOL: dark
|
||||
Favicon = "favicon"
|
||||
MainColor = "main_color"
|
||||
Logo = "logo" // multi-lines text, L1: light, EOL: dark
|
||||
Favicon = "favicon"
|
||||
MainColor = "main_color"
|
||||
HideStorageDetails = "hide_storage_details"
|
||||
|
||||
// preview
|
||||
TextTypes = "text_types"
|
||||
|
@ -210,6 +210,11 @@ type ArchiveDecompressResult interface {
|
||||
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 {
|
||||
InitReference(storage Driver) error
|
||||
}
|
||||
|
@ -19,8 +19,9 @@ import (
|
||||
// then pass the actual path to the op package
|
||||
|
||||
type ListArgs struct {
|
||||
Refresh bool
|
||||
NoLog bool
|
||||
Refresh bool
|
||||
NoLog bool
|
||||
WithStorageDetails bool
|
||||
}
|
||||
|
||||
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 {
|
||||
NoLog bool
|
||||
NoLog bool
|
||||
WithStorageDetails bool
|
||||
}
|
||||
|
||||
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 !args.NoLog {
|
||||
log.Warnf("failed get %s: %s", path, err)
|
||||
|
@ -11,11 +11,11 @@ import (
|
||||
"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)
|
||||
// maybe a virtual file
|
||||
if path != "/" {
|
||||
virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(path))
|
||||
virtualFiles := op.GetStorageVirtualFilesWithDetailsByPath(ctx, stdpath.Dir(path), !args.WithStorageDetails)
|
||||
for _, f := range virtualFiles {
|
||||
if f.GetName() == stdpath.Base(path) {
|
||||
return f, nil
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||
meta, _ := ctx.Value(conf.MetaKey).(*model.Meta)
|
||||
user, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
||||
virtualFiles := op.GetStorageVirtualFilesWithDetailsByPath(ctx, path, !args.WithStorageDetails)
|
||||
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
||||
if err != nil && len(virtualFiles) == 0 {
|
||||
return nil, errors.WithMessage(err, "failed get storage")
|
||||
|
@ -55,3 +55,40 @@ func (p Proxy) Webdav302() bool {
|
||||
func (p Proxy) WebdavProxyURL() bool {
|
||||
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/pkg/generic_sync"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/pkg/errors"
|
||||
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
|
||||
// GetStorageVirtualFilesByPath(/a) => b,c,d
|
||||
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)
|
||||
storages := storagesMap.Values()
|
||||
sort.Slice(storages, func(i, j int) bool {
|
||||
@ -345,21 +378,30 @@ func GetStorageVirtualFilesByPath(prefix string) []model.Obj {
|
||||
})
|
||||
|
||||
prefix = utils.FixAndCleanPath(prefix)
|
||||
set := mapset.NewSet[string]()
|
||||
set := make(map[string]int)
|
||||
for _, v := range storages {
|
||||
mountPath := utils.GetActualMountPath(v.GetStorage().MountPath)
|
||||
// Exclude prefix itself and non prefix
|
||||
if len(prefix) >= len(mountPath) || !utils.IsSubPath(prefix, mountPath) {
|
||||
continue
|
||||
}
|
||||
name := strings.SplitN(strings.TrimPrefix(mountPath[len(prefix):], "/"), "/", 2)[0]
|
||||
if set.Add(name) {
|
||||
files = append(files, &model.Object{
|
||||
Name: name,
|
||||
names := strings.SplitN(strings.TrimPrefix(mountPath[len(prefix):], "/"), "/", 2)
|
||||
idx, ok := set[names[0]]
|
||||
if !ok {
|
||||
set[names[0]] = len(files)
|
||||
obj := &model.Object{
|
||||
Name: names[0],
|
||||
Size: 0,
|
||||
Modified: v.GetStorage().Modified,
|
||||
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
|
||||
|
@ -33,18 +33,19 @@ type DirReq struct {
|
||||
}
|
||||
|
||||
type ObjResp struct {
|
||||
Id string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
Modified time.Time `json:"modified"`
|
||||
Created time.Time `json:"created"`
|
||||
Sign string `json:"sign"`
|
||||
Thumb string `json:"thumb"`
|
||||
Type int `json:"type"`
|
||||
HashInfoStr string `json:"hashinfo"`
|
||||
HashInfo map[*utils.HashType]string `json:"hash_info"`
|
||||
Id string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
Modified time.Time `json:"modified"`
|
||||
Created time.Time `json:"created"`
|
||||
Sign string `json:"sign"`
|
||||
Thumb string `json:"thumb"`
|
||||
Type int `json:"type"`
|
||||
HashInfoStr string `json:"hashinfo"`
|
||||
HashInfo map[*utils.HashType]string `json:"hash_info"`
|
||||
MountDetails *model.StorageDetailsWithName `json:"mount_details,omitempty"`
|
||||
}
|
||||
|
||||
type FsListResp struct {
|
||||
@ -98,7 +99,10 @@ func FsList(c *gin.Context, req *ListReq, user *model.User) {
|
||||
common.ErrorStrResp(c, "Refresh without permission", 403)
|
||||
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 {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
@ -224,19 +228,21 @@ func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
||||
var resp []ObjResp
|
||||
for _, obj := range objs {
|
||||
thumb, _ := model.GetThumb(obj)
|
||||
mountDetails, _ := model.GetStorageDetails(obj)
|
||||
resp = append(resp, ObjResp{
|
||||
Id: obj.GetID(),
|
||||
Path: obj.GetPath(),
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
IsDir: obj.IsDir(),
|
||||
Modified: obj.ModTime(),
|
||||
Created: obj.CreateTime(),
|
||||
HashInfoStr: obj.GetHash().String(),
|
||||
HashInfo: obj.GetHash().Export(),
|
||||
Sign: common.Sign(obj, parent, encrypt),
|
||||
Thumb: thumb,
|
||||
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
||||
Id: obj.GetID(),
|
||||
Path: obj.GetPath(),
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
IsDir: obj.IsDir(),
|
||||
Modified: obj.ModTime(),
|
||||
Created: obj.CreateTime(),
|
||||
HashInfoStr: obj.GetHash().String(),
|
||||
HashInfo: obj.GetHash().Export(),
|
||||
Sign: common.Sign(obj, parent, encrypt),
|
||||
Thumb: thumb,
|
||||
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
||||
MountDetails: mountDetails,
|
||||
})
|
||||
}
|
||||
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)
|
||||
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 {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
@ -350,20 +358,22 @@ func FsGet(c *gin.Context, req *FsGetReq, user *model.User) {
|
||||
}
|
||||
parentMeta, _ := op.GetNearestMeta(parentPath)
|
||||
thumb, _ := model.GetThumb(obj)
|
||||
mountDetails, _ := model.GetStorageDetails(obj)
|
||||
common.SuccessResp(c, FsGetResp{
|
||||
ObjResp: ObjResp{
|
||||
Id: obj.GetID(),
|
||||
Path: obj.GetPath(),
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
IsDir: obj.IsDir(),
|
||||
Modified: obj.ModTime(),
|
||||
Created: obj.CreateTime(),
|
||||
HashInfoStr: obj.GetHash().String(),
|
||||
HashInfo: obj.GetHash().Export(),
|
||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
||||
Type: utils.GetFileType(obj.GetName()),
|
||||
Thumb: thumb,
|
||||
Id: obj.GetID(),
|
||||
Path: obj.GetPath(),
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
IsDir: obj.IsDir(),
|
||||
Modified: obj.ModTime(),
|
||||
Created: obj.CreateTime(),
|
||||
HashInfoStr: obj.GetHash().String(),
|
||||
HashInfo: obj.GetHash().Export(),
|
||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
||||
Type: utils.GetFileType(obj.GetName()),
|
||||
Thumb: thumb,
|
||||
MountDetails: mountDetails,
|
||||
},
|
||||
RawURL: rawURL,
|
||||
Readme: getReadme(meta, reqPath),
|
||||
|
@ -3,9 +3,11 @@ package handles
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"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/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||
@ -13,6 +15,42 @@ import (
|
||||
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) {
|
||||
var req model.PageReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
@ -27,7 +65,7 @@ func ListStorages(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, common.PageResp{
|
||||
Content: storages,
|
||||
Content: makeStorageResp(c, storages),
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user