Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6faca0f

Browse files
authoredMar 22, 2024
[breaking] gRPC UpdateIndex and UpdateLibrariesIndex improvements (#2569)
* gRPC UpdateIndex and UpdateLibrariesIndex improvements The two calls now have the update_if_older_than_secs field that allows to avoid updating the index if it has been already updated. Also the response is more explicit with oneof(..) clause and the status of each update (in case of multiple index update) is returned in the response. * Do not make any output in case of skipped/already-up-to-date * Added json output to 'core update index' * Added json output to 'lib update index' * Removed unused function * Workaround for Windows paths in URI
1 parent ad9936f commit 6faca0f

File tree

12 files changed

+1457
-822
lines changed

12 files changed

+1457
-822
lines changed
 

‎commands/daemon/daemon.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,36 @@ func (s *ArduinoCoreServerImpl) Destroy(ctx context.Context, req *rpc.DestroyReq
121121
// UpdateIndex FIXMEDOC
122122
func (s *ArduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream rpc.ArduinoCoreService_UpdateIndexServer) error {
123123
syncSend := NewSynchronizedSend(stream.Send)
124-
err := commands.UpdateIndex(stream.Context(), req,
125-
func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.UpdateIndexResponse{DownloadProgress: p}) },
124+
res, err := commands.UpdateIndex(stream.Context(), req,
125+
func(p *rpc.DownloadProgress) {
126+
syncSend.Send(&rpc.UpdateIndexResponse{
127+
Message: &rpc.UpdateIndexResponse_DownloadProgress{DownloadProgress: p},
128+
})
129+
},
126130
)
131+
if res != nil {
132+
syncSend.Send(&rpc.UpdateIndexResponse{
133+
Message: &rpc.UpdateIndexResponse_Result_{Result: res},
134+
})
135+
}
127136
return convertErrorToRPCStatus(err)
128137
}
129138

130139
// UpdateLibrariesIndex FIXMEDOC
131140
func (s *ArduinoCoreServerImpl) UpdateLibrariesIndex(req *rpc.UpdateLibrariesIndexRequest, stream rpc.ArduinoCoreService_UpdateLibrariesIndexServer) error {
132141
syncSend := NewSynchronizedSend(stream.Send)
133-
err := commands.UpdateLibrariesIndex(stream.Context(), req,
134-
func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.UpdateLibrariesIndexResponse{DownloadProgress: p}) },
142+
res, err := commands.UpdateLibrariesIndex(stream.Context(), req,
143+
func(p *rpc.DownloadProgress) {
144+
syncSend.Send(&rpc.UpdateLibrariesIndexResponse{
145+
Message: &rpc.UpdateLibrariesIndexResponse_DownloadProgress{DownloadProgress: p},
146+
})
147+
},
135148
)
149+
if res != nil {
150+
syncSend.Send(&rpc.UpdateLibrariesIndexResponse{
151+
Message: &rpc.UpdateLibrariesIndexResponse_Result_{Result: res},
152+
})
153+
}
136154
return convertErrorToRPCStatus(err)
137155
}
138156

‎commands/instances.go

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"fmt"
2121
"net/url"
2222
"path/filepath"
23+
"runtime"
2324
"strings"
25+
"time"
2426

2527
"github.com/arduino/arduino-cli/commands/cmderrors"
2628
"github.com/arduino/arduino-cli/commands/internal/instances"
@@ -406,30 +408,59 @@ func Destroy(ctx context.Context, req *rpc.DestroyRequest) (*rpc.DestroyResponse
406408
}
407409

