Removed database status in favor of much cleaner code

Implemented (but deactivated) anti corruption mechanism
WIP detect changes and save them if opening another database
This commit is contained in:
BONNEVILLE Geoffroy
2018-01-08 18:52:03 +01:00
parent 4a3f36d38b
commit a19519fa73
17 changed files with 220 additions and 101 deletions

View File

@@ -46,9 +46,16 @@ namespace ModernKeePass
? exception.InnerException
: exception;
if (!(realException is SaveException)) return;
unhandledExceptionEventArgs.Handled = true;
MessageDialogHelper.SaveErrorDialog(realException as SaveException, Database);
if (realException is SaveException)
{
unhandledExceptionEventArgs.Handled = true;
MessageDialogHelper.SaveErrorDialog(realException as SaveException, Database);
}
else if (realException is DatabaseOpenedException)
{
unhandledExceptionEventArgs.Handled = true;
MessageDialogHelper.SaveUnchangedDialog(realException as DatabaseOpenedException, Database);
}
}
/// <summary>

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using Windows.Storage.Pickers;
using Windows.UI.Popups;
using Windows.UI.Xaml.Media.Animation;
using ModernKeePass.Exceptions;
using ModernKeePass.Interfaces;
@@ -10,13 +9,16 @@ namespace ModernKeePass.Common
{
public static class MessageDialogHelper
{
public static async void ShowActionDialog(string title, string contentText, string actionButtonText, string cancelButtonText, UICommandInvokedHandler action)
// TODO: include resources
public static async void ShowActionDialog(string title, string contentText, string actionButtonText, string cancelButtonText, UICommandInvokedHandler actionCommand, UICommandInvokedHandler cancelCommand)
{
// Create the message dialog and set its content
var messageDialog = CreateBasicDialog(title, contentText, cancelButtonText);
// Add commands and set their callbacks; both buttons use the same callback function instead of inline event handlers
messageDialog.Commands.Add(new UICommand(actionButtonText, action));
messageDialog.Commands.Add(new UICommand(actionButtonText, actionCommand));
// TODO: correct this
//messageDialog.Commands.Add(new UICommand(cancelButtonText, cancelCommand));
// Show the message dialog
await messageDialog.ShowAsync();
@@ -35,6 +37,19 @@ namespace ModernKeePass.Common
var file = await savePicker.PickSaveFileAsync();
if (file != null) database.Save(file);
}, null);
}
public static void SaveUnchangedDialog(DatabaseOpenedException exception, IDatabase database)
{
ShowActionDialog("Opened database", $"Database {database.Name} is currently opened. What to you wish to do?", "Save changes", "Discard", command =>
{
database.Save();
database.Close();
},
command =>
{
database.Close();
});
}

View File

@@ -0,0 +1,9 @@
using System;
namespace ModernKeePass.Exceptions
{
public class DatabaseOpenedException: Exception
{
}
}

View File

@@ -1,4 +1,5 @@
using Windows.Storage;
using System.Threading.Tasks;
using Windows.Storage;
using ModernKeePass.ViewModels;
using ModernKeePassLib;
using ModernKeePassLib.Cryptography.KeyDerivation;
@@ -10,20 +11,23 @@ namespace ModernKeePass.Interfaces
{
string Name { get; }
bool RecycleBinEnabled { get; set; }
int Status { get; set; }
//int Status { get; set; }
GroupVm RootGroup { get; set; }
GroupVm RecycleBin { get; set; }
StorageFile DatabaseFile { get; set; }
PwUuid DataCipher { get; set; }
PwCompressionAlgorithm CompressionAlgorithm { get; set; }
KdfParameters KeyDerivation { get; set; }
bool IsOpen { get; }
bool IsFileOpen { get; }
bool IsClosed { get; }
void Open(CompositeKey key, bool createNew);
Task Open(CompositeKey key, bool createNew);
void UpdateCompositeKey(CompositeKey key);
void Save();
void Save(StorageFile file);
void CreateRecycleBin();
void AddDeletedItem(PwUuid id);
void Close();
Task Close();
}
}

View File

