diff --git a/News.txt b/News.txt index fd50cf45..3bff5374 100755 --- a/News.txt +++ b/News.txt @@ -1,4 +1,15 @@ -0.9.3 for Linux, Mac and Windows (??? 2012) +0.9.4 for Linux, Mac and Windows (Fri Oct 19 2012) + + Hylke: + - Remove Nautilus extension + - Restore previous revisions of files from the event log + - Fix Mac file system watcher not always triggering + - Add symbolic icon for GNOME 3 (by Lapo) + - New Bitbucket and default user icon + - For encrypted projects, use a different salt for each project + + +0.9.3 for Linux, Mac and Windows (Mon Oct 1 2012) Hylke: - Fix endless loop when adding empty folders diff --git a/SparkleLib/Defines.cs b/SparkleLib/Defines.cs index c224c8a3..05dda0f6 100644 --- a/SparkleLib/Defines.cs +++ b/SparkleLib/Defines.cs @@ -19,7 +19,7 @@ using System; using System.Reflection; [assembly:AssemblyTitle ("SparkleLib")] -[assembly:AssemblyVersion ("0.9.3")] +[assembly:AssemblyVersion ("0.9.4")] [assembly:AssemblyCopyright ("Copyright (c) 2010 Hylke Bons and others")] [assembly:AssemblyTrademark ("SparkleShare is a trademark of SparkleShare Ltd.")] diff --git a/SparkleLib/Git/SparkleFetcherGit.cs b/SparkleLib/Git/SparkleFetcherGit.cs index 914ab654..58735308 100755 --- a/SparkleLib/Git/SparkleFetcherGit.cs +++ b/SparkleLib/Git/SparkleFetcherGit.cs @@ -40,6 +40,8 @@ namespace SparkleLib.Git { // Check if the repo's salt is stored in a branch... SparkleGit git = new SparkleGit (TargetFolder, "branch -a"); + git.StartAndWaitForExit (); + string [] branches = git.StartAndReadStandardOutput ().Split (Environment.NewLine.ToCharArray ()); // TODO double check env.newline ^ diff --git a/SparkleLib/Git/SparkleRepoGit.cs b/SparkleLib/Git/SparkleRepoGit.cs index b92e7e89..53782fe5 100644 --- a/SparkleLib/Git/SparkleRepoGit.cs +++ b/SparkleLib/Git/SparkleRepoGit.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using SparkleLib; @@ -29,6 +30,7 @@ namespace SparkleLib.Git { private bool user_is_set; private bool use_git_bin; + private bool is_encrypted; public SparkleRepo (string path, SparkleConfig config) : base (path, config) @@ -50,6 +52,11 @@ namespace SparkleLib.Git { git = new SparkleGit (LocalPath, "rebase --abort"); git.StartAndWaitForExit (); } + + string password_file_path = Path.Combine (LocalPath, ".git", "password"); + + if (File.Exists (password_file_path)) + this.is_encrypted = true; } @@ -238,9 +245,8 @@ namespace SparkleLib.Git { if (line.Contains ("|")) { speed = line.Substring (line.IndexOf ("|") + 1).Trim (); speed = speed.Replace (", done.", "").Trim (); - speed = speed.Replace ("i", ""); - speed = speed.Replace ("KB/s", "ᴋʙ/s"); - speed = speed.Replace ("MB/s", "ᴍʙ/s"); + speed = speed.Replace ("KiB/s", "ᴋʙ/s"); + speed = speed.Replace ("MiB/s", "ᴍʙ/s"); } } @@ -468,6 +474,7 @@ namespace SparkleLib.Git { foreach (string line in lines) { string conflicting_path = line.Substring (3); conflicting_path = EnsureSpecialCharacters (conflicting_path); + conflicting_path = conflicting_path.Replace ("\"", "\\\""); SparkleLogger.LogInfo ("Git", Name + " | Conflict type: " + line); @@ -539,7 +546,7 @@ namespace SparkleLib.Git { } - public override void RevertFile (string path, string revision) + public override void RestoreFile (string path, string revision, string target_file_path) { if (path == null) throw new ArgumentNullException ("path"); @@ -547,27 +554,58 @@ namespace SparkleLib.Git { if (revision == null) throw new ArgumentNullException ("revision"); - path = path.Replace ("\\", "/"); + SparkleLogger.LogInfo ("Git", Name + " | Restoring \"" + path + "\" (revision " + revision + ")"); - SparkleGit git = new SparkleGit (LocalPath, "checkout " + revision + " \"" + path + "\""); - git.StartAndWaitForExit (); + // FIXME: git-show doesn't decrypt objects, so we can't use it to retrieve + // files from the index. This is a suboptimal workaround but it does the job + if (this.is_encrypted) { + // Restore the older file... + SparkleGit git = new SparkleGit (LocalPath, "checkout " + revision + " \"" + path + "\""); + git.StartAndWaitForExit (); - if (git.ExitCode == 0) - SparkleLogger.LogInfo ("Git", Name + " | Checked out \"" + path + "\" (" + revision + ")"); - else - SparkleLogger.LogInfo ("Git", Name + " | Failed to check out \"" + path + "\" (" + revision + ")"); + string local_file_path = Path.Combine (LocalPath, path); + + // ...move it... + try { + File.Move (local_file_path, target_file_path); + + } catch { + SparkleLogger.LogInfo ("Git", + Name + " | Could not move \"" + local_file_path + "\" to \"" + target_file_path + "\""); + } + + // ...and restore the most recent revision + git = new SparkleGit (LocalPath, "checkout " + CurrentRevision + " \"" + path + "\""); + git.StartAndWaitForExit (); + + // The correct way + } else { + path = path.Replace ("\"", "\\\""); + + SparkleGit git = new SparkleGit (LocalPath, "show " + revision + ":\"" + path + "\""); + git.Start (); + + FileStream stream = File.OpenWrite (target_file_path); + git.StandardOutput.BaseStream.CopyTo (stream); + stream.Close (); + + git.WaitForExit (); + } + + if (target_file_path.StartsWith (LocalPath)) + new Thread (() => OnFileActivity (null)).Start (); } - public override List GetChangeSets (string path, int count) + public override List GetChangeSets (string path) { - return GetChangeSetsInternal (path, count); + return GetChangeSetsInternal (path); } - public override List GetChangeSets (int count) + public override List GetChangeSets () { - return GetChangeSetsInternal (null, count); + return GetChangeSetsInternal (null); } @@ -590,31 +628,27 @@ namespace SparkleLib.Git { if (Error != ErrorStatus.None) { SparkleLogger.LogInfo ("Git", Name + " | Error status changed to " + Error); return true; + } else { return false; } } - private List GetChangeSetsInternal (string path, int count) + private List GetChangeSetsInternal (string path) { - if (count < 1) - throw new ArgumentOutOfRangeException ("count"); - - count = 150; List change_sets = new List (); - SparkleGit git; if (path == null) { - git = new SparkleGit (LocalPath, "log -" + count + " --raw --find-renames --date=iso " + + git = new SparkleGit (LocalPath, "log --since=1.month --raw --find-renames --date=iso " + "--format=medium --no-color --no-merges"); } else { path = path.Replace ("\\", "/"); - git = new SparkleGit (LocalPath, "log -" + count + " --raw --find-renames --date=iso " + - "--format=medium --no-color --no-merges -- " + path); + git = new SparkleGit (LocalPath, "log --raw --find-renames --date=iso " + + "--format=medium --no-color --no-merges -- \"" + path + "\""); } string output = git.StartAndReadStandardOutput (); @@ -681,13 +715,16 @@ namespace SparkleLib.Git { if (entry_line.StartsWith (":")) { string type_letter = entry_line [37].ToString (); string file_path = entry_line.Substring (39); - - if (file_path.EndsWith (".empty")) - file_path = file_path.Substring (0, file_path.Length - ".empty".Length); + bool change_is_folder = false; if (file_path.Equals (".sparkleshare")) continue; + if (file_path.EndsWith (".empty")) { + file_path = file_path.Substring (0, file_path.Length - ".empty".Length); + change_is_folder = true; + } + file_path = EnsureSpecialCharacters (file_path); file_path = file_path.Replace ("\\\"", "\""); @@ -702,15 +739,20 @@ namespace SparkleLib.Git { file_path = file_path.Replace ("\\\"", "\""); to_file_path = to_file_path.Replace ("\\\"", "\""); - if (file_path.EndsWith (".empty")) + if (file_path.EndsWith (".empty")) { file_path = file_path.Substring (0, file_path.Length - 6); + change_is_folder = true; + } - if (to_file_path.EndsWith (".empty")) + if (to_file_path.EndsWith (".empty")) { to_file_path = to_file_path.Substring (0, to_file_path.Length - 6); + change_is_folder = true; + } change_set.Changes.Add ( new SparkleChange () { Path = file_path, + IsFolder = change_is_folder, MovedToPath = to_file_path, Timestamp = change_set.Timestamp, Type = SparkleChangeType.Moved @@ -730,6 +772,7 @@ namespace SparkleLib.Git { change_set.Changes.Add ( new SparkleChange () { Path = file_path, + IsFolder = change_is_folder, Timestamp = change_set.Timestamp, Type = change_type } diff --git a/SparkleLib/SparkleFetcherBase.cs b/SparkleLib/SparkleFetcherBase.cs index 9a9433d9..79c95761 100755 --- a/SparkleLib/SparkleFetcherBase.cs +++ b/SparkleLib/SparkleFetcherBase.cs @@ -184,6 +184,7 @@ namespace SparkleLib { if (File.Exists (identifier_path)) { Identifier = File.ReadAllText (identifier_path).Trim (); + File.SetAttributes (identifier_path, FileAttributes.Hidden); } else { Identifier = CreateIdentifier (); @@ -220,9 +221,8 @@ namespace SparkleLib { "Any files you add or change in this folder will be automatically synced to " + n + uri_builder.ToString () + " and everyone connected to it." + n + n + - "SparkleShare is an Open Source software program that helps people " + n + - "collaborate and share files. If you like what we do, please consider a small " + n + - "donation to support the project: http://www.sparkleshare.org/" + n + + "SparkleShare is an Open Source software program that helps people collaborate and " + n + + "share files. If you like what we do, consider buying us a beer: http://www.sparkleshare.org/" + n + n + "Have fun! :)" + n; } diff --git a/SparkleLib/SparkleRepoBase.cs b/SparkleLib/SparkleRepoBase.cs index f0b26be6..02202ada 100755 --- a/SparkleLib/SparkleRepoBase.cs +++ b/SparkleLib/SparkleRepoBase.cs @@ -54,9 +54,9 @@ namespace SparkleLib { public abstract bool HasRemoteChanges { get; } public abstract bool SyncUp (); public abstract bool SyncDown (); - public abstract List GetChangeSets (int count); - public abstract List GetChangeSets (string path, int count); - public abstract void RevertFile (string path, string revision); + public abstract List GetChangeSets (); + public abstract List GetChangeSets (string path); + public abstract void RestoreFile (string path, string revision, string target_file_path); public event SyncStatusChangedEventHandler SyncStatusChanged = delegate { }; public delegate void SyncStatusChangedEventHandler (SyncStatus new_status); @@ -195,19 +195,17 @@ namespace SparkleLib { // Sync up everything that changed // since we've been offline - if (!this.is_syncing && (HasLocalChanges || HasUnsyncedChanges)) { - SyncUpBase (); - - while (HasLocalChanges) + new Thread (() => { + if (!this.is_syncing && (HasLocalChanges || HasUnsyncedChanges)) { SyncUpBase (); - } - this.remote_timer.Start (); - } + while (HasLocalChanges && !this.is_syncing) + SyncUpBase (); + } - - public List GetChangeSets () { - return GetChangeSets (30); + this.remote_timer.Start (); + + }).Start (); } @@ -218,9 +216,11 @@ namespace SparkleLib { if (IsBuffering) return; - foreach (string exclude_path in ExcludePaths) { - if (args.FullPath.Contains (exclude_path)) - return; + if (args != null) { + foreach (string exclude_path in ExcludePaths) { + if (args.FullPath.Contains (exclude_path)) + return; + } } lock (this.buffer_lock) { diff --git a/SparkleLib/SparkleWrappers.cs b/SparkleLib/SparkleWrappers.cs index bf60ddcf..6e328ae5 100644 --- a/SparkleLib/SparkleWrappers.cs +++ b/SparkleLib/SparkleWrappers.cs @@ -47,6 +47,7 @@ namespace SparkleLib { public SparkleChangeType Type; public DateTime Timestamp; + public bool IsFolder = false; public string Path; public string MovedToPath; diff --git a/SparkleShare/Common/HTML/event-log.html b/SparkleShare/Common/HTML/event-log.html index 368d5722..06a3d95e 100755 --- a/SparkleShare/Common/HTML/event-log.html +++ b/SparkleShare/Common/HTML/event-log.html @@ -10,8 +10,9 @@ $('dl dd:nth-child(-n+10)').css('display', 'block'); $('.day-entry-content .event-entry:last-child').css('border', 'none'); - $('dd a.windows').click(function () { + $('a').click(function (event) { window.external.LinkClicked($(this).attr("href")); + event.preventDefault(); }); // Update the Today and Yesterday labels after midnight @@ -26,7 +27,7 @@ } }, 60 * 1000); - // Hide the 'Show all' link when there are less than 10 events + // Hide the 'Show all' link when there are fewer than 10 events $('.show').each (function () { var entry_count = $(this).parent ().find ('dl').children ().length; @@ -67,7 +68,6 @@ a.show { font-size: 80%; - margin-bottom: 9px; } a:hover { @@ -76,7 +76,7 @@ cursor: pointer; } - small { + small, small a, small a:hover { font-size: ; color: ; } @@ -91,13 +91,12 @@ display: none; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; width: 90%; padding: 0 0 1px 20px; margin: 0 0 4px 0; background-repeat: no-repeat; background-position: center left; - text-overflow: ellipsis; - white-space: nowrap; } .day-entry-header { @@ -107,13 +106,24 @@ font-weight: bold; } + .history-header { + color: #aaa; + padding-top: 22px; + float: left; + width: 90%; + margin-left: 32px; + margin-right: 32px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .event-entry { padding: 24px 14px 14px 64px; margin: 0 32px 0 32px; border-bottom: 1px #ddd solid; background-repeat: no-repeat; background-position: 36px 24px; - min-height: 100px; } .event-user-name { @@ -152,7 +162,54 @@ .moved { background-image: url(''); - } + } + + table { + width: 100%; + } + + .table-wrapper { + padding: 64px 32px; + } + + td { + padding: 0; + margin: 0; + } + + td.name { + font-weight: bold; + width: 45%; + } + + td.time { + color: ; + padding-right: 9px; + font-size: ; + } + + td.date { + color: ; + text-align: right; + padding-right: 6px; + font-size: ; + } + + td.restore { + text-align: right; + font-size: ; + } + + td.avatar { + width: 32px; + } + + td.avatar img { + margin-top: 2px; + border-radius: 3px; + width: 24px; + height: 24px; + } diff --git a/SparkleShare/Linux/SparkleEventLog.cs b/SparkleShare/Linux/SparkleEventLog.cs index 8f28fc09..2eb722c7 100755 --- a/SparkleShare/Linux/SparkleEventLog.cs +++ b/SparkleShare/Linux/SparkleEventLog.cs @@ -131,12 +131,35 @@ namespace SparkleShare { Present (); }); }; + + Controller.ShowSaveDialogEvent += delegate (string file_name, string target_folder_path) { + Application.Invoke (delegate { + FileChooserDialog dialog = new FileChooserDialog ("Restore from History", + this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "Save", ResponseType.Ok); + + dialog.CurrentName = file_name; + dialog.SetCurrentFolder (target_folder_path); + + if (dialog.Run () == (int) ResponseType.Ok) + Controller.SaveDialogCompleted (dialog.Filename); + else + Controller.SaveDialogCancelled (); + + dialog.Destroy (); + }); + }; Controller.UpdateChooserEvent += delegate (string [] folders) { Application.Invoke (delegate { UpdateChooser (folders); }); }; + + Controller.UpdateChooserEnablementEvent += delegate (bool enabled) { + Application.Invoke (delegate { + this.combo_box.Sensitive = enabled; + }); + }; Controller.UpdateContentEvent += delegate (string html) { Application.Invoke (delegate { @@ -168,7 +191,7 @@ namespace SparkleShare { private void WebViewNavigationRequested (object o, WebKit.NavigationRequestedArgs args) { - Controller.LinkClicked (args.Request.Uri.Substring (7)); + Controller.LinkClicked (args.Request.Uri); // Don't follow HREFs (as this would cause a page refresh) if (!args.Request.Uri.Equals ("file:")) @@ -260,7 +283,8 @@ namespace SparkleShare { html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Background (StateType.Normal))); html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive))); html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive))); - + html = html.Replace ("", "85%"); + html = html.Replace ("", pixmaps_path); html = html.Replace ("", diff --git a/SparkleShare/Mac/README.md b/SparkleShare/Mac/README.md index 464b71a0..aa6b0406 100644 --- a/SparkleShare/Mac/README.md +++ b/SparkleShare/Mac/README.md @@ -83,5 +83,5 @@ SparkleShare/Windows/SparkleShare.wxs ### Uninstalling -Simple remove the SparkleShare bundle. +Simply remove the SparkleShare bundle. diff --git a/SparkleShare/Mac/SparkleEventLog.cs b/SparkleShare/Mac/SparkleEventLog.cs index 34cd8b8e..b8d59df5 100755 --- a/SparkleShare/Mac/SparkleEventLog.cs +++ b/SparkleShare/Mac/SparkleEventLog.cs @@ -80,6 +80,7 @@ namespace SparkleShare { new SizeF (ContentView.Frame.Width, ContentView.Frame.Height - 39)) }; + this.hidden_close_button = new NSButton () { KeyEquivalentModifierMask = NSEventModifierMask.CommandKeyMask, KeyEquivalent = "w" @@ -197,7 +198,7 @@ namespace SparkleShare { }); } }; - + Controller.UpdateChooserEvent += delegate (string [] folders) { using (var a = new NSAutoreleasePool ()) { @@ -207,6 +208,15 @@ namespace SparkleShare { } }; + Controller.UpdateChooserEnablementEvent += delegate (bool enabled) { + using (var a = new NSAutoreleasePool ()) + { + InvokeOnMainThread (delegate { + this.popup_button.Enabled = enabled; + }); + } + }; + Controller.UpdateContentEvent += delegate (string html) { using (var a = new NSAutoreleasePool ()) { @@ -228,7 +238,7 @@ namespace SparkleShare { }); } }; - + Controller.UpdateSizeInfoEvent += delegate (string size, string history_size) { using (var a = new NSAutoreleasePool ()) { @@ -238,6 +248,30 @@ namespace SparkleShare { }); } }; + + Controller.ShowSaveDialogEvent += delegate (string file_name, string target_folder_path) { + using (var a = new NSAutoreleasePool ()) + { + InvokeOnMainThread (() => { + // TODO: Make this a sheet + NSSavePanel panel = new NSSavePanel () { + DirectoryUrl = new NSUrl (target_folder_path, true), + NameFieldStringValue = file_name, + ParentWindow = this, + Title = "Restore from History", + PreventsApplicationTerminationWhenModal = false + }; + + if ((NSPanelButtonType) panel.RunModal ()== NSPanelButtonType.Ok) { + string target_file_path = Path.Combine (panel.DirectoryUrl.RelativePath, panel.NameFieldStringValue); + Controller.SaveDialogCompleted (target_file_path); + + } else { + Controller.SaveDialogCancelled (); + } + }); + } + }; } @@ -346,6 +380,7 @@ namespace SparkleShare { html = html.Replace ("", "13.4px"); html = html.Replace ("", "#bbb"); html = html.Replace ("", "#ddd"); + html = html.Replace ("", "10px"); html = html.Replace ("", "#f5f5f5"); html = html.Replace ("", "#0085cf"); html = html.Replace ("", "#009ff8"); @@ -372,7 +407,7 @@ namespace SparkleShare { this.web_view.MainFrame.LoadHtmlString (html, new NSUrl ("")); - web_view.PolicyDelegate = new SparkleWebPolicyDelegate (); + this.web_view.PolicyDelegate = new SparkleWebPolicyDelegate (); ContentView.AddSubview (this.web_view); (this.web_view.PolicyDelegate as SparkleWebPolicyDelegate).LinkClicked += diff --git a/SparkleShare/Mac/SparkleStatusIcon.cs b/SparkleShare/Mac/SparkleStatusIcon.cs index 126e8df2..b8da56a3 100755 --- a/SparkleShare/Mac/SparkleStatusIcon.cs +++ b/SparkleShare/Mac/SparkleStatusIcon.cs @@ -76,13 +76,13 @@ namespace SparkleShare { this.syncing_idle_image = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-idle.png")); this.syncing_up_image = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-up.png")); this.syncing_down_image = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-down.png")); - this.syncing_image = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing.png")); + this.syncing_image = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing.png")); this.syncing_error_image = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-error.png")); this.syncing_idle_image_active = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-idle-active.png")); this.syncing_up_image_active = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-up-active.png")); this.syncing_down_image_active = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-down-active.png")); - this.syncing_image_active = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-active.png")); + this.syncing_image_active = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-active.png")); this.syncing_error_image_active = new NSImage (Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "process-syncing-error-active.png")); this.status_item.Image = this.syncing_idle_image; diff --git a/SparkleShare/SparkleControllerBase.cs b/SparkleShare/SparkleControllerBase.cs index e25c0f6c..64278ac2 100644 --- a/SparkleShare/SparkleControllerBase.cs +++ b/SparkleShare/SparkleControllerBase.cs @@ -260,6 +260,7 @@ namespace SparkleShare { CheckRepositories (); RepositoriesLoaded = true; FolderListChanged (); + UpdateState (); }).Start (); } diff --git a/SparkleShare/SparkleEventLogController.cs b/SparkleShare/SparkleEventLogController.cs index e6b7d5e7..611cd84b 100755 --- a/SparkleShare/SparkleEventLogController.cs +++ b/SparkleShare/SparkleEventLogController.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Text.RegularExpressions; using System.Threading; using SparkleLib; @@ -33,15 +34,23 @@ namespace SparkleShare { public event UpdateContentEventEventHandler UpdateContentEvent = delegate { }; public delegate void UpdateContentEventEventHandler (string html); - + public event UpdateChooserEventHandler UpdateChooserEvent = delegate { }; public delegate void UpdateChooserEventHandler (string [] folders); - + + public event UpdateChooserEnablementEventHandler UpdateChooserEnablementEvent = delegate { }; + public delegate void UpdateChooserEnablementEventHandler (bool enabled); + public event UpdateSizeInfoEventHandler UpdateSizeInfoEvent = delegate { }; public delegate void UpdateSizeInfoEventHandler (string size, string history_size); + + public event ShowSaveDialogEventHandler ShowSaveDialogEvent = delegate { }; + public delegate void ShowSaveDialogEventHandler (string file_name, string target_folder_path); private string selected_folder; + private RevisionInfo restore_revision_info; + private bool history_view_active; public bool WindowIsOpen { get; private set; } @@ -57,10 +66,10 @@ namespace SparkleShare { ContentLoadingEvent (); UpdateSizeInfoEvent ("…", "…"); - Stopwatch watch = new Stopwatch (); - watch.Start (); - new Thread (() => { + Stopwatch watch = new Stopwatch (); + watch.Start (); + string html = HTML; watch.Stop (); @@ -161,6 +170,7 @@ namespace SparkleShare { Thread.Sleep (delay - (int) watch.ElapsedMilliseconds); UpdateChooserEvent (Folders); + UpdateChooserEnablementEvent (true); UpdateContentEvent (html); UpdateSizeInfoEvent (Size, HistorySize); @@ -173,6 +183,9 @@ namespace SparkleShare { }; Program.Controller.OnIdle += delegate { + if (this.history_view_active) + return; + ContentLoadingEvent (); UpdateSizeInfoEvent ("…", "…"); @@ -212,15 +225,98 @@ namespace SparkleShare { public void LinkClicked (string url) { url = url.Replace ("%20", " "); - - if (url.StartsWith (Path.VolumeSeparatorChar.ToString ()) || - url.Substring (1, 1).Equals (":")) { - - Program.Controller.OpenFile (url); - } else if (url.StartsWith ("http")) { + if (url.StartsWith ("http")) { Program.Controller.OpenWebsite (url); + + } else if (url.StartsWith ("restore://") && this.restore_revision_info == null) { + Regex regex = new Regex ("restore://(.+)/([a-f0-9]+)/(.+)/(.{3} [0-9]+ [0-9]+h[0-9]+)/(.+)", RegexOptions.Compiled); + Match match = regex.Match (url); + + if (match.Success) { + string author_name = match.Groups [3].Value; + string timestamp = match.Groups [4].Value; + + this.restore_revision_info = new RevisionInfo () { + Folder = new SparkleFolder (match.Groups [1].Value), + Revision = match.Groups [2].Value, + FilePath = match.Groups [5].Value + }; + + string file_name = Path.GetFileNameWithoutExtension (this.restore_revision_info.FilePath) + + " (" + author_name + " " + timestamp + ")" + Path.GetExtension (this.restore_revision_info.FilePath); + + string target_folder_path = Path.Combine (this.restore_revision_info.Folder.FullPath, + Path.GetDirectoryName (this.restore_revision_info.FilePath)); + + ShowSaveDialogEvent (file_name, target_folder_path); + } + + } else if (url.StartsWith ("back://")) { + this.history_view_active = false; + SelectedFolder = this.selected_folder; // TODO: Return to the same position on the page + + UpdateChooserEnablementEvent (true); + + } else if (url.StartsWith ("history://")) { + this.history_view_active = true; + + ContentLoadingEvent (); + UpdateSizeInfoEvent ("…", "…"); + UpdateChooserEnablementEvent (false); + + string folder = url.Replace ("history://", "").Split ("/".ToCharArray ()) [0]; + string file_path = url.Replace ("history://" + folder + "/", ""); + + foreach (SparkleRepoBase repo in Program.Controller.Repositories) { + if (!repo.Name.Equals (folder)) + continue; + + new Thread (() => { + Stopwatch watch = new Stopwatch (); + watch.Start (); + + List change_sets = repo.GetChangeSets (file_path); + string html = GetHistoryHTMLLog (change_sets, file_path); + + watch.Stop (); + int delay = 500; + + if (watch.ElapsedMilliseconds < delay) + Thread.Sleep (delay - (int) watch.ElapsedMilliseconds); + + UpdateContentEvent (html); + + }).Start (); + + break; + } + + } else { + Program.Controller.OpenFile (url); + } + } + + + public void SaveDialogCompleted (string target_file_path) + { + foreach (SparkleRepoBase repo in Program.Controller.Repositories) { + if (repo.Name.Equals (this.restore_revision_info.Folder.Name)) { + repo.RestoreFile (this.restore_revision_info.FilePath, + this.restore_revision_info.Revision, target_file_path); + + break; + } } + + this.restore_revision_info = null; + Program.Controller.OpenFolder (Path.GetDirectoryName (target_file_path)); + } + + + public void SaveDialogCancelled () + { + this.restore_revision_info = null; } @@ -261,6 +357,56 @@ namespace SparkleShare { } + public string GetHistoryHTMLLog (List change_sets, string file_path) + { + string html = "
" + + "« Back  |  "; + + if (change_sets.Count > 1) + html += "Revisions for “"; + else + html += "No revisions for “"; + + html += Path.GetFileName (file_path) + "”"; + html += "
"; + + int count = 0; + foreach (SparkleChangeSet change_set in change_sets) { + count++; + + if (count == 1) + continue; + + string change_set_avatar = Program.Controller.GetAvatar (change_set.User.Email, 24); + + if (change_set_avatar != null) + change_set_avatar = "file://" + change_set_avatar.Replace ("\\", "/"); + else + change_set_avatar = "file:///user-icon-default.png"; + + html += "" + + "" + + "" + + "" + + "" + + "" + + ""; + + count++; + } + + html += "
" + change_set.User.Name + "" + change_set.Timestamp.ToString ("d MMM yyyy") + "" + change_set.Timestamp.ToString ("HH:mm") + "" + + "Restore…" + + "
"; + html = Program.Controller.EventLogHTML.Replace ("", html); + + return html.Replace ("", "100000000"); + } + + public string GetHTMLLog (List change_sets) { if (change_sets.Count == 0) @@ -307,12 +453,22 @@ namespace SparkleShare { foreach (SparkleChange change in change_set.Changes) { if (change.Type != SparkleChangeType.Moved) { event_entry += "
"; - event_entry += "" + change.Timestamp.ToString ("HH:mm") +"  "; + + if (!change.IsFolder) { + event_entry += "" + change.Timestamp.ToString ("HH:mm") + + "  "; + + } else { + event_entry += "" + change.Timestamp.ToString ("HH:mm") + "  "; + } + event_entry += FormatBreadCrumbs (change_set.Folder.FullPath, change.Path); event_entry += "
"; } else { event_entry += "
"; + event_entry += "" + change.Timestamp.ToString ("HH:mm") +"  "; event_entry += FormatBreadCrumbs (change_set.Folder.FullPath, change.Path); event_entry += "
"; event_entry += "" + change.Timestamp.ToString ("HH:mm") +"  "; @@ -444,5 +600,12 @@ namespace SparkleShare { Date = new DateTime (date_time.Year, date_time.Month, date_time.Day); } } + + + private class RevisionInfo { + public SparkleFolder Folder; + public string FilePath; + public string Revision; + } } } diff --git a/SparkleShare/Windows/SparkleAbout.cs b/SparkleShare/Windows/SparkleAbout.cs index 3a239d4a..6f36956a 100644 --- a/SparkleShare/Windows/SparkleAbout.cs +++ b/SparkleShare/Windows/SparkleAbout.cs @@ -120,7 +120,7 @@ namespace SparkleShare { SparkleLink website_link = new SparkleLink ("Website", Controller.WebsiteLinkAddress); SparkleLink credits_link = new SparkleLink ("Credits", Controller.CreditsLinkAddress); SparkleLink report_problem_link = new SparkleLink ("Report a problem", Controller.ReportProblemLinkAddress); - SparkleLink debug_log_link = new SparkleLink ("Debig log", Controller.DebugLogLinkAddress); + SparkleLink debug_log_link = new SparkleLink ("Debug log", Controller.DebugLogLinkAddress); Canvas canvas = new Canvas (); @@ -154,7 +154,7 @@ namespace SparkleShare { canvas.Children.Add (debug_log_link); Canvas.SetLeft (debug_log_link, 289 + website_link.ActualWidth + credits_link.ActualWidth + - report_problem_link.ActualWidth + 180); + report_problem_link.ActualWidth + 220); Canvas.SetTop (debug_log_link, 222); Content = canvas; diff --git a/SparkleShare/Windows/SparkleEventLog.cs b/SparkleShare/Windows/SparkleEventLog.cs index 5cb4268a..9f6aa473 100644 --- a/SparkleShare/Windows/SparkleEventLog.cs +++ b/SparkleShare/Windows/SparkleEventLog.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see (http://www.gnu.org/licenses/). - using System; using System.ComponentModel; using System.IO; @@ -26,6 +25,7 @@ using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; +using Microsoft.Win32; using Shapes = System.Windows.Shapes; namespace SparkleShare { @@ -50,7 +50,7 @@ namespace SparkleShare { ResizeMode = ResizeMode.NoResize; // TODO Background = new SolidColorBrush (Color.FromRgb (240, 240, 240)); AllowsTransparency = false; - Icon = SparkleUIHelpers.GetImageSource("sparkleshare-app", "ico"); + Icon = SparkleUIHelpers.GetImageSource ("sparkleshare-app", "ico"); int x = (int) (SystemParameters.PrimaryScreenWidth * 0.61); int y = (int) (SystemParameters.PrimaryScreenHeight * 0.5 - (Height * 0.5)); @@ -104,6 +104,7 @@ namespace SparkleShare { this.web_browser.ObjectForScripting = new SparkleScriptingObject (); + spinner = new SparkleSpinner (22); // Disable annoying IE clicking sound @@ -177,6 +178,12 @@ namespace SparkleShare { }); }; + Controller.UpdateChooserEnablementEvent += delegate (bool enabled) { + Dispatcher.BeginInvoke ((Action) delegate { + this.combo_box.IsEnabled = enabled; + }); + }; + Controller.UpdateContentEvent += delegate (string html) { Dispatcher.BeginInvoke ((Action) delegate { UpdateContent (html); @@ -191,6 +198,25 @@ namespace SparkleShare { this.canvas.Children.Remove (this.web_browser); }); }; + + Controller.ShowSaveDialogEvent += delegate (string file_name, string target_folder_path) { + Dispatcher.BeginInvoke ((Action) delegate { + SaveFileDialog dialog = new SaveFileDialog () { + FileName = file_name, + InitialDirectory = target_folder_path, + Title = "Restore from History", + DefaultExt = "." + Path.GetExtension (file_name), + Filter = "All Files|*.*" + }; + + Nullable result = dialog.ShowDialog (this); + + if (result == true) + Controller.SaveDialogCompleted (dialog.FileName); + else + Controller.SaveDialogCancelled (); + }); + }; } @@ -262,6 +288,7 @@ namespace SparkleShare { html = html.Replace ("", "12px"); html = html.Replace ("", "#bbb"); html = html.Replace ("", "#ddd"); + html = html.Replace ("", "90%"); html = html.Replace ("", "#f5f5f5"); html = html.Replace ("", "#0085cf"); html = html.Replace ("", "#009ff8"); @@ -282,9 +309,10 @@ namespace SparkleShare { Dispatcher.BeginInvoke ((Action) delegate { this.spinner.Stop (); - - this.web_browser.NavigateToString (html); - + + this.web_browser.ObjectForScripting = new SparkleScriptingObject (); + this.web_browser.NavigateToString (html); + if (!this.canvas.Children.Contains (this.web_browser)) { this.canvas.Children.Add (this.web_browser); Canvas.SetLeft (this.web_browser, 0); @@ -322,8 +350,8 @@ namespace SparkleShare { string [] actions = new string [] {"added", "deleted", "edited", "moved"}; foreach (string action in actions) { - BitmapSource image = SparkleUIHelpers.GetImageSource ("document-" + action + "-12"); - string file_path = Path.Combine (pixmaps_path, "document-" + action + "-12.png"); + image = SparkleUIHelpers.GetImageSource ("document-" + action + "-12"); + file_path = Path.Combine (pixmaps_path, "document-" + action + "-12.png"); using (FileStream stream = new FileStream (file_path, FileMode.Create)) { @@ -345,8 +373,8 @@ namespace SparkleShare { [DllImport ("urlmon.dll")] [PreserveSig] [return:MarshalAs (UnmanagedType.Error)] - static extern int CoInternetSetFeatureEnabled ( - int feature, [MarshalAs (UnmanagedType.U4)] int flags, bool enable); + static extern int CoInternetSetFeatureEnabled (int feature, + [MarshalAs (UnmanagedType.U4)] int flags, bool enable); } diff --git a/SparkleShare/Windows/SparkleShare.wxs b/SparkleShare/Windows/SparkleShare.wxs index f9c6ce22..5c67bf12 100644 --- a/SparkleShare/Windows/SparkleShare.wxs +++ b/SparkleShare/Windows/SparkleShare.wxs @@ -2,7 +2,7 @@ + Language='1033' Codepage='1252' Version='0.9.4' Manufacturer='SparkleShare'> diff --git a/configure.ac b/configure.ac index 6ddea778..4c6b3f69 100755 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ dnl Process this file with autoconf to produce a configure script. -m4_define([sparkleshare_version], [0.9.3]) +m4_define([sparkleshare_version], [0.9.4]) AC_PREREQ([2.54]) AC_INIT([SparkleShare], sparkleshare_version)