@@ -42,13 +42,14 @@ func RegisterFormat(format Format) {
42
42
func Identify (ctx context.Context , filename string , stream io.Reader ) (Format , io.Reader , error ) {
43
43
var compression Compression
44
44
var archival Archival
45
+ var extraction Extraction
45
46
46
47
rewindableStream , err := newRewindReader (stream )
47
48
if err != nil {
48
49
return nil , nil , err
49
50
}
50
51
51
- // try compression format first, since that's the outer "layer"
52
+ // try compression format first, since that's the outer "layer" if combined
52
53
for name , format := range formats {
53
54
cf , isCompression := format .(Compression )
54
55
if ! isCompression {
@@ -68,10 +69,11 @@ func Identify(ctx context.Context, filename string, stream io.Reader) (Format, i
68
69
}
69
70
}
70
71
71
- // try archive format next
72
+ // try archival and extraction format next
72
73
for name , format := range formats {
73
- af , isArchive := format .(Archival )
74
- if ! isArchive {
74
+ ar , isArchive := format .(Archival )
75
+ ex , isExtract := format .(Extraction )
76
+ if ! isArchive && ! isExtract {
75
77
continue
76
78
}
77
79
@@ -81,20 +83,23 @@ func Identify(ctx context.Context, filename string, stream io.Reader) (Format, i
81
83
}
82
84
83
85
if matchResult .Matched () {
84
- archival = af
86
+ archival = ar
87
+ extraction = ex
85
88
break
86
89
}
87
90
}
88
91
89
- // the stream should be rewound by identifyOne
92
+ // the stream should be rewound by identifyOne; then return the most specific type of match
90
93
bufferedStream := rewindableStream .reader ()
91
94
switch {
92
- case compression != nil && archival == nil :
95
+ case compression != nil && archival == nil && extraction == nil :
93
96
return compression , bufferedStream , nil
94
- case compression == nil && archival != nil :
97
+ case compression == nil && archival != nil && extraction == nil :
95
98
return archival , bufferedStream , nil
96
- case compression != nil && archival != nil :
97
- return CompressedArchive {compression , archival }, bufferedStream , nil
99
+ case compression == nil && archival == nil && extraction != nil :
100
+ return extraction , bufferedStream , nil
101
+ case archival != nil || extraction != nil :
102
+ return Archive {compression , archival , extraction }, bufferedStream , nil
98
103
default :
99
104
return nil , bufferedStream , NoMatch
100
105
}
@@ -161,44 +166,44 @@ func readAtMost(stream io.Reader, n int) ([]byte, error) {
161
166
return nil , err
162
167
}
163
168
164
- // CompressedArchive combines a compression format on top of an archive
165
- // format (e.g. "tar.gz") and provides both functionalities in a single
166
- // type. It ensures that archive functions are wrapped by compressors and
169
+ // Archive represents an archive which may be compressed at the outer layer.
170
+ // It combines a compression format on top of an archive/extraction
171
+ // format (e.g. ".tar.gz") and provides both functionalities in a single
172
+ // type. It ensures that archival functions are wrapped by compressors and
167
173
// decompressors. However, compressed archives have some limitations; for
168
174
// example, files cannot be inserted/appended because of complexities with
169
175
// modifying existing compression state (perhaps this could be overcome,
170
176
// but I'm not about to try it).
171
177
//
172
- // As this type is intended to compose compression and archive formats,
173
- // both must be specified in order for this value to be valid, or its
174
- // methods will return errors .
175
- type CompressedArchive struct {
178
+ // The embedded Archival and Extraction values are used for writing and
179
+ // reading, respectively. Compression is optional and is only needed if the
180
+ // format is compressed externally (for example, tar archives) .
181
+ type Archive struct {
176
182
Compression
177
183
Archival
184
+ Extraction
178
185
}
179
186
180
- // Name returns a concatenation of the archive format name
181
- // and the compression format name.
182
- func (caf CompressedArchive ) Extension () string {
183
- if caf .Compression == nil && caf .Archival == nil {
184
- panic ("missing both compression and archive formats" )
185
- }
187
+ // Name returns a concatenation of the archive and compression format extensions.
188
+ func (ar Archive ) Extension () string {
186
189
var name string
187
- if caf .Archival != nil {
188
- name += caf .Archival .Extension ()
190
+ if ar .Archival != nil {
191
+ name += ar .Archival .Extension ()
192
+ } else if ar .Extraction != nil {
193
+ name += ar .Extraction .Extension ()
189
194
}
190
- if caf .Compression != nil {
191
- name += caf .Compression .Extension ()
195
+ if ar .Compression != nil {
196
+ name += ar .Compression .Extension ()
192
197
}
193
198
return name
194
199
}
195
200
196
- // Match matches if the input matches both the compression and archive format.
197
- func (caf CompressedArchive ) Match (ctx context.Context , filename string , stream io.Reader ) (MatchResult , error ) {
201
+ // Match matches if the input matches both the compression and archival/extraction format.
202
+ func (ar Archive ) Match (ctx context.Context , filename string , stream io.Reader ) (MatchResult , error ) {
198
203
var conglomerate MatchResult
199
204
200
- if caf .Compression != nil {
201
- matchResult , err := caf .Compression .Match (ctx , filename , stream )
205
+ if ar .Compression != nil {
206
+ matchResult , err := ar .Compression .Match (ctx , filename , stream )
202
207
if err != nil {
203
208
return MatchResult {}, err
204
209
}
@@ -208,7 +213,7 @@ func (caf CompressedArchive) Match(ctx context.Context, filename string, stream
208
213
209
214
// wrap the reader with the decompressor so we can
210
215
// attempt to match the archive by reading the stream
211
- rc , err := caf .Compression .OpenReader (stream )
216
+ rc , err := ar .Compression .OpenReader (stream )
212
217
if err != nil {
213
218
return matchResult , err
214
219
}
@@ -218,8 +223,8 @@ func (caf CompressedArchive) Match(ctx context.Context, filename string, stream
218
223
conglomerate = matchResult
219
224
}
220
225
221
- if caf .Archival != nil {
222
- matchResult , err := caf .Archival .Match (ctx , filename , stream )
226
+ if ar .Archival != nil {
227
+ matchResult , err := ar .Archival .Match (ctx , filename , stream )
223
228
if err != nil {
224
229
return MatchResult {}, err
225
230
}
@@ -234,26 +239,32 @@ func (caf CompressedArchive) Match(ctx context.Context, filename string, stream
234
239
}
235
240
236
241
// Archive adds files to the output archive while compressing the result.
237
- func (caf CompressedArchive ) Archive (ctx context.Context , output io.Writer , files []FileInfo ) error {
238
- if caf .Compression != nil {
239
- wc , err := caf .Compression .OpenWriter (output )
242
+ func (ar Archive ) Archive (ctx context.Context , output io.Writer , files []FileInfo ) error {
243
+ if ar .Archival == nil {
244
+ return fmt .Errorf ("no archival format" )
245
+ }
246
+ if ar .Compression != nil {
247
+ wc , err := ar .Compression .OpenWriter (output )
240
248
if err != nil {
241
249
return err
242
250
}
243
251
defer wc .Close ()
244
252
output = wc
245
253
}
246
- return caf .Archival .Archive (ctx , output , files )
254
+ return ar .Archival .Archive (ctx , output , files )
247
255
}
248
256
249
257
// ArchiveAsync adds files to the output archive while compressing the result asynchronously.
250
- func (caf CompressedArchive ) ArchiveAsync (ctx context.Context , output io.Writer , jobs <- chan ArchiveAsyncJob ) error {
251
- do , ok := caf .Archival .(ArchiverAsync )
258
+ func (ar Archive ) ArchiveAsync (ctx context.Context , output io.Writer , jobs <- chan ArchiveAsyncJob ) error {
259
+ if ar .Archival == nil {
260
+ return fmt .Errorf ("no archival format" )
261
+ }
262
+ do , ok := ar .Archival .(ArchiverAsync )
252
263
if ! ok {
253
- return fmt .Errorf ("%s archive does not support async writing" , caf . Extension () )
264
+ return fmt .Errorf ("%T archive does not support async writing" , ar . Archival )
254
265
}
255
- if caf .Compression != nil {
256
- wc , err := caf .Compression .OpenWriter (output )
266
+ if ar .Compression != nil {
267
+ wc , err := ar .Compression .OpenWriter (output )
257
268
if err != nil {
258
269
return err
259
270
}
@@ -264,16 +275,19 @@ func (caf CompressedArchive) ArchiveAsync(ctx context.Context, output io.Writer,
264
275
}
265
276
266
277
// Extract reads files out of an archive while decompressing the results.
267
- func (caf CompressedArchive ) Extract (ctx context.Context , sourceArchive io.Reader , pathsInArchive []string , handleFile FileHandler ) error {
268
- if caf .Compression != nil {
269
- rc , err := caf .Compression .OpenReader (sourceArchive )
278
+ func (ar Archive ) Extract (ctx context.Context , sourceArchive io.Reader , handleFile FileHandler ) error {
279
+ if ar .Extraction == nil {
280
+ return fmt .Errorf ("no extraction format" )
281
+ }
282
+ if ar .Compression != nil {
283
+ rc , err := ar .Compression .OpenReader (sourceArchive )
270
284
if err != nil {
271
285
return err
272
286
}
273
287
defer rc .Close ()
274
288
sourceArchive = rc
275
289
}
276
- return caf . Archival .Extract (ctx , sourceArchive , pathsInArchive , handleFile )
290
+ return ar . Extraction .Extract (ctx , sourceArchive , handleFile )
277
291
}
278
292
279
293
// MatchResult returns true if the format was matched either
@@ -408,8 +422,8 @@ var formats = make(map[string]Format)
408
422
409
423
// Interface guards
410
424
var (
411
- _ Format = (* CompressedArchive )(nil )
412
- _ Archiver = (* CompressedArchive )(nil )
413
- _ ArchiverAsync = (* CompressedArchive )(nil )
414
- _ Extractor = (* CompressedArchive )(nil )
425
+ _ Format = (* Archive )(nil )
426
+ _ Archiver = (* Archive )(nil )
427
+ _ ArchiverAsync = (* Archive )(nil )
428
+ _ Extractor = (* Archive )(nil )
415
429
)
0 commit comments