Skip to content

Commit fa8874f

Browse files
authored
Use codeclysm/extract and remove custom implementation (#867)
* replace test with a meaningful one The tools are the one coming with the cores installed in the builder * use `codeclysm/extract` and remove extract code * fix cleanup in tests * enlarge the scope of the test: verify dir tree is handled correctly * the archives are first extracted in tmp and then moved to correct place we remove the root dir if we find one. The logic is similar to: https://github.com/arduino/arduino-cli/blob/7a146635aaa740e748b84bf8fbfdccf1cc420c61/arduino/resources/install.go#L34 * fix test failing
1 parent 0fd774a commit fa8874f

File tree

4 files changed

+548
-423
lines changed

4 files changed

+548
-423
lines changed

tools/download.go

+49-288
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
package tools
1717

1818
import (
19-
"archive/tar"
20-
"archive/zip"
2119
"bytes"
22-
"compress/bzip2"
23-
"compress/gzip"
20+
"context"
2421
"crypto/sha256"
2522
"encoding/hex"
2623
"encoding/json"
@@ -30,14 +27,13 @@ import (
3027
"net/http"
3128
"os"
3229
"os/exec"
33-
"path"
3430
"path/filepath"
3531
"runtime"
36-
"strings"
3732

38-
"github.com/arduino/arduino-create-agent/utilities"
3933
"github.com/arduino/arduino-create-agent/v2/pkgs"
34+
"github.com/arduino/go-paths-helper"
4035
"github.com/blang/semver"
36+
"github.com/codeclysm/extract/v3"
4137
)
4238

4339
// public vars to allow override in the tests
@@ -46,10 +42,6 @@ var (
4642
Arch = runtime.GOARCH
4743
)
4844

49-
func mimeType(data []byte) (string, error) {
50-
return http.DetectContentType(data[0:512]), nil
51-
}
52-
5345
func pathExists(path string) bool {
5446
_, err := os.Stat(path)
5547
if err == nil {
@@ -129,39 +121,46 @@ func (t *Tools) Download(pack, name, version, behaviour string) error {
129121
return errors.New("checksum doesn't match")
130122
}
131123

132-
// Decompress
133-
t.logger("Unpacking tool " + name)
134-
135-
location := t.directory.Join(pack, correctTool.Name, correctTool.Version).String()
136-
err = os.RemoveAll(location)
137-
124+
tempPath := paths.TempDir()
125+
// Create a temporary dir to extract package
126+
if err := tempPath.MkdirAll(); err != nil {
127+
return fmt.Errorf("creating temp dir for extraction: %s", err)
128+
}
129+
tempDir, err := tempPath.MkTempDir("package-")
138130
if err != nil {
139-
return err
131+
return fmt.Errorf("creating temp dir for extraction: %s", err)
140132
}
133+
defer tempDir.RemoveAll()
141134

142-
srcType, err := mimeType(body)
135+
t.logger("Unpacking tool " + name)
136+
ctx := context.Background()
137+
reader := bytes.NewReader(body)
138+
// Extract into temp directory
139+
if err := extract.Archive(ctx, reader, tempDir.String(), nil); err != nil {
140+
return fmt.Errorf("extracting archive: %s", err)
141+
}
142+
143+
location := t.directory.Join(pack, correctTool.Name, correctTool.Version)
144+
err = location.RemoveAll()
143145
if err != nil {
144146
return err
145147
}
146148

147-
switch srcType {
148-
case "application/zip":
149-
location, err = extractZip(t.logger, body, location)
150-
case "application/x-bz2":
151-
case "application/octet-stream":
152-
location, err = extractBz2(t.logger, body, location)
153-
case "application/x-gzip":
154-
location, err = extractTarGz(t.logger, body, location)
155-
default:
156-
return errors.New("Unknown extension for file " + correctSystem.URL)
149+
// Check package content and find package root dir
150+
root, err := findPackageRoot(tempDir)
151+
if err != nil {
152+
return fmt.Errorf("searching package root dir: %s", err)
157153
}
158154

159-
if err != nil {
160-
t.logger("Error extracting the archive: " + err.Error())
161-
return err
155+
if err := root.Rename(location); err != nil {
156+
if err := root.CopyDirTo(location); err != nil {
157+
return fmt.Errorf("moving extracted archive to destination dir: %s", err)
158+
}
162159
}
163160

164-
err = t.installDrivers(location)
161+
// if the tool contains a post_install script, run it: it means it is a tool that needs to install drivers
162+
// AFAIK this is only the case for the windows-driver tool
163+
err = t.installDrivers(location.String())
165164
if err != nil {
166165
return err
167166
}
@@ -170,13 +169,27 @@ func (t *Tools) Download(pack, name, version, behaviour string) error {
170169
t.logger("Ensure that the files are executable")
171170

172171
// Update the tool map
173-
t.logger("Updating map with location " + location)
172+
t.logger("Updating map with location " + location.String())
174173

175-
t.setMapValue(name, location)
176-
t.setMapValue(name+"-"+correctTool.Version, location)
174+
t.setMapValue(name, location.String())
175+
t.setMapValue(name+"-"+correctTool.Version, location.String())
177176
return t.writeMap()
178177
}
179178

179+
func findPackageRoot(parent *paths.Path) (*paths.Path, error) {
180+
files, err := parent.ReadDir()
181+
if err != nil {
182+
return nil, fmt.Errorf("reading package root dir: %s", err)
183+
}
184+
files.FilterOutPrefix("__MACOSX")
185+
186+
// if there is only one dir, it is the root dir
187+
if len(files) == 1 && files[0].IsDir() {
188+
return files[0], nil
189+
}
190+
return parent, nil
191+
}
192+
180193
func findTool(pack, name, version string, data pkgs.Index) (pkgs.Tool, pkgs.System) {
181194
var correctTool pkgs.Tool
182195
correctTool.Version = "0.0"
@@ -207,258 +220,6 @@ func findTool(pack, name, version string, data pkgs.Index) (pkgs.Tool, pkgs.Syst
207220
return correctTool, correctSystem
208221
}
209222

210-
func commonPrefix(sep byte, paths []string) string {
211-
// Handle special cases.
212-
switch len(paths) {
213-
case 0:
214-
return ""
215-
case 1:
216-
return path.Clean(paths[0])
217-
}
218-
219-
c := []byte(path.Clean(paths[0]))
220-
221-
// We add a trailing sep to handle: common prefix directory is included in the path list
222-
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
223-
// path.Clean will have cleaned off trailing / separators with
224-
// the exception of the root directory, "/" making it "//"
225-
// but this will get fixed up to "/" below).
226-
c = append(c, sep)
227-
228-
// Ignore the first path since it's already in c
229-
for _, v := range paths[1:] {
230-
// Clean up each path before testing it
231-
v = path.Clean(v) + string(sep)
232-
233-
// Find the first non-common byte and truncate c
234-
if len(v) < len(c) {
235-
c = c[:len(v)]
236-
}
237-
for i := 0; i < len(c); i++ {
238-
if v[i] != c[i] {
239-
c = c[:i]
240-
break
241-
}
242-
}
243-
}
244-
245-
// Remove trailing non-separator characters and the final separator
246-
for i := len(c) - 1; i >= 0; i-- {
247-
if c[i] == sep {
248-
c = c[:i]
249-
break
250-
}
251-
}
252-
253-
return string(c)
254-
}
255-
256-
func removeStringFromSlice(s []string, r string) []string {
257-
for i, v := range s {
258-
if v == r {
259-
return append(s[:i], s[i+1:]...)
260-
}
261-
}
262-
return s
263-
}
264-
265-
func findBaseDir(dirList []string) string {
266-
if len(dirList) == 1 {
267-
return path.Dir(dirList[0]) + "/"
268-
}
269-
270-
// https://github.com/backdrop-ops/contrib/issues/55#issuecomment-73814500
271-
dontdiff := []string{"pax_global_header"}
272-
for _, v := range dontdiff {
273-
dirList = removeStringFromSlice(dirList, v)
274-
}
275-
276-
commonBaseDir := commonPrefix('/', dirList)
277-
if commonBaseDir != "" {
278-
commonBaseDir = commonBaseDir + "/"
279-
}
280-
return commonBaseDir
281-
}
282-
283-
func extractZip(log func(msg string), body []byte, location string) (string, error) {
284-
path, _ := utilities.SaveFileonTempDir("tooldownloaded.zip", bytes.NewReader(body))
285-
r, err := zip.OpenReader(path)
286-
if err != nil {
287-
return location, err
288-
}
289-
290-
var dirList []string
291-
292-
for _, f := range r.File {
293-
dirList = append(dirList, f.Name)
294-
}
295-
296-
basedir := findBaseDir(dirList)
297-
log(fmt.Sprintf("selected baseDir %s from Zip Archive Content: %v", basedir, dirList))
298-
299-
for _, f := range r.File {
300-
fullname := filepath.Join(location, strings.Replace(f.Name, basedir, "", -1))
301-
log(fmt.Sprintf("generated fullname %s removing %s from %s", fullname, basedir, f.Name))
302-
if f.FileInfo().IsDir() {
303-
os.MkdirAll(fullname, f.FileInfo().Mode().Perm())
304-
} else {
305-
os.MkdirAll(filepath.Dir(fullname), 0755)
306-
perms := f.FileInfo().Mode().Perm()
307-
out, err := os.OpenFile(fullname, os.O_CREATE|os.O_RDWR, perms)
308-
if err != nil {
309-
return location, err
310-
}
311-
rc, err := f.Open()
312-
if err != nil {
313-
return location, err
314-
}
315-
_, err = io.CopyN(out, rc, f.FileInfo().Size())
316-
if err != nil {
317-
return location, err
318-
}
319-
rc.Close()
320-
out.Close()
321-
322-
mtime := f.FileInfo().ModTime()
323-
err = os.Chtimes(fullname, mtime, mtime)
324-
if err != nil {
325-
return location, err
326-
}
327-
}
328-
}
329-
return location, nil
330-
}
331-
332-
func extractTarGz(log func(msg string), body []byte, location string) (string, error) {
333-
bodyCopy := make([]byte, len(body))
334-
copy(bodyCopy, body)
335-
tarFile, _ := gzip.NewReader(bytes.NewReader(body))
336-
tarReader := tar.NewReader(tarFile)
337-
338-
var dirList []string
339-
340-
for {
341-
header, err := tarReader.Next()
342-
if err == io.EOF {
343-
break
344-
}
345-
dirList = append(dirList, header.Name)
346-
}
347-
348-
basedir := findBaseDir(dirList)
349-
log(fmt.Sprintf("selected baseDir %s from TarGz Archive Content: %v", basedir, dirList))
350-
351-
tarFile, _ = gzip.NewReader(bytes.NewReader(bodyCopy))
352-
tarReader = tar.NewReader(tarFile)
353-
354-
for {
355-
header, err := tarReader.Next()
356-
if err == io.EOF {
357-
break
358-
} else if err != nil {
359-
return location, err
360-
}
361-
362-
path := filepath.Join(location, strings.Replace(header.Name, basedir, "", -1))
363-
info := header.FileInfo()
364-
365-
// Create parent folder
366-
dirmode := info.Mode() | os.ModeDir | 0700
367-
if err = os.MkdirAll(filepath.Dir(path), dirmode); err != nil {
368-
return location, err
369-
}
370-
371-
if info.IsDir() {
372-
if err = os.MkdirAll(path, info.Mode()); err != nil {
373-
return location, err
374-
}
375-
continue
376-
}
377-
378-
if header.Typeflag == tar.TypeSymlink {
379-
_ = os.Symlink(header.Linkname, path)
380-
continue
381-
}
382-
383-
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
384-
if err != nil {
385-
continue
386-
}
387-
_, err = io.Copy(file, tarReader)
388-
if err != nil {
389-
return location, err
390-
}
391-
file.Close()
392-
}
393-
return location, nil
394-
}
395-
396-
func extractBz2(log func(msg string), body []byte, location string) (string, error) {
397-
bodyCopy := make([]byte, len(body))
398-
copy(bodyCopy, body)
399-
tarFile := bzip2.NewReader(bytes.NewReader(body))
400-
tarReader := tar.NewReader(tarFile)
401-
402-
var dirList []string
403-
404-
for {
405-
header, err := tarReader.Next()
406-
if err == io.EOF {
407-
break
408-
}
409-
dirList = append(dirList, header.Name)
410-
}
411-
412-
basedir := findBaseDir(dirList)
413-
log(fmt.Sprintf("selected baseDir %s from Bz2 Archive Content: %v", basedir, dirList))
414-
415-
tarFile = bzip2.NewReader(bytes.NewReader(bodyCopy))
416-
tarReader = tar.NewReader(tarFile)
417-
418-
for {
419-
header, err := tarReader.Next()
420-
if err == io.EOF {
421-
break
422-
} else if err != nil {
423-
continue
424-
//return location, err
425-
}
426-
427-
path := filepath.Join(location, strings.Replace(header.Name, basedir, "", -1))
428-
info := header.FileInfo()
429-
430-
// Create parent folder
431-
dirmode := info.Mode() | os.ModeDir | 0700
432-
if err = os.MkdirAll(filepath.Dir(path), dirmode); err != nil {
433-
return location, err
434-
}
435-
436-
if info.IsDir() {
437-
if err = os.MkdirAll(path, info.Mode()); err != nil {
438-
return location, err
439-
}
440-
continue
441-
}
442-
443-
if header.Typeflag == tar.TypeSymlink {
444-
_ = os.Symlink(header.Linkname, path)
445-
continue
446-
}
447-
448-
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
449-
if err != nil {
450-
continue
451-
//return location, err
452-
}
453-
_, err = io.Copy(file, tarReader)
454-
if err != nil {
455-
return location, err
456-
}
457-
file.Close()
458-
}
459-
return location, nil
460-
}
461-
462223
func (t *Tools) installDrivers(location string) error {
463224
OkPressed := 6
464225
extension := ".bat"

0 commit comments

Comments
 (0)