Merge branch 'master' into patch-samesite

This commit is contained in:
AHdark 2022-09-29 22:44:40 +08:00 committed by GitHub
commit 6a60081a7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 1051 additions and 611 deletions

View file

@ -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

@ -1 +1 @@
Subproject commit 41f585a6f8c8f99ed4b2e279555d6b4dcdf957bc
Subproject commit 02d93206cc5b943c34b5f5ac86c23dd96f5ef603

View file

@ -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()
}
}

View file

@ -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)
}

View file

@ -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.")
}

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -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()
}
}

View file

@ -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

View file

@ -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"},

View file

@ -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

View file

@ -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

View file

@ -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{

View 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
}

View file

@ -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)
}
// 处理表前缀

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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++ {
// 创建任务监控

View file

@ -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

View file

@ -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)
}

View file

@ -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 %qexceed 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) {

View file

@ -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)

View file

@ -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.
}

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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)
)

View file

@ -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)

View file

@ -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 {

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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.")
}

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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 发送邮件

View file

@ -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
}

View file

@ -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
}

View file

@ -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 获取文件缩略图

View file

@ -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()

View file

@ -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

View file

@ -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客户端

View file

@ -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])

View file

@ -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
}

View file

@ -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")
}
// 获取公钥

View file

@ -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])

View file

@ -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()

View file

@ -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 {

View file

@ -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
}

View file

@ -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])

View file

@ -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)
)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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)
}
// 移动文件

View file

@ -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)
}
}

View file

@ -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

View file

@ -4,6 +4,7 @@ import (
"crypto/sha1"
"encoding/gob"
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
)

View file

@ -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"))
}

View file

@ -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
}

View file

@ -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
View 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
View 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)
}
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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,

View file

@ -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,
)

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)}

View file

@ -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])

View file

@ -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)
}
}

View file

@ -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}

View file

@ -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()

View file

@ -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{}
}

View file

@ -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_")

View file

@ -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{}
}

View file

@ -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)
}
}

View file

@ -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)
}
})

View file

@ -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{}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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()

View file

@ -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))

View file

@ -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

View file

@ -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)
}
}

View file

@ -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{}

View file

@ -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{

View file

@ -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