mirror of
https://github.com/AlistGo/alist.git
synced 2025-04-24 06:14:04 +08:00
Merge a4da4f92fe409d01946d9d51cd4b666dca844fca into 41bdab49aa8acca9e88862c3db55cd7a8a84ba6a
This commit is contained in:
commit
4abfa0e552
@ -20,6 +20,7 @@ func Init() {
|
||||
bootstrap.InitStreamLimit()
|
||||
bootstrap.InitIndex()
|
||||
bootstrap.InitUpgradePatch()
|
||||
bootstrap.InitUsage()
|
||||
}
|
||||
|
||||
func Release() {
|
||||
|
@ -160,6 +160,8 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,alist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||
{Key: conf.GlobalStorageSize, Value: "-1", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||
{Key: conf.UsageScanInterval, Value: "3600", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
|
||||
// single settings
|
||||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
|
12
internal/bootstrap/usage.go
Normal file
12
internal/bootstrap/usage.go
Normal file
@ -0,0 +1,12 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/usage"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// InitUsage 初始化使用量统计
|
||||
func InitUsage() {
|
||||
log.Info("init usage calculation")
|
||||
usage.Init()
|
||||
}
|
@ -45,6 +45,8 @@ const (
|
||||
ForwardDirectLinkParams = "forward_direct_link_params"
|
||||
IgnoreDirectLinkParams = "ignore_direct_link_params"
|
||||
WebauthnLoginEnabled = "webauthn_login_enabled"
|
||||
GlobalStorageSize = "global_storage_size"
|
||||
UsageScanInterval = "usage_scan_interval"
|
||||
|
||||
// index
|
||||
SearchIndex = "search_index"
|
||||
|
@ -1,9 +1,71 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Size int64
|
||||
|
||||
func formatSize(size int64) string {
|
||||
if size < 0 {
|
||||
return "Unknown"
|
||||
}
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
||||
index := 0
|
||||
fsize := float64(size)
|
||||
for fsize > 1024 && index < len(units)-1 {
|
||||
fsize /= 1024
|
||||
index++
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", fsize, units[index])
|
||||
}
|
||||
|
||||
func (s Size) String() string {
|
||||
return formatSize(int64(s))
|
||||
}
|
||||
|
||||
// Usage 存储使用量信息
|
||||
type Usage struct {
|
||||
Available int64 `json:"available"`
|
||||
Used int64 `json:"used"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// NewEmptyUsage 创建一个新的未知使用量信息(无限容量)
|
||||
func NewEmptyUsage() *Usage {
|
||||
return &Usage{
|
||||
Available: -1,
|
||||
Used: 0,
|
||||
Total: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTotalGB 设置总容量(GB单位)
|
||||
func (u *Usage) SetTotalGB(totalGB int64) {
|
||||
if totalGB <= 0 {
|
||||
u.Total = -1
|
||||
u.Available = -1
|
||||
return
|
||||
}
|
||||
u.Total = totalGB * 1024 * 1024 * 1024
|
||||
u.Available = u.Total - u.Used
|
||||
if u.Available < 0 {
|
||||
u.Available = 0
|
||||
}
|
||||
}
|
||||
|
||||
// AddUsed 增加已使用容量
|
||||
func (u *Usage) AddUsed(size int64) {
|
||||
u.Used += size
|
||||
if u.Total > 0 {
|
||||
u.Available = u.Total - u.Used
|
||||
if u.Available < 0 {
|
||||
u.Available = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||
MountPath string `json:"mount_path" gorm:"unique" binding:"required"` // must be standardized
|
||||
@ -57,3 +119,6 @@ func (p Proxy) WebdavProxy() bool {
|
||||
func (p Proxy) WebdavNative() bool {
|
||||
return !p.Webdav302() && !p.WebdavProxy()
|
||||
}
|
||||
|
||||
type MountedStorage struct {
|
||||
}
|
||||
|
@ -51,6 +51,27 @@ func Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64
|
||||
return instance.Search(ctx, req)
|
||||
}
|
||||
|
||||
// GetAllNodes 获取所有索引的节点
|
||||
func GetAllNodes(ctx context.Context) ([]model.SearchNode, error) {
|
||||
if instance == nil {
|
||||
return nil, errs.SearchNotAvailable
|
||||
}
|
||||
|
||||
// 使用一个空白的搜索请求,获取所有文件
|
||||
req := model.SearchReq{
|
||||
Parent: "",
|
||||
Keywords: "",
|
||||
Scope: 0, // 所有类型
|
||||
PageReq: model.PageReq{
|
||||
Page: 1,
|
||||
PerPage: model.MaxInt, // 获取所有结果
|
||||
},
|
||||
}
|
||||
|
||||
nodes, _, err := instance.Search(ctx, req)
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
func Index(ctx context.Context, parent string, obj model.Obj) error {
|
||||
if instance == nil {
|
||||
return errs.SearchNotAvailable
|
||||
|
134
internal/usage/usage.go
Normal file
134
internal/usage/usage.go
Normal file
@ -0,0 +1,134 @@
|
||||
package usage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/search"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
globalUsage = model.NewEmptyUsage()
|
||||
usageLock = &sync.RWMutex{}
|
||||
lastScanTime = time.Time{}
|
||||
isScanning = false
|
||||
scanningLock = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// GetGlobalUsage 获取全局使用量信息
|
||||
func GetGlobalUsage() *model.Usage {
|
||||
usageLock.RLock()
|
||||
defer usageLock.RUnlock()
|
||||
return globalUsage
|
||||
}
|
||||
|
||||
// GetGlobalStorageSizeGB 获取全局存储容量(GB)
|
||||
func GetGlobalStorageSizeGB() int64 {
|
||||
sizeStr := setting.GetStr(conf.GlobalStorageSize)
|
||||
size, err := utils.ParseInt(sizeStr)
|
||||
if err != nil || size <= -2 {
|
||||
// 如果解析失败或值非法,使用默认值 -1
|
||||
log.Warnf("invalid global storage size: %s, using default -1", sizeStr)
|
||||
return -1
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// GetScanInterval 获取扫描间隔(秒)
|
||||
func GetScanInterval() int64 {
|
||||
intervalStr := setting.GetStr(conf.UsageScanInterval)
|
||||
interval, err := utils.ParseInt(intervalStr)
|
||||
if err != nil || interval < 0 {
|
||||
// 如果解析失败或值为负数,使用默认值 3600
|
||||
log.Warnf("invalid scan interval: %s, using default 3600", intervalStr)
|
||||
return 3600
|
||||
}
|
||||
return interval
|
||||
}
|
||||
|
||||
// ScanUsageIfNeeded 如果需要则扫描使用量
|
||||
func ScanUsageIfNeeded() {
|
||||
// 如果全局容量设置为 -1 或 0,则不扫描
|
||||
globalSize := GetGlobalStorageSizeGB()
|
||||
if globalSize <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否超过扫描间隔
|
||||
scanInterval := GetScanInterval()
|
||||
now := time.Now()
|
||||
|
||||
scanningLock.Lock()
|
||||
if isScanning || now.Sub(lastScanTime).Seconds() < float64(scanInterval) {
|
||||
scanningLock.Unlock()
|
||||
return
|
||||
}
|
||||
isScanning = true
|
||||
scanningLock.Unlock()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
scanningLock.Lock()
|
||||
isScanning = false
|
||||
lastScanTime = time.Now()
|
||||
scanningLock.Unlock()
|
||||
}()
|
||||
|
||||
log.Infof("start scanning usage")
|
||||
ctx := context.Background()
|
||||
size, err := CalculateUsage(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("calculate usage error: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
usageLock.Lock()
|
||||
globalUsage.Used = size
|
||||
globalUsage.SetTotalGB(globalSize)
|
||||
usageLock.Unlock()
|
||||
|
||||
log.Infof("usage scan completed: used %s, total %s",
|
||||
model.Size(size).String(),
|
||||
model.Size(globalUsage.Total).String())
|
||||
}()
|
||||
}
|
||||
|
||||
// CalculateUsage 计算所有存储的使用量
|
||||
func CalculateUsage(ctx context.Context) (int64, error) {
|
||||
// 使用搜索来计算总大小
|
||||
var totalSize int64 = 0
|
||||
|
||||
// 获取所有文件信息
|
||||
nodes, err := search.GetAllNodes(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 累计文件大小
|
||||
for _, node := range nodes {
|
||||
if !node.IsDir {
|
||||
totalSize += node.Size
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize, nil
|
||||
}
|
||||
|
||||
// Init 初始化使用量模块
|
||||
func Init() {
|
||||
globalSize := GetGlobalStorageSizeGB()
|
||||
usageLock.Lock()
|
||||
globalUsage.SetTotalGB(globalSize)
|
||||
usageLock.Unlock()
|
||||
|
||||
// 初始进行一次扫描
|
||||
if globalSize > 0 {
|
||||
go ScanUsageIfNeeded()
|
||||
}
|
||||
}
|
10
pkg/utils/convert.go
Normal file
10
pkg/utils/convert.go
Normal file
@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ParseInt 将字符串解析为int64
|
||||
func ParseInt(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/internal/usage"
|
||||
"github.com/alist-org/alist/v3/server/webdav"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -44,6 +45,7 @@ func WebDav(dav *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func ServeWebDAV(c *gin.Context) {
|
||||
usage.ScanUsageIfNeeded()
|
||||
user := c.MustGet("user").(*model.User)
|
||||
ctx := context.WithValue(c.Request.Context(), "user", user)
|
||||
handler.ServeHTTP(c.Writer, c.Request.WithContext(ctx))
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/usage"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
)
|
||||
|
||||
@ -165,6 +166,14 @@ var liveProps = map[xml.Name]struct {
|
||||
findFn: findChecksums,
|
||||
dir: false,
|
||||
},
|
||||
{Space: "DAV:", Local: "quota-available-bytes"}: {
|
||||
findFn: findQuotaAvailable,
|
||||
dir: true,
|
||||
},
|
||||
{Space: "DAV:", Local: "quota-used-bytes"}: {
|
||||
findFn: findQuotaUsed,
|
||||
dir: true,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(nigeltao) merge props and allprop?
|
||||
@ -496,3 +505,18 @@ func findChecksums(ctx context.Context, ls LockSystem, name string, fi model.Obj
|
||||
}
|
||||
return checksums, nil
|
||||
}
|
||||
|
||||
func findQuotaAvailable(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
globalUsage := usage.GetGlobalUsage()
|
||||
if globalUsage.Available < 0 {
|
||||
// 如果是无限空间,返回一个大数值
|
||||
return "9223372036854775807", nil // maxInt64
|
||||
}
|
||||
|
||||
return strconv.FormatInt(globalUsage.Available, 10), nil
|
||||
}
|
||||
|
||||
func findQuotaUsed(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
globalUsage := usage.GetGlobalUsage()
|
||||
return strconv.FormatInt(globalUsage.Used, 10), nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user