408410
// UpdateLibrariesIndex updates the library_index.json
409-
func UpdateLibrariesIndex(ctx context.Context, req *rpc.UpdateLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB) error {
411+
func UpdateLibrariesIndex(ctx context.Context, req *rpc.UpdateLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB) (*rpc.UpdateLibrariesIndexResponse_Result, error) {
410412
logrus.Info("Updating libraries index")
413+
411414
pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
412415
if err != nil {
413-
return err
416+
return nil, err
414417
}
415418
indexDir := pme.IndexDir
416419
release()
417420

421+
index := globals.LibrariesIndexResource
422+
result := func(status rpc.IndexUpdateReport_Status) *rpc.UpdateLibrariesIndexResponse_Result {
423+
return &rpc.UpdateLibrariesIndexResponse_Result{
424+
LibrariesIndex: &rpc.IndexUpdateReport{
425+
IndexUrl: globals.LibrariesIndexResource.URL.String(),
426+
Status: status,
427+
},
428+
}
429+
}
430+
431+
// Create the index directory if it doesn't exist
418432
if err := indexDir.MkdirAll(); err != nil {
419-
return &cmderrors.PermissionDeniedError{Message: tr("Could not create index directory"), Cause: err}
433+
return result(rpc.IndexUpdateReport_STATUS_FAILED), &cmderrors.PermissionDeniedError{Message: tr("Could not create index directory"), Cause: err}
434+
}
435+
436+
// Check if the index file is already up-to-date
437+
indexFileName, _ := index.IndexFileName()
438+
if info, err := indexDir.Join(indexFileName).Stat(); err == nil {
439+
ageSecs := int64(time.Since(info.ModTime()).Seconds())
440+
if ageSecs < req.GetUpdateIfOlderThanSecs() {
441+
return result(rpc.IndexUpdateReport_STATUS_ALREADY_UP_TO_DATE), nil
442+
}
420443
}
421444

445+
// Perform index update
422446
if err := globals.LibrariesIndexResource.Download(indexDir, downloadCB); err != nil {
423-
return err
447+
return nil, err
424448
}
425449

426-
return nil
450+
return result(rpc.IndexUpdateReport_STATUS_UPDATED), nil
427451
}
428452

429453
// UpdateIndex FIXMEDOC
430-
func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB) error {
454+
func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB) (*rpc.UpdateIndexResponse_Result, error) {
431455
if !instances.IsValid(req.GetInstance()) {
432-
return &cmderrors.InvalidInstanceError{}
456+
return nil, &cmderrors.InvalidInstanceError{}
457+
}
458+
459+
report := func(indexURL *url.URL, status rpc.IndexUpdateReport_Status) *rpc.IndexUpdateReport {
460+
return &rpc.IndexUpdateReport{
461+
IndexUrl: indexURL.String(),
462+
Status: status,
463+
}
433464
}
434465

435466
indexpath := configuration.DataDir(configuration.Settings)
@@ -440,46 +471,75 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rp
440471
}
441472

442473
failed := false
474+
result := &rpc.UpdateIndexResponse_Result{}
443475
for _, u := range urls {
444-
URL, err := utils.URLParse(u)
476+
URL, err := url.Parse(u)
445477
if err != nil {
446478
logrus.Warnf("unable to parse additional URL: %s", u)
447479
msg := fmt.Sprintf("%s: %v", tr("Unable to parse URL"), err)
448480
downloadCB.Start(u, tr("Downloading index: %s", u))
449481
downloadCB.End(false, msg)
450482
failed = true
483+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_FAILED))
451484
continue
452485
}
453486

454487
logrus.WithField("url", URL).Print("Updating index")
455488

456489
if URL.Scheme == "file" {
457-
downloadCB.Start(u, tr("Downloading index: %s", filepath.Base(URL.Path)))
458490
path := paths.New(URL.Path)
491+
if URL.Scheme == "file" && runtime.GOOS == "windows" && len(URL.Path) > 1 {
492+
// https://github.com/golang/go/issues/32456
493+
// Parsed local file URLs on Windows are returned with a leading / so we remove it
494+
path = paths.New(URL.Path[1:])
495+
}
459496
if _, err := packageindex.LoadIndexNoSign(path); err != nil {
460497
msg := fmt.Sprintf("%s: %v", tr("Invalid package index in %s", path), err)
498+
downloadCB.Start(u, tr("Downloading index: %s", filepath.Base(URL.Path)))
461499
downloadCB.End(false, msg)
462500
failed = true
501+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_FAILED))
463502
} else {
464-
downloadCB.End(true, "")
503+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_SKIPPED))
465504
}
466505
continue
467506
}
468507

