From ee58d7cd1b1309687ce7a2042b56badbbb909b44 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Van Date: Sat, 12 Nov 2011 13:17:18 +0800 Subject: [PATCH] Refactored synchronization code. --- KeePass/Analytics/TrackInfo.cs | 9 +- KeePass/KeePass.csproj | 11 +- KeePass/MainPage.xaml.cs | 3 +- KeePass/Sources/DatabaseUpdater.cs | 56 +---- KeePass/Sources/DropBox/DropBoxAdapter.cs | 124 +++++++++++ KeePass/Sources/DropBox/DropBoxInfo.cs | 17 +- KeePass/Sources/DropBox/DropBoxUpdater.cs | 191 ---------------- KeePass/Sources/IServiceAdapter.cs | 23 ++ KeePass/Sources/ListItem.cs | 10 + KeePass/Sources/ServiceAdapterBase.cs | 46 ++++ KeePass/Sources/SynchronizeErrorEventArgs.cs | 22 ++ .../Sources/SynchronizeErrorEventHandler.cs | 7 + KeePass/Sources/SynchronizeException.cs | 13 ++ KeePass/Sources/Synchronizer.cs | 202 +++++++++++++++++ KeePass/Sources/WebDav/Api/WebDavClient.cs | 8 + KeePass/Sources/WebDav/WebDavAdapter.cs | 121 ++++++++++ KeePass/Sources/WebDav/WebDavUpdater.cs | 208 ------------------ 17 files changed, 614 insertions(+), 457 deletions(-) create mode 100644 KeePass/Sources/DropBox/DropBoxAdapter.cs delete mode 100644 KeePass/Sources/DropBox/DropBoxUpdater.cs create mode 100644 KeePass/Sources/IServiceAdapter.cs create mode 100644 KeePass/Sources/ListItem.cs create mode 100644 KeePass/Sources/ServiceAdapterBase.cs create mode 100644 KeePass/Sources/SynchronizeErrorEventArgs.cs create mode 100644 KeePass/Sources/SynchronizeErrorEventHandler.cs create mode 100644 KeePass/Sources/SynchronizeException.cs create mode 100644 KeePass/Sources/Synchronizer.cs create mode 100644 KeePass/Sources/WebDav/WebDavAdapter.cs delete mode 100644 KeePass/Sources/WebDav/WebDavUpdater.cs diff --git a/KeePass/Analytics/TrackInfo.cs b/KeePass/Analytics/TrackInfo.cs index 1fb09b6..f7d8d26 100644 --- a/KeePass/Analytics/TrackInfo.cs +++ b/KeePass/Analytics/TrackInfo.cs @@ -1,11 +1,10 @@ -using System; +using System; namespace KeePass.Analytics { internal static class TrackInfo { -#warning Your MixPanel token is needed - public const string TOKEN = - "YOUR_TOKEN"; + // Your MixPanel token is needed + public const string TOKEN = "YOUR_TOKEN"; } -} \ No newline at end of file +} diff --git a/KeePass/KeePass.csproj b/KeePass/KeePass.csproj index 543d7fc..07d9a37 100644 --- a/KeePass/KeePass.csproj +++ b/KeePass/KeePass.csproj @@ -243,8 +243,16 @@ Settings.xaml + + + + - + + + + + True True @@ -258,7 +266,6 @@ DropBox.xaml - List.xaml diff --git a/KeePass/MainPage.xaml.cs b/KeePass/MainPage.xaml.cs index 75c3c4f..02c8ccd 100644 --- a/KeePass/MainPage.xaml.cs +++ b/KeePass/MainPage.xaml.cs @@ -213,8 +213,7 @@ private void Update(DatabaseItem item) item.IsUpdating = true; var database = (DatabaseInfo)item.Info; - DatabaseUpdater.Update(database, - _ => item.IsUpdating, + database.Update(_ => item.IsUpdating, DatabaseUpdated); } diff --git a/KeePass/Sources/DatabaseUpdater.cs b/KeePass/Sources/DatabaseUpdater.cs index 169e125..232daae 100644 --- a/KeePass/Sources/DatabaseUpdater.cs +++ b/KeePass/Sources/DatabaseUpdater.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using KeePass.Sources.DropBox; using KeePass.Sources.Web; using KeePass.Sources.WebDav; @@ -10,23 +9,27 @@ namespace KeePass.Sources internal static class DatabaseUpdater { public const string DROPBOX_UPDATER = "DropBox"; - public const string WEB_UPDATER = "Web"; public const string WEBDAV_UPDATER = "WebDAV"; + public const string WEB_UPDATER = "Web"; - public static void Update(DatabaseInfo info, + public static void Update(this DatabaseInfo info, Func queryUpdate, ReportUpdateResult report) { switch (info.Details.Source) { case DROPBOX_UPDATER: - DropBoxUpdater.Update(info, queryUpdate, - x => Report(info, x, report)); + var dropbox = new Synchronizer(info, + new DropBoxAdapter(), queryUpdate); + + dropbox.Synchronize(report); break; case WEBDAV_UPDATER: - WebDavUpdater.Update(info, queryUpdate, - x => Report(info, x, report)); + var webdav = new Synchronizer(info, + new WebDavAdapter(), queryUpdate); + + webdav.Synchronize(report); break; case WEB_UPDATER: @@ -35,44 +38,5 @@ internal static class DatabaseUpdater break; } } - - private static void Report(DatabaseInfo info, - SyncCompleteInfo result, ReportUpdateResult report) - { - string msg = null; - var details = info.Details; - - switch (result.Result) - { - case SyncResults.Downloaded: - using (var buffer = new MemoryStream(result.Database)) - info.SetDatabase(buffer, details); - break; - - case SyncResults.Uploaded: - details.HasLocalChanges = false; - details.Modified = result.Modified; - info.SaveDetails(); - break; - - case SyncResults.Conflict: - details.Url = result.Path; - details.HasLocalChanges = false; - details.Modified = result.Modified; - info.SaveDetails(); - - msg = string.Format( - Properties.Resources.Conflict, - new Uri(result.Path).LocalPath); - break; - - case SyncResults.Failed: - msg = Properties.Resources - .DownloadError; - break; - } - - report(info, result.Result, msg); - } } } \ No newline at end of file diff --git a/KeePass/Sources/DropBox/DropBoxAdapter.cs b/KeePass/Sources/DropBox/DropBoxAdapter.cs new file mode 100644 index 0000000..ea55495 --- /dev/null +++ b/KeePass/Sources/DropBox/DropBoxAdapter.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using DropNet; +using DropNet.Models; +using KeePass.IO.Utils; +using KeePass.Storage; + +namespace KeePass.Sources.DropBox +{ + internal class DropBoxAdapter : ServiceAdapterBase + { + private DropNetClient _client; + private SyncInfo _info; + + public override void Conflict(ListItem item, + Action uploaded) + { + var path = GetNonConflictPath(); + + UploadFileAsync(path, x => uploaded( + Translate(x), _client.GetUrl(path))); + } + + public override void Download(ListItem item, + Action downloaded) + { + _client.GetFileAsync(_info.Path, + x => downloaded(item, x.RawBytes), + OnError); + } + + public override SyncInfo Initialize(DatabaseInfo info) + { + if (info == null) + throw new ArgumentNullException("info"); + + var details = info.Details; + var url = new Uri(details.Url); + _client = CreateClient(url.UserInfo); + + _info = new SyncInfo + { + Path = url.LocalPath, + Modified = details.Modified, + HasLocalChanges = details.HasLocalChanges, + }; + + info.OpenDatabaseFile(x => + { + using (var buffer = new MemoryStream()) + { + BufferEx.CopyStream(x, buffer); + _info.Database = buffer.ToArray(); + } + }); + + return _info; + } + + public override void List(Action ready) + { + _client.GetMetaDataAsync(_info.Path, + meta => ready(Translate(meta)), + OnError); + } + + public override void Upload(ListItem item, + Action uploaded) + { + UploadFileAsync(_info.Path, + meta => uploaded(Translate(meta))); + } + + private static DropNetClient CreateClient(string userInfo) + { + var parts = userInfo.Split(':'); + + return new DropNetClient( + DropBoxInfo.KEY, DropBoxInfo.SECRET, + parts[0], parts[1]); + } + + private string GetNonConflictPath() + { + var path = _info.Path; + var dir = Path.GetDirectoryName(path); + var extension = Path.GetExtension(path); + var fileName = Path.GetFileNameWithoutExtension(path); + + fileName = string.Concat(fileName, + " (7Pass' conflicted copy ", + DateTime.Today.ToString("yyyy-MM-dd"), + ")", extension); + + return Path.Combine(dir, fileName) + .Replace('\\', '/'); + } + + private static ListItem Translate(MetaData meta) + { + return new ListItem + { + Tag = meta, + Timestamp = meta.Modified, + }; + } + + private void UploadFileAsync(string path, + Action completed) + { + var orgPath = path; + + var fileName = Path.GetFileName(path); + path = Path.GetDirectoryName(path) + .Replace('\\', '/'); + + _client.UploadFileAsync(path, + fileName, _info.Database, + x => _client.GetMetaDataAsync( + orgPath, completed, OnError), + OnError); + } + } +} \ No newline at end of file diff --git a/KeePass/Sources/DropBox/DropBoxInfo.cs b/KeePass/Sources/DropBox/DropBoxInfo.cs index ca8e759..af5fe3d 100644 --- a/KeePass/Sources/DropBox/DropBoxInfo.cs +++ b/KeePass/Sources/DropBox/DropBoxInfo.cs @@ -1,11 +1,11 @@ -using System; +using System; using DropNet; namespace KeePass.Sources.DropBox { - internal class DropBoxInfo + internal static class DropBoxInfo { -#warning Your DropBox info is needed + // Your DropBox info is needed public const string KEY = "YOUR_KEY"; public const string SECRET = "YOUR_SECRET"; @@ -20,5 +20,16 @@ public static DropNetClient Create() return new DropNetClient( KEY, SECRET, token, secret); } + + public static string GetUrl( + this DropNetClient client, + string path) + { + var login = client.UserLogin; + + return string.Format( + "dropbox://{0}:{1}@dropbox.com{2}", + login.Token, login.Secret, path); + } } } \ No newline at end of file diff --git a/KeePass/Sources/DropBox/DropBoxUpdater.cs b/KeePass/Sources/DropBox/DropBoxUpdater.cs deleted file mode 100644 index 60b5df1..0000000 --- a/KeePass/Sources/DropBox/DropBoxUpdater.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.IO; -using DropNet; -using DropNet.Exceptions; -using DropNet.Models; -using KeePass.IO.Utils; -using KeePass.Storage; - -namespace KeePass.Sources.DropBox -{ - internal static class DropBoxUpdater - { - public static string GetUrl( - this DropNetClient client, - string path) - { - var login = client.UserLogin; - - return string.Format( - "dropbox://{0}:{1}@dropbox.com{2}", - login.Token, login.Secret, path); - } - - public static void Update(DatabaseInfo info, - Func queryUpdate, - Action report) - { - if (info == null) throw new ArgumentNullException("info"); - if (queryUpdate == null) throw new ArgumentNullException("queryUpdate"); - if (report == null) throw new ArgumentNullException("report"); - - var details = info.Details; - var url = new Uri(details.Url); - var client = CreateClient(url.UserInfo); - - var syncInfo = new SyncInfo - { - Path = url.LocalPath, - Modified = details.Modified, - HasLocalChanges = details.HasLocalChanges, - }; - - info.OpenDatabaseFile(x => - { - using (var buffer = new MemoryStream()) - { - BufferEx.CopyStream(x, buffer); - syncInfo.Database = buffer.ToArray(); - } - }); - - Synchronize(client, syncInfo, x => - { - if (queryUpdate(info)) - report(x); - }); - } - - private static DropNetClient CreateClient(string userInfo) - { - var parts = userInfo.Split(':'); - - return new DropNetClient( - DropBoxInfo.KEY, DropBoxInfo.SECRET, - parts[0], parts[1]); - } - - private static string GetNonConflictPath(string path) - { - var dir = Path.GetDirectoryName(path); - var extension = Path.GetExtension(path); - var fileName = Path.GetFileNameWithoutExtension(path); - - fileName = string.Concat(fileName, - " (7Pass' conflicted copy ", - DateTime.Today.ToString("yyyy-MM-dd"), - ")", extension); - - return Path.Combine(dir, fileName) - .Replace('\\', '/'); - } - - private static void OnFileMetaReady( - DropNetClient client, - SyncInfo info, MetaData meta, - Action report) - { - // TODO: Handle case when file deleted from server - - // No change from server side - if (meta.Modified == info.Modified) - { - if (!info.HasLocalChanges) - { - report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.NoChange, - }); - - return; - } - - // Has local change, upload to server - client.UploadFileAsync(info.Path, - info.Database, - x => report(new SyncCompleteInfo - { - Path = info.Path, - Modified = x.Modified, - Result = SyncResults.Uploaded, - }), - x => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - - return; - } - - // Has changes from server - if (!info.HasLocalChanges) - { - // Database should be updated - client.GetFileAsync(info.Path, - x => report(new SyncCompleteInfo - { - Path = info.Path, - Database = x.RawBytes, - Modified = meta.Modified, - Result = SyncResults.Downloaded, - }), - ex => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - - return; - } - - // Conflict - var path = GetNonConflictPath(info.Path); - - client.UploadFileAsync(path, info.Database, - x => report(new SyncCompleteInfo - { - Modified = x.Modified, - Path = client.GetUrl(path), - Result = SyncResults.Conflict, - }), - x => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - } - - private static void Synchronize(DropNetClient client, - SyncInfo info, Action report) - { - client.GetMetaDataAsync(info.Path, meta => - OnFileMetaReady(client, info, meta, report), - ex => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - } - - private static void UploadFileAsync( - this DropNetClient client, - string path, byte[] fileData, - Action success, - Action failure) - { - var orgPath = path; - - var fileName = Path.GetFileName(path); - path = Path.GetDirectoryName(path) - .Replace('\\', '/'); - - client.UploadFileAsync( - path, fileName, fileData, - x => client.GetMetaDataAsync( - orgPath, success, failure), - failure); - } - } -} \ No newline at end of file diff --git a/KeePass/Sources/IServiceAdapter.cs b/KeePass/Sources/IServiceAdapter.cs new file mode 100644 index 0000000..54b1836 --- /dev/null +++ b/KeePass/Sources/IServiceAdapter.cs @@ -0,0 +1,23 @@ +using System; +using KeePass.Storage; + +namespace KeePass.Sources +{ + internal interface IServiceAdapter + { + event SynchronizeErrorEventHandler Error; + + void Conflict(ListItem item, + Action uploaded); + + void Download(ListItem item, + Action downloaded); + + SyncInfo Initialize(DatabaseInfo info); + + void List(Action ready); + + void Upload(ListItem item, + Action uploaded); + } +} \ No newline at end of file diff --git a/KeePass/Sources/ListItem.cs b/KeePass/Sources/ListItem.cs new file mode 100644 index 0000000..4bd9e7d --- /dev/null +++ b/KeePass/Sources/ListItem.cs @@ -0,0 +1,10 @@ +using System; + +namespace KeePass.Sources +{ + internal class ListItem + { + public object Tag { get; set; } + public string Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/KeePass/Sources/ServiceAdapterBase.cs b/KeePass/Sources/ServiceAdapterBase.cs new file mode 100644 index 0000000..7670451 --- /dev/null +++ b/KeePass/Sources/ServiceAdapterBase.cs @@ -0,0 +1,46 @@ +using System; +using KeePass.Storage; + +namespace KeePass.Sources +{ + internal abstract class ServiceAdapterBase : IServiceAdapter + { + public event SynchronizeErrorEventHandler Error; + + public abstract void Conflict(ListItem item, + Action uploaded); + + public abstract void Download(ListItem item, + Action downloaded); + + public abstract SyncInfo Initialize(DatabaseInfo info); + + public abstract void List(Action ready); + + public abstract void Upload(ListItem item, + Action uploaded); + + /// + /// Raises the event. + /// + /// The + /// instance containing the event data. + protected virtual void OnError(SynchronizeErrorEventArgs e) + { + if (Error != null) + Error(this, e); + } + + protected void OnError() + { + OnError(new SynchronizeErrorEventArgs( + new SynchronizeException())); + } + + protected void OnError(Exception ex) + { + OnError(new SynchronizeErrorEventArgs( + new SynchronizeException("Sync Error", ex))); + } + } +} \ No newline at end of file diff --git a/KeePass/Sources/SynchronizeErrorEventArgs.cs b/KeePass/Sources/SynchronizeErrorEventArgs.cs new file mode 100644 index 0000000..8ff72a9 --- /dev/null +++ b/KeePass/Sources/SynchronizeErrorEventArgs.cs @@ -0,0 +1,22 @@ +using System; + +namespace KeePass.Sources +{ + internal class SynchronizeErrorEventArgs : EventArgs + { + private readonly SynchronizeException _ex; + + public SynchronizeException Exception + { + get { return _ex; } + } + + public SynchronizeErrorEventArgs(SynchronizeException ex) + { + if (ex == null) + throw new ArgumentNullException("ex"); + + _ex = ex; + } + } +} \ No newline at end of file diff --git a/KeePass/Sources/SynchronizeErrorEventHandler.cs b/KeePass/Sources/SynchronizeErrorEventHandler.cs new file mode 100644 index 0000000..fdd047f --- /dev/null +++ b/KeePass/Sources/SynchronizeErrorEventHandler.cs @@ -0,0 +1,7 @@ +using System; + +namespace KeePass.Sources +{ + internal delegate void SynchronizeErrorEventHandler( + object sender, SynchronizeErrorEventArgs e); +} \ No newline at end of file diff --git a/KeePass/Sources/SynchronizeException.cs b/KeePass/Sources/SynchronizeException.cs new file mode 100644 index 0000000..16061a0 --- /dev/null +++ b/KeePass/Sources/SynchronizeException.cs @@ -0,0 +1,13 @@ +using System; + +namespace KeePass.Sources +{ + internal class SynchronizeException : Exception + { + public SynchronizeException() {} + + public SynchronizeException(string message, + Exception innerException) + : base(message, innerException) {} + } +} \ No newline at end of file diff --git a/KeePass/Sources/Synchronizer.cs b/KeePass/Sources/Synchronizer.cs new file mode 100644 index 0000000..4cac519 --- /dev/null +++ b/KeePass/Sources/Synchronizer.cs @@ -0,0 +1,202 @@ +using System; +using System.IO; +using KeePass.Storage; + +namespace KeePass.Sources +{ + internal class Synchronizer + { + private readonly IServiceAdapter _adapter; + private readonly DatabaseInfo _db; + private readonly SyncInfo _info; + private readonly Func _queryUpdate; + + private bool _aborted; + private ReportUpdateResult _reporter; + + private bool Aborted + { + get { return _aborted || !_queryUpdate(_db); } + set { _aborted = value; } + } + + public Synchronizer(DatabaseInfo db, + IServiceAdapter adapter, + Func queryUpdate) + { + if (adapter == null) throw new ArgumentNullException("adapter"); + if (db == null) throw new ArgumentNullException("db"); + if (queryUpdate == null) throw new ArgumentNullException("queryUpdate"); + + _db = db; + _adapter = adapter; + _queryUpdate = queryUpdate; + _info = _adapter.Initialize(db); + + _adapter.Error += _adapter_Error; + } + + public void Synchronize(ReportUpdateResult reporter) + { + if (reporter == null) + throw new ArgumentNullException("reporter"); + + _reporter = reporter; + Try(x => x.List(Listed)); + } + + private void ConflictUploaded( + ListItem item, string path) + { + if (Aborted) + return; + + Report(new SyncCompleteInfo + { + Path = path, + Modified = item.Timestamp, + Result = SyncResults.Conflict, + }); + } + + private void Downloaded(ListItem item, byte[] bytes) + { + if (Aborted) + return; + + Report(new SyncCompleteInfo + { + Database = bytes, + Path = _info.Path, + Modified = item.Timestamp, + Result = SyncResults.Downloaded, + }); + } + + private void Listed(ListItem item) + { + if (Aborted) + return; + + if (item.Timestamp == null) + { + // File deleted from server, upload local file to server. + + Try(x => x.Upload(item, Uploaded)); + return; + } + + if (item.Timestamp == _info.Modified) + { + if (!_info.HasLocalChanges) + { + // Already up-to-date + Report(new SyncCompleteInfo + { + Path = _info.Path, + Result = SyncResults.NoChange, + }); + + return; + } + + // Has local change, upload to server + Try(x => x.Upload(item, Uploaded)); + + return; + } + + if (!_info.HasLocalChanges) + { + // Has changes from server + Try(x => x.Download(item, Downloaded)); + return; + } + + // Conflict + Try(x => x.Conflict(item, ConflictUploaded)); + } + + private void OnSyncError(Exception ex) + { + if (Aborted) + return; + + Aborted = true; + Report(new SyncCompleteInfo + { + Path = _info.Path, + Result = SyncResults.Failed, + }); + } + + private void Report(SyncCompleteInfo result) + { + string msg = null; + var details = _db.Details; + + switch (result.Result) + { + case SyncResults.Downloaded: + using (var buffer = new MemoryStream(result.Database)) + _db.SetDatabase(buffer, details); + break; + + case SyncResults.Uploaded: + details.HasLocalChanges = false; + details.Modified = result.Modified; + _db.SaveDetails(); + break; + + case SyncResults.Conflict: + details.Url = result.Path; + details.HasLocalChanges = false; + details.Modified = result.Modified; + _db.SaveDetails(); + + msg = string.Format( + Properties.Resources.Conflict, + new Uri(result.Path).LocalPath); + break; + + case SyncResults.Failed: + msg = Properties.Resources + .DownloadError; + break; + } + + _reporter(_db, result.Result, msg); + } + + private void Try(Action action) + { + try + { + action(_adapter); + } + catch (Exception ex) + { + OnSyncError(ex); + } + } + + private void Uploaded(ListItem item) + { + if (Aborted) + return; + + Report(new SyncCompleteInfo + { + Path = _info.Path, + Modified = item.Timestamp, + Result = SyncResults.Uploaded, + }); + } + + private void _adapter_Error( + object sender, SynchronizeErrorEventArgs e) + { + OnSyncError(e.Exception); + } + } +} \ No newline at end of file diff --git a/KeePass/Sources/WebDav/Api/WebDavClient.cs b/KeePass/Sources/WebDav/Api/WebDavClient.cs index 1720db1..00c859c 100644 --- a/KeePass/Sources/WebDav/Api/WebDavClient.cs +++ b/KeePass/Sources/WebDav/Api/WebDavClient.cs @@ -40,6 +40,14 @@ public WebDavClient(string user, string password) new Uri(path), null); } + public string GetUrl(string path) + { + return string.Join("\n", new[] + { + path, _user, _password + }); + } + public void ListAsync(string path, Action> complete, Action htmlDetected, diff --git a/KeePass/Sources/WebDav/WebDavAdapter.cs b/KeePass/Sources/WebDav/WebDavAdapter.cs new file mode 100644 index 0000000..6ae0e91 --- /dev/null +++ b/KeePass/Sources/WebDav/WebDavAdapter.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using KeePass.IO.Utils; +using KeePass.Sources.WebDav.Api; +using KeePass.Storage; + +namespace KeePass.Sources.WebDav +{ + internal class WebDavAdapter : ServiceAdapterBase + { + private WebDavClient _client; + private SyncInfo _info; + + public override void Conflict(ListItem item, + Action uploaded) + { + var path = GetNonConflictPath(); + + UploadFileAsync(path, x => + uploaded(x, _client.GetUrl(path))); + } + + public override void Download(ListItem item, + Action downloaded) + { + _client.DownloadAsync(_info.Path, + x => downloaded(item, x), OnError); + } + + public override SyncInfo Initialize(DatabaseInfo info) + { + if (info == null) + throw new ArgumentNullException("info"); + + var details = info.Details; + var parts = details.Url.Split('\n'); + + _client = new WebDavClient( + parts[1], parts[2]); + + _info = new SyncInfo + { + Path = parts[0], + Modified = details.Modified, + HasLocalChanges = details.HasLocalChanges, + }; + + info.OpenDatabaseFile(x => + { + using (var buffer = new MemoryStream()) + { + BufferEx.CopyStream(x, buffer); + _info.Database = buffer.ToArray(); + } + }); + + return _info; + } + + public override void List(Action ready) + { + _client.ListAsync(_info.Path, + x => ready(Translate(x)), + OnError, OnError); + } + + public override void Upload(ListItem item, + Action uploaded) + { + UploadFileAsync(_info.Path, uploaded); + } + + private string GetNonConflictPath() + { + var path = _info.Path; + var local = new Uri(path).LocalPath; + var dir = Path.GetDirectoryName(local); + var extension = Path.GetExtension(local); + var fileName = Path.GetFileNameWithoutExtension(local); + + fileName = string.Concat(fileName, + " (7Pass' conflicted copy ", + DateTime.Today.ToString("yyyy-MM-dd"), + ")", extension); + + var newPath = Path.Combine(dir, fileName) + .Replace('\\', '/'); + + return path.Replace(local, newPath); + } + + private static ListItem Translate( + IEnumerable items) + { + var item = items + .FirstOrDefault(); + + var info = new ListItem + { + Tag = item, + }; + + if (item != null) + info.Timestamp = item.Modified; + + return info; + } + + private void UploadFileAsync(string path, + Action uploaded) + { + _client.UploadAsync(path, _info.Database, + () => _client.ListAsync(path, + y => uploaded(Translate(y)), + OnError, OnError), + OnError); + } + } +} \ No newline at end of file diff --git a/KeePass/Sources/WebDav/WebDavUpdater.cs b/KeePass/Sources/WebDav/WebDavUpdater.cs deleted file mode 100644 index a4d419c..0000000 --- a/KeePass/Sources/WebDav/WebDavUpdater.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using KeePass.IO.Utils; -using KeePass.Sources.WebDav.Api; -using KeePass.Storage; - -namespace KeePass.Sources.WebDav -{ - internal static class WebDavUpdater - { - public static string GetUrl(this WebDavClient client, - string path) - { - return string.Join("\n", new[] - { - path, - client.User, - client.Password - }); - } - - public static void Update(DatabaseInfo info, - Func queryUpdate, - Action report) - { - if (info == null) throw new ArgumentNullException("info"); - if (queryUpdate == null) throw new ArgumentNullException("queryUpdate"); - if (report == null) throw new ArgumentNullException("report"); - - var details = info.Details; - var parts = details.Url.Split('\n'); - - var client = new WebDavClient( - parts[1], parts[2]); - - var syncInfo = new SyncInfo - { - Path = parts[0], - Modified = details.Modified, - HasLocalChanges = details.HasLocalChanges, - }; - - info.OpenDatabaseFile(x => - { - using (var buffer = new MemoryStream()) - { - BufferEx.CopyStream(x, buffer); - syncInfo.Database = buffer.ToArray(); - } - }); - - Synchronize(client, syncInfo, x => - { - if (queryUpdate(info)) - report(x); - }); - } - - private static string GetNonConflictPath(string path) - { - var local = new Uri(path).LocalPath; - var dir = Path.GetDirectoryName(local); - var extension = Path.GetExtension(local); - var fileName = Path.GetFileNameWithoutExtension(local); - - fileName = string.Concat(fileName, - " (7Pass' conflicted copy ", - DateTime.Today.ToString("yyyy-MM-dd"), - ")", extension); - - var newPath = Path.Combine(dir, fileName) - .Replace('\\', '/'); - - return path.Replace(local, newPath); - } - - private static void OnFileMetaReady( - WebDavClient client, - SyncInfo info, ItemInfo meta, - Action report) - { - // File deleted from server - if (meta == null) - { - // Has local change, upload to server - client.UploadFileAsync( - info.Path, info.Database, - x => report(new SyncCompleteInfo - { - Path = info.Path, - Modified = x.Modified, - Result = SyncResults.Uploaded, - }), - () => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - - return; - } - - // No change from server side - if (meta.Modified == info.Modified) - { - if (!info.HasLocalChanges) - { - report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.NoChange, - }); - - return; - } - - // Has local change, upload to server - client.UploadFileAsync( - info.Path, info.Database, - x => report(new SyncCompleteInfo - { - Path = info.Path, - Modified = x.Modified, - Result = SyncResults.Uploaded, - }), - () => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - - return; - } - - // Has changes from server - if (!info.HasLocalChanges) - { - // Database should be updated - client.DownloadAsync(info.Path, - x => report(new SyncCompleteInfo - { - Database = x, - Path = info.Path, - Modified = meta.Modified, - Result = SyncResults.Downloaded, - }), - ex => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - - return; - } - - // Conflict - var path = GetNonConflictPath(info.Path); - - client.UploadFileAsync( - path, info.Database, - x => report(new SyncCompleteInfo - { - Modified = x.Modified, - Path = GetUrl(client, path), - Result = SyncResults.Conflict, - }), - () => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - } - - private static void Synchronize(WebDavClient client, - SyncInfo info, Action report) - { - client.ListAsync(info.Path, items => - OnFileMetaReady(client, info, - items.FirstOrDefault(), report), - () => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - }), - ex => report(new SyncCompleteInfo - { - Path = info.Path, - Result = SyncResults.Failed, - })); - } - - private static void UploadFileAsync( - this WebDavClient client, - string path, byte[] fileData, - Action success, - Action failure) - { - client.UploadAsync( - path, fileData, - () => client.ListAsync(path, - y => success(y.FirstOrDefault()), - failure, - _ => failure()), - _ => failure()); - } - } -} \ No newline at end of file