diff --git a/main.go b/main.go index a1b0ed9ad..671c6333e 100755 --- a/main.go +++ b/main.go @@ -180,17 +180,14 @@ func loop() { // Instantiate Index Index = index.Init(*indexURL, config.GetDataDir()) - // Instantiate Tools - Tools = tools.Tools{ - Directory: config.GetDataDir().String(), - Index: Index, - Logger: func(msg string) { - mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - }, + logger := func(msg string) { + mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB } - Tools.Init() + + // Instantiate Tools + Tools = *tools.New(config.GetDataDir(), Index, logger) // Let's handle the config configDir := config.GetDefaultConfigDir() diff --git a/tools/download.go b/tools/download.go index 2c8cc25a5..76f4056c5 100644 --- a/tools/download.go +++ b/tools/download.go @@ -78,7 +78,7 @@ func pathExists(path string) bool { // if it already exists. func (t *Tools) Download(pack, name, version, behaviour string) error { - body, err := t.Index.Read() + body, err := t.index.Read() if err != nil { return err } @@ -90,7 +90,7 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { correctTool, correctSystem := findTool(pack, name, version, data) if correctTool.Name == "" || correctSystem.URL == "" { - t.Logger("We couldn't find a tool with the name " + name + " and version " + version + " packaged by " + pack) + t.logger("We couldn't find a tool with the name " + name + " and version " + version + " packaged by " + pack) return nil } @@ -98,21 +98,17 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { // Check if it already exists if behaviour == "keep" { - t.mutex.RLock() - location, ok := t.installed[key] - t.mutex.RUnlock() + location, ok := t.getMapValue(key) if ok && pathExists(location) { // overwrite the default tool with this one - t.mutex.Lock() - t.installed[correctTool.Name] = location - t.mutex.Unlock() - t.Logger("The tool is already present on the system") + t.setMapValue(correctTool.Name, location) + t.logger("The tool is already present on the system") return t.writeMap() } } // Download the tool - t.Logger("Downloading tool " + name + " from " + correctSystem.URL) + t.logger("Downloading tool " + name + " from " + correctSystem.URL) resp, err := http.Get(correctSystem.URL) if err != nil { return err @@ -134,9 +130,9 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { } // Decompress - t.Logger("Unpacking tool " + name) + t.logger("Unpacking tool " + name) - location := path.Join(dir(), pack, correctTool.Name, correctTool.Version) + location := t.directory.Join(pack, correctTool.Name, correctTool.Version).String() err = os.RemoveAll(location) if err != nil { @@ -150,18 +146,18 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { switch srcType { case "application/zip": - location, err = extractZip(t.Logger, body, location) + location, err = extractZip(t.logger, body, location) case "application/x-bz2": case "application/octet-stream": - location, err = extractBz2(t.Logger, body, location) + location, err = extractBz2(t.logger, body, location) case "application/x-gzip": - location, err = extractTarGz(t.Logger, body, location) + location, err = extractTarGz(t.logger, body, location) default: return errors.New("Unknown extension for file " + correctSystem.URL) } if err != nil { - t.Logger("Error extracting the archive: " + err.Error()) + t.logger("Error extracting the archive: " + err.Error()) return err } @@ -171,15 +167,13 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { } // Ensure that the files are executable - t.Logger("Ensure that the files are executable") + t.logger("Ensure that the files are executable") // Update the tool map - t.Logger("Updating map with location " + location) + t.logger("Updating map with location " + location) - t.mutex.Lock() - t.installed[name] = location - t.installed[name+"-"+correctTool.Version] = location - t.mutex.Unlock() + t.setMapValue(name, location) + t.setMapValue(name+"-"+correctTool.Version, location) return t.writeMap() } @@ -475,11 +469,11 @@ func (t *Tools) installDrivers(location string) error { preamble = "./" } if _, err := os.Stat(filepath.Join(location, "post_install"+extension)); err == nil { - t.Logger("Installing drivers") + t.logger("Installing drivers") ok := MessageBox("Installing drivers", "We are about to install some drivers needed to use Arduino/Genuino boards\nDo you want to continue?") if ok == OkPressed { os.Chdir(location) - t.Logger(preamble + "post_install" + extension) + t.logger(preamble + "post_install" + extension) oscmd := exec.Command(preamble + "post_install" + extension) if OS != "linux" { // spawning a shell could be the only way to let the user type his password diff --git a/tools/tools.go b/tools/tools.go index 29051440b..ac3e6109b 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -17,49 +17,89 @@ package tools import ( "encoding/json" - "fmt" - "os" - "os/user" - "path" "path/filepath" "strings" "sync" "github.com/arduino/arduino-create-agent/index" + "github.com/arduino/go-paths-helper" "github.com/xrash/smetrics" ) // Tools handle the tools necessary for an upload on a board. // It provides a means to download a tool from the arduino servers. // -// - *Directory* contains the location where the tools are downloaded. -// - *IndexURL* contains the url where the tools description is contained. -// - *Logger* is a StdLogger used for reporting debug and info messages -// - *installed* contains a map of the tools and their exact location +// - *directory* contains the location where the tools are downloaded. +// - *indexURL* contains the url where the tools description is contained. +// - *logger* is a StdLogger used for reporting debug and info messages +// - *installed* contains a map[string]string of the tools installed and their exact location // // Usage: -// You have to instantiate the struct by passing it the required parameters: -// _tools := tools.Tools{ -// Directory: "/home/user/.arduino-create", -// IndexURL: "https://downloads.arduino.cc/packages/package_index.json" -// Logger: log.Logger -// } +// You have to call the New() function passing it the required parameters: +// +// index = index.Init("https://downloads.arduino.cc/packages/package_staging_index.json", dataDir) +// tools := tools.New(dataDir, index, logger) // Tools will represent the installed tools type Tools struct { - Directory string - Index *index.Resource - Logger func(msg string) + directory *paths.Path + index *index.Resource + logger func(msg string) installed map[string]string mutex sync.RWMutex } -// Init creates the Installed map and populates it from a file in .arduino-create -func (t *Tools) Init() { +// New will return a Tool object, allowing the caller to execute operations on it. +// The New functions accept the directory to use to host the tools, +// an index (used to download the tools), +// and a logger to log the operations +func New(directory *paths.Path, index *index.Resource, logger func(msg string)) *Tools { + t := &Tools{ + directory: directory, + index: index, + logger: logger, + installed: map[string]string{}, + mutex: sync.RWMutex{}, + } + _ = t.readMap() + return t +} + +func (t *Tools) setMapValue(key, value string) { t.mutex.Lock() - t.installed = make(map[string]string) + t.installed[key] = value t.mutex.Unlock() - t.readMap() +} + +func (t *Tools) getMapValue(key string) (string, bool) { + t.mutex.RLock() + defer t.mutex.RUnlock() + value, ok := t.installed[key] + return value, ok +} + +// writeMap() writes installed map to the json file "installed.json" +func (t *Tools) writeMap() error { + t.mutex.RLock() + defer t.mutex.RUnlock() + b, err := json.Marshal(t.installed) + if err != nil { + return err + } + filePath := t.directory.Join("installed.json") + return filePath.WriteFile(b) +} + +// readMap() reads the installed map from json file "installed.json" +func (t *Tools) readMap() error { + t.mutex.Lock() + defer t.mutex.Unlock() + filePath := t.directory.Join("installed.json") + b, err := filePath.ReadFile() + if err != nil { + return err + } + return json.Unmarshal(b, &t.installed) } // GetLocation extracts the toolname from a command like @@ -71,22 +111,16 @@ func (t *Tools) GetLocation(command string) (string, error) { var ok bool // Load installed - t.mutex.RLock() - fmt.Println(t.installed) - t.mutex.RUnlock() - err := t.readMap() if err != nil { return "", err } - t.mutex.RLock() - defer t.mutex.RUnlock() - fmt.Println(t.installed) - // use string similarity to resolve a runtime var with a "similar" map element - if location, ok = t.installed[command]; !ok { + if location, ok = t.getMapValue(command); !ok { maxSimilarity := 0.0 + t.mutex.RLock() + defer t.mutex.RUnlock() for i, candidate := range t.installed { similarity := smetrics.Jaro(command, i) if similarity > 0.8 && similarity > maxSimilarity { @@ -97,32 +131,3 @@ func (t *Tools) GetLocation(command string) (string, error) { } return filepath.ToSlash(location), nil } - -// writeMap() writes installed map to the json file "installed.json" -func (t *Tools) writeMap() error { - t.mutex.Lock() - b, err := json.Marshal(t.installed) - defer t.mutex.Unlock() - if err != nil { - return err - } - filePath := path.Join(dir(), "installed.json") - return os.WriteFile(filePath, b, 0644) -} - -// readMap() reads the installed map from json file "installed.json" -func (t *Tools) readMap() error { - t.mutex.Lock() - defer t.mutex.Unlock() - filePath := path.Join(dir(), "installed.json") - b, err := os.ReadFile(filePath) - if err != nil { - return err - } - return json.Unmarshal(b, &t.installed) -} - -func dir() string { - usr, _ := user.Current() - return path.Join(usr.HomeDir, ".arduino-create") -}