508+
// Check if the index is up-to-date
469509
indexResource := resources.IndexResource{URL: URL}
510+
indexFileName, err := indexResource.IndexFileName()
511+
if err != nil {
512+
downloadCB.Start(u, tr("Downloading index: %s", filepath.Base(URL.Path)))
513+
downloadCB.End(false, tr("Invalid index URL: %s", err))
514+
failed = true
515+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_FAILED))
516+
continue
517+
}
518+
indexFile := indexpath.Join(indexFileName)
519+
if info, err := indexFile.Stat(); err == nil {
520+
ageSecs := int64(time.Since(info.ModTime()).Seconds())
521+
if ageSecs < req.GetUpdateIfOlderThanSecs() {
522+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_ALREADY_UP_TO_DATE))
523+
continue
524+
}
525+
}
526+
470527
if strings.HasSuffix(URL.Host, "arduino.cc") && strings.HasSuffix(URL.Path, ".json") {
471528
indexResource.SignatureURL, _ = url.Parse(u) // should not fail because we already parsed it
472529
indexResource.SignatureURL.Path += ".sig"
473530
}
474531
if err := indexResource.Download(indexpath, downloadCB); err != nil {
475532
failed = true
533+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_FAILED))
534+
} else {
535+
result.UpdatedIndexes = append(result.UpdatedIndexes, report(URL, rpc.IndexUpdateReport_STATUS_UPDATED))
476536
}
477537
}
478538

479539
if failed {
480-
return &cmderrors.FailedDownloadError{Message: tr("Some indexes could not be updated.")}
540+
return result, &cmderrors.FailedDownloadError{Message: tr("Some indexes could not be updated.")}
481541
}
482-
return nil
542+
return result, nil
483543
}
484544

