diff --git a/ModernKeePass/App.xaml.cs b/ModernKeePass/App.xaml.cs index a44c8cf..0236fc8 100644 --- a/ModernKeePass/App.xaml.cs +++ b/ModernKeePass/App.xaml.cs @@ -9,6 +9,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; using ModernKeePass.Common; +using ModernKeePass.Exceptions; using ModernKeePass.Interfaces; // The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227 @@ -37,14 +38,17 @@ namespace ModernKeePass private void OnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs) { - // TODO: catch only save errors for now, rethrow (do not handle) otherwise + // Save the argument exception because it's cleared on first access var exception = unhandledExceptionEventArgs.Exception; - MessageDialogHelper.ShowErrorDialog( + var realException = exception is TargetInvocationException && exception.InnerException != null ? exception.InnerException - : exception); + : exception; + + if (!(realException is SaveException)) return; unhandledExceptionEventArgs.Handled = true; + MessageDialogHelper.SaveErrorDialog(realException as SaveException, Database); } /// diff --git a/ModernKeePass/Common/DatabaseHelper.cs b/ModernKeePass/Common/DatabaseHelper.cs index 2ac60bc..5dca0e2 100644 --- a/ModernKeePass/Common/DatabaseHelper.cs +++ b/ModernKeePass/Common/DatabaseHelper.cs @@ -1,6 +1,8 @@ using System; using Windows.Storage; +using Windows.Storage.AccessCache; using Windows.UI.Xaml.Controls; +using ModernKeePass.Exceptions; using ModernKeePass.Interfaces; using ModernKeePass.ViewModels; using ModernKeePassLib; @@ -102,7 +104,7 @@ namespace ModernKeePass.Common { Status = (int)DatabaseStatus.CompositeKeyError; } - catch (Exception ex) + catch (Exception) { Status = (int)DatabaseStatus.Error; throw; @@ -115,9 +117,21 @@ namespace ModernKeePass.Common /// The new database file public void Save(StorageFile file) { + var oldFile = DatabaseFile; DatabaseFile = file; - _pwDatabase.SaveAs(IOConnectionInfo.FromFile(DatabaseFile), true, new NullStatusLogger()); - Status = (int)DatabaseStatus.Opened; + try + { + _pwDatabase.SaveAs(IOConnectionInfo.FromFile(DatabaseFile), true, new NullStatusLogger()); + } + catch + { + DatabaseFile = oldFile; + throw; + } + finally + { + Status = (int)DatabaseStatus.Opened; + } } /// @@ -126,7 +140,14 @@ namespace ModernKeePass.Common public void Save() { if (_pwDatabase == null || !_pwDatabase.IsOpen) return; - _pwDatabase.Save(new NullStatusLogger()); + try + { + _pwDatabase.Save(new NullStatusLogger()); + } + catch (Exception e) + { + throw new SaveException(e); + } } /// @@ -145,7 +166,7 @@ namespace ModernKeePass.Common public void CreateRecycleBin() { - RecycleBin = ((GroupVm)RootGroup).AddNewGroup("Recycle bin"); + RecycleBin = RootGroup.AddNewGroup("Recycle bin"); RecycleBin.IsSelected = true; RecycleBin.IconSymbol = Symbol.Delete; } diff --git a/ModernKeePass/Common/MessageDialogHelper.cs b/ModernKeePass/Common/MessageDialogHelper.cs index 9027486..657d497 100644 --- a/ModernKeePass/Common/MessageDialogHelper.cs +++ b/ModernKeePass/Common/MessageDialogHelper.cs @@ -1,18 +1,22 @@ using System; +using System.Collections.Generic; +using Windows.Storage.Pickers; using Windows.UI.Popups; +using ModernKeePass.Exceptions; +using ModernKeePass.Interfaces; namespace ModernKeePass.Common { public static class MessageDialogHelper { - public static async void ShowDeleteConfirmationDialog(string actionText, string contentText, UICommandInvokedHandler action) + public static async void ShowActionDialog(string title, string contentText, string actionButtonText, string cancelButtonText, UICommandInvokedHandler action) { // Create the message dialog and set its content - var messageDialog = new MessageDialog(contentText); + var messageDialog = new MessageDialog(contentText, title); // Add commands and set their callbacks; both buttons use the same callback function instead of inline event handlers - messageDialog.Commands.Add(new UICommand(actionText, action)); - messageDialog.Commands.Add(new UICommand("Cancel")); + messageDialog.Commands.Add(new UICommand(actionButtonText, action)); + messageDialog.Commands.Add(new UICommand(cancelButtonText)); // Set the command that will be invoked by default messageDialog.DefaultCommandIndex = 1; @@ -24,6 +28,22 @@ namespace ModernKeePass.Common await messageDialog.ShowAsync(); } + public static void SaveErrorDialog(SaveException exception, IDatabase database) + { + ShowActionDialog("Save error", exception.InnerException.Message, "Save as", "Discard", async command => + { + var savePicker = new FileSavePicker + { + SuggestedStartLocation = PickerLocationId.DocumentsLibrary, + SuggestedFileName = $"{database.DatabaseFile.DisplayName} - copy" + }; + savePicker.FileTypeChoices.Add("KeePass 2.x database", new List { ".kdbx" }); + + var file = await savePicker.PickSaveFileAsync(); + if (file != null) database.Save(file); + }); + } + public static async void ShowErrorDialog(Exception exception) { if (exception == null) return; diff --git a/ModernKeePass/Exceptions/SaveException.cs b/ModernKeePass/Exceptions/SaveException.cs new file mode 100644 index 0000000..f24bb90 --- /dev/null +++ b/ModernKeePass/Exceptions/SaveException.cs @@ -0,0 +1,14 @@ +using System; + +namespace ModernKeePass.Exceptions +{ + public class SaveException : Exception + { + public new Exception InnerException { get; } + + public SaveException(Exception exception) + { + InnerException = exception; + } + } +} diff --git a/ModernKeePass/ModernKeePassApp.csproj b/ModernKeePass/ModernKeePassApp.csproj index 05c66f5..1455439 100644 --- a/ModernKeePass/ModernKeePassApp.csproj +++ b/ModernKeePass/ModernKeePassApp.csproj @@ -123,6 +123,7 @@ + diff --git a/ModernKeePass/Pages/EntryDetailPage.xaml.cs b/ModernKeePass/Pages/EntryDetailPage.xaml.cs index 95892a3..865b18a 100644 --- a/ModernKeePass/Pages/EntryDetailPage.xaml.cs +++ b/ModernKeePass/Pages/EntryDetailPage.xaml.cs @@ -76,7 +76,7 @@ namespace ModernKeePass.Pages ? "Are you sure you want to send this entry to the recycle bin?" : "Are you sure you want to delete this entry?"; var text = isRecycleBinEnabled ? "Item moved to the Recycle bin" : "Item permanently removed"; - MessageDialogHelper.ShowDeleteConfirmationDialog("Delete", message, a => + MessageDialogHelper.ShowActionDialog("Warning", message, "Delete", "Cancel", a => { ToastNotificationHelper.ShowMovedToast(Model, "Deleting", text); Model.MarkForDelete(); diff --git a/ModernKeePass/Pages/GroupDetailPage.xaml.cs b/ModernKeePass/Pages/GroupDetailPage.xaml.cs index 9ac7de6..cad355e 100644 --- a/ModernKeePass/Pages/GroupDetailPage.xaml.cs +++ b/ModernKeePass/Pages/GroupDetailPage.xaml.cs @@ -116,7 +116,7 @@ namespace ModernKeePass.Pages ? "Are you sure you want to send the whole group and all its entries to the recycle bin?" : "Are you sure you want to delete the whole group and all its entries?"; var text = isRecycleBinEnabled ? "Item moved to the Recycle bin" : "Item permanently removed"; - MessageDialogHelper.ShowDeleteConfirmationDialog("Delete", message, a => + MessageDialogHelper.ShowActionDialog("Warning", message, "Delete", "Cancel", a => { ToastNotificationHelper.ShowMovedToast(Model, "Deleting", text); Model.MarkForDelete(); diff --git a/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml b/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml index 40e3294..e56a621 100644 --- a/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml +++ b/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml @@ -34,9 +34,13 @@ - + + + + + diff --git a/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml.cs b/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml.cs index 32c9423..773865d 100644 --- a/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml.cs +++ b/ModernKeePass/Pages/MainPageFrames/RecentDatabasesPage.xaml.cs @@ -1,9 +1,5 @@ // Pour en savoir plus sur le modèle d'élément Page vierge, consultez la page http://go.microsoft.com/fwlink/?LinkId=234238 -using System; -using Windows.UI.Xaml; -using ModernKeePass.ViewModels; - namespace ModernKeePass.Pages { /// @@ -11,17 +7,9 @@ namespace ModernKeePass.Pages /// public sealed partial class RecentDatabasesPage { - - public RecentVm Model => (RecentVm)DataContext; public RecentDatabasesPage() { InitializeComponent(); } - - private void OpenDatabaseUserControl_OnValidationChecking(object sender, EventArgs e) - { - var app = (App)Application.Current; - app.Database.DatabaseFile = ((RecentItemVm)Model.SelectedItem).DatabaseFile; - } } } diff --git a/ModernKeePass/ViewModels/Items/RecentItemVm.cs b/ModernKeePass/ViewModels/Items/RecentItemVm.cs index 8ef1944..6315ffb 100644 --- a/ModernKeePass/ViewModels/Items/RecentItemVm.cs +++ b/ModernKeePass/ViewModels/Items/RecentItemVm.cs @@ -1,6 +1,8 @@ -using Windows.Storage; +using System; +using Windows.Storage; using ModernKeePass.Common; using Windows.Storage.AccessCache; +using Windows.UI.Xaml; using ModernKeePass.Interfaces; namespace ModernKeePass.ViewModels @@ -27,5 +29,21 @@ namespace ModernKeePass.ViewModels get { return _isSelected; } set { SetProperty(ref _isSelected, value); } } + + public void OpenDatabaseFile() + { + OpenDatabaseFile((Application.Current as App)?.Database); + } + + public void OpenDatabaseFile(IDatabase database) + { + database.DatabaseFile = DatabaseFile; + } + + public async void UpdateAccessTime() + { + var mru = StorageApplicationPermissions.MostRecentlyUsedList; + await mru.GetFileAsync(Token); + } } } diff --git a/ModernKeePass/ViewModels/RecentVm.cs b/ModernKeePass/ViewModels/RecentVm.cs index 044c686..49e9270 100644 --- a/ModernKeePass/ViewModels/RecentVm.cs +++ b/ModernKeePass/ViewModels/RecentVm.cs @@ -37,13 +37,12 @@ namespace ModernKeePass.ViewModels public RecentVm() { - // TODO: opening the files actually changes the MRU order var mru = StorageApplicationPermissions.MostRecentlyUsedList; foreach (var entry in mru.Entries) { try { - var file = mru.GetFileAsync(entry.Token).GetAwaiter().GetResult(); + var file = mru.GetFileAsync(entry.Token, AccessCacheOptions.SuppressAccessTimeUpdate).GetAwaiter().GetResult(); RecentItems.Add(new RecentItemVm(entry, file)); } catch (Exception)