Skip to content

Commit 5fdc569

Browse files
cmagliematteosuppo
authored andcommitted
Allow extract interruption
1 parent d489ebb commit 5fdc569

2 files changed

Lines changed: 109 additions & 15 deletions

File tree

cancelable_reader.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package extract
2+
3+
import (
4+
"errors"
5+
"io"
6+
)
7+
8+
func copyCancel(dst io.Writer, src io.Reader, cancel <-chan bool) (int64, error) {
9+
return io.Copy(dst, newCancelableReader(src, cancel))
10+
}
11+
12+
type cancelableReader struct {
13+
cancel <-chan bool
14+
src io.Reader
15+
}
16+
17+
func (r *cancelableReader) Read(p []byte) (int, error) {
18+
select {
19+
case <-r.cancel:
20+
return 0, errors.New("interrupted")
21+
default:
22+
return r.src.Read(p)
23+
}
24+
}
25+
26+
func newCancelableReader(src io.Reader, cancel <-chan bool) *cancelableReader {
27+
return &cancelableReader{
28+
cancel: cancel,
29+
src: src,
30+
}
31+
}

extract.go

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,28 @@ type Renamer func(string) string
5353
// handle the names of the files.
5454
// If the file is not an archive, an error is returned.
5555
func Archive(body io.Reader, location string, rename Renamer) error {
56+
dummy := make(chan bool, 1)
57+
defer close(dummy)
58+
return ArchiveCancel(body, location, rename, dummy)
59+
}
60+
61+
// ArchiveCancel is the same as Archive but with an extra channel to stop
62+
// the extraction.
63+
func ArchiveCancel(body io.Reader, location string, rename Renamer, cancel <-chan bool) error {
5664
body, kind, err := match(body)
5765
if err != nil {
5866
errors.Annotatef(err, "Detect archive type")
5967
}
6068

6169
switch kind.Extension {
6270
case "zip":
63-
return Zip(body, location, rename)
71+
return ZipCancel(body, location, rename, cancel)
6472
case "gz":
65-
return Gz(body, location, rename)
73+
return GzCancel(body, location, rename, cancel)
6674
case "bz2":
67-
return Bz2(body, location, rename)
75+
return Bz2Cancel(body, location, rename, cancel)
6876
case "tar":
69-
return Tar(body, location, rename)
77+
return TarCancel(body, location, rename, cancel)
7078
default:
7179
return errors.New("Not a supported archive")
7280
}
@@ -75,6 +83,13 @@ func Archive(body io.Reader, location string, rename Renamer) error {
7583
// Bz2 extracts a .bz2 or .tar.bz2 archived stream of data in the specified location.
7684
// It accepts a rename function to handle the names of the files (see the example)
7785
func Bz2(body io.Reader, location string, rename Renamer) error {
86+
dummy := make(chan bool, 1)
87+
defer close(dummy)
88+
return Bz2Cancel(body, location, rename, dummy)
89+
}
90+
91+
// Bz2Cancel is the same as Bz2 but with an extra channel to stop the extraction.
92+
func Bz2Cancel(body io.Reader, location string, rename Renamer, cancel <-chan bool) error {
7893
reader := bzip2.NewReader(body)
7994

8095
body, kind, err := match(reader)
@@ -83,10 +98,10 @@ func Bz2(body io.Reader, location string, rename Renamer) error {
8398
}
8499

85100
if kind.Extension == "tar" {
86-
return Tar(body, location, rename)
101+
return TarCancel(body, location, rename, cancel)
87102
}
88103

89-
err = copy(location, 0666, body)
104+
err = copy(location, 0666, body, cancel)
90105
if err != nil {
91106
return err
92107
}
@@ -96,6 +111,13 @@ func Bz2(body io.Reader, location string, rename Renamer) error {
96111
// Gz extracts a .gz or .tar.gz archived stream of data in the specified location.
97112
// It accepts a rename function to handle the names of the files (see the example)
98113
func Gz(body io.Reader, location string, rename Renamer) error {
114+
dummy := make(chan bool, 1)
115+
defer close(dummy)
116+
return GzCancel(body, location, rename, dummy)
117+
}
118+
119+
// GzCancel is the same as Gz but with an extra channel to stop the extraction.
120+
func GzCancel(body io.Reader, location string, rename Renamer, cancel <-chan bool) error {
99121
reader, err := gzip.NewReader(body)
100122
if err != nil {
101123
return errors.Annotatef(err, "Gunzip")
@@ -107,9 +129,9 @@ func Gz(body io.Reader, location string, rename Renamer) error {
107129
}
108130

109131
if kind.Extension == "tar" {
110-
return Tar(body, location, rename)
132+
return TarCancel(body, location, rename, cancel)
111133
}
112-
err = copy(location, 0666, body)
134+
err = copy(location, 0666, body, cancel)
113135
if err != nil {
114136
return err
115137
}
@@ -129,6 +151,13 @@ type link struct {
129151
// Tar extracts a .tar archived stream of data in the specified location.
130152
// It accepts a rename function to handle the names of the files (see the example)
131153
func Tar(body io.Reader, location string, rename Renamer) error {
154+
dummy := make(chan bool, 1)
155+
defer close(dummy)
156+
return TarCancel(body, location, rename, dummy)
157+
}
158+
159+
// TarCancel is the same as Tar but with an extra channel to stop the extraction.
160+
func TarCancel(body io.Reader, location string, rename Renamer, cancel <-chan bool) error {
132161
files := []file{}
133162
links := []link{}
134163
symlinks := []link{}
@@ -137,6 +166,12 @@ func Tar(body io.Reader, location string, rename Renamer) error {
137166
// attempting to create a file where there's no folder
138167
tr := tar.NewReader(body)
139168
for {
169+
select {
170+
case <-cancel:
171+
return errors.New("interrupted")
172+
default:
173+
}
174+
140175
header, err := tr.Next()
141176
if err == io.EOF {
142177
break
@@ -165,7 +200,7 @@ func Tar(body io.Reader, location string, rename Renamer) error {
165200
}
166201
case tar.TypeReg, tar.TypeRegA:
167202
var data bytes.Buffer
168-
if _, err := io.Copy(&data, tr); err != nil {
203+
if _, err := copyCancel(&data, tr, cancel); err != nil {
169204
return errors.Annotatef(err, "Read contents of file %s", path)
170205
}
171206
files = append(files, file{Path: path, Mode: info.Mode(), Data: data})
@@ -184,18 +219,28 @@ func Tar(body io.Reader, location string, rename Renamer) error {
184219

185220
// Now we make another pass creating the files and links
186221
for i := range files {
187-
if err := copy(files[i].Path, files[i].Mode, &files[i].Data); err != nil {
222+
if err := copy(files[i].Path, files[i].Mode, &files[i].Data, cancel); err != nil {
188223
return errors.Annotatef(err, "Create file %s", files[i].Path)
189224
}
190225
}
191226

192227
for i := range links {
228+
select {
229+
case <-cancel:
230+
return errors.New("interrupted")
231+
default:
232+
}
193233
if err := os.Link(links[i].Name, links[i].Path); err != nil {
194234
return errors.Annotatef(err, "Create link %s", links[i].Path)
195235
}
196236
}
197237

198238
for i := range symlinks {
239+
select {
240+
case <-cancel:
241+
return errors.New("interrupted")
242+
default:
243+
}
199244
if err := os.Symlink(symlinks[i].Name, symlinks[i].Path); err != nil {
200245
return errors.Annotatef(err, "Create link %s", symlinks[i].Path)
201246
}
@@ -206,9 +251,16 @@ func Tar(body io.Reader, location string, rename Renamer) error {
206251
// Zip extracts a .zip archived stream of data in the specified location.
207252
// It accepts a rename function to handle the names of the files (see the example).
208253
func Zip(body io.Reader, location string, rename Renamer) error {
254+
dummy := make(chan bool, 1)
255+
defer close(dummy)
256+
return ZipCancel(body, location, rename, dummy)
257+
}
258+
259+
// ZipCancel is the same as Bz2 but with an extra channel to stop the extraction.
260+
func ZipCancel(body io.Reader, location string, rename Renamer, cancel <-chan bool) error {
209261
// read the whole body into a buffer. Not sure this is the best way to do it
210262
buffer := bytes.NewBuffer([]byte{})
211-
io.Copy(buffer, body)
263+
copyCancel(buffer, body, cancel)
212264

213265
archive, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(buffer.Len()))
214266
if err != nil {
@@ -221,6 +273,12 @@ func Zip(body io.Reader, location string, rename Renamer) error {
221273
// We make the first pass creating the directory structure, or we could end up
222274
// attempting to create a file where there's no folder
223275
for _, header := range archive.File {
276+
select {
277+
case <-cancel:
278+
return errors.New("interrupted")
279+
default:
280+
}
281+
224282
path := header.Name
225283
if rename != nil {
226284
path = rename(path)
@@ -255,7 +313,7 @@ func Zip(body io.Reader, location string, rename Renamer) error {
255313
return errors.Annotatef(err, "Open file %s", path)
256314
}
257315
var data bytes.Buffer
258-
if _, err := io.Copy(&data, f); err != nil {
316+
if _, err := copyCancel(&data, f, cancel); err != nil {
259317
return errors.Annotatef(err, "Read contents of file %s", path)
260318
}
261319
files = append(files, file{Path: path, Mode: info.Mode(), Data: data})
@@ -264,12 +322,17 @@ func Zip(body io.Reader, location string, rename Renamer) error {
264322

265323
// Now we make another pass creating the files and links
266324
for i := range files {
267-
if err := copy(files[i].Path, files[i].Mode, &files[i].Data); err != nil {
325+
if err := copy(files[i].Path, files[i].Mode, &files[i].Data, cancel); err != nil {
268326
return errors.Annotatef(err, "Create file %s", files[i].Path)
269327
}
270328
}
271329

272330
for i := range links {
331+
select {
332+
case <-cancel:
333+
return errors.New("interrupted")
334+
default:
335+
}
273336
if err := os.Symlink(links[i].Name, links[i].Path); err != nil {
274337
return errors.Annotatef(err, "Create link %s", links[i].Path)
275338
}
@@ -278,7 +341,7 @@ func Zip(body io.Reader, location string, rename Renamer) error {
278341
return nil
279342
}
280343

281-
func copy(path string, mode os.FileMode, src io.Reader) error {
344+
func copy(path string, mode os.FileMode, src io.Reader, cancel <-chan bool) error {
282345
// We add the execution permission to be able to create files inside it
283346
err := os.MkdirAll(filepath.Dir(path), mode|os.ModeDir|100)
284347
if err != nil {
@@ -289,7 +352,7 @@ func copy(path string, mode os.FileMode, src io.Reader) error {
289352
return err
290353
}
291354
defer file.Close()
292-
_, err = io.Copy(file, src)
355+
_, err = copyCancel(file, src, cancel)
293356
return err
294357
}
295358

0 commit comments

Comments
 (0)