Skip to content

Commit a4e3e3f

Browse files
committed
Add simple backup restore window
1 parent 025653b commit a4e3e3f

File tree

10 files changed

+273
-14
lines changed

10 files changed

+273
-14
lines changed

Loki/Backup.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ public static class Backup
99
{
1010
public static void BackupCharacter(CharacterFile source)
1111
{
12-
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
13-
string backupDirPath = Path.Join(localAppData, "TwoThreeSix", "Loki", "CharacterBackup");
14-
Directory.CreateDirectory(backupDirPath);
15-
12+
CharacterBackupDir.Create();
1613
int latestBackupNumber =
17-
Directory.EnumerateFiles(backupDirPath)
18-
.Select(Path.GetFileName)
14+
CharacterBackupDir.EnumerateFiles()
15+
.Select(f=>f.Name)
1916
.Select(fileName => Regex.Match(fileName, @"^.*-backup-(\d+)\.fch$"))
2017
.Where(match => match.Success)
2118
.Select(match =>
@@ -25,9 +22,29 @@ public static void BackupCharacter(CharacterFile source)
2522
string charFilename = Path.GetFileNameWithoutExtension(source.FilePath);
2623
string backupFilename = $"{charFilename}-backup-{latestBackupNumber + 1}.fch";
2724

28-
string backupFilePath = Path.Join(backupDirPath, backupFilename);
25+
string backupFilePath = Path.Join(CharacterBackupDir.FullName, backupFilename);
2926

3027
File.Copy(source.FilePath, backupFilePath);
3128
}
29+
30+
public static DirectoryInfo CharacterBackupDir { get; } = GetBackupDir();
31+
32+
private static DirectoryInfo GetBackupDir()
33+
{
34+
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
35+
string backupDirPath = Path.Join(localAppData, "TwoThreeSix", "Loki", "CharacterBackup");
36+
return new DirectoryInfo(backupDirPath);
37+
}
38+
39+
public static FileInfo[] GetBackupsFor(CharacterFile file)
40+
{
41+
var characterName = Path.GetFileNameWithoutExtension(file.FilePath);
42+
43+
return CharacterBackupDir.EnumerateFiles("*.fch")
44+
.Select(f => (file: f, regex: Regex.Match(f.Name, @"^(.*)-backup-\d+\.fch")))
45+
.Where(pair => pair.regex.Success && pair.regex.Groups[1].Value == characterName)
46+
.Select(pair => pair.file)
47+
.ToArray();
48+
}
3249
}
3350
}

Loki/BackupFileInfo.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.IO;
3+
using JetBrains.Annotations;
4+
5+
namespace Loki
6+
{
7+
public class BackupFileInfo
8+
{
9+
public string BackupDate { get; }
10+
public string BackupTimeOfDay { get; }
11+
public FileInfo File { get; }
12+
public string Name { get; }
13+
14+
public BackupFileInfo([NotNull] FileInfo backupFile)
15+
{
16+
File = backupFile ?? throw new ArgumentNullException(nameof(backupFile));
17+
Name = backupFile.Name;
18+
BackupDate = "Sometime";
19+
BackupTimeOfDay = File.CreationTime.ToString("HH:mm");
20+
if (File.CreationTime.Date == DateTime.Now.Date)
21+
{
22+
BackupDate = "Today";
23+
}
24+
else if((DateTime.Now.Date - File.CreationTime.Date).Days <=7)
25+
{
26+
BackupDate = File.CreationTime.Date.ToString("dddd");
27+
}
28+
else
29+
{
30+
BackupDate = File.CreationTime.Date.ToString("d");
31+
}
32+
}
33+
}
34+
}

