diff --git a/src/KeyKeeper/PasswordStore/IPassStore.cs b/src/KeyKeeper/PasswordStore/IPassStore.cs index 617df9b..e7fbe14 100644 --- a/src/KeyKeeper/PasswordStore/IPassStore.cs +++ b/src/KeyKeeper/PasswordStore/IPassStore.cs @@ -7,6 +7,7 @@ public interface IPassStore bool Locked { get; } IPassStoreDirectory GetRootDirectory(); + IPassStoreDirectory? GetGroupByType(byte groupType); int GetTotalEntryCount(); void Unlock(CompositeKey key); void Lock(); diff --git a/src/KeyKeeper/PasswordStore/IPassStoreDirectory.cs b/src/KeyKeeper/PasswordStore/IPassStoreDirectory.cs index 044c30d..beb0718 100644 --- a/src/KeyKeeper/PasswordStore/IPassStoreDirectory.cs +++ b/src/KeyKeeper/PasswordStore/IPassStoreDirectory.cs @@ -6,4 +6,5 @@ namespace KeyKeeper.PasswordStore; public interface IPassStoreDirectory : IEnumerable { bool DeleteEntry(Guid id); + void AddEntry(PassStoreEntry entry); } diff --git a/src/KeyKeeper/PasswordStore/PassStoreEntryGroup.cs b/src/KeyKeeper/PasswordStore/PassStoreEntryGroup.cs index bf627d9..4bed5da 100644 --- a/src/KeyKeeper/PasswordStore/PassStoreEntryGroup.cs +++ b/src/KeyKeeper/PasswordStore/PassStoreEntryGroup.cs @@ -64,6 +64,12 @@ public class PassStoreEntryGroup : PassStoreEntry, IPassStoreDirectory } } + public void AddEntry(PassStoreEntry entry) + { + entry.Parent = this; + ChildEntries.Add(entry); + } + public bool DeleteEntry(Guid id) { if (ChildEntries == null) diff --git a/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs b/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs index 640c87e..7b8f471 100644 --- a/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs +++ b/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs @@ -49,6 +49,15 @@ public class PassStoreFileAccessor : IPassStore return (IPassStoreDirectory)root!; } + public IPassStoreDirectory? GetGroupByType(byte groupType) + { + if (Locked) + throw new InvalidOperationException(); + return (root as PassStoreEntryGroup)?.ChildEntries + .OfType() + .FirstOrDefault(g => g.GroupType == groupType); + } + public int GetTotalEntryCount() { throw new NotImplementedException(); @@ -252,15 +261,24 @@ public class PassStoreFileAccessor : IPassStore private PassStoreEntry WriteInitialStoreTree(OuterEncryptionWriter w) { - PassStoreEntry root = - new PassStoreEntryGroup( - Guid.NewGuid(), - DateTime.UtcNow, - DateTime.UtcNow, - Guid.Empty, - "", - GROUP_TYPE_ROOT - ); + PassStoreEntryGroup defaultGroup = new( + Guid.NewGuid(), + DateTime.UtcNow, + DateTime.UtcNow, + Guid.Empty, + "", + GROUP_TYPE_DEFAULT + ); + PassStoreEntryGroup root = new( + Guid.NewGuid(), + DateTime.UtcNow, + DateTime.UtcNow, + Guid.Empty, + "", + GROUP_TYPE_ROOT, + [defaultGroup] + ); + defaultGroup.Parent = root; root.WriteToStream(w); return root; } diff --git a/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs b/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs index 77d810b..48fab08 100644 --- a/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs +++ b/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Avalonia.Threading; using KeyKeeper.PasswordStore; +using static KeyKeeper.PasswordStore.FileFormatConstants; namespace KeyKeeper.ViewModels; @@ -44,7 +45,7 @@ public partial class RepositoryWindowViewModel : ViewModelBase } /// - /// ( ). + /// Сбрасывает таймер блокировки (вызывается при любой активности пользователя). /// public void ResetLockTimer() { @@ -54,7 +55,9 @@ public partial class RepositoryWindowViewModel : ViewModelBase private void SwitchToUnlocked() { - CurrentPage = new UnlockedRepositoryViewModel(passStore); + var directory = passStore.GetGroupByType(GROUP_TYPE_DEFAULT) + ?? passStore.GetRootDirectory(); + CurrentPage = new UnlockedRepositoryViewModel(passStore, directory); StartLockTimer(); } @@ -109,4 +112,4 @@ public partial class RepositoryWindowViewModel : ViewModelBase var r = remaining ?? LockTimeout; LockTimerDisplay = $"{r:mm\\:ss}"; } -} \ No newline at end of file +} diff --git a/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs b/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs index fe37cd5..2ffbe7c 100644 --- a/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs +++ b/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs @@ -8,39 +8,64 @@ namespace KeyKeeper.ViewModels; public class UnlockedRepositoryViewModel : ViewModelBase { private IPassStore passStore; + private IPassStoreDirectory currentDirectory; + private bool hasUnsavedChanges; public IEnumerable Passwords { get { - return passStore.GetRootDirectory() + return currentDirectory .Where(entry => entry is PassStoreEntryPassword) .Select(entry => (entry as PassStoreEntryPassword)!); } } - public UnlockedRepositoryViewModel(IPassStore store) + public bool HasUnsavedChanges + { + get => hasUnsavedChanges; + private set + { + hasUnsavedChanges = value; + OnPropertyChanged(nameof(HasUnsavedChanges)); + } + } + + public UnlockedRepositoryViewModel(IPassStore store, IPassStoreDirectory directory) { passStore = store; + currentDirectory = directory; + HasUnsavedChanges = false; } public void AddEntry(PassStoreEntry entry) { if (entry is PassStoreEntryPassword) { - (passStore.GetRootDirectory() as PassStoreEntryGroup)!.ChildEntries.Add(entry); + currentDirectory.AddEntry(entry); + HasUnsavedChanges = true; OnPropertyChanged(nameof(Passwords)); } } public void DeleteEntry(Guid id) { - (passStore.GetRootDirectory() as PassStoreEntryGroup)!.DeleteEntry(id); + currentDirectory.DeleteEntry(id); + HasUnsavedChanges = true; + OnPropertyChanged(nameof(Passwords)); + } + + public void UpdateEntry(PassStoreEntryPassword updatedEntry) + { + currentDirectory.DeleteEntry(updatedEntry.Id); + currentDirectory.AddEntry(updatedEntry); + HasUnsavedChanges = true; OnPropertyChanged(nameof(Passwords)); } public void Save() { passStore.Save(); + HasUnsavedChanges = false; } -} \ No newline at end of file +} diff --git a/src/KeyKeeper/Views/CloseConfirmationDialog.axaml b/src/KeyKeeper/Views/CloseConfirmationDialog.axaml new file mode 100644 index 0000000..2779e45 --- /dev/null +++ b/src/KeyKeeper/Views/CloseConfirmationDialog.axaml @@ -0,0 +1,29 @@ + + + + + +