|
package handles |
|
|
|
import ( |
|
"fmt" |
|
"io" |
|
stdpath "path" |
|
|
|
"github.com/alist-org/alist/v3/pkg/tache" |
|
|
|
"github.com/alist-org/alist/v3/internal/errs" |
|
"github.com/alist-org/alist/v3/internal/fs" |
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/internal/op" |
|
"github.com/alist-org/alist/v3/internal/sign" |
|
"github.com/alist-org/alist/v3/pkg/generic" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
"github.com/alist-org/alist/v3/server/common" |
|
"github.com/gin-gonic/gin" |
|
"github.com/pkg/errors" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
type MkdirOrLinkReq struct { |
|
Path string `json:"path" form:"path"` |
|
} |
|
|
|
func FsMkdir(c *gin.Context) { |
|
var req MkdirOrLinkReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
user := c.MustGet("user").(*model.User) |
|
reqPath, err := user.JoinPath(req.Path) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
if !user.CanWrite() { |
|
meta, err := op.GetNearestMeta(stdpath.Dir(reqPath)) |
|
if err != nil { |
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) { |
|
common.ErrorResp(c, err, 500, true) |
|
return |
|
} |
|
} |
|
if !common.CanWrite(meta, reqPath) { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
} |
|
if err := fs.MakeDir(c, reqPath); err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
common.SuccessResp(c) |
|
} |
|
|
|
type MoveCopyReq struct { |
|
SrcDir string `json:"src_dir"` |
|
DstDir string `json:"dst_dir"` |
|
Override bool `json:"override"` |
|
Names []string `json:"names"` |
|
} |
|
|
|
func FsMove(c *gin.Context) { |
|
var req MoveCopyReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
if len(req.Names) == 0 { |
|
common.ErrorStrResp(c, "Empty file names", 400) |
|
return |
|
} |
|
user := c.MustGet("user").(*model.User) |
|
if !user.CanMove() { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
srcDir, err := user.JoinPath(req.SrcDir) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
dstDir, err := user.JoinPath(req.DstDir) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
for i, name := range req.Names { |
|
err := fs.Move(c, stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1) |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
} |
|
common.SuccessResp(c) |
|
} |
|
|
|
func FsCopy(c *gin.Context) { |
|
var req MoveCopyReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
if len(req.Names) == 0 { |
|
common.ErrorStrResp(c, "Empty file names", 400) |
|
return |
|
} |
|
user := c.MustGet("user").(*model.User) |
|
if !user.CanCopy() { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
srcDir, err := user.JoinPath(req.SrcDir) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
dstDir, err := user.JoinPath(req.DstDir) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
var addedTasks []tache.TaskWithInfo |
|
for i, name := range req.Names { |
|
t, err := fs.Copy(c, stdpath.Join(srcDir, name), dstDir, req.Override, len(req.Names) > i+1) |
|
if t != nil { |
|
addedTasks = append(addedTasks, t) |
|
} |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
} |
|
common.SuccessResp(c, gin.H{ |
|
"tasks": getTaskInfos(addedTasks), |
|
}) |
|
} |
|
|
|
|
|
|
|
|
|
type CopyItem struct { |
|
SrcFile string `json:"src_file"` |
|
DstDir string `json:"dst_dir"` |
|
} |
|
type CopyItemReq struct { |
|
Override bool `json:"override"` |
|
Names []CopyItem `json:"names"` |
|
} |
|
|
|
func FsCopyItem(c *gin.Context) { |
|
var req CopyItemReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
if len(req.Names) == 0 { |
|
common.ErrorStrResp(c, "Empty file names", 400) |
|
return |
|
} |
|
user := c.MustGet("user").(*model.User) |
|
if !user.CanCopy() { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var addedTasks []tache.TaskWithInfo |
|
for i, name := range req.Names { |
|
t, err := fs.Copy(c, name.SrcFile, name.DstDir, req.Override, len(req.Names) > i+1) |
|
if t != nil { |
|
addedTasks = append(addedTasks, t) |
|
} |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
} |
|
common.SuccessResp(c, gin.H{ |
|
"tasks": getTaskInfos(addedTasks), |
|
}) |
|
} |
|
|
|
type RenameReq struct { |
|
Path string `json:"path"` |
|
Name string `json:"name"` |
|
} |
|
|
|
func FsRename(c *gin.Context) { |
|
var req RenameReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
user := c.MustGet("user").(*model.User) |
|
if !user.CanRename() { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
reqPath, err := user.JoinPath(req.Path) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
if err := fs.Rename(c, reqPath, req.Name); err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
common.SuccessResp(c) |
|
} |
|
|
|
type RemoveReq struct { |
|
Dir string `json:"dir"` |
|
Names []string `json:"names"` |
|
} |
|
|
|
func FsRemove(c *gin.Context) { |
|
var req RemoveReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
if len(req.Names) == 0 { |
|
common.ErrorStrResp(c, "Empty file names", 400) |
|
return |
|
} |
|
user := c.MustGet("user").(*model.User) |
|
if !user.CanRemove() { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
reqDir, err := user.JoinPath(req.Dir) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
for _, name := range req.Names { |
|
err := fs.Remove(c, stdpath.Join(reqDir, name)) |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
} |
|
|
|
common.SuccessResp(c) |
|
} |
|
|
|
type RemoveEmptyDirectoryReq struct { |
|
SrcDir string `json:"src_dir"` |
|
} |
|
|
|
func FsRemoveEmptyDirectory(c *gin.Context) { |
|
var req RemoveEmptyDirectoryReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
|
|
user := c.MustGet("user").(*model.User) |
|
if !user.CanRemove() { |
|
common.ErrorResp(c, errs.PermissionDenied, 403) |
|
return |
|
} |
|
srcDir, err := user.JoinPath(req.SrcDir) |
|
if err != nil { |
|
common.ErrorResp(c, err, 403) |
|
return |
|
} |
|
|
|
meta, err := op.GetNearestMeta(srcDir) |
|
if err != nil { |
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) { |
|
common.ErrorResp(c, err, 500, true) |
|
return |
|
} |
|
} |
|
c.Set("meta", meta) |
|
|
|
rootFiles, err := fs.List(c, srcDir, &fs.ListArgs{}) |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
|
|
|
|
filePathMap := make(map[model.Obj]string) |
|
|
|
fileParentMap := make(map[model.Obj]model.Obj) |
|
|
|
removingFiles := generic.NewQueue[model.Obj]() |
|
|
|
removedFiles := make(map[string]bool) |
|
for _, file := range rootFiles { |
|
if !file.IsDir() { |
|
continue |
|
} |
|
removingFiles.Push(file) |
|
filePathMap[file] = srcDir |
|
} |
|
|
|
for !removingFiles.IsEmpty() { |
|
|
|
removingFile := removingFiles.Pop() |
|
removingFilePath := fmt.Sprintf("%s/%s", filePathMap[removingFile], removingFile.GetName()) |
|
|
|
if removedFiles[removingFilePath] { |
|
continue |
|
} |
|
|
|
subFiles, err := fs.List(c, removingFilePath, &fs.ListArgs{Refresh: true}) |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
|
|
if len(subFiles) == 0 { |
|
|
|
err = fs.Remove(c, removingFilePath) |
|
removedFiles[removingFilePath] = true |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
|
|
parentFile, exist := fileParentMap[removingFile] |
|
if exist { |
|
removingFiles.Push(parentFile) |
|
} |
|
|
|
} else { |
|
|
|
for _, subFile := range subFiles { |
|
if !subFile.IsDir() { |
|
continue |
|
} |
|
removingFiles.Push(subFile) |
|
filePathMap[subFile] = removingFilePath |
|
fileParentMap[subFile] = removingFile |
|
} |
|
} |
|
|
|
} |
|
|
|
common.SuccessResp(c) |
|
} |
|
|
|
|
|
func Link(c *gin.Context) { |
|
var req MkdirOrLinkReq |
|
if err := c.ShouldBind(&req); err != nil { |
|
common.ErrorResp(c, err, 400) |
|
return |
|
} |
|
|
|
|
|
|
|
rawPath := req.Path |
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{}) |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
if storage.Config().OnlyLocal { |
|
common.SuccessResp(c, model.Link{ |
|
URL: fmt.Sprintf("%s/p%s?d&sign=%s", |
|
common.GetApiUrl(c.Request), |
|
utils.EncodePath(rawPath, true), |
|
sign.Sign(rawPath)), |
|
}) |
|
return |
|
} |
|
link, _, err := fs.Link(c, rawPath, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header, HttpReq: c.Request}) |
|
if err != nil { |
|
common.ErrorResp(c, err, 500) |
|
return |
|
} |
|
if link.MFile != nil { |
|
defer func(ReadSeekCloser io.ReadCloser) { |
|
err := ReadSeekCloser.Close() |
|
if err != nil { |
|
log.Errorf("close link data error: %v", err) |
|
} |
|
}(link.MFile) |
|
} |
|
common.SuccessResp(c, link) |
|
return |
|
} |
|
|