Skip to content

Commit

Permalink
Add simple backup restore window
Browse files Browse the repository at this point in the history
  • Loading branch information
Wufflez committed Mar 11, 2021
1 parent 025653b commit a4e3e3f
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 14 deletions.
31 changes: 24 additions & 7 deletions Loki/Backup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand All @@ -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();
}
}
}
34 changes: 34 additions & 0 deletions Loki/BackupFileInfo.cs
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
}
83 changes: 83 additions & 0 deletions Loki/Backups.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<Window x:Class="Loki.Backups"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:loki="clr-namespace:Loki"
mc:Ignorable="d"
WindowStyle="SingleBorderWindow"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Title="Restore from a backup" Height="520" Width="500"
Loaded="Backups_OnLoaded">

<Window.CommandBindings>
<CommandBinding Command="{x:Static loki:Commands.RestoreCharacter}" CanExecute="RestoreCanExecute" Executed="RestoreExecuted"></CommandBinding>
<CommandBinding Command="Close" Executed="CloseExecuted"></CommandBinding>
</Window.CommandBindings>
<Window.Resources>
<loki:BoolToVisibilityConverter x:Key="InvBoolToVis" False="Visible" True="Collapsed"/>
<Style TargetType="Button">
<Setter Property="Width" Value="112"/>
<Setter Property="Height" Value="28"/>
<Setter Property="Margin" Value="4"/>
</Style>
</Window.Resources>
<DockPanel>

<!-- Bit at the bottom with buttons on -->
<Border Background="{x:Static SystemColors.ControlDarkBrush}"
DockPanel.Dock="Bottom">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<CheckBox IsChecked="True" x:Name="BackupCheckbox" VerticalAlignment="Center"
ToolTip="Creates a new backup of the current character file before doing the restore operation.">
Create new backup before restore
</CheckBox>
<Button Command="{x:Static loki:Commands.RestoreCharacter}"
ToolTip="Restore from the selected backup">
Restore Selected
</Button>
<Button Command="Close" ToolTip="Cancel backup and return to main window">
Cancel
</Button>
</StackPanel>

</Border>

<!-- Warning for doing a naked backup -->
<Border DockPanel.Dock="Bottom" Background="DarkRed"
Visibility="{Binding ElementName=BackupCheckbox, Path=IsChecked, Converter={StaticResource InvBoolToVis}}">
<Label Foreground="WhiteSmoke" HorizontalAlignment="Center">Restoring will permanently overwrite current character data</Label>
</Border>
<!-- Main area above the buttons -->
<Grid>

<!-- List of Backups to select -->
<ListView x:Name="BackupFileList" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate DataType="loki:BackupFileInfo">
<DockPanel Margin="2">
<Border DockPanel.Dock="Right" Background="{x:Static SystemColors.ControlLightBrush}" CornerRadius="4" Padding="2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" UseLayoutRounding="True">
<Image Source="Resources/calander-16.ico" Margin="8,2"/>
<TextBlock Text="{Binding BackupDate}" Width="80" Margin="2"/>
<Image Source="Resources/clock-16.ico" Margin="8,2"/>
<TextBlock Text="{Binding BackupTimeOfDay}" Margin="2"/>
</StackPanel>

</Border>
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<!-- Loading Indicator -->
<Label
x:Name="StatusIndicator"
Foreground="#2B2B2B" VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="24">Loading</Label>

</Grid>
</DockPanel>
</Window>
89 changes: 89 additions & 0 deletions Loki/Backups.xaml.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Interaction logic for Backups.xaml
/// </summary>
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();
}
}
}
3 changes: 3 additions & 0 deletions Loki/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
4 changes: 4 additions & 0 deletions Loki/Loki.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
</PropertyGroup>

<ItemGroup>
<None Remove="Resources\calander-16.ico" />
<None Remove="Resources\clock-16.ico" />
<None Remove="Resources\loki.ico" />
</ItemGroup>

Expand All @@ -31,6 +33,8 @@
</ItemGroup>

<ItemGroup>
<Resource Include="Resources\calander-16.ico" />
<Resource Include="Resources\clock-16.ico" />
<Resource Include="Resources\loki.ico" />
</ItemGroup>

Expand Down
25 changes: 18 additions & 7 deletions Loki/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand All @@ -17,6 +17,7 @@
<Window.CommandBindings>
<CommandBinding Command="{x:Static loki:Commands.RevertCharacter}" CanExecute="CanSaveOrRevertExecute" Executed="RevertExecuted"/>
<CommandBinding Command="{x:Static loki:Commands.SaveCharacter}" CanExecute="CanSaveOrRevertExecute" Executed="SaveExecuted"/>
<CommandBinding Command="{x:Static loki:Commands.RestoreCharacter}" CanExecute="CanRestoreExecute" Executed="RestoreExecuted" />
</Window.CommandBindings>

<!-- Mother -->
Expand All @@ -32,14 +33,24 @@
<Border Margin="4" DockPanel.Dock="Bottom">
<DockPanel HorizontalAlignment="Stretch">

<Button DockPanel.Dock="Right" Width="96" Height="26" Margin="2,0"
Command="{x:Static loki:Commands.SaveCharacter}">Save</Button>
<Button DockPanel.Dock="Right" Width="96" Height="26" Margin="2,0,0,0"
Command="{x:Static loki:Commands.RevertCharacter}">Revert</Button>
<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" Margin="4,0" IsChecked="{Binding CreateBackup}">
Command="{x:Static loki:Commands.RevertCharacter}"
ToolTip="Re-loads the file, reverting any unsaved changes">Revert</Button>

<Button DockPanel.Dock="Right" Width="96" Height="26" Margin="2,0"
Command="{x:Static loki:Commands.SaveCharacter}"
ToolTip="Saves all changes to the character file">Save</Button>

<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" Margin="4,0"
ToolTip="If checked, will cause a backup to be automatically created upon saving"
IsChecked="{Binding CreateBackup}">
Backup Character
</CheckBox>

<Button DockPanel.Dock="Right" Height="26" Margin="2,0" Padding="32,0"
ToolTip="Select a backup to restore the character from"
Command="{x:Static loki:Commands.RestoreCharacter}">Backup Restore</Button>

<Label DockPanel.Dock="Left">Created by Wuffles</Label>
</DockPanel>
</Border>
Expand Down Expand Up @@ -144,7 +155,7 @@
<!-- Todo: Tidy up this mess lol. -->
<DockPanel>


<!-- Item Picker -->
<ScrollViewer DockPanel.Dock="Left" Width="256" Margin="4">
<ItemsControl DockPanel.Dock="Left">
Expand Down Expand Up @@ -238,7 +249,7 @@
</UniformGrid>
</loki:AspectRatioBox>
</DockPanel>


</TabItem>
</TabControl>
Expand Down
18 changes: 18 additions & 0 deletions Loki/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Binary file added Loki/Resources/calander-16.ico
Binary file not shown.
Binary file added Loki/Resources/clock-16.ico
Binary file not shown.

0 comments on commit a4e3e3f

Please sign in to comment.