Loki/Backups.xaml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<Window x:Class="Loki.Backups"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:loki="clr-namespace:Loki"
7+
mc:Ignorable="d"
8+
WindowStyle="SingleBorderWindow"
9+
ResizeMode="NoResize"
10+
WindowStartupLocation="CenterOwner"
11+
Title="Restore from a backup" Height="520" Width="500"
12+
Loaded="Backups_OnLoaded">
13+
14+
<Window.CommandBindings>
15+
<CommandBinding Command="{x:Static loki:Commands.RestoreCharacter}" CanExecute="RestoreCanExecute" Executed="RestoreExecuted"></CommandBinding>
16+
<CommandBinding Command="Close" Executed="CloseExecuted"></CommandBinding>
17+
</Window.CommandBindings>
18+
<Window.Resources>
19+
<loki:BoolToVisibilityConverter x:Key="InvBoolToVis" False="Visible" True="Collapsed"/>
20+
<Style TargetType="Button">
21+
<Setter Property="Width" Value="112"/>
22+
<Setter Property="Height" Value="28"/>
23+
<Setter Property="Margin" Value="4"/>
24+
</Style>
25+
</Window.Resources>
26+
<DockPanel>
27+
28+
<!-- Bit at the bottom with buttons on -->
29+
<Border Background="{x:Static SystemColors.ControlDarkBrush}"
30+
DockPanel.Dock="Bottom">
31+
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
32+
<CheckBox IsChecked="True" x:Name="BackupCheckbox" VerticalAlignment="Center"
33+
ToolTip="Creates a new backup of the current character file before doing the restore operation.">
34+
Create new backup before restore
35+
</CheckBox>
36+
<Button Command="{x:Static loki:Commands.RestoreCharacter}"
37+
ToolTip="Restore from the selected backup">
38+
Restore Selected
39+
</Button>
40+
<Button Command="Close" ToolTip="Cancel backup and return to main window">
41+
Cancel
42+
</Button>
43+
</StackPanel>
44+
45+
</Border>
46+
47+
<!-- Warning for doing a naked backup -->
48+
<Border DockPanel.Dock="Bottom" Background="DarkRed"
49+
Visibility="{Binding ElementName=BackupCheckbox, Path=IsChecked, Converter={StaticResource InvBoolToVis}}">
50+
<Label Foreground="WhiteSmoke" HorizontalAlignment="Center">Restoring will permanently overwrite current character data</Label>
51+
</Border>
52+
<!-- Main area above the buttons -->
53+
<Grid>
54+
55+
<!-- List of Backups to select -->
56+
<ListView x:Name="BackupFileList" HorizontalContentAlignment="Stretch">
57+
<ListView.ItemTemplate>
58+
<DataTemplate DataType="loki:BackupFileInfo">
59+
<DockPanel Margin="2">
60+
<Border DockPanel.Dock="Right" Background="{x:Static SystemColors.ControlLightBrush}" CornerRadius="4" Padding="2">
61+
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" UseLayoutRounding="True">
62+
<Image Source="Resources/calander-16.ico" Margin="8,2"/>
63+
<TextBlock Text="{Binding BackupDate}" Width="80" Margin="2"/>
64+
<Image Source="Resources/clock-16.ico" Margin="8,2"/>
65+
<TextBlock Text="{Binding BackupTimeOfDay}" Margin="2"/>
66+
</StackPanel>
67+
68+
</Border>
69+
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
70+
</DockPanel>
71+
</DataTemplate>
72+
</ListView.ItemTemplate>
73+
</ListView>
74+
75+
<!-- Loading Indicator -->
76+
<Label
77+
x:Name="StatusIndicator"
78+
Foreground="#2B2B2B" VerticalAlignment="Center" HorizontalAlignment="Center"
79+
FontSize="24">Loading</Label>
80+
81+
</Grid>
82+
</DockPanel>
83+
</Window>

