Merge branch 'pausing'

This commit is contained in:
Hylke Bons 2014-11-01 10:21:43 +00:00
commit 2797ece573
12 changed files with 500 additions and 206 deletions

View file

@ -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:

View file

@ -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.")]

View file

@ -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

View file

@ -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));
}
}
}
}

View file

@ -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 ();

View file

@ -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…");

View file

@ -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>

View file

@ -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;
}
}
}
}

View file

@ -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 "Cant reach the host";
case ErrorStatus.HostIdentityChanged: return "The hosts 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 doesnt 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 werent 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;
}
}
}

View file

@ -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' />

View file

@ -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 ());

View file

@ -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)