Use vault views for entry and recycle-bin state
This commit is contained in:
+11
-2
@@ -24,6 +24,7 @@ import (
|
||||
"git.julianfamily.org/keepassgo/internal/clipboard"
|
||||
"git.julianfamily.org/keepassgo/internal/session"
|
||||
"git.julianfamily.org/keepassgo/internal/vault"
|
||||
"git.julianfamily.org/keepassgo/internal/vaultview"
|
||||
)
|
||||
|
||||
func (u *ui) bannerSurface() uiBanner {
|
||||
@@ -558,6 +559,11 @@ func copyPath(path []string) []string {
|
||||
}
|
||||
|
||||
func pathExistsInModel(model vault.Model, path []string) bool {
|
||||
if len(path) > 0 && path[0] == "Root" {
|
||||
view := vaultview.VaultRoot(model)
|
||||
viewPath := entriesViewPathForModel(model, path)
|
||||
return len(view.EntriesInPath(viewPath)) > 0 || len(view.ChildGroups(viewPath)) > 0 || hasExactGroup(model, view.ToPhysicalPath(viewPath))
|
||||
}
|
||||
return len(model.EntriesInPath(path)) > 0 || len(model.ChildGroups(path)) > 0 || hasExactGroup(model, path)
|
||||
}
|
||||
|
||||
@@ -569,9 +575,12 @@ func normalizeEntriesPathWithoutModel(path []string, root string) []string {
|
||||
return []string{root}
|
||||
}
|
||||
if path[0] == "Root" {
|
||||
return copyPath(path)
|
||||
}
|
||||
if path[0] == vaultview.KeepassRoot {
|
||||
return append([]string{root}, path[1:]...)
|
||||
}
|
||||
return copyPath(path)
|
||||
return append([]string{root}, copyPath(path)...)
|
||||
}
|
||||
|
||||
func (u *ui) normalizedEntriesPath(path []string) []string {
|
||||
@@ -590,7 +599,7 @@ func (u *ui) normalizedEntriesPath(path []string) []string {
|
||||
return []string{root}
|
||||
}
|
||||
if path[0] == "Root" && root != "" {
|
||||
candidate := append([]string{root}, path[1:]...)
|
||||
candidate := copyPath(path)
|
||||
if pathExistsInModel(model, candidate) {
|
||||
return candidate
|
||||
}
|
||||
|
||||
+21
-21
@@ -3606,11 +3606,11 @@ func TestUICreateGroupActionSupportsNestedSubgroups(t *testing.T) {
|
||||
t.Fatalf("createGroupAction() error = %v", err)
|
||||
}
|
||||
|
||||
if got := u.state.Session.(*uiSession).model.ChildGroups([]string{"Root"}); !slices.Equal(got, []string{"Infrastructure"}) {
|
||||
t.Fatalf("ChildGroups(Root) = %v, want [Infrastructure]", got)
|
||||
if got := u.state.Session.(*uiSession).model.ChildGroups([]string{"keepass"}); !slices.Equal(got, []string{"Infrastructure"}) {
|
||||
t.Fatalf("ChildGroups(keepass) = %v, want [Infrastructure]", got)
|
||||
}
|
||||
if got := u.state.Session.(*uiSession).model.ChildGroups([]string{"Root", "Infrastructure"}); !slices.Equal(got, []string{"Prod"}) {
|
||||
t.Fatalf("ChildGroups(Root/Infrastructure) = %v, want [Prod]", got)
|
||||
if got := u.state.Session.(*uiSession).model.ChildGroups([]string{"keepass", "Infrastructure"}); !slices.Equal(got, []string{"Prod"}) {
|
||||
t.Fatalf("ChildGroups(keepass/Infrastructure) = %v, want [Prod]", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5125,8 +5125,8 @@ func TestUIAutoEntersSingleVaultRootGroupAndDisplaysSlashRoot(t *testing.T) {
|
||||
t.Fatalf("openVaultAction() error = %v", err)
|
||||
}
|
||||
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"keepass"}) {
|
||||
t.Fatalf("currentPath = %v, want [keepass]", got)
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"Root"}) {
|
||||
t.Fatalf("currentPath = %v, want [Root]", got)
|
||||
}
|
||||
if got := u.displayPath(); len(got) != 0 {
|
||||
t.Fatalf("displayPath() = %v, want root slash path", got)
|
||||
@@ -5152,8 +5152,8 @@ func TestUIAutoEntersSingleVaultRootWhenRecycleBinAlsoExists(t *testing.T) {
|
||||
|
||||
u.showEntriesSection()
|
||||
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"keepass"}) {
|
||||
t.Fatalf("currentPath = %v, want [keepass]", got)
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"Root"}) {
|
||||
t.Fatalf("currentPath = %v, want [Root]", got)
|
||||
}
|
||||
if got := u.displayPath(); len(got) != 0 {
|
||||
t.Fatalf("displayPath() = %v, want root slash path", got)
|
||||
@@ -5174,15 +5174,15 @@ func TestUIShowEntriesSectionRestoresHiddenRootAfterLeavingEntries(t *testing.T)
|
||||
})
|
||||
|
||||
u.showEntriesSection()
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"keepass"}) {
|
||||
t.Fatalf("currentPath after initial entries section = %v, want [keepass]", got)
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"Root"}) {
|
||||
t.Fatalf("currentPath after initial entries section = %v, want [Root]", got)
|
||||
}
|
||||
|
||||
u.showAPITokensSection()
|
||||
u.showEntriesSection()
|
||||
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"keepass"}) {
|
||||
t.Fatalf("currentPath after returning to entries = %v, want [keepass]", got)
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"Root"}) {
|
||||
t.Fatalf("currentPath after returning to entries = %v, want [Root]", got)
|
||||
}
|
||||
if got := u.displayPath(); len(got) != 0 {
|
||||
t.Fatalf("displayPath() after returning to entries = %v, want root slash path", got)
|
||||
@@ -5215,8 +5215,8 @@ func TestUISyncCurrentPathNormalizesHiddenRootAfterSectionSwitch(t *testing.T) {
|
||||
|
||||
u.syncCurrentPath()
|
||||
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"keepass"}) {
|
||||
t.Fatalf("currentPath after syncCurrentPath() = %v, want [keepass]", got)
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"Root"}) {
|
||||
t.Fatalf("currentPath after syncCurrentPath() = %v, want [Root]", got)
|
||||
}
|
||||
if got := u.displayPath(); len(got) != 0 {
|
||||
t.Fatalf("displayPath() after syncCurrentPath() = %v, want root slash path", got)
|
||||
@@ -5235,7 +5235,7 @@ func TestUIShowEntriesSectionRestoresEntriesViewState(t *testing.T) {
|
||||
})
|
||||
|
||||
u.showEntriesSection()
|
||||
u.setCurrentPath([]string{"keepass", "Crew", "Internet"})
|
||||
u.setCurrentPath([]string{"Root", "Crew", "Internet"})
|
||||
u.search.SetText("amazon")
|
||||
u.filter()
|
||||
u.state.SelectedEntryID = "amazon"
|
||||
@@ -5245,8 +5245,8 @@ func TestUIShowEntriesSectionRestoresEntriesViewState(t *testing.T) {
|
||||
u.showAPITokensSection()
|
||||
u.showEntriesSection()
|
||||
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"keepass", "Crew", "Internet"}) {
|
||||
t.Fatalf("currentPath after returning to entries = %v, want [keepass Crew Internet]", got)
|
||||
if got := u.currentPath; !slices.Equal(got, []string{"Root", "Crew", "Internet"}) {
|
||||
t.Fatalf("currentPath after returning to entries = %v, want [Root Crew Internet]", got)
|
||||
}
|
||||
if got := u.search.Text(); got != "amazon" {
|
||||
t.Fatalf("search text after returning to entries = %q, want amazon", got)
|
||||
@@ -8073,7 +8073,7 @@ func TestUISelectedRemoteCardUsesLocalCacheSummaryForBoundRemote(t *testing.T) {
|
||||
wantDetails := []string{
|
||||
"/vaults/cache",
|
||||
"Sync target: home.kdbx · dav.example.invalid",
|
||||
"Last group: Root / Internet",
|
||||
"Last group: Internet",
|
||||
}
|
||||
if !slices.Equal(gotDetails, wantDetails) {
|
||||
t.Fatalf("selectedRemoteCardDetailLines() = %v, want %v", gotDetails, wantDetails)
|
||||
@@ -8105,7 +8105,7 @@ func TestUISelectedRemoteCardUsesConnectionSummaryWithoutLocalCache(t *testing.T
|
||||
wantDetails := []string{
|
||||
"Path: vaults/home.kdbx",
|
||||
"Server: https://dav.example.invalid",
|
||||
"Last group: Root / Internet",
|
||||
"Last group: Internet",
|
||||
}
|
||||
if !slices.Equal(gotDetails, wantDetails) {
|
||||
t.Fatalf("selectedRemoteCardDetailLines() = %v, want %v", gotDetails, wantDetails)
|
||||
@@ -9327,8 +9327,8 @@ func TestUIAPIPolicyTargetActionsUseCurrentContext(t *testing.T) {
|
||||
if err := u.useCurrentGroupForPolicyAction(); err != nil {
|
||||
t.Fatalf("useCurrentGroupForPolicyAction() error = %v", err)
|
||||
}
|
||||
if got := u.apiPolicyPath.Text(); got != "bashertarr" {
|
||||
t.Fatalf("apiPolicyPath.Text() = %q, want %q", got, "bashertarr")
|
||||
if got := u.apiPolicyPath.Text(); got != "Crew / bashertarr" {
|
||||
t.Fatalf("apiPolicyPath.Text() = %q, want %q", got, "Crew / bashertarr")
|
||||
}
|
||||
if !u.apiPolicyGroupScopeW.Value {
|
||||
t.Fatal("apiPolicyGroupScopeW.Value = false, want true")
|
||||
|
||||
@@ -1260,14 +1260,10 @@ func (u *ui) recentVaultGroup(path string) []string {
|
||||
}
|
||||
|
||||
func (u *ui) hiddenVaultRoot() string {
|
||||
if u.state.Section != appstate.SectionEntries {
|
||||
return ""
|
||||
if u.state.Section == appstate.SectionEntries {
|
||||
return "Root"
|
||||
}
|
||||
model, err := u.state.Session.Current()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return vaultview.HiddenRoot(model)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *ui) enterHiddenVaultRoot() {
|
||||
@@ -1294,7 +1290,7 @@ func (u *ui) restoreRecentVaultGroup(path string) {
|
||||
u.setCurrentPath(saved)
|
||||
return
|
||||
}
|
||||
if len(model.EntriesInPath(saved)) > 0 || len(model.ChildGroups(saved)) > 0 || hasExactGroup(model, saved) {
|
||||
if pathExistsInModel(model, saved) {
|
||||
u.setCurrentPath(saved)
|
||||
return
|
||||
}
|
||||
@@ -1317,7 +1313,7 @@ func (u *ui) restoreRecentRemoteGroup(baseURL, path string) {
|
||||
u.setCurrentPath(saved)
|
||||
return
|
||||
}
|
||||
if len(model.EntriesInPath(saved)) > 0 || len(model.ChildGroups(saved)) > 0 || hasExactGroup(model, saved) {
|
||||
if pathExistsInModel(model, saved) {
|
||||
u.setCurrentPath(saved)
|
||||
return
|
||||
}
|
||||
@@ -1339,7 +1335,7 @@ func (u *ui) restoreEntriesPath(path []string) {
|
||||
u.setCurrentPath(path)
|
||||
return
|
||||
}
|
||||
if len(model.EntriesInPath(path)) > 0 || len(model.ChildGroups(path)) > 0 || hasExactGroup(model, path) {
|
||||
if pathExistsInModel(model, path) {
|
||||
u.setCurrentPath(path)
|
||||
return
|
||||
}
|
||||
@@ -1415,6 +1411,22 @@ func pathHasPrefix(path, prefix []string) bool {
|
||||
return slices.Equal(path[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func entriesViewPathForModel(model vault.Model, path []string) []string {
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case usesPhysicalEntriesRoot(model) && path[0] == "Root":
|
||||
return append([]string(nil), path[1:]...)
|
||||
case usesLogicalEntriesRoot(model):
|
||||
return append([]string(nil), path...)
|
||||
case path[0] == "Root":
|
||||
return append([]string(nil), path[1:]...)
|
||||
default:
|
||||
return append([]string(nil), path...)
|
||||
}
|
||||
}
|
||||
|
||||
func hasExactGroup(model vault.Model, path []string) bool {
|
||||
for _, group := range model.Groups {
|
||||
if slices.Equal(group, path) {
|
||||
@@ -1433,12 +1445,14 @@ func (u *ui) currentGroupDeletionState() (bool, string) {
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
path := append([]string(nil), u.currentPath...)
|
||||
if len(model.ChildGroups(path)) > 0 {
|
||||
view := vaultview.VaultRoot(model)
|
||||
path := entriesViewPathForModel(model, u.currentPath)
|
||||
physicalPath := view.ToPhysicalPath(path)
|
||||
if len(model.ChildGroups(physicalPath)) > 0 {
|
||||
return false, "This group contains child groups. Move or delete them before removing the group."
|
||||
}
|
||||
for _, item := range model.Entries {
|
||||
if slices.Equal(item.Path, path) || pathHasPrefix(item.Path, path) {
|
||||
if slices.Equal(item.Path, physicalPath) || pathHasPrefix(item.Path, physicalPath) {
|
||||
return false, "This group contains entries. Move or delete them before removing the group."
|
||||
}
|
||||
}
|
||||
@@ -1450,6 +1464,47 @@ func (u *ui) currentGroupDeletionState() (bool, string) {
|
||||
return true, "Deleting this empty group will not remove any entries."
|
||||
}
|
||||
|
||||
func usesPhysicalEntriesRoot(model vault.Model) bool {
|
||||
if len(model.Entries) == 0 && len(model.Groups) == 0 && len(model.RecycleBin) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, group := range model.Groups {
|
||||
if len(group) > 0 && group[0] == vaultview.KeepassRoot {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, entry := range model.Entries {
|
||||
if len(entry.Path) > 0 && entry.Path[0] == vaultview.KeepassRoot {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, entry := range model.RecycleBin {
|
||||
if len(entry.Path) > 0 && entry.Path[0] == vaultview.KeepassRoot {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func usesLogicalEntriesRoot(model vault.Model) bool {
|
||||
for _, group := range model.Groups {
|
||||
if len(group) > 0 && group[0] == "Root" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, entry := range model.Entries {
|
||||
if len(entry.Path) > 0 && entry.Path[0] == "Root" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, entry := range model.RecycleBin {
|
||||
if len(entry.Path) > 0 && entry.Path[0] == "Root" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *ui) deleteGroupPendingConfirmation() bool {
|
||||
return len(u.deleteGroupPath) > 0 && slices.Equal(u.deleteGroupPath, u.currentPath)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user