@@ -114,6 +114,7 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Exceptions\DatabaseOpenedException.cs" />
<Compile Include="Interfaces\ILicenseService.cs" />
<Compile Include="Interfaces\IRecent.cs" />
<Compile Include="Interfaces\IRecentItem.cs" />

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml.Controls;
using ModernKeePass.Exceptions;
@@ -16,7 +17,7 @@ namespace ModernKeePass.Services
{
public class DatabaseService: IDatabase
{
public enum DatabaseStatus
/*public enum DatabaseStatus
{
Error = -3,
NoCompositeKey = -2,
@@ -24,9 +25,10 @@ namespace ModernKeePass.Services
Closed = 0,
Opening = 1,
Opened = 2
}
}*/
private readonly PwDatabase _pwDatabase = new PwDatabase();
private readonly ISettings _settings;
private StorageFile _realDatabaseFile;
private StorageFile _databaseFile;
private GroupVm _recycleBin;
@@ -42,7 +44,7 @@ namespace ModernKeePass.Services
}
}
public int Status { get; set; } = (int)DatabaseStatus.Closed;
//public int Status { get; set; } = (int)DatabaseStatus.Closed;
public string Name => DatabaseFile?.Name;
public bool RecycleBinEnabled
@@ -56,8 +58,28 @@ namespace ModernKeePass.Services
get { return _databaseFile; }
set
{
// No file, database is closed
/*if (value == null)
Status = (int) DatabaseStatus.Closed;
else
{
// There already is an opened file
if (Status == (int) DatabaseStatus.Opened)
{
if (_pwDatabase.Modified)
throw new DatabaseOpenedException();
Close().GetAwaiter().GetResult();
}
_databaseFile = value;
Status = (int) DatabaseStatus.Opening;
}*/
if (IsOpen)
{
//if (_pwDatabase.Modified)
throw new DatabaseOpenedException();
//Close().GetAwaiter().GetResult();
}
_databaseFile = value;
Status = (int)DatabaseStatus.Opening;
}
}
@@ -79,6 +101,10 @@ namespace ModernKeePass.Services
set { _pwDatabase.KdfParameters = value; }
}
public bool IsOpen => _pwDatabase.IsOpen;
public bool IsFileOpen => !_pwDatabase.IsOpen && _databaseFile != null;
public bool IsClosed => _databaseFile == null;
public DatabaseService() : this(new SettingsService())
{ }
@@ -93,14 +119,16 @@ namespace ModernKeePass.Services
/// <param name="key">The database composite key</param>
/// <param name="createNew">True to create a new database before opening it</param>
/// <returns>An error message, if any</returns>
public void Open(CompositeKey key, bool createNew = false)
public async Task Open(CompositeKey key, bool createNew = false)
{
// TODO: Check if there is an existing backup file
try
{
if (key == null)
{
Status = (int)DatabaseStatus.NoCompositeKey;
return;
//Status = (int)DatabaseStatus.NoCompositeKey;
//return;
throw new ArgumentNullException(nameof(key));
}
var ioConnection = IOConnectionInfo.FromFile(DatabaseFile);
if (createNew)
@@ -120,17 +148,51 @@ namespace ModernKeePass.Services
else _pwDatabase.Open(ioConnection, key, new NullStatusLogger());
if (!_pwDatabase.IsOpen) return;
Status = (int)DatabaseStatus.Opened;
// Copy database in temp directory and use this file for operations
if (_settings.GetSetting<bool>("AntiCorruption"))
{
_realDatabaseFile = _databaseFile;
var backupFile =
await ApplicationData.Current.RoamingFolder.CreateFileAsync(Name,
CreationCollisionOption.FailIfExists);
Save(backupFile);
}
//Status = (int)DatabaseStatus.Opened;
RootGroup = new GroupVm(_pwDatabase.RootGroup, null, RecycleBinEnabled ? _pwDatabase.RecycleBinUuid : null);
}
catch (InvalidCompositeKeyException)
catch (InvalidCompositeKeyException ex)
{
Status = (int)DatabaseStatus.CompositeKeyError;
//Status = (int)DatabaseStatus.CompositeKeyError;
throw new ArgumentException(ex.Message, ex);
}
catch (Exception)
/*catch (Exception)
{
Status = (int)DatabaseStatus.Error;
//Status = (int)DatabaseStatus.Error;
throw;
}*/
}
/// <summary>
/// Commit the changes to the currently opened database to file
/// </summary>
public void Save()
{
if (!_pwDatabase.IsOpen) return;
try
{
_pwDatabase.Save(new NullStatusLogger());
// Test if save worked correctly
if (_settings.GetSetting<bool>("AntiCorruption"))
{
_pwDatabase.Open(_pwDatabase.IOConnectionInfo, _pwDatabase.MasterKey, new NullStatusLogger());
}
}
catch (Exception e)
{
throw new SaveException(e);
}
}
@@ -151,35 +213,28 @@ namespace ModernKeePass.Services
DatabaseFile = oldFile;
throw;
}
finally
/*finally
{
Status = (int)DatabaseStatus.Opened;
}
}
/// <summary>
/// Commit the changes to the currently opened database to file
/// </summary>
public void Save()
{
if (_pwDatabase == null || !_pwDatabase.IsOpen) return;
try
{
_pwDatabase.Save(new NullStatusLogger());
}
catch (Exception e)
{
throw new SaveException(e);
}
}*/
}
/// <summary>
/// Close the currently opened database
/// </summary>
public void Close()
public async Task Close()
{
_pwDatabase?.Close();
Status = (int)DatabaseStatus.Closed;
// Restore the backup DB to the original one
if (_settings.GetSetting<bool>("AntiCorruption"))
{
if (_pwDatabase.Modified)
Save(_realDatabaseFile);
await DatabaseFile.DeleteAsync();
}
DatabaseFile = null;
}
public void AddDeletedItem(PwUuid id)

