1616package updater
1717
1818import (
19+ "bufio"
1920 "context"
2021 "encoding/hex"
2122 "fmt"
23+ "log/slog"
24+ "regexp"
2225 "runtime"
2326 "strconv"
2427 "strings"
@@ -28,6 +31,7 @@ import (
2831
2932 "github.com/arduino/arduino-flasher-cli/cmd/feedback"
3033 "github.com/arduino/arduino-flasher-cli/cmd/i18n"
34+ "github.com/arduino/arduino-flasher-cli/internal/helper"
3135 "github.com/arduino/arduino-flasher-cli/internal/updater/artifacts"
3236)
3337
@@ -36,7 +40,7 @@ const DownloadDiskSpace = uint64(12)
3640const ExtractDiskSpace = uint64 (10 )
3741const yesPrompt = "yes"
3842
39- func Flash (ctx context.Context , imagePath * paths.Path , version string , forceYes bool , preserveUser bool , tempDir string ) error {
43+ func Flash (ctx context.Context , imagePath * paths.Path , version string , forceYes bool , preserveUser bool , tempDir string , callback FlahsCallback ) error {
4044 if ! imagePath .Exist () {
4145 temp , err := SetTempDir ("download-" , tempDir )
4246 if err != nil {
@@ -90,10 +94,26 @@ func Flash(ctx context.Context, imagePath *paths.Path, version string, forceYes
9094 imagePath = tempContent [0 ]
9195 }
9296
93- return FlashBoard (ctx , imagePath .String (), version , preserveUser )
97+ return FlashBoard (ctx , imagePath .String (), version , preserveUser , nil )
9498}
9599
96- func FlashBoard (ctx context.Context , downloadedImagePath string , version string , preserveUser bool ) error {
100+ type TypeEvent int
101+
102+ const (
103+ EventWaiting TypeEvent = 3
104+ EventFlashed TypeEvent = 4
105+ )
106+
107+ type FlashEvent struct {
108+ Type TypeEvent
109+ Progress int
110+ MaxProgress int
111+ Log string
112+ }
113+
114+ type FlahsCallback func (FlashEvent )
115+
116+ func FlashBoard (ctx context.Context , downloadedImagePath string , version string , preserveUser bool , callback FlahsCallback ) error {
97117 var flashDir * paths.Path
98118 for _ , entry := range []string {"flash" , "flash_UnoQ" } {
99119 if p := paths .New (downloadedImagePath , entry ); p .Exist () {
@@ -161,15 +181,48 @@ func FlashBoard(ctx context.Context, downloadedImagePath string, version string,
161181
162182 }
163183
184+ totalPartitions , err := getTotalPartition (flashDir .Join (rawProgram ))
185+ if err != nil {
186+ return err
187+ }
188+
164189 feedback .Print (i18n .Tr ("Flashing with qdl" ))
165190 cmd , err := paths .NewProcess (nil , qdlPath .String (), "--allow-missing" , "--storage" , "emmc" , "prog_firehose_ddr.elf" , rawProgram , "patch0.xml" )
166191 if err != nil {
167192 return err
168193 }
169194 // Setting the directory is needed because rawprogram0.xml contains relative file paths
170195 cmd .SetDir (flashDir .String ())
171- cmd .RedirectStderrTo (stdout )
172- cmd .RedirectStdoutTo (stdout )
196+
197+ w := stdout
198+ if callback != nil {
199+ progress := 0
200+ w = helper .NewCallbackWriter (func (line string ) {
201+ parsedLine , err := parseQdlLogLine (line )
202+ if err != nil {
203+ slog .Warn ("could not parse qdl log line" , "error" , err , "line" , line )
204+ return
205+ }
206+
207+ switch parsedLine .Op {
208+ case Waiting :
209+ callback (FlashEvent {
210+ Type : EventWaiting ,
211+ Log : line ,
212+ })
213+ case Flasherd :
214+ progress ++
215+ callback (FlashEvent {
216+ Type : EventFlashed ,
217+ Log : line ,
218+ Progress : progress ,
219+ MaxProgress : totalPartitions ,
220+ })
221+ }
222+ })
223+ }
224+ cmd .RedirectStderrTo (w )
225+ cmd .RedirectStdoutTo (w )
173226 if err := cmd .RunWithinContext (ctx ); err != nil {
174227 return err
175228 }
@@ -226,3 +279,59 @@ func checkBoardGPTTable(ctx context.Context, qdlPath, flashDir *paths.Path) erro
226279
227280 return nil
228281}
282+
283+ type Op int
284+
285+ const (
286+ Waiting Op = 1
287+ Flasherd Op = 2
288+ )
289+
290+ var qdlProgressRegex = regexp .MustCompile (`(\w)\s+(:?(".*?")\s+(\w+)(?:\s+at\s+(\d+kB/s))?)?` )
291+
292+ type QDLLogLine struct {
293+ Op Op
294+ Log string
295+ }
296+
297+ func parseQdlLogLine (line string ) (QDLLogLine , error ) {
298+ matches := qdlProgressRegex .FindStringSubmatch (line )
299+ if matches == nil {
300+ return QDLLogLine {}, fmt .Errorf ("line %q does not match progress format" , line )
301+ }
302+ slog .Debug ("parsed qdl log line" , "full" , matches [0 ], "matches" , matches )
303+
304+ if strings .HasPrefix (matches [1 ], "Waiting for" ) || strings .HasPrefix (matches [1 ], "waiting for" ) {
305+ return QDLLogLine {
306+ Op : Waiting ,
307+ Log : line ,
308+ }, nil
309+ }
310+
311+ if strings .HasPrefix (matches [1 ], "Flashed" ) {
312+ return QDLLogLine {
313+ Op : Flasherd ,
314+ Log : line ,
315+ }, nil
316+ }
317+
318+ return QDLLogLine {}, fmt .Errorf ("line %q does not match known operations" , line )
319+ }
320+
321+ func getTotalPartition (path * paths.Path ) (int , error ) {
322+ f , err := path .Open ()
323+ if err != nil {
324+ return 0 , err
325+ }
326+
327+ r := bufio .NewScanner (f )
328+ var total int
329+ for r .Scan () {
330+ c := strings .Count (r .Text (), "<program" )
331+ total += c
332+ }
333+ if err := r .Err (); err != nil {
334+ return 0 , err
335+ }
336+ return total , nil
337+ }
0 commit comments