1616package tools
1717
1818import (
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
4642 Arch = runtime .GOARCH
4743)
4844
49- func mimeType (data []byte ) (string , error ) {
50- return http .DetectContentType (data [0 :512 ]), nil
51- }
52-
5345func 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+
180193func 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-
462223func (t * Tools ) installDrivers (location string ) error {
463224 OkPressed := 6
464225 extension := ".bat"
0 commit comments