Merge branch 'master' into patch-samesite
This commit is contained in:
commit
6a60081a7b
105 changed files with 1051 additions and 611 deletions
|
@ -9,8 +9,10 @@ RUN git clone --recurse-submodules https://github.com/cloudreve/Cloudreve.git
|
|||
|
||||
# build frontend
|
||||
WORKDIR /cloudreve_builder/Cloudreve/assets
|
||||
ENV GENERATE_SOURCEMAP false
|
||||
|
||||
RUN yarn install --network-timeout 1000000
|
||||
RUN yarn run build && find . -name "*.map" -type f -delete
|
||||
RUN yarn run build
|
||||
|
||||
# build backend
|
||||
WORKDIR /cloudreve_builder/Cloudreve
|
||||
|
@ -39,4 +41,4 @@ RUN chmod +x ./cloudreve && mkdir -p /data/aria2 && chmod -R 766 /data/aria2
|
|||
EXPOSE 5212
|
||||
VOLUME ["/cloudreve/uploads", "/cloudreve/avatar", "/data"]
|
||||
|
||||
ENTRYPOINT ["./cloudreve"]
|
||||
ENTRYPOINT ["./cloudreve"]
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 41f585a6f8c8f99ed4b2e279555d6b4dcdf957bc
|
||||
Subproject commit 02d93206cc5b943c34b5f5ac86c23dd96f5ef603
|
|
@ -98,16 +98,7 @@ func Init(path string, statics fs.FS) {
|
|||
}
|
||||
|
||||
for _, dependency := range dependencies {
|
||||
switch dependency.mode {
|
||||
case "master":
|
||||
if conf.SystemConfig.Mode == "master" {
|
||||
dependency.factory()
|
||||
}
|
||||
case "slave":
|
||||
if conf.SystemConfig.Mode == "slave" {
|
||||
dependency.factory()
|
||||
}
|
||||
default:
|
||||
if dependency.mode == conf.SystemConfig.Mode || dependency.mode == "both" {
|
||||
dependency.factory()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ func RunScript(name string) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
if err := invoker.RunDBScript(name, ctx); err != nil {
|
||||
util.Log().Error("数据库脚本执行失败: %s", err)
|
||||
util.Log().Error("Failed to execute database script: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
util.Log().Info("数据库脚本 [%s] 执行完毕", name)
|
||||
util.Log().Info("Finish executing database script %q.", name)
|
||||
}
|
||||
|
|
|
@ -46,13 +46,13 @@ func (b *GinFS) Exists(prefix string, filepath string) bool {
|
|||
// InitStatic 初始化静态资源文件
|
||||
func InitStatic(statics fs.FS) {
|
||||
if util.Exists(util.RelativePath(StaticFolder)) {
|
||||
util.Log().Info("检测到 statics 目录存在,将使用此目录下的静态资源文件")
|
||||
util.Log().Info("Folder with name \"statics\" already exists, it will be used to serve static files.")
|
||||
StaticFS = static.LocalFile(util.RelativePath("statics"), false)
|
||||
} else {
|
||||
// 初始化静态资源
|
||||
embedFS, err := fs.Sub(statics, "assets/build")
|
||||
if err != nil {
|
||||
util.Log().Panic("无法初始化静态资源, %s", err)
|
||||
util.Log().Panic("Failed to initialize static resources: %s", err)
|
||||
}
|
||||
|
||||
StaticFS = &GinFS{
|
||||
|
@ -62,19 +62,19 @@ func InitStatic(statics fs.FS) {
|
|||
// 检查静态资源的版本
|
||||
f, err := StaticFS.Open("version.json")
|
||||
if err != nil {
|
||||
util.Log().Warning("静态资源版本标识文件不存在,请重新构建或删除 statics 目录")
|
||||
util.Log().Warning("Missing version identifier file in static resources, please delete \"statics\" folder and rebuild it.")
|
||||
return
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法读取静态资源文件版本,请重新构建或删除 statics 目录")
|
||||
util.Log().Warning("Failed to read version identifier file in static resources, please delete \"statics\" folder and rebuild it.")
|
||||
return
|
||||
}
|
||||
|
||||
var v staticVersion
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
util.Log().Warning("无法解析静态资源文件版本, %s", err)
|
||||
util.Log().Warning("Failed to parse version identifier file in static resources: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -84,12 +84,12 @@ func InitStatic(statics fs.FS) {
|
|||
}
|
||||
|
||||
if v.Name != staticName {
|
||||
util.Log().Warning("静态资源版本不匹配,请重新构建或删除 statics 目录")
|
||||
util.Log().Warning("Static resource version mismatch, please delete \"statics\" folder and rebuild it.")
|
||||
return
|
||||
}
|
||||
|
||||
if v.Version != conf.RequiredStaticVersion {
|
||||
util.Log().Warning("静态资源版本不匹配 [当前 %s, 需要: %s],请重新构建或删除 statics 目录", v.Version, conf.RequiredStaticVersion)
|
||||
util.Log().Warning("Static resource version mismatch [Current %s, Desired: %s],please delete \"statics\" folder and rebuild it.", v.Version, conf.RequiredStaticVersion)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -99,13 +99,13 @@ func Eject(statics fs.FS) {
|
|||
// 初始化静态资源
|
||||
embedFS, err := fs.Sub(statics, "assets/build")
|
||||
if err != nil {
|
||||
util.Log().Panic("无法初始化静态资源, %s", err)
|
||||
util.Log().Panic("Failed to initialize static resources: %s", err)
|
||||
}
|
||||
|
||||
var walk func(relPath string, d fs.DirEntry, err error) error
|
||||
walk = func(relPath string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return errors.Errorf("无法获取[%s]的信息, %s, 跳过...", relPath, err)
|
||||
return errors.Errorf("Failed to read info of %q: %s, skipping...", relPath, err)
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
|
@ -114,13 +114,13 @@ func Eject(statics fs.FS) {
|
|||
defer out.Close()
|
||||
|
||||
if err != nil {
|
||||
return errors.Errorf("无法创建文件[%s], %s, 跳过...", relPath, err)
|
||||
return errors.Errorf("Failed to create file %q: %s, skipping...", relPath, err)
|
||||
}
|
||||
|
||||
util.Log().Info("导出 [%s]...", relPath)
|
||||
util.Log().Info("Ejecting %q...", relPath)
|
||||
obj, _ := embedFS.Open(relPath)
|
||||
if _, err := io.Copy(out, bufio.NewReader(obj)); err != nil {
|
||||
return errors.Errorf("无法写入文件[%s], %s, 跳过...", relPath, err)
|
||||
return errors.Errorf("Cannot write file %q: %s, skipping...", relPath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -129,8 +129,8 @@ func Eject(statics fs.FS) {
|
|||
// util.Log().Info("开始导出内置静态资源...")
|
||||
err = fs.WalkDir(embedFS, ".", walk)
|
||||
if err != nil {
|
||||
util.Log().Error("导出内置静态资源遇到错误:%s", err)
|
||||
util.Log().Error("Error occurs while ejecting static resources: %s", err)
|
||||
return
|
||||
}
|
||||
util.Log().Info("内置静态资源导出完成")
|
||||
util.Log().Info("Finish ejecting static resources.")
|
||||
}
|
||||
|
|
2
build.sh
2
build.sh
|
@ -24,13 +24,13 @@ buildAssets() {
|
|||
rm -rf assets/build
|
||||
|
||||
export CI=false
|
||||
export GENERATE_SOURCEMAP=false
|
||||
|
||||
cd $REPO/assets
|
||||
|
||||
yarn install
|
||||
yarn run build
|
||||
cd build
|
||||
find . -name "*.map" -type f -delete
|
||||
cd $REPO
|
||||
zip -r - assets/build >assets.zip
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -39,7 +39,6 @@ require (
|
|||
github.com/upyun/go-sdk v2.1.0+incompatible
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -101,7 +100,6 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.12 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1436,10 +1436,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
|
|||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
|
59
main.go
59
main.go
|
@ -1,14 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"flag"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v3/bootstrap"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/cloudreve/Cloudreve/v3/routers"
|
||||
|
@ -41,6 +48,9 @@ func init() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
// 关闭数据库连接
|
||||
defer model.DB.Close()
|
||||
|
||||
if isEject {
|
||||
// 开始导出内置静态资源文件
|
||||
bootstrap.Eject(staticFS)
|
||||
|
@ -54,16 +64,35 @@ func main() {
|
|||
}
|
||||
|
||||
api := routers.InitRouter()
|
||||
server := &http.Server{Handler: api}
|
||||
|
||||
// 收到信号后关闭服务器
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
go func() {
|
||||
sig := <-sigChan
|
||||
util.Log().Info("收到信号 %s,开始关闭 server", sig)
|
||||
ctx := context.Background()
|
||||
if conf.SystemConfig.GracePeriod != 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(conf.SystemConfig.GracePeriod)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
err := server.Shutdown(ctx)
|
||||
if err != nil {
|
||||
util.Log().Error("关闭 server 错误, %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 如果启用了SSL
|
||||
if conf.SSLConfig.CertPath != "" {
|
||||
go func() {
|
||||
util.Log().Info("开始监听 %s", conf.SSLConfig.Listen)
|
||||
if err := api.RunTLS(conf.SSLConfig.Listen,
|
||||
conf.SSLConfig.CertPath, conf.SSLConfig.KeyPath); err != nil {
|
||||
util.Log().Error("无法监听[%s],%s", conf.SSLConfig.Listen, err)
|
||||
}
|
||||
}()
|
||||
util.Log().Info("开始监听 %s", conf.SSLConfig.Listen)
|
||||
server.Addr = conf.SSLConfig.Listen
|
||||
if err := server.ListenAndServeTLS(conf.SSLConfig.CertPath, conf.SSLConfig.KeyPath); err != nil {
|
||||
util.Log().Error("无法监听[%s],%s", conf.SSLConfig.Listen, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果启用了Unix
|
||||
|
@ -78,14 +107,26 @@ func main() {
|
|||
|
||||
api.TrustedPlatform = conf.UnixConfig.ProxyHeader
|
||||
util.Log().Info("开始监听 %s", conf.UnixConfig.Listen)
|
||||
if err := api.RunUnix(conf.UnixConfig.Listen); err != nil {
|
||||
if err := RunUnix(server); err != nil {
|
||||
util.Log().Error("无法监听[%s],%s", conf.UnixConfig.Listen, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
util.Log().Info("开始监听 %s", conf.SystemConfig.Listen)
|
||||
if err := api.Run(conf.SystemConfig.Listen); err != nil {
|
||||
server.Addr = conf.SystemConfig.Listen
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
util.Log().Error("无法监听[%s],%s", conf.SystemConfig.Listen, err)
|
||||
}
|
||||
}
|
||||
|
||||
func RunUnix(server *http.Server) error {
|
||||
listener, err := net.Listen("unix", conf.UnixConfig.Listen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
defer os.Remove(conf.UnixConfig.Listen)
|
||||
|
||||
return server.Serve(listener)
|
||||
}
|
||||
|
|
|
@ -142,18 +142,18 @@ func uploadCallbackCheck(c *gin.Context, policyType string) serializer.Response
|
|||
// 验证 Callback Key
|
||||
sessionID := c.Param("sessionID")
|
||||
if sessionID == "" {
|
||||
return serializer.ParamErr("Session ID 不能为空", nil)
|
||||
return serializer.ParamErr("Session ID cannot be empty", nil)
|
||||
}
|
||||
|
||||
callbackSessionRaw, exist := cache.Get(filesystem.UploadSessionCachePrefix + sessionID)
|
||||
if !exist {
|
||||
return serializer.ParamErr("上传会话不存在或已过期", nil)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "上传会话不存在或已过期", nil)
|
||||
}
|
||||
|
||||
callbackSession := callbackSessionRaw.(serializer.UploadSession)
|
||||
c.Set(filesystem.UploadSessionCtx, &callbackSession)
|
||||
if callbackSession.Policy.Type != policyType {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "Policy not supported", nil)
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 清理回调会话
|
||||
|
@ -162,7 +162,7 @@ func uploadCallbackCheck(c *gin.Context, policyType string) serializer.Response
|
|||
// 查找用户
|
||||
user, err := model.GetActiveUserByID(callbackSession.UID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeCheckLogin, "找不到用户", err)
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
c.Set(filesystem.UserCtx, &user)
|
||||
return serializer.Response{}
|
||||
|
@ -194,14 +194,14 @@ func QiniuCallbackAuth() gin.HandlerFunc {
|
|||
mac := qbox.NewMac(session.Policy.AccessKey, session.Policy.SecretKey)
|
||||
ok, err := mac.VerifyCallback(c.Request)
|
||||
if err != nil {
|
||||
util.Log().Debug("无法验证回调请求,%s", err)
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "无法验证回调请求"})
|
||||
util.Log().Debug("Failed to verify callback request: %s", err)
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Failed to verify callback request."})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "回调签名无效"})
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Invalid signature."})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -215,8 +215,8 @@ func OSSCallbackAuth() gin.HandlerFunc {
|
|||
return func(c *gin.Context) {
|
||||
err := oss.VerifyCallbackSignature(c.Request)
|
||||
if err != nil {
|
||||
util.Log().Debug("回调签名验证失败,%s", err)
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "回调签名验证失败"})
|
||||
util.Log().Debug("Failed to verify callback request: %s", err)
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Failed to verify callback request."})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ func UpyunCallbackAuth() gin.HandlerFunc {
|
|||
// 计算正文MD5
|
||||
actualContentMD5 := fmt.Sprintf("%x", md5.Sum(body))
|
||||
if actualContentMD5 != contentMD5 {
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "MD5不一致"})
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "MD5 mismatch."})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ func UpyunCallbackAuth() gin.HandlerFunc {
|
|||
|
||||
// 对比签名
|
||||
if signature != actualSignature {
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "鉴权失败"})
|
||||
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Signature not match"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -289,7 +289,7 @@ func IsAdmin() gin.HandlerFunc {
|
|||
return func(c *gin.Context) {
|
||||
user, _ := c.Get("user")
|
||||
if user.(*model.User).Group.ID != 1 && user.(*model.User).ID != 1 {
|
||||
c.JSON(200, serializer.Err(serializer.CodeAdminRequired, "您不是管理组成员", nil))
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func UseSlaveAria2Instance(clusterController cluster.Controller) gin.HandlerFunc
|
|||
// 获取对应主机节点的从机Aria2实例
|
||||
caller, err := clusterController.GetAria2Instance(siteID.(string))
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNotSet, "无法获取 Aria2 实例", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeNotSet, "Failed to get Aria2 instance", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func UseSlaveAria2Instance(clusterController cluster.Controller) gin.HandlerFunc
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(200, serializer.ParamErr("未知的主机节点ID", nil))
|
||||
c.JSON(200, serializer.ParamErr("Unknown master node ID", nil))
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
@ -44,14 +44,14 @@ func SlaveRPCSignRequired(nodePool cluster.Pool) gin.HandlerFunc {
|
|||
return func(c *gin.Context) {
|
||||
nodeID, err := strconv.ParseUint(c.GetHeader(auth.CrHeaderPrefix+"Node-Id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.ParamErr("未知的主机节点ID", err))
|
||||
c.JSON(200, serializer.ParamErr("Unknown master node ID", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
slaveNode := nodePool.GetNodeByID(uint(nodeID))
|
||||
if slaveNode == nil {
|
||||
c.JSON(200, serializer.ParamErr("未知的主机节点ID", err))
|
||||
c.JSON(200, serializer.ParamErr("Unknown master node ID", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func HashID(IDType int) gin.HandlerFunc {
|
|||
c.Next()
|
||||
return
|
||||
}
|
||||
c.JSON(200, serializer.ParamErr("无法解析对象ID", nil))
|
||||
c.JSON(200, serializer.ParamErr("Failed to parse object ID", nil))
|
||||
c.Abort()
|
||||
return
|
||||
|
||||
|
|
|
@ -23,13 +23,13 @@ func FrontendFileHandler() gin.HandlerFunc {
|
|||
// 读取index.html
|
||||
file, err := bootstrap.StaticFS.Open("/index.html")
|
||||
if err != nil {
|
||||
util.Log().Warning("静态文件[index.html]不存在,可能会影响首页展示")
|
||||
util.Log().Warning("Static file \"index.html\" does not exist, it might affect the display of the homepage.")
|
||||
return ignoreFunc
|
||||
}
|
||||
|
||||
fileContentBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
util.Log().Warning("静态文件[index.html]读取失败,可能会影响首页展示")
|
||||
util.Log().Warning("Cannot read static file \"index.html\", it might affect the display of the homepage.")
|
||||
return ignoreFunc
|
||||
}
|
||||
fileContent := string(fileContentBytes)
|
||||
|
|
|
@ -23,10 +23,10 @@ func Session(secret string) gin.HandlerFunc {
|
|||
var err error
|
||||
Store, err = redis.NewStoreWithDB(10, conf.RedisConfig.Network, conf.RedisConfig.Server, conf.RedisConfig.Password, conf.RedisConfig.DB, []byte(secret))
|
||||
if err != nil {
|
||||
util.Log().Panic("无法连接到 Redis:%s", err)
|
||||
util.Log().Panic("Failed to connect to Redis:%s", err)
|
||||
}
|
||||
|
||||
util.Log().Info("已连接到 Redis 服务器:%s", conf.RedisConfig.Server)
|
||||
util.Log().Info("Connect to Redis server %q.", conf.RedisConfig.Server)
|
||||
} else {
|
||||
Store = memstore.NewStore([]byte(secret))
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ func CSRFCheck() gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "来源非法", nil))
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "Invalid origin", nil))
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ func ShareOwner() gin.HandlerFunc {
|
|||
if userCtx, ok := c.Get("user"); ok {
|
||||
user = userCtx.(*model.User)
|
||||
} else {
|
||||
c.JSON(200, serializer.Err(serializer.CodeCheckLogin, "请先登录", nil))
|
||||
c.JSON(200, serializer.Err(serializer.CodeCheckLogin, "", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if share, ok := c.Get("share"); ok {
|
||||
if share.(*model.Share).Creator().ID != user.ID {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNotFound, "分享不存在", nil))
|
||||
c.JSON(200, serializer.Err(serializer.CodeShareLinkNotFound, "", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ func ShareAvailable() gin.HandlerFunc {
|
|||
share := model.GetShareByHashID(c.Param("id"))
|
||||
|
||||
if share == nil || !share.IsAvailable() {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNotFound, "分享不存在或已失效", nil))
|
||||
c.JSON(200, serializer.Err(serializer.CodeShareLinkNotFound, "", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func ShareCanPreview() gin.HandlerFunc {
|
|||
c.Next()
|
||||
return
|
||||
}
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "此分享无法预览",
|
||||
c.JSON(200, serializer.Err(serializer.CodeDisabledSharePreview, "",
|
||||
nil))
|
||||
c.Abort()
|
||||
return
|
||||
|
@ -85,7 +85,7 @@ func CheckShareUnlocked() gin.HandlerFunc {
|
|||
unlocked := util.GetSession(c, sessionKey) != nil
|
||||
if !unlocked {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr,
|
||||
"无权访问此分享", nil))
|
||||
"", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func BeforeShareDownload() gin.HandlerFunc {
|
|||
// 检查用户是否可以下载此分享的文件
|
||||
err := share.CanBeDownloadBy(user)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, err.Error(),
|
||||
c.JSON(200, serializer.Err(serializer.CodeGroupNotAllowed, err.Error(),
|
||||
nil))
|
||||
c.Abort()
|
||||
return
|
||||
|
@ -118,7 +118,7 @@ func BeforeShareDownload() gin.HandlerFunc {
|
|||
// 对积分、下载次数进行更新
|
||||
err = share.DownloadBy(user, c)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, err.Error(),
|
||||
c.JSON(200, serializer.Err(serializer.CodeGroupNotAllowed, err.Error(),
|
||||
nil))
|
||||
c.Abort()
|
||||
return
|
||||
|
|
|
@ -11,9 +11,9 @@ var defaultSettings = []Setting{
|
|||
{Name: "siteName", Value: `Cloudreve`, Type: "basic"},
|
||||
{Name: "register_enabled", Value: `1`, Type: "register"},
|
||||
{Name: "default_group", Value: `2`, Type: "register"},
|
||||
{Name: "siteKeywords", Value: `网盘,网盘`, Type: "basic"},
|
||||
{Name: "siteKeywords", Value: `Cloudreve, cloud storage`, Type: "basic"},
|
||||
{Name: "siteDes", Value: `Cloudreve`, Type: "basic"},
|
||||
{Name: "siteTitle", Value: `平步云端`, Type: "basic"},
|
||||
{Name: "siteTitle", Value: `Inclusive cloud storage for everyone`, Type: "basic"},
|
||||
{Name: "siteScript", Value: ``, Type: "basic"},
|
||||
{Name: "siteID", Value: uuid.Must(uuid.NewV4()).String(), Type: "basic"},
|
||||
{Name: "fromName", Value: `Cloudreve`, Type: "mail"},
|
||||
|
|
|
@ -60,7 +60,7 @@ func (task *Download) BeforeSave() (err error) {
|
|||
// Create 创建离线下载记录
|
||||
func (task *Download) Create() (uint, error) {
|
||||
if err := DB.Create(task).Error; err != nil {
|
||||
util.Log().Warning("无法插入离线下载记录, %s", err)
|
||||
util.Log().Warning("Failed to insert download record: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
return task.ID, nil
|
||||
|
@ -69,7 +69,7 @@ func (task *Download) Create() (uint, error) {
|
|||
// Save 更新
|
||||
func (task *Download) Save() error {
|
||||
if err := DB.Save(task).Error; err != nil {
|
||||
util.Log().Warning("无法更新离线下载记录, %s", err)
|
||||
util.Log().Warning("Failed to update download record: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -43,7 +43,7 @@ func (file *File) Create() error {
|
|||
tx := DB.Begin()
|
||||
|
||||
if err := tx.Create(file).Error; err != nil {
|
||||
util.Log().Warning("无法插入文件记录, %s", err)
|
||||
util.Log().Warning("Failed to insert file record: %s", err)
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
@ -186,6 +186,10 @@ func RemoveFilesWithSoftLinks(files []File) ([]File, error) {
|
|||
// 结果值
|
||||
filteredFiles := make([]File, 0)
|
||||
|
||||
if len(files) == 0 {
|
||||
return filteredFiles, nil
|
||||
}
|
||||
|
||||
// 查询软链接的文件
|
||||
var filesWithSoftLinks []File
|
||||
tx := DB
|
||||
|
|
|
@ -257,6 +257,19 @@ func TestFile_GetPolicy(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemoveFilesWithSoftLinks_EmptyArg(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
// 传入空
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)files(.+)")
|
||||
file, err := RemoveFilesWithSoftLinks([]File{})
|
||||
asserts.Error(mock.ExpectationsWereMet())
|
||||
asserts.NoError(err)
|
||||
asserts.Equal(len(file), 0)
|
||||
DB.Find(&File{})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveFilesWithSoftLinks(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
files := []File{
|
||||
|
|
|
@ -161,7 +161,7 @@ func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy b
|
|||
// 复制文件记录
|
||||
for _, oldFile := range originFiles {
|
||||
if !oldFile.CanCopy() {
|
||||
util.Log().Warning("无法复制正在上传中的文件 [%s], 跳过...", oldFile.Name)
|
||||
util.Log().Warning("Cannot copy file %q because it's being uploaded now, skipping...", oldFile.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -224,8 +224,8 @@ func (folder *Folder) CopyFolderTo(folderID uint, dstFolder *Folder) (size uint6
|
|||
} else if IDCache, ok := newIDCache[*folder.ParentID]; ok {
|
||||
newID = IDCache
|
||||
} else {
|
||||
util.Log().Warning("无法取得新的父目录:%d", folder.ParentID)
|
||||
return size, errors.New("无法取得新的父目录")
|
||||
util.Log().Warning("Failed to get parent folder %q", folder.ParentID)
|
||||
return size, errors.New("Failed to get parent folder")
|
||||
}
|
||||
|
||||
// 插入新的目录记录
|
||||
|
@ -254,7 +254,7 @@ func (folder *Folder) CopyFolderTo(folderID uint, dstFolder *Folder) (size uint6
|
|||
// 复制文件记录
|
||||
for _, oldFile := range originFiles {
|
||||
if !oldFile.CanCopy() {
|
||||
util.Log().Warning("无法复制正在上传中的文件 [%s], 跳过...", oldFile.Name)
|
||||
util.Log().Warning("Cannot copy file %q because it's being uploaded now, skipping...", oldFile.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ var DB *gorm.DB
|
|||
|
||||
// Init 初始化 MySQL 链接
|
||||
func Init() {
|
||||
util.Log().Info("初始化数据库连接")
|
||||
util.Log().Info("Initializing database connection...")
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
|
@ -51,13 +51,13 @@ func Init() {
|
|||
conf.DatabaseConfig.Name,
|
||||
conf.DatabaseConfig.Charset))
|
||||
default:
|
||||
util.Log().Panic("不支持数据库类型: %s", conf.DatabaseConfig.Type)
|
||||
util.Log().Panic("Unsupported database type %q.", conf.DatabaseConfig.Type)
|
||||
}
|
||||
}
|
||||
|
||||
//db.SetLogger(util.Log())
|
||||
if err != nil {
|
||||
util.Log().Panic("连接数据库不成功, %s", err)
|
||||
util.Log().Panic("Failed to connect to database: %s", err)
|
||||
}
|
||||
|
||||
// 处理表前缀
|
||||
|
|
|
@ -23,12 +23,12 @@ func needMigration() bool {
|
|||
func migration() {
|
||||
// 确认是否需要执行迁移
|
||||
if !needMigration() {
|
||||
util.Log().Info("数据库版本匹配,跳过数据库迁移")
|
||||
util.Log().Info("Database version fulfilled, skip schema migration.")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
util.Log().Info("开始进行数据库初始化...")
|
||||
util.Log().Info("Start initializing database schema...")
|
||||
|
||||
// 清除所有缓存
|
||||
if instance, ok := cache.Store.(*cache.RedisStore); ok {
|
||||
|
@ -61,7 +61,7 @@ func migration() {
|
|||
// 执行数据库升级脚本
|
||||
execUpgradeScripts()
|
||||
|
||||
util.Log().Info("数据库初始化结束")
|
||||
util.Log().Info("Finish initializing database schema.")
|
||||
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ func addDefaultPolicy() {
|
|||
// 未找到初始存储策略时,则创建
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
defaultPolicy := Policy{
|
||||
Name: "默认存储策略",
|
||||
Name: "Default storage policy",
|
||||
Type: "local",
|
||||
MaxSize: 0,
|
||||
AutoRename: true,
|
||||
|
@ -82,7 +82,7 @@ func addDefaultPolicy() {
|
|||
},
|
||||
}
|
||||
if err := DB.Create(&defaultPolicy).Error; err != nil {
|
||||
util.Log().Panic("无法创建初始存储策略, %s", err)
|
||||
util.Log().Panic("Failed to create default storage policy: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ func addDefaultGroups() {
|
|||
// 未找到初始管理组时,则创建
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
defaultAdminGroup := Group{
|
||||
Name: "管理员",
|
||||
Name: "Admin",
|
||||
PolicyList: []uint{1},
|
||||
MaxStorage: 1 * 1024 * 1024 * 1024,
|
||||
ShareEnabled: true,
|
||||
|
@ -113,7 +113,7 @@ func addDefaultGroups() {
|
|||
},
|
||||
}
|
||||
if err := DB.Create(&defaultAdminGroup).Error; err != nil {
|
||||
util.Log().Panic("无法创建管理用户组, %s", err)
|
||||
util.Log().Panic("Failed to create admin user group: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ func addDefaultGroups() {
|
|||
// 未找到初始注册会员时,则创建
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
defaultAdminGroup := Group{
|
||||
Name: "注册会员",
|
||||
Name: "User",
|
||||
PolicyList: []uint{1},
|
||||
MaxStorage: 1 * 1024 * 1024 * 1024,
|
||||
ShareEnabled: true,
|
||||
|
@ -134,7 +134,7 @@ func addDefaultGroups() {
|
|||
},
|
||||
}
|
||||
if err := DB.Create(&defaultAdminGroup).Error; err != nil {
|
||||
util.Log().Panic("无法创建初始注册会员用户组, %s", err)
|
||||
util.Log().Panic("Failed to create initial user group: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ func addDefaultGroups() {
|
|||
// 未找到初始游客用户组时,则创建
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
defaultAdminGroup := Group{
|
||||
Name: "游客",
|
||||
Name: "Anonymous",
|
||||
PolicyList: []uint{},
|
||||
Policies: "[]",
|
||||
OptionsSerialized: GroupOption{
|
||||
|
@ -151,7 +151,7 @@ func addDefaultGroups() {
|
|||
},
|
||||
}
|
||||
if err := DB.Create(&defaultAdminGroup).Error; err != nil {
|
||||
util.Log().Panic("无法创建初始游客用户组, %s", err)
|
||||
util.Log().Panic("Failed to create anonymous user group: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,15 +169,15 @@ func addDefaultUser() {
|
|||
defaultUser.GroupID = 1
|
||||
err := defaultUser.SetPassword(password)
|
||||
if err != nil {
|
||||
util.Log().Panic("无法创建密码, %s", err)
|
||||
util.Log().Panic("Failed to create password: %s", err)
|
||||
}
|
||||
if err := DB.Create(&defaultUser).Error; err != nil {
|
||||
util.Log().Panic("无法创建初始用户, %s", err)
|
||||
util.Log().Panic("Failed to create initial root user: %s", err)
|
||||
}
|
||||
|
||||
c := color.New(color.FgWhite).Add(color.BgBlack).Add(color.Bold)
|
||||
util.Log().Info("初始管理员账号:" + c.Sprint("admin@cloudreve.org"))
|
||||
util.Log().Info("初始管理员密码:" + c.Sprint(password))
|
||||
util.Log().Info("Admin user name: " + c.Sprint("admin@cloudreve.org"))
|
||||
util.Log().Info("Admin password: " + c.Sprint(password))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ func addDefaultNode() {
|
|||
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
defaultAdminGroup := Node{
|
||||
Name: "主机(本机)",
|
||||
Name: "Master (Local machine)",
|
||||
Status: NodeActive,
|
||||
Type: MasterNodeType,
|
||||
Aria2OptionsSerialized: Aria2Option{
|
||||
|
@ -195,7 +195,7 @@ func addDefaultNode() {
|
|||
},
|
||||
}
|
||||
if err := DB.Create(&defaultAdminGroup).Error; err != nil {
|
||||
util.Log().Panic("无法创建初始节点记录, %s", err)
|
||||
util.Log().Panic("Failed to create initial node: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ type Share struct {
|
|||
// Create 创建分享
|
||||
func (share *Share) Create() (uint, error) {
|
||||
if err := DB.Create(share).Error; err != nil {
|
||||
util.Log().Warning("无法插入数据库记录, %s", err)
|
||||
util.Log().Warning("Failed to insert share record: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
return share.ID, nil
|
||||
|
@ -131,9 +131,9 @@ func (share *Share) CanBeDownloadBy(user *User) error {
|
|||
// 用户组权限
|
||||
if !user.Group.OptionsSerialized.ShareDownload {
|
||||
if user.IsAnonymous() {
|
||||
return errors.New("未登录用户无法下载")
|
||||
return errors.New("you must login to download")
|
||||
}
|
||||
return errors.New("您当前的用户组无权下载")
|
||||
return errors.New("your group has no permission to download")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ const (
|
|||
// Create 创建标签记录
|
||||
func (tag *Tag) Create() (uint, error) {
|
||||
if err := DB.Create(tag).Error; err != nil {
|
||||
util.Log().Warning("无法插入离线下载记录, %s", err)
|
||||
util.Log().Warning("Failed to insert tag record: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
return tag.ID, nil
|
||||
|
|
|
@ -19,7 +19,7 @@ type Task struct {
|
|||
// Create 创建任务记录
|
||||
func (task *Task) Create() (uint, error) {
|
||||
if err := DB.Create(task).Error; err != nil {
|
||||
util.Log().Warning("无法插入任务记录, %s", err)
|
||||
util.Log().Warning("Failed to insert task record: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
return task.ID, nil
|
||||
|
|
|
@ -3,8 +3,6 @@ package aria2
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -14,6 +12,8 @@ import (
|
|||
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/monitor"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/balancer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
|
||||
)
|
||||
|
||||
// Instance 默认使用的Aria2处理实例
|
||||
|
@ -40,7 +40,7 @@ func Init(isReload bool, pool cluster.Pool, mqClient mq.MQ) {
|
|||
|
||||
if !isReload {
|
||||
// 从数据库中读取未完成任务,创建监控
|
||||
unfinished := model.GetDownloadsByStatus(common.Ready, common.Paused, common.Downloading)
|
||||
unfinished := model.GetDownloadsByStatus(common.Ready, common.Paused, common.Downloading, common.Seeding)
|
||||
|
||||
for i := 0; i < len(unfinished); i++ {
|
||||
// 创建任务监控
|
||||
|
|
|
@ -46,13 +46,15 @@ const (
|
|||
Canceled
|
||||
// Unknown 未知状态
|
||||
Unknown
|
||||
// Seeding 做种中
|
||||
Seeding
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotEnabled 功能未开启错误
|
||||
ErrNotEnabled = serializer.NewError(serializer.CodeNoPermissionErr, "离线下载功能未开启", nil)
|
||||
ErrNotEnabled = serializer.NewError(serializer.CodeFeatureNotEnabled, "", nil)
|
||||
// ErrUserNotFound 未找到下载任务创建者
|
||||
ErrUserNotFound = serializer.NewError(serializer.CodeNotFound, "无法找到任务创建者", nil)
|
||||
ErrUserNotFound = serializer.NewError(serializer.CodeUserNotFound, "", nil)
|
||||
)
|
||||
|
||||
// DummyAria2 未开启Aria2功能时使用的默认处理器
|
||||
|
@ -94,11 +96,14 @@ func (instance *DummyAria2) DeleteTempFile(src *model.Download) error {
|
|||
}
|
||||
|
||||
// GetStatus 将给定的状态字符串转换为状态标识数字
|
||||
func GetStatus(status string) int {
|
||||
switch status {
|
||||
func GetStatus(status rpc.StatusInfo) int {
|
||||
switch status.Status {
|
||||
case "complete":
|
||||
return Complete
|
||||
case "active":
|
||||
if status.BitTorrent.Mode != "" && status.CompletedLength == status.TotalLength {
|
||||
return Seeding
|
||||
}
|
||||
return Downloading
|
||||
case "waiting":
|
||||
return Ready
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDummyAria2(t *testing.T) {
|
||||
|
@ -35,11 +37,18 @@ func TestDummyAria2(t *testing.T) {
|
|||
func TestGetStatus(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
a.Equal(GetStatus("complete"), Complete)
|
||||
a.Equal(GetStatus("active"), Downloading)
|
||||
a.Equal(GetStatus("waiting"), Ready)
|
||||
a.Equal(GetStatus("paused"), Paused)
|
||||
a.Equal(GetStatus("error"), Error)
|
||||
a.Equal(GetStatus("removed"), Canceled)
|
||||
a.Equal(GetStatus("unknown"), Unknown)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "complete"}), Complete)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "active",
|
||||
BitTorrent: rpc.BitTorrentInfo{Mode: ""}}), Downloading)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "active",
|
||||
BitTorrent: rpc.BitTorrentInfo{Mode: "single"},
|
||||
TotalLength: "100", CompletedLength: "50"}), Downloading)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "active",
|
||||
BitTorrent: rpc.BitTorrentInfo{Mode: "multi"},
|
||||
TotalLength: "100", CompletedLength: "100"}), Seeding)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "waiting"}), Ready)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "paused"}), Paused)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "error"}), Error)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "removed"}), Canceled)
|
||||
a.Equal(GetStatus(rpc.StatusInfo{Status: "unknown"}), Unknown)
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ func NewMonitor(task *model.Download, pool cluster.Pool, mqClient mq.MQ) {
|
|||
|
||||
monitor.notifier = mqClient.Subscribe(monitor.Task.GID, 0)
|
||||
} else {
|
||||
monitor.setErrorStatus(errors.New("节点不可用"))
|
||||
monitor.setErrorStatus(errors.New("node not avaliable"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,11 +77,12 @@ func (monitor *Monitor) Update() bool {
|
|||
|
||||
if err != nil {
|
||||
monitor.retried++
|
||||
util.Log().Warning("无法获取下载任务[%s]的状态,%s", monitor.Task.GID, err)
|
||||
util.Log().Warning("Cannot get status of download task %q: %s", monitor.Task.GID, err)
|
||||
|
||||
// 十次重试后认定为任务失败
|
||||
if monitor.retried > MAX_RETRY {
|
||||
util.Log().Warning("无法获取下载任务[%s]的状态,超过最大重试次数限制,%s", monitor.Task.GID, err)
|
||||
util.Log().Warning("Cannot get status of download task %q,exceed maximum retry threshold: %s",
|
||||
monitor.Task.GID, err)
|
||||
monitor.setErrorStatus(err)
|
||||
monitor.RemoveTempFolder()
|
||||
return true
|
||||
|
@ -93,7 +94,7 @@ func (monitor *Monitor) Update() bool {
|
|||
|
||||
// 磁力链下载需要跟随
|
||||
if len(status.FollowedBy) > 0 {
|
||||
util.Log().Debug("离线下载[%s]重定向至[%s]", monitor.Task.GID, status.FollowedBy[0])
|
||||
util.Log().Debug("Redirected download task from %q to %q.", monitor.Task.GID, status.FollowedBy[0])
|
||||
monitor.Task.GID = status.FollowedBy[0]
|
||||
monitor.Task.Save()
|
||||
return false
|
||||
|
@ -101,28 +102,28 @@ func (monitor *Monitor) Update() bool {
|
|||
|
||||
// 更新任务信息
|
||||
if err := monitor.UpdateTaskInfo(status); err != nil {
|
||||
util.Log().Warning("无法更新下载任务[%s]的任务信息[%s],", monitor.Task.GID, err)
|
||||
util.Log().Warning("Failed to update status of download task %q: %s", monitor.Task.GID, err)
|
||||
monitor.setErrorStatus(err)
|
||||
monitor.RemoveTempFolder()
|
||||
return true
|
||||
}
|
||||
|
||||
util.Log().Debug("离线下载[%s]更新状态[%s]", status.Gid, status.Status)
|
||||
util.Log().Debug("Remote download %q status updated to %q.", status.Gid, status.Status)
|
||||
|
||||
switch status.Status {
|
||||
case "complete":
|
||||
switch common.GetStatus(status) {
|
||||
case common.Complete, common.Seeding:
|
||||
return monitor.Complete(task.TaskPoll)
|
||||
case "error":
|
||||
case common.Error:
|
||||
return monitor.Error(status)
|
||||
case "active", "waiting", "paused":
|
||||
case common.Downloading, common.Ready, common.Paused:
|
||||
return false
|
||||
case "removed":
|
||||
case common.Canceled:
|
||||
monitor.Task.Status = common.Canceled
|
||||
monitor.Task.Save()
|
||||
monitor.RemoveTempFolder()
|
||||
return true
|
||||
default:
|
||||
util.Log().Warning("下载任务[%s]返回未知状态信息[%s],", monitor.Task.GID, status.Status)
|
||||
util.Log().Warning("Download task %q returns unknown status %q.", monitor.Task.GID, status.Status)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +133,7 @@ func (monitor *Monitor) UpdateTaskInfo(status rpc.StatusInfo) error {
|
|||
originSize := monitor.Task.TotalSize
|
||||
|
||||
monitor.Task.GID = status.Gid
|
||||
monitor.Task.Status = common.GetStatus(status.Status)
|
||||
monitor.Task.Status = common.GetStatus(status)
|
||||
|
||||
// 文件大小、已下载大小
|
||||
total, err := strconv.ParseUint(status.TotalLength, 10, 64)
|
||||
|
@ -235,6 +236,40 @@ func (monitor *Monitor) RemoveTempFolder() {
|
|||
|
||||
// Complete 完成下载,返回是否中断监控
|
||||
func (monitor *Monitor) Complete(pool task.Pool) bool {
|
||||
// 未开始转存,提交转存任务
|
||||
if monitor.Task.TaskID == 0 {
|
||||
return monitor.transfer(pool)
|
||||
}
|
||||
|
||||
// 做种完成
|
||||
if common.GetStatus(monitor.Task.StatusInfo) == common.Complete {
|
||||
transferTask, err := model.GetTasksByID(monitor.Task.TaskID)
|
||||
if err != nil {
|
||||
monitor.setErrorStatus(err)
|
||||
monitor.RemoveTempFolder()
|
||||
return true
|
||||
}
|
||||
|
||||
// 转存完成,回收下载目录
|
||||
if transferTask.Type == task.TransferTaskType && transferTask.Status >= task.Error {
|
||||
job, err := task.NewRecycleTask(monitor.Task)
|
||||
if err != nil {
|
||||
monitor.setErrorStatus(err)
|
||||
monitor.RemoveTempFolder()
|
||||
return true
|
||||
}
|
||||
|
||||
// 提交回收任务
|
||||
pool.Submit(job)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (monitor *Monitor) transfer(pool task.Pool) bool {
|
||||
// 创建中转任务
|
||||
file := make([]string, 0, len(monitor.Task.StatusInfo.Files))
|
||||
sizes := make(map[string]uint64, len(monitor.Task.StatusInfo.Files))
|
||||
|
@ -269,7 +304,7 @@ func (monitor *Monitor) Complete(pool task.Pool) bool {
|
|||
monitor.Task.TaskID = job.Model().ID
|
||||
monitor.Task.Save()
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
func (monitor *Monitor) setErrorStatus(err error) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package monitor
|
|||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/common"
|
||||
|
@ -13,7 +15,6 @@ import (
|
|||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
testMock "github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var mock sqlmock.Sqlmock
|
||||
|
@ -431,6 +432,14 @@ func TestMonitor_Complete(t *testing.T) {
|
|||
mock.ExpectExec("UPDATE(.+)downloads").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)tasks").WillReturnRows(sqlmock.NewRows([]string{"id", "type", "status"}).AddRow(1, 2, 4))
|
||||
mock.ExpectQuery("SELECT(.+)users").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(9414))
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT(.+)tasks").WillReturnResult(sqlmock.NewResult(2, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
a.False(m.Complete(mockPool))
|
||||
m.Task.StatusInfo.Status = "complete"
|
||||
a.True(m.Complete(mockPool))
|
||||
a.NoError(mock.ExpectationsWereMet())
|
||||
mockNode.AssertExpectations(t)
|
||||
|
|
|
@ -4,35 +4,27 @@ package rpc
|
|||
|
||||
// StatusInfo represents response of aria2.tellStatus
|
||||
type StatusInfo struct {
|
||||
Gid string `json:"gid"` // GID of the download.
|
||||
Status string `json:"status"` // active for currently downloading/seeding downloads. waiting for downloads in the queue; download is not started. paused for paused downloads. error for downloads that were stopped because of error. complete for stopped and completed downloads. removed for the downloads removed by user.
|
||||
TotalLength string `json:"totalLength"` // Total length of the download in bytes.
|
||||
CompletedLength string `json:"completedLength"` // Completed length of the download in bytes.
|
||||
UploadLength string `json:"uploadLength"` // Uploaded length of the download in bytes.
|
||||
BitField string `json:"bitfield"` // Hexadecimal representation of the download progress. The highest bit corresponds to the piece at index 0. Any set bits indicate loaded pieces, while unset bits indicate not yet loaded and/or missing pieces. Any overflow bits at the end are set to zero. When the download was not started yet, this key will not be included in the response.
|
||||
DownloadSpeed string `json:"downloadSpeed"` // Download speed of this download measured in bytes/sec.
|
||||
UploadSpeed string `json:"uploadSpeed"` // LocalUpload speed of this download measured in bytes/sec.
|
||||
InfoHash string `json:"infoHash"` // InfoHash. BitTorrent only.
|
||||
NumSeeders string `json:"numSeeders"` // The number of seeders aria2 has connected to. BitTorrent only.
|
||||
Seeder string `json:"seeder"` // true if the local endpoint is a seeder. Otherwise false. BitTorrent only.
|
||||
PieceLength string `json:"pieceLength"` // Piece length in bytes.
|
||||
NumPieces string `json:"numPieces"` // The number of pieces.
|
||||
Connections string `json:"connections"` // The number of peers/servers aria2 has connected to.
|
||||
ErrorCode string `json:"errorCode"` // The code of the last error for this item, if any. The value is a string. The error codes are defined in the EXIT STATUS section. This value is only available for stopped/completed downloads.
|
||||
ErrorMessage string `json:"errorMessage"` // The (hopefully) human readable error message associated to errorCode.
|
||||
FollowedBy []string `json:"followedBy"` // List of GIDs which are generated as the result of this download. For example, when aria2 downloads a Metalink file, it generates downloads described in the Metalink (see the --follow-metalink option). This value is useful to track auto-generated downloads. If there are no such downloads, this key will not be included in the response.
|
||||
BelongsTo string `json:"belongsTo"` // GID of a parent download. Some downloads are a part of another download. For example, if a file in a Metalink has BitTorrent resources, the downloads of ".torrent" files are parts of that parent. If this download has no parent, this key will not be included in the response.
|
||||
Dir string `json:"dir"` // Directory to save files.
|
||||
Files []FileInfo `json:"files"` // Returns the list of files. The elements of this list are the same structs used in aria2.getFiles() method.
|
||||
BitTorrent struct {
|
||||
AnnounceList [][]string `json:"announceList"` // List of lists of announce URIs. If the torrent contains announce and no announce-list, announce is converted to the announce-list format.
|
||||
Comment string `json:"comment"` // The comment of the torrent. comment.utf-8 is used if available.
|
||||
CreationDate int64 `json:"creationDate"` // The creation time of the torrent. The value is an integer since the epoch, measured in seconds.
|
||||
Mode string `json:"mode"` // File mode of the torrent. The value is either single or multi.
|
||||
Info struct {
|
||||
Name string `json:"name"` // name in info dictionary. name.utf-8 is used if available.
|
||||
} `json:"info"` // Struct which contains data from Info dictionary. It contains following keys.
|
||||
} `json:"bittorrent"` // Struct which contains information retrieved from the .torrent (file). BitTorrent only. It contains following keys.
|
||||
Gid string `json:"gid"` // GID of the download.
|
||||
Status string `json:"status"` // active for currently downloading/seeding downloads. waiting for downloads in the queue; download is not started. paused for paused downloads. error for downloads that were stopped because of error. complete for stopped and completed downloads. removed for the downloads removed by user.
|
||||
TotalLength string `json:"totalLength"` // Total length of the download in bytes.
|
||||
CompletedLength string `json:"completedLength"` // Completed length of the download in bytes.
|
||||
UploadLength string `json:"uploadLength"` // Uploaded length of the download in bytes.
|
||||
BitField string `json:"bitfield"` // Hexadecimal representation of the download progress. The highest bit corresponds to the piece at index 0. Any set bits indicate loaded pieces, while unset bits indicate not yet loaded and/or missing pieces. Any overflow bits at the end are set to zero. When the download was not started yet, this key will not be included in the response.
|
||||
DownloadSpeed string `json:"downloadSpeed"` // Download speed of this download measured in bytes/sec.
|
||||
UploadSpeed string `json:"uploadSpeed"` // LocalUpload speed of this download measured in bytes/sec.
|
||||
InfoHash string `json:"infoHash"` // InfoHash. BitTorrent only.
|
||||
NumSeeders string `json:"numSeeders"` // The number of seeders aria2 has connected to. BitTorrent only.
|
||||
Seeder string `json:"seeder"` // true if the local endpoint is a seeder. Otherwise false. BitTorrent only.
|
||||
PieceLength string `json:"pieceLength"` // Piece length in bytes.
|
||||
NumPieces string `json:"numPieces"` // The number of pieces.
|
||||
Connections string `json:"connections"` // The number of peers/servers aria2 has connected to.
|
||||
ErrorCode string `json:"errorCode"` // The code of the last error for this item, if any. The value is a string. The error codes are defined in the EXIT STATUS section. This value is only available for stopped/completed downloads.
|
||||
ErrorMessage string `json:"errorMessage"` // The (hopefully) human readable error message associated to errorCode.
|
||||
FollowedBy []string `json:"followedBy"` // List of GIDs which are generated as the result of this download. For example, when aria2 downloads a Metalink file, it generates downloads described in the Metalink (see the --follow-metalink option). This value is useful to track auto-generated downloads. If there are no such downloads, this key will not be included in the response.
|
||||
BelongsTo string `json:"belongsTo"` // GID of a parent download. Some downloads are a part of another download. For example, if a file in a Metalink has BitTorrent resources, the downloads of ".torrent" files are parts of that parent. If this download has no parent, this key will not be included in the response.
|
||||
Dir string `json:"dir"` // Directory to save files.
|
||||
Files []FileInfo `json:"files"` // Returns the list of files. The elements of this list are the same structs used in aria2.getFiles() method.
|
||||
BitTorrent BitTorrentInfo `json:"bittorrent"` // Struct which contains information retrieved from the .torrent (file). BitTorrent only. It contains following keys.
|
||||
}
|
||||
|
||||
// URIInfo represents an element of response of aria2.getUris
|
||||
|
@ -100,3 +92,13 @@ type Method struct {
|
|||
Name string `json:"methodName"` // Method name to call
|
||||
Params []interface{} `json:"params"` // Array containing parameters to the method call
|
||||
}
|
||||
|
||||
type BitTorrentInfo struct {
|
||||
AnnounceList [][]string `json:"announceList"` // List of lists of announce URIs. If the torrent contains announce and no announce-list, announce is converted to the announce-list format.
|
||||
Comment string `json:"comment"` // The comment of the torrent. comment.utf-8 is used if available.
|
||||
CreationDate int64 `json:"creationDate"` // The creation time of the torrent. The value is an integer since the epoch, measured in seconds.
|
||||
Mode string `json:"mode"` // File mode of the torrent. The value is either single or multi.
|
||||
Info struct {
|
||||
Name string `json:"name"` // name in info dictionary. name.utf-8 is used if available.
|
||||
} `json:"info"` // Struct which contains data from Info dictionary. It contains following keys.
|
||||
}
|
||||
|
|
|
@ -17,10 +17,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrAuthFailed = serializer.NewError(serializer.CodeNoPermissionErr, "鉴权失败", nil)
|
||||
ErrAuthFailed = serializer.NewError(serializer.CodeInvalidSign, "invalid sign", nil)
|
||||
ErrAuthHeaderMissing = serializer.NewError(serializer.CodeNoPermissionErr, "authorization header is missing", nil)
|
||||
ErrExpiresMissing = serializer.NewError(serializer.CodeNoPermissionErr, "expire timestamp is missing", nil)
|
||||
ErrExpired = serializer.NewError(serializer.CodeSignExpired, "签名已过期", nil)
|
||||
ErrExpired = serializer.NewError(serializer.CodeSignExpired, "signature expired", nil)
|
||||
)
|
||||
|
||||
const CrHeaderPrefix = "X-Cr-"
|
||||
|
@ -136,7 +136,7 @@ func Init() {
|
|||
} else {
|
||||
secretKey = conf.SlaveConfig.Secret
|
||||
if secretKey == "" {
|
||||
util.Log().Panic("未指定 SlaveSecret,请前往配置文件中指定")
|
||||
util.Log().Panic("SlaveSecret is not set, please specify it in config file.")
|
||||
}
|
||||
}
|
||||
General = HMACAuth{
|
||||
|
|
2
pkg/cache/driver.go
vendored
2
pkg/cache/driver.go
vendored
|
@ -24,7 +24,7 @@ func Init(isSlave bool) {
|
|||
if isSlave {
|
||||
err := Store.Sets(conf.OptionOverwrite, "setting_")
|
||||
if err != nil {
|
||||
util.Log().Warning("无法覆盖数据库设置: %s", err)
|
||||
util.Log().Warning("Failed to overwrite database setting: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
pkg/cache/memo.go
vendored
2
pkg/cache/memo.go
vendored
|
@ -53,7 +53,7 @@ func (store *MemoStore) GarbageCollect() {
|
|||
store.Store.Range(func(key, value interface{}) bool {
|
||||
if item, ok := value.(itemWithTTL); ok {
|
||||
if item.expires > 0 && item.expires < time.Now().Unix() {
|
||||
util.Log().Debug("回收垃圾[%s]", key.(string))
|
||||
util.Log().Debug("Cache %q is garbage collected.", key.(string))
|
||||
store.Store.Delete(key)
|
||||
}
|
||||
}
|
||||
|
|
2
pkg/cache/redis.go
vendored
2
pkg/cache/redis.go
vendored
|
@ -66,7 +66,7 @@ func NewRedisStore(size int, network, address, password, database string) *Redis
|
|||
redis.DialPassword(password),
|
||||
)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法创建Redis连接:%s", err)
|
||||
util.Log().Warning("Failed to create Redis connection: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
|
|
|
@ -8,5 +8,5 @@ import (
|
|||
var (
|
||||
ErrFeatureNotExist = errors.New("No nodes in nodepool match the feature specificed")
|
||||
ErrIlegalPath = errors.New("path out of boundary of setting temp folder")
|
||||
ErrMasterNotFound = serializer.NewError(serializer.CodeMasterNotFound, "未知的主机节点", nil)
|
||||
ErrMasterNotFound = serializer.NewError(serializer.CodeMasterNotFound, "Unknown master node id", nil)
|
||||
)
|
||||
|
|
|
@ -161,7 +161,7 @@ func (r *rpcService) Init() error {
|
|||
// 解析RPC服务地址
|
||||
server, err := url.Parse(r.parent.Model.Aria2OptionsSerialized.Server)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法解析主机 Aria2 RPC 服务地址,%s", err)
|
||||
util.Log().Warning("Failed to parse Aria2 RPC server URL: %s", err)
|
||||
return err
|
||||
}
|
||||
server.Path = "/jsonrpc"
|
||||
|
@ -171,7 +171,7 @@ func (r *rpcService) Init() error {
|
|||
if r.parent.Model.Aria2OptionsSerialized.Options != "" {
|
||||
err = json.Unmarshal([]byte(r.parent.Model.Aria2OptionsSerialized.Options), &globalOptions)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法解析主机 Aria2 配置,%s", err)
|
||||
util.Log().Warning("Failed to parse aria2 options: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ func (r *rpcService) Status(task *model.Download) (rpc.StatusInfo, error) {
|
|||
res, err := r.Caller.TellStatus(task.GID)
|
||||
if err != nil {
|
||||
// 失败后重试
|
||||
util.Log().Debug("无法获取离线下载状态,%s,稍后重试", err)
|
||||
util.Log().Debug("Failed to get download task status, please retry later: %s", err)
|
||||
time.Sleep(r.retryDuration)
|
||||
res, err = r.Caller.TellStatus(task.GID)
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ func (r *rpcService) Cancel(task *model.Download) error {
|
|||
// 取消下载任务
|
||||
_, err := r.Caller.Remove(task.GID)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法取消离线下载任务[%s], %s", task.GID, err)
|
||||
util.Log().Warning("Failed to cancel task %q: %s", task.GID, err)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -264,7 +264,7 @@ func (s *rpcService) DeleteTempFile(task *model.Download) error {
|
|||
time.Sleep(d)
|
||||
err := os.RemoveAll(src)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除离线下载临时目录[%s], %s", src, err)
|
||||
util.Log().Warning("Failed to delete temp download folder: %q: %s", src, err)
|
||||
}
|
||||
}(s.deletePaddingDuration, task.Parent)
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ func Init() {
|
|||
Default = &NodePool{}
|
||||
Default.Init()
|
||||
if err := Default.initFromDB(); err != nil {
|
||||
util.Log().Warning("节点池初始化失败, %s", err)
|
||||
util.Log().Warning("Failed to initialize node pool: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ func (pool *NodePool) GetNodeByID(id uint) Node {
|
|||
}
|
||||
|
||||
func (pool *NodePool) nodeStatusChange(isActive bool, id uint) {
|
||||
util.Log().Debug("从机节点 [ID=%d] 状态变更 [Active=%t]", id, isActive)
|
||||
util.Log().Debug("Slave node [ID=%d] status changed to [Active=%t].", id, isActive)
|
||||
var node Node
|
||||
pool.lock.Lock()
|
||||
if n, ok := pool.inactive[id]; ok {
|
||||
|
|
|
@ -172,7 +172,7 @@ func (node *SlaveNode) StartPingLoop() {
|
|||
recoverDuration := time.Duration(model.GetIntSetting("slave_recover_interval", 600)) * time.Second
|
||||
pingTicker := time.Duration(0)
|
||||
|
||||
util.Log().Debug("从机节点 [%s] 启动心跳循环", node.Model.Name)
|
||||
util.Log().Debug("Slave node %q heartbeat loop started.", node.Model.Name)
|
||||
retry := 0
|
||||
recoverMode := false
|
||||
isFirstLoop := true
|
||||
|
@ -185,39 +185,39 @@ loop:
|
|||
pingTicker = tickDuration
|
||||
}
|
||||
|
||||
util.Log().Debug("从机节点 [%s] 发送Ping", node.Model.Name)
|
||||
util.Log().Debug("Slave node %q send ping.", node.Model.Name)
|
||||
res, err := node.Ping(node.getHeartbeatContent(isFirstLoop))
|
||||
isFirstLoop = false
|
||||
|
||||
if err != nil {
|
||||
util.Log().Debug("Ping从机节点 [%s] 时发生错误: %s", node.Model.Name, err)
|
||||
util.Log().Debug("Error while ping slave node %q: %s", node.Model.Name, err)
|
||||
retry++
|
||||
if retry >= model.GetIntSetting("slave_node_retry", 3) {
|
||||
util.Log().Debug("从机节点 [%s] Ping 重试已达到最大限制,将从机节点标记为不可用", node.Model.Name)
|
||||
util.Log().Debug("Retry threshold for pinging slave node %q exceeded, mark it as offline.", node.Model.Name)
|
||||
node.changeStatus(false)
|
||||
|
||||
if !recoverMode {
|
||||
// 启动恢复监控循环
|
||||
util.Log().Debug("从机节点 [%s] 进入恢复模式", node.Model.Name)
|
||||
util.Log().Debug("Slave node %q entered recovery mode.", node.Model.Name)
|
||||
pingTicker = recoverDuration
|
||||
recoverMode = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if recoverMode {
|
||||
util.Log().Debug("从机节点 [%s] 复活", node.Model.Name)
|
||||
util.Log().Debug("Slave node %q recovered.", node.Model.Name)
|
||||
pingTicker = tickDuration
|
||||
recoverMode = false
|
||||
isFirstLoop = true
|
||||
}
|
||||
|
||||
util.Log().Debug("从机节点 [%s] 状态: %s", node.Model.Name, res)
|
||||
util.Log().Debug("Status of slave node %q: %s", node.Model.Name, res)
|
||||
node.changeStatus(true)
|
||||
retry = 0
|
||||
}
|
||||
|
||||
case <-node.close:
|
||||
util.Log().Debug("从机节点 [%s] 收到关闭信号", node.Model.Name)
|
||||
util.Log().Debug("Slave node %q received shutdown signal.", node.Model.Name)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ func RemoteCallback(url string, body serializer.UploadCallback) error {
|
|||
Data: body,
|
||||
})
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeCallbackError, "无法编码回调正文", err)
|
||||
return serializer.NewError(serializer.CodeCallbackError, "Failed to encode callback content", err)
|
||||
}
|
||||
|
||||
resp := request.GeneralClient.Request(
|
||||
|
@ -433,13 +433,13 @@ func RemoteCallback(url string, body serializer.UploadCallback) error {
|
|||
)
|
||||
|
||||
if resp.Err != nil {
|
||||
return serializer.NewError(serializer.CodeCallbackError, "从机无法发起回调请求", resp.Err)
|
||||
return serializer.NewError(serializer.CodeCallbackError, "Slave cannot send callback request", resp.Err)
|
||||
}
|
||||
|
||||
// 解析回调服务端响应
|
||||
response, err := resp.DecodeResponse()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("从机无法解析主机返回的响应 (StatusCode=%d)", resp.Response.StatusCode)
|
||||
msg := fmt.Sprintf("Slave cannot parse callback response from master (StatusCode=%d).", resp.Response.StatusCode)
|
||||
return serializer.NewError(serializer.CodeCallbackError, msg, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package conf
|
|||
import (
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/go-ini/ini"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// database 数据库
|
||||
|
@ -26,6 +26,7 @@ type system struct {
|
|||
Debug bool
|
||||
SessionSecret string
|
||||
HashIDSalt string
|
||||
GracePeriod int `validate:"gte=0"`
|
||||
}
|
||||
|
||||
type ssl struct {
|
||||
|
@ -87,13 +88,13 @@ func Init(path string) {
|
|||
}, defaultConf)
|
||||
f, err := util.CreatNestedFile(path)
|
||||
if err != nil {
|
||||
util.Log().Panic("无法创建配置文件, %s", err)
|
||||
util.Log().Panic("Failed to create config file: %s", err)
|
||||
}
|
||||
|
||||
// 写入配置文件
|
||||
_, err = f.WriteString(confContent)
|
||||
if err != nil {
|
||||
util.Log().Panic("无法写入配置文件, %s", err)
|
||||
util.Log().Panic("Failed to write config file: %s", err)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
|
@ -101,7 +102,7 @@ func Init(path string) {
|
|||
|
||||
cfg, err = ini.Load(path)
|
||||
if err != nil {
|
||||
util.Log().Panic("无法解析配置文件 '%s': %s", path, err)
|
||||
util.Log().Panic("Failed to parse config file %q: %s", path, err)
|
||||
}
|
||||
|
||||
sections := map[string]interface{}{
|
||||
|
@ -116,7 +117,7 @@ func Init(path string) {
|
|||
for sectionName, sectionStruct := range sections {
|
||||
err = mapSection(sectionName, sectionStruct)
|
||||
if err != nil {
|
||||
util.Log().Panic("配置文件 %s 分区解析失败: %s", sectionName, err)
|
||||
util.Log().Panic("Failed to parse config section %q: %s", sectionName, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ func garbageCollect() {
|
|||
collectCache(store)
|
||||
}
|
||||
|
||||
util.Log().Info("定时任务 [cron_garbage_collect] 执行完毕")
|
||||
util.Log().Info("Crontab job \"cron_garbage_collect\" complete.")
|
||||
}
|
||||
|
||||
func collectArchiveFile() {
|
||||
|
@ -36,23 +36,23 @@ func collectArchiveFile() {
|
|||
if err == nil && !info.IsDir() &&
|
||||
strings.HasPrefix(filepath.Base(path), "archive_") &&
|
||||
time.Now().Sub(info.ModTime()).Seconds() > float64(expires) {
|
||||
util.Log().Debug("删除过期打包下载临时文件 [%s]", path)
|
||||
util.Log().Debug("Delete expired batch download temp file %q.", path)
|
||||
// 删除符合条件的文件
|
||||
if err := os.Remove(path); err != nil {
|
||||
util.Log().Debug("临时文件 [%s] 删除失败 , %s", path, err)
|
||||
util.Log().Debug("Failed to delete temp file %q: %s", path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
util.Log().Debug("[定时任务] 无法列取临时打包目录")
|
||||
util.Log().Debug("Crontab job cannot list temp batch download folder: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func collectCache(store *cache.MemoStore) {
|
||||
util.Log().Debug("清理内存缓存")
|
||||
util.Log().Debug("Cleanup memory cache.")
|
||||
store.GarbageCollect()
|
||||
}
|
||||
|
||||
|
@ -78,22 +78,22 @@ func uploadSessionCollect() {
|
|||
for uid, filesIDs := range userToFiles {
|
||||
user, err := model.GetUserByID(uid)
|
||||
if err != nil {
|
||||
util.Log().Warning("上传会话所属用户不存在, %s", err)
|
||||
util.Log().Warning("Owner of the upload session cannot be found: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fs, err := filesystem.NewFileSystem(&user)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法初始化文件系统, %s", err)
|
||||
util.Log().Warning("Failed to initialize filesystem: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = fs.Delete(context.Background(), []uint{}, filesIDs, false); err != nil {
|
||||
util.Log().Warning("无法删除上传会话, %s", err)
|
||||
util.Log().Warning("Failed to delete upload session: %s", err)
|
||||
}
|
||||
|
||||
fs.Recycle()
|
||||
}
|
||||
|
||||
util.Log().Info("定时任务 [cron_recycle_upload_session] 执行完毕")
|
||||
util.Log().Info("Crontab job \"cron_recycle_upload_session\" complete.")
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func Reload() {
|
|||
|
||||
// Init 初始化定时任务
|
||||
func Init() {
|
||||
util.Log().Info("初始化定时任务...")
|
||||
util.Log().Info("Initialize crontab jobs...")
|
||||
// 读取cron日程设置
|
||||
options := model.GetSettingByNames(
|
||||
"cron_garbage_collect",
|
||||
|
@ -34,12 +34,12 @@ func Init() {
|
|||
case "cron_recycle_upload_session":
|
||||
handler = uploadSessionCollect
|
||||
default:
|
||||
util.Log().Warning("未知定时任务类型 [%s],跳过", k)
|
||||
util.Log().Warning("Unknown crontab job type %q, skipping...", k)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := Cron.AddFunc(v, handler); err != nil {
|
||||
util.Log().Warning("无法启动定时任务 [%s] , %s", k, err)
|
||||
util.Log().Warning("Failed to start crontab job %q: %s", k, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ var Lock sync.RWMutex
|
|||
|
||||
// Init 初始化
|
||||
func Init() {
|
||||
util.Log().Debug("邮件队列初始化")
|
||||
util.Log().Debug("Initializing email sending queue...")
|
||||
Lock.Lock()
|
||||
defer Lock.Unlock()
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ type Driver interface {
|
|||
|
||||
var (
|
||||
// ErrChanNotOpen 邮件队列未开启
|
||||
ErrChanNotOpen = errors.New("邮件队列未开启")
|
||||
ErrChanNotOpen = errors.New("email queue is not started")
|
||||
// ErrNoActiveDriver 无可用邮件发送服务
|
||||
ErrNoActiveDriver = errors.New("无可用邮件发送服务")
|
||||
ErrNoActiveDriver = errors.New("no avaliable email provider")
|
||||
)
|
||||
|
||||
// Send 发送邮件
|
||||
|
|
|
@ -68,7 +68,7 @@ func (client *SMTP) Init() {
|
|||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
client.chOpen = false
|
||||
util.Log().Error("邮件发送队列出现异常, %s ,10 秒后重置", err)
|
||||
util.Log().Error("Exception while sending email: %s, queue will be reset in 10 seconds.", err)
|
||||
time.Sleep(time.Duration(10) * time.Second)
|
||||
client.Init()
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func (client *SMTP) Init() {
|
|||
select {
|
||||
case m, ok := <-client.ch:
|
||||
if !ok {
|
||||
util.Log().Debug("邮件队列关闭")
|
||||
util.Log().Debug("Email queue closing...")
|
||||
client.chOpen = false
|
||||
return
|
||||
}
|
||||
|
@ -102,15 +102,15 @@ func (client *SMTP) Init() {
|
|||
open = true
|
||||
}
|
||||
if err := mail.Send(s, m); err != nil {
|
||||
util.Log().Warning("邮件发送失败, %s", err)
|
||||
util.Log().Warning("Failed to send email: %s", err)
|
||||
} else {
|
||||
util.Log().Debug("邮件已发送")
|
||||
util.Log().Debug("Email sent.")
|
||||
}
|
||||
// 长时间没有新邮件,则关闭SMTP连接
|
||||
case <-time.After(time.Duration(client.Config.Keepalive) * time.Second):
|
||||
if open {
|
||||
if err := s.Close(); err != nil {
|
||||
util.Log().Warning("无法关闭 SMTP 连接 %s", err)
|
||||
util.Log().Warning("Failed to close SMTP connection: %s", err)
|
||||
}
|
||||
open = false
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ func (fs *FileSystem) doCompress(ctx context.Context, file *model.File, folder *
|
|||
fs.Policy = file.GetPolicy()
|
||||
err := fs.DispatchHandler()
|
||||
if err != nil {
|
||||
util.Log().Warning("无法压缩文件%s,%s", file.Name, err)
|
||||
util.Log().Warning("Failed to compress file %q: %s", file.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ func (fs *FileSystem) doCompress(ctx context.Context, file *model.File, folder *
|
|||
file.SourceName,
|
||||
)
|
||||
if err != nil {
|
||||
util.Log().Debug("Open%s,%s", file.Name, err)
|
||||
util.Log().Debug("Failed to open %q: %s", file.Name, err)
|
||||
return
|
||||
}
|
||||
if closer, ok := fileToZip.(io.Closer); ok {
|
||||
|
@ -176,7 +176,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
// 结束时删除临时压缩文件
|
||||
if tempZipFilePath != "" {
|
||||
if err := os.Remove(tempZipFilePath); err != nil {
|
||||
util.Log().Warning("无法删除临时压缩文件 %s , %s", tempZipFilePath, err)
|
||||
util.Log().Warning("Failed to delete temp archive file %q: %s", tempZipFilePath, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -197,7 +197,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
|
||||
zipFile, err := util.CreatNestedFile(tempZipFilePath)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法创建临时压缩文件 %s , %s", tempZipFilePath, err)
|
||||
util.Log().Warning("Failed to create temp archive file %q: %s", tempZipFilePath, err)
|
||||
tempZipFilePath = ""
|
||||
return err
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
// 下载前先判断是否是可解压的格式
|
||||
format, readStream, err := archiver.Identify(fs.FileTarget[0].SourceName, fileStream)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法识别文件格式 %s , %s", fs.FileTarget[0].SourceName, err)
|
||||
util.Log().Warning("Failed to detect compressed format of file %q: %s", fs.FileTarget[0].SourceName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
if isZip {
|
||||
_, err = io.Copy(zipFile, readStream)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法写入临时压缩文件 %s , %s", tempZipFilePath, err)
|
||||
util.Log().Warning("Failed to write temp archive file %q: %s", tempZipFilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
wg.Done()
|
||||
}
|
||||
if err := recover(); err != nil {
|
||||
util.Log().Warning("上传压缩包内文件时出错")
|
||||
util.Log().Warning("Error while uploading files inside of archive file.")
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
@ -274,7 +274,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
}, true)
|
||||
fileStream.Close()
|
||||
if err != nil {
|
||||
util.Log().Debug("无法上传压缩包内的文件%s , %s , 跳过", rawPath, err)
|
||||
util.Log().Debug("Failed to upload file %q in archive file: %s, skipping...", rawPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,7 +297,7 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst, encoding string)
|
|||
// 上传文件
|
||||
fileStream, err := f.Open()
|
||||
if err != nil {
|
||||
util.Log().Warning("无法打开压缩包内文件%s , %s , 跳过", rawPath, err)
|
||||
util.Log().Warning("Failed to open file %q in archive file: %s, skipping...", rawPath, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
|
|||
return failed, nil
|
||||
}
|
||||
|
||||
return failed, errors.New("删除失败")
|
||||
return failed, errors.New("delete failed")
|
||||
}
|
||||
|
||||
// Thumb 获取文件缩略图
|
||||
|
|
|
@ -43,7 +43,7 @@ func (handler Driver) List(ctx context.Context, path string, recursive bool) ([]
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
util.Log().Warning("无法遍历目录 %s, %s", path, err)
|
||||
util.Log().Warning("Failed to walk folder %q: %s", path, err)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ func (handler Driver) Get(ctx context.Context, path string) (response.RSCloser,
|
|||
// 打开文件
|
||||
file, err := os.Open(util.RelativePath(path))
|
||||
if err != nil {
|
||||
util.Log().Debug("无法打开文件:%s", err)
|
||||
util.Log().Debug("Failed to open file: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -94,8 +94,8 @@ func (handler Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
|
|||
// 如果非 Overwrite,则检查是否有重名冲突
|
||||
if fileInfo.Mode&fsctx.Overwrite != fsctx.Overwrite {
|
||||
if util.Exists(dst) {
|
||||
util.Log().Warning("物理同名文件已存在或不可用: %s", dst)
|
||||
return errors.New("物理同名文件已存在或不可用")
|
||||
util.Log().Warning("File with the same name existed or unavailable: %s", dst)
|
||||
return errors.New("file with the same name existed or unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ func (handler Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
|
|||
if !util.Exists(basePath) {
|
||||
err := os.MkdirAll(basePath, Perm)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法创建目录,%s", err)
|
||||
util.Log().Warning("Failed to create directory: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ func (handler Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
|
|||
|
||||
out, err = os.OpenFile(dst, openMode, Perm)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法打开或创建文件,%s", err)
|
||||
util.Log().Warning("Failed to open or create file: %s", err)
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
@ -131,22 +131,22 @@ func (handler Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
|
|||
if fileInfo.Mode&fsctx.Append == fsctx.Append {
|
||||
stat, err := out.Stat()
|
||||
if err != nil {
|
||||
util.Log().Warning("无法读取文件信息,%s", err)
|
||||
util.Log().Warning("Failed to read file info: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if uint64(stat.Size()) < fileInfo.AppendStart {
|
||||
return errors.New("未上传完成的文件分片与预期大小不一致")
|
||||
return errors.New("size of unfinished uploaded chunks is not as expected")
|
||||
} else if uint64(stat.Size()) > fileInfo.AppendStart {
|
||||
out.Close()
|
||||
if err := handler.Truncate(ctx, dst, fileInfo.AppendStart); err != nil {
|
||||
return fmt.Errorf("覆盖分片时发生错误: %w", err)
|
||||
return fmt.Errorf("failed to overwrite chunk: %w", err)
|
||||
}
|
||||
|
||||
out, err = os.OpenFile(dst, openMode, Perm)
|
||||
defer out.Close()
|
||||
if err != nil {
|
||||
util.Log().Warning("无法打开或创建文件,%s", err)
|
||||
util.Log().Warning("Failed to create or open file: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -158,10 +158,10 @@ func (handler Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
|
|||
}
|
||||
|
||||
func (handler Driver) Truncate(ctx context.Context, src string, size uint64) error {
|
||||
util.Log().Warning("截断文件 [%s] 至 [%d]", src, size)
|
||||
util.Log().Warning("Truncate file %q to [%d].", src, size)
|
||||
out, err := os.OpenFile(src, os.O_WRONLY, Perm)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法打开文件,%s", err)
|
||||
util.Log().Warning("Failed to open file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -180,7 +180,7 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
|
|||
if util.Exists(filePath) {
|
||||
err := os.Remove(filePath)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除文件,%s", err)
|
||||
util.Log().Warning("Failed to delete file: %s", err)
|
||||
retErr = err
|
||||
deleteFailed = append(deleteFailed, value)
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ func (handler Driver) Source(
|
|||
) (string, error) {
|
||||
file, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||
if !ok {
|
||||
return "", errors.New("无法获取文件记录上下文")
|
||||
return "", errors.New("failed to read file model context")
|
||||
}
|
||||
|
||||
// 是否启用了CDN
|
||||
|
@ -238,7 +238,7 @@ func (handler Driver) Source(
|
|||
downloadSessionID := util.RandStringRunes(16)
|
||||
err = cache.Set("download_"+downloadSessionID, file, int(ttl))
|
||||
if err != nil {
|
||||
return "", serializer.NewError(serializer.CodeCacheOperation, "无法创建下载会话", err)
|
||||
return "", serializer.NewError(serializer.CodeCacheOperation, "Failed to create download session", err)
|
||||
}
|
||||
|
||||
// 签名生成文件记录
|
||||
|
@ -257,7 +257,7 @@ func (handler Driver) Source(
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
return "", serializer.NewError(serializer.CodeEncryptError, "无法对URL进行签名", err)
|
||||
return "", serializer.NewError(serializer.CodeEncryptError, "Failed to sign url", err)
|
||||
}
|
||||
|
||||
finalURL := baseURL.ResolveReference(signedURI).String()
|
||||
|
|
|
@ -95,7 +95,7 @@ func (client *Client) ListChildren(ctx context.Context, path string) ([]FileInfo
|
|||
}
|
||||
if retried < ListRetry {
|
||||
retried++
|
||||
util.Log().Debug("路径[%s]列取请求失败[%s],5秒钟后重试", path, err)
|
||||
util.Log().Debug("Failed to list path %q: %s, will retry in 5 seconds.", path, err)
|
||||
time.Sleep(time.Duration(5) * time.Second)
|
||||
return client.ListChildren(context.WithValue(ctx, fsctx.RetryCtx, retried), path)
|
||||
}
|
||||
|
@ -445,7 +445,7 @@ func (client *Client) GetThumbURL(ctx context.Context, dst string, w, h uint) (s
|
|||
}
|
||||
}
|
||||
|
||||
return "", errors.New("无法生成缩略图")
|
||||
return "", errors.New("failed to generate thumb")
|
||||
}
|
||||
|
||||
// MonitorUpload 监控客户端分片上传进度
|
||||
|
@ -460,39 +460,39 @@ func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size ui
|
|||
for {
|
||||
select {
|
||||
case <-callbackChan:
|
||||
util.Log().Debug("客户端完成回调")
|
||||
util.Log().Debug("Client finished OneDrive callback.")
|
||||
return
|
||||
case <-time.After(time.Duration(ttl) * time.Second):
|
||||
// 上传会话到期,仍未完成上传,创建占位符
|
||||
client.DeleteUploadSession(context.Background(), uploadURL)
|
||||
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0, WithConflictBehavior("replace"))
|
||||
if err != nil {
|
||||
util.Log().Debug("无法创建占位文件,%s", err)
|
||||
util.Log().Debug("Failed to create placeholder file: %s", err)
|
||||
}
|
||||
return
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
util.Log().Debug("检查上传情况")
|
||||
util.Log().Debug("Checking OneDrive upload status.")
|
||||
status, err := client.GetUploadSessionStatus(context.Background(), uploadURL)
|
||||
|
||||
if err != nil {
|
||||
if resErr, ok := err.(*RespError); ok {
|
||||
if resErr.APIError.Code == "itemNotFound" {
|
||||
util.Log().Debug("上传会话已完成,稍后检查回调")
|
||||
util.Log().Debug("Upload completed, will check upload callback later.")
|
||||
select {
|
||||
case <-time.After(time.Duration(interval) * time.Second):
|
||||
util.Log().Warning("未发送回调,删除文件")
|
||||
util.Log().Warning("No callback is made, file will be deleted.")
|
||||
cache.Deletes([]string{callbackKey}, "callback_")
|
||||
_, err = client.Delete(context.Background(), []string{path})
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除未回调的文件,%s", err)
|
||||
util.Log().Warning("Failed to delete file without callback: %s", err)
|
||||
}
|
||||
case <-callbackChan:
|
||||
util.Log().Debug("客户端完成回调")
|
||||
util.Log().Debug("Client finished callback.")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
util.Log().Debug("无法获取上传会话状态,继续下一轮,%s", err.Error())
|
||||
util.Log().Debug("Failed to get upload session status: %s, continue next iteration.", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -509,7 +509,7 @@ func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size ui
|
|||
}
|
||||
uploadFullSize, _ := strconv.ParseUint(sizeRange[1], 10, 64)
|
||||
if (sizeRange[0] == "0" && sizeRange[1] == "") || uploadFullSize+1 != size {
|
||||
util.Log().Debug("未开始上传或文件大小不一致,取消上传会话")
|
||||
util.Log().Debug("Upload has not started, or uploaded file size not match, canceling upload session...")
|
||||
// 取消上传会话,实测OneDrive取消上传会话后,客户端还是可以上传,
|
||||
// 所以上传一个空文件占位,阻止客户端上传
|
||||
client.DeleteUploadSession(context.Background(), uploadURL)
|
||||
|
@ -577,7 +577,7 @@ func (client *Client) request(ctx context.Context, method string, url string, bo
|
|||
if res.Response.StatusCode < 200 || res.Response.StatusCode >= 300 {
|
||||
decodeErr = json.Unmarshal([]byte(respBody), &errResp)
|
||||
if decodeErr != nil {
|
||||
util.Log().Debug("Onedrive返回未知响应[%s]", respBody)
|
||||
util.Log().Debug("Onedrive returns unknown response: %s", respBody)
|
||||
return "", sysError(decodeErr)
|
||||
}
|
||||
return "", &errResp
|
||||
|
|
|
@ -10,13 +10,13 @@ import (
|
|||
|
||||
var (
|
||||
// ErrAuthEndpoint 无法解析授权端点地址
|
||||
ErrAuthEndpoint = errors.New("无法解析授权端点地址")
|
||||
ErrAuthEndpoint = errors.New("failed to parse endpoint url")
|
||||
// ErrInvalidRefreshToken 上传策略无有效的RefreshToken
|
||||
ErrInvalidRefreshToken = errors.New("上传策略无有效的RefreshToken")
|
||||
ErrInvalidRefreshToken = errors.New("no valid refresh token in this policy")
|
||||
// ErrDeleteFile 无法删除文件
|
||||
ErrDeleteFile = errors.New("无法删除文件")
|
||||
ErrDeleteFile = errors.New("cannot delete file")
|
||||
// ErrClientCanceled 客户端取消操作
|
||||
ErrClientCanceled = errors.New("客户端取消操作")
|
||||
ErrClientCanceled = errors.New("client canceled")
|
||||
)
|
||||
|
||||
// Client OneDrive客户端
|
||||
|
|
|
@ -143,7 +143,7 @@ func (handler Driver) Thumb(ctx context.Context, path string) (*response.Content
|
|||
ok = false
|
||||
)
|
||||
if thumbSize, ok = ctx.Value(fsctx.ThumbSizeCtx).([2]uint); !ok {
|
||||
return nil, errors.New("无法获取缩略图尺寸设置")
|
||||
return nil, errors.New("failed to get thumbnail size")
|
||||
}
|
||||
|
||||
res, err := handler.Client.GetThumbURL(ctx, path, thumbSize[0], thumbSize[1])
|
||||
|
|
|
@ -152,7 +152,7 @@ func (client *Client) UpdateCredential(ctx context.Context, isSlave bool) error
|
|||
// 获取新的凭证
|
||||
if client.Credential == nil || client.Credential.RefreshToken == "" {
|
||||
// 无有效的RefreshToken
|
||||
util.Log().Error("上传策略[%s]凭证刷新失败,请重新授权OneDrive账号", client.Policy.Name)
|
||||
util.Log().Error("Failed to refresh credential for policy %q, please login your Microsoft account again.", client.Policy.Name)
|
||||
return ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ func GetPublicKey(r *http.Request) ([]byte, error) {
|
|||
// 确保这个 public key 是由 OSS 颁发的
|
||||
if !strings.HasPrefix(string(pubURL), "http://gosspublic.alicdn.com/") &&
|
||||
!strings.HasPrefix(string(pubURL), "https://gosspublic.alicdn.com/") {
|
||||
return pubKey, errors.New("公钥URL无效")
|
||||
return pubKey, errors.New("public key url invalid")
|
||||
}
|
||||
|
||||
// 获取公钥
|
||||
|
|
|
@ -91,7 +91,7 @@ func (handler *Driver) CORS() error {
|
|||
// InitOSSClient 初始化OSS鉴权客户端
|
||||
func (handler *Driver) InitOSSClient(forceUsePublicEndpoint bool) error {
|
||||
if handler.Policy == nil {
|
||||
return errors.New("存储策略为空")
|
||||
return errors.New("empty policy")
|
||||
}
|
||||
|
||||
// 决定是否使用内网 Endpoint
|
||||
|
@ -286,7 +286,7 @@ func (handler *Driver) Delete(ctx context.Context, files []string) ([]string, er
|
|||
// 统计未删除的文件
|
||||
failed := util.SliceDifference(files, delRes.DeletedObjects)
|
||||
if len(failed) > 0 {
|
||||
return failed, errors.New("删除失败")
|
||||
return failed, errors.New("failed to delete")
|
||||
}
|
||||
|
||||
return []string{}, nil
|
||||
|
@ -304,7 +304,7 @@ func (handler *Driver) Thumb(ctx context.Context, path string) (*response.Conten
|
|||
ok = false
|
||||
)
|
||||
if thumbSize, ok = ctx.Value(fsctx.ThumbSizeCtx).([2]uint); !ok {
|
||||
return nil, errors.New("无法获取缩略图尺寸设置")
|
||||
return nil, errors.New("failed to get thumbnail size")
|
||||
}
|
||||
|
||||
thumbParam := fmt.Sprintf("image/resize,m_lfit,h_%d,w_%d", thumbSize[1], thumbSize[0])
|
||||
|
|
|
@ -197,7 +197,7 @@ func (handler *Driver) Delete(ctx context.Context, files []string) ([]string, er
|
|||
return failedResp.Files, errors.New(reqResp.Error)
|
||||
}
|
||||
}
|
||||
return files, errors.New("未知的返回结果格式")
|
||||
return files, errors.New("unknown format of returned response")
|
||||
}
|
||||
|
||||
return []string{}, nil
|
||||
|
@ -265,7 +265,7 @@ func (handler *Driver) Source(
|
|||
)
|
||||
|
||||
if err != nil {
|
||||
return "", serializer.NewError(serializer.CodeEncryptError, "无法对URL进行签名", err)
|
||||
return "", serializer.NewError(serializer.CodeEncryptError, "Failed to sign URL", err)
|
||||
}
|
||||
|
||||
finalURL := serverURL.ResolveReference(signedURI).String()
|
||||
|
|
|
@ -62,7 +62,7 @@ func NewDriver(policy *model.Policy) (*Driver, error) {
|
|||
// InitS3Client 初始化S3会话
|
||||
func (handler *Driver) InitS3Client() error {
|
||||
if handler.Policy == nil {
|
||||
return errors.New("存储策略为空")
|
||||
return errors.New("empty policy")
|
||||
}
|
||||
|
||||
if handler.svc == nil {
|
||||
|
|
|
@ -5,6 +5,9 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver"
|
||||
|
@ -13,8 +16,6 @@ import (
|
|||
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Driver 影子存储策略,将上传任务指派给从机节点处理,并等待从机通知上传结果
|
||||
|
@ -118,6 +119,6 @@ func (d *Driver) List(ctx context.Context, path string, recursive bool) ([]respo
|
|||
}
|
||||
|
||||
// 取消上传凭证
|
||||
func (handler Driver) CancelToken(ctx context.Context, uploadSession *serializer.UploadSession) error {
|
||||
func (d *Driver) CancelToken(ctx context.Context, uploadSession *serializer.UploadSession) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ func (handler Driver) Thumb(ctx context.Context, path string) (*response.Content
|
|||
ok = false
|
||||
)
|
||||
if thumbSize, ok = ctx.Value(fsctx.ThumbSizeCtx).([2]uint); !ok {
|
||||
return nil, errors.New("无法获取缩略图尺寸设置")
|
||||
return nil, errors.New("failed to get thumbnail size")
|
||||
}
|
||||
|
||||
thumbParam := fmt.Sprintf("!/fwfh/%dx%d", thumbSize[0], thumbSize[1])
|
||||
|
|
|
@ -7,20 +7,20 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrUnknownPolicyType = errors.New("未知存储策略类型")
|
||||
ErrFileSizeTooBig = errors.New("单个文件尺寸太大")
|
||||
ErrFileExtensionNotAllowed = errors.New("不允许上传此类型的文件")
|
||||
ErrInsufficientCapacity = errors.New("容量空间不足")
|
||||
ErrIllegalObjectName = errors.New("目标名称非法")
|
||||
ErrClientCanceled = errors.New("客户端取消操作")
|
||||
ErrRootProtected = errors.New("无法对根目录进行操作")
|
||||
ErrInsertFileRecord = serializer.NewError(serializer.CodeDBError, "无法插入文件记录", nil)
|
||||
ErrFileExisted = serializer.NewError(serializer.CodeObjectExist, "同名文件或目录已存在", nil)
|
||||
ErrFileUploadSessionExisted = serializer.NewError(serializer.CodeObjectExist, "当前目录下已经有同名文件正在上传中,请尝试清空上传会话", nil)
|
||||
ErrFolderExisted = serializer.NewError(serializer.CodeObjectExist, "同名目录已存在", nil)
|
||||
ErrPathNotExist = serializer.NewError(404, "路径不存在", nil)
|
||||
ErrObjectNotExist = serializer.NewError(404, "文件不存在", nil)
|
||||
ErrIO = serializer.NewError(serializer.CodeIOFailed, "无法读取文件数据", nil)
|
||||
ErrDBListObjects = serializer.NewError(serializer.CodeDBError, "无法列取对象记录", nil)
|
||||
ErrDBDeleteObjects = serializer.NewError(serializer.CodeDBError, "无法删除对象记录", nil)
|
||||
ErrUnknownPolicyType = serializer.NewError(serializer.CodeInternalSetting, "Unknown policy type", nil)
|
||||
ErrFileSizeTooBig = serializer.NewError(serializer.CodeFileTooLarge, "", nil)
|
||||
ErrFileExtensionNotAllowed = serializer.NewError(serializer.CodeFileTypeNotAllowed, "", nil)
|
||||
ErrInsufficientCapacity = serializer.NewError(serializer.CodeInsufficientCapacity, "", nil)
|
||||
ErrIllegalObjectName = serializer.NewError(serializer.CodeIllegalObjectName, "", nil)
|
||||
ErrClientCanceled = errors.New("Client canceled operation")
|
||||
ErrRootProtected = serializer.NewError(serializer.CodeRootProtected, "", nil)
|
||||
ErrInsertFileRecord = serializer.NewError(serializer.CodeDBError, "Failed to create file record", nil)
|
||||
ErrFileExisted = serializer.NewError(serializer.CodeObjectExist, "", nil)
|
||||
ErrFileUploadSessionExisted = serializer.NewError(serializer.CodeConflictUploadOngoing, "", nil)
|
||||
ErrPathNotExist = serializer.NewError(serializer.CodeParentNotExist, "", nil)
|
||||
ErrObjectNotExist = serializer.NewError(serializer.CodeParentNotExist, "", nil)
|
||||
ErrIO = serializer.NewError(serializer.CodeIOFailed, "Failed to read file data", nil)
|
||||
ErrDBListObjects = serializer.NewError(serializer.CodeDBError, "Failed to list object records", nil)
|
||||
ErrDBDeleteObjects = serializer.NewError(serializer.CodeDBError, "Failed to delete object records", nil)
|
||||
ErrOneObjectOnly = serializer.ParamErr("You can only copy one object at the same time", nil)
|
||||
)
|
||||
|
|
|
@ -72,7 +72,7 @@ func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder, file fs
|
|||
|
||||
if err != nil {
|
||||
if err := fs.Trigger(ctx, "AfterValidateFailed", file); err != nil {
|
||||
util.Log().Debug("AfterValidateFailed 钩子执行失败,%s", err)
|
||||
util.Log().Debug("AfterValidateFailed hook execution failed: %s", err)
|
||||
}
|
||||
return nil, ErrFileExisted.WithError(err)
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ func (fs *FileSystem) deleteGroupedFile(ctx context.Context, files map[uint][]*m
|
|||
// 取消上传会话
|
||||
for _, upSession := range uploadSessions {
|
||||
if err := fs.Handler.CancelToken(ctx, upSession); err != nil {
|
||||
util.Log().Warning("无法取消 [%s] 的上传会话: %s", upSession.Name, err)
|
||||
util.Log().Warning("Failed to cancel upload session for %q: %s", upSession.Name, err)
|
||||
}
|
||||
|
||||
cache.Deletes([]string{upSession.Key}, UploadSessionCachePrefix)
|
||||
|
@ -270,14 +270,14 @@ func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error
|
|||
if !fs.Policy.IsOriginLinkEnable {
|
||||
return "", serializer.NewError(
|
||||
serializer.CodePolicyNotAllowed,
|
||||
"当前存储策略无法获得外链",
|
||||
"This policy is not enabled for getting source link",
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
source, err := fs.SignURL(ctx, &fs.FileTarget[0], 0, false)
|
||||
if err != nil {
|
||||
return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err)
|
||||
return "", serializer.NewError(serializer.CodeNotSet, "Failed to get source link", err)
|
||||
}
|
||||
|
||||
return source, nil
|
||||
|
@ -298,7 +298,7 @@ func (fs *FileSystem) SignURL(ctx context.Context, file *model.File, ttl int64,
|
|||
siteURL := model.GetSiteURL()
|
||||
source, err := fs.Handler.Source(ctx, fs.FileTarget[0].SourceName, *siteURL, ttl, isDownload, fs.User.Group.SpeedLimit)
|
||||
if err != nil {
|
||||
return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err)
|
||||
return "", serializer.NewError(serializer.CodeNotSet, "Failed to get source link", err)
|
||||
}
|
||||
|
||||
return source, nil
|
||||
|
|
|
@ -203,7 +203,7 @@ func NewFileSystemFromCallback(c *gin.Context) (*FileSystem, error) {
|
|||
// 获取回调会话
|
||||
callbackSessionRaw, ok := c.Get(UploadSessionCtx)
|
||||
if !ok {
|
||||
return nil, errors.New("找不到回调会话")
|
||||
return nil, errors.New("upload session not exist")
|
||||
}
|
||||
callbackSession := callbackSessionRaw.(*serializer.UploadSession)
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ func (fs *FileSystem) Trigger(ctx context.Context, name string, file fsctx.FileH
|
|||
for _, hook := range hooks {
|
||||
err := hook(ctx, fs, file)
|
||||
if err != nil {
|
||||
util.Log().Warning("钩子执行失败:%s", err)
|
||||
util.Log().Warning("Failed to execute hook:%s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ func HookDeleteTempFile(ctx context.Context, fs *FileSystem, file fsctx.FileHead
|
|||
// 删除临时文件
|
||||
_, err := fs.Handler.Delete(ctx, []string{file.Info().SavePath})
|
||||
if err != nil {
|
||||
util.Log().Warning("无法清理上传临时文件,%s", err)
|
||||
util.Log().Warning("Failed to clean-up temp files: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -71,16 +71,16 @@ func getThumbWorker() *Pool {
|
|||
thumbPool = &Pool{
|
||||
worker: make(chan int, maxWorker),
|
||||
}
|
||||
util.Log().Debug("初始化Thumb任务队列,WorkerNum = %d", maxWorker)
|
||||
util.Log().Debug("Initialize thumbnails task queue with: WorkerNum = %d", maxWorker)
|
||||
})
|
||||
return thumbPool
|
||||
}
|
||||
func (pool *Pool) addWorker() {
|
||||
pool.worker <- 1
|
||||
util.Log().Debug("Thumb任务队列,addWorker")
|
||||
util.Log().Debug("Worker added to thumbnails task queue.")
|
||||
}
|
||||
func (pool *Pool) releaseWorker() {
|
||||
util.Log().Debug("Thumb任务队列,releaseWorker")
|
||||
util.Log().Debug("Worker released from thumbnails task queue.")
|
||||
<-pool.worker
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
|||
|
||||
image, err := thumb.NewThumbFromFile(source, file.Name)
|
||||
if err != nil {
|
||||
util.Log().Warning("生成缩略图时无法解析 [%s] 图像数据:%s", file.SourceName, err)
|
||||
util.Log().Warning("Cannot generate thumb because of failed to parse image %q: %s", file.SourceName, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
util.Log().Warning("无法保存缩略图:%s", err)
|
||||
util.Log().Warning("Failed to save thumb: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ func (fs *FileSystem) Copy(ctx context.Context, dirs, files []uint, src, dst str
|
|||
if len(dirs) > 0 {
|
||||
subFileSizes, err := srcFolder.CopyFolderTo(dirs[0], dstFolder)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
return ErrObjectNotExist.WithError(err)
|
||||
}
|
||||
newUsedStorage += subFileSizes
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func (fs *FileSystem) Copy(ctx context.Context, dirs, files []uint, src, dst str
|
|||
if len(files) > 0 {
|
||||
subFileSizes, err := srcFolder.MoveOrCopyFileTo(files, dstFolder, true)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
return ErrObjectNotExist.WithError(err)
|
||||
}
|
||||
newUsedStorage += subFileSizes
|
||||
}
|
||||
|
@ -106,13 +106,13 @@ func (fs *FileSystem) Move(ctx context.Context, dirs, files []uint, src, dst str
|
|||
// 处理目录及子文件移动
|
||||
err := srcFolder.MoveFolderTo(dirs, dstFolder)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
return ErrFileExisted.WithError(err)
|
||||
}
|
||||
|
||||
// 处理文件移动
|
||||
_, err = srcFolder.MoveOrCopyFileTo(files, dstFolder, false)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
return ErrFileExisted.WithError(err)
|
||||
}
|
||||
|
||||
// 移动文件
|
||||
|
|
|
@ -69,7 +69,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file *fsctx.FileStream) (err e
|
|||
followUpErr := fs.Trigger(ctx, "AfterValidateFailed", file)
|
||||
// 失败后再失败...
|
||||
if followUpErr != nil {
|
||||
util.Log().Debug("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
||||
util.Log().Debug("AfterValidateFailed hook execution failed: %s", followUpErr)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -113,13 +113,13 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file fsctx.
|
|||
// 客户端正常关闭,不执行操作
|
||||
default:
|
||||
// 客户端取消上传,删除临时文件
|
||||
util.Log().Debug("客户端取消上传")
|
||||
util.Log().Debug("Client canceled upload.")
|
||||
if fs.Hooks["AfterUploadCanceled"] == nil {
|
||||
return
|
||||
}
|
||||
err := fs.Trigger(ctx, "AfterUploadCanceled", file)
|
||||
if err != nil {
|
||||
util.Log().Debug("执行 AfterUploadCanceled 钩子出错,%s", err)
|
||||
util.Log().Debug("AfterUploadCanceled hook execution failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,11 +73,11 @@ const (
|
|||
// CodeMasterNotFound 主机节点未注册
|
||||
CodeMasterNotFound = 40009
|
||||
// CodeUploadSessionExpired 上传会话已过期
|
||||
CodeUploadSessionExpired = 400011
|
||||
CodeUploadSessionExpired = 40011
|
||||
// CodeInvalidChunkIndex 无效的分片序号
|
||||
CodeInvalidChunkIndex = 400012
|
||||
CodeInvalidChunkIndex = 40012
|
||||
// CodeInvalidContentLength 无效的正文长度
|
||||
CodeInvalidContentLength = 400013
|
||||
CodeInvalidContentLength = 40013
|
||||
// CodeBatchSourceSize 超出批量获取外链限制
|
||||
CodeBatchSourceSize = 40014
|
||||
// CodeBatchAria2Size 超出最大 Aria2 任务数量限制
|
||||
|
@ -118,6 +118,80 @@ const (
|
|||
CodeEmailSent = 40033
|
||||
// CodeUserCannotActivate 用户无法激活
|
||||
CodeUserCannotActivate = 40034
|
||||
// 存储策略不存在
|
||||
CodePolicyNotExist = 40035
|
||||
// 无法删除默认存储策略
|
||||
CodeDeleteDefaultPolicy = 40036
|
||||
// 存储策略下还有文件
|
||||
CodePolicyUsedByFiles = 40037
|
||||
// 存储策略绑定了用户组
|
||||
CodePolicyUsedByGroups = 40038
|
||||
// 用户组不存在
|
||||
CodeGroupNotFound = 40039
|
||||
// 对系统用户组执行非法操作
|
||||
CodeInvalidActionOnSystemGroup = 40040
|
||||
// 用户组正在被使用
|
||||
CodeGroupUsedByUser = 40041
|
||||
// 为初始用户更改用户组
|
||||
CodeChangeGroupForDefaultUser = 40042
|
||||
// 对系统用户执行非法操作
|
||||
CodeInvalidActionOnDefaultUser = 40043
|
||||
// 文件不存在
|
||||
CodeFileNotFound = 40044
|
||||
// 列取文件失败
|
||||
CodeListFilesError = 40045
|
||||
// 对系统节点进行非法操作
|
||||
CodeInvalidActionOnSystemNode = 40046
|
||||
// 创建文件系统出错
|
||||
CodeCreateFSError = 40047
|
||||
// 创建任务出错
|
||||
CodeCreateTaskError = 40048
|
||||
// 文件尺寸太大
|
||||
CodeFileTooLarge = 40049
|
||||
// 文件类型不允许
|
||||
CodeFileTypeNotAllowed = 40050
|
||||
// 用户容量不足
|
||||
CodeInsufficientCapacity = 40051
|
||||
// 对象名非法
|
||||
CodeIllegalObjectName = 40052
|
||||
// 不支持对根目录执行此操作
|
||||
CodeRootProtected = 40053
|
||||
// 当前目录下已经有同名文件正在上传中
|
||||
CodeConflictUploadOngoing = 40054
|
||||
// 文件信息不一致
|
||||
CodeMetaMismatch = 40055
|
||||
// 不支持该格式的压缩文件
|
||||
CodeUnsupportedArchiveType = 40056
|
||||
// 可用存储策略发生变化
|
||||
CodePolicyChanged = 40057
|
||||
// 分享链接无效
|
||||
CodeShareLinkNotFound = 40058
|
||||
// 不能转存自己的分享
|
||||
CodeSaveOwnShare = 40059
|
||||
// 从机无法向主机发送回调请求
|
||||
CodeSlavePingMaster = 40060
|
||||
// Cloudreve 版本不一致
|
||||
CodeVersionMismatch = 40061
|
||||
// 积分不足
|
||||
CodeInsufficientCredit = 40062
|
||||
// 用户组冲突
|
||||
CodeGroupConflict = 40063
|
||||
// 当前已处于此用户组中
|
||||
CodeGroupInvalid = 40064
|
||||
// 兑换码无效
|
||||
CodeInvalidGiftCode = 40065
|
||||
// 已绑定了QQ账号
|
||||
CodeQQBindConflict = 40066
|
||||
// QQ账号已被绑定其他账号
|
||||
CodeQQBindOtherAccount = 40067
|
||||
// QQ 未绑定对应账号
|
||||
CodeQQNotLinked = 40068
|
||||
// 密码不正确
|
||||
CodeIncorrectPassword = 40069
|
||||
// 分享无法预览
|
||||
CodeDisabledSharePreview = 40070
|
||||
// 签名无效
|
||||
CodeInvalidSign = 40071
|
||||
// CodeDBError 数据库操作失败
|
||||
CodeDBError = 50001
|
||||
// CodeEncryptError 加密失败
|
||||
|
@ -130,6 +204,14 @@ const (
|
|||
CodeCacheOperation = 50006
|
||||
// CodeCallbackError 回调失败
|
||||
CodeCallbackError = 50007
|
||||
// 后台设置更新失败
|
||||
CodeUpdateSetting = 50008
|
||||
// 跨域策略添加失败
|
||||
CodeAddCORS = 50009
|
||||
// 节点不可用
|
||||
CodeNodeOffline = 50010
|
||||
// 文件元信息查询失败
|
||||
CodeQueryMetaFailed = 50011
|
||||
//CodeParamErr 各种奇奇怪怪的参数错误
|
||||
CodeParamErr = 40001
|
||||
// CodeNotSet 未定错误,后续尝试从error中获取
|
||||
|
@ -155,7 +237,8 @@ func ParamErr(msg string, err error) Response {
|
|||
// Err 通用错误处理
|
||||
func Err(errCode int, msg string, err error) Response {
|
||||
// 底层错误是AppError,则尝试从AppError中获取详细信息
|
||||
if appError, ok := err.(AppError); ok {
|
||||
var appError AppError
|
||||
if errors.As(err, &appError) {
|
||||
errCode = appError.Code
|
||||
err = appError.RawError
|
||||
msg = appError.Msg
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"crypto/sha1"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package serializer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSlaveTransferReq_Hash(t *testing.T) {
|
||||
|
@ -18,3 +19,14 @@ func TestSlaveTransferReq_Hash(t *testing.T) {
|
|||
}
|
||||
a.NotEqual(s1.Hash("1"), s2.Hash("1"))
|
||||
}
|
||||
|
||||
func TestSlaveRecycleReq_Hash(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
s1 := &SlaveRecycleReq{
|
||||
Path: "1",
|
||||
}
|
||||
s2 := &SlaveRecycleReq{
|
||||
Path: "2",
|
||||
}
|
||||
a.NotEqual(s1.Hash("1"), s2.Hash("1"))
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ const (
|
|||
TransferTaskType
|
||||
// ImportTaskType 导入任务
|
||||
ImportTaskType
|
||||
// RecycleTaskType 回收任务
|
||||
RecycleTaskType
|
||||
)
|
||||
|
||||
// 任务状态
|
||||
|
@ -113,6 +115,8 @@ func GetJobFromModel(task *model.Task) (Job, error) {
|
|||
return NewTransferTaskFromModel(task)
|
||||
case ImportTaskType:
|
||||
return NewImportTaskFromModel(task)
|
||||
case RecycleTaskType:
|
||||
return NewRecycleTaskFromModel(task)
|
||||
default:
|
||||
return nil, ErrUnknownTaskType
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ package task
|
|||
|
||||
import (
|
||||
"errors"
|
||||
testMock "github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
testMock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
|
@ -103,4 +103,16 @@ func TestGetJobFromModel(t *testing.T) {
|
|||
asserts.Nil(job)
|
||||
asserts.Error(err)
|
||||
}
|
||||
// RecycleTaskType
|
||||
{
|
||||
task := &model.Task{
|
||||
Status: 0,
|
||||
Type: RecycleTaskType,
|
||||
}
|
||||
mock.ExpectQuery("SELECT(.+)users(.+)").WillReturnError(errors.New("error"))
|
||||
job, err := GetJobFromModel(task)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.Nil(job)
|
||||
asserts.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
130
pkg/task/recycle.go
Normal file
130
pkg/task/recycle.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package task
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// RecycleTask 文件回收任务
|
||||
type RecycleTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps RecycleProps
|
||||
Err *JobError
|
||||
}
|
||||
|
||||
// RecycleProps 回收任务属性
|
||||
type RecycleProps struct {
|
||||
// 下载任务 GID
|
||||
DownloadGID string `json:"download_gid"`
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *RecycleTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *RecycleTask) Type() int {
|
||||
return RecycleTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *RecycleTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *RecycleTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *RecycleTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *RecycleTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *RecycleTask) SetErrorMsg(msg string, err error) {
|
||||
jobErr := &JobError{Msg: msg}
|
||||
if err != nil {
|
||||
jobErr.Error = err.Error()
|
||||
}
|
||||
job.SetError(jobErr)
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *RecycleTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *RecycleTask) Do() {
|
||||
download, err := model.GetDownloadByGid(job.TaskProps.DownloadGID, job.User.ID)
|
||||
if err != nil {
|
||||
util.Log().Warning("回收任务 %d 找不到下载记录", job.TaskModel.ID)
|
||||
job.SetErrorMsg("无法找到下载任务", err)
|
||||
return
|
||||
}
|
||||
nodeID := download.GetNodeID()
|
||||
node := cluster.Default.GetNodeByID(nodeID)
|
||||
if node == nil {
|
||||
util.Log().Warning("回收任务 %d 找不到节点", job.TaskModel.ID)
|
||||
job.SetErrorMsg("从机节点不可用", nil)
|
||||
return
|
||||
}
|
||||
err = node.GetAria2Instance().DeleteTempFile(download)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除中转临时目录[%s], %s", download.Parent, err)
|
||||
job.SetErrorMsg("文件回收失败", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NewRecycleTask 新建回收任务
|
||||
func NewRecycleTask(download *model.Download) (Job, error) {
|
||||
newTask := &RecycleTask{
|
||||
User: download.GetOwner(),
|
||||
TaskProps: RecycleProps{
|
||||
DownloadGID: download.GID,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewRecycleTaskFromModel 从数据库记录中恢复回收任务
|
||||
func NewRecycleTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &RecycleTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
117
pkg/task/recycle_test.go
Normal file
117
pkg/task/recycle_test.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package task
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRecycleTask_Props(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
task := &RecycleTask{
|
||||
User: &model.User{},
|
||||
}
|
||||
asserts.NotEmpty(task.Props())
|
||||
asserts.Equal(RecycleTaskType, task.Type())
|
||||
asserts.EqualValues(0, task.Creator())
|
||||
asserts.Nil(task.Model())
|
||||
}
|
||||
|
||||
func TestRecycleTask_SetStatus(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
task := &RecycleTask{
|
||||
User: &model.User{},
|
||||
TaskModel: &model.Task{
|
||||
Model: gorm.Model{ID: 1},
|
||||
},
|
||||
}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
task.SetStatus(3)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestRecycleTask_SetError(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
task := &RecycleTask{
|
||||
User: &model.User{},
|
||||
TaskModel: &model.Task{
|
||||
Model: gorm.Model{ID: 1},
|
||||
},
|
||||
}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
task.SetErrorMsg("error", nil)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.Equal("error", task.GetError().Msg)
|
||||
}
|
||||
|
||||
func TestNewRecycleTask(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// 成功
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
job, err := NewRecycleTask(&model.Download{
|
||||
Model: gorm.Model{ID: 1},
|
||||
GID: "test_g_id",
|
||||
Parent: "/",
|
||||
UserID: 1,
|
||||
NodeID: 1,
|
||||
})
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.NotNil(job)
|
||||
asserts.NoError(err)
|
||||
}
|
||||
|
||||
// 失败
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT(.+)").WillReturnError(errors.New("error"))
|
||||
mock.ExpectRollback()
|
||||
job, err := NewRecycleTask(&model.Download{
|
||||
Model: gorm.Model{ID: 1},
|
||||
GID: "test_g_id",
|
||||
Parent: "test/not_exist",
|
||||
UserID: 1,
|
||||
NodeID: 1,
|
||||
})
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.Nil(job)
|
||||
asserts.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRecycleTaskFromModel(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// 成功
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
job, err := NewRecycleTaskFromModel(&model.Task{Props: "{}"})
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.NoError(err)
|
||||
asserts.NotNil(job)
|
||||
}
|
||||
|
||||
// JSON解析失败
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
job, err := NewRecycleTaskFromModel(&model.Task{Props: "?"})
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.Error(err)
|
||||
asserts.Nil(job)
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ package slavetask
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
|
@ -10,7 +12,6 @@ import (
|
|||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/task"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"os"
|
||||
)
|
||||
|
||||
// TransferTask 文件中转任务
|
||||
|
@ -79,8 +80,6 @@ func (job *TransferTask) GetError() *task.JobError {
|
|||
|
||||
// Do 开始执行任务
|
||||
func (job *TransferTask) Do() {
|
||||
defer job.Recycle()
|
||||
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
job.SetErrorMsg("无法初始化匿名文件系统", err)
|
||||
|
@ -137,11 +136,3 @@ func (job *TransferTask) Do() {
|
|||
util.Log().Warning("无法发送转存成功通知到从机, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Recycle 回收临时文件
|
||||
func (job *TransferTask) Recycle() {
|
||||
err := os.Remove(job.Req.Src)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除中转临时文件[%s], %s", job.Req.Src, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package task
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -87,8 +86,6 @@ func (job *TransferTask) GetError() *JobError {
|
|||
|
||||
// Do 开始执行任务
|
||||
func (job *TransferTask) Do() {
|
||||
defer job.Recycle()
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(job.User)
|
||||
if err != nil {
|
||||
|
@ -96,9 +93,8 @@ func (job *TransferTask) Do() {
|
|||
return
|
||||
}
|
||||
|
||||
for index, file := range job.TaskProps.Src {
|
||||
job.TaskModel.SetProgress(index)
|
||||
|
||||
successCount := 0
|
||||
for _, file := range job.TaskProps.Src {
|
||||
dst := path.Join(job.TaskProps.Dst, filepath.Base(file))
|
||||
if job.TaskProps.TrimPath {
|
||||
// 保留原始目录
|
||||
|
@ -132,21 +128,14 @@ func (job *TransferTask) Do() {
|
|||
|
||||
if err != nil {
|
||||
job.SetErrorMsg("文件转存失败", err)
|
||||
} else {
|
||||
successCount++
|
||||
job.TaskModel.SetProgress(successCount)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Recycle 回收临时文件
|
||||
func (job *TransferTask) Recycle() {
|
||||
if job.TaskProps.NodeID == 1 {
|
||||
err := os.RemoveAll(job.TaskProps.Parent)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除中转临时目录[%s], %s", job.TaskProps.Parent, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewTransferTask 新建中转任务
|
||||
func NewTransferTask(user uint, src []string, dst, parent string, trim bool, node uint, sizes map[string]uint64) (Job, error) {
|
||||
creator, err := model.GetActiveUserByID(user)
|
||||
|
|
|
@ -88,11 +88,6 @@ func TestTransferTask_Do(t *testing.T) {
|
|||
}
|
||||
task.TaskProps.Src = []string{"test/not_exist"}
|
||||
task.TaskProps.Parent = "test/not_exist"
|
||||
// 更新进度
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1,
|
||||
1))
|
||||
mock.ExpectCommit()
|
||||
// 更新错误
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1,
|
||||
|
@ -113,11 +108,6 @@ func TestTransferTask_Do(t *testing.T) {
|
|||
task.TaskProps.Src = []string{"test/not_exist"}
|
||||
task.TaskProps.Parent = "test/not_exist"
|
||||
task.TaskProps.TrimPath = true
|
||||
// 更新进度
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1,
|
||||
1))
|
||||
mock.ExpectCommit()
|
||||
// 更新错误
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1,
|
||||
|
|
|
@ -57,7 +57,7 @@ func UpyunCallback(c *gin.Context) {
|
|||
if err := c.ShouldBind(&callbackBody); err == nil {
|
||||
if callbackBody.Code != 200 {
|
||||
util.Log().Debug(
|
||||
"又拍云回调返回错误代码%d,信息:%s",
|
||||
"Upload callback returned error code:%d, message: %s",
|
||||
callbackBody.Code,
|
||||
callbackBody.Message,
|
||||
)
|
||||
|
|
|
@ -132,14 +132,14 @@ func Thumb(c *gin.Context) {
|
|||
// 获取文件ID
|
||||
fileID, ok := c.Get("object_id")
|
||||
if !ok {
|
||||
c.JSON(200, serializer.ParamErr("文件不存在", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeFileNotFound, "", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取缩略图
|
||||
resp, err := fs.GetThumb(ctx, fileID.(uint))
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeNotSet, "无法获取缩略图", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeNotSet, "Failed to get thumbnail", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -142,13 +142,13 @@ func PreviewShareReadme(c *gin.Context) {
|
|||
allowFileName := []string{"readme.txt", "readme.md"}
|
||||
fileName := strings.ToLower(path.Base(service.Path))
|
||||
if !util.ContainsString(allowFileName, fileName) {
|
||||
c.JSON(200, serializer.ParamErr("非README文件", nil))
|
||||
c.JSON(200, serializer.ParamErr("Not a README file", nil))
|
||||
}
|
||||
|
||||
// 必须是目录分享
|
||||
if shareCtx, ok := c.Get("share"); ok {
|
||||
if !shareCtx.(*model.Share).IsDir {
|
||||
c.JSON(200, serializer.ParamErr("此分享无自述文件", nil))
|
||||
c.JSON(200, serializer.ParamErr("This share has no README file", nil))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ func StartLoginAuthn(c *gin.Context) {
|
|||
userName := c.Param("username")
|
||||
expectedUser, err := model.GetActiveUserByEmail(userName)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "User not exist", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func FinishLoginAuthn(c *gin.Context) {
|
|||
userName := c.Param("username")
|
||||
expectedUser, err := model.GetActiveUserByEmail(userName)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "User not exist", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ func StartRegAuthn(c *gin.Context) {
|
|||
|
||||
instance, err := authn.NewAuthnInstance()
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeInternalSetting, "无法初始化Authn", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ func FinishRegAuthn(c *gin.Context) {
|
|||
|
||||
instance, err := authn.NewAuthnInstance()
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeInternalSetting, "无法初始化Authn", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -271,26 +271,26 @@ func UploadAvatar(c *gin.Context) {
|
|||
maxSize := model.GetIntSetting("avatar_size", 2097152)
|
||||
if c.Request.ContentLength == -1 || c.Request.ContentLength > int64(maxSize) {
|
||||
request.BlackHole(c.Request.Body)
|
||||
c.JSON(200, serializer.Err(serializer.CodeUploadFailed, "头像尺寸太大", nil))
|
||||
c.JSON(200, serializer.Err(serializer.CodeFileTooLarge, "", nil))
|
||||
return
|
||||
}
|
||||
|
||||
// 取得上传的文件
|
||||
file, err := c.FormFile("avatar")
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法读取头像数据", err))
|
||||
c.JSON(200, serializer.ParamErr("Failed to read avatar file data", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化头像
|
||||
r, err := file.Open()
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法读取头像数据", err))
|
||||
c.JSON(200, serializer.ParamErr("Failed to read avatar file data", err))
|
||||
return
|
||||
}
|
||||
avatar, err := thumb.NewThumbFromFile(r, file.Filename)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法解析图像数据", err))
|
||||
c.JSON(200, serializer.ParamErr("Invalid image", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -298,7 +298,7 @@ func UploadAvatar(c *gin.Context) {
|
|||
u := CurrentUser(c)
|
||||
err = avatar.CreateAvatar(u.ID)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法创建头像", err))
|
||||
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "Failed to create avatar file", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -306,7 +306,7 @@ func UploadAvatar(c *gin.Context) {
|
|||
if err := u.Update(map[string]interface{}{
|
||||
"avatar": "file",
|
||||
}); err != nil {
|
||||
c.JSON(200, serializer.Err(serializer.CodeDBError, "无法更新头像", err))
|
||||
c.JSON(200, serializer.DBErr("Failed to update avatar attribute", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ func init() {
|
|||
func ServeWebDAV(c *gin.Context) {
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
util.Log().Warning("无法为WebDAV初始化文件系统,%s", err)
|
||||
util.Log().Warning("Failed to initialize filesystem for WebDAV,%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@ type Aria2TestService struct {
|
|||
func (service *Aria2TestService) TestMaster() serializer.Response {
|
||||
res, err := aria2.TestRPCConnection(service.RPC, service.Token, 5)
|
||||
if err != nil {
|
||||
return serializer.ParamErr(err.Error(), err)
|
||||
return serializer.ParamErr("Failed to connect to RPC server: "+err.Error(), err)
|
||||
}
|
||||
|
||||
if res.Version == "" {
|
||||
return serializer.ParamErr("RPC 服务返回非预期响应", nil)
|
||||
return serializer.ParamErr("RPC server returns unexpected response", nil)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res.Version}
|
||||
|
@ -39,7 +39,7 @@ func (service *Aria2TestService) TestMaster() serializer.Response {
|
|||
func (service *Aria2TestService) TestSlave() serializer.Response {
|
||||
slave, err := url.Parse(service.Server)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无法解析从机端地址,"+err.Error(), nil)
|
||||
return serializer.ParamErr("Cannot parse slave server URL, "+err.Error(), nil)
|
||||
}
|
||||
|
||||
controller, _ := url.Parse("/api/v3/slave/ping/aria2")
|
||||
|
@ -60,11 +60,11 @@ func (service *Aria2TestService) TestSlave() serializer.Response {
|
|||
),
|
||||
).DecodeResponse()
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无连接到从机,"+err.Error(), nil)
|
||||
return serializer.ParamErr("Failed to connect to slave node, "+err.Error(), nil)
|
||||
}
|
||||
|
||||
if res.Code != 0 {
|
||||
return serializer.ParamErr("成功接到从机,但是从机返回:"+res.Msg, nil)
|
||||
return serializer.ParamErr("Successfully connected to slave, but slave returns: "+res.Msg, nil)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res.Data.(string)}
|
||||
|
|
|
@ -36,13 +36,13 @@ func (service *ListFolderService) List(c *gin.Context) serializer.Response {
|
|||
// 列取存储策略中的目录
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法创建文件系统", err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -50,7 +50,7 @@ func (service *ListFolderService) List(c *gin.Context) serializer.Response {
|
|||
fs.Policy = &policy
|
||||
res, err := fs.ListPhysical(c.Request.Context(), service.Path)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeIOFailed, "无法列取目录", err)
|
||||
return serializer.Err(serializer.CodeListFilesError, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
|
@ -63,20 +63,20 @@ func (service *ListFolderService) List(c *gin.Context) serializer.Response {
|
|||
// 查找用户
|
||||
user, err := model.GetUserByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "用户不存在", err)
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(&user)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法创建文件系统", err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 列取目录
|
||||
res, err := fs.List(c.Request.Context(), service.Path, nil)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeIOFailed, "无法列取目录", err)
|
||||
return serializer.Err(serializer.CodeListFilesError, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
|
@ -88,7 +88,7 @@ func (service *ListFolderService) List(c *gin.Context) serializer.Response {
|
|||
func (service *FileBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
files, err := model.GetFilesByIDs(service.ID, 0)
|
||||
if err != nil {
|
||||
return serializer.DBErr("无法列出待删除文件", err)
|
||||
return serializer.DBErr("Failed to list files for deleting", err)
|
||||
}
|
||||
|
||||
// 根据用户分组
|
||||
|
@ -135,7 +135,7 @@ func (service *FileBatchService) Delete(c *gin.Context) serializer.Response {
|
|||
func (service *FileService) Get(c *gin.Context) serializer.Response {
|
||||
file, err := model.GetFilesByIDs([]uint{service.ID}, 0)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "文件不存在", err)
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", err)
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), fsctx.FileModelCtx, &file[0])
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// AddGroupService 用户组添加服务
|
||||
|
@ -21,7 +20,7 @@ type GroupService struct {
|
|||
func (service *GroupService) Get() serializer.Response {
|
||||
group, err := model.GetGroupByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err)
|
||||
return serializer.Err(serializer.CodeGroupNotFound, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: group}
|
||||
|
@ -32,12 +31,12 @@ func (service *GroupService) Delete() serializer.Response {
|
|||
// 查找用户组
|
||||
group, err := model.GetGroupByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "用户组不存在", err)
|
||||
return serializer.Err(serializer.CodeGroupNotFound, "", err)
|
||||
}
|
||||
|
||||
// 是否为系统用户组
|
||||
if group.ID <= 3 {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "系统用户组无法删除", err)
|
||||
return serializer.Err(serializer.CodeInvalidActionOnSystemGroup, "", err)
|
||||
}
|
||||
|
||||
// 检查是否有用户使用
|
||||
|
@ -46,7 +45,7 @@ func (service *GroupService) Delete() serializer.Response {
|
|||
Select("count(id)").Row()
|
||||
row.Scan(&total)
|
||||
if total > 0 {
|
||||
return serializer.ParamErr(fmt.Sprintf("有 %d 位用户仍属于此用户组,请先删除这些用户或者更改用户组", total), nil)
|
||||
return serializer.Err(serializer.CodeGroupUsedByUser, strconv.Itoa(total), nil)
|
||||
}
|
||||
|
||||
model.DB.Delete(&group)
|
||||
|
@ -58,11 +57,11 @@ func (service *GroupService) Delete() serializer.Response {
|
|||
func (service *AddGroupService) Add() serializer.Response {
|
||||
if service.Group.ID > 0 {
|
||||
if err := model.DB.Save(&service.Group).Error; err != nil {
|
||||
return serializer.ParamErr("用户组保存失败", err)
|
||||
return serializer.DBErr("Failed to save group record", err)
|
||||
}
|
||||
} else {
|
||||
if err := model.DB.Create(&service.Group).Error; err != nil {
|
||||
return serializer.ParamErr("用户组添加失败", err)
|
||||
return serializer.DBErr("Failed to create group record", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@ type AddNodeService struct {
|
|||
func (service *AddNodeService) Add() serializer.Response {
|
||||
if service.Node.ID > 0 {
|
||||
if err := model.DB.Save(&service.Node).Error; err != nil {
|
||||
return serializer.ParamErr("节点保存失败", err)
|
||||
return serializer.DBErr("Failed to save node record", err)
|
||||
}
|
||||
} else {
|
||||
if err := model.DB.Create(&service.Node).Error; err != nil {
|
||||
return serializer.ParamErr("节点添加失败", err)
|
||||
return serializer.DBErr("Failed to create node record", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,16 +84,16 @@ type ToggleNodeService struct {
|
|||
func (service *ToggleNodeService) Toggle() serializer.Response {
|
||||
node, err := model.GetNodeByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("找不到节点", err)
|
||||
return serializer.DBErr("Node not found", err)
|
||||
}
|
||||
|
||||
// 是否为系统节点
|
||||
if node.ID <= 1 {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "系统节点无法更改", err)
|
||||
return serializer.Err(serializer.CodeInvalidActionOnSystemNode, "", err)
|
||||
}
|
||||
|
||||
if err = node.SetStatus(service.Desired); err != nil {
|
||||
return serializer.DBErr("无法更改节点状态", err)
|
||||
return serializer.DBErr("Failed to change node status", err)
|
||||
}
|
||||
|
||||
if service.Desired == model.NodeActive {
|
||||
|
@ -115,17 +115,17 @@ func (service *NodeService) Delete() serializer.Response {
|
|||
// 查找用户组
|
||||
node, err := model.GetNodeByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "节点不存在", err)
|
||||
return serializer.DBErr("Node record not found", err)
|
||||
}
|
||||
|
||||
// 是否为系统节点
|
||||
if node.ID <= 1 {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "系统节点无法删除", err)
|
||||
return serializer.Err(serializer.CodeInvalidActionOnSystemNode, "", err)
|
||||
}
|
||||
|
||||
cluster.Default.Delete(node.ID)
|
||||
if err := model.DB.Delete(&node).Error; err != nil {
|
||||
return serializer.DBErr("无法删除节点", err)
|
||||
return serializer.DBErr("Failed to delete node record", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -135,7 +135,7 @@ func (service *NodeService) Delete() serializer.Response {
|
|||
func (service *NodeService) Get() serializer.Response {
|
||||
node, err := model.GetNodeByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "节点不存在", err)
|
||||
return serializer.DBErr("Node not exist", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: node}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -58,12 +59,12 @@ type PolicyService struct {
|
|||
func (service *PolicyService) Delete() serializer.Response {
|
||||
// 禁止删除默认策略
|
||||
if service.ID == 1 {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "默认存储策略无法删除", nil)
|
||||
return serializer.Err(serializer.CodeDeleteDefaultPolicy, "", nil)
|
||||
}
|
||||
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
// 检查是否有文件使用
|
||||
|
@ -72,7 +73,7 @@ func (service *PolicyService) Delete() serializer.Response {
|
|||
Select("count(id)").Row()
|
||||
row.Scan(&total)
|
||||
if total > 0 {
|
||||
return serializer.ParamErr(fmt.Sprintf("有 %d 个文件仍在使用此存储策略,请先删除这些文件", total), nil)
|
||||
return serializer.Err(serializer.CodePolicyUsedByFiles, strconv.Itoa(total), nil)
|
||||
}
|
||||
|
||||
// 检查用户组使用
|
||||
|
@ -83,7 +84,7 @@ func (service *PolicyService) Delete() serializer.Response {
|
|||
).Find(&groups)
|
||||
|
||||
if len(groups) > 0 {
|
||||
return serializer.ParamErr(fmt.Sprintf("有 %d 个用户组绑定了此存储策略,请先解除绑定", len(groups)), nil)
|
||||
return serializer.Err(serializer.CodePolicyUsedByGroups, strconv.Itoa(len(groups)), nil)
|
||||
}
|
||||
|
||||
model.DB.Delete(&policy)
|
||||
|
@ -96,7 +97,7 @@ func (service *PolicyService) Delete() serializer.Response {
|
|||
func (service *PolicyService) Get() serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: policy}
|
||||
|
@ -106,12 +107,12 @@ func (service *PolicyService) Get() serializer.Response {
|
|||
func (service *PolicyService) GetOAuth(c *gin.Context) serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil || policy.Type != "onedrive" {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", nil)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
client, err := onedrive.NewClient(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法初始化 OneDrive 客户端", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize OneDrive client", err)
|
||||
}
|
||||
|
||||
util.SetSession(c, map[string]interface{}{
|
||||
|
@ -130,11 +131,11 @@ func (service *PolicyService) GetOAuth(c *gin.Context) serializer.Response {
|
|||
func (service *PolicyService) AddSCF() serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", nil)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
if err := cos.CreateSCF(&policy, service.Region); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "云函数创建失败", err)
|
||||
return serializer.ParamErr("Failed to create SCF function", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -144,17 +145,17 @@ func (service *PolicyService) AddSCF() serializer.Response {
|
|||
func (service *PolicyService) AddCORS() serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", nil)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
switch policy.Type {
|
||||
case "oss":
|
||||
handler, err := oss.NewDriver(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "跨域策略添加失败", err)
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
if err := handler.CORS(); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "跨域策略添加失败", err)
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
case "cos":
|
||||
u, _ := url.Parse(policy.Server)
|
||||
|
@ -171,19 +172,19 @@ func (service *PolicyService) AddCORS() serializer.Response {
|
|||
}
|
||||
|
||||
if err := handler.CORS(); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "跨域策略添加失败", err)
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
case "s3":
|
||||
handler, err := s3.NewDriver(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "跨域策略添加失败", err)
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
|
||||
if err := handler.CORS(); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "跨域策略添加失败", err)
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
default:
|
||||
return serializer.ParamErr("不支持此策略", nil)
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -193,7 +194,7 @@ func (service *PolicyService) AddCORS() serializer.Response {
|
|||
func (service *SlavePingService) Test() serializer.Response {
|
||||
master, err := url.Parse(service.Callback)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无法解析主机站点地址,请检查主机 参数设置 - 站点信息 - 站点URL设置,"+err.Error(), nil)
|
||||
return serializer.ParamErr("Failed to parse Master site url: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
controller, _ := url.Parse("/api/v3/site/ping")
|
||||
|
@ -207,7 +208,7 @@ func (service *SlavePingService) Test() serializer.Response {
|
|||
).DecodeResponse()
|
||||
|
||||
if err != nil {
|
||||
return serializer.ParamErr("从机无法向主机发送回调请求,请检查主机端 参数设置 - 站点信息 - 站点URL设置,并确保从机可以连接到此地址,"+err.Error(), nil)
|
||||
return serializer.Err(serializer.CodeSlavePingMaster, err.Error(), nil)
|
||||
}
|
||||
|
||||
version := conf.BackendVersion
|
||||
|
@ -215,7 +216,7 @@ func (service *SlavePingService) Test() serializer.Response {
|
|||
version += "-pro"
|
||||
}
|
||||
if res.Data.(string) != version {
|
||||
return serializer.ParamErr("Cloudreve版本不一致,主机:"+res.Data.(string)+",从机:"+version, nil)
|
||||
return serializer.Err(serializer.CodeVersionMismatch, "Master: "+res.Data.(string)+", Slave: "+version, nil)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -225,7 +226,7 @@ func (service *SlavePingService) Test() serializer.Response {
|
|||
func (service *SlaveTestService) Test() serializer.Response {
|
||||
slave, err := url.Parse(service.Server)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无法解析从机端地址,"+err.Error(), nil)
|
||||
return serializer.ParamErr("Failed to parse slave node server URL: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
controller, _ := url.Parse("/api/v3/slave/ping")
|
||||
|
@ -248,11 +249,11 @@ func (service *SlaveTestService) Test() serializer.Response {
|
|||
),
|
||||
).DecodeResponse()
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无连接到从机,"+err.Error(), nil)
|
||||
return serializer.ParamErr("Failed to connect to slave node: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
if res.Code != 0 {
|
||||
return serializer.ParamErr("成功接到从机,但是从机返回:"+res.Msg, nil)
|
||||
return serializer.ParamErr("Successfully connected to slave node, but slave returns: "+res.Msg, nil)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -266,11 +267,11 @@ func (service *AddPolicyService) Add() serializer.Response {
|
|||
|
||||
if service.Policy.ID > 0 {
|
||||
if err := model.DB.Save(&service.Policy).Error; err != nil {
|
||||
return serializer.ParamErr("存储策略保存失败", err)
|
||||
return serializer.DBErr("Failed to save policy", err)
|
||||
}
|
||||
} else {
|
||||
if err := model.DB.Create(&service.Policy).Error; err != nil {
|
||||
return serializer.ParamErr("存储策略添加失败", err)
|
||||
return serializer.DBErr("Failed to create policy", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,7 +287,7 @@ func (service *PathTestService) Test() serializer.Response {
|
|||
path = filepath.Join(path, "test.txt")
|
||||
file, err := util.CreatNestedFile(util.RelativePath(path))
|
||||
if err != nil {
|
||||
return serializer.ParamErr(fmt.Sprintf("无法创建路径 %s , %s", path, err.Error()), nil)
|
||||
return serializer.ParamErr(fmt.Sprintf("Failed to create \"%s\": %s", path, err.Error()), nil)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
|
|
@ -17,7 +17,7 @@ type ShareBatchService struct {
|
|||
// Delete 删除文件
|
||||
func (service *ShareBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Share{}).Error; err != nil {
|
||||
return serializer.DBErr("无法删除分享", err)
|
||||
return serializer.DBErr("Failed to delete share record", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
|
|
@ -43,8 +43,8 @@ type MailTestService struct {
|
|||
|
||||
// Send 发送测试邮件
|
||||
func (service *MailTestService) Send() serializer.Response {
|
||||
if err := email.Send(service.Email, "Cloudreve发信测试", "这是一封测试邮件,用于测试 Cloudreve 发信设置。"); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "发信失败, "+err.Error(), nil)
|
||||
if err := email.Send(service.Email, "Cloudreve Email delivery test", "This is a test Email, to test Cloudreve Email delivery settings"); err != nil {
|
||||
return serializer.Err(serializer.CodeFailedSendEmail, err.Error(), nil)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
@ -65,14 +65,14 @@ func (service *BatchSettingChangeService) Change() serializer.Response {
|
|||
if err := tx.Model(&model.Setting{}).Where("name = ?", setting.Key).Update("value", setting.Value).Error; err != nil {
|
||||
cache.Deletes(cacheClean, "setting_")
|
||||
tx.Rollback()
|
||||
return serializer.DBErr("设置 "+setting.Key+" 更新失败", err)
|
||||
return serializer.Err(serializer.CodeUpdateSetting, "Setting "+setting.Key+" failed to update", err)
|
||||
}
|
||||
|
||||
cacheClean = append(cacheClean, setting.Key)
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return serializer.DBErr("设置更新失败", err)
|
||||
return serializer.DBErr("Failed to update setting", err)
|
||||
}
|
||||
|
||||
cache.Deletes(cacheClean, "setting_")
|
||||
|
|
|
@ -28,7 +28,7 @@ func (service *ImportTaskService) Create(c *gin.Context, user *model.User) seria
|
|||
// 创建任务
|
||||
job, err := task.NewImportTask(service.UID, service.PolicyID, service.Src, service.Dst, service.Recursive)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
|
||||
return serializer.DBErr("Failed to create task record.", err)
|
||||
}
|
||||
task.TaskPoll.Submit(job)
|
||||
return serializer.Response{}
|
||||
|
@ -37,7 +37,7 @@ func (service *ImportTaskService) Create(c *gin.Context, user *model.User) seria
|
|||
// Delete 删除任务
|
||||
func (service *TaskBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Download{}).Error; err != nil {
|
||||
return serializer.DBErr("无法删除任务", err)
|
||||
return serializer.DBErr("Failed to delete task records", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func (service *TaskBatchService) Delete(c *gin.Context) serializer.Response {
|
|||
// DeleteGeneral 删除常规任务
|
||||
func (service *TaskBatchService) DeleteGeneral(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Task{}).Error; err != nil {
|
||||
return serializer.DBErr("无法删除任务", err)
|
||||
return serializer.DBErr("Failed to delete task records", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@ type UserBatchService struct {
|
|||
func (service *UserService) Ban() serializer.Response {
|
||||
user, err := model.GetUserByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "用户不存在", err)
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
if user.ID == 1 {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "无法封禁初始用户", err)
|
||||
return serializer.Err(serializer.CodeInvalidActionOnDefaultUser, "", err)
|
||||
}
|
||||
|
||||
if user.Status == model.Active {
|
||||
|
@ -50,12 +50,12 @@ func (service *UserBatchService) Delete() serializer.Response {
|
|||
for _, uid := range service.ID {
|
||||
user, err := model.GetUserByID(uid)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "用户不存在", err)
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
// 不能删除初始用户
|
||||
if uid == 1 {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "无法删除初始用户", err)
|
||||
return serializer.Err(serializer.CodeInvalidActionOnDefaultUser, "", err)
|
||||
}
|
||||
|
||||
// 删除与此用户相关的所有资源
|
||||
|
@ -64,7 +64,7 @@ func (service *UserBatchService) Delete() serializer.Response {
|
|||
// 删除所有文件
|
||||
root, err := fs.User.Root()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "无法找到用户根目录", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "User's root folder not exist", err)
|
||||
}
|
||||
fs.Delete(context.Background(), []uint{root.ID}, []uint{}, false)
|
||||
|
||||
|
@ -89,7 +89,7 @@ func (service *UserBatchService) Delete() serializer.Response {
|
|||
func (service *UserService) Get() serializer.Response {
|
||||
group, err := model.GetUserByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "用户不存在", err)
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: group}
|
||||
|
@ -112,16 +112,16 @@ func (service *AddUserService) Add() serializer.Response {
|
|||
|
||||
// 检查愚蠢操作
|
||||
if user.ID == 1 && user.GroupID != 1 {
|
||||
return serializer.ParamErr("无法更改初始用户的用户组", nil)
|
||||
return serializer.Err(serializer.CodeChangeGroupForDefaultUser, "", nil)
|
||||
}
|
||||
|
||||
if err := model.DB.Save(&user).Error; err != nil {
|
||||
return serializer.ParamErr("用户保存失败", err)
|
||||
return serializer.DBErr("Failed to save user record", err)
|
||||
}
|
||||
} else {
|
||||
service.User.SetPassword(service.Password)
|
||||
if err := model.DB.Create(&service.User).Error; err != nil {
|
||||
return serializer.ParamErr("用户组添加失败", err)
|
||||
return serializer.DBErr("Failed to create user record", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,24 +24,24 @@ func (service *BatchAddURLService) Add(c *gin.Context, taskType int) serializer.
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 检查用户组权限
|
||||
if !fs.User.Group.OptionsSerialized.Aria2 {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 存放目录是否存在
|
||||
if exist, _ := fs.IsPathExist(service.Dst); !exist {
|
||||
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
|
||||
return serializer.Err(serializer.CodeParentNotExist, "", nil)
|
||||
}
|
||||
|
||||
// 检查批量任务数量
|
||||
limit := fs.User.Group.OptionsSerialized.Aria2BatchSize
|
||||
if limit > 0 && len(service.URLs) > limit {
|
||||
return serializer.Err(serializer.CodeBatchAria2Size, "Exceed aria2 batch size", nil)
|
||||
return serializer.Err(serializer.CodeBatchAria2Size, "", nil)
|
||||
}
|
||||
|
||||
res := make([]serializer.Response, 0, len(service.URLs))
|
||||
|
@ -71,25 +71,25 @@ func (service *AddURLService) Add(c *gin.Context, fs *filesystem.FileSystem, tas
|
|||
// 创建文件系统
|
||||
fs, err = filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 检查用户组权限
|
||||
if !fs.User.Group.OptionsSerialized.Aria2 {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 存放目录是否存在
|
||||
if exist, _ := fs.IsPathExist(service.Dst); !exist {
|
||||
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
|
||||
return serializer.Err(serializer.CodeParentNotExist, "", nil)
|
||||
}
|
||||
}
|
||||
|
||||
downloads := model.GetDownloadsByStatusAndUser(0, fs.User.ID, common.Downloading, common.Paused, common.Ready)
|
||||
limit := fs.User.Group.OptionsSerialized.Aria2BatchSize
|
||||
if limit > 0 && len(downloads)+1 > limit {
|
||||
return serializer.Err(serializer.CodeBatchAria2Size, "Exceed aria2 batch size", nil)
|
||||
return serializer.Err(serializer.CodeBatchAria2Size, "", nil)
|
||||
}
|
||||
|
||||
// 创建任务
|
||||
|
@ -107,20 +107,20 @@ func (service *AddURLService) Add(c *gin.Context, fs *filesystem.FileSystem, tas
|
|||
// 获取 Aria2 实例
|
||||
err, node := cluster.Default.BalanceNodeByFeature("aria2", lb)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Aria2 实例获取失败", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to get Aria2 instance", err)
|
||||
}
|
||||
|
||||
// 创建任务
|
||||
gid, err := node.GetAria2Instance().CreateTask(task, fs.User.Group.OptionsSerialized.Aria2Options)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
|
||||
return serializer.Err(serializer.CodeCreateTaskError, "", err)
|
||||
}
|
||||
|
||||
task.GID = gid
|
||||
task.NodeID = node.ID()
|
||||
_, err = task.Create()
|
||||
if err != nil {
|
||||
return serializer.DBErr("任务创建失败", err)
|
||||
return serializer.DBErr("Failed to create task record", err)
|
||||
}
|
||||
|
||||
// 创建任务监控
|
||||
|
@ -136,14 +136,14 @@ func Add(c *gin.Context, service *serializer.SlaveAria2Call) serializer.Response
|
|||
// 创建任务
|
||||
gid, err := caller.(common.Aria2).CreateTask(service.Task, service.GroupOptions)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法创建离线下载任务", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to create aria2 task", err)
|
||||
}
|
||||
|
||||
// 创建事件通知回调
|
||||
siteID, _ := c.Get("MasterSiteID")
|
||||
mq.GlobalMQ.SubscribeCallback(gid, func(message mq.Message) {
|
||||
if err := cluster.DefaultController.SendNotification(siteID.(string), message.TriggeredBy, message); err != nil {
|
||||
util.Log().Warning("无法发送离线下载任务状态变更通知, %s", err)
|
||||
util.Log().Warning("Failed to send remote download task status change notifications: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ func (service *DownloadListService) Finished(c *gin.Context, user *model.User) s
|
|||
// Downloading 获取正在下载中的任务
|
||||
func (service *DownloadListService) Downloading(c *gin.Context, user *model.User) serializer.Response {
|
||||
// 查找下载记录
|
||||
downloads := model.GetDownloadsByStatusAndUser(service.Page, user.ID, common.Downloading, common.Paused, common.Ready)
|
||||
downloads := model.GetDownloadsByStatusAndUser(service.Page, user.ID, common.Downloading, common.Seeding, common.Paused, common.Ready)
|
||||
intervals := make(map[uint]int)
|
||||
for _, download := range downloads {
|
||||
if _, ok := intervals[download.ID]; !ok {
|
||||
|
@ -54,13 +54,13 @@ func (service *DownloadTaskService) Delete(c *gin.Context) serializer.Response {
|
|||
// 查找下载记录
|
||||
download, err := model.GetDownloadByGid(c.Param("gid"), user.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "下载记录不存在", err)
|
||||
return serializer.Err(serializer.CodeNotFound, "Download record not found", err)
|
||||
}
|
||||
|
||||
if download.Status >= common.Error {
|
||||
if download.Status >= common.Error && download.Status <= common.Unknown {
|
||||
// 如果任务已完成,则删除任务记录
|
||||
if err := download.Delete(); err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "任务记录删除失败", err)
|
||||
return serializer.DBErr("Failed to delete task record", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
@ -68,11 +68,11 @@ func (service *DownloadTaskService) Delete(c *gin.Context) serializer.Response {
|
|||
// 取消任务
|
||||
node := cluster.Default.GetNodeByID(download.GetNodeID())
|
||||
if node == nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "目标节点不可用", err)
|
||||
return serializer.Err(serializer.CodeNodeOffline, "", err)
|
||||
}
|
||||
|
||||
if err := node.GetAria2Instance().Cancel(download); err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "操作失败", err)
|
||||
return serializer.Err(serializer.CodeNotSet, "Operation failed", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -86,17 +86,17 @@ func (service *SelectFileService) Select(c *gin.Context) serializer.Response {
|
|||
// 查找下载记录
|
||||
download, err := model.GetDownloadByGid(c.Param("gid"), user.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "下载记录不存在", err)
|
||||
return serializer.Err(serializer.CodeNotFound, "Download record not found", err)
|
||||
}
|
||||
|
||||
if download.StatusInfo.BitTorrent.Mode != "multi" || (download.Status != common.Downloading && download.Status != common.Paused) {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "此下载任务无法选取文件", err)
|
||||
return serializer.ParamErr("You cannot select files for this task", nil)
|
||||
}
|
||||
|
||||
// 选取下载
|
||||
node := cluster.Default.GetNodeByID(download.GetNodeID())
|
||||
if err := node.GetAria2Instance().Select(download, service.Indexes); err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "操作失败", err)
|
||||
return serializer.Err(serializer.CodeNotSet, "Operation failed", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -110,7 +110,7 @@ func SlaveStatus(c *gin.Context, service *serializer.SlaveAria2Call) serializer.
|
|||
// 查询任务
|
||||
status, err := caller.(common.Aria2).Status(service.Task)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "离线下载任务查询失败", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to query remote download task status", err)
|
||||
}
|
||||
|
||||
return serializer.NewResponseWithGobData(status)
|
||||
|
@ -124,7 +124,7 @@ func SlaveCancel(c *gin.Context, service *serializer.SlaveAria2Call) serializer.
|
|||
// 查询任务
|
||||
err := caller.(common.Aria2).Cancel(service.Task)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "任务取消失败", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to cancel task", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -138,7 +138,7 @@ func SlaveSelect(c *gin.Context, service *serializer.SlaveAria2Call) serializer.
|
|||
// 查询任务
|
||||
err := caller.(common.Aria2).Select(service.Task, service.Files)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "任务选取失败", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to select files", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
@ -152,7 +152,7 @@ func SlaveDeleteTemp(c *gin.Context, service *serializer.SlaveAria2Call) seriali
|
|||
// 查询任务
|
||||
err := caller.(common.Aria2).DeleteTempFile(service.Task)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "临时文件删除失败", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to delete temp files", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
|
|
@ -28,36 +28,36 @@ func (service *OneDriveOauthService) Auth(c *gin.Context) serializer.Response {
|
|||
|
||||
policyID, ok := util.GetSession(c, "onedrive_oauth_policy").(uint)
|
||||
if !ok {
|
||||
return serializer.Err(serializer.CodeNotFound, "授权会话不存在,请重试", nil)
|
||||
return serializer.Err(serializer.CodeNotFound, "", nil)
|
||||
}
|
||||
|
||||
util.DeleteSession(c, "onedrive_oauth_policy")
|
||||
|
||||
policy, err := model.GetPolicyByID(policyID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", nil)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
client, err := onedrive.NewClient(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法初始化 OneDrive 客户端", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize OneDrive client", err)
|
||||
}
|
||||
|
||||
credential, err := client.ObtainToken(c, onedrive.WithCode(service.Code))
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "AccessToken 获取失败", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to fetch AccessToken", err)
|
||||
}
|
||||
|
||||
// 更新存储策略的 RefreshToken
|
||||
client.Policy.AccessKey = credential.RefreshToken
|
||||
if err := client.Policy.SaveAndClearCache(); err != nil {
|
||||
return serializer.DBErr("无法更新 RefreshToken", err)
|
||||
return serializer.DBErr("Failed to update RefreshToken", err)
|
||||
}
|
||||
|
||||
cache.Deletes([]string{client.Policy.AccessKey}, "onedrive_")
|
||||
if client.Policy.OptionsSerialized.OdDriver != "" && strings.Contains(client.Policy.OptionsSerialized.OdDriver, "http") {
|
||||
if err := querySharePointSiteID(c, client.Policy); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法查询 SharePoint 站点 ID", err)
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to query SharePoint site ID", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromCallback(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, err.Error(), err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -156,7 +156,7 @@ func (service *OneDriveCallback) PreProcess(c *gin.Context) serializer.Response
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromCallback(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -166,7 +166,7 @@ func (service *OneDriveCallback) PreProcess(c *gin.Context) serializer.Response
|
|||
// 获取文件信息
|
||||
info, err := fs.Handler.(onedrive.Driver).Client.Meta(context.Background(), "", uploadSession.SavePath)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件元信息查询失败", err)
|
||||
return serializer.Err(serializer.CodeQueryMetaFailed, "", err)
|
||||
}
|
||||
|
||||
// 验证与回调会话中是否一致
|
||||
|
@ -181,7 +181,7 @@ func (service *OneDriveCallback) PreProcess(c *gin.Context) serializer.Response
|
|||
|
||||
if isSizeCheckFailed || !strings.EqualFold(info.GetSourcePath(), actualPath) {
|
||||
fs.Handler.(onedrive.Driver).Client.Delete(context.Background(), []string{info.GetSourcePath()})
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err)
|
||||
return serializer.Err(serializer.CodeMetaMismatch, "", err)
|
||||
}
|
||||
service.Meta = info
|
||||
return ProcessCallback(service, c)
|
||||
|
@ -192,7 +192,7 @@ func (service *COSCallback) PreProcess(c *gin.Context) serializer.Response {
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromCallback(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -202,12 +202,12 @@ func (service *COSCallback) PreProcess(c *gin.Context) serializer.Response {
|
|||
// 获取文件信息
|
||||
info, err := fs.Handler.(cos.Driver).Meta(context.Background(), uploadSession.SavePath)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err)
|
||||
return serializer.Err(serializer.CodeMetaMismatch, "", err)
|
||||
}
|
||||
|
||||
// 验证实际文件信息与回调会话中是否一致
|
||||
if uploadSession.Size != info.Size || uploadSession.Key != info.CallbackKey {
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err)
|
||||
return serializer.Err(serializer.CodeMetaMismatch, "", err)
|
||||
}
|
||||
|
||||
return ProcessCallback(service, c)
|
||||
|
@ -218,7 +218,7 @@ func (service *S3Callback) PreProcess(c *gin.Context) serializer.Response {
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromCallback(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -228,12 +228,12 @@ func (service *S3Callback) PreProcess(c *gin.Context) serializer.Response {
|
|||
// 获取文件信息
|
||||
info, err := fs.Handler.(*s3.Driver).Meta(context.Background(), uploadSession.SavePath)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err)
|
||||
return serializer.Err(serializer.CodeMetaMismatch, "", err)
|
||||
}
|
||||
|
||||
// 验证实际文件信息与回调会话中是否一致
|
||||
if uploadSession.Size != info.Size {
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err)
|
||||
return serializer.Err(serializer.CodeMetaMismatch, "", err)
|
||||
}
|
||||
|
||||
return ProcessCallback(service, c)
|
||||
|
@ -244,7 +244,7 @@ func (service *UploadCallbackService) PreProcess(c *gin.Context) serializer.Resp
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromCallback(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -254,7 +254,7 @@ func (service *UploadCallbackService) PreProcess(c *gin.Context) serializer.Resp
|
|||
// 验证文件大小
|
||||
if uploadSession.Size != service.Size {
|
||||
fs.Handler.Delete(context.Background(), []string{uploadSession.SavePath})
|
||||
return serializer.Err(serializer.CodeUploadFailed, "文件大小不一致", nil)
|
||||
return serializer.Err(serializer.CodeMetaMismatch, "", err)
|
||||
}
|
||||
|
||||
return ProcessCallback(service, c)
|
||||
|
|
|
@ -18,7 +18,7 @@ func (service *DirectoryService) ListDirectory(c *gin.Context) serializer.Respon
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -48,7 +48,7 @@ func (service *DirectoryService) CreateDirectory(c *gin.Context) serializer.Resp
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ func (service *SingleFileService) Create(c *gin.Context) serializer.Response {
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -84,13 +84,13 @@ func (service *SlaveListService) List(c *gin.Context) serializer.Response {
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
objects, err := fs.Handler.List(context.Background(), service.Path, service.Recursive)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeIOFailed, "无法列取文件", err)
|
||||
return serializer.Err(serializer.CodeIOFailed, "Cannot list files", err)
|
||||
}
|
||||
|
||||
res, _ := json.Marshal(objects)
|
||||
|
@ -101,21 +101,21 @@ func (service *SlaveListService) List(c *gin.Context) serializer.Response {
|
|||
func (service *ArchiveService) DownloadArchived(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
userRaw, exist := cache.Get("archive_user_" + service.ID)
|
||||
if !exist {
|
||||
return serializer.Err(404, "归档会话不存在", nil)
|
||||
return serializer.Err(serializer.CodeNotFound, "Archive session not exist", nil)
|
||||
}
|
||||
user := userRaw.(model.User)
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(&user)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 查找打包的临时文件
|
||||
archiveSession, exist := cache.Get("archive_" + service.ID)
|
||||
if !exist {
|
||||
return serializer.Err(404, "归档会话不存在", nil)
|
||||
return serializer.Err(serializer.CodeNotFound, "Archive session not exist", nil)
|
||||
}
|
||||
|
||||
// 开始打包
|
||||
|
@ -126,7 +126,7 @@ func (service *ArchiveService) DownloadArchived(ctx context.Context, c *gin.Cont
|
|||
ctx = context.WithValue(ctx, fsctx.GinCtx, c)
|
||||
err = fs.Compress(ctx, c.Writer, items.Dirs, items.Items, true)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "无法创建压缩文件", err)
|
||||
return serializer.Err(serializer.CodeNotSet, "Failed to compress file", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
|
@ -138,7 +138,7 @@ func (service *ArchiveService) DownloadArchived(ctx context.Context, c *gin.Cont
|
|||
func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -167,7 +167,7 @@ func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Con
|
|||
func (service *FileAnonymousGetService) Source(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -243,7 +243,7 @@ func (service *FileIDService) CreateDownloadSession(ctx context.Context, c *gin.
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -267,14 +267,14 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 查找打包的临时文件
|
||||
file, exist := cache.Get("download_" + service.ID)
|
||||
if !exist {
|
||||
return serializer.Err(404, "文件下载会话不存在", nil)
|
||||
return serializer.Err(serializer.CodeNotFound, "Download session not exist", nil)
|
||||
}
|
||||
fs.FileTarget = []model.File{file.(model.File)}
|
||||
|
||||
|
@ -308,7 +308,7 @@ func (service *FileIDService) PreviewContent(ctx context.Context, c *gin.Context
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -327,7 +327,7 @@ func (service *FileIDService) PreviewContent(ctx context.Context, c *gin.Context
|
|||
path := ctx.Value(fsctx.PathCtx).(string)
|
||||
err := fs.ResetFileIfNotExist(ctx, path)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeFileNotFound, err.Error(), err)
|
||||
}
|
||||
objectID = uint(0)
|
||||
}
|
||||
|
@ -371,7 +371,7 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se
|
|||
fileSize, err := strconv.ParseUint(c.Request.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
|
||||
return serializer.ParamErr("无法解析文件尺寸", err)
|
||||
return serializer.ParamErr("Invalid content-length value", err)
|
||||
}
|
||||
|
||||
fileData := fsctx.FileStream{
|
||||
|
@ -384,7 +384,7 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c)
|
||||
|
||||
|
@ -392,7 +392,7 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se
|
|||
fileID, _ := c.Get("object_id")
|
||||
originFile, _ := model.GetFilesByIDs([]uint{fileID.(uint)}, fs.User.ID)
|
||||
if len(originFile) == 0 {
|
||||
return serializer.Err(404, "文件不存在", nil)
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", nil)
|
||||
}
|
||||
fileData.Name = originFile[0].Name
|
||||
|
||||
|
@ -433,12 +433,12 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se
|
|||
func (s *ItemIDService) Sources(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "无法初始化文件系统", err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
if len(s.Raw().Items) > fs.User.Group.OptionsSerialized.SourceBatchSize {
|
||||
return serializer.Err(serializer.CodeBatchSourceSize, "超出批量获取外链的最大数量限制", err)
|
||||
return serializer.Err(serializer.CodeBatchSourceSize, "", err)
|
||||
}
|
||||
|
||||
res := make([]serializer.Sources, 0, len(s.Raw().Items))
|
||||
|
|
|
@ -102,30 +102,30 @@ func (service *ItemDecompressService) CreateDecompressTask(c *gin.Context) seria
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 检查用户组权限
|
||||
if !fs.User.Group.OptionsSerialized.ArchiveTask {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 存放目录是否存在
|
||||
if exist, _ := fs.IsPathExist(service.Dst); !exist {
|
||||
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
|
||||
return serializer.Err(serializer.CodeParentNotExist, "", nil)
|
||||
}
|
||||
|
||||
// 压缩包是否存在
|
||||
exist, file := fs.IsFileExist(service.Src)
|
||||
if !exist {
|
||||
return serializer.Err(serializer.CodeNotFound, "文件不存在", nil)
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", nil)
|
||||
}
|
||||
|
||||
// 文件尺寸限制
|
||||
if fs.User.Group.OptionsSerialized.DecompressSize != 0 && file.Size > fs.User.Group.
|
||||
OptionsSerialized.DecompressSize {
|
||||
return serializer.Err(serializer.CodeParamErr, "文件太大", nil)
|
||||
return serializer.Err(serializer.CodeFileTooLarge, "", nil)
|
||||
}
|
||||
|
||||
// 支持的压缩格式后缀
|
||||
|
@ -140,13 +140,13 @@ func (service *ItemDecompressService) CreateDecompressTask(c *gin.Context) seria
|
|||
}
|
||||
}
|
||||
if !matched {
|
||||
return serializer.Err(serializer.CodeParamErr, "不支持该格式的压缩文件", nil)
|
||||
return serializer.Err(serializer.CodeUnsupportedArchiveType, "", nil)
|
||||
}
|
||||
|
||||
// 创建任务
|
||||
job, err := task.NewDecompressTask(fs.User, service.Src, service.Dst, service.Encoding)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
|
||||
return serializer.Err(serializer.CodeCreateTaskError, "", err)
|
||||
}
|
||||
task.TaskPoll.Submit(job)
|
||||
|
||||
|
@ -159,13 +159,13 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 检查用户组权限
|
||||
if !fs.User.Group.OptionsSerialized.ArchiveTask {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 补齐压缩文件扩展名(如果没有)
|
||||
|
@ -175,30 +175,30 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize
|
|||
|
||||
// 存放目录是否存在,是否重名
|
||||
if exist, _ := fs.IsPathExist(service.Dst); !exist {
|
||||
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
|
||||
return serializer.Err(serializer.CodeParentNotExist, "", nil)
|
||||
}
|
||||
if exist, _ := fs.IsFileExist(path.Join(service.Dst, service.Name)); exist {
|
||||
return serializer.ParamErr("名为 "+service.Name+" 的文件已存在", nil)
|
||||
return serializer.ParamErr("File "+service.Name+" already exist", nil)
|
||||
}
|
||||
|
||||
// 检查文件名合法性
|
||||
if !fs.ValidateLegalName(context.Background(), service.Name) {
|
||||
return serializer.ParamErr("文件名非法", nil)
|
||||
return serializer.Err(serializer.CodeIllegalObjectName, "", nil)
|
||||
}
|
||||
if !fs.ValidateExtension(context.Background(), service.Name) {
|
||||
return serializer.ParamErr("不允许存储此扩展名的文件", nil)
|
||||
return serializer.Err(serializer.CodeFileTypeNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 递归列出待压缩子目录
|
||||
folders, err := model.GetRecursiveChildFolder(service.Src.Raw().Dirs, fs.User.ID, true)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "无法列出子目录", err)
|
||||
return serializer.DBErr("Failed to list folders", err)
|
||||
}
|
||||
|
||||
// 列出所有待压缩文件
|
||||
files, err := model.GetChildFilesOfFolders(&folders)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "无法列出子文件", err)
|
||||
return serializer.DBErr("Failed to list files", err)
|
||||
}
|
||||
|
||||
// 计算待压缩文件大小
|
||||
|
@ -210,21 +210,21 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize
|
|||
// 文件尺寸限制
|
||||
if fs.User.Group.OptionsSerialized.CompressSize != 0 && totalSize > fs.User.Group.
|
||||
OptionsSerialized.CompressSize {
|
||||
return serializer.Err(serializer.CodeParamErr, "文件太大", nil)
|
||||
return serializer.Err(serializer.CodeFileTooLarge, "", nil)
|
||||
}
|
||||
|
||||
// 按照平均压缩率计算用户空间是否足够
|
||||
compressRatio := 0.4
|
||||
spaceNeeded := uint64(math.Round(float64(totalSize) * compressRatio))
|
||||
if fs.User.GetRemainingCapacity() < spaceNeeded {
|
||||
return serializer.Err(serializer.CodeParamErr, "剩余空间不足", err)
|
||||
return serializer.Err(serializer.CodeInsufficientCapacity, "", err)
|
||||
}
|
||||
|
||||
// 创建任务
|
||||
job, err := task.NewCompressTask(fs.User, path.Join(service.Dst, service.Name), service.Src.Raw().Dirs,
|
||||
service.Src.Raw().Items)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
|
||||
return serializer.Err(serializer.CodeCreateTaskError, "", err)
|
||||
}
|
||||
task.TaskPoll.Submit(job)
|
||||
|
||||
|
@ -237,13 +237,13 @@ func (service *ItemIDService) Archive(ctx context.Context, c *gin.Context) seria
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 检查用户组权限
|
||||
if !fs.User.Group.OptionsSerialized.ArchiveDownload {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 创建打包下载会话
|
||||
|
@ -290,7 +290,7 @@ func (service *ItemMoveService) Move(ctx context.Context, c *gin.Context) serial
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -311,13 +311,13 @@ func (service *ItemMoveService) Move(ctx context.Context, c *gin.Context) serial
|
|||
func (service *ItemMoveService) Copy(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
// 复制操作只能对一个目录或文件对象进行操作
|
||||
if len(service.Src.Items)+len(service.Src.Dirs) > 1 {
|
||||
return serializer.ParamErr("只能复制一个对象", nil)
|
||||
return filesystem.ErrOneObjectOnly
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -337,13 +337,13 @@ func (service *ItemMoveService) Copy(ctx context.Context, c *gin.Context) serial
|
|||
func (service *ItemRenameService) Rename(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
// 重命名作只能对一个目录或文件对象进行操作
|
||||
if len(service.Src.Items)+len(service.Src.Dirs) > 1 {
|
||||
return serializer.ParamErr("只能操作一个对象", nil)
|
||||
return filesystem.ErrOneObjectOnly
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -370,12 +370,12 @@ func (service *ItemPropertyService) GetProperty(ctx context.Context, c *gin.Cont
|
|||
if !service.IsFolder {
|
||||
res, err := hashid.DecodeHashID(service.ID, hashid.FileID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "对象不存在", err)
|
||||
return serializer.Err(serializer.CodeNotFound, "", err)
|
||||
}
|
||||
|
||||
file, err := model.GetFilesByIDs([]uint{res}, user.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("找不到文件", err)
|
||||
return serializer.DBErr("Failed to query file records", err)
|
||||
}
|
||||
|
||||
props.CreatedAt = file[0].CreatedAt
|
||||
|
@ -387,11 +387,11 @@ func (service *ItemPropertyService) GetProperty(ctx context.Context, c *gin.Cont
|
|||
if service.TraceRoot {
|
||||
parent, err := model.GetFoldersByIDs([]uint{file[0].FolderID}, user.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("找不到父目录", err)
|
||||
return serializer.DBErr("Parent folder record not exist", err)
|
||||
}
|
||||
|
||||
if err := parent[0].TraceRoot(); err != nil {
|
||||
return serializer.DBErr("无法溯源父目录", err)
|
||||
return serializer.DBErr("Failed to trace root folder", err)
|
||||
}
|
||||
|
||||
props.Path = path.Join(parent[0].Position, parent[0].Name)
|
||||
|
@ -399,12 +399,12 @@ func (service *ItemPropertyService) GetProperty(ctx context.Context, c *gin.Cont
|
|||
} else {
|
||||
res, err := hashid.DecodeHashID(service.ID, hashid.FolderID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "对象不存在", err)
|
||||
return serializer.Err(serializer.CodeNotFound, "", err)
|
||||
}
|
||||
|
||||
folder, err := model.GetFoldersByIDs([]uint{res}, user.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("找不到目录", err)
|
||||
return serializer.DBErr("Failed to query folder records", err)
|
||||
}
|
||||
|
||||
props.CreatedAt = folder[0].CreatedAt
|
||||
|
@ -422,14 +422,14 @@ func (service *ItemPropertyService) GetProperty(ctx context.Context, c *gin.Cont
|
|||
childFolders, err := model.GetRecursiveChildFolder([]uint{folder[0].ID},
|
||||
user.ID, true)
|
||||
if err != nil {
|
||||
return serializer.DBErr("无法列取子目录", err)
|
||||
return serializer.DBErr("Failed to list child folders", err)
|
||||
}
|
||||
props.ChildFolderNum = len(childFolders) - 1
|
||||
|
||||
// 统计子文件
|
||||
files, err := model.GetChildFilesOfFolders(&childFolders)
|
||||
if err != nil {
|
||||
return serializer.DBErr("无法列取子文件", err)
|
||||
return serializer.DBErr("Failed to list child files", err)
|
||||
}
|
||||
|
||||
// 统计子文件个数和大小
|
||||
|
@ -441,7 +441,7 @@ func (service *ItemPropertyService) GetProperty(ctx context.Context, c *gin.Cont
|
|||
// 查找父目录
|
||||
if service.TraceRoot {
|
||||
if err := folder[0].TraceRoot(); err != nil {
|
||||
return serializer.DBErr("无法溯源父目录", err)
|
||||
return serializer.DBErr("Failed to list child folders", err)
|
||||
}
|
||||
|
||||
props.Path = folder[0].Position
|
||||
|
|
|
@ -23,14 +23,14 @@ func (service *ItemSearchService) Search(c *gin.Context) serializer.Response {
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
if service.Path != "" {
|
||||
ok, parent := fs.IsPathExist(service.Path)
|
||||
if !ok {
|
||||
return serializer.Err(serializer.CodeParentNotExist, "Cannot find parent folder", nil)
|
||||
return serializer.Err(serializer.CodeParentNotExist, "", nil)
|
||||
}
|
||||
|
||||
fs.Root = parent
|
||||
|
@ -60,9 +60,9 @@ func (service *ItemSearchService) Search(c *gin.Context) serializer.Response {
|
|||
}
|
||||
}
|
||||
}
|
||||
return serializer.Err(serializer.CodeNotFound, "标签不存在", nil)
|
||||
return serializer.Err(serializer.CodeNotFound, "", nil)
|
||||
default:
|
||||
return serializer.ParamErr("未知搜索类型", nil)
|
||||
return serializer.ParamErr("Unknown search type", nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
|
@ -16,9 +20,6 @@ import (
|
|||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SlaveDownloadService 从机文件下載服务
|
||||
|
@ -49,14 +50,14 @@ func (service *SlaveDownloadService) ServeFile(ctx context.Context, c *gin.Conte
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 解码文件路径
|
||||
fileSource, err := base64.RawURLEncoding.DecodeString(service.PathEncoded)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无法解析的文件地址", err)
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", err)
|
||||
}
|
||||
|
||||
// 根据URL里的信息创建一个文件对象和用户对象
|
||||
|
@ -97,7 +98,7 @@ func (service *SlaveFilesService) Delete(ctx context.Context, c *gin.Context) se
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
@ -110,7 +111,7 @@ func (service *SlaveFilesService) Delete(ctx context.Context, c *gin.Context) se
|
|||
return serializer.Response{
|
||||
Code: serializer.CodeNotFullySuccess,
|
||||
Data: string(data),
|
||||
Msg: fmt.Sprintf("有 %d 个文件未能成功删除", len(failed)),
|
||||
Msg: fmt.Sprintf("Failed to delete %d files(s)", len(failed)),
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
|
@ -122,21 +123,21 @@ func (service *SlaveFileService) Thumb(ctx context.Context, c *gin.Context) seri
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 解码文件路径
|
||||
fileSource, err := base64.RawURLEncoding.DecodeString(service.PathEncoded)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("无法解析的文件地址", err)
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", err)
|
||||
}
|
||||
fs.FileTarget = []model.File{{SourceName: string(fileSource), PicInfo: "1,1"}}
|
||||
|
||||
// 获取缩略图
|
||||
resp, err := fs.GetThumb(ctx, 0)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "无法获取缩略图", err)
|
||||
return serializer.Err(serializer.CodeNotSet, "Failed to get thumb", err)
|
||||
}
|
||||
|
||||
defer resp.Content.Close()
|
||||
|
@ -156,7 +157,7 @@ func CreateTransferTask(c *gin.Context, req *serializer.SlaveTransferReq) serial
|
|||
if err := cluster.DefaultController.SubmitTask(job.MasterID, job, req.Hash(job.MasterID), func(job interface{}) {
|
||||
task.TaskPoll.Submit(job.(task.Job))
|
||||
}); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "任务创建失败", err)
|
||||
return serializer.Err(serializer.CodeCreateTaskError, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
|
|
|
@ -32,7 +32,7 @@ type TagService struct {
|
|||
func (service *TagService) Delete(c *gin.Context, user *model.User) serializer.Response {
|
||||
id, _ := c.Get("object_id")
|
||||
if err := model.DeleteTagByID(id.(uint), user.ID); err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "Failed to delete a tag", err)
|
||||
return serializer.DBErr("Failed to delete a tag", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func (service *LinkTagCreateService) Create(c *gin.Context, user *model.User) se
|
|||
}
|
||||
id, err := tag.Create()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "Failed to create a tag", err)
|
||||
return serializer.DBErr("Failed to create a tag", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
|
@ -79,7 +79,7 @@ func (service *FilterTagCreateService) Create(c *gin.Context, user *model.User)
|
|||
}
|
||||
id, err := tag.Create()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "Failed to create a tag", err)
|
||||
return serializer.DBErr("Failed to create a tag", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
|
|
|
@ -33,13 +33,13 @@ func (service *CreateUploadSessionService) Create(ctx context.Context, c *gin.Co
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
|
||||
// 取得存储策略的ID
|
||||
rawID, err := hashid.DecodeHashID(service.PolicyID, hashid.PolicyID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
if fs.Policy.ID != rawID {
|
||||
|
@ -77,7 +77,7 @@ type UploadService struct {
|
|||
func (service *UploadService) LocalUpload(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
uploadSessionRaw, ok := cache.Get(filesystem.UploadSessionCachePrefix + service.ID)
|
||||
if !ok {
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "LocalUpload session expired or not exist", nil)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "", nil)
|
||||
}
|
||||
|
||||
uploadSession := uploadSessionRaw.(serializer.UploadSession)
|
||||
|
@ -88,23 +88,23 @@ func (service *UploadService) LocalUpload(ctx context.Context, c *gin.Context) s
|
|||
}
|
||||
|
||||
if uploadSession.UID != fs.User.ID {
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "Local upload session expired or not exist", nil)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "", nil)
|
||||
}
|
||||
|
||||
// 查找上传会话创建的占位文件
|
||||
file, err := model.GetFilesByUploadSession(service.ID, fs.User.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "Local upload session file placeholder not exist", err)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "", err)
|
||||
}
|
||||
|
||||
// 重设 fs 存储策略
|
||||
if !uploadSession.Policy.IsTransitUpload(uploadSession.Size) {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "Storage policy not supported", err)
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "", err)
|
||||
}
|
||||
|
||||
fs.Policy = &uploadSession.Policy
|
||||
if err := fs.DispatchHandler(); err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "Unknown storage policy", err)
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
expectedSizeStart := file.Size
|
||||
|
@ -118,7 +118,7 @@ func (service *UploadService) LocalUpload(ctx context.Context, c *gin.Context) s
|
|||
}
|
||||
|
||||
if expectedSizeStart > actualSizeStart {
|
||||
util.Log().Info("尝试上传覆盖分片[%d] Start=%d", service.Index, actualSizeStart)
|
||||
util.Log().Info("Trying to overwrite chunk[%d] Start=%d", service.Index, actualSizeStart)
|
||||
}
|
||||
|
||||
return processChunkUpload(ctx, c, fs, &uploadSession, service.Index, file, fsctx.Append)
|
||||
|
@ -128,14 +128,14 @@ func (service *UploadService) LocalUpload(ctx context.Context, c *gin.Context) s
|
|||
func (service *UploadService) SlaveUpload(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
uploadSessionRaw, ok := cache.Get(filesystem.UploadSessionCachePrefix + service.ID)
|
||||
if !ok {
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "Slave upload session expired or not exist", nil)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "", nil)
|
||||
}
|
||||
|
||||
uploadSession := uploadSessionRaw.(serializer.UploadSession)
|
||||
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
|
||||
fs.Handler = local.Driver{}
|
||||
|
@ -226,14 +226,14 @@ func (service *UploadSessionService) Delete(ctx context.Context, c *gin.Context)
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 查找需要删除的上传会话的占位文件
|
||||
file, err := model.GetFilesByUploadSession(service.ID, fs.User.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "Local Upload session file placeholder not exist", err)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "", err)
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
|
@ -249,13 +249,13 @@ func (service *UploadSessionService) SlaveDelete(ctx context.Context, c *gin.Con
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
session, ok := cache.Get(filesystem.UploadSessionCachePrefix + service.ID)
|
||||
if !ok {
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "Slave Upload session file placeholder not exist", nil)
|
||||
return serializer.Err(serializer.CodeUploadSessionExpired, "", nil)
|
||||
}
|
||||
|
||||
if _, err := fs.Handler.Delete(ctx, []string{session.(serializer.UploadSession).SavePath}); err != nil {
|
||||
|
@ -271,7 +271,7 @@ func DeleteAllUploadSession(ctx context.Context, c *gin.Context) serializer.Resp
|
|||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue