Unverified Commit c8ff0425 authored by Cristian Maglie's avatar Cristian Maglie Committed by GitHub

[breaking] Refactor `DownloadProgress` gRPC message: more clear field meaning. (#1875)

* Refactored DownloadProgress protocol

* Updated integration tests

* Do not create overly verbose errors

* Updated docs

* Update rpc/cc/arduino/cli/commands/v1/commands.proto
Co-authored-by: default avatarper1234 <accounts@perglass.com>
Co-authored-by: default avatarper1234 <accounts@perglass.com>
parent bcb69d99
......@@ -127,12 +127,7 @@ func (pme *Explorer) DownloadToolRelease(tool *cores.ToolRelease, config *downlo
Message: tr("Error downloading tool %s", tool),
Cause: errors.New(tr("no versions available for the current OS"))}
}
if err := resource.Download(pme.DownloadDir, config, tool.String(), progressCB); err != nil {
return &arduino.FailedDownloadError{
Message: tr("Error downloading tool %s", tool),
Cause: err}
}
return nil
return resource.Download(pme.DownloadDir, config, tool.String(), progressCB)
}
// DownloadPlatformRelease downloads a PlatformRelease. If the platform is already downloaded a
......
......@@ -32,7 +32,16 @@ var tr = i18n.Tr
// DownloadFile downloads a file from a URL into the specified path. An optional config and options may be passed (or nil to use the defaults).
// A DownloadProgressCB callback function must be passed to monitor download progress.
func DownloadFile(path *paths.Path, URL string, label string, downloadCB rpc.DownloadProgressCB, config *downloader.Config, options ...downloader.DownloadOptions) error {
func DownloadFile(path *paths.Path, URL string, label string, downloadCB rpc.DownloadProgressCB, config *downloader.Config, options ...downloader.DownloadOptions) (returnedError error) {
downloadCB.Start(URL, label)
defer func() {
if returnedError == nil {
downloadCB.End(true, "")
} else {
downloadCB.End(false, returnedError.Error())
}
}()
if config == nil {
c, err := GetDownloaderConfig()
if err != nil {
......@@ -45,15 +54,9 @@ func DownloadFile(path *paths.Path, URL string, label string, downloadCB rpc.Dow
if err != nil {
return err
}
downloadCB(&rpc.DownloadProgress{
File: label,
Url: d.URL,
TotalSize: d.Size(),
})
defer downloadCB(&rpc.DownloadProgress{Completed: true})
err = d.RunAndPoll(func(downloaded int64) {
downloadCB(&rpc.DownloadProgress{Downloaded: downloaded})
downloadCB.Update(downloaded, d.Size())
}, 250*time.Millisecond)
if err != nil {
return err
......@@ -61,7 +64,8 @@ func DownloadFile(path *paths.Path, URL string, label string, downloadCB rpc.Dow
// The URL is not reachable for some reason
if d.Resp.StatusCode >= 400 && d.Resp.StatusCode <= 599 {
return &arduino.FailedDownloadError{Message: tr("Server responded with: %s", d.Resp.Status)}
msg := tr("Server responded with: %s", d.Resp.Status)
return &arduino.FailedDownloadError{Message: msg}
}
return nil
......
......@@ -44,12 +44,8 @@ func (r *DownloadResource) Download(downloadDir *paths.Path, config *downloader.
}
} else {
// File is cached, nothing to do here
// This signal means that the file is already downloaded
downloadCB(&rpc.DownloadProgress{
File: label,
Completed: true,
})
downloadCB.Start(r.URL, label)
downloadCB.End(true, tr("%s already downloaded", label))
return nil
}
} else {
......
......@@ -67,9 +67,7 @@ func runSearchCommand(cmd *cobra.Command, args []string) {
}
if indexesNeedUpdating(indexUpdateInterval) {
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst},
output.ProgressBar(),
output.PrintErrorFromDownloadResult(tr("Error updating index")))
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, output.ProgressBar())
if err != nil {
os.Exit(errorcodes.ErrGeneric)
}
......
......@@ -20,6 +20,7 @@ import (
"os"
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/cli/output"
"github.com/arduino/arduino-cli/commands"
......@@ -44,10 +45,9 @@ func runUpdateIndexCommand(cmd *cobra.Command, args []string) {
inst := instance.CreateInstanceAndRunFirstUpdate()
logrus.Info("Executing `arduino-cli core update-index`")
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst},
output.ProgressBar(),
output.PrintErrorFromDownloadResult(tr("Error updating index")))
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, output.ProgressBar())
if err != nil {
feedback.Error(err)
os.Exit(errorcodes.ErrGeneric)
}
}
......@@ -157,8 +157,7 @@ func FirstUpdate(instance *rpc.Instance) error {
Instance: instance,
IgnoreCustomPackageIndexes: true,
},
output.ProgressBar(),
output.PrintErrorFromDownloadResult(tr("Error updating index")))
output.ProgressBar())
if err != nil {
return err
}
......
......@@ -17,6 +17,7 @@ package output
import (
"fmt"
"sync"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/i18n"
......@@ -41,21 +42,6 @@ func ProgressBar() rpc.DownloadProgressCB {
}
}
// PrintErrorFromDownloadResult returns a DownloadResultCB that only prints
// the errors with the give message prefixed.
func PrintErrorFromDownloadResult(msg string) rpc.DownloadResultCB {
if OutputFormat != "json" {
return func(res *rpc.DownloadResult) {
if !res.GetSuccessful() {
feedback.Errorf("%s: %s", msg, res.GetError())
}
}
}
return func(res *rpc.DownloadResult) {
// XXX: Output result in JSON?
}
}
// TaskProgress returns a TaskProgressCB that prints the task progress.
// If JSON output format has been selected, the callback outputs nothing.
func TaskProgress() rpc.TaskProgressCB {
......@@ -70,25 +56,39 @@ func TaskProgress() rpc.TaskProgressCB {
// NewDownloadProgressBarCB creates a progress bar callback that outputs a progress
// bar on the terminal
func NewDownloadProgressBarCB() func(*rpc.DownloadProgress) {
var mux sync.Mutex
var bar *pb.ProgressBar
var prefix string
var label string
started := false
return func(curr *rpc.DownloadProgress) {
mux.Lock()
defer mux.Unlock()
// fmt.Printf(">>> %v\n", curr)
if filename := curr.GetFile(); filename != "" {
if curr.GetCompleted() {
fmt.Println(tr("%s already downloaded", filename))
return
}
prefix = filename
bar = pb.StartNew(int(curr.GetTotalSize()))
bar.Prefix(prefix)
if start := curr.GetStart(); start != nil {
label = start.GetLabel()
bar = pb.New(0)
bar.Prefix(label)
bar.SetUnits(pb.U_BYTES)
}
if curr.GetDownloaded() != 0 {
bar.Set(int(curr.GetDownloaded()))
if update := curr.GetUpdate(); update != nil {
bar.SetTotal64(update.GetTotalSize())
if !started {
bar.Start()
started = true
}
bar.Set64(update.GetDownloaded())
}
if curr.GetCompleted() {
bar.FinishPrintOver(tr("%s downloaded", prefix))
if end := curr.GetEnd(); end != nil {
msg := end.GetMessage()
if end.GetSuccess() && msg == "" {
msg = tr("downloaded")
}
if started {
bar.FinishPrintOver(label + " " + msg)
} else {
feedback.Print(label + " " + msg)
}
}
}
}
......
......@@ -56,9 +56,9 @@ func runUpdateCommand(cmd *cobra.Command, args []string) {
inst := instance.CreateInstanceAndRunFirstUpdate()
logrus.Info("Executing `arduino-cli update`")
err := commands.UpdateCoreLibrariesIndex(context.Background(), &rpc.UpdateCoreLibrariesIndexRequest{Instance: inst},
output.ProgressBar(),
output.PrintErrorFromDownloadResult(tr("Error updating index")))
err := commands.UpdateCoreLibrariesIndex(context.Background(),
&rpc.UpdateCoreLibrariesIndexRequest{Instance: inst},
output.ProgressBar())
if err != nil {
feedback.Errorf(tr("Error updating core and libraries index: %v"), err)
os.Exit(errorcodes.ErrGeneric)
......
......@@ -162,12 +162,7 @@ func (s *ArduinoCoreServerImpl) Destroy(ctx context.Context, req *rpc.DestroyReq
// UpdateIndex FIXMEDOC
func (s *ArduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream rpc.ArduinoCoreService_UpdateIndexServer) error {
err := commands.UpdateIndex(stream.Context(), req,
func(p *rpc.DownloadProgress) {
stream.Send(&rpc.UpdateIndexResponse{Message: &rpc.UpdateIndexResponse_DownloadProgress{DownloadProgress: p}})
},
func(r *rpc.DownloadResult) {
stream.Send(&rpc.UpdateIndexResponse{Message: &rpc.UpdateIndexResponse_DownloadResult{DownloadResult: r}})
},
func(p *rpc.DownloadProgress) { stream.Send(&rpc.UpdateIndexResponse{DownloadProgress: p}) },
)
return convertErrorToRPCStatus(err)
}
......@@ -187,7 +182,6 @@ func (s *ArduinoCoreServerImpl) UpdateLibrariesIndex(req *rpc.UpdateLibrariesInd
func (s *ArduinoCoreServerImpl) UpdateCoreLibrariesIndex(req *rpc.UpdateCoreLibrariesIndexRequest, stream rpc.ArduinoCoreService_UpdateCoreLibrariesIndexServer) error {
err := commands.UpdateCoreLibrariesIndex(stream.Context(), req,
func(p *rpc.DownloadProgress) { stream.Send(&rpc.UpdateCoreLibrariesIndexResponse{DownloadProgress: p}) },
func(res *rpc.DownloadResult) { /* ignore */ },
)
if err != nil {
return convertErrorToRPCStatus(err)
......
......@@ -19,7 +19,7 @@ import (
"context"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
......@@ -490,7 +490,7 @@ func UpdateLibrariesIndex(ctx context.Context, req *rpc.UpdateLibrariesIndexRequ
}
// UpdateIndex FIXMEDOC
func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB, downloadResultCB rpc.DownloadResultCB) error {
func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB) error {
if instances.GetInstance(req.GetInstance().GetId()) == nil {
return &arduino.InvalidInstanceError{}
}
......@@ -504,14 +504,12 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rp
failed := false
for _, u := range urls {
logrus.Info("URL: ", u)
URL, err := utils.URLParse(u)
if err != nil {
logrus.Warnf("unable to parse additional URL: %s", u)
downloadResultCB(&rpc.DownloadResult{
Url: u,
Error: fmt.Sprintf("%s: %v", tr("Unable to parse URL"), err),
})
msg := fmt.Sprintf("%s: %v", tr("Unable to parse URL"), err)
downloadCB.Start(u, tr("Downloading index: %s", u))
downloadCB.End(false, msg)
failed = true
continue
}
......@@ -519,49 +517,26 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rp
logrus.WithField("url", URL).Print("Updating index")
if URL.Scheme == "file" {
downloadCB.Start(u, tr("Downloading index: %s", filepath.Base(URL.Path)))
path := paths.New(URL.Path)
if _, err := packageindex.LoadIndexNoSign(path); err != nil {
downloadResultCB(&rpc.DownloadResult{
Url: u,
Error: fmt.Sprintf("%s: %v", tr("Invalid package index in %s", path), err),
})
msg := fmt.Sprintf("%s: %v", tr("Invalid package index in %s", path), err)
downloadCB.End(false, msg)
failed = true
continue
} else {
downloadCB.End(true, "")
}
fi, _ := os.Stat(path.String())
downloadCB(&rpc.DownloadProgress{
File: tr("Downloading index: %s", path.Base()),
TotalSize: fi.Size(),
})
downloadCB(&rpc.DownloadProgress{Completed: true})
downloadResultCB(&rpc.DownloadResult{
Url: u,
Successful: true,
})
continue
}
indexResource := resources.IndexResource{
URL: URL,
}
indexResource := resources.IndexResource{URL: URL}
if strings.HasSuffix(URL.Host, "arduino.cc") && strings.HasSuffix(URL.Path, ".json") {
indexResource.SignatureURL, _ = url.Parse(u) // should not fail because we already parsed it
indexResource.SignatureURL.Path += ".sig"
}
if err := indexResource.Download(indexpath, downloadCB); err != nil {
downloadResultCB(&rpc.DownloadResult{
Url: u,
Error: err.Error(),
})
failed = true
continue
}
downloadResultCB(&rpc.DownloadResult{
Url: u,
Successful: true,
})
}
if failed {
......@@ -571,17 +546,13 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rp
}
// UpdateCoreLibrariesIndex updates both Cores and Libraries indexes
func UpdateCoreLibrariesIndex(ctx context.Context, req *rpc.UpdateCoreLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB, downloadResultCB rpc.DownloadResultCB) error {
err := UpdateIndex(ctx, &rpc.UpdateIndexRequest{
Instance: req.Instance,
}, downloadCB, downloadResultCB)
func UpdateCoreLibrariesIndex(ctx context.Context, req *rpc.UpdateCoreLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB) error {
err := UpdateIndex(ctx, &rpc.UpdateIndexRequest{Instance: req.Instance}, downloadCB)
if err != nil {
return err
}
err = UpdateLibrariesIndex(ctx, &rpc.UpdateLibrariesIndexRequest{
Instance: req.Instance,
}, downloadCB)
err = UpdateLibrariesIndex(ctx, &rpc.UpdateLibrariesIndexRequest{Instance: req.Instance}, downloadCB)
if err != nil {
return err
}
......
......@@ -76,55 +76,96 @@ replacement.
### Breaking changes in UpdateIndex API (both gRPC and go-lang)
The gRPC message `cc.arduino.cli.commands.v1.UpdateIndexResponse` has been changed from:
The gRPC message `cc.arduino.cli.commands.v1.DownloadProgress` has been changed from:
```
message UpdateIndexResponse {
// Progress of the platforms index download.
DownloadProgress download_progress = 1;
message DownloadProgress {
// URL of the download.
string url = 1;
// The file being downloaded.
string file = 2;
// Total size of the file being downloaded.
int64 total_size = 3;
// Size of the downloaded portion of the file.
int64 downloaded = 4;
// Whether the download is complete.
bool completed = 5;
}
```
to
```
message UpdateIndexResponse {
message DownloadProgress {
oneof message {
// Progress of the platforms index download.
DownloadProgress download_progress = 1;
// Report of the index update downloads.
DownloadResult download_result = 2;
DownloadProgressStart start = 1;
DownloadProgressUpdate update = 2;
DownloadProgressEnd end = 3;
}
}
message DownloadResult {
// Index URL.
message DownloadProgressStart {
// URL of the download.
string url = 1;
// Download result: true if successful, false if an error occurred.
bool successful = 2;
// Download error details.
string error = 3;
// The label to display on the progress bar.
string label = 2;
}
message DownloadProgressUpdate {
// Size of the downloaded portion of the file.
int64 downloaded = 1;
// Total size of the file being downloaded.
int64 total_size = 2;
}
message DownloadProgressEnd {
// True if the download is successful
bool success = 1;
// Info or error message, depending on the value of 'success'. Some examples:
// "File xxx already downloaded" or "Connection timeout"
string message = 2;
}
```
The new message format allows a better handling of the progress update reports on downloads. Every download now will
report a sequence of message as follows:
```
DownloadProgressStart{url="https://...", label="Downloading package index..."}
DownloadProgressUpdate{downloaded=0, total_size=103928}
DownloadProgressUpdate{downloaded=29380, total_size=103928}
DownloadProgressUpdate{downloaded=69540, total_size=103928}
DownloadProgressEnd{success=true, message=""}
```
or if an error occurs:
```
DownloadProgressStart{url="https://...", label="Downloading package index..."}
DownloadProgressUpdate{downloaded=0, total_size=103928}
DownloadProgressEnd{success=false, message="Server closed connection"}
```
or if the file is already cached:
```
DownloadProgressStart{url="https://...", label="Downloading package index..."}
DownloadProgressEnd{success=true, message="Index already downloaded"}
```
even if not strictly a breaking change it's worth noting that the detailed error message is now streamed in the
response. About the go-lang API the following functions in `github.com/arduino/arduino-cli/commands`:
About the go-lang API the following functions in `github.com/arduino/arduino-cli/commands`:
```go
func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB) (*rpc.UpdateIndexResponse, error) { ... }
func UpdateCoreLibrariesIndex(ctx context.Context, req *rpc.UpdateCoreLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB) error { ... }
```
have changed their signature to:
```go
func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB, downloadResultCB rpc.DownloadResultCB) error { ... }
func UpdateCoreLibrariesIndex(ctx context.Context, req *rpc.UpdateCoreLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB, downloadResultCB rpc.DownloadResultCB) error { ... }
```
`UpdateIndex` do not return anymore the latest `UpdateIndexResponse` (it was always empty). Both `UpdateIndex` and
`UpdateCoreLibrariesIndex` now accepts an `rpc.DownloadResultCB` to get download results, you can pass an empty callback
if you're not interested in the error details.
`UpdateIndex` do not return anymore the latest `UpdateIndexResponse` (beacuse it was always empty).
## 0.27.0
......
......@@ -41,59 +41,38 @@ func TestDaemonCoreUpdateIndex(t *testing.T) {
` "http://downloads.arduino.cc/package_inexistent_index.json"]`)
require.NoError(t, err)
analyzeUpdateIndexClient := func(cl commands.ArduinoCoreService_UpdateIndexClient) (error, map[string]*commands.DownloadProgressEnd) {
analyzer := NewDownloadProgressAnalyzer(t)
for {
msg, err := cl.Recv()
// fmt.Println("DOWNLOAD>", msg)
if err == io.EOF {
return nil, analyzer.Results
}
if err != nil {
return err, analyzer.Results
}
require.NoError(t, err)
analyzer.Process(msg.GetDownloadProgress())
}
}
{
cl, err := grpcInst.UpdateIndex(context.Background(), true)
require.NoError(t, err)
res, err := analyzeUpdateIndexStream(t, cl)
err, res := analyzeUpdateIndexClient(cl)
require.NoError(t, err)
require.Len(t, res, 1)
require.True(t, res["https://downloads.arduino.cc/packages/package_index.tar.bz2"].Successful)
require.True(t, res["https://downloads.arduino.cc/packages/package_index.tar.bz2"].Success)
}
{
cl, err := grpcInst.UpdateIndex(context.Background(), false)
require.NoError(t, err)
res, err := analyzeUpdateIndexStream(t, cl)
err, res := analyzeUpdateIndexClient(cl)
require.Error(t, err)
require.Len(t, res, 3)
require.True(t, res["https://downloads.arduino.cc/packages/package_index.tar.bz2"].Successful)
require.True(t, res["http://arduino.esp8266.com/stable/package_esp8266com_index.json"].Successful)
require.False(t, res["http://downloads.arduino.cc/package_inexistent_index.json"].Successful)
}
}
// analyzeUpdateIndexStream runs an update index checking if the sequence of DownloadProgress and
// DownloadResult messages is correct. It returns a map reporting all the DownloadResults messages
// received (it maps urls to DownloadResults).
func analyzeUpdateIndexStream(t *testing.T, cl commands.ArduinoCoreService_UpdateIndexClient) (map[string]*commands.DownloadResult, error) {
ongoingDownload := ""
results := map[string]*commands.DownloadResult{}
for {
msg, err := cl.Recv()
if err == io.EOF {
return results, nil
}
if err != nil {
return results, err
}
require.NoError(t, err)
fmt.Printf("UPDATE> %+v\n", msg)
if progress := msg.GetDownloadProgress(); progress != nil {
if progress.Url != "" && progress.Url != ongoingDownload {
require.Empty(t, ongoingDownload, "DownloadProgress: started a download without 'completing' the previous one")
ongoingDownload = progress.Url
}
if progress.Completed {
require.NotEmpty(t, ongoingDownload, "DownloadProgress: received a 'completed' notification but never initiated a download")
ongoingDownload = ""
}
if progress.Downloaded > 0 {
require.NotEmpty(t, ongoingDownload, "DownloadProgress: received a download update but never initiated a download")
}
} else if result := msg.GetDownloadResult(); result != nil {
require.Empty(t, ongoingDownload, "DownloadResult: got a download result without completing the current download first")
results[result.Url] = result
} else {
require.FailNow(t, "DownloadProgress: received an empty message (without a Progress or a Result)")
}
require.True(t, res["https://downloads.arduino.cc/packages/package_index.tar.bz2"].Success)
require.True(t, res["http://arduino.esp8266.com/stable/package_esp8266com_index.json"].Success)
require.False(t, res["http://downloads.arduino.cc/package_inexistent_index.json"].Success)
}
}
// This file is part of arduino-cli.
//
// Copyright 2022 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.
package daemon_test
import (
"testing"
"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/stretchr/testify/require"
)
// DownloadProgressAnalyzer analyzes DownloadProgress messages for consistency
type DownloadProgressAnalyzer struct {
t *testing.T
ongoingDownload string
Results map[string]*commands.DownloadProgressEnd
}
// NewDownloadProgressAnalyzer creates a new DownloadProgressAnalyzer
func NewDownloadProgressAnalyzer(t *testing.T) *DownloadProgressAnalyzer {
return &DownloadProgressAnalyzer{
t: t,
Results: map[string]*commands.DownloadProgressEnd{},
}
}
// Process the given DownloadProgress message. If inconsistencies are detected the
// test will fail.
func (a *DownloadProgressAnalyzer) Process(progress *commands.DownloadProgress) {
if progress == nil {
return
}
if start := progress.GetStart(); start != nil {
require.Empty(a.t, a.ongoingDownload, "DownloadProgressStart: started a download without 'completing' the previous one")
a.ongoingDownload = start.Url
} else if update := progress.GetUpdate(); update != nil {
require.NotEmpty(a.t, a.ongoingDownload, "DownloadProgressUpdate: received update, but the download is not yet started...")
} else if end := progress.GetEnd(); end != nil {
require.NotEmpty(a.t, a.ongoingDownload, "DownloadProgress: received a 'completed' notification but never initiated a download")
a.Results[a.ongoingDownload] = end
a.ongoingDownload = ""
} else {
require.FailNow(a.t, "DownloadProgress: received an empty DownloadProgress (without Start, Update or End)")
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -238,21 +238,8 @@ message UpdateIndexRequest {
}
message UpdateIndexResponse {
oneof message {
// Progress of the platforms index download.
DownloadProgress download_progress = 1;
// Report of the index update downloads.
DownloadResult download_result = 2;
}
}
message DownloadResult {
// Index URL.
string url = 1;
// Download result: true if successful, false if an error occurred.
bool successful = 2;
// Download error details.
string error = 3;
// Progress of the package index download.
DownloadProgress download_progress = 1;
}
message UpdateLibrariesIndexRequest {
......
......@@ -18,8 +18,41 @@ package commands
// DownloadProgressCB is a callback to get updates on download progress
type DownloadProgressCB func(curr *DownloadProgress)
// DownloadResultCB is a callback to get the result of a download
type DownloadResultCB func(res *DownloadResult)
// Start sends a "start" DownloadProgress message to the callback function
func (d DownloadProgressCB) Start(url, label string) {
d(&DownloadProgress{
Message: &DownloadProgress_Start{
Start: &DownloadProgressStart{
Url: url,
Label: label,
},
},
})
}
// Update sends an "update" DownloadProgress message to the callback function
func (d DownloadProgressCB) Update(downloaded int64, totalSize int64) {
d(&DownloadProgress{
Message: &DownloadProgress_Update{
Update: &DownloadProgressUpdate{
Downloaded: downloaded,
TotalSize: totalSize,
},
},
})
}
// End sends an "end" DownloadProgress message to the callback function
func (d DownloadProgressCB) End(success bool, message string) {
d(&DownloadProgress{
Message: &DownloadProgress_End{
End: &DownloadProgressEnd{
Success: success,
Message: message,
},
},
})
}
// TaskProgressCB is a callback to receive progress messages
type TaskProgressCB func(msg *TaskProgress)
......
This diff is collapsed.
......@@ -25,16 +25,33 @@ message Instance {
}
message DownloadProgress {
oneof message {
DownloadProgressStart start = 1;
DownloadProgressUpdate update = 2;
DownloadProgressEnd end = 3;
}
}
message DownloadProgressStart {
// URL of the download.
string url = 1;
// The file being downloaded.
string file = 2;
// Total size of the file being downloaded.
int64 total_size = 3;
// The label to display on the progress bar.
string label = 2;
}
message DownloadProgressUpdate {
// Size of the downloaded portion of the file.
int64 downloaded = 4;
// Whether the download is complete.
bool completed = 5;
int64 downloaded = 1;
// Total size of the file being downloaded.
int64 total_size = 2;
}
message DownloadProgressEnd {
// True if the download is successful
bool success = 1;
// Info or error message, depending on the value of 'success'. Some examples:
// "File xxx already downloaded" or "Connection timeout"
string message = 2;
}
message TaskProgress {
......
......@@ -94,8 +94,10 @@ def test_core_updateindex_url_not_found(run_command, httpserver):
result = run_command(["core", "update-index", f"--additional-urls={url}"])
assert result.failed
lines = [l.strip() for l in result.stderr.splitlines()]
assert f"Error updating index: Error downloading index '{url}': Server responded with: 404 NOT FOUND" in lines
linesout = [l.strip() for l in result.stdout.splitlines()]
assert "Downloading index: test_index.json Server responded with: 404 NOT FOUND" in linesout
lineserr = [l.strip() for l in result.stderr.splitlines()]
assert "Some indexes could not be updated." in lineserr
def test_core_updateindex_internal_server_error(run_command, httpserver):
......@@ -107,11 +109,8 @@ def test_core_updateindex_internal_server_error(run_command, httpserver):
result = run_command(["core", "update-index", f"--additional-urls={url}"])
assert result.failed
lines = [l.strip() for l in result.stderr.splitlines()]
assert (
f"Error updating index: Error downloading index '{url}': Server responded with: 500 INTERNAL SERVER ERROR"
in lines
)
lines = [l.strip() for l in result.stdout.splitlines()]
assert "Downloading index: test_index.json Server responded with: 500 INTERNAL SERVER ERROR" in lines
def test_core_install_without_updateindex(run_command):
......
......@@ -60,8 +60,8 @@ def test_update_with_url_not_found(run_command, httpserver):
res = run_command(["update", f"--additional-urls={url}"])
assert res.failed
lines = [l.strip() for l in res.stderr.splitlines()]
assert f"Error updating index: Error downloading index '{url}': Server responded with: 404 NOT FOUND" in lines
lines = [l.strip() for l in res.stdout.splitlines()]
assert "Downloading index: test_index.json Server responded with: 404 NOT FOUND" in lines
def test_update_with_url_internal_server_error(run_command, httpserver):
......@@ -73,11 +73,8 @@ def test_update_with_url_internal_server_error(run_command, httpserver):
res = run_command(["update", f"--additional-urls={url}"])
assert res.failed
lines = [l.strip() for l in res.stderr.splitlines()]
assert (
f"Error updating index: Error downloading index '{url}': Server responded with: 500 INTERNAL SERVER ERROR"
in lines
)
lines = [l.strip() for l in res.stdout.splitlines()]
assert "Downloading index: test_index.json Server responded with: 500 INTERNAL SERVER ERROR" in lines
def test_update_showing_outdated_using_library_with_invalid_version(run_command, data_dir):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment