From a4e3e3f8c743606bdce832edf50682260fa8e9e1 Mon Sep 17 00:00:00 2001 From: Adam Rhodes Date: Thu, 11 Mar 2021 17:59:19 +0000 Subject: [PATCH] Add simple backup restore window --- Loki/Backup.cs | 31 +++++++++--- Loki/BackupFileInfo.cs | 34 +++++++++++++ Loki/Backups.xaml | 83 ++++++++++++++++++++++++++++++ Loki/Backups.xaml.cs | 89 +++++++++++++++++++++++++++++++++ Loki/Commands.cs | 3 ++ Loki/Loki.csproj | 4 ++ Loki/MainWindow.xaml | 25 ++++++--- Loki/MainWindow.xaml.cs | 18 +++++++ Loki/Resources/calander-16.ico | Bin 0 -> 1150 bytes Loki/Resources/clock-16.ico | Bin 0 -> 1150 bytes 10 files changed, 273 insertions(+), 14 deletions(-) create mode 100644 Loki/BackupFileInfo.cs create mode 100644 Loki/Backups.xaml create mode 100644 Loki/Backups.xaml.cs create mode 100644 Loki/Resources/calander-16.ico create mode 100644 Loki/Resources/clock-16.ico diff --git a/Loki/Backup.cs b/Loki/Backup.cs index 492327e..8c4c666 100644 --- a/Loki/Backup.cs +++ b/Loki/Backup.cs @@ -9,13 +9,10 @@ public static class Backup { public static void BackupCharacter(CharacterFile source) { - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string backupDirPath = Path.Join(localAppData, "TwoThreeSix", "Loki", "CharacterBackup"); - Directory.CreateDirectory(backupDirPath); - + CharacterBackupDir.Create(); int latestBackupNumber = - Directory.EnumerateFiles(backupDirPath) - .Select(Path.GetFileName) + CharacterBackupDir.EnumerateFiles() + .Select(f=>f.Name) .Select(fileName => Regex.Match(fileName, @"^.*-backup-(\d+)\.fch$")) .Where(match => match.Success) .Select(match => @@ -25,9 +22,29 @@ public static void BackupCharacter(CharacterFile source) string charFilename = Path.GetFileNameWithoutExtension(source.FilePath); string backupFilename = $"{charFilename}-backup-{latestBackupNumber + 1}.fch"; - string backupFilePath = Path.Join(backupDirPath, backupFilename); + string backupFilePath = Path.Join(CharacterBackupDir.FullName, backupFilename); File.Copy(source.FilePath, backupFilePath); } + + public static DirectoryInfo CharacterBackupDir { get; } = GetBackupDir(); + + private static DirectoryInfo GetBackupDir() + { + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string backupDirPath = Path.Join(localAppData, "TwoThreeSix", "Loki", "CharacterBackup"); + return new DirectoryInfo(backupDirPath); + } + + public static FileInfo[] GetBackupsFor(CharacterFile file) + { + var characterName = Path.GetFileNameWithoutExtension(file.FilePath); + + return CharacterBackupDir.EnumerateFiles("*.fch") + .Select(f => (file: f, regex: Regex.Match(f.Name, @"^(.*)-backup-\d+\.fch"))) + .Where(pair => pair.regex.Success && pair.regex.Groups[1].Value == characterName) + .Select(pair => pair.file) + .ToArray(); + } } } diff --git a/Loki/BackupFileInfo.cs b/Loki/BackupFileInfo.cs new file mode 100644 index 0000000..1f6310a --- /dev/null +++ b/Loki/BackupFileInfo.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using JetBrains.Annotations; + +namespace Loki +{ + public class BackupFileInfo + { + public string BackupDate { get; } + public string BackupTimeOfDay { get; } + public FileInfo File { get; } + public string Name { get; } + + public BackupFileInfo([NotNull] FileInfo backupFile) + { + File = backupFile ?? throw new ArgumentNullException(nameof(backupFile)); + Name = backupFile.Name; + BackupDate = "Sometime"; + BackupTimeOfDay = File.CreationTime.ToString("HH:mm"); + if (File.CreationTime.Date == DateTime.Now.Date) + { + BackupDate = "Today"; + } + else if((DateTime.Now.Date - File.CreationTime.Date).Days <=7) + { + BackupDate = File.CreationTime.Date.ToString("dddd"); + } + else + { + BackupDate = File.CreationTime.Date.ToString("d"); + } + } + } +} diff --git a/Loki/Backups.xaml b/Loki/Backups.xaml new file mode 100644 index 0000000..41ce5c7 --- /dev/null +++ b/Loki/Backups.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + Create new backup before restore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Loki/Backups.xaml.cs b/Loki/Backups.xaml.cs new file mode 100644 index 0000000..9cc1408 --- /dev/null +++ b/Loki/Backups.xaml.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using JetBrains.Annotations; + +namespace Loki +{ + /// + /// Interaction logic for Backups.xaml + /// + public partial class Backups + { + private readonly CharacterFile _currentFile; + + public Backups([NotNull] CharacterFile currentFile) + { + _currentFile = currentFile ?? throw new ArgumentNullException(nameof(currentFile)); + InitializeComponent(); + } + + private async void Backups_OnLoaded(object sender, RoutedEventArgs e) + { + try + { + Debug.Assert(_currentFile != null); + + var backupFiles = await Task.Run(() => Backup.GetBackupsFor(_currentFile)); + + BackupFileList.ItemsSource = backupFiles.OrderByDescending(file => file.CreationTime) + .Select(file => new BackupFileInfo(file)).ToArray(); +; + if (backupFiles.Length > 0) + { + StatusIndicator.Visibility = Visibility.Hidden; + } + else + { + StatusIndicator.Content = "No backups"; + } + } + catch (Exception) + { + // TODO: Log exception + StatusIndicator.Foreground = Brushes.Firebrick; + StatusIndicator.Content = "Failed to load backups"; + } + } + + private void RestoreCanExecute(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = BackupFileList?.SelectedItem != null; + + private void RestoreExecuted(object sender, ExecutedRoutedEventArgs e) + { + try + { + + if (!(BackupFileList.SelectedItem is BackupFileInfo backupInfo)) + throw new InvalidOperationException("Cannot perform backup without selected backup info"); + + // Create a backup first unless the user opted not to do so. + if (BackupCheckbox.IsChecked == true) + Backup.BackupCharacter(_currentFile); + + backupInfo.File.CopyTo(_currentFile.FilePath, true); + + MessageBox.Show("Character restored successfully!", "Restore complete", MessageBoxButton.OK, + MessageBoxImage.Information); + DialogResult = true; + } + catch (Exception ex) + { + // TODO: Log errors from restoring. + MessageBox.Show("Failed to restore backup!", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + } + + Close(); + } + + private void CloseExecuted(object sender, ExecutedRoutedEventArgs e) + { + DialogResult = false; + Close(); + } + } +} diff --git a/Loki/Commands.cs b/Loki/Commands.cs index 2d87761..062b1ea 100644 --- a/Loki/Commands.cs +++ b/Loki/Commands.cs @@ -9,5 +9,8 @@ public static class Commands public static RoutedUICommand SaveCharacter = new RoutedUICommand("Save", nameof(RevertCharacter), typeof(Commands)); + + public static RoutedUICommand RestoreCharacter = + new RoutedUICommand("Restore", nameof(RestoreCharacter), typeof(Commands)); } } diff --git a/Loki/Loki.csproj b/Loki/Loki.csproj index fe346f8..81934d0 100644 --- a/Loki/Loki.csproj +++ b/Loki/Loki.csproj @@ -16,6 +16,8 @@ + + @@ -31,6 +33,8 @@ + + diff --git a/Loki/MainWindow.xaml b/Loki/MainWindow.xaml index 61e8413..4a70599 100644 --- a/Loki/MainWindow.xaml +++ b/Loki/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" mc:Ignorable="d" WindowStartupLocation="CenterScreen" - MinHeight="280" MinWidth="260" + MinHeight="290" MinWidth="540" Height="593" Width="1165" Title="Loki" DataContext="{Binding RelativeSource={RelativeSource Self}}" @@ -17,6 +17,7 @@ + @@ -32,14 +33,24 @@ - - + Command="{x:Static loki:Commands.RevertCharacter}" + ToolTip="Re-loads the file, reverting any unsaved changes">Revert + + + + Backup Character + + @@ -144,7 +155,7 @@ - + @@ -238,7 +249,7 @@ - + diff --git a/Loki/MainWindow.xaml.cs b/Loki/MainWindow.xaml.cs index afc49c5..25062dc 100644 --- a/Loki/MainWindow.xaml.cs +++ b/Loki/MainWindow.xaml.cs @@ -177,5 +177,23 @@ private void ItemPickerItemMouseMove(object sender, MouseEventArgs e) } } } + + private void CanRestoreExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = SelectedCharacterFile != null; + } + + private void RestoreExecuted(object sender, ExecutedRoutedEventArgs e) + { + // Select backup to restore from. + var restoreWindow = new Backups(SelectedCharacterFile) {Owner = this}; + restoreWindow.ShowDialog(); + + if (restoreWindow.DialogResult == true) + { + // Reload profile, as user has restored it from another file. + LoadProfile(SelectedCharacterFile); + } + } } } diff --git a/Loki/Resources/calander-16.ico b/Loki/Resources/calander-16.ico new file mode 100644 index 0000000000000000000000000000000000000000..f4c3aeca87e5921ca15f017e3cd99a5f5b5a94fb GIT binary patch literal 1150 zcmbV~e@s(X6vuB{8tWp7nMM;ttoS1nAw(=H^D#@S5dk{`rBO6M6^E9%bOl%nI`__efhj|?)RK? z-+SjgLL~HByqM6tk{pX5WEmmk8w!=EGOtrZ)HL>)rPS;7?uv>ESS%Klm6d&Pxm*L4 zm6iXNmzTq8wSGcrX(?E)!C-LHvy>8vB(0~XX9jfn{eF18UeG#gU{!K$K&Ca z<*-HfGjh2+jh>-&1MesnZ{x)Cm0N(tE)?7wG5}z zDUQAw!NtaUes;Ie>!Il#@Q0xTa4>h9b z36TDahm-#pI9yiCpMUvs6NcyCe}>ko!}V{+15gTiP@lYwWpSz4VQIsL%pW=5nr~uL z@;3ZV`=HOs#@I*z-QC@veE-{tAQBAK$o*pmYj^#C+$&=cPW;BnX&FO|rWkv#|Alq> z8idAzY(^rT9q%9O|Kyw3SgWtYo@?*0Ty4T`dkBf9HV*Td#$qgIV&pa4Za1%IXRukY zkFz@*XtG~L8|E1whI zzGvv~cT!v!7;tgRe82BG{`otEz|aeLyif7+m6QG-b}=Gie*UV21~F6f5Ico?*hSF_ zw~&@{6Y7+n*F{SGo{Zp!8c-aiRZXb$KG#h;D>TjHybnp_7hJ ztfv1l6#yz47dBU;oa#oyph%<&jg$~orsn>*K{tq;?^Ypq_eSi`_QO;-jpGGVoD$l)%`{NQRQPKG?P<$1wlSQnqEzDRwm9L8rNo7$rk&9x@3dK@R zWMr(FNLJM3X=6e~If+vesj_y_!e||4+x OTltax#coFjA^!nl{5sbF literal 0 HcmV?d00001 diff --git a/Loki/Resources/clock-16.ico b/Loki/Resources/clock-16.ico new file mode 100644 index 0000000000000000000000000000000000000000..3323fb8adcac4280272e60bf9c34871474f14cae GIT binary patch literal 1150 zcmchWUuaTs6vuy3(8!=YvW{1wf7pIncX*zJdjt?2eMQv*I4&-wM-~phtv@A`5 zKC`(z%zNFE5C2JZ*WYJxFuN3(Gsey5Sh~CZ;0i^U9lih`d)s4|S9nNAxvOc>vjZJm z`t5`iw?@q`K_)&83BM08k5n`Sb9N2qZdK|k97a#KTJra;LULvXO20~Fxd=Cd5jfk^ z2VRpA=A)A!h@!*;nPOe4dfRae8ypmp@%SJ8vIv&pNpL+0Q4mGRC(nx#Q>=^6sH*Bc z$J+I5z{RDfBOsQe2gU$Cyf43$7i5~5dJS0Hkwbf{`8!k!MfQO+mVSC03X3^N=K!9+ zs@38fvPH(o+QBf`Pn98i(QUL9gZ4f}^O#kycz?2j<*u}eGU^?Q)|z&x@KoiXIWjtvU= zdscxR&~cbE_E^`}26-jvRaP1DK&FA7PIKUl#_Z{6(L0ze|NMVe@wcWKfROxVLcVSw TB)pE0tCY%G6Zha=)S&h^nt0p< literal 0 HcmV?d00001