Loki/Backups.xaml.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using System.Windows;
7+
using System.Windows.Input;
8+
using System.Windows.Media;
9+
using JetBrains.Annotations;
10+
11+
namespace Loki
12+
{
13+
/// <summary>
14+
/// Interaction logic for Backups.xaml
15+
/// </summary>
16+
public partial class Backups
17+
{
18+
private readonly CharacterFile _currentFile;
19+
20+
public Backups([NotNull] CharacterFile currentFile)
21+
{
22+
_currentFile = currentFile ?? throw new ArgumentNullException(nameof(currentFile));
23+
InitializeComponent();
24+
}
25+
26+
private async void Backups_OnLoaded(object sender, RoutedEventArgs e)
27+
{
28+
try
29+
{
30+
Debug.Assert(_currentFile != null);
31+
32+
var backupFiles = await Task.Run(() => Backup.GetBackupsFor(_currentFile));
33+
34+
BackupFileList.ItemsSource = backupFiles.OrderByDescending(file => file.CreationTime)
35+
.Select(file => new BackupFileInfo(file)).ToArray();
36+
;
37+
if (backupFiles.Length > 0)
38+
{
39+
StatusIndicator.Visibility = Visibility.Hidden;
40+
}
41+
else
42+
{
43+
StatusIndicator.Content = "No backups";
44+
}
45+
}
46+
catch (Exception)
47+
{
48+
// TODO: Log exception
49+
StatusIndicator.Foreground = Brushes.Firebrick;
50+
StatusIndicator.Content = "Failed to load backups";
51+
}
52+
}
53+
54+
private void RestoreCanExecute(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = BackupFileList?.SelectedItem != null;
55+
56+
private void RestoreExecuted(object sender, ExecutedRoutedEventArgs e)
57+
{
58+
try
59+
{
60+
61+
if (!(BackupFileList.SelectedItem is BackupFileInfo backupInfo))
62+
throw new InvalidOperationException("Cannot perform backup without selected backup info");
63+
64+
// Create a backup first unless the user opted not to do so.
65+
if (BackupCheckbox.IsChecked == true)
66+
Backup.BackupCharacter(_currentFile);
67+
68+
backupInfo.File.CopyTo(_currentFile.FilePath, true);
69+
70+
MessageBox.Show("Character restored successfully!", "Restore complete", MessageBoxButton.OK,
71+
MessageBoxImage.Information);
72+
DialogResult = true;
73+
}
74+
catch (Exception ex)
75+
{
76+
// TODO: Log errors from restoring.
77+
MessageBox.Show("Failed to restore backup!", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
78+
}
79+
80+
Close();
81+
}
82+
83+
private void CloseExecuted(object sender, ExecutedRoutedEventArgs e)
84+
{
85+
DialogResult = false;
86+
Close();
87+
}
88+
}
89+
}

Loki/Commands.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,8 @@ public static class Commands
99

1010
public static RoutedUICommand SaveCharacter =
1111
new RoutedUICommand("Save", nameof(RevertCharacter), typeof(Commands));
12+
13+
public static RoutedUICommand RestoreCharacter =
14+
new RoutedUICommand("Restore", nameof(RestoreCharacter), typeof(Commands));
1215
}
1316
}

Loki/Loki.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
</PropertyGroup>
1717

1818
<ItemGroup>
19+
<None Remove="Resources\calander-16.ico" />
20+
<None Remove="Resources\clock-16.ico" />
1921
<None Remove="Resources\loki.ico" />
2022
</ItemGroup>
2123

@@ -31,6 +33,8 @@
3133
</ItemGroup>
3234

3335
<ItemGroup>
36+
<Resource Include="Resources\calander-16.ico" />
37+
<Resource Include="Resources\clock-16.ico" />
3438
<Resource Include="Resources\loki.ico" />
3539
</ItemGroup>
3640

