diff --git a/assets/main.qml b/assets/main.qml index c641ae4..db93be7 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" @@ -335,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 4338295..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) } @@ -121,7 +120,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) { @@ -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,30 +156,31 @@ 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) qml.Changed(&ui, &ui.Password.Metadata) qml.Changed(&ui, &ui.Password.Name) - ui.setStatus(status) + if status != "" { + ui.setStatus(status) + } } 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 2c058aa..6b0e7c1 100644 --- a/pass.go +++ b/pass.go @@ -4,7 +4,6 @@ import ( "bufio" "encoding/base64" "errors" - "fmt" "io" "io/ioutil" "log" @@ -14,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 } @@ -28,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 @@ -74,19 +69,22 @@ func NewPasswordStore() *PasswordStore { log.Fatal(err) } ps.Prefix = path + ps.passwords = make(map[string]string) ps.indexAll() ps.watch() return ps } // 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) +func (ps *PasswordStore) Query(q string) []string { + var hits []string + for pwPath, pwName := range ps.passwords { + if match(q, pwName) { + hits = append(hits, pwPath) } } + + sort.Strings(hits) return hits } @@ -119,23 +117,30 @@ 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:] - } - - ps.add(Password{Name: name, Path: path}) + ps.add(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.index(ps.Prefix) } func (ps *PasswordStore) watch() { @@ -146,15 +151,34 @@ func (ps *PasswordStore) watch() { go func() { for { - <-c - ps.indexAll() + ps.updateIndex(<-c) } }() } -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) 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: + ps.remove(eventInfo.Path()) + ps.indexAll() + ps.publishUpdate("Index updated") + case notify.Write: + // Path and Name haven ot changed, ignore. + } +} + +func (ps *PasswordStore) add(path string) { + ps.passwords[path] = generateName(path, ps.Prefix) +} + +func (ps *PasswordStore) remove(path string) { + delete(ps.passwords, path) } func findPasswordStore() (string, error) {