diff --git a/App/Keychain.csproj b/App/Keychain.csproj index 415ac19..d30f67a 100644 --- a/App/Keychain.csproj +++ b/App/Keychain.csproj @@ -19,6 +19,7 @@ + diff --git a/App/Program.cs b/App/Program.cs index ecd4b5d..4d00e40 100644 --- a/App/Program.cs +++ b/App/Program.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Logic; +using Repository; namespace Keychain; @@ -25,8 +26,8 @@ class Program private static ServiceProvider SetupServices() { var services = new ServiceCollection(); - //services.AddTransient(); services.AddSingleton(); + services.AddSingleton(); return services.BuildServiceProvider(); } } \ No newline at end of file diff --git a/App/UI/MainWindow/MainWindow.cs b/App/UI/MainWindow/MainWindow.cs index c35dfa4..fece2e9 100644 --- a/App/UI/MainWindow/MainWindow.cs +++ b/App/UI/MainWindow/MainWindow.cs @@ -1,5 +1,5 @@ using Adw; -using Keychain.UI.ViewModels; +using Keychain.ViewModels; namespace Keychain.UI; diff --git a/App/ViewModels/PasswordStoreShortcut.cs b/App/ViewModels/PasswordStoreShortcut.cs index 242cd2f..6437ae0 100644 --- a/App/ViewModels/PasswordStoreShortcut.cs +++ b/App/ViewModels/PasswordStoreShortcut.cs @@ -5,16 +5,9 @@ namespace Keychain.ViewModels; public class PasswordStoreShortcut : INotifyPropertyChanged { - private IPasswordService passwordService; + private IPasswordStoreService passwordService; public event PropertyChangedEventHandler? PropertyChanged; - private string displayName; - private bool displayNameSet = false; - private string? iconName; - private string path; - public bool DisplayNameSet { get => displayNameSet; } - - public string DisplayName { get => displayName; diff --git a/Logic/IPasswordStoreService.cs b/Logic/IPasswordStoreService.cs index 7ceb3f3..74b5969 100644 --- a/Logic/IPasswordStoreService.cs +++ b/Logic/IPasswordStoreService.cs @@ -6,9 +6,8 @@ public interface IPasswordStoreService { IEnumerable GetAll(); PasswordStore Get(uint ID); - int Delete(uint ID); - int Delete(PasswordStore item); - int Create(string path, string? displayName = null, string? iconName = null); - int Create(PasswordStore item); - int Edit(uint ID, PasswordStore newItem); + void Delete(uint ID); + void Delete(PasswordStore item); + void Create(PasswordStore item); + void Edit(uint ID, PasswordStore newItem); } diff --git a/Logic/PasswordStoreService.cs b/Logic/PasswordStoreService.cs index f0407d4..df7bb02 100644 --- a/Logic/PasswordStoreService.cs +++ b/Logic/PasswordStoreService.cs @@ -7,38 +7,33 @@ public class PasswordStoreService : IPasswordStoreService { private readonly IRepository repository; - public int Create(string path, string? displayName = null, string? iconName = null) + public void Create(PasswordStore item) + { + Create(item); + } + + public void Delete(uint ID) { throw new NotImplementedException(); } - public int Create(PasswordStore item) + public void Delete(PasswordStore item) { - return Create(item.Path, item.DisplayName, item.IconName); + Delete(item.ID); } - public int Delete(uint ID) - { - throw new NotImplementedException(); - } - - public int Delete(PasswordStore item) - { - return Delete(item.ID); - } - - public int Edit(uint ID, PasswordStore newItem) + public void Edit(uint ID, PasswordStore newItem) { throw new NotImplementedException(); } public PasswordStore Get(uint ID) { - return repository.ReadAll().Where(item => item.ID.Equals(ID)).First(); + return repository.Get(ID); } public IEnumerable GetAll() { - return (IEnumerable)repository.ReadAll(); + return (IEnumerable)repository.GetAll(); } } diff --git a/Models/PasswordStore.cs b/Models/PasswordStore.cs index 36243b7..28f6517 100644 --- a/Models/PasswordStore.cs +++ b/Models/PasswordStore.cs @@ -2,8 +2,8 @@ public class PasswordStore { - public uint ID; - public string Path; - public string? DisplayName; - public string? IconName; + public uint ID { get; set; } + public string Path { get; set; } + public string? DisplayName { get; set; } + public string? IconName { get; set; } } \ No newline at end of file diff --git a/Repository/IRepository.cs b/Repository/IRepository.cs index af02874..5f2ddd4 100644 --- a/Repository/IRepository.cs +++ b/Repository/IRepository.cs @@ -1,10 +1,14 @@ using System.Collections; +using Models; namespace Repository; public interface IRepository { - IEnumerable ReadAll(); - void WriteAll(IEnumerable items); - object Get(uint id); + List GetAll(); + PasswordStore? Get(uint id); + void Edit(uint ID, PasswordStore newItem); + void Create(PasswordStore item); + void Delete(uint ID); + void Delete(PasswordStore item); } \ No newline at end of file diff --git a/Repository/JsonRepository.cs b/Repository/JsonRepository.cs new file mode 100644 index 0000000..d28602f --- /dev/null +++ b/Repository/JsonRepository.cs @@ -0,0 +1,172 @@ +using System.Text.Json; +using Models; + +namespace Repository; + +public class JsonRepository : IRepository, IDisposable +{ + private const string _appName = "Keychain"; + + private uint _autoIncrementedId; + private readonly string _filePath; + private List _cache; + private bool _cacheAhead; + + public JsonRepository(string fileName) + { + string? xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + string dataHome; + if (!string.IsNullOrEmpty(xdgDataHome)) + { + dataHome = Path.Combine(xdgDataHome, _appName); + } + else + { + dataHome = Path.Combine( + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"), + _appName + ); + } + _filePath = Path.Combine(dataHome, fileName); + + ReadAllFromFile(); + + var lastItem = _cache.OrderBy(item => item.ID).LastOrDefault(); + _autoIncrementedId = lastItem != null ? lastItem.ID : 0; + } + + private void ReadAllFromFile() + { + var items = new List(); + + if (File.Exists(_filePath)) + { + try + { + string json = File.ReadAllText(_filePath); + items = JsonSerializer.Deserialize>(json) ?? new List(); + } + catch (JsonException e) + { + WriteToStdErr($"JSON error: {e.Message}"); + } + catch (IOException e) + { + WriteToStdErr($"File I/O error: {e.Message}"); + } + catch (Exception e) + { + WriteToStdErr($"Unexpected error: {e.Message}"); + } + } + + _cache = items; + _cacheAhead = false; + } + + public List GetAll() + { + return _cache; + } + + public PasswordStore? Get(uint id) + { + try + { + return _cache.First(item => item.ID.Equals(id)); + } + catch (InvalidOperationException) + { + // Not found + return null; + } + catch (Exception e) + { + WriteToStdErr($"Unexpected error: {e.Message}"); + return null; + } + } + + public void Dispose() + { + if (_cacheAhead) + { + try + { + string json = JsonSerializer.Serialize(_cache); + string? directory = Path.GetDirectoryName(_filePath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory!); + } + File.WriteAllText(_filePath, json); + } + catch (IOException e) + { + WriteToStdErr($"File I/O error: {e.Message}"); + } + catch (Exception e) + { + WriteToStdErr($"Unexpected error: {e.Message}"); + } + + ReadAllFromFile(); + } + } + + public void Edit(uint ID, PasswordStore newItem) + { + try + { + PasswordStore item = _cache.First(item => item.ID.Equals(ID)); + item.DisplayName = newItem.DisplayName; + item.IconName = newItem.IconName; + item.Path = newItem.Path; + _cacheAhead = true; + } + catch (InvalidOperationException) + { + WriteToStdErr($"Edit error: Item with ID {ID} not found."); + } + catch (Exception e) + { + WriteToStdErr($"Unexpected error: {e.Message}"); + } + } + + public void Create(PasswordStore item) + { + item.ID = ++_autoIncrementedId; + _cache.Add(item); + _cacheAhead = true; + } + + public void Delete(PasswordStore item) + { + Delete(item.ID); + } + + public void Delete(uint id) + { + try + { + var item = _cache.First(item => item.ID.Equals(id)); + _cache.Remove(item); + _cacheAhead = true; + } + catch (InvalidOperationException) + { + WriteToStdErr($"Delete error: Item with ID {id} not found."); + } + catch (Exception e) + { + WriteToStdErr($"Unexpected error: {e.Message}"); + } + } + + private void WriteToStdErr(string message) + { + using var sw = new StreamWriter(Console.OpenStandardError()); + sw.WriteLine(message); + } +} \ No newline at end of file diff --git a/Repository/Repository.cs b/Repository/Repository.cs deleted file mode 100644 index 7df8078..0000000 --- a/Repository/Repository.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Collections; -using System.Text.Json; - -namespace Repository; - -public class Repository : IRepository -{ - private const string _appName = "Keychain"; - private readonly string _filePath; - private List? _cache; - private bool _cacheDirty = true; - - public Repository(string fileName) - { - var xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); - string dataHome; - if (!string.IsNullOrEmpty(xdgDataHome)) - { - dataHome = Path.Combine(xdgDataHome, _appName); - } - else - { - dataHome = Path.Combine( - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"), - _appName - ); - } - _filePath = Path.Combine(dataHome, fileName); - } - - public IEnumerable ReadAll() - { - if (!_cacheDirty && _cache != null) - return _cache; - - if (!File.Exists(_filePath)) - { - _cache = new List(); - } - else - { - var json = File.ReadAllText(_filePath); - _cache = JsonSerializer.Deserialize>(json) ?? new List(); - } - _cacheDirty = false; - return _cache; - } - - public object Get(uint id) - { - - } - - public void WriteAll(IEnumerable items) - { - var json = JsonSerializer.Serialize(items); - var directory = Path.GetDirectoryName(_filePath); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory!); - } - File.WriteAllText(_filePath, json); - - _cache = (List)items; - _cacheDirty = false; - } -} \ No newline at end of file diff --git a/Repository/Repository.csproj b/Repository/Repository.csproj index fa71b7a..66d6eb9 100644 --- a/Repository/Repository.csproj +++ b/Repository/Repository.csproj @@ -6,4 +6,8 @@ enable + + + +