From aeaa60875848a26b0f71eb31d439624ba8196c92 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Fri, 30 Apr 2021 19:05:49 +0100 Subject: [PATCH] WIP: progress. --- internal/zenutil/env_darwin.go | 1 + internal/zenutil/env_unix.go | 1 + internal/zenutil/env_windows.go | 1 + internal/zenutil/run_darwin.go | 34 +++---------- internal/zenutil/run_progress.go | 82 ++++++++++++++++++++++++++++---- internal/zenutil/run_unix.go | 43 +++-------------- progress.go | 3 ++ zenity.go | 6 +++ 8 files changed, 97 insertions(+), 74 deletions(-) diff --git a/internal/zenutil/env_darwin.go b/internal/zenutil/env_darwin.go index fd9fdda..7233920 100644 --- a/internal/zenutil/env_darwin.go +++ b/internal/zenutil/env_darwin.go @@ -10,4 +10,5 @@ var ( Command bool Timeout int Separator = "\x00" + Canceled error ) diff --git a/internal/zenutil/env_unix.go b/internal/zenutil/env_unix.go index 9cd9e04..5788b42 100644 --- a/internal/zenutil/env_unix.go +++ b/internal/zenutil/env_unix.go @@ -13,4 +13,5 @@ var ( Command bool Timeout int Separator = "\x1e" + Canceled error ) diff --git a/internal/zenutil/env_windows.go b/internal/zenutil/env_windows.go index ee0957a..5c20d99 100644 --- a/internal/zenutil/env_windows.go +++ b/internal/zenutil/env_windows.go @@ -10,4 +10,5 @@ var ( Command bool Timeout int Separator string + Canceled error ) diff --git a/internal/zenutil/run_darwin.go b/internal/zenutil/run_darwin.go index eb24d99..3112dc7 100644 --- a/internal/zenutil/run_darwin.go +++ b/internal/zenutil/run_darwin.go @@ -106,37 +106,15 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e } dlg := &progressDialog{ - done: make(chan struct{}), - lines: make(chan string), + cmd: cmd, max: max, + lines: make(chan string), + done: make(chan struct{}), } + go dlg.pipe(pipe) go func() { - err := cmd.Wait() - if cerr := ctx.Err(); cerr != nil { - err = cerr - } - dlg.err = err - close(dlg.done) - os.RemoveAll(t) - }() - go func() { - defer pipe.Close() - for { - var line string - select { - case s, ok := <-dlg.lines: - if !ok { - return - } - line = s - case <-ctx.Done(): - return - case <-time.After(40 * time.Millisecond): - } - if _, err := pipe.Write([]byte(line + "\n")); err != nil { - return - } - } + defer os.RemoveAll(t) + dlg.wait() }() return dlg, nil } diff --git a/internal/zenutil/run_progress.go b/internal/zenutil/run_progress.go index 23c51ff..f0d2f3a 100644 --- a/internal/zenutil/run_progress.go +++ b/internal/zenutil/run_progress.go @@ -3,15 +3,25 @@ package zenutil import ( + "context" + "io" + "os" + "os/exec" + "runtime" "strconv" + "sync/atomic" + "time" ) type progressDialog struct { - err error - done chan struct{} - lines chan string - percent bool + ctx context.Context + cmd *exec.Cmd max int + percent bool + closed int32 + lines chan string + done chan struct{} + err error } func (d *progressDialog) send(line string) error { @@ -23,12 +33,6 @@ func (d *progressDialog) send(line string) error { } } -func (d *progressDialog) Close() error { - close(d.lines) - <-d.done - return d.err -} - func (d *progressDialog) Text(text string) error { return d.send("#" + text) } @@ -48,3 +52,61 @@ func (d *progressDialog) MaxValue() int { func (d *progressDialog) Done() <-chan struct{} { return d.done } + +func (d *progressDialog) Complete() error { + close(d.lines) + select { + case <-d.done: + return d.err + default: + return nil + } +} + +func (d *progressDialog) Close() error { + atomic.StoreInt32(&d.closed, 1) + d.cmd.Process.Signal(os.Interrupt) + <-d.done + return d.err +} + +func (d *progressDialog) wait() { + err := d.cmd.Wait() + if cerr := d.ctx.Err(); cerr != nil { + err = cerr + } + if eerr, ok := err.(*exec.ExitError); ok { + switch { + case eerr.ExitCode() == -1 && atomic.LoadInt32(&d.closed) != 0: + err = nil + case eerr.ExitCode() == 1: + err = Canceled + } + } + d.err = err + close(d.done) +} + +func (d *progressDialog) pipe(w io.WriteCloser) { + defer w.Close() + var timeout = time.Second + if runtime.GOOS == "darwin" { + timeout = 40 * time.Millisecond + } + for { + var line string + select { + case s, ok := <-d.lines: + if !ok { + return + } + line = s + case <-d.ctx.Done(): + return + case <-time.After(timeout): + } + if _, err := w.Write([]byte(line + "\n")); err != nil { + return + } + } +} diff --git a/internal/zenutil/run_unix.go b/internal/zenutil/run_unix.go index 96ed624..5a4b0cd 100644 --- a/internal/zenutil/run_unix.go +++ b/internal/zenutil/run_unix.go @@ -63,43 +63,14 @@ func RunProgress(ctx context.Context, max int, args []string) (*progressDialog, } dlg := &progressDialog{ - done: make(chan struct{}), - lines: make(chan string), - percent: true, + ctx: ctx, + cmd: cmd, max: max, + percent: true, + lines: make(chan string), + done: make(chan struct{}), } - go func() { - err := cmd.Wait() - select { - case _, ok := <-dlg.lines: - if !ok { - err = nil - } - default: - } - if cerr := ctx.Err(); cerr != nil { - err = cerr - } - dlg.err = err - close(dlg.done) - }() - go func() { - defer cmd.Process.Signal(syscall.SIGTERM) - for { - var line string - select { - case s, ok := <-dlg.lines: - if !ok { - return - } - line = s - case <-ctx.Done(): - return - } - if _, err := pipe.Write([]byte(line + "\n")); err != nil { - return - } - } - }() + go dlg.pipe(pipe) + go dlg.wait() return dlg, nil } diff --git a/progress.go b/progress.go index 5f5dffe..3fd781f 100644 --- a/progress.go +++ b/progress.go @@ -19,6 +19,9 @@ type ProgressDialog interface { // MaxValue gets how much work the task requires in total. MaxValue() int + // Complete marks the task completed. + Complete() error + // Close closes the dialog. Close() error diff --git a/zenity.go b/zenity.go index 7f7763f..bb7f3ea 100644 --- a/zenity.go +++ b/zenity.go @@ -13,6 +13,8 @@ package zenity import ( "context" "image/color" + + "github.com/ncruces/zenity/internal/zenutil" ) type stringErr string @@ -31,6 +33,10 @@ const ErrExtraButton = stringErr("extra button pressed") // ErrUnsupported is returned when a combination of options is not supported. const ErrUnsupported = stringErr("unsupported option") +func init() { + zenutil.Canceled = ErrCanceled +} + type options struct { // General options title *string