Skip to content

Commit a679bf3

Browse files
Improve installed.json handling in v2/tools (#983)
* Introduce mutex policy to the v2 tools and use it to write the installed.json * Use a map to store installed.json information * Use the correct lock for reading and writing * Include the v2.Tools constructor in the old Tools struct * Add corrupted json test
1 parent 6287df1 commit a679bf3

File tree

4 files changed

+61
-43
lines changed

4 files changed

+61
-43
lines changed

tools/download.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525

2626
"github.com/arduino/arduino-create-agent/gen/tools"
2727
"github.com/arduino/arduino-create-agent/utilities"
28-
"github.com/arduino/arduino-create-agent/v2/pkgs"
2928
)
3029

3130
// Download will parse the index at the indexURL for the tool to download.
@@ -45,8 +44,8 @@ import (
4544
// if it already exists.
4645
func (t *Tools) Download(pack, name, version, behaviour string) error {
4746

48-
tool := pkgs.New(t.index, t.directory.String(), behaviour)
49-
_, err := tool.Install(context.Background(), &tools.ToolPayload{Name: name, Version: version, Packager: pack})
47+
t.tools.SetBehaviour(behaviour)
48+
_, err := t.tools.Install(context.Background(), &tools.ToolPayload{Name: name, Version: version, Packager: pack})
5049
if err != nil {
5150
return err
5251
}

tools/download_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,23 @@ func TestDownload(t *testing.T) {
160160
})
161161
}
162162
}
163+
164+
func TestCorruptedInstalled(t *testing.T) {
165+
// prepare the test environment
166+
tempDir := t.TempDir()
167+
tempDirPath := paths.New(tempDir)
168+
testIndex := index.Resource{
169+
IndexFile: *paths.New("testdata", "test_tool_index.json"),
170+
LastRefresh: time.Now(),
171+
}
172+
corruptedJSON := tempDirPath.Join("installed.json")
173+
fileJSON, err := corruptedJSON.Create()
174+
require.NoError(t, err)
175+
defer fileJSON.Close()
176+
_, err = fileJSON.Write([]byte("Hello"))
177+
require.NoError(t, err)
178+
testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) })
179+
// Download the tool
180+
err = testTools.Download("arduino-test", "avrdude", "6.3.0-arduino17", "keep")
181+
require.NoError(t, err)
182+
}

tools/tools.go

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"sync"
2323

2424
"github.com/arduino/arduino-create-agent/index"
25+
"github.com/arduino/arduino-create-agent/v2/pkgs"
2526
"github.com/arduino/go-paths-helper"
2627
"github.com/xrash/smetrics"
2728
)
@@ -47,6 +48,7 @@ type Tools struct {
4748
logger func(msg string)
4849
installed map[string]string
4950
mutex sync.RWMutex
51+
tools *pkgs.Tools
5052
}
5153

5254
// New will return a Tool object, allowing the caller to execute operations on it.
@@ -60,6 +62,7 @@ func New(directory *paths.Path, index *index.Resource, logger func(msg string))
6062
logger: logger,
6163
installed: map[string]string{},
6264
mutex: sync.RWMutex{},
65+
tools: pkgs.New(index, directory.String(), "replace"),
6366
}
6467
_ = t.readMap()
6568
return t

v2/pkgs/tools.go

+36-40
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"path/filepath"
3030
"runtime"
3131
"strings"
32+
"sync"
3233

3334
"github.com/arduino/arduino-create-agent/gen/tools"
3435
"github.com/arduino/arduino-create-agent/index"
@@ -60,17 +61,23 @@ type Tools struct {
6061
index *index.Resource
6162
folder string
6263
behaviour string
64+
installed map[string]string
65+
mutex sync.RWMutex
6366
}
6467

