@@ -19,7 +19,7 @@ import (
1919 "context"
2020 "encoding/json"
2121 "fmt"
22- "io/ioutil "
22+ "io"
2323 "net/http"
2424 "regexp"
2525 "sort"
@@ -32,24 +32,43 @@ import (
3232 "github.com/arduino/arduino-cli/arduino/discovery"
3333 "github.com/arduino/arduino-cli/arduino/httpclient"
3434 "github.com/arduino/arduino-cli/commands"
35+ "github.com/arduino/arduino-cli/inventory"
3536 rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3637 "github.com/pkg/errors"
3738 "github.com/sirupsen/logrus"
3839)
3940
40- type boardNotFoundError struct {}
41-
42- func (e * boardNotFoundError ) Error () string {
43- return tr ("board not found" )
44- }
45-
4641var (
47- // ErrNotFound is returned when the API returns 404
48- ErrNotFound = & boardNotFoundError {}
4942 vidPidURL = "https://builder.arduino.cc/v3/boards/byVidPid"
5043 validVidPid = regexp .MustCompile (`0[xX][a-fA-F\d]{4}` )
5144)
5245
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+
5372func apiByVidPid (vid , pid string ) ([]* rpc.BoardListItem , error ) {
5473 // ensure vid and pid are valid before hitting the API
5574 if ! validVidPid .MatchString (vid ) {
@@ -60,7 +79,6 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
6079 }
6180
6281 url := fmt .Sprintf ("%s/%s/%s" , vidPidURL , vid , pid )
63- retVal := []* rpc.BoardListItem {}
6482 req , _ := http .NewRequest ("GET" , url , nil )
6583 req .Header .Set ("Content-Type" , "application/json" )
6684
@@ -72,50 +90,53 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
7290 return nil , errors .Wrap (err , tr ("failed to initialize http client" ))
7391 }
7492
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+ }
91104
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+ }
94112
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+ }
98122
99- retVal = append (retVal , & rpc.BoardListItem {
123+ return []* rpc.BoardListItem {
124+ {
100125 Name : name ,
101126 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
108129}
109130
110131func identifyViaCloudAPI (port * discovery.Port ) ([]* rpc.BoardListItem , error ) {
111132 // If the port is not USB do not try identification via cloud
112133 id := port .Properties
113134 if ! id .ContainsKey ("vid" ) || ! id .ContainsKey ("pid" ) {
114- return nil , ErrNotFound
135+ return nil , nil
115136 }
116137
117138 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" ))
119140}
120141
121142// 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
146167 // the builder API if the board is a USB device port
147168 if len (boards ) == 0 {
148169 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" )
155173 }
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)
160174 boards = items
161175 }
162176
0 commit comments