From 55bc644e2ac49463d8717a525a1b6cdb8e613cc6 Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Wed, 9 Nov 2016 14:00:32 -0800 Subject: [PATCH 1/5] Fix index update --- pass.go | 60 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/pass.go b/pass.go index 2c058aa..d0736fb 100644 --- a/pass.go +++ b/pass.go @@ -4,7 +4,6 @@ import ( "bufio" "encoding/base64" "errors" - "fmt" "io" "io/ioutil" "log" @@ -14,6 +13,8 @@ import ( "path/filepath" "strings" + "fmt" + "github.com/proglottis/gpgme" "github.com/rjeczalik/notify" ) @@ -119,23 +120,32 @@ func match(query, candidate string) bool { } +func generateName(path, prefix string) string { + name := strings.TrimPrefix(path, prefix) + name = strings.TrimSuffix(name, ".gpg") + name = strings.TrimPrefix(name, "/") + const MaxLen = 40 + if len(name) > MaxLen { + name = "..." + name[len(name)-MaxLen:] + } + return name +} + func (ps *PasswordStore) indexFile(path string, info os.FileInfo, err error) error { if strings.HasSuffix(path, ".gpg") { - name := strings.TrimPrefix(path, ps.Prefix) - name = strings.TrimSuffix(name, ".gpg") - name = strings.TrimPrefix(name, "/") - const MaxLen = 40 - if len(name) > MaxLen { - name = "..." + name[len(name)-MaxLen:] - } - + name := generateName(path, ps.Prefix) ps.add(Password{Name: name, Path: path}) } return nil } +func (ps *PasswordStore) index(path string) { + filepath.Walk(path, ps.indexFile) +} + func (ps *PasswordStore) indexAll() { - filepath.Walk(ps.Prefix, ps.indexFile) + ps.clearAll() + ps.index(ps.Prefix) } func (ps *PasswordStore) watch() { @@ -146,17 +156,43 @@ func (ps *PasswordStore) watch() { go func() { for { - <-c - ps.indexAll() + eventInfo := <-c + switch eventInfo.Event() { + case notify.Create: + ps.index(eventInfo.Path()) + case notify.Remove: + ps.remove(eventInfo.Path()) + case notify.Rename: + // EventInfo contains old path, but we don't know the new one. Update all + ps.indexAll() + case notify.Write: + // Path and Name haven ot changed, ignore. + } + } }() } +func (ps *PasswordStore) clearAll() { + ps.passwords = nil +} + func (ps *PasswordStore) add(p Password) { ps.passwords = append(ps.passwords, p) ps.publishUpdate(fmt.Sprintf("Indexed %d entries", len(ps.passwords))) } +func (ps *PasswordStore) remove(path string) { + for i, p := range ps.passwords { + if p.Path == path { + ps.passwords[i] = ps.passwords[len(ps.passwords)-1] + ps.passwords = ps.passwords[:len(ps.passwords)-1] + ps.publishUpdate(fmt.Sprintf("Indexed %d entries", len(ps.passwords))) + return + } + } +} + func findPasswordStore() (string, error) { var homeDir string From 4d9bdfc19f260652c15379ea08b83fd216651e8e Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Wed, 9 Nov 2016 14:16:31 -0800 Subject: [PATCH 2/5] Extract indexUpdate function. Improve UI updates --- main.go | 6 ++++-- pass.go | 35 ++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index 4338295..153000e 100644 --- a/main.go +++ b/main.go @@ -121,7 +121,7 @@ func (p *Passwords) Select(selected int) { // Query updates the hitlist with the given query func (ui *UI) Query(q string) { ui.query = q - passwords.Update("queried") + passwords.Update("Queried") } func (ui *UI) setStatus(s string) { @@ -170,7 +170,9 @@ func (p *Passwords) Update(status string) { qml.Changed(&ui, &ui.Password) qml.Changed(&ui, &ui.Password.Metadata) qml.Changed(&ui, &ui.Password.Name) - ui.setStatus(status) + if status != "" { + ui.setStatus(status) + } } var ui UI diff --git a/pass.go b/pass.go index d0736fb..45b9895 100644 --- a/pass.go +++ b/pass.go @@ -13,8 +13,6 @@ import ( "path/filepath" "strings" - "fmt" - "github.com/proglottis/gpgme" "github.com/rjeczalik/notify" ) @@ -156,30 +154,34 @@ func (ps *PasswordStore) watch() { go func() { for { - eventInfo := <-c - switch eventInfo.Event() { - case notify.Create: - ps.index(eventInfo.Path()) - case notify.Remove: - ps.remove(eventInfo.Path()) - case notify.Rename: - // EventInfo contains old path, but we don't know the new one. Update all - ps.indexAll() - case notify.Write: - // Path and Name haven ot changed, ignore. - } - + ps.updateIndex(<-c) } }() } +func (ps *PasswordStore) updateIndex(eventInfo notify.EventInfo) { + switch eventInfo.Event() { + case notify.Create: + ps.index(eventInfo.Path()) + ps.publishUpdate("Entry added") + case notify.Remove: + ps.remove(eventInfo.Path()) + ps.publishUpdate("Entry removed") + case notify.Rename: + // EventInfo contains old path, but we don't know the new one. Update all + ps.indexAll() + ps.publishUpdate("Index updated") + case notify.Write: + // Path and Name haven ot changed, ignore. + } +} + func (ps *PasswordStore) clearAll() { ps.passwords = nil } func (ps *PasswordStore) add(p Password) { ps.passwords = append(ps.passwords, p) - ps.publishUpdate(fmt.Sprintf("Indexed %d entries", len(ps.passwords))) } func (ps *PasswordStore) remove(path string) { @@ -187,7 +189,6 @@ func (ps *PasswordStore) remove(path string) { if p.Path == path { ps.passwords[i] = ps.passwords[len(ps.passwords)-1] ps.passwords = ps.passwords[:len(ps.passwords)-1] - ps.publishUpdate(fmt.Sprintf("Indexed %d entries", len(ps.passwords))) return } } From bbfd4da961b84d185eb0cad68acaea463ccd155f Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Wed, 9 Nov 2016 16:52:42 -0800 Subject: [PATCH 3/5] Minor cleanup --- assets/main.qml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/assets/main.qml b/assets/main.qml index c641ae4..b18d1df 100644 --- a/assets/main.qml +++ b/assets/main.qml @@ -20,12 +20,7 @@ ApplicationWindow { MouseArea { id: mouseRegion - property variant clickPos: "1,1" - anchors.rightMargin: 0 - anchors.bottomMargin: 0 - anchors.leftMargin: 0 - anchors.topMargin: 0 - + property var clickPos: "1, 1" anchors.fill: parent; onPressed: { @@ -44,10 +39,6 @@ ApplicationWindow { color: "#333" radius: 10 - anchors.rightMargin: 0 - anchors.bottomMargin: 0 - anchors.leftMargin: 0 - anchors.topMargin: 0 anchors.fill: parent border.width: 2 border.color: "#aaa" From 3e7637579dcca7175028fff4d3ab95903fd65355 Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Wed, 9 Nov 2016 16:51:31 -0800 Subject: [PATCH 4/5] Switch to a map-based storage for passwords --- pass.go | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/pass.go b/pass.go index 45b9895..fefb49c 100644 --- a/pass.go +++ b/pass.go @@ -13,13 +13,15 @@ import ( "path/filepath" "strings" + "sort" + "github.com/proglottis/gpgme" "github.com/rjeczalik/notify" ) // PasswordStore keeps track of all the passwords type PasswordStore struct { - passwords []Password + passwords map[string]string Prefix string subscribers []Subscriber } @@ -73,19 +75,28 @@ func NewPasswordStore() *PasswordStore { log.Fatal(err) } ps.Prefix = path + ps.passwords = make(map[string]string) ps.indexAll() ps.watch() return ps } +type byName []Password + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } + // Query the PasswordStore func (ps *PasswordStore) Query(q string) []Password { var hits []Password - for _, p := range ps.passwords { - if match(q, p.Name) { - hits = append(hits, p) + for pwPath, pwName := range ps.passwords { + if match(q, pwName) { + hits = append(hits, Password{Name: pwName, Path: pwPath}) } } + + sort.Sort(byName(hits)) return hits } @@ -131,8 +142,7 @@ func generateName(path, prefix string) string { func (ps *PasswordStore) indexFile(path string, info os.FileInfo, err error) error { if strings.HasSuffix(path, ".gpg") { - name := generateName(path, ps.Prefix) - ps.add(Password{Name: name, Path: path}) + ps.add(path) } return nil } @@ -142,7 +152,6 @@ func (ps *PasswordStore) index(path string) { } func (ps *PasswordStore) indexAll() { - ps.clearAll() ps.index(ps.Prefix) } @@ -168,7 +177,7 @@ func (ps *PasswordStore) updateIndex(eventInfo notify.EventInfo) { ps.remove(eventInfo.Path()) ps.publishUpdate("Entry removed") case notify.Rename: - // EventInfo contains old path, but we don't know the new one. Update all + ps.remove(eventInfo.Path()) ps.indexAll() ps.publishUpdate("Index updated") case notify.Write: @@ -176,22 +185,12 @@ func (ps *PasswordStore) updateIndex(eventInfo notify.EventInfo) { } } -func (ps *PasswordStore) clearAll() { - ps.passwords = nil -} - -func (ps *PasswordStore) add(p Password) { - ps.passwords = append(ps.passwords, p) +func (ps *PasswordStore) add(path string) { + ps.passwords[path] = generateName(path, ps.Prefix) } func (ps *PasswordStore) remove(path string) { - for i, p := range ps.passwords { - if p.Path == path { - ps.passwords[i] = ps.passwords[len(ps.passwords)-1] - ps.passwords = ps.passwords[:len(ps.passwords)-1] - return - } - } + delete(ps.passwords, path) } func findPasswordStore() (string, error) { From ee9897bd2ec2398967b929814ee0b016fdc1dba1 Mon Sep 17 00:00:00 2001 From: Luis Maqueda Date: Mon, 14 Nov 2016 11:52:08 -0800 Subject: [PATCH 5/5] Switch to a map-based storage for passwords (cont.) --- assets/main.qml | 2 +- keyinfo.go | 9 ++------- main.go | 26 ++++++++++++-------------- pass.go | 36 ++++++++++++------------------------ 4 files changed, 27 insertions(+), 46 deletions(-) diff --git a/assets/main.qml b/assets/main.qml index b18d1df..db93be7 100644 --- a/assets/main.qml +++ b/assets/main.qml @@ -326,7 +326,7 @@ ApplicationWindow { property var view: ListView.view property int itemIndex: index - text: passwords.get(index).name; + text: passwords.get(index); font.pixelSize: 18 color: ListView.isCurrentItem? "#dd00bb":"gray" diff --git a/keyinfo.go b/keyinfo.go index bd69944..5c69337 100644 --- a/keyinfo.go +++ b/keyinfo.go @@ -67,11 +67,6 @@ func parseKeyinfo(statusLine string) GPGAgentKeyInfo { } } -func (p *Password) isCached() bool { - ki := p.KeyInfo() - return ki.Cached -} - var algoNames map[packet.PublicKeyAlgorithm]string func init() { @@ -91,11 +86,11 @@ func algoString(a packet.PublicKeyAlgorithm) string { } // KeyInfo gets the KeyInfo for this password -func (p *Password) KeyInfo() KeyInfo { +func keyInfo(path string) KeyInfo { gpgmeMutex.Lock() defer gpgmeMutex.Unlock() // Find the keyID for the encrypted data - encKeyID := findKey(p.Path) + encKeyID := findKey(path) // Extract key from gpgme c, _ := gpgme.New() diff --git a/main.go b/main.go index 153000e..3c395e1 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ type Passwords struct { Selected int Len int store *PasswordStore - hits []Password + hits []string } // Quit the application @@ -55,13 +55,13 @@ func (ui *UI) ToggleShowMetadata() { } // Get gets the password at a specific index -func (p *Passwords) Get(index int) Password { +func (p *Passwords) Get(index int) string { if index > len(p.hits) { fmt.Println("Bad password fetch", index, len(p.hits), p.Len) - return Password{} + return "" } pw := p.hits[index] - return pw + return p.store.passwords[pw] } // ClearClipboard clears the clipboard @@ -101,8 +101,7 @@ func (p *Passwords) CopyToClipboard(selected int) { ui.setStatus("No password selected") return } - pw := (p.hits)[selected] - pass := pw.Password() + pass := Password(p.hits[selected]) if err := clipboard.WriteAll(pass); err != nil { panic(err) } @@ -143,12 +142,12 @@ func (p *Passwords) Update(status string) { p.hits = p.store.Query(ui.query) p.Len = len(p.hits) - var pw Password + var pw string ui.Password.Info = "Test" if p.Selected < p.Len { pw = (p.hits)[p.Selected] - ki := pw.KeyInfo() + ki := keyInfo(pw) if ki.Algorithm != "" { ui.Password.Info = fmt.Sprintf("Encrypted with %d bit %s key %s", ki.BitLength, ki.Algorithm, ki.Fingerprint) @@ -157,14 +156,14 @@ func (p *Passwords) Update(status string) { ui.Password.Info = "Not encrypted" ui.Password.Cached = false } - ui.Password.Name = pw.Name + ui.Password.Name = p.store.passwords[pw] } if ui.ShowMetadata { - ui.Password.Metadata = pw.Metadata() + ui.Password.Metadata = Metadata(pw) } else { ui.Password.Metadata = "Press enter to decrypt" - ui.Password.Metadata = pw.Raw() + ui.Password.Metadata = Raw(pw) } qml.Changed(p, &p.Len) qml.Changed(&ui, &ui.Password) @@ -177,12 +176,11 @@ func (p *Passwords) Update(status string) { var ui UI var passwords Passwords -var ps *PasswordStore func main() { - ps = NewPasswordStore() - passwords.store = ps + ps := NewPasswordStore() ps.Subscribe(passwords.Update) + passwords.store = ps passwords.Update("Started") if err := qml.Run(run); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) diff --git a/pass.go b/pass.go index fefb49c..6b0e7c1 100644 --- a/pass.go +++ b/pass.go @@ -29,39 +29,33 @@ type PasswordStore struct { // Subscriber is a callback for changes in the PasswordStore type Subscriber func(status string) -// A Password entry in Passwords -type Password struct { - Name string - Path string -} - -func (p *Password) decrypt() (io.Reader, error) { +func decrypt(path string) (io.Reader, error) { gpgmeMutex.Lock() defer gpgmeMutex.Unlock() - file, _ := os.Open(p.Path) + file, _ := os.Open(path) defer file.Close() return gpgme.Decrypt(file) } // Raw returns the password in encrypted form -func (p *Password) Raw() string { - file, _ := os.Open(p.Path) +func Raw(path string) string { + file, _ := os.Open(path) defer file.Close() data, _ := ioutil.ReadAll(file) return base64.StdEncoding.EncodeToString(data) } // Metadata of the password -func (p *Password) Metadata() string { - out, _ := p.decrypt() +func Metadata(path string) string { + out, _ := decrypt(path) nr := bufio.NewReader(out) nr.ReadString('\n') metadata, _ := nr.ReadString('\003') return metadata } -func (p *Password) Password() string { - decrypted, _ := p.decrypt() +func Password(path string) string { + decrypted, _ := decrypt(path) nr := bufio.NewReader(decrypted) password, _ := nr.ReadString('\n') return password @@ -81,22 +75,16 @@ func NewPasswordStore() *PasswordStore { return ps } -type byName []Password - -func (a byName) Len() int { return len(a) } -func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } - // Query the PasswordStore -func (ps *PasswordStore) Query(q string) []Password { - var hits []Password +func (ps *PasswordStore) Query(q string) []string { + var hits []string for pwPath, pwName := range ps.passwords { if match(q, pwName) { - hits = append(hits, Password{Name: pwName, Path: pwPath}) + hits = append(hits, pwPath) } } - sort.Sort(byName(hits)) + sort.Strings(hits) return hits }