View File

@@ -124,37 +124,31 @@ namespace ModernKeePass.ViewModels
public async Task<bool> OpenDatabase(bool createNew)
{
var error = string.Empty;
try
{
_isOpening = true;
Database.Open(CreateCompositeKey(), createNew);
await Database.Open(CreateCompositeKey(), createNew);
await Task.Run(() => RootGroup = Database.RootGroup);
return true;
}
catch (ArgumentException)
{
var errorMessage = new StringBuilder(_resource.GetResourceValue("CompositeKeyErrorUserStart"));
if (HasPassword) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserPassword"));
if (HasPassword && HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserOr"));
if (HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserKeyFile"));
if (HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserAccount"));
UpdateStatus(errorMessage.ToString(), StatusTypes.Error);
}
catch (Exception e)
{
error = $"{_resource.GetResourceValue("CompositeKeyErrorOpen")}: {e.Message}";
var error = $"{_resource.GetResourceValue("CompositeKeyErrorOpen")}: {e.Message}";
UpdateStatus(error, StatusTypes.Error);
}
finally
{
_isOpening = false;
}
switch ((DatabaseService.DatabaseStatus)Database.Status)
{
case DatabaseService.DatabaseStatus.Opened:
await Task.Run(() => RootGroup = Database.RootGroup);
return true;
case DatabaseService.DatabaseStatus.CompositeKeyError:
var errorMessage = new StringBuilder(_resource.GetResourceValue("CompositeKeyErrorUserStart"));
if (HasPassword) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserPassword"));
if (HasPassword && HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserOr"));
if (HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserKeyFile"));
if (HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserAccount"));
UpdateStatus(errorMessage.ToString(), StatusTypes.Error);
break;
case DatabaseService.DatabaseStatus.Error:
UpdateStatus(error, StatusTypes.Error);
break;
}
return false;
}

View File

@@ -51,7 +51,7 @@ namespace ModernKeePass.ViewModels
public MainVm(Frame referenceFrame, Frame destinationFrame, IDatabase database, IResource resource, IRecent recent)
{
var isDatabaseOpen = database != null && database.Status == (int) DatabaseService.DatabaseStatus.Opened;
var isDatabaseOpen = database != null && database.IsOpen;
var mainMenuItems = new ObservableCollection<MainMenuItemVm>
{
@@ -62,7 +62,7 @@ namespace ModernKeePass.ViewModels
Destination = destinationFrame,
Parameter = referenceFrame,
SymbolIcon = Symbol.Page2,
IsSelected = database != null && database.Status == (int) DatabaseService.DatabaseStatus.Opening
IsSelected = database != null && database.IsFileOpen
},
new MainMenuItemVm
{
@@ -90,7 +90,7 @@ namespace ModernKeePass.ViewModels
Parameter = referenceFrame,
SymbolIcon = Symbol.Copy,
IsSelected =
(database == null || database.Status == (int) DatabaseService.DatabaseStatus.Closed) &&
(database == null || database.IsClosed) &&
recent.EntryCount > 0,
IsEnabled = recent.EntryCount > 0
},
@@ -120,7 +120,7 @@ namespace ModernKeePass.ViewModels
SelectedItem = mainMenuItems.FirstOrDefault(m => m.IsSelected);
// Add currently opened database to the menu
if (database != null && database.Status == (int) DatabaseService.DatabaseStatus.Opened)
if (database != null && database.IsOpen)
mainMenuItems.Add(new MainMenuItemVm
{
Title = database.Name,

View File

@@ -8,7 +8,7 @@ namespace ModernKeePass.ViewModels
{
public class OpenVm: NotifyPropertyChangedBase
{
public bool ShowPasswordBox => _database?.Status == (int) DatabaseService.DatabaseStatus.Opening;
public bool ShowPasswordBox => _database.IsFileOpen;
public string Name => _database?.Name;
@@ -19,7 +19,7 @@ namespace ModernKeePass.ViewModels
public OpenVm(IDatabase database)
{
_database = database;
if (database == null || database.Status != (int) DatabaseService.DatabaseStatus.Opening) return;
if (database == null || !database.IsFileOpen) return;
OpenFile(database.DatabaseFile);
}

View File

@@ -1,4 +1,5 @@
using Windows.Storage;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml;
using ModernKeePass.Interfaces;
@@ -14,10 +15,11 @@ namespace ModernKeePass.ViewModels
_database = database;
}
public void Save(bool close = true)
public async Task Save(bool close = true)
{
_database.Save();
if (close) _database.Close();
if (close)
await _database.Close();
}
public void Save(StorageFile file)

View File

@@ -61,7 +61,7 @@ namespace ModernKeePass.ViewModels
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Setting,
PageType = typeof(SettingsDatabasePage),
IsEnabled = database?.Status == 2
IsEnabled = database.IsOpen
},
new ListMenuItemVm
{
@@ -69,7 +69,7 @@ namespace ModernKeePass.ViewModels
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Permissions,
PageType = typeof(SettingsSecurityPage),
IsEnabled = database?.Status == 2
IsEnabled = database.IsOpen
}
};
SelectedItem = menuItems.FirstOrDefault(m => m.IsSelected);

View File

@@ -83,7 +83,7 @@ namespace ModernKeePass.Views
ToastNotificationHelper.ShowMovedToast(Model, resource.GetResourceValue("EntityDeleting"), text);
Model.MarkForDelete();
if (Frame.CanGoBack) Frame.GoBack();
});
}, null);
}
private void RestoreButton_Click(object sender, RoutedEventArgs e)

View File

@@ -119,7 +119,7 @@ namespace ModernKeePass.Views
ToastNotificationHelper.ShowMovedToast(Model, resource.GetResourceValue("EntityDeleting"), text);
Model.MarkForDelete();
if (Frame.CanGoBack) Frame.GoBack();
});
}, null);
}
private void RestoreButton_Click(object sender, RoutedEventArgs e)

View File

@@ -28,10 +28,10 @@ namespace ModernKeePass.Views
_mainFrame = e.Parameter as Frame;
}
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
private async void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
Model.Save();
_mainFrame.Navigate(typeof(Views.MainPage));
await Model.Save();
_mainFrame.Navigate(typeof(MainPage));
}
private async void SaveAsButton_OnClick(object sender, RoutedEventArgs e)
@@ -47,7 +47,7 @@ namespace ModernKeePass.Views
if (file == null) return;
Model.Save(file);
_mainFrame.Navigate(typeof(Views.MainPage));
_mainFrame.Navigate(typeof(MainPage));
}
}
}

View File

@@ -90,7 +90,12 @@ namespace ModernKeePass.Views.UserControls
private void PasswordBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter && Model.IsValid) OpenButton_OnClick(null, null);
if (e.Key == VirtualKey.Enter && Model.IsValid)
{
OpenButton_OnClick(sender, e);
// Stop the event from triggering twice
e.Handled = true;
}
}
private async void KeyFileButton_Click(object sender, RoutedEventArgs e)