Loki/MainWindow.xaml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
88
mc:Ignorable="d"
99
WindowStartupLocation="CenterScreen"
10-
MinHeight="280" MinWidth="260"
10+
MinHeight="290" MinWidth="540"
1111
Height="593" Width="1165"
1212
Title="Loki"
1313
DataContext="{Binding RelativeSource={RelativeSource Self}}"
@@ -17,6 +17,7 @@
1717
<Window.CommandBindings>
1818
<CommandBinding Command="{x:Static loki:Commands.RevertCharacter}" CanExecute="CanSaveOrRevertExecute" Executed="RevertExecuted"/>
1919
<CommandBinding Command="{x:Static loki:Commands.SaveCharacter}" CanExecute="CanSaveOrRevertExecute" Executed="SaveExecuted"/>
20+
<CommandBinding Command="{x:Static loki:Commands.RestoreCharacter}" CanExecute="CanRestoreExecute" Executed="RestoreExecuted" />
2021
</Window.CommandBindings>
2122

2223
<!-- Mother -->
@@ -32,14 +33,24 @@
3233
<Border Margin="4" DockPanel.Dock="Bottom">
3334
<DockPanel HorizontalAlignment="Stretch">
3435

35-
<Button DockPanel.Dock="Right" Width="96" Height="26" Margin="2,0"
36-
Command="{x:Static loki:Commands.SaveCharacter}">Save</Button>
3736
<Button DockPanel.Dock="Right" Width="96" Height="26" Margin="2,0,0,0"
38-
Command="{x:Static loki:Commands.RevertCharacter}">Revert</Button>
39-
<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" Margin="4,0" IsChecked="{Binding CreateBackup}">
37+
Command="{x:Static loki:Commands.RevertCharacter}"
38+
ToolTip="Re-loads the file, reverting any unsaved changes">Revert</Button>
39+
40+
<Button DockPanel.Dock="Right" Width="96" Height="26" Margin="2,0"
41+
Command="{x:Static loki:Commands.SaveCharacter}"
42+
ToolTip="Saves all changes to the character file">Save</Button>
43+
44+
<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" Margin="4,0"
45+
ToolTip="If checked, will cause a backup to be automatically created upon saving"
46+
IsChecked="{Binding CreateBackup}">
4047
Backup Character
4148
</CheckBox>
4249

50+
<Button DockPanel.Dock="Right" Height="26" Margin="2,0" Padding="32,0"
51+
ToolTip="Select a backup to restore the character from"
52+
Command="{x:Static loki:Commands.RestoreCharacter}">Backup Restore</Button>
53+
4354
<Label DockPanel.Dock="Left">Created by Wuffles</Label>
4455
</DockPanel>
4556
</Border>
@@ -144,7 +155,7 @@
144155
<!-- Todo: Tidy up this mess lol. -->
145156
<DockPanel>
146157

147-
158+
148159
<!-- Item Picker -->
149160
<ScrollViewer DockPanel.Dock="Left" Width="256" Margin="4">
150161
<ItemsControl DockPanel.Dock="Left">
@@ -238,7 +249,7 @@
238249
</UniformGrid>
239250
</loki:AspectRatioBox>
240251
</DockPanel>
241-
252+
242253

243254
</TabItem>
244255
</TabControl>

Loki/MainWindow.xaml.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,5 +177,23 @@ private void ItemPickerItemMouseMove(object sender, MouseEventArgs e)
177177
}
178178
}
179179
}
180+
181+
private void CanRestoreExecute(object sender, CanExecuteRoutedEventArgs e)
182+
{
183+
e.CanExecute = SelectedCharacterFile != null;
184+
}
185+
186+
private void RestoreExecuted(object sender, ExecutedRoutedEventArgs e)
187+
{
188+
// Select backup to restore from.
189+
var restoreWindow = new Backups(SelectedCharacterFile) {Owner = this};
190+
restoreWindow.ShowDialog();
191+
192+
if (restoreWindow.DialogResult == true)
193+
{
194+
// Reload profile, as user has restored it from another file.
195+
LoadProfile(SelectedCharacterFile);
196+
}
197+
}
180198
}
181199
}

Loki/Resources/calander-16.ico

1.12 KB
Binary file not shown.

Loki/Resources/clock-16.ico

1.12 KB
Binary file not shown.

0 commit comments

Comments
 (0)