From bd581203f2da08a846f7d3270e7d721cec44e522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=B6lts?= Date: Mon, 10 Oct 2016 15:59:44 +0200 Subject: [PATCH 1/2] Implements #5 Cache databases for disconnected (offline) usage --- CHANGELOG.md | 2 + .../Configuration/PluginConfiguration.cs | 10 + KeeAnywhere/Forms/SettingsForm.Designer.cs | 67 ++++- KeeAnywhere/Forms/SettingsForm.cs | 53 +++- KeeAnywhere/KeeAnywhere.csproj | 5 +- KeeAnywhere/KeeAnywhereExt.cs | 14 +- KeeAnywhere/Offline/CacheManagerService.cs | 125 ++++++++ KeeAnywhere/Offline/CacheProvider.cs | 268 ++++++++++++++++++ KeeAnywhere/Offline/ICacheSupervisor.cs | 20 ++ .../AmazonDrive/AmazonDriveStorageProvider.cs | 5 +- .../AmazonS3/AmazonS3StorageProvider.cs | 13 +- .../Box/BoxStorageProvider.cs | 5 +- .../Dropbox/DropboxStorageProvider.cs | 5 +- .../GoogleDrive/GoogleDriveStorageProvider.cs | 5 +- .../HiDrive/HiDriveStorageProvider.cs | 5 +- .../HubiC/HubiCStorageProvider.cs | 5 +- .../StorageProviders/IStorageProvider.cs | 2 +- .../OneDrive/OneDriveStorageProvider.cs | 6 +- .../StorageProviders/StorageService.cs | 35 ++- .../WebRequest/KeeAnywhereWebRequest.cs | 7 +- .../KeeAnywhereWebRequestCreator.cs | 27 -- 21 files changed, 610 insertions(+), 74 deletions(-) create mode 100644 KeeAnywhere/Offline/CacheManagerService.cs create mode 100644 KeeAnywhere/Offline/CacheProvider.cs create mode 100644 KeeAnywhere/Offline/ICacheSupervisor.cs delete mode 100644 KeeAnywhere/WebRequest/KeeAnywhereWebRequestCreator.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e03098..ce475d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### New +- \#5 Cache databases for disconnected (offline) usage + ### Fixed - \#58 Amazon S3: Cannot save/write database file to root of bucket diff --git a/KeeAnywhere/Configuration/PluginConfiguration.cs b/KeeAnywhere/Configuration/PluginConfiguration.cs index 66c73f8..bb08cc7 100644 --- a/KeeAnywhere/Configuration/PluginConfiguration.cs +++ b/KeeAnywhere/Configuration/PluginConfiguration.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using System.Runtime.Serialization; +using KeePass.App.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -11,6 +13,14 @@ public class PluginConfiguration [DataMember] public bool IsOfflineCacheEnabled { get; set; } + public string OfflineCacheFolder + { + get + { + return Path.Combine(AppConfigSerializer.LocalAppDataDirectory, "KeeAnywhereOfflineCache"); + } + } + [DataMember] [JsonConverter(typeof(StringEnumConverter))] public AccountStorageLocation AccountStorageLocation { get; set; } diff --git a/KeeAnywhere/Forms/SettingsForm.Designer.cs b/KeeAnywhere/Forms/SettingsForm.Designer.cs index 3764c56..56e78ee 100644 --- a/KeeAnywhere/Forms/SettingsForm.Designer.cs +++ b/KeeAnywhere/Forms/SettingsForm.Designer.cs @@ -43,7 +43,7 @@ private void InitializeComponent() this.m_rbStorageLocation_LocalUserSecureStore = new System.Windows.Forms.RadioButton(); this.lblAccountStorageLocation = new System.Windows.Forms.Label(); this.m_tabGeneral = new System.Windows.Forms.TabPage(); - this.m_cbOfflineCache = new System.Windows.Forms.CheckBox(); + this.m_chkOfflineCache = new System.Windows.Forms.CheckBox(); this.m_tabAbout = new System.Windows.Forms.TabPage(); this.m_lblAboutVersion = new System.Windows.Forms.Label(); this.m_lblAboutExplanation = new System.Windows.Forms.Label(); @@ -61,6 +61,9 @@ private void InitializeComponent() this.m_mnuHelp_Sep2 = new System.Windows.Forms.ToolStripSeparator(); this.m_mnuHelp_Donate = new System.Windows.Forms.ToolStripMenuItem(); this.m_pnlFormButtons = new System.Windows.Forms.TableLayoutPanel(); + this.m_gbOfflineCache = new System.Windows.Forms.GroupBox(); + this.m_btnClearCache = new System.Windows.Forms.Button(); + this.m_btnOpenCacheFolder = new System.Windows.Forms.Button(); this.m_btnHelp = new KeeAnywhere.Forms.DropDownButton(); this.m_btnAccountAdd = new KeeAnywhere.Forms.DropDownButton(); ((System.ComponentModel.ISupportInitialize)(this.m_bannerImage)).BeginInit(); @@ -70,6 +73,7 @@ private void InitializeComponent() this.m_tabAbout.SuspendLayout(); this.m_mnuHelp.SuspendLayout(); this.m_pnlFormButtons.SuspendLayout(); + this.m_gbOfflineCache.SuspendLayout(); this.SuspendLayout(); // // m_bannerImage @@ -209,7 +213,7 @@ private void InitializeComponent() // // m_tabGeneral // - this.m_tabGeneral.Controls.Add(this.m_cbOfflineCache); + this.m_tabGeneral.Controls.Add(this.m_gbOfflineCache); this.m_tabGeneral.Location = new System.Drawing.Point(4, 22); this.m_tabGeneral.Name = "m_tabGeneral"; this.m_tabGeneral.Padding = new System.Windows.Forms.Padding(3); @@ -218,15 +222,16 @@ private void InitializeComponent() this.m_tabGeneral.Text = "General"; this.m_tabGeneral.UseVisualStyleBackColor = true; // - // m_cbOfflineCache + // m_chkOfflineCache // - this.m_cbOfflineCache.AutoSize = true; - this.m_cbOfflineCache.Location = new System.Drawing.Point(7, 7); - this.m_cbOfflineCache.Name = "m_cbOfflineCache"; - this.m_cbOfflineCache.Size = new System.Drawing.Size(189, 17); - this.m_cbOfflineCache.TabIndex = 0; - this.m_cbOfflineCache.Text = "Cache Databases for offline usage"; - this.m_cbOfflineCache.UseVisualStyleBackColor = true; + this.m_chkOfflineCache.AutoSize = true; + this.m_chkOfflineCache.Location = new System.Drawing.Point(6, 19); + this.m_chkOfflineCache.Name = "m_chkOfflineCache"; + this.m_chkOfflineCache.Size = new System.Drawing.Size(189, 17); + this.m_chkOfflineCache.TabIndex = 0; + this.m_chkOfflineCache.Text = "Cache Databases for offline usage"; + this.m_chkOfflineCache.UseVisualStyleBackColor = true; + this.m_chkOfflineCache.CheckedChanged += new System.EventHandler(this.OnOfflineCacheChanged); // // m_tabAbout // @@ -394,6 +399,40 @@ private void InitializeComponent() this.m_pnlFormButtons.Size = new System.Drawing.Size(604, 29); this.m_pnlFormButtons.TabIndex = 9; // + // m_gbOfflineCache + // + this.m_gbOfflineCache.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.m_gbOfflineCache.Controls.Add(this.m_btnOpenCacheFolder); + this.m_gbOfflineCache.Controls.Add(this.m_btnClearCache); + this.m_gbOfflineCache.Controls.Add(this.m_chkOfflineCache); + this.m_gbOfflineCache.Location = new System.Drawing.Point(6, 6); + this.m_gbOfflineCache.Name = "m_gbOfflineCache"; + this.m_gbOfflineCache.Size = new System.Drawing.Size(578, 84); + this.m_gbOfflineCache.TabIndex = 1; + this.m_gbOfflineCache.TabStop = false; + this.m_gbOfflineCache.Text = "Offline Cache"; + // + // m_btnClearCache + // + this.m_btnClearCache.Location = new System.Drawing.Point(6, 43); + this.m_btnClearCache.Name = "m_btnClearCache"; + this.m_btnClearCache.Size = new System.Drawing.Size(75, 23); + this.m_btnClearCache.TabIndex = 1; + this.m_btnClearCache.Text = "Clear Cache"; + this.m_btnClearCache.UseVisualStyleBackColor = true; + this.m_btnClearCache.Click += new System.EventHandler(this.OnClearCache); + // + // m_btnOpenCacheFolder + // + this.m_btnOpenCacheFolder.Location = new System.Drawing.Point(88, 43); + this.m_btnOpenCacheFolder.Name = "m_btnOpenCacheFolder"; + this.m_btnOpenCacheFolder.Size = new System.Drawing.Size(182, 23); + this.m_btnOpenCacheFolder.TabIndex = 2; + this.m_btnOpenCacheFolder.Text = "Open Cache Folder in Explorer"; + this.m_btnOpenCacheFolder.UseVisualStyleBackColor = true; + this.m_btnOpenCacheFolder.Click += new System.EventHandler(this.OnOpenCacheFolder); + // // m_btnHelp // this.m_btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); @@ -440,10 +479,11 @@ private void InitializeComponent() this.m_tabAccounts.ResumeLayout(false); this.m_tabAccounts.PerformLayout(); this.m_tabGeneral.ResumeLayout(false); - this.m_tabGeneral.PerformLayout(); this.m_tabAbout.ResumeLayout(false); this.m_mnuHelp.ResumeLayout(false); this.m_pnlFormButtons.ResumeLayout(false); + this.m_gbOfflineCache.ResumeLayout(false); + this.m_gbOfflineCache.PerformLayout(); this.ResumeLayout(false); } @@ -457,7 +497,7 @@ private void InitializeComponent() private System.Windows.Forms.TabPage m_tabAbout; private System.Windows.Forms.Button m_btnOK; private System.Windows.Forms.Button m_btnCancel; - private System.Windows.Forms.CheckBox m_cbOfflineCache; + private System.Windows.Forms.CheckBox m_chkOfflineCache; private System.Windows.Forms.Label m_lblAboutExplanation; private System.Windows.Forms.Label m_lblAboutHeader; private System.Windows.Forms.RadioButton m_rbStorageLocation_Disk; @@ -483,5 +523,8 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripSeparator m_mnuHelp_Sep2; private System.Windows.Forms.ToolStripMenuItem m_mnuHelp_Donate; private System.Windows.Forms.TableLayoutPanel m_pnlFormButtons; + private System.Windows.Forms.GroupBox m_gbOfflineCache; + private System.Windows.Forms.Button m_btnOpenCacheFolder; + private System.Windows.Forms.Button m_btnClearCache; } } \ No newline at end of file diff --git a/KeeAnywhere/Forms/SettingsForm.cs b/KeeAnywhere/Forms/SettingsForm.cs index a7ba4d1..91c99ad 100644 --- a/KeeAnywhere/Forms/SettingsForm.cs +++ b/KeeAnywhere/Forms/SettingsForm.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; @@ -19,8 +20,6 @@ public partial class SettingsForm : Form public SettingsForm() { InitializeComponent(); - - m_tcSettings.TabPages.Remove(m_tabGeneral); } public void InitEx(ConfigurationService configService, UIService uiService) @@ -40,7 +39,7 @@ private void OnBtnCancelClick(object sender, EventArgs e) private void OnBtnOkClick(object sender, EventArgs e) { // General Settings - m_configService.PluginConfiguration.IsOfflineCacheEnabled = m_cbOfflineCache.Checked; + m_configService.PluginConfiguration.IsOfflineCacheEnabled = m_chkOfflineCache.Checked; if (m_rbStorageLocation_LocalUserSecureStore.Checked) m_configService.PluginConfiguration.AccountStorageLocation = AccountStorageLocation.LocalUserSecureStore; @@ -170,7 +169,8 @@ private void UpdateAccountList() private void InitGeneralTab() { - m_cbOfflineCache.Checked = m_configService.PluginConfiguration.IsOfflineCacheEnabled; + m_chkOfflineCache.Checked = m_configService.PluginConfiguration.IsOfflineCacheEnabled; + UpdateCacheButtonState(); } private async void OnAccountAdd(object sender, EventArgs e) @@ -307,6 +307,51 @@ private async void OnAccountCheck(object sender, EventArgs e) this.UseWaitCursor = false; } + private void OnClearCache(object sender, EventArgs e) + { + var isOk = MessageService.AskYesNo("Do you really want to clear Offline Cache Folder?\r\nMaybe your unsynced changes get lost!", "Clear Offline Cache", false); + + if (isOk) + ClearCache(); + } + + private void ClearCache() + { + var path = m_configService.PluginConfiguration.OfflineCacheFolder; + + if (!Directory.Exists(path)) + return; + + Directory.Delete(path, true); + } + + private void OnOpenCacheFolder(object sender, EventArgs e) + { + var path = m_configService.PluginConfiguration.OfflineCacheFolder; + if (Directory.Exists(path)) + Process.Start(path); + else + MessageService.ShowInfo("Offline Cache Folder does not exist:", path); + } + + private void OnOfflineCacheChanged(object sender, EventArgs e) + { + UpdateCacheButtonState(); + + var path = m_configService.PluginConfiguration.OfflineCacheFolder; + if (m_chkOfflineCache.Checked || !Directory.Exists(path)) return; + var isOk = MessageService.AskYesNo("You are disabeling Offline Cache.\r\nDo you want to clear Offline Cache Folder?\r\nMaybe your unsynced changes get lost!", "Clear Offline Cache", false); + + if (isOk) + ClearCache(); + } + + private void UpdateCacheButtonState() + { + var isEnabled = m_chkOfflineCache.Checked; + m_btnOpenCacheFolder.Enabled = isEnabled; + m_btnClearCache.Enabled = isEnabled; + } } } diff --git a/KeeAnywhere/KeeAnywhere.csproj b/KeeAnywhere/KeeAnywhere.csproj index 385a474..4f53d07 100644 --- a/KeeAnywhere/KeeAnywhere.csproj +++ b/KeeAnywhere/KeeAnywhere.csproj @@ -239,6 +239,8 @@ OAuth2Form.cs + + True True @@ -299,7 +301,7 @@ - + @@ -362,6 +364,7 @@ + diff --git a/KeeAnywhere/KeeAnywhereExt.cs b/KeeAnywhere/KeeAnywhereExt.cs index a45e148..b4499c9 100644 --- a/KeeAnywhere/KeeAnywhereExt.cs +++ b/KeeAnywhere/KeeAnywhereExt.cs @@ -5,6 +5,7 @@ using System.Windows.Forms; using KeeAnywhere.Configuration; using KeeAnywhere.Forms; +using KeeAnywhere.Offline; using KeeAnywhere.StorageProviders; using KeePass.Plugins; using KeePass.UI; @@ -29,6 +30,7 @@ public class KeeAnywhereExt : Plugin private ToolStripMenuItem _tsShowSettings; private UIService _uiService; + private CacheManagerService _cacheManagerService; /// @@ -50,7 +52,7 @@ public override bool Initialize(IPluginHost pluginHost) if (NativeLib.IsUnix()) return false; _host = pluginHost; - + //_host.MainWindow.FileOpened; // Some binding redirection fixes for Google Drive API FixGoogleApiDependencyLoading(); @@ -61,9 +63,13 @@ public override bool Initialize(IPluginHost pluginHost) _configService = new ConfigurationService(pluginHost); _configService.Load(); + // Initialize CacheManager + _cacheManagerService = new CacheManagerService(_configService, _host); + _cacheManagerService.RegisterEvents(); + // Initialize storage providers - _storageService = new StorageService(_configService); - _storageService.Register(); + _storageService = new StorageService(_configService, _cacheManagerService); + _storageService.RegisterPrefixes(); // Initialize UIService _uiService = new UIService(_configService, _storageService); @@ -230,6 +236,8 @@ public override void Terminate() if (_host == null) return; _configService.Save(); + _cacheManagerService.UnRegisterEvents(); + _host.MainWindow.ToolsMenu.DropDownItems.Remove(_tsShowSettings); diff --git a/KeeAnywhere/Offline/CacheManagerService.cs b/KeeAnywhere/Offline/CacheManagerService.cs new file mode 100644 index 0000000..3496112 --- /dev/null +++ b/KeeAnywhere/Offline/CacheManagerService.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using KeeAnywhere.Configuration; +using KeeAnywhere.StorageProviders; +using KeePass.App.Configuration; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib.Delegates; +using KeePassLib.Utility; + +namespace KeeAnywhere.Offline +{ + public class CacheManagerService : ICacheSupervisor + { + private readonly ConfigurationService _configService; + private readonly IPluginHost _host; + + public CacheManagerService(ConfigurationService configService, IPluginHost host) + { + if (configService == null) throw new ArgumentNullException("configService"); + if (host == null) throw new ArgumentNullException("host"); + + _configService = configService; + _host = host; + } + + public void RegisterEvents() + { + var kp = _host.MainWindow; + //kp.FileOpened += OnFileOpened; + //kp.FileClosed += OnFileClosed; + kp.FileSaving += OnFileSaving; + kp.FileSaved += OnFileSaved; + } + + public void UnRegisterEvents() + { + var kp = _host.MainWindow; + //kp.FileOpened -= OnFileOpened; + //kp.FileClosed -= OnFileClosed; + kp.FileSaving -= OnFileSaving; + kp.FileSaved -= OnFileSaved; + } + + private void OnFileSaved(object sender, FileSavedEventArgs fileSavedEventArgs) + { + this.IsSaving = false; + } + + private void OnFileSaving(object sender, FileSavingEventArgs fileSavingEventArgs) + { + this.IsSaving = true; + } + + private void OnFileClosed(object sender, FileClosedEventArgs e) + { + throw new NotImplementedException(); + } + + private void OnFileOpened(object sender, FileOpenedEventArgs e) + { + throw new NotImplementedException(); + } + + public IStorageProvider GetCachedProvider(IStorageProvider provider, Uri uri) + { + var cachedProvider = new CacheProvider(provider, uri, this); + + return cachedProvider; + } + + public string CacheFolder + { + get + { + var path = _configService.PluginConfiguration.OfflineCacheFolder; + + if (string.IsNullOrEmpty(path)) + throw new ArgumentNullException("Offline Cache Folder not configured."); + + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + + return path; + } + } + + public bool IsSaving { get; protected set; } + + public void CouldNotSaveToRemote(Uri requestedUri, Exception ex) + { + Task.Run(() => MessageService.ShowWarning( + "KeeAnywhere Offline Cache:\r\nCould not save to remote (but saved in offline cache):", + requestedUri.ToString(), + ex)); + } + + public void CouldNotOpenFromRemote(Uri requestedUri, Exception ex) + { + Task.Run(() => MessageService.ShowWarning( + "KeeAnywhere Offline Cache:\r\nCould not open from remote, using offline cached version:", + requestedUri.ToString(), + ex)); + } + + public void OpenWithConflict(Uri requestedUri) + { + Task.Run(() => MessageService.ShowWarning( + "KeeAnywhere Offline Cache:\r\nConflict opening Datbase.", + "Both offline cached and online remote database are changed.\r\nMerging both datbases on next save is required.", + requestedUri.ToString())); + } + + public void UpdatedRemoteOnLoad(Uri requestedUri) + { + Task.Run(() => MessageService.ShowWarning( + "KeeAnywhere Offline Cache:\r\nRemote Database was updated with last offline cached changes during open.", + requestedUri.ToString())); + } + } +} diff --git a/KeeAnywhere/Offline/CacheProvider.cs b/KeeAnywhere/Offline/CacheProvider.cs new file mode 100644 index 0000000..555a761 --- /dev/null +++ b/KeeAnywhere/Offline/CacheProvider.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using KeeAnywhere.StorageProviders; +using KeePassLib.Cryptography; +using KeePassLib.Utility; + +namespace KeeAnywhere.Offline +{ + public class CacheProvider : IStorageProvider + { + private readonly IStorageProvider _baseProvider; + private readonly Uri _requestedUri; + private readonly ICacheSupervisor _cacheSupervisor; + private string _cachedFilename; + + public CacheProvider(IStorageProvider baseProvider, Uri requestedUri, ICacheSupervisor cacheSupervisor) + { + if (baseProvider == null) throw new ArgumentNullException("baseProvider"); + if (requestedUri == null) throw new ArgumentNullException("requestedUri"); + if (cacheSupervisor == null) throw new ArgumentNullException("cacheSupervisor"); + _baseProvider = baseProvider; + _requestedUri = requestedUri; + _cacheSupervisor = cacheSupervisor; + } + + public string CachedFilename + { + get + { + if (_cachedFilename == null) + { + var hashBytes = GetMD5Hash(_requestedUri.ToString()); + var hash = MemUtil.ByteArrayToHexString(hashBytes); + _cachedFilename = Path.Combine(_cacheSupervisor.CacheFolder, Path.ChangeExtension(hash, "kdbx")); + } + + return _cachedFilename; + } + } + + public string BaseHashFilename + { + get { return Path.ChangeExtension(this.CachedFilename, "basehash"); } + } + + public string LocalHashFilename + { + get { return Path.ChangeExtension(this.CachedFilename, "localhash"); } + } + + + public bool IsCached() + { + return File.Exists(this.CachedFilename) + && File.Exists(this.BaseHashFilename) + && File.Exists(this.LocalHashFilename); + } + + public bool IsCachedFileChanged() + { + var localHash = GetLocalHash(); + var baseHash = GetBaseHash(); + + return localHash != baseHash; + } + + private string GetBaseHash() + { + return File.ReadAllText(this.BaseHashFilename); + } + + private string GetLocalHash() + { + return File.ReadAllText(this.LocalHashFilename); + } + + + public async Task Load(string path) + { + try + { + var remoteDetails = await LoadFromRemote(path); + + if (_cacheSupervisor.IsSaving) return remoteDetails.Stream; + + if (this.IsCached()) // If file was cached before? + { + var baseHash = GetBaseHash(); + var localHash = GetLocalHash(); + + if (localHash != baseHash) // Cached file has been changed since last remote update? + { + if (baseHash != remoteDetails.Hash) // Remote file has been changed since last update? + { + // Conflict + _cacheSupervisor.OpenWithConflict(_requestedUri); + } + else + { + // No conflict: try updating remote + try + { + await UpdateRemoteFromCache(path); + _cacheSupervisor.UpdatedRemoteOnLoad(_requestedUri); + } + catch (Exception) + { + } + } + + return File.OpenRead(this.CachedFilename); + } + + + /* + * 1. Read remote + * 2. If remote readable: Is remote changed? + * a. yes => conflict, show message + * b. no => try update remote with cached + * 3. open cached + */ + } + + await UpdateCacheFromRemote(remoteDetails); + return remoteDetails.Stream; + } + catch (Exception ex) // Could not load file from Network: open cached file, if present + { + if (!this.IsCached()) + throw; + + if (!_cacheSupervisor.IsSaving) + { + _cacheSupervisor.CouldNotOpenFromRemote(_requestedUri, ex); + } + + return File.OpenRead(this.CachedFilename); + } + } + + private async Task UpdateRemoteFromCache(string path) + { + var cachedStream = File.OpenRead(this.CachedFilename); + await this.Save(cachedStream, path); + var localHash = GetLocalHash(); + File.WriteAllText(this.BaseHashFilename, localHash); + } + + private async Task UpdateCacheFromRemote(FileDetails remoteDetails) + { + await UpdateCache(remoteDetails.Stream, remoteDetails.Hash); + File.WriteAllText(this.BaseHashFilename, remoteDetails.Hash); + } + + private async Task UpdateCache(Stream stream, string hash) + { + using (var cachedStream = File.Create(this.CachedFilename)) + { + await stream.CopyToAsync(cachedStream); + cachedStream.Close(); + stream.Position = 0; + } + File.WriteAllText(this.LocalHashFilename, hash); + } + + private async Task LoadFromRemote(string path) + { + Stream dataStream = new MemoryStream(); + string hash = null; + + using (var remoteStream = await _baseProvider.Load(path)) + { + // Copy remote to temporary stream (because remote stream may not be seekable) + var tempStream = new MemoryStream(); + await remoteStream.CopyToAsync(tempStream); + tempStream.Position = 0; + + // Get Hash of remote stream + hash = HashStream(tempStream, dataStream); + + remoteStream.Close(); + } + + return new FileDetails(dataStream, hash); + } + + private static string HashStream(Stream sourceStream, Stream destStream) + { + string hash; + + using (var hashingRemoteStream = new HashingStreamEx(sourceStream, false, new SHA256Managed())) + { + hashingRemoteStream.CopyTo(destStream); + hashingRemoteStream.Close(); + hash = MemUtil.ByteArrayToHexString(hashingRemoteStream.Hash); + destStream.Position = 0; + } + + return hash; + } + + public async Task Save(Stream stream, string path) + { + var cachedStream = new MemoryStream(); + var hash = HashStream(stream, cachedStream); + await UpdateCache(cachedStream, hash); + + try // Try to save to remote + { + await _baseProvider.Save(cachedStream, path); + File.WriteAllText(this.BaseHashFilename, hash); + } + catch (Exception ex) + { + _cacheSupervisor.CouldNotSaveToRemote(_requestedUri, ex); + } + } + + + public Task GetRootItem() + { + return _baseProvider.GetRootItem(); + } + + public Task> GetChildrenByParentItem(StorageProviderItem parent) + { + return _baseProvider.GetChildrenByParentItem(parent); + } + + public bool IsFilenameValid(string filename) + { + return _baseProvider.IsFilenameValid(filename); + } + + + private static byte[] GetMD5Hash(string s) + { + if (s == null) throw new ArgumentNullException("s"); + + //MD5 Hash aus dem String berechnen. Dazu muss der string in ein Byte[] + //zerlegt werden. Danach muss das Resultat wieder zurück in ein string. + var md5 = new MD5CryptoServiceProvider(); + var textToHash = Encoding.Default.GetBytes(s); + var result = md5.ComputeHash(textToHash); + + //return BitConverter.ToString(result); + //return string.Concat(result.Select(_ => _.ToString("X2"))); + return result; + } + } + + public struct FileDetails + { + public FileDetails(Stream stream, string hash) + { + Stream = stream; + Hash = hash; + } + + public string Hash { get; set; } + public Stream Stream { get; set; } + } +} \ No newline at end of file diff --git a/KeeAnywhere/Offline/ICacheSupervisor.cs b/KeeAnywhere/Offline/ICacheSupervisor.cs new file mode 100644 index 0000000..81614f9 --- /dev/null +++ b/KeeAnywhere/Offline/ICacheSupervisor.cs @@ -0,0 +1,20 @@ +using System; + +namespace KeeAnywhere.Offline +{ + public interface ICacheSupervisor + { + string CacheFolder { get; } + + bool IsSaving { get; } + + void CouldNotSaveToRemote(Uri requestedUri, Exception ex); + + void CouldNotOpenFromRemote(Uri requestedUri, Exception ex); + + void OpenWithConflict(Uri requestedUri); + + void UpdatedRemoteOnLoad(Uri requestedUri); + + } +} \ No newline at end of file diff --git a/KeeAnywhere/StorageProviders/AmazonDrive/AmazonDriveStorageProvider.cs b/KeeAnywhere/StorageProviders/AmazonDrive/AmazonDriveStorageProvider.cs index ba19e4d..8f46b55 100644 --- a/KeeAnywhere/StorageProviders/AmazonDrive/AmazonDriveStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/AmazonDrive/AmazonDriveStorageProvider.cs @@ -50,7 +50,7 @@ public async Task Load(string path) return stream; } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { var api = await GetApi(); @@ -72,7 +72,8 @@ public async Task Save(Stream stream, string path) node = await api.Files.UploadNew(folder.id, fileName, () => stream); } - return node != null; + if (node == null) + throw new InvalidOperationException("Save to Amazon Drive failed."); } public async Task GetRootItem() diff --git a/KeeAnywhere/StorageProviders/AmazonS3/AmazonS3StorageProvider.cs b/KeeAnywhere/StorageProviders/AmazonS3/AmazonS3StorageProvider.cs index 0096dbf..c7d2217 100644 --- a/KeeAnywhere/StorageProviders/AmazonS3/AmazonS3StorageProvider.cs +++ b/KeeAnywhere/StorageProviders/AmazonS3/AmazonS3StorageProvider.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -39,7 +40,7 @@ public async Task Load(string path) } } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { using (var api = AmazonS3Helper.GetApi(_account)) { @@ -59,8 +60,8 @@ public async Task Save(Stream stream, string path) } catch (AmazonS3Exception ex) { - if (ex.StatusCode == System.Net.HttpStatusCode.NotFound) - return false; + //if (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + // throw new FileNotFoundException("Amazon S3: File not found.", ); //status wasn't not found, so throw the exception throw; @@ -75,7 +76,9 @@ public async Task Save(Stream stream, string path) var response = await api.PutObjectAsync(request); - return response != null; + if (response == null) + throw new InvalidOperationException("Save to Amazon S3 failed."); + } } diff --git a/KeeAnywhere/StorageProviders/Box/BoxStorageProvider.cs b/KeeAnywhere/StorageProviders/Box/BoxStorageProvider.cs index 68d2ce6..12cc93c 100644 --- a/KeeAnywhere/StorageProviders/Box/BoxStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/Box/BoxStorageProvider.cs @@ -32,7 +32,7 @@ public async Task Load(string path) return stream; } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { var api = await GetApi(); BoxFile item; @@ -62,7 +62,8 @@ public async Task Save(Stream stream, string path) } - return item != null; + if (item == null) + throw new InvalidOperationException("Save to Box failed."); } diff --git a/KeeAnywhere/StorageProviders/Dropbox/DropboxStorageProvider.cs b/KeeAnywhere/StorageProviders/Dropbox/DropboxStorageProvider.cs index 0f0ccb0..bbdda34 100644 --- a/KeeAnywhere/StorageProviders/Dropbox/DropboxStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/Dropbox/DropboxStorageProvider.cs @@ -40,13 +40,14 @@ public async Task Load(string path) return stream; } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { path = RootPath(path); var result = await Api.Files.UploadAsync(path, WriteMode.Overwrite.Instance, body: stream); - return result.IsFile; + if (!result.IsFile) + throw new InvalidOperationException("Save to Dropbox failed."); } public async Task GetRootItem() diff --git a/KeeAnywhere/StorageProviders/GoogleDrive/GoogleDriveStorageProvider.cs b/KeeAnywhere/StorageProviders/GoogleDrive/GoogleDriveStorageProvider.cs index 9d30fe5..7c12aaf 100644 --- a/KeeAnywhere/StorageProviders/GoogleDrive/GoogleDriveStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/GoogleDrive/GoogleDriveStorageProvider.cs @@ -42,7 +42,7 @@ public async Task Load(string path) } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { var api = await GetApi(); @@ -72,7 +72,8 @@ public async Task Save(Stream stream, string path) progress = await api.Files.Create(file, stream, "application/octet-stream").UploadAsync(); } - return progress.Status == UploadStatus.Completed && progress.Exception == null; + if (progress.Status != UploadStatus.Completed || progress.Exception != null) + throw new InvalidOperationException("Save to Google Drive failed."); } public async Task GetRootItem() diff --git a/KeeAnywhere/StorageProviders/HiDrive/HiDriveStorageProvider.cs b/KeeAnywhere/StorageProviders/HiDrive/HiDriveStorageProvider.cs index 9b5ea57..100c634 100644 --- a/KeeAnywhere/StorageProviders/HiDrive/HiDriveStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/HiDrive/HiDriveStorageProvider.cs @@ -30,7 +30,7 @@ public async Task Load(string path) return stream; } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { var api = await GetApi(); var pid = await GetHomeId(); @@ -40,7 +40,8 @@ public async Task Save(Stream stream, string path) var item = await api.File.Upload(filename, pathname, pid).ExecuteAsync(stream); - return item != null; + if (item == null) + throw new InvalidOperationException("Save to HiDrive failed."); } public async Task GetRootItem() diff --git a/KeeAnywhere/StorageProviders/HubiC/HubiCStorageProvider.cs b/KeeAnywhere/StorageProviders/HubiC/HubiCStorageProvider.cs index f1beb15..3b50db4 100644 --- a/KeeAnywhere/StorageProviders/HubiC/HubiCStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/HubiC/HubiCStorageProvider.cs @@ -33,7 +33,7 @@ public async Task Load(string path) return stream; } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { if (stream == null) throw new ArgumentNullException("stream"); if (path == null) throw new ArgumentNullException("path"); @@ -50,7 +50,8 @@ public async Task Save(Stream stream, string path) var isOk = await client.UploadObject(container, normalizedPath, stream); - return isOk; + if (!isOk) + throw new InvalidOperationException("Save to HubiC failed."); } public async Task GetRootItem() diff --git a/KeeAnywhere/StorageProviders/IStorageProvider.cs b/KeeAnywhere/StorageProviders/IStorageProvider.cs index 70edb62..ffad594 100644 --- a/KeeAnywhere/StorageProviders/IStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/IStorageProvider.cs @@ -8,7 +8,7 @@ public interface IStorageProvider { // File operations Task Load(string path); - Task Save(Stream stream, string path); + Task Save(Stream stream, string path); // Query operations Task GetRootItem(); diff --git a/KeeAnywhere/StorageProviders/OneDrive/OneDriveStorageProvider.cs b/KeeAnywhere/StorageProviders/OneDrive/OneDriveStorageProvider.cs index a484561..476888a 100644 --- a/KeeAnywhere/StorageProviders/OneDrive/OneDriveStorageProvider.cs +++ b/KeeAnywhere/StorageProviders/OneDrive/OneDriveStorageProvider.cs @@ -34,7 +34,7 @@ public async Task Load(string path) } - public async Task Save(Stream stream, string path) + public async Task Save(Stream stream, string path) { var api = await OneDriveHelper.GetApi(_account); @@ -47,7 +47,9 @@ public async Task Save(Stream stream, string path) .Request() .PutAsync(stream); - return uploadedItem != null; + if (uploadedItem == null) + throw new InvalidOperationException("Save to OneDrive failed."); + } public async Task GetRootItem() diff --git a/KeeAnywhere/StorageProviders/StorageService.cs b/KeeAnywhere/StorageProviders/StorageService.cs index 832deee..9b72442 100644 --- a/KeeAnywhere/StorageProviders/StorageService.cs +++ b/KeeAnywhere/StorageProviders/StorageService.cs @@ -1,20 +1,28 @@ using System; using System.Linq; +using System.Net; using System.Threading.Tasks; using KeeAnywhere.Configuration; +using KeeAnywhere.Offline; using KeeAnywhere.WebRequest; +using KeePass.Forms; +using KeePass.Plugins; using KeePassLib.Serialization; namespace KeeAnywhere.StorageProviders { - public class StorageService + public class StorageService: IWebRequestCreate { private readonly ConfigurationService _configService; + private readonly CacheManagerService _cacheManagerService; - public StorageService(ConfigurationService configService) + public StorageService(ConfigurationService configService, CacheManagerService cacheManagerService) { if (configService == null) throw new ArgumentNullException("configService"); + if (cacheManagerService == null) throw new ArgumentNullException("cacheManagerService"); + _configService = configService; + _cacheManagerService = cacheManagerService; } public IStorageProvider GetProviderByUri(StorageUri uri) @@ -67,14 +75,33 @@ public async Task CreateAccount(StorageType type) return account; } + public System.Net.WebRequest Create(Uri uri) + { + var providerUri = new StorageUri(uri); + var provider = this.GetProviderByUri(providerUri); + + if (_configService.PluginConfiguration.IsOfflineCacheEnabled) + { + provider = _cacheManagerService.GetCachedProvider(provider, uri); + } + + var itemPath = providerUri.GetPath(); - public void Register() + return new KeeAnywhereWebRequest(provider, itemPath); + } + + public void RegisterPrefixes() { foreach (var descriptor in StorageRegistry.Descriptors) { FileTransactionEx.Configure(descriptor.Scheme, false); - System.Net.WebRequest.RegisterPrefix(descriptor.Scheme + ":", new KeeAnywhereWebRequestCreator(this)); + System.Net.WebRequest.RegisterPrefix(descriptor.Scheme + ":", this); } } + + + + + } } \ No newline at end of file diff --git a/KeeAnywhere/WebRequest/KeeAnywhereWebRequest.cs b/KeeAnywhere/WebRequest/KeeAnywhereWebRequest.cs index 327ea94..de9466c 100644 --- a/KeeAnywhere/WebRequest/KeeAnywhereWebRequest.cs +++ b/KeeAnywhere/WebRequest/KeeAnywhereWebRequest.cs @@ -77,14 +77,15 @@ public override WebResponse GetResponse() { using (var stream = this._requestStream.GetReadableStream()) { - return await _provider.Save(stream, _itemPath); + await _provider.Save(stream, _itemPath); } }); - if (!isOk.Result) + isOk.Wait(); + if (isOk.IsFaulted) { throw new InvalidOperationException(string.Format("KeeAnywhere: Upload to folder {0} failed", - _itemPath)); + _itemPath), isOk.Exception); } _response = new KeeAnywhereWebResponse(); diff --git a/KeeAnywhere/WebRequest/KeeAnywhereWebRequestCreator.cs b/KeeAnywhere/WebRequest/KeeAnywhereWebRequestCreator.cs deleted file mode 100644 index 3edea09..0000000 --- a/KeeAnywhere/WebRequest/KeeAnywhereWebRequestCreator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Net; -using KeeAnywhere.StorageProviders; - -namespace KeeAnywhere.WebRequest -{ - public sealed class KeeAnywhereWebRequestCreator : IWebRequestCreate - { - private readonly StorageService _storageService; - - public KeeAnywhereWebRequestCreator(StorageService storageService) - { - if (storageService == null) throw new ArgumentNullException("storageService"); - _storageService = storageService; - } - - public System.Net.WebRequest Create(Uri uri) - { - var providerUri = new StorageUri(uri); - var provider = _storageService.GetProviderByUri(providerUri); - var itemPath = providerUri.GetPath(); - - return new KeeAnywhereWebRequest(provider, itemPath); - } - - } -} From 326acb9bb3611c4576efc82acb51132d595ca302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=B6lts?= Date: Mon, 17 Oct 2016 17:21:48 +0200 Subject: [PATCH 2/2] Implements #61 Simple Automatic Backup (Remote and/or Local) --- CHANGELOG.md | 1 + .../Configuration/ConfigurationService.cs | 8 + .../Configuration/PluginConfiguration.cs | 33 +++ KeeAnywhere/Forms/ChangelogForm.Designer.cs | 110 +++++++++ KeeAnywhere/Forms/ChangelogForm.cs | 69 ++++++ KeeAnywhere/Forms/ChangelogForm.resx | 120 ++++++++++ KeeAnywhere/Forms/SettingsForm.Designer.cs | 222 ++++++++++++++---- KeeAnywhere/Forms/SettingsForm.cs | 50 +++- KeeAnywhere/Forms/SettingsForm.resx | 3 + KeeAnywhere/KeeAnywhere.csproj | 16 ++ KeeAnywhere/KeeAnywhereExt.cs | 10 +- KeeAnywhere/UIService.cs | 20 ++ KeeAnywhere/packages.config | 1 + 13 files changed, 604 insertions(+), 59 deletions(-) create mode 100644 KeeAnywhere/Forms/ChangelogForm.Designer.cs create mode 100644 KeeAnywhere/Forms/ChangelogForm.cs create mode 100644 KeeAnywhere/Forms/ChangelogForm.resx diff --git a/CHANGELOG.md b/CHANGELOG.md index ce475d0..1ecb7d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### New - \#5 Cache databases for disconnected (offline) usage +- \#61 Simple Automatic Backup (Remote and/or Local) ### Fixed diff --git a/KeeAnywhere/Configuration/ConfigurationService.cs b/KeeAnywhere/Configuration/ConfigurationService.cs index 160175d..009b859 100644 --- a/KeeAnywhere/Configuration/ConfigurationService.cs +++ b/KeeAnywhere/Configuration/ConfigurationService.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using KeeAnywhere.StorageProviders; +using KeePass.App.Configuration; using KeePass.Plugins; using KeePassLib.Utility; using Newtonsoft.Json; @@ -150,6 +152,12 @@ private void LoadPluginConfiguration() { this.PluginConfiguration = new PluginConfiguration(); } + + if (string.IsNullOrEmpty(this.PluginConfiguration.BackupToLocalFolder)) + { + this.PluginConfiguration.BackupToLocalFolder = Path.Combine(AppConfigSerializer.LocalAppDataDirectory, + "KeeAnywhereBackups"); + } } private void SavePluginConfiguration() diff --git a/KeeAnywhere/Configuration/PluginConfiguration.cs b/KeeAnywhere/Configuration/PluginConfiguration.cs index bb08cc7..3f65666 100644 --- a/KeeAnywhere/Configuration/PluginConfiguration.cs +++ b/KeeAnywhere/Configuration/PluginConfiguration.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.IO; using System.Runtime.Serialization; using KeePass.App.Configuration; @@ -10,6 +11,8 @@ namespace KeeAnywhere.Configuration [DataContract] public class PluginConfiguration { + private string _backupToLocalFolder; + [DataMember] public bool IsOfflineCacheEnabled { get; set; } @@ -21,6 +24,36 @@ public string OfflineCacheFolder } } + [DataMember] + public bool IsBackupToLocalEnabled { get; set; } + + [DataMember] + public bool IsBackupToRemoteEnabled { get; set; } + + [DataMember] + public string BackupToLocalFolder + { + get { return _backupToLocalFolder; } + set + { + _backupToLocalFolder = value; + if (this.IsBackupToLocalEnabled && !string.IsNullOrEmpty(_backupToLocalFolder) && !Directory.Exists(_backupToLocalFolder)) + { + try + { + Directory.CreateDirectory(_backupToLocalFolder); + } + catch (Exception) + { } + } + } + } + + [DataMember] + [DefaultValue(10)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int BackupCopies { get; set; } + [DataMember] [JsonConverter(typeof(StringEnumConverter))] public AccountStorageLocation AccountStorageLocation { get; set; } diff --git a/KeeAnywhere/Forms/ChangelogForm.Designer.cs b/KeeAnywhere/Forms/ChangelogForm.Designer.cs new file mode 100644 index 0000000..f67de93 --- /dev/null +++ b/KeeAnywhere/Forms/ChangelogForm.Designer.cs @@ -0,0 +1,110 @@ +namespace KeeAnywhere.Forms +{ + partial class ChangelogForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.m_bannerImage = new System.Windows.Forms.PictureBox(); + this.m_btnClose = new System.Windows.Forms.Button(); + this.m_btnOpenSettings = new System.Windows.Forms.Button(); + this.m_browser = new System.Windows.Forms.WebBrowser(); + ((System.ComponentModel.ISupportInitialize)(this.m_bannerImage)).BeginInit(); + this.SuspendLayout(); + // + // m_bannerImage + // + this.m_bannerImage.Dock = System.Windows.Forms.DockStyle.Top; + this.m_bannerImage.Location = new System.Drawing.Point(0, 0); + this.m_bannerImage.Name = "m_bannerImage"; + this.m_bannerImage.Size = new System.Drawing.Size(577, 60); + this.m_bannerImage.TabIndex = 5; + this.m_bannerImage.TabStop = false; + // + // m_btnClose + // + this.m_btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.m_btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.m_btnClose.Location = new System.Drawing.Point(490, 522); + this.m_btnClose.Name = "m_btnClose"; + this.m_btnClose.Size = new System.Drawing.Size(75, 23); + this.m_btnClose.TabIndex = 9; + this.m_btnClose.Text = "&Close"; + this.m_btnClose.UseVisualStyleBackColor = true; + // + // m_btnOpenSettings + // + this.m_btnOpenSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.m_btnOpenSettings.DialogResult = System.Windows.Forms.DialogResult.Yes; + this.m_btnOpenSettings.Location = new System.Drawing.Point(333, 522); + this.m_btnOpenSettings.Name = "m_btnOpenSettings"; + this.m_btnOpenSettings.Size = new System.Drawing.Size(151, 23); + this.m_btnOpenSettings.TabIndex = 10; + this.m_btnOpenSettings.Text = "Open &Setting now"; + this.m_btnOpenSettings.UseVisualStyleBackColor = true; + // + // m_browser + // + this.m_browser.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.m_browser.Location = new System.Drawing.Point(13, 66); + this.m_browser.MinimumSize = new System.Drawing.Size(20, 20); + this.m_browser.Name = "m_browser"; + this.m_browser.Size = new System.Drawing.Size(552, 450); + this.m_browser.TabIndex = 14; + // + // ChangelogForm + // + this.AcceptButton = this.m_btnOpenSettings; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.m_btnClose; + this.ClientSize = new System.Drawing.Size(577, 557); + this.Controls.Add(this.m_browser); + this.Controls.Add(this.m_btnOpenSettings); + this.Controls.Add(this.m_btnClose); + this.Controls.Add(this.m_bannerImage); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ChangelogForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "KeeAnywhere Upgraded"; + this.Load += new System.EventHandler(this.OnFormLoad); + ((System.ComponentModel.ISupportInitialize)(this.m_bannerImage)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.PictureBox m_bannerImage; + private System.Windows.Forms.Button m_btnClose; + private System.Windows.Forms.Button m_btnOpenSettings; + private System.Windows.Forms.WebBrowser m_browser; + } +} \ No newline at end of file diff --git a/KeeAnywhere/Forms/ChangelogForm.cs b/KeeAnywhere/Forms/ChangelogForm.cs new file mode 100644 index 0000000..ea9efbd --- /dev/null +++ b/KeeAnywhere/Forms/ChangelogForm.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using KeePass.UI; + +namespace KeeAnywhere.Forms +{ + public partial class ChangelogForm : Form + { + private bool _isUpgraded; + + public ChangelogForm() + { + InitializeComponent(); + } + + public void InitEx(bool isUpgraded) + { + _isUpgraded = isUpgraded; + } + + private void OnFormLoad(object sender, EventArgs e) + { + GlobalWindowManager.AddWindow(this); + + this.Icon = PluginResources.Icon_OneDrive_16x16; + + if (_isUpgraded) + { + this.Text = "KeeAnywhere upgraded"; + BannerFactory.CreateBannerEx(this, m_bannerImage, + PluginResources.KeeAnywhere_48x48, "KeeAnywhere has been upgraded", + "Please check changelog and adjust your settings if needed."); + } + else + { + this.Text = "KeeAnywhere Changelog"; + BannerFactory.CreateBannerEx(this, m_bannerImage, + PluginResources.KeeAnywhere_48x48, "KeeAnywhere Changelog", + "See detailed changes for each version."); + } + + var outStream = new MemoryStream(); + var assembly = Assembly.GetExecutingAssembly(); + using (var inReader = new StreamReader(assembly.GetManifestResourceStream("KeeAnywhere.CHANGELOG.md"))) + { + + var outWriter = new StreamWriter(outStream); + + CommonMark.CommonMarkConverter.Convert(inReader, outWriter); + outWriter.Flush(); + outStream.Position = 0; + + m_browser.DocumentStream = outStream; + } + + m_btnOpenSettings.Visible = _isUpgraded; + } + } +} diff --git a/KeeAnywhere/Forms/ChangelogForm.resx b/KeeAnywhere/Forms/ChangelogForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/KeeAnywhere/Forms/ChangelogForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/KeeAnywhere/Forms/SettingsForm.Designer.cs b/KeeAnywhere/Forms/SettingsForm.Designer.cs index 56e78ee..db6abce 100644 --- a/KeeAnywhere/Forms/SettingsForm.Designer.cs +++ b/KeeAnywhere/Forms/SettingsForm.Designer.cs @@ -34,6 +34,7 @@ private void InitializeComponent() this.m_tcSettings = new System.Windows.Forms.TabControl(); this.m_tabAccounts = new System.Windows.Forms.TabPage(); this.m_btnAccountCheck = new System.Windows.Forms.Button(); + this.m_btnAccountAdd = new KeeAnywhere.Forms.DropDownButton(); this.m_mnuAdd = new System.Windows.Forms.ContextMenuStrip(this.components); this.m_btnAccountRemove = new System.Windows.Forms.Button(); this.m_lvAccounts = new System.Windows.Forms.ListView(); @@ -43,6 +44,17 @@ private void InitializeComponent() this.m_rbStorageLocation_LocalUserSecureStore = new System.Windows.Forms.RadioButton(); this.lblAccountStorageLocation = new System.Windows.Forms.Label(); this.m_tabGeneral = new System.Windows.Forms.TabPage(); + this.m_gbBackup = new System.Windows.Forms.GroupBox(); + this.m_btnBackupToLocalFolder = new System.Windows.Forms.Button(); + this.m_numUpDownBackupCopies = new System.Windows.Forms.NumericUpDown(); + this.m_lblBackupCopies = new System.Windows.Forms.Label(); + this.m_txtBackupToLocalFolder = new System.Windows.Forms.TextBox(); + this.m_lblBackupToLocalFolder = new System.Windows.Forms.Label(); + this.m_chkBackupToLocal = new System.Windows.Forms.CheckBox(); + this.m_chkBackupToRemote = new System.Windows.Forms.CheckBox(); + this.m_gbOfflineCache = new System.Windows.Forms.GroupBox(); + this.m_btnOpenCacheFolder = new System.Windows.Forms.Button(); + this.m_btnClearCache = new System.Windows.Forms.Button(); this.m_chkOfflineCache = new System.Windows.Forms.CheckBox(); this.m_tabAbout = new System.Windows.Forms.TabPage(); this.m_lblAboutVersion = new System.Windows.Forms.Label(); @@ -61,19 +73,18 @@ private void InitializeComponent() this.m_mnuHelp_Sep2 = new System.Windows.Forms.ToolStripSeparator(); this.m_mnuHelp_Donate = new System.Windows.Forms.ToolStripMenuItem(); this.m_pnlFormButtons = new System.Windows.Forms.TableLayoutPanel(); - this.m_gbOfflineCache = new System.Windows.Forms.GroupBox(); - this.m_btnClearCache = new System.Windows.Forms.Button(); - this.m_btnOpenCacheFolder = new System.Windows.Forms.Button(); this.m_btnHelp = new KeeAnywhere.Forms.DropDownButton(); - this.m_btnAccountAdd = new KeeAnywhere.Forms.DropDownButton(); + this.m_dlgSelectBackupToLocalFolder = new System.Windows.Forms.FolderBrowserDialog(); ((System.ComponentModel.ISupportInitialize)(this.m_bannerImage)).BeginInit(); this.m_tcSettings.SuspendLayout(); this.m_tabAccounts.SuspendLayout(); this.m_tabGeneral.SuspendLayout(); + this.m_gbBackup.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.m_numUpDownBackupCopies)).BeginInit(); + this.m_gbOfflineCache.SuspendLayout(); this.m_tabAbout.SuspendLayout(); this.m_mnuHelp.SuspendLayout(); this.m_pnlFormButtons.SuspendLayout(); - this.m_gbOfflineCache.SuspendLayout(); this.SuspendLayout(); // // m_bannerImage @@ -128,6 +139,17 @@ private void InitializeComponent() this.m_btnAccountCheck.UseVisualStyleBackColor = true; this.m_btnAccountCheck.Click += new System.EventHandler(this.OnAccountCheck); // + // m_btnAccountAdd + // + this.m_btnAccountAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.m_btnAccountAdd.Location = new System.Drawing.Point(490, 38); + this.m_btnAccountAdd.Menu = this.m_mnuAdd; + this.m_btnAccountAdd.Name = "m_btnAccountAdd"; + this.m_btnAccountAdd.Size = new System.Drawing.Size(75, 23); + this.m_btnAccountAdd.TabIndex = 11; + this.m_btnAccountAdd.Text = "Add..."; + this.m_btnAccountAdd.UseVisualStyleBackColor = true; + // // m_mnuAdd // this.m_mnuAdd.Name = "m_mnuAdd"; @@ -213,6 +235,7 @@ private void InitializeComponent() // // m_tabGeneral // + this.m_tabGeneral.Controls.Add(this.m_gbBackup); this.m_tabGeneral.Controls.Add(this.m_gbOfflineCache); this.m_tabGeneral.Location = new System.Drawing.Point(4, 22); this.m_tabGeneral.Name = "m_tabGeneral"; @@ -222,10 +245,136 @@ private void InitializeComponent() this.m_tabGeneral.Text = "General"; this.m_tabGeneral.UseVisualStyleBackColor = true; // + // m_gbBackup + // + this.m_gbBackup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.m_gbBackup.Controls.Add(this.m_btnBackupToLocalFolder); + this.m_gbBackup.Controls.Add(this.m_numUpDownBackupCopies); + this.m_gbBackup.Controls.Add(this.m_lblBackupCopies); + this.m_gbBackup.Controls.Add(this.m_txtBackupToLocalFolder); + this.m_gbBackup.Controls.Add(this.m_lblBackupToLocalFolder); + this.m_gbBackup.Controls.Add(this.m_chkBackupToLocal); + this.m_gbBackup.Controls.Add(this.m_chkBackupToRemote); + this.m_gbBackup.Location = new System.Drawing.Point(6, 96); + this.m_gbBackup.Name = "m_gbBackup"; + this.m_gbBackup.Size = new System.Drawing.Size(578, 159); + this.m_gbBackup.TabIndex = 2; + this.m_gbBackup.TabStop = false; + this.m_gbBackup.Text = "Automatic Backup"; + // + // m_btnBackupToLocalFolder + // + this.m_btnBackupToLocalFolder.Location = new System.Drawing.Point(400, 76); + this.m_btnBackupToLocalFolder.Name = "m_btnBackupToLocalFolder"; + this.m_btnBackupToLocalFolder.Size = new System.Drawing.Size(25, 23); + this.m_btnBackupToLocalFolder.TabIndex = 6; + this.m_btnBackupToLocalFolder.Text = "..."; + this.m_btnBackupToLocalFolder.UseVisualStyleBackColor = true; + this.m_btnBackupToLocalFolder.Click += new System.EventHandler(this.OnSelectBackupToLocalFolder); + // + // m_numUpDownBackupCopies + // + this.m_numUpDownBackupCopies.Location = new System.Drawing.Point(145, 104); + this.m_numUpDownBackupCopies.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.m_numUpDownBackupCopies.Name = "m_numUpDownBackupCopies"; + this.m_numUpDownBackupCopies.Size = new System.Drawing.Size(48, 20); + this.m_numUpDownBackupCopies.TabIndex = 5; + this.m_numUpDownBackupCopies.Value = new decimal(new int[] { + 10, + 0, + 0, + 0}); + // + // m_lblBackupCopies + // + this.m_lblBackupCopies.AutoSize = true; + this.m_lblBackupCopies.Location = new System.Drawing.Point(6, 106); + this.m_lblBackupCopies.Name = "m_lblBackupCopies"; + this.m_lblBackupCopies.Size = new System.Drawing.Size(133, 13); + this.m_lblBackupCopies.TabIndex = 4; + this.m_lblBackupCopies.Text = "Number of Copies to keep:"; + // + // m_txtBackupToLocalFolder + // + this.m_txtBackupToLocalFolder.Location = new System.Drawing.Point(145, 78); + this.m_txtBackupToLocalFolder.Name = "m_txtBackupToLocalFolder"; + this.m_txtBackupToLocalFolder.Size = new System.Drawing.Size(249, 20); + this.m_txtBackupToLocalFolder.TabIndex = 3; + // + // m_lblBackupToLocalFolder + // + this.m_lblBackupToLocalFolder.AutoSize = true; + this.m_lblBackupToLocalFolder.Location = new System.Drawing.Point(6, 81); + this.m_lblBackupToLocalFolder.Name = "m_lblBackupToLocalFolder"; + this.m_lblBackupToLocalFolder.Size = new System.Drawing.Size(108, 13); + this.m_lblBackupToLocalFolder.TabIndex = 2; + this.m_lblBackupToLocalFolder.Text = "Local Backup Folder:"; + // + // m_chkBackupToLocal + // + this.m_chkBackupToLocal.AutoSize = true; + this.m_chkBackupToLocal.Location = new System.Drawing.Point(9, 51); + this.m_chkBackupToLocal.Name = "m_chkBackupToLocal"; + this.m_chkBackupToLocal.Size = new System.Drawing.Size(104, 17); + this.m_chkBackupToLocal.TabIndex = 1; + this.m_chkBackupToLocal.Text = "Backup to Local"; + this.m_chkBackupToLocal.UseVisualStyleBackColor = true; + this.m_chkBackupToLocal.CheckedChanged += new System.EventHandler(this.OnBackupChanged); + // + // m_chkBackupToRemote + // + this.m_chkBackupToRemote.AutoSize = true; + this.m_chkBackupToRemote.Location = new System.Drawing.Point(9, 28); + this.m_chkBackupToRemote.Name = "m_chkBackupToRemote"; + this.m_chkBackupToRemote.Size = new System.Drawing.Size(115, 17); + this.m_chkBackupToRemote.TabIndex = 0; + this.m_chkBackupToRemote.Text = "Backup to Remote"; + this.m_chkBackupToRemote.UseVisualStyleBackColor = true; + this.m_chkBackupToRemote.CheckedChanged += new System.EventHandler(this.OnBackupChanged); + // + // m_gbOfflineCache + // + this.m_gbOfflineCache.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.m_gbOfflineCache.Controls.Add(this.m_btnOpenCacheFolder); + this.m_gbOfflineCache.Controls.Add(this.m_btnClearCache); + this.m_gbOfflineCache.Controls.Add(this.m_chkOfflineCache); + this.m_gbOfflineCache.Location = new System.Drawing.Point(6, 6); + this.m_gbOfflineCache.Name = "m_gbOfflineCache"; + this.m_gbOfflineCache.Size = new System.Drawing.Size(578, 84); + this.m_gbOfflineCache.TabIndex = 1; + this.m_gbOfflineCache.TabStop = false; + this.m_gbOfflineCache.Text = "Offline Cache"; + // + // m_btnOpenCacheFolder + // + this.m_btnOpenCacheFolder.Location = new System.Drawing.Point(90, 43); + this.m_btnOpenCacheFolder.Name = "m_btnOpenCacheFolder"; + this.m_btnOpenCacheFolder.Size = new System.Drawing.Size(182, 23); + this.m_btnOpenCacheFolder.TabIndex = 2; + this.m_btnOpenCacheFolder.Text = "Open Cache Folder in Explorer"; + this.m_btnOpenCacheFolder.UseVisualStyleBackColor = true; + this.m_btnOpenCacheFolder.Click += new System.EventHandler(this.OnOpenCacheFolder); + // + // m_btnClearCache + // + this.m_btnClearCache.Location = new System.Drawing.Point(9, 43); + this.m_btnClearCache.Name = "m_btnClearCache"; + this.m_btnClearCache.Size = new System.Drawing.Size(75, 23); + this.m_btnClearCache.TabIndex = 1; + this.m_btnClearCache.Text = "Clear Cache"; + this.m_btnClearCache.UseVisualStyleBackColor = true; + this.m_btnClearCache.Click += new System.EventHandler(this.OnClearCache); + // // m_chkOfflineCache // this.m_chkOfflineCache.AutoSize = true; - this.m_chkOfflineCache.Location = new System.Drawing.Point(6, 19); + this.m_chkOfflineCache.Location = new System.Drawing.Point(9, 20); this.m_chkOfflineCache.Name = "m_chkOfflineCache"; this.m_chkOfflineCache.Size = new System.Drawing.Size(189, 17); this.m_chkOfflineCache.TabIndex = 0; @@ -399,40 +548,6 @@ private void InitializeComponent() this.m_pnlFormButtons.Size = new System.Drawing.Size(604, 29); this.m_pnlFormButtons.TabIndex = 9; // - // m_gbOfflineCache - // - this.m_gbOfflineCache.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.m_gbOfflineCache.Controls.Add(this.m_btnOpenCacheFolder); - this.m_gbOfflineCache.Controls.Add(this.m_btnClearCache); - this.m_gbOfflineCache.Controls.Add(this.m_chkOfflineCache); - this.m_gbOfflineCache.Location = new System.Drawing.Point(6, 6); - this.m_gbOfflineCache.Name = "m_gbOfflineCache"; - this.m_gbOfflineCache.Size = new System.Drawing.Size(578, 84); - this.m_gbOfflineCache.TabIndex = 1; - this.m_gbOfflineCache.TabStop = false; - this.m_gbOfflineCache.Text = "Offline Cache"; - // - // m_btnClearCache - // - this.m_btnClearCache.Location = new System.Drawing.Point(6, 43); - this.m_btnClearCache.Name = "m_btnClearCache"; - this.m_btnClearCache.Size = new System.Drawing.Size(75, 23); - this.m_btnClearCache.TabIndex = 1; - this.m_btnClearCache.Text = "Clear Cache"; - this.m_btnClearCache.UseVisualStyleBackColor = true; - this.m_btnClearCache.Click += new System.EventHandler(this.OnClearCache); - // - // m_btnOpenCacheFolder - // - this.m_btnOpenCacheFolder.Location = new System.Drawing.Point(88, 43); - this.m_btnOpenCacheFolder.Name = "m_btnOpenCacheFolder"; - this.m_btnOpenCacheFolder.Size = new System.Drawing.Size(182, 23); - this.m_btnOpenCacheFolder.TabIndex = 2; - this.m_btnOpenCacheFolder.Text = "Open Cache Folder in Explorer"; - this.m_btnOpenCacheFolder.UseVisualStyleBackColor = true; - this.m_btnOpenCacheFolder.Click += new System.EventHandler(this.OnOpenCacheFolder); - // // m_btnHelp // this.m_btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); @@ -444,16 +559,9 @@ private void InitializeComponent() this.m_btnHelp.Text = "Help..."; this.m_btnHelp.UseVisualStyleBackColor = true; // - // m_btnAccountAdd + // m_dlgSelectBackupToLocalFolder // - this.m_btnAccountAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.m_btnAccountAdd.Location = new System.Drawing.Point(490, 38); - this.m_btnAccountAdd.Menu = this.m_mnuAdd; - this.m_btnAccountAdd.Name = "m_btnAccountAdd"; - this.m_btnAccountAdd.Size = new System.Drawing.Size(75, 23); - this.m_btnAccountAdd.TabIndex = 11; - this.m_btnAccountAdd.Text = "Add..."; - this.m_btnAccountAdd.UseVisualStyleBackColor = true; + this.m_dlgSelectBackupToLocalFolder.Description = "Pleae select your local folder for backups."; // // SettingsForm // @@ -479,11 +587,14 @@ private void InitializeComponent() this.m_tabAccounts.ResumeLayout(false); this.m_tabAccounts.PerformLayout(); this.m_tabGeneral.ResumeLayout(false); + this.m_gbBackup.ResumeLayout(false); + this.m_gbBackup.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.m_numUpDownBackupCopies)).EndInit(); + this.m_gbOfflineCache.ResumeLayout(false); + this.m_gbOfflineCache.PerformLayout(); this.m_tabAbout.ResumeLayout(false); this.m_mnuHelp.ResumeLayout(false); this.m_pnlFormButtons.ResumeLayout(false); - this.m_gbOfflineCache.ResumeLayout(false); - this.m_gbOfflineCache.PerformLayout(); this.ResumeLayout(false); } @@ -526,5 +637,14 @@ private void InitializeComponent() private System.Windows.Forms.GroupBox m_gbOfflineCache; private System.Windows.Forms.Button m_btnOpenCacheFolder; private System.Windows.Forms.Button m_btnClearCache; + private System.Windows.Forms.GroupBox m_gbBackup; + private System.Windows.Forms.NumericUpDown m_numUpDownBackupCopies; + private System.Windows.Forms.Label m_lblBackupCopies; + private System.Windows.Forms.TextBox m_txtBackupToLocalFolder; + private System.Windows.Forms.Label m_lblBackupToLocalFolder; + private System.Windows.Forms.CheckBox m_chkBackupToLocal; + private System.Windows.Forms.CheckBox m_chkBackupToRemote; + private System.Windows.Forms.Button m_btnBackupToLocalFolder; + private System.Windows.Forms.FolderBrowserDialog m_dlgSelectBackupToLocalFolder; } } \ No newline at end of file diff --git a/KeeAnywhere/Forms/SettingsForm.cs b/KeeAnywhere/Forms/SettingsForm.cs index 91c99ad..760d5f2 100644 --- a/KeeAnywhere/Forms/SettingsForm.cs +++ b/KeeAnywhere/Forms/SettingsForm.cs @@ -39,7 +39,13 @@ private void OnBtnCancelClick(object sender, EventArgs e) private void OnBtnOkClick(object sender, EventArgs e) { // General Settings - m_configService.PluginConfiguration.IsOfflineCacheEnabled = m_chkOfflineCache.Checked; + var cfg = m_configService.PluginConfiguration; + cfg.IsOfflineCacheEnabled = m_chkOfflineCache.Checked; + cfg.IsBackupToRemoteEnabled = m_chkBackupToRemote.Checked; + cfg.IsBackupToLocalEnabled = m_chkBackupToLocal.Checked; + cfg.BackupToLocalFolder = m_txtBackupToLocalFolder.Text; + cfg.BackupCopies = (int)m_numUpDownBackupCopies.Value; + if (m_rbStorageLocation_LocalUserSecureStore.Checked) m_configService.PluginConfiguration.AccountStorageLocation = AccountStorageLocation.LocalUserSecureStore; @@ -169,8 +175,17 @@ private void UpdateAccountList() private void InitGeneralTab() { - m_chkOfflineCache.Checked = m_configService.PluginConfiguration.IsOfflineCacheEnabled; + var cfg = m_configService.PluginConfiguration; + + m_chkOfflineCache.Checked = cfg.IsOfflineCacheEnabled; + + m_chkBackupToLocal.Checked = cfg.IsBackupToLocalEnabled; + m_chkBackupToRemote.Checked = cfg.IsBackupToRemoteEnabled; + m_txtBackupToLocalFolder.Text = cfg.BackupToLocalFolder; + m_numUpDownBackupCopies.Value = cfg.BackupCopies; + UpdateCacheButtonState(); + UpdateBackupState(); } private async void OnAccountAdd(object sender, EventArgs e) @@ -197,7 +212,7 @@ private void OnAccountRemove(object sender, EventArgs e) private void OnWhatsNew(object sender, EventArgs e) { - Process.Start("https://github.com/Kyrodan/KeeAnywhere/blob/master/CHANGELOG.md"); + m_uiService.ShowChangelogDialog(false); } private void OnReportBug(object sender, EventArgs e) @@ -353,5 +368,34 @@ private void UpdateCacheButtonState() m_btnOpenCacheFolder.Enabled = isEnabled; m_btnClearCache.Enabled = isEnabled; } + + private void UpdateBackupState() + { + var isEnabled = m_chkBackupToLocal.Checked; + m_lblBackupToLocalFolder.Enabled = isEnabled; + m_txtBackupToLocalFolder.Enabled = isEnabled; + m_btnBackupToLocalFolder.Enabled = isEnabled; + + m_lblBackupCopies.Enabled = m_chkBackupToLocal.Checked || m_chkBackupToRemote.Checked; + m_numUpDownBackupCopies.Enabled = m_chkBackupToLocal.Checked || m_chkBackupToRemote.Checked; + + } + + private void OnBackupChanged(object sender, EventArgs e) + { + UpdateBackupState(); + } + + private void OnSelectBackupToLocalFolder(object sender, EventArgs e) + { + var dlg = m_dlgSelectBackupToLocalFolder; + dlg.SelectedPath = m_txtBackupToLocalFolder.Text; + + var result = dlg.ShowDialog(this); + if (result == DialogResult.OK) + { + m_txtBackupToLocalFolder.Text = dlg.SelectedPath; + } + } } } diff --git a/KeeAnywhere/Forms/SettingsForm.resx b/KeeAnywhere/Forms/SettingsForm.resx index 6326077..68c2500 100644 --- a/KeeAnywhere/Forms/SettingsForm.resx +++ b/KeeAnywhere/Forms/SettingsForm.resx @@ -132,6 +132,9 @@ Daniel Bölts 289, 17 + + 406, 17 + 101 diff --git a/KeeAnywhere/KeeAnywhere.csproj b/KeeAnywhere/KeeAnywhere.csproj index 4f53d07..a19d0e6 100644 --- a/KeeAnywhere/KeeAnywhere.csproj +++ b/KeeAnywhere/KeeAnywhere.csproj @@ -55,6 +55,10 @@ ..\packages\Box.V2.2.11.0\lib\portable-net40+sl50+win+wp80\Box.V2.dll True + + ..\packages\CommonMark.NET.0.14.0\lib\net45\CommonMark.dll + True + ..\packages\CredentialManagement.1.0.2\lib\net35\CredentialManagement.dll True @@ -199,6 +203,12 @@ CloudDriveFilePicker.cs + + Form + + + ChangelogForm.cs + Form @@ -310,6 +320,9 @@ CloudDriveFilePicker.cs Designer + + ChangelogForm.cs + DonationForm.cs @@ -333,6 +346,9 @@ + + CHANGELOG.md + Designer diff --git a/KeeAnywhere/KeeAnywhereExt.cs b/KeeAnywhere/KeeAnywhereExt.cs index b4499c9..f3092ac 100644 --- a/KeeAnywhere/KeeAnywhereExt.cs +++ b/KeeAnywhere/KeeAnywhereExt.cs @@ -113,6 +113,9 @@ public override bool Initialize(IPluginHost pluginHost) } } + if (_configService.IsUpgraded) + _uiService.ShowChangelogDialog(true); + // Indicate that the plugin started successfully return true; } @@ -208,7 +211,7 @@ private bool HasAccounts() if (result == DialogResult.Yes) { - OnShowSetting(this, EventArgs.Empty); + _uiService.ShowSettingsDialog(); } return false; @@ -264,10 +267,7 @@ public override void Terminate() private void OnShowSetting(object sender, EventArgs e) { - _uiService.ShowDonationDialog(); - var form = new SettingsForm(); - form.InitEx(_configService, _uiService); - UIUtil.ShowDialogAndDestroy(form); + _uiService.ShowSettingsDialog(); } private static void FixGoogleApiDependencyLoading() diff --git a/KeeAnywhere/UIService.cs b/KeeAnywhere/UIService.cs index 676e93a..53e47d9 100644 --- a/KeeAnywhere/UIService.cs +++ b/KeeAnywhere/UIService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using System.Windows.Forms; using KeeAnywhere.Configuration; using KeeAnywhere.Forms; using KeeAnywhere.StorageProviders; @@ -109,5 +110,24 @@ public void ShowDonationDialog() _donationDialogAlreadyShownInThisUpgradedSession = true; } + + public void ShowChangelogDialog(bool isUpgraded) + { + var dlg = new ChangelogForm(); + dlg.InitEx(isUpgraded); + + var result = UIUtil.ShowDialogAndDestroy(dlg); + if (result == DialogResult.Yes) + this.ShowSettingsDialog(); + + } + + public void ShowSettingsDialog() + { + this.ShowDonationDialog(); + var form = new SettingsForm(); + form.InitEx(_configService, this); + UIUtil.ShowDialogAndDestroy(form); + } } } \ No newline at end of file diff --git a/KeeAnywhere/packages.config b/KeeAnywhere/packages.config index d50baf3..a7569f2 100644 --- a/KeeAnywhere/packages.config +++ b/KeeAnywhere/packages.config @@ -6,6 +6,7 @@ +