From 9b6f8291fe528789db6a75b9787431a2b5ceffca Mon Sep 17 00:00:00 2001 From: Candra Kharista Date: Fri, 18 Jun 2021 14:59:46 +0700 Subject: [PATCH 1/3] add: context that are bounds to execution context add: allow to override suppress warning option fix: some typo and update readme with working example --- option.go | 10 ++++++++++ proxy.go | 54 ++++++++++++++++++++++++++++++++++++--------------- proxy_test.go | 43 ++++++++++++++++++++++++++++++++++++++++ readme.md | 14 ++++++++++--- 4 files changed, 102 insertions(+), 19 deletions(-) diff --git a/option.go b/option.go index ebdd108..2036619 100644 --- a/option.go +++ b/option.go @@ -18,6 +18,9 @@ type Options struct { // set verbosity verbose bool + + // allow to suppress warning + suppressWarning bool } // CommandName customize inkscape executable name @@ -43,6 +46,13 @@ func CommandQueueLength(length int) Option { } } +// SuppressWarning override default suppress warning option, that are enabled +func SuppressWarning(suppress bool) Option { + return func(o *Options) { + o.suppressWarning = suppress + } +} + // Verbose override log verbosity // useful for debugging func Verbose(verbose bool) Option { diff --git a/proxy.go b/proxy.go index 5729448..9f1061c 100644 --- a/proxy.go +++ b/proxy.go @@ -24,6 +24,7 @@ const ( var ( ErrCommandNotAvailable = errors.New("inkscape not available") ErrCommandNotReady = errors.New("inkscape not ready") + ErrCommandExecCanceled = errors.New("command execution canceled") ) // bytes.Buffer pool @@ -36,7 +37,7 @@ type chanWriter struct { func (w *chanWriter) Write(data []byte) (int, error) { // look like the buffer being reused internally by the exec.Command - // so we can directly read the buffer in another goroutine while still being used in exec.Command goroutine + // so we can't directly read the buffer in another goroutine while still being used in exec.Command goroutine // copy to be written buffer and pass it into channel bufferToWrite := make([]byte, len(data)) @@ -46,7 +47,7 @@ func (w *chanWriter) Write(data []byte) (int, error) { return written, nil } -// Proxy runs inkspace instance in background and +// Proxy runs inkscape instance in background and // provides mechanism to interfacing with running // instance via stdin type Proxy struct { @@ -144,17 +145,22 @@ wait: } case bytesErr := <-stderrC: + log.Println("stderr :", bytesErr) if len(bytesErr) == 0 { break } - if bytes.Contains(bytesErr, []byte("WARNING")) { - break + // only skip warning when option suppressWarning are true + if p.options.suppressWarning { + if bytes.Contains(bytesErr, []byte("WARNING")) { + break + } } p.stderr <- bytes.TrimSpace(bytesErr) case bytesOut := <-stdoutC: + log.Println("stderr :", bytesOut) if len(bytesOut) == 0 { break } @@ -195,7 +201,7 @@ func (p *Proxy) Run(args ...string) error { // Close satisfy io.Closer interface func (p *Proxy) Close() error { // send quit command - _, err := p.sendCommand([]byte(quitCommand), false) + _, err := p.sendCommand(context.Background(), []byte(quitCommand), false) p.cancel() close(p.requestLimiter) @@ -206,7 +212,7 @@ func (p *Proxy) Close() error { return err } -func (p *Proxy) sendCommand(b []byte, waitPrompt ...bool) ([]byte, error) { +func (p *Proxy) sendCommand(ctx context.Context, b []byte, waitPrompt ...bool) ([]byte, error) { wait := true if len(waitPrompt) > 0 { wait = waitPrompt[0] @@ -246,17 +252,22 @@ func (p *Proxy) sendCommand(b []byte, waitPrompt ...bool) ([]byte, error) { waitLoop: for { + select { + // wait till context canceled, early return + case <-ctx.Done(): + return output, ErrCommandExecCanceled // wait until received prompt - bytesOut := <-p.stdout - p.debug(string(bytesOut)) - parts := bytes.Split(bytesOut, []byte("\n")) - for _, part := range parts { - if isPrompt(part) { - break waitLoop + case bytesOut := <-p.stdout: + p.debug(string(bytesOut)) + parts := bytes.Split(bytesOut, []byte("\n")) + for _, part := range parts { + if isPrompt(part) { + break waitLoop + } } - } - output = append(output, bytesOut...) + output = append(output, bytesOut...) + } } // drain error channel @@ -278,20 +289,31 @@ errLoop: // RawCommands send inkscape shell commands func (p *Proxy) RawCommands(args ...string) ([]byte, error) { + return p.RawCommandsContext(context.Background(), args...) +} + +// RawCommands send inkscape shell commands +func (p *Proxy) RawCommandsContext(ctx context.Context, args ...string) ([]byte, error) { buffer := bufferPool.Get() defer bufferPool.Put(buffer) // construct command buffer buffer.WriteString(strings.Join(args, ";")) - res, err := p.sendCommand(buffer.Bytes()) + res, err := p.sendCommand(ctx, buffer.Bytes()) return res, err } // Svg2Pdf convert svg input file to output pdf file func (p *Proxy) Svg2Pdf(svgIn, pdfOut string) error { - res, err := p.RawCommands( + return p.Svg2PdfContext(context.Background(), svgIn, pdfOut) +} + +// Svg2PdfContext convert svg input file to output pdf file +func (p *Proxy) Svg2PdfContext(ctx context.Context, svgIn, pdfOut string) error { + res, err := p.RawCommandsContext( + ctx, FileOpen(svgIn), ExportFileName(pdfOut), ExportDo(), diff --git a/proxy_test.go b/proxy_test.go index 3d171f5..d0567ba 100644 --- a/proxy_test.go +++ b/proxy_test.go @@ -1,11 +1,15 @@ package inkscape import ( + "context" "fmt" + "math/rand" "os" "testing" + "time" ) +// TestConcurrent tests the library usage in concurrent environment func TestConcurrent(t *testing.T) { tempFiles := make([]string, 0) defer func() { @@ -34,6 +38,45 @@ func TestConcurrent(t *testing.T) { } } +// TestExecContext tests against command execution within context boundary +func TestExecContext(t *testing.T) { + const file = "circle.svg" + n := rand.Intn(1000) + tmpFile := fmt.Sprintf("%s.tmp.%d.pdf", file, n) + defer func() { + os.Remove(tmpFile) + }() + + proxy := NewProxy(Verbose(true)) + err := proxy.Run() + if err != nil { + t.Error(err) + t.FailNow() + } + defer proxy.Close() + + // gives very short life of execution context to tests + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*1) + done := make(chan struct{}) + defer cancel() + + go func() { + // this command expected to run no more than specified timeout duration + err := proxy.Svg2PdfContext(ctx, file, tmpFile) + if err != nil { + if err != ErrCommandExecCanceled { + t.Error(err) + } + } + if err == nil { + t.Error("expected command to be canceled, got success command execution") + } + done <- struct{}{} + }() + + <-done +} + func TestOpenFail(t *testing.T) { proxy := NewProxy(Verbose(true)) err := proxy.Run() diff --git a/readme.md b/readme.md index 13ae1dd..207d196 100644 --- a/readme.md +++ b/readme.md @@ -15,9 +15,18 @@ go get github.com/galihrivanto/go-inkscape ``` # **simple usage** + ```go -func main() { - var ( +package main + +import ( + "flag" + "fmt" + "github.com/galihrivanto/go-inkscape" + "os" +) + +var ( svgInput string pdfOutput string ) @@ -49,7 +58,6 @@ func main() { fmt.Println("done!!") } -} ``` # **advanced usage** From 293c0074e2fa7f4a180c5a2876311f9b1bc0fa89 Mon Sep 17 00:00:00 2001 From: Candra Kharista Date: Fri, 18 Jun 2021 15:05:03 +0700 Subject: [PATCH 2/3] fix: update some interface documentation comment --- proxy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy.go b/proxy.go index 9f1061c..b6324e1 100644 --- a/proxy.go +++ b/proxy.go @@ -292,7 +292,7 @@ func (p *Proxy) RawCommands(args ...string) ([]byte, error) { return p.RawCommandsContext(context.Background(), args...) } -// RawCommands send inkscape shell commands +// RawCommandsContext send inkscape shell commands that are bounded into specific context func (p *Proxy) RawCommandsContext(ctx context.Context, args ...string) ([]byte, error) { buffer := bufferPool.Get() defer bufferPool.Put(buffer) @@ -310,7 +310,7 @@ func (p *Proxy) Svg2Pdf(svgIn, pdfOut string) error { return p.Svg2PdfContext(context.Background(), svgIn, pdfOut) } -// Svg2PdfContext convert svg input file to output pdf file +// Svg2PdfContext convert svg input file to output pdf file that are bounded into specific context func (p *Proxy) Svg2PdfContext(ctx context.Context, svgIn, pdfOut string) error { res, err := p.RawCommandsContext( ctx, From d03b8756e9d4b686bab482c41388e6514442a224 Mon Sep 17 00:00:00 2001 From: Candra Kharista Date: Fri, 18 Jun 2021 15:07:52 +0700 Subject: [PATCH 3/3] fix: set default suppressWarning option --- proxy.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proxy.go b/proxy.go index b6324e1..6d200c2 100644 --- a/proxy.go +++ b/proxy.go @@ -332,9 +332,10 @@ func (p *Proxy) Svg2PdfContext(ctx context.Context, svgIn, pdfOut string) error func NewProxy(opts ...Option) *Proxy { // default value options := Options{ - commandName: defaultCmdName, - maxRetry: 5, - verbose: false, + commandName: defaultCmdName, + maxRetry: 5, + verbose: false, + suppressWarning: true, } // merge options