@@ -19,7 +19,7 @@ import (
19
19
"context"
20
20
"encoding/json"
21
21
"fmt"
22
- "io/ioutil "
22
+ "io"
23
23
"net/http"
24
24
"regexp"
25
25
"sort"
@@ -32,24 +32,43 @@ import (
32
32
"github.com/arduino/arduino-cli/arduino/discovery"
33
33
"github.com/arduino/arduino-cli/arduino/httpclient"
34
34
"github.com/arduino/arduino-cli/commands"
35
+ "github.com/arduino/arduino-cli/inventory"
35
36
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
36
37
"github.com/pkg/errors"
37
38
"github.com/sirupsen/logrus"
38
39
)
39
40
40
- type boardNotFoundError struct {}
41
-
42
- func (e * boardNotFoundError ) Error () string {
43
- return tr ("board not found" )
44
- }
45
-
46
41
var (
47
- // ErrNotFound is returned when the API returns 404
48
- ErrNotFound = & boardNotFoundError {}
49
42
vidPidURL = "https://builder.arduino.cc/v3/boards/byVidPid"
50
43
validVidPid = regexp .MustCompile (`0[xX][a-fA-F\d]{4}` )
51
44
)
52
45
46
+ func cachedAPIByVidPid (vid , pid string ) ([]* rpc.BoardListItem , error ) {
47
+ var resp []* rpc.BoardListItem
48
+
49
+ cacheKey := fmt .Sprintf ("cache.builder-api.v3/boards/byvid/pid/%s/%s" , vid , pid )
50
+ if cachedResp := inventory .Store .GetString (cacheKey + ".data" ); cachedResp != "" {
51
+ ts := inventory .Store .GetTime (cacheKey + ".ts" )
52
+ if time .Since (ts ) < time .Hour * 24 {
53
+ // Use cached response
54
+ if err := json .Unmarshal ([]byte (cachedResp ), & resp ); err == nil {
55
+ return resp , nil
56
+ }
57
+ }
58
+ }
59
+
60
+ resp , err := apiByVidPid (vid , pid ) // Perform API requrest
61
+
62
+ if err == nil {
63
+ if cachedResp , err := json .Marshal (resp ); err == nil {
64
+ inventory .Store .Set (cacheKey + ".data" , string (cachedResp ))
65
+ inventory .Store .Set (cacheKey + ".ts" , time .Now ())
66
+ inventory .WriteStore ()
67
+ }
68
+ }
69
+ return resp , err
70
+ }
71
+
53
72
func apiByVidPid (vid , pid string ) ([]* rpc.BoardListItem , error ) {
54
73
// ensure vid and pid are valid before hitting the API
55
74
if ! validVidPid .MatchString (vid ) {
@@ -60,7 +79,6 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
60
79
}
61
80
62
81
url := fmt .Sprintf ("%s/%s/%s" , vidPidURL , vid , pid )
63
- retVal := []* rpc.BoardListItem {}
64
82
req , _ := http .NewRequest ("GET" , url , nil )
65
83
req .Header .Set ("Content-Type" , "application/json" )
66
84
@@ -72,50 +90,53 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
72
90
return nil , errors .Wrap (err , tr ("failed to initialize http client" ))
73
91
}
74
92
75
- if res , err := httpClient .Do (req ); err == nil {
76
- if res .StatusCode >= 400 {
77
- if res .StatusCode == 404 {
78
- return nil , ErrNotFound
79
- }
80
- return nil , errors .Errorf (tr ("the server responded with status %s" ), res .Status )
81
- }
82
-
83
- body , _ := ioutil .ReadAll (res .Body )
84
- res .Body .Close ()
85
-
86
- var dat map [string ]interface {}
87
- err = json .Unmarshal (body , & dat )
88
- if err != nil {
89
- return nil , errors .Wrap (err , tr ("error processing response from server" ))
90
- }
93
+ res , err := httpClient .Do (req )
94
+ if err != nil {
95
+ return nil , errors .Wrap (err , tr ("error querying Arduino Cloud Api" ))
96
+ }
97
+ if res .StatusCode == 404 {
98
+ // This is not an error, it just means that the board is not recognized
99
+ return nil , nil
100
+ }
101
+ if res .StatusCode >= 400 {
102
+ return nil , errors .Errorf (tr ("the server responded with status %s" ), res .Status )
103
+ }
91
104
92
- name , nameFound := dat ["name" ].(string )
93
- fqbn , fbqnFound := dat ["fqbn" ].(string )
105
+ resp , err := io .ReadAll (res .Body )
106
+ if err != nil {
107
+ return nil , err
108
+ }
109
+ if err := res .Body .Close (); err != nil {
110
+ return nil , err
111
+ }
94
112
95
- if ! nameFound || ! fbqnFound {
96
- return nil , errors .New (tr ("wrong format in server response" ))
97
- }
113
+ var dat map [string ]interface {}
114
+ if err := json .Unmarshal (resp , & dat ); err != nil {
115
+ return nil , errors .Wrap (err , tr ("error processing response from server" ))
116
+ }
117
+ name , nameFound := dat ["name" ].(string )
118
+ fqbn , fbqnFound := dat ["fqbn" ].(string )
119
+ if ! nameFound || ! fbqnFound {
120
+ return nil , errors .New (tr ("wrong format in server response" ))
121
+ }
98
122
99
- retVal = append (retVal , & rpc.BoardListItem {
123
+ return []* rpc.BoardListItem {
124
+ {
100
125
Name : name ,
101
126
Fqbn : fqbn ,
102
- })
103
- } else {
104
- return nil , errors .Wrap (err , tr ("error querying Arduino Cloud Api" ))
105
- }
106
-
107
- return retVal , nil
127
+ },
128
+ }, nil
108
129
}
109
130
110
131
func identifyViaCloudAPI (port * discovery.Port ) ([]* rpc.BoardListItem , error ) {
111
132
// If the port is not USB do not try identification via cloud
112
133
id := port .Properties
113
134
if ! id .ContainsKey ("vid" ) || ! id .ContainsKey ("pid" ) {
114
- return nil , ErrNotFound
135
+ return nil , nil
115
136
}
116
137
117
138
logrus .Debug ("Querying builder API for board identification..." )
118
- return apiByVidPid (id .Get ("vid" ), id .Get ("pid" ))
139
+ return cachedAPIByVidPid (id .Get ("vid" ), id .Get ("pid" ))
119
140
}
120
141
121
142
// identify returns a list of boards checking first the installed platforms or the Cloud API
@@ -146,17 +167,10 @@ func identify(pme *packagemanager.Explorer, port *discovery.Port) ([]*rpc.BoardL
146
167
// the builder API if the board is a USB device port
147
168
if len (boards ) == 0 {
148
169
items , err := identifyViaCloudAPI (port )
149
- if errors .Is (err , ErrNotFound ) {
150
- // the board couldn't be detected, print a warning
151
- logrus .Debug ("Board not recognized" )
152
- } else if err != nil {
153
- // this is bad, bail out
154
- return nil , & arduino.UnavailableError {Message : tr ("Error getting board info from Arduino Cloud" )}
170
+ if err != nil {
171
+ // this is bad, but keep going
172
+ logrus .WithError (err ).Debug ("Error querying builder API" )
155
173
}
156
-
157
- // add a DetectedPort entry in any case: the `Boards` field will
158
- // be empty but the port will be shown anyways (useful for 3rd party
159
- // boards)
160
174
boards = items
161
175
}
162
176
0 commit comments