Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/internal/archive/rardecode/utils.go
1562 views
1
package rardecode
2
3
import (
4
"fmt"
5
"github.com/alist-org/alist/v3/internal/archive/tool"
6
"github.com/alist-org/alist/v3/internal/errs"
7
"github.com/alist-org/alist/v3/internal/model"
8
"github.com/alist-org/alist/v3/internal/stream"
9
"github.com/nwaples/rardecode/v2"
10
"io"
11
"io/fs"
12
"os"
13
stdpath "path"
14
"sort"
15
"strings"
16
"time"
17
)
18
19
type VolumeFile struct {
20
stream.SStreamReadAtSeeker
21
name string
22
}
23
24
func (v *VolumeFile) Name() string {
25
return v.name
26
}
27
28
func (v *VolumeFile) Size() int64 {
29
return v.SStreamReadAtSeeker.GetRawStream().GetSize()
30
}
31
32
func (v *VolumeFile) Mode() fs.FileMode {
33
return 0644
34
}
35
36
func (v *VolumeFile) ModTime() time.Time {
37
return v.SStreamReadAtSeeker.GetRawStream().ModTime()
38
}
39
40
func (v *VolumeFile) IsDir() bool {
41
return false
42
}
43
44
func (v *VolumeFile) Sys() any {
45
return nil
46
}
47
48
func (v *VolumeFile) Stat() (fs.FileInfo, error) {
49
return v, nil
50
}
51
52
func (v *VolumeFile) Close() error {
53
return nil
54
}
55
56
type VolumeFs struct {
57
parts map[string]*VolumeFile
58
}
59
60
func (v *VolumeFs) Open(name string) (fs.File, error) {
61
file, ok := v.parts[name]
62
if !ok {
63
return nil, fs.ErrNotExist
64
}
65
return file, nil
66
}
67
68
func makeOpts(ss []*stream.SeekableStream) (string, rardecode.Option, error) {
69
if len(ss) == 1 {
70
reader, err := stream.NewReadAtSeeker(ss[0], 0)
71
if err != nil {
72
return "", nil, err
73
}
74
fileName := "file.rar"
75
fsys := &VolumeFs{parts: map[string]*VolumeFile{
76
fileName: {SStreamReadAtSeeker: reader, name: fileName},
77
}}
78
return fileName, rardecode.FileSystem(fsys), nil
79
} else {
80
parts := make(map[string]*VolumeFile, len(ss))
81
for i, s := range ss {
82
reader, err := stream.NewReadAtSeeker(s, 0)
83
if err != nil {
84
return "", nil, err
85
}
86
fileName := fmt.Sprintf("file.part%d.rar", i+1)
87
parts[fileName] = &VolumeFile{SStreamReadAtSeeker: reader, name: fileName}
88
}
89
return "file.part1.rar", rardecode.FileSystem(&VolumeFs{parts: parts}), nil
90
}
91
}
92
93
type WrapReader struct {
94
files []*rardecode.File
95
}
96
97
func (r *WrapReader) Files() []tool.SubFile {
98
ret := make([]tool.SubFile, 0, len(r.files))
99
for _, f := range r.files {
100
ret = append(ret, &WrapFile{File: f})
101
}
102
return ret
103
}
104
105
type WrapFile struct {
106
*rardecode.File
107
}
108
109
func (f *WrapFile) Name() string {
110
if f.File.IsDir {
111
return f.File.Name + "/"
112
}
113
return f.File.Name
114
}
115
116
func (f *WrapFile) FileInfo() fs.FileInfo {
117
return &WrapFileInfo{File: f.File}
118
}
119
120
type WrapFileInfo struct {
121
*rardecode.File
122
}
123
124
func (f *WrapFileInfo) Name() string {
125
return stdpath.Base(f.File.Name)
126
}
127
128
func (f *WrapFileInfo) Size() int64 {
129
return f.File.UnPackedSize
130
}
131
132
func (f *WrapFileInfo) ModTime() time.Time {
133
return f.File.ModificationTime
134
}
135
136
func (f *WrapFileInfo) IsDir() bool {
137
return f.File.IsDir
138
}
139
140
func (f *WrapFileInfo) Sys() any {
141
return nil
142
}
143
144
func list(ss []*stream.SeekableStream, password string) (*WrapReader, error) {
145
fileName, fsOpt, err := makeOpts(ss)
146
if err != nil {
147
return nil, err
148
}
149
opts := []rardecode.Option{fsOpt}
150
if password != "" {
151
opts = append(opts, rardecode.Password(password))
152
}
153
files, err := rardecode.List(fileName, opts...)
154
// rardecode输出文件列表的顺序不一定是父目录在前,子目录在后
155
// 父路径的长度一定比子路径短,排序后的files可保证父路径在前
156
sort.Slice(files, func(i, j int) bool {
157
return len(files[i].Name) < len(files[j].Name)
158
})
159
if err != nil {
160
return nil, filterPassword(err)
161
}
162
return &WrapReader{files: files}, nil
163
}
164
165
func getReader(ss []*stream.SeekableStream, password string) (*rardecode.Reader, error) {
166
fileName, fsOpt, err := makeOpts(ss)
167
if err != nil {
168
return nil, err
169
}
170
opts := []rardecode.Option{fsOpt}
171
if password != "" {
172
opts = append(opts, rardecode.Password(password))
173
}
174
rc, err := rardecode.OpenReader(fileName, opts...)
175
if err != nil {
176
return nil, filterPassword(err)
177
}
178
ss[0].Closers.Add(rc)
179
return &rc.Reader, nil
180
}
181
182
func decompress(reader *rardecode.Reader, header *rardecode.FileHeader, filePath, outputPath string) error {
183
targetPath := outputPath
184
dir, base := stdpath.Split(filePath)
185
if dir != "" {
186
targetPath = stdpath.Join(targetPath, dir)
187
err := os.MkdirAll(targetPath, 0700)
188
if err != nil {
189
return err
190
}
191
}
192
if base != "" {
193
err := _decompress(reader, header, targetPath, func(_ float64) {})
194
if err != nil {
195
return err
196
}
197
}
198
return nil
199
}
200
201
func _decompress(reader *rardecode.Reader, header *rardecode.FileHeader, targetPath string, up model.UpdateProgress) error {
202
f, err := os.OpenFile(stdpath.Join(targetPath, stdpath.Base(header.Name)), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
203
if err != nil {
204
return err
205
}
206
defer func() { _ = f.Close() }()
207
_, err = io.Copy(f, &stream.ReaderUpdatingProgress{
208
Reader: &stream.SimpleReaderWithSize{
209
Reader: reader,
210
Size: header.UnPackedSize,
211
},
212
UpdateProgress: up,
213
})
214
if err != nil {
215
return err
216
}
217
return nil
218
}
219
220
func filterPassword(err error) error {
221
if err != nil && strings.Contains(err.Error(), "password") {
222
return errs.WrongArchivePassword
223
}
224
return err
225
}
226
227