6568
// New will return a Tool object, allowing the caller to execute operations on it.
6669
// The New function will accept an index as parameter (used to download the indexes)
6770
// and a folder used to download the indexes
6871
func New(index *index.Resource, folder, behaviour string) *Tools {
69-
return &Tools{
72+
t := &Tools{
7073
index: index,
7174
folder: folder,
7275
behaviour: behaviour,
76+
installed: map[string]string{},
77+
mutex: sync.RWMutex{},
7378
}
79+
t.readInstalled()
80+
return t
7481
}
7582

7683
// Installedhead is here only because it was required by the front-end.
@@ -181,13 +188,10 @@ func (t *Tools) Install(ctx context.Context, payload *tools.ToolPayload) (*tools
181188
key := correctTool.Name + "-" + correctTool.Version
182189
// Check if it already exists
183190
if t.behaviour == "keep" && pathExists(t.folder) {
184-
location, ok, err := checkInstalled(t.folder, key)
185-
if err != nil {
186-
return nil, err
187-
}
191+
location, ok := t.installed[key]
188192
if ok && pathExists(location) {
189193
// overwrite the default tool with this one
190-
err := writeInstalled(t.folder, path)
194+
err := t.writeInstalled(path)
191195
if err != nil {
192196
return nil, err
193197
}
@@ -245,7 +249,7 @@ func (t *Tools) install(ctx context.Context, path, url, checksum string) (*tools
245249
}
246250

247251
// Write installed.json for retrocompatibility with v1
248-
err = writeInstalled(t.folder, path)
252+
err = t.writeInstalled(path)
249253
if err != nil {
250254
return nil, err
251255
}
@@ -285,61 +289,53 @@ func rename(base string) extract.Renamer {
285289
}
286290
}
287291

288-
func readInstalled(installedFile string) (map[string]string, error) {
292+
func (t *Tools) readInstalled() error {
293+
t.mutex.RLock()
294+
defer t.mutex.RUnlock()
289295
// read installed.json
290-
installed := map[string]string{}
291-
data, err := os.ReadFile(installedFile)
292-
if err == nil {
293-
err = json.Unmarshal(data, &installed)
294-
if err != nil {
295-
return nil, err
296-
}
297-
}
298-
return installed, nil
299-
}
300-
301-
func checkInstalled(folder, key string) (string, bool, error) {
302-
installedFile, err := utilities.SafeJoin(folder, "installed.json")
303-
if err != nil {
304-
return "", false, err
305-
}
306-
installed, err := readInstalled(installedFile)
307-
if err != nil {
308-
return "", false, err
309-
}
310-
location, ok := installed[key]
311-
return location, ok, err
312-
}
313-
314-
func writeInstalled(folder, path string) error {
315-
// read installed.json
316-
installedFile, err := utilities.SafeJoin(folder, "installed.json")
296+
installedFile, err := utilities.SafeJoin(t.folder, "installed.json")
317297
if err != nil {
318298
return err
319299
}
320-
installed, err := readInstalled(installedFile)
300+
data, err := os.ReadFile(installedFile)
321301
if err != nil {
322302
return err
323303
}
304+
return json.Unmarshal(data, &t.installed)
305+
}
306+
307+
func (t *Tools) writeInstalled(path string) error {
308+
t.mutex.Lock()
309+
defer t.mutex.Unlock()
324310

325311
parts := strings.Split(path, string(filepath.Separator))
326312
tool := parts[len(parts)-2]
327313
toolWithVersion := fmt.Sprint(tool, "-", parts[len(parts)-1])
328-
toolFile, err := utilities.SafeJoin(folder, path)
314+
toolFile, err := utilities.SafeJoin(t.folder, path)
329315
if err != nil {
330316
return err
331317
}
332-
installed[tool] = toolFile
333-
installed[toolWithVersion] = toolFile
318+
t.installed[tool] = toolFile
319+
t.installed[toolWithVersion] = toolFile
334320

335-
data, err := json.Marshal(installed)
321+
data, err := json.Marshal(t.installed)
322+
if err != nil {
323+
return err
324+
}
325+
326+
installedFile, err := utilities.SafeJoin(t.folder, "installed.json")
336327
if err != nil {
337328
return err
338329
}
339330

340331
return os.WriteFile(installedFile, data, 0644)
341332
}
342333

334+
// SetBehaviour sets the download behaviour to either keep or replace
335+
func (t *Tools) SetBehaviour(behaviour string) {
336+
t.behaviour = behaviour
337+
}
338+
343339
func pathExists(path string) bool {
344340
_, err := os.Stat(path)
345341
if err == nil {

0 commit comments

Comments
 (0)