From 80ef088a0824e806bc5a8340b116fcf7e56b2b8b Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Fri, 18 Nov 2016 12:29:24 -0800 Subject: [PATCH 1/4] Don't trigger copy-related actions if decryption was cancelled --- main.go | 7 ++++++- pass.go | 9 ++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 4338295..684b13d 100644 --- a/main.go +++ b/main.go @@ -102,7 +102,12 @@ func (p *Passwords) CopyToClipboard(selected int) { return } pw := (p.hits)[selected] - pass := pw.Password() + pass, err := pw.Password() + if err != nil { + ui.setStatus("Cancelled") + return + } + if err := clipboard.WriteAll(pass); err != nil { panic(err) } diff --git a/pass.go b/pass.go index 2c058aa..840abbb 100644 --- a/pass.go +++ b/pass.go @@ -59,11 +59,14 @@ func (p *Password) Metadata() string { return metadata } -func (p *Password) Password() string { - decrypted, _ := p.decrypt() +func (p *Password) Password() (string, error) { + decrypted, err := p.decrypt() + if err != nil { + return "", err + } nr := bufio.NewReader(decrypted) password, _ := nr.ReadString('\n') - return password + return password, nil } // NewPasswordStore creates a new password store From ab575c67f091f8fec4f1402e843dd6000da71f3e Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Fri, 18 Nov 2016 14:04:08 -0800 Subject: [PATCH 2/4] Fix reset countdown bug --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 684b13d..231828a 100644 --- a/main.go +++ b/main.go @@ -183,6 +183,7 @@ var passwords Passwords var ps *PasswordStore func main() { + ui.countdownDone = make(chan bool) ps = NewPasswordStore() passwords.store = ps ps.Subscribe(passwords.Update) From fd9f1d82f11ce25a60657126199a4ef8f0505e0a Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Fri, 18 Nov 2016 14:25:16 -0800 Subject: [PATCH 3/4] Fix race condition on countdown and simplify logic --- main.go | 56 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/main.go b/main.go index 231828a..07e5e1d 100644 --- a/main.go +++ b/main.go @@ -6,18 +6,26 @@ import ( "os" "time" + "sync" + "github.com/atotto/clipboard" "github.com/limetext/qml-go" ) +const ( + timeoutTickDuration = 10 * time.Millisecond + clipboardTimeout = 15 * time.Second +) + // UI is the model for the password UI type UI struct { Status string query string - Countdown float64 - countingDown bool - countdownDone chan bool + lock sync.Mutex + Countdown float64 + countingDown bool + resetCountdown chan bool ShowMetadata bool @@ -29,6 +37,18 @@ type UI struct { } } +func (ui *UI) isCountingDown() bool { + ui.lock.Lock() + defer ui.lock.Unlock() + return ui.countingDown +} + +func (ui *UI) setCountingDown(value bool) { + ui.lock.Lock() + defer ui.lock.Unlock() + ui.countingDown = value +} + // Passwords is the model for the password list type Passwords struct { Selected int @@ -66,28 +86,28 @@ func (p *Passwords) Get(index int) Password { // ClearClipboard clears the clipboard func (ui *UI) ClearClipboard() { - if ui.countingDown { - ui.countdownDone <- true + + if ui.isCountingDown() { + ui.resetCountdown <- true + return } - ui.countingDown = true - tick := 10 * time.Millisecond - t := time.NewTicker(tick) - remaining := 15.0 + + ui.setCountingDown(true) + defer ui.setCountingDown(false) + t := time.NewTicker(timeoutTickDuration) + remaining := clipboardTimeout for { select { - case <-ui.countdownDone: - t.Stop() - ui.countingDown = false - return + case <-ui.resetCountdown: + remaining = clipboardTimeout case <-t.C: - ui.setCountdown(remaining) - ui.setStatus(fmt.Sprintf("Will clear in %.f seconds", remaining)) - remaining -= tick.Seconds() + ui.setCountdown(remaining.Seconds()) + ui.setStatus(fmt.Sprintf("Will clear in %.f seconds", remaining.Seconds())) + remaining -= timeoutTickDuration if remaining <= 0 { clipboard.WriteAll("") ui.Clearmetadata() ui.setStatus("Clipboard cleared") - ui.countingDown = false t.Stop() return } @@ -183,7 +203,7 @@ var passwords Passwords var ps *PasswordStore func main() { - ui.countdownDone = make(chan bool) + ui.resetCountdown = make(chan bool) ps = NewPasswordStore() passwords.store = ps ps.Subscribe(passwords.Update) From 9692c7b2c49b7d0829fdb0fbe01b35f37963063b Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Sun, 20 Nov 2016 22:17:30 -0800 Subject: [PATCH 4/4] Simplify and encapsulate counter logic. Limit number of spawning goroutines to 1 --- main.go | 89 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/main.go b/main.go index 07e5e1d..6ab01e6 100644 --- a/main.go +++ b/main.go @@ -22,10 +22,8 @@ type UI struct { Status string query string - lock sync.Mutex - Countdown float64 - countingDown bool - resetCountdown chan bool + Countdown float64 + counter Counter ShowMetadata bool @@ -37,16 +35,47 @@ type UI struct { } } -func (ui *UI) isCountingDown() bool { - ui.lock.Lock() - defer ui.lock.Unlock() - return ui.countingDown +type Counter struct { + sync.Mutex + t *time.Ticker + remaining time.Duration + countingDown bool +} + +func (c *Counter) isRunning() bool { + c.Lock() + defer c.Unlock() + return c.countingDown +} + +func (c *Counter) start(onTick func(remaining float64)) { + c.Lock() + defer c.Unlock() + c.remaining = clipboardTimeout + c.countingDown = true + c.t = time.NewTicker(timeoutTickDuration) + go func() { + for { + <-c.t.C + c.Lock() + c.remaining -= timeoutTickDuration + c.Unlock() + onTick(c.remaining.Seconds()) + } + }() +} + +func (c *Counter) stop() { + c.Lock() + defer c.Unlock() + c.countingDown = false + c.t.Stop() } -func (ui *UI) setCountingDown(value bool) { - ui.lock.Lock() - defer ui.lock.Unlock() - ui.countingDown = value +func (c *Counter) reset() { + c.Lock() + defer c.Unlock() + c.remaining = clipboardTimeout } // Passwords is the model for the password list @@ -87,32 +116,23 @@ func (p *Passwords) Get(index int) Password { // ClearClipboard clears the clipboard func (ui *UI) ClearClipboard() { - if ui.isCountingDown() { - ui.resetCountdown <- true + if ui.counter.isRunning() { + ui.counter.reset() return } - ui.setCountingDown(true) - defer ui.setCountingDown(false) - t := time.NewTicker(timeoutTickDuration) - remaining := clipboardTimeout - for { - select { - case <-ui.resetCountdown: - remaining = clipboardTimeout - case <-t.C: - ui.setCountdown(remaining.Seconds()) - ui.setStatus(fmt.Sprintf("Will clear in %.f seconds", remaining.Seconds())) - remaining -= timeoutTickDuration - if remaining <= 0 { - clipboard.WriteAll("") - ui.Clearmetadata() - ui.setStatus("Clipboard cleared") - t.Stop() - return - } + onTick := func(remaining float64) { + ui.setCountdown(ui.counter.remaining.Seconds()) + ui.setStatus(fmt.Sprintf("Will clear in %.f seconds", remaining)) + if remaining <= 0 { + clipboard.WriteAll("") + ui.Clearmetadata() + ui.setStatus("Clipboard cleared") + ui.counter.stop() } } + + ui.counter.start(onTick) } // CopyToClipboard copies the selected password to the system clipboard @@ -132,7 +152,7 @@ func (p *Passwords) CopyToClipboard(selected int) { panic(err) } ui.setStatus("Copied to clipboard") - go ui.ClearClipboard() + ui.ClearClipboard() p.Update("") // Trigger a manual update, since the key is probably unlocked now } @@ -203,7 +223,6 @@ var passwords Passwords var ps *PasswordStore func main() { - ui.resetCountdown = make(chan bool) ps = NewPasswordStore() passwords.store = ps ps.Subscribe(passwords.Update)