Merge branch 'pausing'
This commit is contained in:
commit
2797ece573
11
News.txt
11
News.txt
|
@ -1,3 +1,14 @@
|
|||
1.5.0 for Linux, Mac and Windows (???):
|
||||
|
||||
Hylke
|
||||
- Per folder sync status in icon menu
|
||||
- Add ability to pause sync and add a commit message on resume
|
||||
- Remove dock icon on Mac and force windows on top
|
||||
- Update certificate for Gravatar
|
||||
- Resizable "Recent Changes" window on Windows
|
||||
- Several stability fixes
|
||||
|
||||
|
||||
1.4.0 for Linux, Mac and Windows (Sun Apr 20, 2014):
|
||||
|
||||
Hylke:
|
||||
|
|
|
@ -19,7 +19,7 @@ using System;
|
|||
using System.Reflection;
|
||||
|
||||
[assembly:AssemblyTitle ("SparkleLib")]
|
||||
[assembly:AssemblyVersion ("1.4")]
|
||||
[assembly:AssemblyVersion ("1.5.0")]
|
||||
[assembly:AssemblyCopyright ("Copyright (c) 2010 Hylke Bons and others")]
|
||||
[assembly:AssemblyTrademark ("SparkleShare is a trademark of SparkleShare Ltd.")]
|
||||
|
||||
|
|
|
@ -235,7 +235,10 @@ namespace SparkleLib.Git {
|
|||
return false;
|
||||
}
|
||||
|
||||
string message = FormatCommitMessage ();
|
||||
string message = base.status_message;
|
||||
|
||||
if (string.IsNullOrEmpty (message))
|
||||
message = FormatCommitMessage ();
|
||||
|
||||
if (message != null)
|
||||
Commit (message);
|
||||
|
@ -759,6 +762,13 @@ namespace SparkleLib.Git {
|
|||
}
|
||||
|
||||
|
||||
public override List<SparkleChange> UnsyncedChanges {
|
||||
get {
|
||||
return ParseStatus ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override List<SparkleChangeSet> GetChangeSets ()
|
||||
{
|
||||
return GetChangeSetsInternal (null);
|
||||
|
@ -1084,54 +1094,86 @@ namespace SparkleLib.Git {
|
|||
}
|
||||
|
||||
|
||||
// Creates a pretty commit message based on what has changed
|
||||
private string FormatCommitMessage ()
|
||||
{
|
||||
int count = 0;
|
||||
string message = "";
|
||||
|
||||
private List<SparkleChange> ParseStatus ()
|
||||
{
|
||||
List<SparkleChange> changes = new List<SparkleChange> ();
|
||||
int count = 0;
|
||||
|
||||
SparkleGit git_status = new SparkleGit (LocalPath, "status --porcelain");
|
||||
git_status.Start ();
|
||||
|
||||
|
||||
while (!git_status.StandardOutput.EndOfStream) {
|
||||
string line = git_status.StandardOutput.ReadLine ();
|
||||
line = line.Trim ();
|
||||
|
||||
|
||||
if (line.EndsWith (".empty") || line.EndsWith (".empty\""))
|
||||
line = line.Replace (".empty", "");
|
||||
|
||||
SparkleChange change;
|
||||
|
||||
if (line.StartsWith ("R")) {
|
||||
string path = line.Substring (3, line.IndexOf (" -> ") - 3).Trim ("\"".ToCharArray ());
|
||||
string moved_to_path = line.Substring (line.IndexOf (" -> ") + 4).Trim ("\"".ToCharArray ());
|
||||
string path = line.Substring (3, line.IndexOf (" -> ") - 3).Trim ("\" ".ToCharArray ());
|
||||
string moved_to_path = line.Substring (line.IndexOf (" -> ") + 4).Trim ("\" ".ToCharArray ());
|
||||
|
||||
change = new SparkleChange () {
|
||||
Type = SparkleChangeType.Moved,
|
||||
Path = EnsureSpecialCharacters (path),
|
||||
MovedToPath = EnsureSpecialCharacters (moved_to_path)
|
||||
};
|
||||
|
||||
} else {
|
||||
string path = line.Substring (2).Trim ("\" ".ToCharArray ());
|
||||
change = new SparkleChange () { Path = EnsureSpecialCharacters (path) };
|
||||
change.Type = SparkleChangeType.Added;
|
||||
|
||||
message += "< ‘" + EnsureSpecialCharacters (path) + "’\n";
|
||||
message += "> ‘" + EnsureSpecialCharacters (moved_to_path) + "’\n";
|
||||
if (line.StartsWith ("M")) {
|
||||
change.Type = SparkleChangeType.Edited;
|
||||
|
||||
} else if (line.StartsWith ("D")) {
|
||||
change.Type = SparkleChangeType.Deleted;
|
||||
}
|
||||
}
|
||||
|
||||
changes.Add (change);
|
||||
|
||||
count++;
|
||||
if (count == 10)
|
||||
break;
|
||||
}
|
||||
|
||||
git_status.StandardOutput.ReadToEnd ();
|
||||
git_status.WaitForExit ();
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
|
||||
// Creates a pretty commit message based on what has changed
|
||||
private string FormatCommitMessage ()
|
||||
{
|
||||
string message = "";
|
||||
|
||||
foreach (SparkleChange change in ParseStatus ()) {
|
||||
if (change.Type == SparkleChangeType.Moved) {
|
||||
message += "< ‘" + EnsureSpecialCharacters (change.Path) + "’\n";
|
||||
message += "> ‘" + EnsureSpecialCharacters (change.MovedToPath) + "’\n";
|
||||
|
||||
} else {
|
||||
if (line.StartsWith ("M")) {
|
||||
if (change.Type == SparkleChangeType.Edited) {
|
||||
message += "/";
|
||||
|
||||
} else if (line.StartsWith ("D")) {
|
||||
} else if (change.Type == SparkleChangeType.Deleted) {
|
||||
message += "-";
|
||||
|
||||
} else {
|
||||
} else if (change.Type == SparkleChangeType.Added) {
|
||||
message += "+";
|
||||
}
|
||||
|
||||
string path = line.Substring (3).Trim ("\"".ToCharArray ());
|
||||
message += " ‘" + EnsureSpecialCharacters (path) + "’\n";
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count == 10) {
|
||||
message += "...\n";
|
||||
break;
|
||||
message += " ‘" + change.Path + "’\n";
|
||||
}
|
||||
}
|
||||
|
||||
git_status.StandardOutput.ReadToEnd ();
|
||||
git_status.WaitForExit ();
|
||||
|
||||
if (string.IsNullOrWhiteSpace (message))
|
||||
return null;
|
||||
else
|
||||
|
|
|
@ -76,5 +76,34 @@ namespace SparkleLib {
|
|||
{
|
||||
return ((file.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint);
|
||||
}
|
||||
|
||||
|
||||
public static string ToPrettyDate (this DateTime timestamp)
|
||||
{
|
||||
TimeSpan time_diff = DateTime.Now.Subtract(timestamp);
|
||||
int day_diff = (int) time_diff.TotalDays;
|
||||
|
||||
if (day_diff == 0) {
|
||||
return "at " + timestamp.ToString ("HH:mm");
|
||||
|
||||
} else if (day_diff == 1) {
|
||||
return "yesterday at " + timestamp.ToString ("HH:mm");
|
||||
|
||||
} else if (day_diff < 7) {
|
||||
return timestamp.ToString ("dddd");
|
||||
|
||||
} else if (day_diff < 31) {
|
||||
if (day_diff < 14)
|
||||
return "last week";
|
||||
else
|
||||
return string.Format ("{0} weeks ago", Math.Ceiling ((double) day_diff / 7));
|
||||
|
||||
} else if (day_diff < 62) {
|
||||
return "last month";
|
||||
|
||||
} else {
|
||||
return string.Format ("{0} months ago", Math.Ceiling ((double) day_diff / 31));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace SparkleLib {
|
|||
|
||||
public enum SyncStatus {
|
||||
Idle,
|
||||
Paused,
|
||||
SyncUp,
|
||||
SyncDown,
|
||||
Error
|
||||
|
@ -46,7 +47,6 @@ namespace SparkleLib {
|
|||
|
||||
public abstract class SparkleRepoBase {
|
||||
|
||||
|
||||
public abstract bool SyncUp ();
|
||||
public abstract bool SyncDown ();
|
||||
public abstract void RestoreFile (string path, string revision, string target_file_path);
|
||||
|
@ -59,10 +59,10 @@ namespace SparkleLib {
|
|||
public abstract double HistorySize { get; }
|
||||
|
||||
public abstract List<string> ExcludePaths { get; }
|
||||
public abstract List<SparkleChange> UnsyncedChanges { get; }
|
||||
public abstract List<SparkleChangeSet> GetChangeSets ();
|
||||
public abstract List<SparkleChangeSet> GetChangeSets (string path);
|
||||
|
||||
|
||||
public static bool UseCustomWatcher = false;
|
||||
|
||||
|
||||
|
@ -82,12 +82,13 @@ namespace SparkleLib {
|
|||
public readonly string LocalPath;
|
||||
public readonly string Name;
|
||||
public readonly Uri RemoteUrl;
|
||||
public List<SparkleChangeSet> ChangeSets { get; protected set; }
|
||||
public List<SparkleChangeSet> ChangeSets { get; private set; }
|
||||
public SyncStatus Status { get; private set; }
|
||||
public ErrorStatus Error { get; protected set; }
|
||||
public bool IsBuffering { get; private set; }
|
||||
public double ProgressPercentage { get; private set; }
|
||||
public double ProgressSpeed { get; private set; }
|
||||
public DateTime LastSync { get { return ChangeSets [0].Timestamp; }}
|
||||
|
||||
public virtual string Identifier {
|
||||
get {
|
||||
|
@ -157,8 +158,12 @@ namespace SparkleLib {
|
|||
this.identifier = Identifier;
|
||||
ChangeSets = GetChangeSets ();
|
||||
|
||||
string identifier_file_path = Path.Combine (LocalPath, ".sparkleshare");
|
||||
File.SetAttributes (identifier_file_path, FileAttributes.Hidden);
|
||||
string is_paused = this.local_config.GetFolderOptionalAttribute (Name, "paused");
|
||||
if (is_paused != null && is_paused.Equals (bool.TrueString))
|
||||
Status = SyncStatus.Paused;
|
||||
|
||||
string identifier_file_path = Path.Combine (LocalPath, ".sparkleshare");
|
||||
File.SetAttributes (identifier_file_path, FileAttributes.Hidden);
|
||||
|
||||
if (!UseCustomWatcher)
|
||||
this.watcher = new SparkleWatcher (LocalPath);
|
||||
|
@ -171,7 +176,7 @@ namespace SparkleLib {
|
|||
|
||||
private void RemoteTimerElapsedDelegate (object sender, EventArgs args)
|
||||
{
|
||||
if (this.is_syncing || IsBuffering)
|
||||
if (this.is_syncing || IsBuffering || Status == SyncStatus.Paused)
|
||||
return;
|
||||
|
||||
int time_comparison = DateTime.Compare (this.last_poll, DateTime.Now.Subtract (this.poll_interval));
|
||||
|
@ -205,14 +210,16 @@ namespace SparkleLib {
|
|||
{
|
||||
// Sync up everything that changed since we've been offline
|
||||
new Thread (() => {
|
||||
if (HasRemoteChanges)
|
||||
SyncDownBase ();
|
||||
if (Status != SyncStatus.Paused) {
|
||||
if (HasRemoteChanges)
|
||||
SyncDownBase ();
|
||||
|
||||
if (HasUnsyncedChanges || HasLocalChanges) {
|
||||
do {
|
||||
SyncUpBase ();
|
||||
if (HasUnsyncedChanges || HasLocalChanges) {
|
||||
do {
|
||||
SyncUpBase ();
|
||||
|
||||
} while (HasLocalChanges);
|
||||
} while (HasLocalChanges);
|
||||
}
|
||||
}
|
||||
|
||||
if (!UseCustomWatcher)
|
||||
|
@ -237,6 +244,11 @@ namespace SparkleLib {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Status == SyncStatus.Paused) {
|
||||
ChangesDetected ();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this.buffer_lock) {
|
||||
if (IsBuffering || this.is_syncing || !HasLocalChanges)
|
||||
|
@ -304,10 +316,8 @@ namespace SparkleLib {
|
|||
|
||||
public void ForceRetry ()
|
||||
{
|
||||
if (Error == ErrorStatus.None || this.is_syncing)
|
||||
return;
|
||||
|
||||
SyncUpBase ();
|
||||
if (Error != ErrorStatus.None && !this.is_syncing)
|
||||
SyncUpBase ();
|
||||
}
|
||||
|
||||
|
||||
|
@ -388,6 +398,8 @@ namespace SparkleLib {
|
|||
|
||||
if (!UseCustomWatcher)
|
||||
this.watcher.Enable ();
|
||||
|
||||
this.status_message = "";
|
||||
}
|
||||
|
||||
|
||||
|
@ -530,7 +542,11 @@ namespace SparkleLib {
|
|||
Thread.Sleep (100);
|
||||
|
||||
SparkleLogger.LogInfo (Name, "Syncing due to announcement");
|
||||
SyncDownBase ();
|
||||
|
||||
if (Status == SyncStatus.Paused)
|
||||
SparkleLogger.LogInfo (Name, "We're paused, skipping sync");
|
||||
else
|
||||
SyncDownBase ();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -558,6 +574,35 @@ namespace SparkleLib {
|
|||
}
|
||||
|
||||
|
||||
public void Pause ()
|
||||
{
|
||||
if (Status == SyncStatus.Idle) {
|
||||
this.local_config.SetFolderOptionalAttribute (Name, "paused", bool.TrueString);
|
||||
Status = SyncStatus.Paused;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected string status_message = "";
|
||||
|
||||
public void Resume (string message)
|
||||
{
|
||||
this.status_message = message;
|
||||
|
||||
if (Status == SyncStatus.Paused) {
|
||||
this.local_config.SetFolderOptionalAttribute (Name, "paused", bool.FalseString);
|
||||
Status = SyncStatus.Idle;
|
||||
|
||||
if (HasUnsyncedChanges || HasLocalChanges) {
|
||||
do {
|
||||
SyncUpBase ();
|
||||
|
||||
} while (HasLocalChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
this.remote_timer.Stop ();
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Gtk;
|
||||
#if HAVE_APP_INDICATOR
|
||||
|
@ -32,6 +33,7 @@ namespace SparkleShare {
|
|||
private MenuItem recent_events_item;
|
||||
private MenuItem quit_item;
|
||||
private MenuItem state_item;
|
||||
private SparkleMenuItem [] state_menu_items;
|
||||
|
||||
#if HAVE_APP_INDICATOR
|
||||
private Indicator indicator;
|
||||
|
@ -89,6 +91,13 @@ namespace SparkleShare {
|
|||
Application.Invoke (delegate {
|
||||
(this.state_item.Child as Label).Text = state_text;
|
||||
this.state_item.ShowAll ();
|
||||
|
||||
if (Controller.Projects.Length == this.state_menu_items.Length) {
|
||||
for (int i = 0; i < Controller.Projects.Length; i++) {
|
||||
(this.state_menu_items [i].Child as Label).Text = Controller.Projects [i].StatusMessage;
|
||||
this.state_menu_items [i].ShowAll ();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -117,35 +126,68 @@ namespace SparkleShare {
|
|||
this.menu.Add (new SeparatorMenuItem ());
|
||||
this.menu.Add (folder_item);
|
||||
|
||||
if (Program.Controller.Folders.Count > 0) {
|
||||
|
||||
this.state_menu_items = new SparkleMenuItem [Controller.Projects.Length];
|
||||
|
||||
if (Controller.Projects.Length > 0) {
|
||||
int i = 0;
|
||||
foreach (string folder_name in Controller.Folders) {
|
||||
ImageMenuItem item = new SparkleMenuItem (folder_name);
|
||||
foreach (ProjectInfo project in Controller.Projects) {
|
||||
SparkleMenuItem item = new SparkleMenuItem (project.Name);
|
||||
|
||||
Gdk.Pixbuf folder_icon;
|
||||
folder_icon = IconTheme.Default.LoadIcon ("folder", 16, IconLookupFlags.GenericFallback);
|
||||
|
||||
if (!string.IsNullOrEmpty (Controller.FolderErrors [i])) {
|
||||
folder_icon = IconTheme.Default.LoadIcon ("dialog-warning", 16, IconLookupFlags.GenericFallback);
|
||||
item.Submenu = new Menu ();
|
||||
item.Submenu = new Menu ();
|
||||
|
||||
this.state_menu_items [i] = new MenuItem (project.StatusMessage) { Sensitive = false };
|
||||
|
||||
(item.Submenu as Menu).Add (this.state_menu_items [i]);
|
||||
(item.Submenu as Menu).Add (new SeparatorMenuItem ());
|
||||
|
||||
if (project.IsPaused) {
|
||||
MenuItem resume_item;
|
||||
|
||||
if (project.UnsyncedChangesInfo.Count > 0) {
|
||||
string icons_path = new string [] {SparkleUI.AssetsPath, "icons", "hicolor", "12x12", "status"}.Combine ();
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in project.UnsyncedChangesInfo)
|
||||
(item.Submenu as Menu).Add (new MenuItem (pair.Key) {
|
||||
Image = new Image () {
|
||||
File = new string [] {icons_path, pair.Value.Replace ("-12", ""))}.Combine () },
|
||||
Sensitive = false
|
||||
});
|
||||
|
||||
MenuItem error_item = new MenuItem (Controller.FolderErrors [i]) { Sensitive = false };
|
||||
MenuItem try_again_item = new MenuItem ("Try Again");
|
||||
try_again_item.Activated += Controller.TryAgainDelegate (folder_name);
|
||||
|
||||
(item.Submenu as Menu).Add (error_item);
|
||||
(item.Submenu as Menu).Add (new SeparatorMenuItem ());
|
||||
(item.Submenu as Menu).Add (try_again_item);
|
||||
|
||||
(item.Submenu as Menu).Add (new SeparatorMenuItem ());
|
||||
resume_item = new MenuItem ("Sync and Resume…");
|
||||
|
||||
} else {
|
||||
resume_item = new MenuItem ("Resume");
|
||||
}
|
||||
|
||||
resume_item.Activated += Controller.ResumeDelegate (project.Name);
|
||||
(item.Submenu as Menu).Add (resume_item);
|
||||
|
||||
} else {
|
||||
folder_icon = IconTheme.Default.LoadIcon ("folder", 16, IconLookupFlags.GenericFallback);
|
||||
item.Activated += Controller.OpenFolderDelegate (folder_name);
|
||||
if (Controller.Projects [i].HasError) {
|
||||
folder_icon = IconTheme.Default.LoadIcon ("dialog-warning", 16, IconLookupFlags.GenericFallback);
|
||||
|
||||
MenuItem try_again_item = new MenuItem ("Try Again");
|
||||
try_again_item.Activated += Controller.TryAgainDelegate (project.Name);
|
||||
(item.Submenu as Menu).Add (try_again_item);
|
||||
|
||||
} else {
|
||||
MenuItem pause_item = new MenuItem ("Pause");
|
||||
pause_item.Activated += Controller.PauseDelegate (project.Name);
|
||||
(item.Submenu as Menu).Add (pause_item);
|
||||
}
|
||||
}
|
||||
|
||||
(item.Child as Label).UseUnderline = false;
|
||||
item.Image = new Image (folder_icon);
|
||||
this.menu.Add (item);
|
||||
|
||||
|
||||
i++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.recent_events_item = new MenuItem ("Recent Changes…");
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<key>CFBundleName</key>
|
||||
<string>SparkleShare</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4</string>
|
||||
<string>1.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.4</string>
|
||||
<string>1.5.0</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
|
@ -35,7 +36,8 @@ namespace SparkleShare {
|
|||
private NSMenuItem state_item, folder_item, add_item, about_item, recent_events_item, quit_item,
|
||||
code_item, copy_item, link_code_item;
|
||||
|
||||
private NSMenuItem [] folder_menu_items, error_menu_items, try_again_menu_items;
|
||||
private NSMenuItem [] folder_menu_items, try_again_menu_items, pause_menu_items,
|
||||
resume_menu_items, state_menu_items;
|
||||
|
||||
private NSImage syncing_idle_image = NSImage.ImageNamed ("process-syncing-idle");
|
||||
private NSImage syncing_up_image = NSImage.ImageNamed ("process-syncing-up");
|
||||
|
@ -95,7 +97,14 @@ namespace SparkleShare {
|
|||
};
|
||||
|
||||
Controller.UpdateStatusItemEvent += delegate (string state_text) {
|
||||
Program.Controller.Invoke (() => { this.state_item.Title = state_text; });
|
||||
Program.Controller.Invoke (() => {
|
||||
this.state_item.Title = state_text;
|
||||
|
||||
if (Controller.Projects.Length == this.state_menu_items.Length) {
|
||||
for (int i = 0; i < Controller.Projects.Length; i++)
|
||||
this.state_menu_items [i].Title = Controller.Projects [i].StatusMessage;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Controller.UpdateMenuEvent += delegate {
|
||||
|
@ -168,36 +177,64 @@ namespace SparkleShare {
|
|||
Enabled = Controller.QuitItemEnabled
|
||||
};
|
||||
|
||||
this.folder_menu_items = new NSMenuItem [Controller.Folders.Length];
|
||||
this.error_menu_items = new NSMenuItem [Controller.Folders.Length];
|
||||
this.try_again_menu_items = new NSMenuItem [Controller.Folders.Length];
|
||||
this.folder_menu_items = new NSMenuItem [Controller.Projects.Length];
|
||||
this.try_again_menu_items = new NSMenuItem [Controller.Projects.Length];
|
||||
this.pause_menu_items = new NSMenuItem [Controller.Projects.Length];
|
||||
this.resume_menu_items = new NSMenuItem [Controller.Projects.Length];
|
||||
this.state_menu_items = new NSMenuItem [Controller.Projects.Length];
|
||||
|
||||
if (Controller.Folders.Length > 0) {
|
||||
if (Controller.Projects.Length > 0) {
|
||||
int i = 0;
|
||||
foreach (string folder_name in Controller.Folders) {
|
||||
NSMenuItem item = new NSMenuItem () { Title = folder_name };
|
||||
foreach (ProjectInfo project in Controller.Projects) {
|
||||
NSMenuItem item = new NSMenuItem () { Title = project.Name };
|
||||
this.folder_menu_items [i] = item;
|
||||
|
||||
if (!string.IsNullOrEmpty (Controller.FolderErrors [i])) {
|
||||
item.Image = this.caution_image;
|
||||
item.Submenu = new NSMenu ();
|
||||
item.Submenu = new NSMenu ();
|
||||
item.Image = this.folder_image;
|
||||
|
||||
this.error_menu_items [i] = new NSMenuItem ();
|
||||
this.error_menu_items [i].Title = Controller.FolderErrors [i];
|
||||
this.state_menu_items [i] = new NSMenuItem (project.StatusMessage);
|
||||
|
||||
this.try_again_menu_items [i] = new NSMenuItem ();
|
||||
this.try_again_menu_items [i].Title = "Try Again";
|
||||
this.try_again_menu_items [i].Activated += Controller.TryAgainDelegate (folder_name);;
|
||||
item.Submenu.AddItem (this.state_menu_items [i]);
|
||||
item.Submenu.AddItem (NSMenuItem.SeparatorItem);
|
||||
|
||||
item.Submenu.AddItem (this.error_menu_items [i]);
|
||||
item.Submenu.AddItem (NSMenuItem.SeparatorItem);
|
||||
item.Submenu.AddItem (this.try_again_menu_items [i]);
|
||||
if (project.IsPaused) {
|
||||
if (project.UnsyncedChangesInfo.Count > 0) {
|
||||
foreach (KeyValuePair<string, string> pair in project.UnsyncedChangesInfo)
|
||||
item.Submenu.AddItem (new NSMenuItem (pair.Key) {
|
||||
Image = NSImage.ImageNamed (pair.Value)
|
||||
});
|
||||
|
||||
item.Submenu.AddItem (NSMenuItem.SeparatorItem);
|
||||
this.resume_menu_items [i] = new NSMenuItem ("Sync and Resume…");
|
||||
|
||||
} else {
|
||||
this.resume_menu_items [i] = new NSMenuItem ("Resume");
|
||||
}
|
||||
|
||||
this.resume_menu_items [i].Activated += Controller.ResumeDelegate (project.Name);
|
||||
item.Submenu.AddItem (this.resume_menu_items [i]);
|
||||
|
||||
} else {
|
||||
item.Image = this.folder_image;
|
||||
this.folder_menu_items [i].Activated += Controller.OpenFolderDelegate (folder_name);
|
||||
if (Controller.Projects [i].HasError) {
|
||||
item.Image = this.caution_image;
|
||||
|
||||
this.try_again_menu_items [i] = new NSMenuItem ();
|
||||
this.try_again_menu_items [i].Title = "Try Again";
|
||||
this.try_again_menu_items [i].Activated += Controller.TryAgainDelegate (project.Name);
|
||||
|
||||
item.Submenu.AddItem (this.try_again_menu_items [i]);
|
||||
|
||||
} else {
|
||||
this.pause_menu_items [i] = new NSMenuItem ("Pause");
|
||||
this.pause_menu_items [i].Activated += Controller.PauseDelegate (project.Name);
|
||||
|
||||
item.Submenu.AddItem (this.pause_menu_items [i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Controller.Projects [i].HasError)
|
||||
this.folder_menu_items [i].Activated += Controller.OpenFolderDelegate (project.Name);
|
||||
|
||||
item.Image.Size = new SizeF (16, 16);
|
||||
i++;
|
||||
};
|
||||
|
@ -263,6 +300,5 @@ namespace SparkleShare {
|
|||
MenuIsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,77 @@ namespace SparkleShare {
|
|||
}
|
||||
|
||||
|
||||
public class ProjectInfo {
|
||||
|
||||
private SparkleRepoBase repo;
|
||||
|
||||
public string Name { get { return this.repo.Name; }}
|
||||
public string Path { get { return this.repo.LocalPath; }}
|
||||
|
||||
public bool IsPaused { get { return this.repo.Status == SyncStatus.Paused; }}
|
||||
public bool HasError { get { return this.repo.Status == SyncStatus.Error; }}
|
||||
|
||||
|
||||
public string StatusMessage {
|
||||
get {
|
||||
string status_message = string.Format ("Synced {0}", this.repo.LastSync.ToPrettyDate ());
|
||||
|
||||
if (this.repo.Status == SyncStatus.SyncUp)
|
||||
status_message = "Sending changes… " + this.repo.ProgressPercentage + "%";
|
||||
|
||||
if (this.repo.Status == SyncStatus.SyncDown)
|
||||
status_message = "Receiving changes… " + this.repo.ProgressPercentage + "%";
|
||||
|
||||
if (this.repo.Status == SyncStatus.SyncUp || this.repo.Status == SyncStatus.SyncDown) {
|
||||
if (this.repo.ProgressSpeed > 0)
|
||||
status_message += " " + this.repo.ProgressSpeed.ToSize () + "/s";
|
||||
}
|
||||
|
||||
if (IsPaused) {
|
||||
return "Paused";
|
||||
|
||||
} else if (HasError) {
|
||||
switch (this.repo.Error) {
|
||||
case ErrorStatus.HostUnreachable: return "Can’t reach the host";
|
||||
case ErrorStatus.HostIdentityChanged: return "The host’s identity has changed";
|
||||
case ErrorStatus.AuthenticationFailed: return "Authentication failed";
|
||||
case ErrorStatus.DiskSpaceExceeded: return "Host is out of disk space";
|
||||
case ErrorStatus.UnreadableFiles: return "Some local files are unreadable or in use";
|
||||
case ErrorStatus.NotFound: return "Project doesn’t exist on host";
|
||||
case ErrorStatus.IncompatibleClientServer: return "Incompatible client/server versions";
|
||||
}
|
||||
}
|
||||
|
||||
return status_message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Dictionary<string, string> UnsyncedChangesInfo {
|
||||
get {
|
||||
Dictionary<string, string> changes_info = new Dictionary<string, string> ();
|
||||
|
||||
foreach (SparkleChange change in repo.UnsyncedChanges) {
|
||||
switch (change.Type) {
|
||||
case SparkleChangeType.Added: changes_info [change.Path] = "document-added-12.png"; break;
|
||||
case SparkleChangeType.Edited: changes_info [change.Path] = "document-edited-12.png"; break;
|
||||
case SparkleChangeType.Deleted: changes_info [change.Path] = "document-deleted-12.png"; break;
|
||||
case SparkleChangeType.Moved: changes_info [change.MovedToPath] = "document-moved-12.png"; break;
|
||||
}
|
||||
}
|
||||
|
||||
return changes_info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ProjectInfo (SparkleRepoBase repo)
|
||||
{
|
||||
this.repo = repo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SparkleStatusIconController {
|
||||
|
||||
public event UpdateIconEventHandler UpdateIconEvent = delegate { };
|
||||
|
@ -50,23 +121,8 @@ namespace SparkleShare {
|
|||
public IconState CurrentState = IconState.Idle;
|
||||
public string StateText = "Welcome to SparkleShare!";
|
||||
|
||||
public string [] Folders = new string [0];
|
||||
public string [] FolderErrors = new string [0];
|
||||
|
||||
public ProjectInfo [] Projects = new ProjectInfo [0];
|
||||
|
||||
public string FolderSize {
|
||||
get {
|
||||
double size = 0;
|
||||
|
||||
foreach (SparkleRepoBase repo in Program.Controller.Repositories)
|
||||
size += repo.Size;
|
||||
|
||||
if (size == 0)
|
||||
return "";
|
||||
else
|
||||
return "— " + size.ToSize ();
|
||||
}
|
||||
}
|
||||
|
||||
public int ProgressPercentage {
|
||||
get {
|
||||
|
@ -122,10 +178,10 @@ namespace SparkleShare {
|
|||
if (CurrentState != IconState.Error) {
|
||||
CurrentState = IconState.Idle;
|
||||
|
||||
if (Folders.Length == 0)
|
||||
if (Projects.Length == 0)
|
||||
StateText = "Welcome to SparkleShare!";
|
||||
else
|
||||
StateText = "Projects up to date " + FolderSize;
|
||||
StateText = "Projects up to date";
|
||||
}
|
||||
|
||||
UpdateFolders ();
|
||||
|
@ -138,16 +194,17 @@ namespace SparkleShare {
|
|||
if (CurrentState != IconState.Error) {
|
||||
CurrentState = IconState.Idle;
|
||||
|
||||
if (Folders.Length == 0)
|
||||
if (Projects.Length == 0)
|
||||
StateText = "Welcome to SparkleShare!";
|
||||
else
|
||||
StateText = "Projects up to date " + FolderSize;
|
||||
StateText = "Projects up to date";
|
||||
}
|
||||
|
||||
UpdateFolders ();
|
||||
|
||||
UpdateIconEvent (CurrentState);
|
||||
UpdateStatusItemEvent (StateText);
|
||||
UpdateQuitItemEvent (QuitItemEnabled);
|
||||
UpdateMenuEvent (CurrentState);
|
||||
};
|
||||
|
||||
|
@ -188,17 +245,18 @@ namespace SparkleShare {
|
|||
|
||||
Program.Controller.OnError += delegate {
|
||||
CurrentState = IconState.Error;
|
||||
StateText = "Failed to send some changes";
|
||||
StateText = "Some changes weren’t synced";
|
||||
|
||||
UpdateFolders ();
|
||||
|
||||
UpdateIconEvent (CurrentState);
|
||||
UpdateStatusItemEvent (StateText);
|
||||
UpdateQuitItemEvent (QuitItemEnabled);
|
||||
UpdateMenuEvent (CurrentState);
|
||||
};
|
||||
|
||||
|
||||
// FIXME: Hack to work around a race condition causing
|
||||
// FIXME: Work around a race condition causing
|
||||
// the icon to not always show the right state
|
||||
Timers.Timer timer = new Timers.Timer () { Interval = 30 * 1000 };
|
||||
|
||||
|
@ -211,32 +269,7 @@ namespace SparkleShare {
|
|||
}
|
||||
|
||||
|
||||
public void SubfolderClicked (string subfolder)
|
||||
{
|
||||
Program.Controller.OpenSparkleShareFolder (subfolder);
|
||||
}
|
||||
|
||||
|
||||
public void TryAgainClicked (string subfolder)
|
||||
{
|
||||
foreach (SparkleRepoBase repo in Program.Controller.Repositories)
|
||||
if (repo.Name.Equals (subfolder))
|
||||
new Thread (() => repo.ForceRetry ()).Start ();
|
||||
}
|
||||
|
||||
|
||||
public EventHandler OpenFolderDelegate (string subfolder)
|
||||
{
|
||||
return delegate { SubfolderClicked (subfolder); };
|
||||
}
|
||||
|
||||
|
||||
public EventHandler TryAgainDelegate (string subfolder)
|
||||
{
|
||||
return delegate { TryAgainClicked (subfolder); };
|
||||
}
|
||||
|
||||
|
||||
// Main menu items
|
||||
public void RecentEventsClicked ()
|
||||
{
|
||||
new Thread (() => {
|
||||
|
@ -248,71 +281,95 @@ namespace SparkleShare {
|
|||
}).Start ();
|
||||
}
|
||||
|
||||
|
||||
public void AddHostedProjectClicked ()
|
||||
{
|
||||
new Thread (() => Program.Controller.ShowSetupWindow (PageType.Add)).Start ();
|
||||
}
|
||||
|
||||
|
||||
public void CopyToClipboardClicked ()
|
||||
{
|
||||
Program.Controller.CopyToClipboard (Program.Controller.CurrentUser.PublicKey);
|
||||
}
|
||||
|
||||
|
||||
public void AboutClicked ()
|
||||
{
|
||||
Program.Controller.ShowAboutWindow ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void QuitClicked ()
|
||||
{
|
||||
Program.Controller.Quit ();
|
||||
}
|
||||
|
||||
|
||||
private Object folders_lock = new Object ();
|
||||
// Project items
|
||||
public void ProjectClicked (string project)
|
||||
{
|
||||
Program.Controller.OpenSparkleShareFolder (project);
|
||||
}
|
||||
|
||||
public void PauseClicked (string project)
|
||||
{
|
||||
GetRepoByName (project).Pause ();
|
||||
UpdateMenuEvent (CurrentState);
|
||||
}
|
||||
|
||||
public void ResumeClicked (string project)
|
||||
{
|
||||
new Thread (() => GetRepoByName (project).Resume ("")).Start ();
|
||||
UpdateMenuEvent (CurrentState);
|
||||
}
|
||||
|
||||
public void TryAgainClicked (string project)
|
||||
{
|
||||
new Thread (() => GetRepoByName (project).ForceRetry ()).Start ();
|
||||
}
|
||||
|
||||
|
||||
// Helper delegates
|
||||
public EventHandler OpenFolderDelegate (string project)
|
||||
{
|
||||
return delegate { ProjectClicked (project); };
|
||||
}
|
||||
|
||||
public EventHandler TryAgainDelegate (string project)
|
||||
{
|
||||
return delegate { TryAgainClicked (project); };
|
||||
}
|
||||
|
||||
public EventHandler PauseDelegate (string project)
|
||||
{
|
||||
return delegate { PauseClicked (project); };
|
||||
}
|
||||
|
||||
public EventHandler ResumeDelegate (string project)
|
||||
{
|
||||
return delegate { ResumeClicked (project); };
|
||||
}
|
||||
|
||||
|
||||
private Object projects_lock = new Object ();
|
||||
|
||||
private void UpdateFolders ()
|
||||
{
|
||||
lock (this.folders_lock) {
|
||||
List<string> folders = new List<string> ();
|
||||
List<string> folder_errors = new List<string> ();
|
||||
lock (this.projects_lock) {
|
||||
List<ProjectInfo> projects = new List<ProjectInfo> ();
|
||||
|
||||
foreach (SparkleRepoBase repo in Program.Controller.Repositories) {
|
||||
folders.Add (repo.Name);
|
||||
|
||||
if (repo.Error == ErrorStatus.HostUnreachable) {
|
||||
folder_errors.Add ("Can't reach the host");
|
||||
|
||||
} else if (repo.Error == ErrorStatus.HostIdentityChanged) {
|
||||
folder_errors.Add ("The host's identity has changed");
|
||||
|
||||
} else if (repo.Error == ErrorStatus.AuthenticationFailed) {
|
||||
folder_errors.Add ("Authentication failed");
|
||||
|
||||
} else if (repo.Error == ErrorStatus.DiskSpaceExceeded) {
|
||||
folder_errors.Add ("Host is out of disk space");
|
||||
|
||||
} else if (repo.Error == ErrorStatus.UnreadableFiles) {
|
||||
folder_errors.Add ("Some local files are unreadable or in use");
|
||||
|
||||
} else if (repo.Error == ErrorStatus.NotFound) {
|
||||
folder_errors.Add ("Project doesn't exist on host");
|
||||
|
||||
} else if (repo.Error == ErrorStatus.IncompatibleClientServer) {
|
||||
folder_errors.Add ("Incompatible client/server versions");
|
||||
|
||||
} else {
|
||||
folder_errors.Add ("");
|
||||
}
|
||||
}
|
||||
|
||||
Folders = folders.ToArray ();
|
||||
FolderErrors = folder_errors.ToArray ();
|
||||
foreach (SparkleRepoBase repo in Program.Controller.Repositories)
|
||||
projects.Add (new ProjectInfo (repo));
|
||||
|
||||
Projects = projects.ToArray ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SparkleRepoBase GetRepoByName (string name)
|
||||
{
|
||||
foreach (SparkleRepoBase repo in Program.Controller.Repositories)
|
||||
if (repo.Name.Equals (name))
|
||||
return repo;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
|
||||
<Product Name='SparkleShare' Id='184950D5-67F6-4D06-9717-7E2F1607A7B0' UpgradeCode='D3DF1D99-87F5-47A7-A349-863DD6E4B73A'
|
||||
Language='1033' Codepage='1252' Version='1.4' Manufacturer='SparkleShare'>
|
||||
Language='1033' Codepage='1252' Version='1.5.0' Manufacturer='SparkleShare'>
|
||||
|
||||
<Package Id='*' Keywords='Installer' Description="SparkleShare Setup" Manufacturer='SparkleShare'
|
||||
InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
@ -44,7 +45,8 @@ namespace SparkleShare {
|
|||
private SparkleMenuItem log_item;
|
||||
private SparkleMenuItem state_item;
|
||||
private SparkleMenuItem exit_item;
|
||||
|
||||
private SparkleMenuItem [] state_menu_items;
|
||||
|
||||
private SparkleNotifyIcon notify_icon = new SparkleNotifyIcon ();
|
||||
|
||||
|
||||
|
@ -86,6 +88,12 @@ namespace SparkleShare {
|
|||
Dispatcher.BeginInvoke ((Action) delegate {
|
||||
this.state_item.Header = state_text;
|
||||
this.state_item.UpdateLayout ();
|
||||
|
||||
if (Controller.Projects.Length == this.state_menu_items.Length) {
|
||||
for (int i = 0; i < Controller.Projects.Length; i++)
|
||||
this.state_menu_items [i].Header = Controller.Projects [i].StatusMessage;
|
||||
}
|
||||
|
||||
this.notify_icon.HeaderText = "SparkleShare\n" + state_text;
|
||||
});
|
||||
};
|
||||
|
@ -183,51 +191,75 @@ namespace SparkleShare {
|
|||
this.context_menu.Items.Add (new Separator ());
|
||||
this.context_menu.Items.Add (folder_item);
|
||||
|
||||
if (Controller.Folders.Length > 0) {
|
||||
this.state_menu_items = new SparkleMenuItem [Controller.Projects.Length];
|
||||
|
||||
if (Controller.Projects.Length > 0) {
|
||||
int i = 0;
|
||||
foreach (string folder_name in Controller.Folders) {
|
||||
foreach (ProjectInfo project in Controller.Projects) {
|
||||
SparkleMenuItem subfolder_item = new SparkleMenuItem () {
|
||||
Header = folder_name.Replace ("_", "__")
|
||||
Header = project.Name.Replace ("_", "__")
|
||||
};
|
||||
|
||||
Image subfolder_image = new Image () {
|
||||
|
||||
item.Icon = new Image () {
|
||||
Source = SparkleUIHelpers.GetImageSource ("folder"),
|
||||
Width = 16,
|
||||
Height = 16
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty (Controller.FolderErrors [i])) {
|
||||
subfolder_item.Icon = new Image () {
|
||||
Source = (BitmapSource) Imaging.CreateBitmapSourceFromHIcon (
|
||||
System.Drawing.SystemIcons.Exclamation.Handle, Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromWidthAndHeight (16,16))
|
||||
};
|
||||
|
||||
SparkleMenuItem error_item = new SparkleMenuItem () {
|
||||
Header = Controller.FolderErrors [i],
|
||||
IsEnabled = false
|
||||
};
|
||||
this.state_menu_items [i] = new SparkleMenuItem () {
|
||||
Header = project.StatusMessage,
|
||||
IsEnabled = false
|
||||
};
|
||||
|
||||
item.Items.Add (this.state_menu_items [i]);
|
||||
item.Items.Add (new Separator ());
|
||||
|
||||
if (project.IsPaused) {
|
||||
SparkleMenuItem resume_item;
|
||||
|
||||
SparkleMenuItem try_again_item = new SparkleMenuItem () {
|
||||
Header = "Try again"
|
||||
};
|
||||
|
||||
try_again_item.Click += delegate { Controller.TryAgainDelegate (folder_name); };
|
||||
|
||||
subfolder_item.Items.Add (error_item);
|
||||
subfolder_item.Items.Add (new Separator ());
|
||||
subfolder_item.Items.Add (try_again_item);
|
||||
if (project.UnsyncedChangesInfo.Count > 0) {
|
||||
foreach (KeyValuePair<string, string> pair in project.UnsyncedChangesInfo)
|
||||
item.Items.Add (new SparkleMenuItem () {
|
||||
Header = pair.Key,
|
||||
// TODO image
|
||||
IsEnabled = false
|
||||
});
|
||||
|
||||
item.Items.Add (new Separator ());
|
||||
resume_item = new SparkleMenuItem () { Header = "Sync and Resume…" };
|
||||
|
||||
} else {
|
||||
resume_item = new SparkleMenuItem () { Header = "Resume" };
|
||||
}
|
||||
|
||||
resume_item.Click += Controller.ResumeDelegate (project.Name);
|
||||
item.Items.Add (resume_item);
|
||||
|
||||
} else {
|
||||
subfolder_item.Icon = subfolder_image;
|
||||
subfolder_item.Click += new RoutedEventHandler (Controller.OpenFolderDelegate (folder_name));
|
||||
if (Controller.Projects [i].HasError) {
|
||||
item.Icon = new Image () {
|
||||
Source = (BitmapSource) Imaging.CreateBitmapSourceFromHIcon (
|
||||
System.Drawing.SystemIcons.Exclamation.Handle, Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromWidthAndHeight (16,16))
|
||||
};
|
||||
|
||||
SparkleMenuItem try_again_item = new SparkleMenuItem () { Header = "Try Again" };
|
||||
try_again_item.Click += Controller.TryAgainDelegate (project.Name);
|
||||
item.Items.Add (try_again_item);
|
||||
|
||||
} else {
|
||||
SparkleMenuItem pause_item = new SparkleMenuItem () { Header = "Pause" };
|
||||
pause_item.Click += Controller.PauseDelegate (project.Name);
|
||||
item.Items.Add (pause_item);
|
||||
}
|
||||
}
|
||||
|
||||
this.context_menu.Items.Add (subfolder_item);
|
||||
this.context_menu.Items.Add (item);
|
||||
i++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
folder_item.Items.Add (this.log_item);
|
||||
folder_item.Items.Add (add_item);
|
||||
folder_item.Items.Add (new Separator ());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
dnl Process this file with autoconf to produce a configure script.
|
||||
m4_define([sparkleshare_version], [1.4])
|
||||
m4_define([sparkleshare_version], [1.5.0])
|
||||
|
||||
AC_PREREQ([2.54])
|
||||
AC_INIT([SparkleShare], sparkleshare_version)
|
||||
|
|
Loading…
Reference in a new issue