mirror of
https://github.com/AlistGo/alist.git
synced 2025-04-22 12:58:45 +08:00
205 lines
4.8 KiB
Go
205 lines
4.8 KiB
Go
package tool
|
|
|
|
import (
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
stdpath "path"
|
|
"strings"
|
|
|
|
"github.com/alist-org/alist/v3/internal/model"
|
|
"github.com/alist-org/alist/v3/internal/stream"
|
|
)
|
|
|
|
type SubFile interface {
|
|
Name() string
|
|
FileInfo() fs.FileInfo
|
|
Open() (io.ReadCloser, error)
|
|
}
|
|
|
|
type CanEncryptSubFile interface {
|
|
IsEncrypted() bool
|
|
SetPassword(password string)
|
|
}
|
|
|
|
type ArchiveReader interface {
|
|
Files() []SubFile
|
|
}
|
|
|
|
func GenerateMetaTreeFromFolderTraversal(r ArchiveReader) (bool, []model.ObjTree) {
|
|
encrypted := false
|
|
dirMap := make(map[string]*model.ObjectTree)
|
|
for _, file := range r.Files() {
|
|
if encrypt, ok := file.(CanEncryptSubFile); ok && encrypt.IsEncrypted() {
|
|
encrypted = true
|
|
}
|
|
|
|
name := strings.TrimPrefix(file.Name(), "/")
|
|
var dir string
|
|
var dirObj *model.ObjectTree
|
|
isNewFolder := false
|
|
if !file.FileInfo().IsDir() {
|
|
// 先将 文件 添加到 所在的文件夹
|
|
dir = stdpath.Dir(name)
|
|
dirObj = dirMap[dir]
|
|
if dirObj == nil {
|
|
isNewFolder = dir != "."
|
|
dirObj = &model.ObjectTree{}
|
|
dirObj.IsFolder = true
|
|
dirObj.Name = stdpath.Base(dir)
|
|
dirObj.Modified = file.FileInfo().ModTime()
|
|
dirMap[dir] = dirObj
|
|
}
|
|
dirObj.Children = append(
|
|
dirObj.Children, &model.ObjectTree{
|
|
Object: *MakeModelObj(file.FileInfo()),
|
|
},
|
|
)
|
|
} else {
|
|
dir = strings.TrimSuffix(name, "/")
|
|
dirObj = dirMap[dir]
|
|
if dirObj == nil {
|
|
isNewFolder = dir != "."
|
|
dirObj = &model.ObjectTree{}
|
|
dirMap[dir] = dirObj
|
|
}
|
|
dirObj.IsFolder = true
|
|
dirObj.Name = stdpath.Base(dir)
|
|
dirObj.Modified = file.FileInfo().ModTime()
|
|
}
|
|
if isNewFolder {
|
|
// 将 文件夹 添加到 父文件夹
|
|
// 考虑压缩包仅记录文件的路径,不记录文件夹
|
|
// 循环创建所有父文件夹
|
|
parentDir := stdpath.Dir(dir)
|
|
for {
|
|
parentDirObj := dirMap[parentDir]
|
|
if parentDirObj == nil {
|
|
parentDirObj = &model.ObjectTree{}
|
|
if parentDir != "." {
|
|
parentDirObj.IsFolder = true
|
|
parentDirObj.Name = stdpath.Base(parentDir)
|
|
parentDirObj.Modified = file.FileInfo().ModTime()
|
|
}
|
|
dirMap[parentDir] = parentDirObj
|
|
}
|
|
parentDirObj.Children = append(parentDirObj.Children, dirObj)
|
|
|
|
parentDir = stdpath.Dir(parentDir)
|
|
if dirMap[parentDir] != nil {
|
|
break
|
|
}
|
|
dirObj = parentDirObj
|
|
}
|
|
}
|
|
}
|
|
if len(dirMap) > 0 {
|
|
return encrypted, dirMap["."].GetChildren()
|
|
} else {
|
|
return encrypted, nil
|
|
}
|
|
}
|
|
|
|
func MakeModelObj(file os.FileInfo) *model.Object {
|
|
return &model.Object{
|
|
Name: file.Name(),
|
|
Size: file.Size(),
|
|
Modified: file.ModTime(),
|
|
IsFolder: file.IsDir(),
|
|
}
|
|
}
|
|
|
|
type WrapFileInfo struct {
|
|
model.Obj
|
|
}
|
|
|
|
func DecompressFromFolderTraversal(r ArchiveReader, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error {
|
|
var err error
|
|
files := r.Files()
|
|
if args.InnerPath == "/" {
|
|
for i, file := range files {
|
|
name := file.Name()
|
|
err = decompress(file, name, outputPath, args.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
up(float64(i+1) * 100.0 / float64(len(files)))
|
|
}
|
|
} else {
|
|
innerPath := strings.TrimPrefix(args.InnerPath, "/")
|
|
innerBase := stdpath.Base(innerPath)
|
|
createdBaseDir := false
|
|
for _, file := range files {
|
|
name := file.Name()
|
|
if name == innerPath {
|
|
err = _decompress(file, outputPath, args.Password, up)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
break
|
|
} else if strings.HasPrefix(name, innerPath+"/") {
|
|
targetPath := stdpath.Join(outputPath, innerBase)
|
|
if !createdBaseDir {
|
|
err = os.Mkdir(targetPath, 0700)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
createdBaseDir = true
|
|
}
|
|
restPath := strings.TrimPrefix(name, innerPath+"/")
|
|
err = decompress(file, restPath, targetPath, args.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func decompress(file SubFile, filePath, outputPath, password string) error {
|
|
targetPath := outputPath
|
|
dir, base := stdpath.Split(filePath)
|
|
if dir != "" {
|
|
targetPath = stdpath.Join(targetPath, dir)
|
|
err := os.MkdirAll(targetPath, 0700)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if base != "" {
|
|
err := _decompress(file, targetPath, password, func(_ float64) {})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func _decompress(file SubFile, targetPath, password string, up model.UpdateProgress) error {
|
|
if encrypt, ok := file.(CanEncryptSubFile); ok && encrypt.IsEncrypted() {
|
|
encrypt.SetPassword(password)
|
|
}
|
|
rc, err := file.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = rc.Close() }()
|
|
f, err := os.OpenFile(stdpath.Join(targetPath, file.FileInfo().Name()), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
_, err = io.Copy(f, &stream.ReaderUpdatingProgress{
|
|
Reader: &stream.SimpleReaderWithSize{
|
|
Reader: rc,
|
|
Size: file.FileInfo().Size(),
|
|
},
|
|
UpdateProgress: up,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|