WIP: progress.

This commit is contained in:
Nuno Cruces 2021-04-30 19:05:49 +01:00
parent 0a67c8f698
commit aeaa608758
8 changed files with 97 additions and 74 deletions

View file

@ -10,4 +10,5 @@ var (
Command bool Command bool
Timeout int Timeout int
Separator = "\x00" Separator = "\x00"
Canceled error
) )

View file

@ -13,4 +13,5 @@ var (
Command bool Command bool
Timeout int Timeout int
Separator = "\x1e" Separator = "\x1e"
Canceled error
) )

View file

@ -10,4 +10,5 @@ var (
Command bool Command bool
Timeout int Timeout int
Separator string Separator string
Canceled error
) )

View file

@ -106,37 +106,15 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
} }
dlg := &progressDialog{ dlg := &progressDialog{
done: make(chan struct{}), cmd: cmd,
lines: make(chan string),
max: max, max: max,
lines: make(chan string),
done: make(chan struct{}),
} }
go dlg.pipe(pipe)
go func() { go func() {
err := cmd.Wait() defer os.RemoveAll(t)
if cerr := ctx.Err(); cerr != nil { dlg.wait()
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
}
}
}() }()
return dlg, nil return dlg, nil
} }

View file

@ -3,15 +3,25 @@
package zenutil package zenutil
import ( import (
"context"
"io"
"os"
"os/exec"
"runtime"
"strconv" "strconv"
"sync/atomic"
"time"
) )
type progressDialog struct { type progressDialog struct {
err error ctx context.Context
done chan struct{} cmd *exec.Cmd
lines chan string
percent bool
max int max int
percent bool
closed int32
lines chan string
done chan struct{}
err error
} }
func (d *progressDialog) send(line string) 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 { func (d *progressDialog) Text(text string) error {
return d.send("#" + text) return d.send("#" + text)
} }
@ -48,3 +52,61 @@ func (d *progressDialog) MaxValue() int {
func (d *progressDialog) Done() <-chan struct{} { func (d *progressDialog) Done() <-chan struct{} {
return d.done 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
}
}
}

View file

@ -63,43 +63,14 @@ func RunProgress(ctx context.Context, max int, args []string) (*progressDialog,
} }
dlg := &progressDialog{ dlg := &progressDialog{
done: make(chan struct{}), ctx: ctx,
lines: make(chan string), cmd: cmd,
percent: true,
max: max, max: max,
percent: true,
lines: make(chan string),
done: make(chan struct{}),
} }
go func() { go dlg.pipe(pipe)
err := cmd.Wait() go dlg.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
}
}
}()
return dlg, nil return dlg, nil
} }

View file

@ -19,6 +19,9 @@ type ProgressDialog interface {
// MaxValue gets how much work the task requires in total. // MaxValue gets how much work the task requires in total.
MaxValue() int MaxValue() int
// Complete marks the task completed.
Complete() error
// Close closes the dialog. // Close closes the dialog.
Close() error Close() error

View file

@ -13,6 +13,8 @@ package zenity
import ( import (
"context" "context"
"image/color" "image/color"
"github.com/ncruces/zenity/internal/zenutil"
) )
type stringErr string type stringErr string
@ -31,6 +33,10 @@ const ErrExtraButton = stringErr("extra button pressed")
// ErrUnsupported is returned when a combination of options is not supported. // ErrUnsupported is returned when a combination of options is not supported.
const ErrUnsupported = stringErr("unsupported option") const ErrUnsupported = stringErr("unsupported option")
func init() {
zenutil.Canceled = ErrCanceled
}
type options struct { type options struct {
// General options // General options
title *string title *string