Files
Felis/internal/fileedit/progress.go

62 lines
1.8 KiB
Go

package fileedit
import (
"bufio"
"encoding/json"
"fmt"
"io"
"strings"
"time"
)
// ProgressPrefix marks a progress line: how many of an upload's or an unzip's
// bytes are in so far, as JSON. Those two run as Jobs felis-api does not wait on,
// and the panel shows how far one has got by reading the latest such line from
// the tail of the Pod's log. Like ResultPrefix it is found by its marker, since
// the log is stdout and stderr merged.
const ProgressPrefix = "FELIS-FILES-PROGRESS: "
// Progress is one progress line.
type Progress struct {
Done int64 `json:"done"`
Total int64 `json:"total"`
}
// ThrottledProgress returns a progress func that prints to w at most once per
// every, plus the first call (the last print starts at the zero time) and the
// one that reaches the total, so the log grows by a line a second however fast
// the bytes move and still ends on the true final count. A write error is
// dropped: progress is a courtesy, and the result line that follows is what
// felis-api acts on.
func ThrottledProgress(w io.Writer, every time.Duration, now func() time.Time) func(done, total int64) {
var last time.Time
return func(done, total int64) {
t := now()
if done < total && t.Sub(last) < every {
return
}
last = t
b, _ := json.Marshal(Progress{Done: done, Total: total})
fmt.Fprintf(w, "%s%s\n", ProgressPrefix, b)
}
}
// lastProgress finds the latest well-formed progress line in a log.
func lastProgress(log string) (Progress, bool) {
var p Progress
found := false
sc := bufio.NewScanner(strings.NewReader(log))
sc.Buffer(make([]byte, 0, 4096), maxLogBytes)
for sc.Scan() {
rest, ok := strings.CutPrefix(sc.Text(), ProgressPrefix)
if !ok {
continue
}
var q Progress
if json.Unmarshal([]byte(rest), &q) == nil {
p, found = q, true
}
}
return p, found
}