diff --git a/internal/updater/artifacts/download_resources.sh b/internal/updater/artifacts/download_resources.sh index 640565b..597e4ef 100644 --- a/internal/updater/artifacts/download_resources.sh +++ b/internal/updater/artifacts/download_resources.sh @@ -5,7 +5,7 @@ set -e BASE_DIR="$(cd "$(dirname "$0")" && pwd)" REPO="arduino/qdl-packing" -TAG="v2.2-22" +TAG="add-list-command-25" # Remove existing resource directories if they exist rm -rf $BASE_DIR/resources_* diff --git a/internal/updater/flasher.go b/internal/updater/flasher.go index ac99b1f..23547e9 100644 --- a/internal/updater/flasher.go +++ b/internal/updater/flasher.go @@ -18,10 +18,14 @@ package updater import ( "context" "encoding/hex" + "errors" "fmt" + "log/slog" + "os/exec" "runtime" "strconv" "strings" + "time" "github.com/arduino/go-paths-helper" "github.com/fatih/color" @@ -112,27 +116,13 @@ type FlashEvent struct { type FlashCallback func(FlashEvent) func FlashBoard(ctx context.Context, downloadedImagePath *paths.Path, version string, preserveUser bool, callback FlashCallback) error { - flashDir, err := searchForFlashDir(downloadedImagePath) - if err != nil { - return err - } - - qdlDir, err := paths.MkTempDir("", "qdl-") + qdlPath, cleanup, err := installQdl() if err != nil { return err } - defer func() { _ = qdlDir.RemoveAll() }() - - qdlPath := qdlDir.Join("qdl") - if runtime.GOOS == "windows" { - qdlPath = qdlDir.Join("qdl.exe") - } + defer cleanup() - err = qdlPath.WriteFile(artifacts.QdlBinary) - if err != nil { - return err - } - err = qdlPath.Chmod(0755) + flashDir, err := searchForFlashDir(downloadedImagePath) if err != nil { return err } @@ -239,6 +229,27 @@ func searchForFlashDir(extractPath *paths.Path) (*paths.Path, error) { } } +func installQdl() (*paths.Path, func(), error) { + qdlDir, err := paths.MkTempDir("", "qdl-") + if err != nil { + return nil, nil, err + } + + qdlPath := qdlDir.Join("qdl") + if runtime.GOOS == "windows" { + qdlPath = qdlDir.Join("qdl.exe") + } + + if err = qdlPath.WriteFile(artifacts.QdlBinary); err != nil { + return nil, nil, err + } + if err = qdlPath.Chmod(0755); err != nil { + return nil, nil, err + } + + return qdlPath, func() { _ = qdlDir.RemoveAll() }, nil +} + // Checks the board GPT table and counts the number of partitions, this tells if the board supports preserving or not user's data. func checkBoardGPTTable(ctx context.Context, qdlPath, flashDir *paths.Path) error { dumpBinPath := qdlPath.Parent().Join("dump.bin") @@ -288,3 +299,35 @@ func checkBoardGPTTable(ctx context.Context, qdlPath, flashDir *paths.Path) erro return nil } + +// WantForQdlDevice waits for a QDL device to be connected. +// This is like and hack because QDL does not have a specific command to wait for a device, +// so we use the read command with a dummy ELF and XML file to detect when a device is connected. +func WaitForQdlDevice(ctx context.Context) error { + qdlPath, cleanup, err := installQdl() + if err != nil { + return err + } + defer cleanup() + + for { + cmd, err := paths.NewProcess(nil, qdlPath.String(), "--list") + if err != nil { + return err + } + if out, err := cmd.RunAndCaptureCombinedOutput(ctx); err != nil { + slog.Debug("wait for qdl device command exit", "out", string(out), "err", err) + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + if exitErr.ExitCode() != 1 { + return fmt.Errorf("error waiting for QDL device: %w: %s", err, out) + } + } + } else { + slog.Debug("qdl device detected", "out", string(out)) + return nil + } + + time.Sleep(1 * time.Second) + } +} diff --git a/service/service_flash.go b/service/service_flash.go index 24daa0f..9f83c9a 100644 --- a/service/service_flash.go +++ b/service/service_flash.go @@ -67,6 +67,10 @@ func (s *flasherServerImpl) Flash(req *flasher.FlashRequest, stream flasher.Flas } } + if err := updater.WaitForQdlDevice(ctx); err != nil { + return fmt.Errorf("could not find connected Arduino Uno QDL device: %w", err) + } + rel, err := client.GetReleaseByVersion(ctx, req.GetVersion()) if err != nil { return fmt.Errorf("could not get release info: %w", err)