From e22b32cfc39519e95ba60a1993263ac0fa86c912 Mon Sep 17 00:00:00 2001 From: Nate Bross Date: Fri, 29 Sep 2023 10:03:11 -0500 Subject: [PATCH] feat(storage): added a SQLite persistence layer for saving clips between runs. (#5) --- SharpFM.App/MainWindow.axaml | 11 ++- SharpFM.App/Models/Clip.cs | 27 +++++++ SharpFM.App/Models/ClipDbContext.cs | 36 +++++++++ SharpFM.App/SharpFM.App.csproj | 1 + SharpFM.App/ViewModels/ClipViewModel.cs | 16 +++- SharpFM.App/ViewModels/MainWindowViewModel.cs | 77 +++++++++++++++++-- SharpFM.Core/FileMakerClip.cs | 18 +++++ 7 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 SharpFM.App/Models/Clip.cs create mode 100644 SharpFM.App/Models/ClipDbContext.cs diff --git a/SharpFM.App/MainWindow.axaml b/SharpFM.App/MainWindow.axaml index 135cbbd..5bf3d80 100644 --- a/SharpFM.App/MainWindow.axaml +++ b/SharpFM.App/MainWindow.axaml @@ -21,15 +21,20 @@ + + - - + + + + + @@ -41,7 +46,7 @@ diff --git a/SharpFM.App/Models/Clip.cs b/SharpFM.App/Models/Clip.cs new file mode 100644 index 0000000..8be4ede --- /dev/null +++ b/SharpFM.App/Models/Clip.cs @@ -0,0 +1,27 @@ +namespace SharpFM.App.Models; + +/// +/// Clip Data Model +/// +public class Clip +{ + /// + /// Database Id + /// + public int ClipId { get; set; } + + /// + /// Display name for clip may match Name inside the xml data or may not. + /// + public string ClipName { get; set; } = string.Empty; + + /// + /// The data format to use when putting the data back on the clipboard for FileMaker. + /// + public string ClipType { get; set; } = string.Empty; + + /// + /// Raw xml data from the clip. + /// + public string ClipXml { get; set; } = string.Empty; +} diff --git a/SharpFM.App/Models/ClipDbContext.cs b/SharpFM.App/Models/ClipDbContext.cs new file mode 100644 index 0000000..592713e --- /dev/null +++ b/SharpFM.App/Models/ClipDbContext.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using System; + + +namespace SharpFM.App.Models; + +/// +/// Clip Db Context +/// +public class ClipDbContext : DbContext +{ + /// + /// Clips stored in the Db. + /// + public DbSet Clips => Set(); + + /// + /// Database path. + /// + public string DbPath { get; } + + /// + /// Constructor. + /// + public ClipDbContext() + { + var folder = Environment.SpecialFolder.LocalApplicationData; + var path = Environment.GetFolderPath(folder); + DbPath = System.IO.Path.Join(path, "sharpFM.db"); + } + + // The following configures EF to create a Sqlite database file in the + // special "local" folder for your platform. + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseSqlite($"Data Source={DbPath}"); +} diff --git a/SharpFM.App/SharpFM.App.csproj b/SharpFM.App/SharpFM.App.csproj index 5718d19..6a06a36 100644 --- a/SharpFM.App/SharpFM.App.csproj +++ b/SharpFM.App/SharpFM.App.csproj @@ -33,6 +33,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/SharpFM.App/ViewModels/ClipViewModel.cs b/SharpFM.App/ViewModels/ClipViewModel.cs index c1897f6..7098d7d 100644 --- a/SharpFM.App/ViewModels/ClipViewModel.cs +++ b/SharpFM.App/ViewModels/ClipViewModel.cs @@ -15,9 +15,23 @@ private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") public FileMakerClip Clip { get; set; } - public ClipViewModel(FileMakerClip clip) + public ClipViewModel(FileMakerClip clip) : this(clip, null) { } + + public ClipViewModel(FileMakerClip clip, int? clipId) { Clip = clip; + ClipId = clipId; + } + + private int? _clipId; + public int? ClipId + { + get => _clipId; + set + { + _clipId = value; + NotifyPropertyChanged(); + } } public string ClipType diff --git a/SharpFM.App/ViewModels/MainWindowViewModel.cs b/SharpFM.App/ViewModels/MainWindowViewModel.cs index 80556a8..1e9f6b4 100644 --- a/SharpFM.App/ViewModels/MainWindowViewModel.cs +++ b/SharpFM.App/ViewModels/MainWindowViewModel.cs @@ -8,12 +8,15 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using FluentAvalonia.UI.Data; +using SharpFM.App.Models; using SharpFM.Core; namespace SharpFM.App.ViewModels; public partial class MainWindowViewModel : INotifyPropertyChanged { + public ClipDbContext _context; + public event PropertyChangedEventHandler? PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") @@ -23,7 +26,64 @@ private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") public MainWindowViewModel() { - Keys = new ObservableCollection(); + _context = new ClipDbContext(); + _context.Database.EnsureCreated(); + + Console.WriteLine($"Database path: {_context.DbPath}."); + + FileMakerClips = new ObservableCollection(); + + foreach (var clip in _context.Clips) + { + FileMakerClips.Add(new ClipViewModel( + new FileMakerClip( + clip.ClipName, + clip.ClipType, + clip.ClipXml + ), + clip.ClipId + ) + ); + } + } + + public void SaveToDb() + { + var dbClips = _context.Clips.ToList(); + + foreach (var clip in FileMakerClips) + { + var dbClip = dbClips.FirstOrDefault(dbc => dbc.ClipName == clip.Name); + + if (dbClip is not null) + { + dbClip.ClipType = clip.ClipType; + dbClip.ClipXml = clip.ClipXml; + } + else + { + _context.Clips.Add(new Clip() + { + ClipName = clip.Name, + ClipType = clip.ClipType, + ClipXml = clip.ClipXml + }); + } + } + + _context.SaveChanges(); + } + + public void ClearDb() + { + var clips = _context.Clips.ToList(); + + foreach (var clip in clips) + { + _context.Clips.Remove(clip); + } + + _context.SaveChanges(); } public void ExitApplication() @@ -41,7 +101,7 @@ public void NewEmptyItem() var clip = new FileMakerClip("New", FileMakerClip.ClipTypes.First()?.KeyId ?? "", Array.Empty()); var clipVm = new ClipViewModel(clip); - Keys.Add(clipVm); + FileMakerClips.Add(clipVm); } catch (Exception e) { @@ -73,7 +133,7 @@ public void CopyAsClass() } } - public async Task PasteFileMakerClipData(CancellationToken token) + public async Task PasteFileMakerClipData() { try { @@ -102,12 +162,12 @@ public async Task PasteFileMakerClipData(CancellationToken token) // don't bother adding a duplicate. For some reason entries were getting entered twice per clip // this is not the most efficient method to detect it, but it works well enough for now - if (Keys.Any(k => k.ClipXml == clip.XmlData)) + if (FileMakerClips.Any(k => k.ClipXml == clip.XmlData)) { continue; } - Keys.Add(new ClipViewModel(clip)); + FileMakerClips.Add(new ClipViewModel(clip)); } } catch (Exception e) @@ -115,7 +175,7 @@ public async Task PasteFileMakerClipData(CancellationToken token) } } - public async Task CopySelectedToClip(CancellationToken token) + public async Task CopySelectedToClip() { try { @@ -140,6 +200,9 @@ public async Task CopySelectedToClip(CancellationToken token) } } + /// + /// SharpFM Version. + /// public string Version { get @@ -150,7 +213,7 @@ public string Version } } - public ObservableCollection Keys { get; set; } + public ObservableCollection FileMakerClips { get; set; } private ClipViewModel? _selectedClip; public ClipViewModel? SelectedClip diff --git a/SharpFM.Core/FileMakerClip.cs b/SharpFM.Core/FileMakerClip.cs index 8685222..fe39877 100644 --- a/SharpFM.Core/FileMakerClip.cs +++ b/SharpFM.Core/FileMakerClip.cs @@ -27,6 +27,24 @@ public class ClipFormat new ClipFormat() { KeyId = "Mac-XMSC", DisplayName = "Script" } }; + public FileMakerClip(string name, string format, string xml) + { + // grab the input clip name + Name = name; + + // load the format + ClipboardFormat = format; + + try + { + XmlData = PrettyXml(xml); + } + catch + { + XmlData = xml; + } + } + /// /// Constructor taking in the raw data byte array. ///