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