mirror of https://github.com/1Panel-dev/1Panel
appstorecrontabdatabasedockerdocker-composedocker-containerdocker-imagedocker-uifilemanagerlamplnmppanel
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
5.1 KiB
212 lines
5.1 KiB
package service |
|
|
|
import ( |
|
"fmt" |
|
"math" |
|
"os" |
|
"path" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/1Panel-dev/1Panel/backend/app/dto" |
|
"github.com/1Panel-dev/1Panel/backend/app/dto/request" |
|
"github.com/1Panel-dev/1Panel/backend/app/dto/response" |
|
"github.com/1Panel-dev/1Panel/backend/buserr" |
|
"github.com/1Panel-dev/1Panel/backend/constant" |
|
"github.com/1Panel-dev/1Panel/backend/utils/files" |
|
"github.com/shirou/gopsutil/v3/disk" |
|
) |
|
|
|
type RecycleBinService struct { |
|
} |
|
|
|
type IRecycleBinService interface { |
|
Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error) |
|
Create(create request.RecycleBinCreate) error |
|
Reduce(reduce request.RecycleBinReduce) error |
|
Clear() error |
|
} |
|
|
|
func NewIRecycleBinService() IRecycleBinService { |
|
return &RecycleBinService{} |
|
} |
|
|
|
func (r RecycleBinService) Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error) { |
|
var ( |
|
result []response.RecycleBinDTO |
|
) |
|
partitions, err := disk.Partitions(false) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
op := files.NewFileOp() |
|
for _, p := range partitions { |
|
dir := path.Join(p.Mountpoint, ".1panel_clash") |
|
if !op.Stat(dir) { |
|
continue |
|
} |
|
clashFiles, err := os.ReadDir(dir) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
for _, file := range clashFiles { |
|
if strings.HasPrefix(file.Name(), "_1p_") { |
|
recycleDTO, err := getRecycleBinDTOFromName(file.Name()) |
|
recycleDTO.IsDir = file.IsDir() |
|
recycleDTO.From = dir |
|
if err == nil { |
|
result = append(result, *recycleDTO) |
|
} |
|
} |
|
} |
|
} |
|
startIndex := (search.Page - 1) * search.PageSize |
|
endIndex := startIndex + search.PageSize |
|
|
|
if startIndex > len(result) { |
|
return int64(len(result)), result, nil |
|
} |
|
if endIndex > len(result) { |
|
endIndex = len(result) |
|
} |
|
return int64(len(result)), result[startIndex:endIndex], nil |
|
} |
|
|
|
func (r RecycleBinService) Create(create request.RecycleBinCreate) error { |
|
op := files.NewFileOp() |
|
if !op.Stat(create.SourcePath) { |
|
return buserr.New(constant.ErrLinkPathNotFound) |
|
} |
|
clashDir, err := getClashDir(create.SourcePath) |
|
if err != nil { |
|
return err |
|
} |
|
paths := strings.Split(create.SourcePath, "/") |
|
rNamePre := strings.Join(paths, "_1p_") |
|
deleteTime := time.Now() |
|
openFile, err := op.OpenFile(create.SourcePath) |
|
if err != nil { |
|
return err |
|
} |
|
fileInfo, err := openFile.Stat() |
|
if err != nil { |
|
return err |
|
} |
|
size := 0 |
|
if fileInfo.IsDir() { |
|
sizeF, err := op.GetDirSize(create.SourcePath) |
|
if err != nil { |
|
return err |
|
} |
|
size = int(sizeF) |
|
} else { |
|
size = int(fileInfo.Size()) |
|
} |
|
|
|
rName := fmt.Sprintf("_1p_%s%s_p_%d_%d", "file", rNamePre, size, deleteTime.Unix()) |
|
return op.Mv(create.SourcePath, path.Join(clashDir, rName)) |
|
} |
|
|
|
func (r RecycleBinService) Reduce(reduce request.RecycleBinReduce) error { |
|
filePath := path.Join(reduce.From, reduce.RName) |
|
op := files.NewFileOp() |
|
if !op.Stat(filePath) { |
|
return buserr.New(constant.ErrLinkPathNotFound) |
|
} |
|
recycleBinDTO, err := getRecycleBinDTOFromName(reduce.RName) |
|
if err != nil { |
|
return err |
|
} |
|
if !op.Stat(path.Dir(recycleBinDTO.SourcePath)) { |
|
return buserr.New("ErrSourcePathNotFound") |
|
} |
|
if op.Stat(recycleBinDTO.SourcePath) { |
|
if err = op.RmRf(recycleBinDTO.SourcePath); err != nil { |
|
return err |
|
} |
|
} |
|
return op.Mv(filePath, recycleBinDTO.SourcePath) |
|
} |
|
|
|
func (r RecycleBinService) Clear() error { |
|
partitions, err := disk.Partitions(false) |
|
if err != nil { |
|
return err |
|
} |
|
op := files.NewFileOp() |
|
for _, p := range partitions { |
|
dir := path.Join(p.Mountpoint, ".1panel_clash") |
|
if !op.Stat(dir) { |
|
continue |
|
} |
|
newDir := path.Join(p.Mountpoint, "1panel_clash") |
|
if err := op.Mv(dir, newDir); err != nil { |
|
return err |
|
} |
|
go func() { |
|
_ = op.DeleteDir(newDir) |
|
}() |
|
} |
|
return nil |
|
} |
|
|
|
func getClashDir(realPath string) (string, error) { |
|
partitions, err := disk.Partitions(false) |
|
if err != nil { |
|
return "", err |
|
} |
|
for _, p := range partitions { |
|
if p.Mountpoint == "/" { |
|
continue |
|
} |
|
if strings.HasPrefix(realPath, p.Mountpoint) { |
|
clashDir := path.Join(p.Mountpoint, ".1panel_clash") |
|
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil { |
|
return "", err |
|
} |
|
return clashDir, nil |
|
} |
|
} |
|
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir) |
|
} |
|
|
|
func createClashDir(clashDir string) error { |
|
op := files.NewFileOp() |
|
if !op.Stat(clashDir) { |
|
if err := op.CreateDir(clashDir, 0755); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func getRecycleBinDTOFromName(filename string) (*response.RecycleBinDTO, error) { |
|
r := regexp.MustCompile(`_1p_file_1p_(.+)_p_(\d+)_(\d+)`) |
|
matches := r.FindStringSubmatch(filename) |
|
if len(matches) != 4 { |
|
return nil, fmt.Errorf("invalid filename format") |
|
} |
|
sourcePath := "/" + strings.ReplaceAll(matches[1], "_1p_", "/") |
|
size, err := strconv.ParseInt(matches[2], 10, 64) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if size < math.MinInt || size > math.MaxInt { |
|
return nil, fmt.Errorf("size out of int range") |
|
} |
|
|
|
deleteTime, err := strconv.ParseInt(matches[3], 10, 64) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return &response.RecycleBinDTO{ |
|
Name: path.Base(sourcePath), |
|
Size: int(size), |
|
Type: "file", |
|
DeleteTime: time.Unix(deleteTime, 0), |
|
SourcePath: sourcePath, |
|
RName: filename, |
|
}, nil |
|
}
|
|
|