485545
// firstUpdate downloads libraries and packages indexes if they don't exist.
@@ -493,7 +553,7 @@ func firstUpdate(ctx context.Context, instance *rpc.Instance, downloadCb func(ms
493553
// The library_index.json file doesn't exists, that means the CLI is run for the first time
494554
// so we proceed with the first update that downloads the file
495555
req := &rpc.UpdateLibrariesIndexRequest{Instance: instance}
496-
if err := UpdateLibrariesIndex(ctx, req, downloadCb); err != nil {
556+
if _, err := UpdateLibrariesIndex(ctx, req, downloadCb); err != nil {
497557
return err
498558
}
499559
}
@@ -515,7 +575,7 @@ func firstUpdate(ctx context.Context, instance *rpc.Instance, downloadCb func(ms
515575
// library update we download that file and all the other package indexes from
516576
// additional_urls
517577
req := &rpc.UpdateIndexRequest{Instance: instance}
518-
if err := UpdateIndex(ctx, req, downloadCb); err != nil {
578+
if _, err := UpdateIndex(ctx, req, downloadCb); err != nil {
519579
return err
520580
}
521581
break

‎docs/UPGRADING.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,76 @@ Here you can find a list of migration guides to handle breaking changes between
44

55
## 0.36.0
66

7+
### The gRPC `cc.arduino.cli.commands.v1.UpdateIndexResponse` and `UpdateLibrariesIndexResponse` have changed.
8+
9+
The responses coming from the update index commands:
10+
11+
```proto
12+
message UpdateIndexResponse {
13+
// Progress of the package index download.
14+
DownloadProgress download_progress = 1;
15+
}
16+
17+
message UpdateLibrariesIndexResponse {
18+
// Progress of the libraries index download.
19+
DownloadProgress download_progress = 1;
20+
}
21+
```
22+
23+
are now more explicit and contains details about the result of the operation:
24+
25+
```proto
26+
message UpdateIndexResponse {
27+
message Result {
28+
// The result of the packages index update.
29+
repeated IndexUpdateReport updated_indexes = 1;
30+
}
31+
oneof message {
32+
// Progress of the package index download.
33+
DownloadProgress download_progress = 1;
34+
// The result of the index update.
35+
Result result = 2;
36+
}
37+
}
38+
39+
message UpdateLibrariesIndexResponse {
40+
message Result {
41+
// The result of the libraries index update.
42+
IndexUpdateReport libraries_index = 1;
43+
}
44+
oneof message {
45+
// Progress of the libraries index download.
46+
DownloadProgress download_progress = 1;
47+
// The result of the index update.
48+
Result result = 2;
49+
}
50+
}
51+
```
52+
53+
The `IndexUpdateReport` message contains details for each index update operation performed:
54+
55+
```proto
56+
message IndexUpdateReport {
57+
enum Status {
58+
// The status of the index update is unspecified.
59+
STATUS_UNSPECIFIED = 0;
60+
// The index has been successfully updated.
61+
STATUS_UPDATED = 1;
62+
// The index was already up to date.
63+
STATUS_ALREADY_UP_TO_DATE = 2;
64+
// The index update failed.
65+
STATUS_FAILED = 3;
66+
// The index update was skipped.
67+
STATUS_SKIPPED = 4;
68+
}
69+
70+
// The URL of the index that was updated.
71+
string index_url = 1;
72+
// The result of the index update.
73+
Status status = 2;
74+
}
75+
```
76+
777
### The gRPC `cc.arduino.cli.commands.v1.Profile` message has been removed in favor of `SketchProfile`
878

979
The message `Profile` has been replaced with `SketchProfile` in the `InitResponse.profile` field:

‎internal/cli/core/search.go

Lines changed: 13 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,11 @@ import (
1919
"context"
2020
"fmt"
2121
"os"
22-
"path"
2322
"strings"
2423
"time"
2524

2625
"github.com/arduino/arduino-cli/commands"
2726
"github.com/arduino/arduino-cli/commands/core"
28-
"github.com/arduino/arduino-cli/internal/arduino/globals"
29-
"github.com/arduino/arduino-cli/internal/arduino/utils"
30-
"github.com/arduino/arduino-cli/internal/cli/configuration"
3127
"github.com/arduino/arduino-cli/internal/cli/feedback"
3228
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
3329
"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -55,17 +51,24 @@ func initSearchCommand() *cobra.Command {
5551
}
5652

5753
// indexUpdateInterval specifies the time threshold over which indexes are updated
58-
const indexUpdateInterval = "24h"
54+
const indexUpdateInterval = 24 * time.Hour
5955

6056
func runSearchCommand(cmd *cobra.Command, args []string, allVersions bool) {
6157
inst := instance.CreateAndInit()
6258

63-
if indexesNeedUpdating(indexUpdateInterval) {
64-
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar())
65-
if err != nil {
66-
feedback.FatalError(err, feedback.ErrGeneric)
59+
res, err := commands.UpdateIndex(
60+
context.Background(),
61+
&rpc.UpdateIndexRequest{Instance: inst, UpdateIfOlderThanSecs: int64(indexUpdateInterval.Seconds())},
62+
feedback.ProgressBar())
63+
if err != nil {
64+
feedback.FatalError(err, feedback.ErrGeneric)
65+
}
66+
for _, idxRes := range res.GetUpdatedIndexes() {
67+
if idxRes.GetStatus() == rpc.IndexUpdateReport_STATUS_UPDATED {
68+
// At least one index has been updated, reinitialize the instance
69+
instance.Init(inst)
70+
break
6771
}
68-
instance.Init(inst)
6972
}
7073

7174
arguments := strings.ToLower(strings.Join(args, " "))
@@ -134,58 +137,3 @@ func (sr searchResults) String() string {
134137
}
135138
return t.Render()
136139
}
137-
138-
// indexesNeedUpdating returns whether one or more index files need updating.
139-
// A duration string must be provided to calculate the time threshold
140-
// used to update the indexes, if the duration is not valid a default
141-
// of 24 hours is used.
142-
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
143-
func indexesNeedUpdating(duration string) bool {
144-
indexpath := configuration.DataDir(configuration.Settings)
145-
146-
now := time.Now()
147-
modTimeThreshold, err := time.ParseDuration(duration)
148-
if err != nil {
149-
feedback.Fatal(tr("Invalid timeout: %s", err), feedback.ErrBadArgument)
150-
}
151-
152-
urls := []string{globals.DefaultIndexURL}
153-
urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...)
154-
for _, u := range urls {
155-
URL, err := utils.URLParse(u)
156-
if err != nil {
157-
continue
158-
}
159-
160-
if URL.Scheme == "file" {
161-
// No need to update local files
162-
continue
163-
}
164-
165-
// should handle:
166-
// - package_index.json
167-
// - package_index.json.sig
168-
// - package_index.json.gz
169-
// - package_index.tar.bz2
170-
indexFileName := path.Base(URL.Path)
171-
indexFileName = strings.TrimSuffix(indexFileName, ".tar.bz2")
172-
indexFileName = strings.TrimSuffix(indexFileName, ".gz")
173-
indexFileName = strings.TrimSuffix(indexFileName, ".sig")
174-
indexFileName = strings.TrimSuffix(indexFileName, ".json")
175-
// and obtain package_index.json as result
176-
coreIndexPath := indexpath.Join(indexFileName + ".json")
177-
if coreIndexPath.NotExist() {
178-
return true
179-
}
180-
181-
info, err := coreIndexPath.Stat()
182-
if err != nil {
183-
return true
184-
}
185-
186-
if now.After(info.ModTime().Add(modTimeThreshold)) {
187-
return true
188-
}
189-
}
190-
return false
191-
}

‎internal/cli/core/update_index.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/arduino/arduino-cli/commands"
2323
"github.com/arduino/arduino-cli/internal/cli/feedback"
24+
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
2425
"github.com/arduino/arduino-cli/internal/cli/instance"
2526
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2627
"github.com/sirupsen/logrus"
@@ -42,13 +43,28 @@ func initUpdateIndexCommand() *cobra.Command {
4243
func runUpdateIndexCommand(cmd *cobra.Command, args []string) {
4344
inst := instance.CreateAndInit()
4445
logrus.Info("Executing `arduino-cli core update-index`")
45-
UpdateIndex(inst)
46+
resp := UpdateIndex(inst)
47+
48+
feedback.PrintResult(&updateIndexResult{result.NewUpdateIndexResponse_ResultResult(resp)})
4649
}
4750

4851
// UpdateIndex updates the index of platforms.
49-
func UpdateIndex(inst *rpc.Instance) {
50-
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar())
52+
func UpdateIndex(inst *rpc.Instance) *rpc.UpdateIndexResponse_Result {
53+
res, err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar())
5154
if err != nil {
5255
feedback.FatalError(err, feedback.ErrGeneric)
5356
}
57+
return res
58+
}
59+
60+
type updateIndexResult struct {
61+
*result.UpdateIndexResponse_ResultResult
62+
}
63+
64+
func (r *updateIndexResult) Data() interface{} {
65+
return r
66+
}
67+
68+
func (r *updateIndexResult) String() string {
69+
return ""
5470
}

‎internal/cli/feedback/result/rpc.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,3 +1065,62 @@ func NewIsDebugSupportedResponse(resp *rpc.IsDebugSupportedResponse) *IsDebugSup
10651065
DebugFQBN: resp.GetDebugFqbn(),
10661066
}
10671067
}
1068+
1069+
type UpdateIndexResponse_ResultResult struct {
1070+
UpdatedIndexes []*IndexUpdateReportResult `json:"updated_indexes,omitempty"`
1071+
}
1072+
1073+
func NewUpdateIndexResponse_ResultResult(resp *rpc.UpdateIndexResponse_Result) *UpdateIndexResponse_ResultResult {
1074+
return &UpdateIndexResponse_ResultResult{
1075+
UpdatedIndexes: f.Map(resp.GetUpdatedIndexes(), NewIndexUpdateReportResult),
1076+
}
1077+
}
1078+
1079+
type UpdateLibrariesIndexResponse_ResultResult struct {
1080+
LibrariesIndex *IndexUpdateReportResult `json:"libraries_index"`
1081+
}
1082+
1083+
func NewUpdateLibrariesIndexResponse_ResultResult(resp *rpc.UpdateLibrariesIndexResponse_Result) *UpdateLibrariesIndexResponse_ResultResult {
1084+
return &UpdateLibrariesIndexResponse_ResultResult{
1085+
LibrariesIndex: NewIndexUpdateReportResult(resp.GetLibrariesIndex()),
1086+
}
1087+
}
1088+
1089+
type IndexUpdateReportResult struct {
1090+
IndexURL string `json:"index_url"`
1091+
Status IndexUpdateReport_Status `json:"status"`
1092+
}
1093+
1094+
func NewIndexUpdateReportResult(resp *rpc.IndexUpdateReport) *IndexUpdateReportResult {
1095+
return &IndexUpdateReportResult{
1096+
IndexURL: resp.GetIndexUrl(),
1097+
Status: NewIndexUpdateReport_Status(resp.GetStatus()),
1098+
}
1099+
}
1100+
1101+
type IndexUpdateReport_Status string
1102+
1103+
const (
1104+
IndexUpdateReport_StatusUnspecified IndexUpdateReport_Status = "unspecified"
1105+
IndexUpdateReport_StatusAlreadyUpToDate IndexUpdateReport_Status = "already-up-to-date"
1106+
IndexUpdateReport_StatusFailed IndexUpdateReport_Status = "failed"
1107+
IndexUpdateReport_StatusSkipped IndexUpdateReport_Status = "skipped"
1108+
IndexUpdateReport_StatusUpdated IndexUpdateReport_Status = "updated"
1109+
)
1110+
1111+
func NewIndexUpdateReport_Status(r rpc.IndexUpdateReport_Status) IndexUpdateReport_Status {
1112+
switch r {
1113+
case rpc.IndexUpdateReport_STATUS_UNSPECIFIED:
1114+
return IndexUpdateReport_StatusUnspecified
1115+
case rpc.IndexUpdateReport_STATUS_UPDATED:
1116+
return IndexUpdateReport_StatusUpdated
1117+
case rpc.IndexUpdateReport_STATUS_ALREADY_UP_TO_DATE:
1118+
return IndexUpdateReport_StatusAlreadyUpToDate
1119+
case rpc.IndexUpdateReport_STATUS_FAILED:
1120+
return IndexUpdateReport_StatusFailed
1121+
case rpc.IndexUpdateReport_STATUS_SKIPPED:
1122+
return IndexUpdateReport_StatusSkipped
1123+
default:
1124+
return IndexUpdateReport_StatusUnspecified
1125+
}
1126+
}

‎internal/cli/feedback/result/rpc_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,18 @@ func TestAllFieldAreMapped(t *testing.T) {
221221
isDebugSupportedResponseRpc := &rpc.IsDebugSupportedResponse{}
222222
isDebugSupportedResponseResult := result.NewIsDebugSupportedResponse(isDebugSupportedResponseRpc)
223223
mustContainsAllPropertyOfRpcStruct(t, isDebugSupportedResponseRpc, isDebugSupportedResponseResult)
224+
225+
updateIndexResponse_ResultRpc := &rpc.UpdateIndexResponse_Result{}
226+
updateIndexResponse_ResultResult := result.NewUpdateIndexResponse_ResultResult(updateIndexResponse_ResultRpc)
227+
mustContainsAllPropertyOfRpcStruct(t, updateIndexResponse_ResultRpc, updateIndexResponse_ResultResult)
228+
229+
updateLibrariesIndexResponse_ResultRpc := &rpc.UpdateLibrariesIndexResponse_Result{}
230+
updateLibrariesIndexResponse_ResultResult := result.NewUpdateLibrariesIndexResponse_ResultResult(updateLibrariesIndexResponse_ResultRpc)
231+
mustContainsAllPropertyOfRpcStruct(t, updateLibrariesIndexResponse_ResultRpc, updateLibrariesIndexResponse_ResultResult)
232+
233+
indexUpdateReportRpc := &rpc.IndexUpdateReport{}
234+
indexUpdateReportResult := result.NewIndexUpdateReportResult(indexUpdateReportRpc)
235+
mustContainsAllPropertyOfRpcStruct(t, indexUpdateReportRpc, indexUpdateReportResult)
224236
}
225237

226238
func TestEnumsMapsEveryRpcCounterpart(t *testing.T) {
@@ -251,6 +263,15 @@ func TestEnumsMapsEveryRpcCounterpart(t *testing.T) {
251263
require.Len(t, results, len(rpc.LibrarySearchStatus_name))
252264
require.True(t, isUnique(results))
253265
})
266+
t.Run("IndexUpdateReport_Status enums maps every element", func(t *testing.T) {
267+
results := make([]result.IndexUpdateReport_Status, 0, len(rpc.IndexUpdateReport_Status_name))
268+
for key := range rpc.IndexUpdateReport_Status_name {
269+
results = append(results, result.NewIndexUpdateReport_Status(rpc.IndexUpdateReport_Status(key)))
270+
}
271+
require.NotEmpty(t, results)
272+
require.Len(t, results, len(rpc.IndexUpdateReport_Status_name))
273+
require.True(t, isUnique(results))
274+
})
254275
}
255276

256277
func isUnique[T comparable](s []T) bool {

‎internal/cli/lib/search.go

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ import (
2424

2525
"github.com/arduino/arduino-cli/commands"
2626
"github.com/arduino/arduino-cli/commands/lib"
27-
"github.com/arduino/arduino-cli/internal/cli/configuration"
2827
"github.com/arduino/arduino-cli/internal/cli/feedback"
2928
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
3029
"github.com/arduino/arduino-cli/internal/cli/instance"
3130
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
32-
"github.com/arduino/go-paths-helper"
3331
"github.com/sirupsen/logrus"
3432
"github.com/spf13/cobra"
3533
)
@@ -109,14 +107,15 @@ func runSearchCommand(args []string, namesOnly bool, omitReleasesDetails bool) {
109107

110108
logrus.Info("Executing `arduino-cli lib search`")
111109

112-
if indexNeedsUpdating(indexUpdateInterval) {
113-
if err := commands.UpdateLibrariesIndex(
114-
context.Background(),
115-
&rpc.UpdateLibrariesIndexRequest{Instance: inst},
116-
feedback.ProgressBar(),
117-
); err != nil {
118-
feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric)
119-
}
110+
res, err := commands.UpdateLibrariesIndex(
111+
context.Background(),
112+
&rpc.UpdateLibrariesIndexRequest{Instance: inst, UpdateIfOlderThanSecs: int64(indexUpdateInterval.Seconds())},
113+
feedback.ProgressBar(),
114+
)
115+
if err != nil {
116+
feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric)
117+
}
118+
if res.GetLibrariesIndex().GetStatus() == rpc.IndexUpdateReport_STATUS_UPDATED {
120119
instance.Init(inst)
121120
}
122121

@@ -221,20 +220,3 @@ func (res librarySearchResult) String() string {
221220

222221
return out.String()
223222
}
224-
225-
// indexNeedsUpdating returns whether library_index.json needs updating
226-
func indexNeedsUpdating(timeout time.Duration) bool {
227-
// Library index path is constant (relative to the data directory).
228-
// It does not depend on board manager URLs or any other configuration.
229-
dataDir := configuration.Settings.GetString("directories.Data")
230-
indexPath := paths.New(dataDir).Join("library_index.json")
231-
// Verify the index file exists and we can read its fstat attrs.
232-
if indexPath.NotExist() {
233-
return true
234-
}
235-
info, err := indexPath.Stat()
236-
if err != nil {
237-
return true
238-
}
239-
return time.Since(info.ModTime()) > timeout
240-
}

‎internal/cli/lib/update_index.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/arduino/arduino-cli/commands"
2323
"github.com/arduino/arduino-cli/internal/cli/feedback"
24+
"github.com/arduino/arduino-cli/internal/cli/feedback/result"
2425
"github.com/arduino/arduino-cli/internal/cli/instance"
2526
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2627
"github.com/sirupsen/logrus"
@@ -42,15 +43,29 @@ func initUpdateIndexCommand() *cobra.Command {
4243
func runUpdateIndexCommand(cmd *cobra.Command, args []string) {
4344
inst := instance.CreateAndInit()
4445
logrus.Info("Executing `arduino-cli lib update-index`")
45-
UpdateIndex(inst)
46+
resp := UpdateIndex(inst)
47+
feedback.PrintResult(&libUpdateIndexResult{result.NewUpdateLibrariesIndexResponse_ResultResult(resp)})
4648
}
4749

4850
// UpdateIndex updates the index of libraries.
49-
func UpdateIndex(inst *rpc.Instance) {
50-
err := commands.UpdateLibrariesIndex(context.Background(), &rpc.UpdateLibrariesIndexRequest{
51+
func UpdateIndex(inst *rpc.Instance) *rpc.UpdateLibrariesIndexResponse_Result {
52+
resp, err := commands.UpdateLibrariesIndex(context.Background(), &rpc.UpdateLibrariesIndexRequest{
5153
Instance: inst,
5254
}, feedback.ProgressBar())
5355
if err != nil {
5456
feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric)
5557
}
58+
return resp
59+
}
60+
61+
type libUpdateIndexResult struct {
62+
*result.UpdateLibrariesIndexResponse_ResultResult
63+
}
64+
65+
func (l *libUpdateIndexResult) String() string {
66+
return ""
67+
}
68+
69+
func (l *libUpdateIndexResult) Data() interface{} {
70+
return l
5671
}

‎internal/integrationtest/core/core_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,9 @@ func TestCoreUpdateWithLocalUrl(t *testing.T) {
445445
testIndex = "/" + strings.ReplaceAll(testIndex, "\\", "/")
446446
}
447447

448-
stdout, _, err := cli.Run("core", "update-index", "--additional-urls=file://"+testIndex)
448+
stdout, _, err := cli.Run("core", "update-index", "--additional-urls=file://"+testIndex, "--format", "json")
449449
require.NoError(t, err)
450-
require.Contains(t, string(stdout), "Downloading index: test_index.json downloaded")
450+
requirejson.Parse(t, stdout).MustContain(`{"updated_indexes":[{"index_url":"file://` + testIndex + `","status":"skipped"}]}`)
451451
}
452452

453453
func TestCoreSearchManuallyInstalledCoresNotPrinted(t *testing.T) {

‎rpc/cc/arduino/cli/commands/v1/commands.pb.go

Lines changed: 1104 additions & 700 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎rpc/cc/arduino/cli/commands/v1/commands.proto

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,21 +276,63 @@ message UpdateIndexRequest {
276276
Instance instance = 1;
277277
// If set to true user defined package indexes will not be updated.
278278
bool ignore_custom_package_indexes = 2;
279+
// Only perform index update if the index file is older than this value in
280+
// seconds.
281+
int64 update_if_older_than_secs = 3;
279282
}
280283

281284
message UpdateIndexResponse {
282-
// Progress of the package index download.
283-
DownloadProgress download_progress = 1;
285+
message Result {
286+
// The result of the packages index update.
287+
repeated IndexUpdateReport updated_indexes = 1;
288+
}
289+
oneof message {
290+
// Progress of the package index download.
291+
DownloadProgress download_progress = 1;
292+
// The result of the index update.
293+
Result result = 2;
294+
}
284295
}
285296

286297
message UpdateLibrariesIndexRequest {
287298
// Arduino Core Service instance from the Init response.
288299
Instance instance = 1;
300+
// Only perform index update if the index file is older than this value in
301+
// seconds.
302+
int64 update_if_older_than_secs = 2;
289303
}
290304

291305
message UpdateLibrariesIndexResponse {
292-
// Progress of the libraries index download.
293-
DownloadProgress download_progress = 1;
306+
message Result {
307+
// The result of the libraries index update.
308+
IndexUpdateReport libraries_index = 1;
309+
}
310+
oneof message {
311+
// Progress of the libraries index download.
312+
DownloadProgress download_progress = 1;
313+
// The result of the index update.
314+
Result result = 2;
315+
}
316+
}
317+
318+
message IndexUpdateReport {
319+
enum Status {
320+
// The status of the index update is unspecified.
321+
STATUS_UNSPECIFIED = 0;
322+
// The index has been successfully updated.
323+
STATUS_UPDATED = 1;
324+
// The index was already up to date.
325+
STATUS_ALREADY_UP_TO_DATE = 2;
326+
// The index update failed.
327+
STATUS_FAILED = 3;
328+
// The index update was skipped.
329+
STATUS_SKIPPED = 4;
330+
}
331+
332+
// The URL of the index that was updated.
333+
string index_url = 1;
334+
// The result of the index update.
335+
Status status = 2;
294336
}
295337

296338
message VersionRequest {}

0 commit comments

Comments
 (0)
Please sign in to comment.