feat(archive): archive manage (#7817)
* feat(archive): archive management * fix(ftp-server): remove duplicate ReadAtSeeker realization * fix(archive): bad seeking of SeekableStream * fix(archive): split internal and driver extraction api * feat(archive): patch * fix(shutdown): clear decompress upload tasks * chore * feat(archive): support .iso format * chore
This commit is contained in:
126
internal/archive/archives/archives.go
Normal file
126
internal/archive/archives/archives.go
Normal file
@ -0,0 +1,126 @@
|
||||
package archives
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/archive/tool"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Archives struct {
|
||||
}
|
||||
|
||||
func (_ *Archives) AcceptedExtensions() []string {
|
||||
return []string{
|
||||
".br", ".bz2", ".gz", ".lz4", ".lz", ".sz", ".s2", ".xz", ".zz", ".zst", ".tar", ".rar", ".7z",
|
||||
}
|
||||
}
|
||||
|
||||
func (_ *Archives) GetMeta(ss *stream.SeekableStream, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
||||
fsys, err := getFs(ss, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = fsys.ReadDir(".")
|
||||
if err != nil {
|
||||
return nil, filterPassword(err)
|
||||
}
|
||||
return &model.ArchiveMetaInfo{
|
||||
Comment: "",
|
||||
Encrypted: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (_ *Archives) List(ss *stream.SeekableStream, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
||||
fsys, err := getFs(ss, args.ArchiveArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
innerPath := strings.TrimPrefix(args.InnerPath, "/")
|
||||
if innerPath == "" {
|
||||
innerPath = "."
|
||||
}
|
||||
obj, err := fsys.ReadDir(innerPath)
|
||||
if err != nil {
|
||||
return nil, filterPassword(err)
|
||||
}
|
||||
return utils.SliceConvert(obj, func(src os.DirEntry) (model.Obj, error) {
|
||||
info, err := src.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toModelObj(info), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (_ *Archives) Extract(ss *stream.SeekableStream, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
|
||||
fsys, err := getFs(ss, args.ArchiveArgs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
file, err := fsys.Open(strings.TrimPrefix(args.InnerPath, "/"))
|
||||
if err != nil {
|
||||
return nil, 0, filterPassword(err)
|
||||
}
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, 0, filterPassword(err)
|
||||
}
|
||||
return file, stat.Size(), nil
|
||||
}
|
||||
|
||||
func (_ *Archives) Decompress(ss *stream.SeekableStream, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error {
|
||||
fsys, err := getFs(ss, args.ArchiveArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isDir := false
|
||||
path := strings.TrimPrefix(args.InnerPath, "/")
|
||||
if path == "" {
|
||||
isDir = true
|
||||
path = "."
|
||||
} else {
|
||||
stat, err := fsys.Stat(path)
|
||||
if err != nil {
|
||||
return filterPassword(err)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
isDir = true
|
||||
outputPath = stdpath.Join(outputPath, stat.Name())
|
||||
err = os.Mkdir(outputPath, 0700)
|
||||
if err != nil {
|
||||
return filterPassword(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if isDir {
|
||||
err = fs.WalkDir(fsys, path, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath := strings.TrimPrefix(p, path+"/")
|
||||
dstPath := stdpath.Join(outputPath, relPath)
|
||||
if d.IsDir() {
|
||||
err = os.MkdirAll(dstPath, 0700)
|
||||
} else {
|
||||
dir := stdpath.Dir(dstPath)
|
||||
err = decompress(fsys, p, dir, func(_ float64) {})
|
||||
}
|
||||
return err
|
||||
})
|
||||
} else {
|
||||
err = decompress(fsys, path, outputPath, up)
|
||||
}
|
||||
return filterPassword(err)
|
||||
}
|
||||
|
||||
var _ tool.Tool = (*Archives)(nil)
|
||||
|
||||
func init() {
|
||||
tool.RegisterTool(&Archives{})
|
||||
}
|
80
internal/archive/archives/utils.go
Normal file
80
internal/archive/archives/utils.go
Normal file
@ -0,0 +1,80 @@
|
||||
package archives
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"github.com/mholt/archives"
|
||||
"io"
|
||||
fs2 "io/fs"
|
||||
"os"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getFs(ss *stream.SeekableStream, args model.ArchiveArgs) (*archives.ArchiveFS, error) {
|
||||
reader, err := stream.NewReadAtSeeker(ss, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
format, _, err := archives.Identify(ss.Ctx, ss.GetName(), reader)
|
||||
if err != nil {
|
||||
return nil, errs.UnknownArchiveFormat
|
||||
}
|
||||
extractor, ok := format.(archives.Extractor)
|
||||
if !ok {
|
||||
return nil, errs.UnknownArchiveFormat
|
||||
}
|
||||
switch f := format.(type) {
|
||||
case archives.SevenZip:
|
||||
f.Password = args.Password
|
||||
case archives.Rar:
|
||||
f.Password = args.Password
|
||||
}
|
||||
return &archives.ArchiveFS{
|
||||
Stream: io.NewSectionReader(reader, 0, ss.GetSize()),
|
||||
Format: extractor,
|
||||
Context: ss.Ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toModelObj(file os.FileInfo) *model.Object {
|
||||
return &model.Object{
|
||||
Name: file.Name(),
|
||||
Size: file.Size(),
|
||||
Modified: file.ModTime(),
|
||||
IsFolder: file.IsDir(),
|
||||
}
|
||||
}
|
||||
|
||||
func filterPassword(err error) error {
|
||||
if err != nil && strings.Contains(err.Error(), "password") {
|
||||
return errs.WrongArchivePassword
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func decompress(fsys fs2.FS, filePath, targetPath string, up model.UpdateProgress) error {
|
||||
rc, err := fsys.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
stat, err := rc.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(stdpath.Join(targetPath, stat.Name()), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, &stream.ReaderUpdatingProgress{
|
||||
Reader: &stream.SimpleReaderWithSize{
|
||||
Reader: rc,
|
||||
Size: stat.Size(),
|
||||
},
|
||||
UpdateProgress: up,
|
||||
})
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user