Persist recent KeePassGO vault list
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -164,6 +165,7 @@ type ui struct {
|
|||||||
lifecycleMode string
|
lifecycleMode string
|
||||||
keyboardFocus focusID
|
keyboardFocus focusID
|
||||||
defaultSaveAsPath string
|
defaultSaveAsPath string
|
||||||
|
recentVaultsPath string
|
||||||
editingEntry bool
|
editingEntry bool
|
||||||
recentVaults []string
|
recentVaults []string
|
||||||
}
|
}
|
||||||
@@ -241,6 +243,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession) *ui {
|
|||||||
selectedHistoryIndex: -1,
|
selectedHistoryIndex: -1,
|
||||||
lifecycleMode: "local",
|
lifecycleMode: "local",
|
||||||
defaultSaveAsPath: defaultSaveAsPath(),
|
defaultSaveAsPath: defaultSaveAsPath(),
|
||||||
|
recentVaultsPath: defaultRecentVaultsPath(),
|
||||||
}
|
}
|
||||||
u.state.Session = sess
|
u.state.Session = sess
|
||||||
u.phoneSplit.Value = 0.46
|
u.phoneSplit.Value = 0.46
|
||||||
@@ -249,6 +252,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession) *ui {
|
|||||||
u.copyIcon, _ = widget.NewIcon(icons.ContentContentCopy)
|
u.copyIcon, _ = widget.NewIcon(icons.ContentContentCopy)
|
||||||
u.passwordProfile.SetText("strong")
|
u.passwordProfile.SetText("strong")
|
||||||
u.keyboardFocus = focusSearch
|
u.keyboardFocus = focusSearch
|
||||||
|
u.loadRecentVaults()
|
||||||
u.filter()
|
u.filter()
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
@@ -274,6 +278,14 @@ func defaultSaveAsPath() string {
|
|||||||
return filepath.Join(cacheDir, "keepassgo", "vault.kdbx")
|
return filepath.Join(cacheDir, "keepassgo", "vault.kdbx")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultRecentVaultsPath() string {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil || strings.TrimSpace(configDir) == "" {
|
||||||
|
configDir = os.TempDir()
|
||||||
|
}
|
||||||
|
return filepath.Join(configDir, "keepassgo", "recent-vaults.json")
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) selectedAttachmentItems() []attachmentItem {
|
func (u *ui) selectedAttachmentItems() []attachmentItem {
|
||||||
item, ok := u.selectedEntry()
|
item, ok := u.selectedEntry()
|
||||||
if !ok || len(item.Attachments) == 0 {
|
if !ok || len(item.Attachments) == 0 {
|
||||||
@@ -561,6 +573,52 @@ func (u *ui) noteRecentVault(path string) {
|
|||||||
if len(u.recentVaultClicks) < len(u.recentVaults) {
|
if len(u.recentVaultClicks) < len(u.recentVaults) {
|
||||||
u.recentVaultClicks = make([]widget.Clickable, len(u.recentVaults))
|
u.recentVaultClicks = make([]widget.Clickable, len(u.recentVaults))
|
||||||
}
|
}
|
||||||
|
u.saveRecentVaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) loadRecentVaults() {
|
||||||
|
if strings.TrimSpace(u.recentVaultsPath) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(u.recentVaultsPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var paths []string
|
||||||
|
if err := json.Unmarshal(content, &paths); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filtered := make([]string, 0, len(paths))
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, path := range paths {
|
||||||
|
path = strings.TrimSpace(path)
|
||||||
|
if path == "" || seen[path] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[path] = true
|
||||||
|
filtered = append(filtered, path)
|
||||||
|
if len(filtered) == 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.recentVaults = filtered
|
||||||
|
if len(u.recentVaultClicks) < len(u.recentVaults) {
|
||||||
|
u.recentVaultClicks = make([]widget.Clickable, len(u.recentVaults))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) saveRecentVaults() {
|
||||||
|
if strings.TrimSpace(u.recentVaultsPath) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(u.recentVaultsPath), 0o700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content, err := json.MarshalIndent(u.recentVaults, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = os.WriteFile(u.recentVaultsPath, content, 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) hiddenVaultRoot() string {
|
func (u *ui) hiddenVaultRoot() string {
|
||||||
|
|||||||
@@ -1732,6 +1732,7 @@ func TestUINoteRecentVaultDeduplicatesAndOrdersMostRecentFirst(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
u := newUIWithSession("desktop", &session.Manager{})
|
u := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
u.recentVaultsPath = filepath.Join(t.TempDir(), "recent-vaults.json")
|
||||||
u.noteRecentVault("/tmp/one.kdbx")
|
u.noteRecentVault("/tmp/one.kdbx")
|
||||||
u.noteRecentVault("/tmp/two.kdbx")
|
u.noteRecentVault("/tmp/two.kdbx")
|
||||||
u.noteRecentVault("/tmp/one.kdbx")
|
u.noteRecentVault("/tmp/one.kdbx")
|
||||||
@@ -1741,6 +1742,24 @@ func TestUINoteRecentVaultDeduplicatesAndOrdersMostRecentFirst(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUILoadsRecentVaultsFromPersistedConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
configPath := filepath.Join(t.TempDir(), "recent-vaults.json")
|
||||||
|
first := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
first.recentVaultsPath = configPath
|
||||||
|
first.noteRecentVault("/tmp/one.kdbx")
|
||||||
|
first.noteRecentVault("/tmp/two.kdbx")
|
||||||
|
|
||||||
|
second := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
second.recentVaultsPath = configPath
|
||||||
|
second.loadRecentVaults()
|
||||||
|
|
||||||
|
if got := second.recentVaults; !slices.Equal(got, []string{"/tmp/two.kdbx", "/tmp/one.kdbx"}) {
|
||||||
|
t.Fatalf("recentVaults after reload = %v, want [/tmp/two.kdbx /tmp/one.kdbx]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user