Merge branch 'master' into gettext-cs

Conflicts:
	SparkleLib/Git/SparkleFetcherGit.cs
This commit is contained in:
serras 2012-02-12 16:24:12 +01:00
commit e2e706dc6a
42 changed files with 1151 additions and 2256 deletions

View file

@ -1,4 +1,4 @@
basedirs = build help SparkleLib data po basedirs = build SparkleLib data po
SUBDIRS = $(basedirs) $(GUISUBDIRS) SUBDIRS = $(basedirs) $(GUISUBDIRS)
DIST_SUBDIRS = $(basedirs) SparkleShare DIST_SUBDIRS = $(basedirs) SparkleShare

11
NEWS
View file

@ -1,4 +1,11 @@
0.8.1 for Linux and Mac (Sun Jan 29): 0.8.2 for Linux and Mac (Sat Feb 11 2012):
Hylke:
- Use the more reliable and less resource intensive FSEvents on Mac.
- Improvements to the reconnect mechanism of the notification system
0.8.1 for Linux and Mac (Sun Jan 29 2012):
Hylke: Hylke:
- Disable the Quit menu item when a sync is going on - Disable the Quit menu item when a sync is going on
@ -9,7 +16,7 @@
- Close event log on Cmd+W on Mac - Close event log on Cmd+W on Mac
0.8.0 for Linux and Mac (Sun Jan 22): 0.8.0 for Linux and Mac (Sun Jan 22 2012):
Hylke: Hylke:
- Show syncing progress in the status icon - Show syncing progress in the status icon

View file

@ -82,8 +82,7 @@ $ sudo apt-get install libappindicator0.1-cil-dev
```bash ```bash
$ sudo yum install gtk-sharp2-devel mono-core mono-devel monodevelop \ $ sudo yum install gtk-sharp2-devel mono-core mono-devel monodevelop \
ndesk-dbus-devel ndesk-dbus-glib-devel nautilus-python-devel nant \ ndesk-dbus-devel ndesk-dbus-glib-devel nautilus-python-devel nant \
notify-sharp-devel webkit-sharp-devel webkitgtk-devel libtool intltool \ notify-sharp-devel webkit-sharp-devel webkitgtk-devel libtool intltool
gnome-doc-utils
``` ```

View file

@ -79,8 +79,8 @@ namespace SparkleLib {
} }
base.target_folder = target_folder; TargetFolder = target_folder;
base.remote_url = uri.ToString (); RemoteUrl = uri.ToString ();
} }
@ -89,8 +89,7 @@ namespace SparkleLib {
this.git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath, this.git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath,
"clone " + "clone " +
"--progress " + // Redirects progress stats to standarderror "--progress " + // Redirects progress stats to standarderror
"\"" + base.remote_url + "\" " + "\"" + RemoteUrl + "\" " + "\"" + SparkleHelpers.NormalizeSeparatorsToOS(TargetFolder) + "\"");
"\"" + SparkleHelpers.NormalizeSeparatorsToOS(base.target_folder) + "\"");
this.git.StartInfo.RedirectStandardError = true; this.git.StartInfo.RedirectStandardError = true;
this.git.Start (); this.git.Start ();
@ -140,35 +139,36 @@ namespace SparkleLib {
} else { } else {
InstallConfiguration (); InstallConfiguration ();
InstallExcludeRules (); InstallExcludeRules ();
AddWarnings ();
return true; return true;
} }
} }
public override string [] Warnings { private void AddWarnings ()
get { {
SparkleGit git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath, SparkleGit git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath,
"config --global core.excludesfile"); "config --global core.excludesfile");
git.Start (); git.Start ();
// Reading the standard output HAS to go before // Reading the standard output HAS to go before
// WaitForExit, or it will hang forever on output > 4096 bytes // WaitForExit, or it will hang forever on output > 4096 bytes
string output = git.StandardOutput.ReadToEnd ().Trim (); string output = git.StandardOutput.ReadToEnd ().Trim ();
git.WaitForExit (); git.WaitForExit ();
if (string.IsNullOrEmpty (output)) { if (string.IsNullOrEmpty (output)) {
return null; return;
} else { } else {
return new string [] { Warnings = new string [] {
string.Format ("You seem to have configured a system gitignore file. " + string.Format ("You seem to have configured a system gitignore file. " +
"This may interfere with SparkleShare.\n({0})", output) "This may interfere with SparkleShare.\n({0})", output)
}; };
}
} }
} }
public override void Stop () public override void Stop ()
{ {
if (this.git != null && !this.git.HasExited) { if (this.git != null && !this.git.HasExited) {
@ -176,7 +176,7 @@ namespace SparkleLib {
this.git.Dispose (); this.git.Dispose ();
} }
base.Stop (); Dispose ();
} }
@ -184,7 +184,7 @@ namespace SparkleLib {
// the newly cloned repository // the newly cloned repository
private void InstallConfiguration () private void InstallConfiguration ()
{ {
string repo_config_file_path = SparkleHelpers.CombineMore (base.target_folder, ".git", "config"); string repo_config_file_path = SparkleHelpers.CombineMore (TargetFolder, ".git", "config");
string config = String.Join (Environment.NewLine, File.ReadAllLines (repo_config_file_path)); string config = String.Join (Environment.NewLine, File.ReadAllLines (repo_config_file_path));
string n = Environment.NewLine; string n = Environment.NewLine;
@ -220,7 +220,7 @@ namespace SparkleLib {
private void InstallExcludeRules () private void InstallExcludeRules ()
{ {
DirectoryInfo info = Directory.CreateDirectory ( DirectoryInfo info = Directory.CreateDirectory (
SparkleHelpers.CombineMore (this.target_folder, ".git", "info")); SparkleHelpers.CombineMore (TargetFolder, ".git", "info"));
// File that lists the files we want git to ignore // File that lists the files we want git to ignore
string exclude_rules_file_path = Path.Combine (info.FullName, "exclude"); string exclude_rules_file_path = Path.Combine (info.FullName, "exclude");

View file

@ -16,6 +16,7 @@
using System; using System;
using System.IO;
using System.Diagnostics; using System.Diagnostics;
namespace SparkleLib { namespace SparkleLib {
@ -23,28 +24,58 @@ namespace SparkleLib {
public class SparkleGit : Process { public class SparkleGit : Process {
public static string ExecPath = null; public static string ExecPath = null;
public static string Path = null;
public SparkleGit (string path, string args) : base () public SparkleGit (string path, string args) : base ()
{ {
Path = LocateGit ();
EnableRaisingEvents = true; EnableRaisingEvents = true;
StartInfo.FileName = SparkleBackend.DefaultBackend.Path; StartInfo.FileName = Path;
StartInfo.RedirectStandardOutput = true; StartInfo.RedirectStandardOutput = true;
StartInfo.UseShellExecute = false; StartInfo.UseShellExecute = false;
StartInfo.WorkingDirectory = path; StartInfo.WorkingDirectory = path;
StartInfo.CreateNoWindow = true; StartInfo.CreateNoWindow = true;
if (!string.IsNullOrEmpty (ExecPath)) if (string.IsNullOrEmpty (ExecPath))
StartInfo.Arguments = "--exec-path=\"" + ExecPath + "\" " + args;
else
StartInfo.Arguments = args; StartInfo.Arguments = args;
else
StartInfo.Arguments = "--exec-path=\"" + ExecPath + "\" " + args;
} }
new public void Start () new public void Start ()
{ {
SparkleHelpers.DebugInfo ("Cmd", "git " + StartInfo.Arguments); SparkleHelpers.DebugInfo ("Cmd", "git " + StartInfo.Arguments);
base.Start ();
try {
base.Start ();
} catch (Exception e) {
SparkleHelpers.DebugInfo ("Cmd", "There's a problem running Git: " + e.Message);
Environment.Exit (-1);
}
}
private string LocateGit ()
{
if (!string.IsNullOrEmpty (Path))
return Path;
string [] possible_git_paths = new string [] {
"/usr/bin/git",
"/usr/local/bin/git",
"/opt/local/bin/git",
"/usr/local/git/bin/git"
};
foreach (string path in possible_git_paths)
if (File.Exists (path))
return path;
return "git";
} }
} }
} }

View file

@ -17,10 +17,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml;
namespace SparkleLib { namespace SparkleLib {
@ -63,7 +61,7 @@ namespace SparkleLib {
public override List<string> ExcludePaths { public override List<string> ExcludePaths {
get { get {
List<string> rules = new List<string> (); List<string> rules = new List<string> ();
rules.Add (Path.DirectorySeparatorChar + ".git"); rules.Add (".git");
return rules; return rules;
} }
@ -98,12 +96,12 @@ namespace SparkleLib {
} }
private void CalculateSizes () private void UpdateSizes ()
{ {
double size = CalculateSize ( double size = CalculateSizes (
new DirectoryInfo (LocalPath)); new DirectoryInfo (LocalPath));
double history_size = CalculateSize ( double history_size = CalculateSizes (
new DirectoryInfo (Path.Combine (LocalPath, ".git"))); new DirectoryInfo (Path.Combine (LocalPath, ".git")));
string size_file_path = new string [] {LocalPath, ".git", "repo_size"}.Combine (); string size_file_path = new string [] {LocalPath, ".git", "repo_size"}.Combine ();
@ -167,32 +165,36 @@ namespace SparkleLib {
} }
public override bool CheckForRemoteChanges () public override bool HasRemoteChanges
{ {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Checking for remote changes..."); get {
SparkleGit git = new SparkleGit (LocalPath, "ls-remote " + Url + " master"); SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Checking for remote changes...");
SparkleGit git = new SparkleGit (LocalPath, "ls-remote " + Url + " master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode != 0)
return false;
string remote_revision = git.StandardOutput.ReadToEnd ().TrimEnd ();
if (!remote_revision.StartsWith (CurrentRevision)) {
SparkleHelpers.DebugInfo ("Git",
"[" + Name + "] Remote changes found. (" + remote_revision + ")");
git.Start (); return true;
git.WaitForExit ();
if (git.ExitCode != 0) } else {
return false; return false;
}
string remote_revision = git.StandardOutput.ReadToEnd ().TrimEnd ();
if (!remote_revision.StartsWith (CurrentRevision)) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found. (" + remote_revision + ")");
return true;
} else {
return false;
} }
} }
public override bool SyncUp () public override bool SyncUp ()
{ {
if (AnyDifferences) { if (HasLocalChanges) {
Add (); Add ();
string message = FormatCommitMessage (); string message = FormatCommitMessage ();
@ -242,13 +244,13 @@ namespace SparkleLib {
if (number >= percentage) { if (number >= percentage) {
percentage = number; percentage = number;
base.OnSyncProgressChanged (percentage, speed); base.OnProgressChanged (percentage, speed);
} }
} }
git.WaitForExit (); git.WaitForExit ();
CalculateSizes (); UpdateSizes ();
if (git.ExitCode == 0) if (git.ExitCode == 0)
return true; return true;
@ -299,13 +301,13 @@ namespace SparkleLib {
if (number >= percentage) { if (number >= percentage) {
percentage = number; percentage = number;
base.OnSyncProgressChanged (percentage, speed); base.OnProgressChanged (percentage, speed);
} }
} }
git.WaitForExit (); git.WaitForExit ();
CalculateSizes (); UpdateSizes ();
if (git.ExitCode == 0) { if (git.ExitCode == 0) {
Rebase (); Rebase ();
@ -317,7 +319,7 @@ namespace SparkleLib {
} }
public override bool AnyDifferences { public override bool HasLocalChanges {
get { get {
PrepareDirectories (LocalPath); PrepareDirectories (LocalPath);
@ -412,7 +414,7 @@ namespace SparkleLib {
{ {
DisableWatching (); DisableWatching ();
if (AnyDifferences) { if (HasLocalChanges) {
Add (); Add ();
string commit_message = FormatCommitMessage (); string commit_message = FormatCommitMessage ();
@ -427,7 +429,7 @@ namespace SparkleLib {
if (git.ExitCode != 0) { if (git.ExitCode != 0) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict detected. Trying to get out..."); SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict detected. Trying to get out...");
while (AnyDifferences) while (HasLocalChanges)
ResolveConflict (); ResolveConflict ();
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved."); SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
@ -653,7 +655,8 @@ namespace SparkleLib {
string to_file_path; string to_file_path;
if (file_path.EndsWith (".empty")) if (file_path.EndsWith (".empty"))
file_path = file_path.Substring (0, file_path.Length - ".empty".Length); file_path = file_path.Substring (0,
file_path.Length - ".empty".Length);
if (change_type.Equals ("A") && !file_path.Contains (".notes")) { if (change_type.Equals ("A") && !file_path.Contains (".notes")) {
change_set.Added.Add (file_path); change_set.Added.Add (file_path);
@ -669,6 +672,14 @@ namespace SparkleLib {
file_path = entry_line.Substring (42, tab_pos - 42); file_path = entry_line.Substring (42, tab_pos - 42);
to_file_path = entry_line.Substring (tab_pos + 1); to_file_path = entry_line.Substring (tab_pos + 1);
if (file_path.EndsWith (".empty"))
file_path = file_path.Substring (0,
file_path.Length - ".empty".Length);
if (to_file_path.EndsWith (".empty"))
to_file_path = to_file_path.Substring (0,
to_file_path.Length - ".empty".Length);
change_set.MovedFrom.Add (file_path); change_set.MovedFrom.Add (file_path);
change_set.MovedTo.Add (to_file_path); change_set.MovedTo.Add (to_file_path);
} }
@ -807,34 +818,15 @@ namespace SparkleLib {
} }
public override bool UsesNotificationCenter
{
get {
string file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "disable_notification_center");
return !File.Exists (file_path);
}
}
public override void CreateInitialChangeSet ()
{
base.CreateInitialChangeSet ();
SyncUp ();
}
// Recursively gets a folder's size in bytes // Recursively gets a folder's size in bytes
public override double CalculateSize (DirectoryInfo parent) private double CalculateSizes (DirectoryInfo parent)
{ {
if (!Directory.Exists (parent.ToString ())) if (!Directory.Exists (parent.ToString ()))
return 0; return 0;
double size = 0; double size = 0;
// Ignore the temporary 'rebase-apply' and '.tmp' directories. This prevents potential if (parent.Name.Equals ("rebase-apply"))
// crashes when files are being queried whilst the files have already been deleted.
if (parent.Name.Equals ("rebase-apply") ||
parent.Name.Equals (".tmp"))
return 0; return 0;
try { try {
@ -846,7 +838,7 @@ namespace SparkleLib {
} }
foreach (DirectoryInfo directory in parent.GetDirectories ()) foreach (DirectoryInfo directory in parent.GetDirectories ())
size += CalculateSize (directory); size += CalculateSizes (directory);
} catch (Exception) { } catch (Exception) {
return 0; return 0;

View file

@ -6,6 +6,7 @@ SOURCES = \
Git/SparkleFetcherGit.cs \ Git/SparkleFetcherGit.cs \
Git/SparkleGit.cs \ Git/SparkleGit.cs \
Git/SparkleRepoGit.cs \ Git/SparkleRepoGit.cs \
SparkleAnnouncement.cs \
SparkleBackend.cs \ SparkleBackend.cs \
SparkleChangeSet.cs \ SparkleChangeSet.cs \
SparkleConfig.cs \ SparkleConfig.cs \
@ -13,6 +14,7 @@ SOURCES = \
SparkleFetcherBase.cs \ SparkleFetcherBase.cs \
SparkleHelpers.cs \ SparkleHelpers.cs \
SparkleListenerBase.cs \ SparkleListenerBase.cs \
SparkleListenerFactory.cs \
SparkleListenerTcp.cs \ SparkleListenerTcp.cs \
SparkleRepoBase.cs \ SparkleRepoBase.cs \
SparkleWatcher.cs SparkleWatcher.cs

View file

@ -0,0 +1,34 @@
// SparkleShare, a collaboration and sharing tool.
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// 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;
namespace SparkleLib {
public class SparkleAnnouncement {
public readonly string FolderIdentifier;
public readonly string Message;
public SparkleAnnouncement (string folder_identifier, string message)
{
FolderIdentifier = folder_identifier;
Message = message;
}
}
}

View file

@ -21,40 +21,7 @@ using System.Runtime.InteropServices;
namespace SparkleLib { namespace SparkleLib {
public class SparkleBackend { public static class SparkleBackend {
public static SparkleBackend DefaultBackend = new SparkleBackendGit ();
public string Name;
public string Path;
public SparkleBackend (string name, string [] paths)
{
Name = name;
Path = "git";
foreach (string path in paths) {
if (File.Exists (path)) {
Path = path;
break;
}
}
}
public bool IsPresent {
get {
return (Path != null);
}
}
public bool IsUsablePath (string path)
{
return (path.Length > 0);
}
public static string Version { public static string Version {
get { get {
@ -65,7 +32,7 @@ namespace SparkleLib {
// Strange magic needed by Platform () // Strange magic needed by Platform ()
[DllImport ("libc")] [DllImport ("libc")]
static extern int uname (IntPtr buf); private static extern int uname (IntPtr buf);
// This fixes the PlatformID enumeration for MacOSX in Environment.OSVersion.Platform, // This fixes the PlatformID enumeration for MacOSX in Environment.OSVersion.Platform,
@ -90,44 +57,4 @@ namespace SparkleLib {
} }
} }
} }
public class SparkleBackendGit : SparkleBackend {
private static string name = "Git";
private static string [] paths = new string [] {
"/opt/local/bin/git",
"/usr/bin/git",
"/usr/local/bin/git",
"/usr/local/git/bin/git"
};
public SparkleBackendGit () : base (name, paths) { }
}
public class SparkleBackendHg : SparkleBackend {
private static string name = "Hg";
private static string [] paths = new string [] {
"/opt/local/bin/hg",
"/usr/bin/hg"
};
public SparkleBackendHg () : base (name, paths) { }
}
public class SparkleBackendScp : SparkleBackend {
private static string name = "Scp";
private static string [] paths = new string [] {
"/usr/bin/scp"
};
public SparkleBackendScp () : base (name, paths) { }
}
} }

View file

@ -29,6 +29,7 @@ namespace SparkleLib {
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
"sparkleshare"); "sparkleshare");
// TODO: declare elsewhere
public static SparkleConfig DefaultConfig = new SparkleConfig (default_config_path, "config.xml"); public static SparkleConfig DefaultConfig = new SparkleConfig (default_config_path, "config.xml");
public static bool DebugMode = true; public static bool DebugMode = true;

View file

@ -16,9 +16,7 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Security.AccessControl;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@ -37,18 +35,21 @@ namespace SparkleLib {
public event FailedEventHandler Failed; public event FailedEventHandler Failed;
public event ProgressChangedEventHandler ProgressChanged; public event ProgressChangedEventHandler ProgressChanged;
public string [] ExcludeRules; public abstract bool Fetch ();
public abstract void Stop ();
protected string target_folder; public string TargetFolder;
protected string remote_url; public string RemoteUrl;
public string [] ExcludeRules;
public string [] Warnings;
private Thread thread; private Thread thread;
public SparkleFetcherBase (string server, string remote_folder, string target_folder) public SparkleFetcherBase (string server, string remote_folder, string target_folder)
{ {
this.target_folder = target_folder; TargetFolder = target_folder;
this.remote_url = server + "/" + remote_folder; RemoteUrl = server + "/" + remote_folder;
ExcludeRules = new string [] { ExcludeRules = new string [] {
// gedit and emacs // gedit and emacs
@ -121,22 +122,18 @@ namespace SparkleLib {
} }
public abstract bool Fetch ();
public abstract string [] Warnings { get; }
// Clones the remote repository // Clones the remote repository
public void Start () public void Start ()
{ {
SparkleHelpers.DebugInfo ("Fetcher", "[" + this.target_folder + "] Fetching folder: " + this.remote_url); SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl);
if (Started != null) if (Started != null)
Started (); Started ();
if (Directory.Exists (this.target_folder)) if (Directory.Exists (TargetFolder))
Directory.Delete (this.target_folder, true); Directory.Delete (TargetFolder, true);
string host = GetHost (this.remote_url); string host = GetHost (RemoteUrl);
if (String.IsNullOrEmpty (host)) { if (String.IsNullOrEmpty (host)) {
if (Failed != null) if (Failed != null)
@ -170,20 +167,6 @@ namespace SparkleLib {
} }
public virtual void Stop ()
{
this.thread.Abort ();
this.thread.Join ();
}
public string RemoteUrl {
get {
return this.remote_url;
}
}
public void Dispose () public void Dispose ()
{ {
if (this.thread != null) { if (this.thread != null) {

View file

@ -40,12 +40,14 @@
<Compile Include="Git/SparkleFetcherGit.cs" /> <Compile Include="Git/SparkleFetcherGit.cs" />
<Compile Include="Hg/SparkleFetcherHg.cs" /> <Compile Include="Hg/SparkleFetcherHg.cs" />
<Compile Include="Defines.cs" /> <Compile Include="Defines.cs" />
<Compile Include="SparkleAnnouncement.cs" />
<Compile Include="SparkleHelpers.cs" /> <Compile Include="SparkleHelpers.cs" />
<Compile Include="SparklePaths.cs" /> <Compile Include="SparklePaths.cs" />
<Compile Include="SparkleOptions.cs" /> <Compile Include="SparkleOptions.cs" />
<Compile Include="SparkleChangeSet.cs" /> <Compile Include="SparkleChangeSet.cs" />
<Compile Include="SparkleListenerBase.cs" /> <Compile Include="SparkleListenerBase.cs" />
<Compile Include="SparkleListenerIrc.cs" /> <Compile Include="SparkleListenerFactory.cs" />
<Compile Include="SparkleListenerTcp.cs" />
<Compile Include="SparkleBackend.cs" /> <Compile Include="SparkleBackend.cs" />
<Compile Include="SparkleConfig.cs" /> <Compile Include="SparkleConfig.cs" />
<Compile Include="SparkleWatcher.cs" /> <Compile Include="SparkleWatcher.cs" />

View file

@ -16,76 +16,11 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Timers; using System.Timers;
using System.Linq;
namespace SparkleLib { namespace SparkleLib {
public class SparkleAnnouncement {
public readonly string FolderIdentifier;
public readonly string Message;
public SparkleAnnouncement (string folder_identifier, string message)
{
FolderIdentifier = folder_identifier;
Message = message;
}
}
public static class SparkleListenerFactory {
private static List<SparkleListenerBase> listeners = new List<SparkleListenerBase> ();
public static SparkleListenerBase CreateListener (string folder_name, string folder_identifier)
{
string uri = SparkleConfig.DefaultConfig.GetFolderOptionalAttribute (
folder_name, "announcements_url");
if (uri == null) {
// This is SparkleShare's centralized notification service.
// Don't worry, we only use this server as a backup if you
// don't have your own. All data needed to connect is hashed and
// we don't store any personal information ever
uri = "tcp://notifications.sparkleshare.org:1986";
}
Uri announce_uri = new Uri (uri);
// We use only one listener per server to keep
// the number of connections as low as possible
foreach (SparkleListenerBase listener in listeners) {
if (listener.Server.Equals (announce_uri)) {
SparkleHelpers.DebugInfo ("ListenerFactory",
"Refered to existing listener for " + announce_uri);
listener.AlsoListenToBase (folder_identifier);
return (SparkleListenerBase) listener;
}
}
// Create a new listener with the appropriate
// type if one doesn't exist yet for that server
switch (announce_uri.Scheme) {
case "tcp":
listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier));
break;
default:
listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier));
break;
}
SparkleHelpers.DebugInfo ("ListenerFactory", "Issued new listener for " + announce_uri);
return (SparkleListenerBase) listeners [listeners.Count - 1];
}
}
// A persistent connection to the server that // A persistent connection to the server that
// listens for change notifications // listens for change notifications
public abstract class SparkleListenerBase { public abstract class SparkleListenerBase {
@ -105,8 +40,8 @@ namespace SparkleLib {
public abstract void Connect (); public abstract void Connect ();
public abstract bool IsConnected { get; } public abstract bool IsConnected { get; }
public abstract bool IsConnecting { get; } public abstract bool IsConnecting { get; }
protected abstract void Announce (SparkleAnnouncement announcent); protected abstract void AnnounceInternal (SparkleAnnouncement announcent);
protected abstract void AlsoListenTo (string folder_identifier); protected abstract void AlsoListenToInternal (string folder_identifier);
protected List<string> channels = new List<string> (); protected List<string> channels = new List<string> ();
@ -143,7 +78,7 @@ namespace SparkleLib {
} }
public void AnnounceBase (SparkleAnnouncement announcement) public void Announce (SparkleAnnouncement announcement)
{ {
if (!IsRecentAnnouncement (announcement)) { if (!IsRecentAnnouncement (announcement)) {
if (IsConnected) { if (IsConnected) {
@ -151,7 +86,7 @@ namespace SparkleLib {
"Announcing message " + announcement.Message + " to " + "Announcing message " + announcement.Message + " to " +
announcement.FolderIdentifier + " on " + Server); announcement.FolderIdentifier + " on " + Server);
Announce (announcement); AnnounceInternal (announcement);
AddRecentAnnouncement (announcement); AddRecentAnnouncement (announcement);
} else { } else {
@ -170,14 +105,14 @@ namespace SparkleLib {
} }
public void AlsoListenToBase (string channel) public void AlsoListenTo (string channel)
{ {
if (!this.channels.Contains (channel) && IsConnected) { if (!this.channels.Contains (channel) && IsConnected) {
SparkleHelpers.DebugInfo ("Listener", SparkleHelpers.DebugInfo ("Listener",
"Subscribing to channel " + channel); "Subscribing to channel " + channel);
this.channels.Add (channel); this.channels.Add (channel);
AlsoListenTo (channel); AlsoListenToInternal (channel);
} }
} }
@ -202,7 +137,7 @@ namespace SparkleLib {
foreach (KeyValuePair<string, SparkleAnnouncement> item in this.queue_up) { foreach (KeyValuePair<string, SparkleAnnouncement> item in this.queue_up) {
SparkleAnnouncement announcement = item.Value; SparkleAnnouncement announcement = item.Value;
AnnounceBase (announcement); Announce (announcement);
} }
this.queue_down.Clear (); this.queue_down.Clear ();
@ -210,9 +145,9 @@ namespace SparkleLib {
} }
public void OnDisconnected () public void OnDisconnected (string message)
{ {
SparkleHelpers.DebugInfo ("Listener", "Signal of " + Server + " lost"); SparkleHelpers.DebugInfo ("Listener", "Disconnected from " + Server + ": " + message);
if (Disconnected != null) if (Disconnected != null)
Disconnected (); Disconnected ();
@ -294,4 +229,3 @@ namespace SparkleLib {
} }
} }
} }

View file

@ -0,0 +1,89 @@
// SparkleShare, a collaboration and sharing tool.
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// 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.Collections.Generic;
namespace SparkleLib {
public static class SparkleListenerFactory {
private static List<SparkleListenerBase> listeners = new List<SparkleListenerBase> ();
public static SparkleListenerBase CreateListener (string folder_name, string folder_identifier)
{
// Check if the user wants to use a global custom notification service
string uri = SparkleConfig.DefaultConfig.GetConfigOption ("announcements_url");
// Check if the user wants a use a custom notification service for this folder
if (string.IsNullOrEmpty (uri))
uri = SparkleConfig.DefaultConfig.GetFolderOptionalAttribute (
folder_name, "announcements_url");
// Fall back to the fallback service is neither is the case
if (string.IsNullOrEmpty (uri)) {
// This is SparkleShare's centralized notification service.
// It communicates "It's time to sync!" signals between clients.
//
// Here's how it works: the client listens to a channel (the
// folder identifier, a SHA-1 hash) for when it's time to sync.
// Clients also send the current revision hash to the channel
// for other clients to pick up when you've synced up any
// changes.
//
// Please see the SparkleShare wiki if you wish to run
// your own service instead
uri = "tcp://notifications.sparkleshare.org:1986";
}
Uri announce_uri = new Uri (uri);
// We use only one listener per notification service to keep
// the number of connections as low as possible
foreach (SparkleListenerBase listener in listeners) {
if (listener.Server.Equals (announce_uri)) {
SparkleHelpers.DebugInfo ("ListenerFactory",
"Refered to existing " + announce_uri.Scheme +
" listener for " + announce_uri);
// We already seem to have a listener for this server,
// refer to the existing one instead
listener.AlsoListenTo (folder_identifier);
return (SparkleListenerBase) listener;
}
}
// Create a new listener with the appropriate
// type if one doesn't exist yet for that server
switch (announce_uri.Scheme) {
case "tcp":
listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier));
break;
default:
listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier));
break;
}
SparkleHelpers.DebugInfo ("ListenerFactory",
"Issued new " + announce_uri.Scheme + " listener for " + announce_uri);
return (SparkleListenerBase) listeners [listeners.Count - 1];
}
}
}

View file

@ -16,23 +16,20 @@
using System; using System;
using System.IO; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace SparkleLib { namespace SparkleLib {
public class SparkleListenerTcp : SparkleListenerBase { public class SparkleListenerTcp : SparkleListenerBase {
private Socket socket; private Socket socket;
private Object socket_lock = new Object ();
private Thread thread; private Thread thread;
private bool is_connected = false; private Object socket_lock = new Object ();
private bool is_connecting = false; private bool is_connected = false;
private bool is_connecting = false;
private DateTime last_ping = DateTime.Now;
public SparkleListenerTcp (Uri server, string folder_identifier) : public SparkleListenerTcp (Uri server, string folder_identifier) :
@ -60,83 +57,138 @@ namespace SparkleLib {
// Starts a new thread and listens to the channel // Starts a new thread and listens to the channel
public override void Connect () public override void Connect ()
{ {
SparkleHelpers.DebugInfo ("ListenerTcp", "Connecting to " + Server.Host);
this.is_connecting = true; this.is_connecting = true;
this.thread = new Thread ( this.thread = new Thread (
new ThreadStart (delegate { new ThreadStart (delegate {
int port = Server.Port;
if (port < 0)
port = 1986;
try { try {
// Connect and subscribe to the channel
int port = Server.Port;
if (port < 0)
port = 9999;
lock (this.socket_lock) { lock (this.socket_lock) {
this.socket = new Socket (AddressFamily.InterNetwork, this.socket = new Socket (AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp) { SocketType.Stream, ProtocolType.Tcp) {
ReceiveTimeout = 5 * 1000,
ReceiveTimeout = 30 * 1000 SendTimeout = 5 * 1000
}; };
// Try to connect to the server
this.socket.Connect (Server.Host, port); this.socket.Connect (Server.Host, port);
this.is_connecting = false; this.is_connecting = false;
this.is_connected = true; this.is_connected = true;
OnConnected (); OnConnected ();
foreach (string channel in base.channels) { // Subscribe to channels of interest to us
SparkleHelpers.DebugInfo ("ListenerTcp", "Subscribing to channel " + channel); foreach (string channel in base.channels)
this.socket.Send (Encoding.UTF8.GetBytes ("subscribe " + channel + "\n")); AlsoListenToInternal (channel);
}
} }
byte [] bytes = new byte [4096];
// List to the channels, this blocks the thread
while (this.socket.Connected) {
int bytes_read = this.socket.Receive (bytes);
if (bytes_read > 0) {
string received = Encoding.UTF8.GetString (bytes);
string line = received.Substring (0, received.IndexOf ("\n"));
if (!line.Contains ("!"))
continue;
string folder_identifier = line.Substring (0, line.IndexOf ("!"));
string message = CleanMessage (line.Substring (line.IndexOf ("!") + 1));
if (!folder_identifier.Equals ("debug") &&
!String.IsNullOrEmpty (message)) {
OnAnnouncement (new SparkleAnnouncement (folder_identifier, message));
}
} else {
SparkleHelpers.DebugInfo ("ListenerTcp", "Error on socket");
lock (this.socket_lock) {
this.socket.Close ();
this.is_connected = false;
OnDisconnected ();
}
}
}
SparkleHelpers.DebugInfo ("ListenerTcp", "Disconnected from " + Server.Host);
} catch (SocketException e) { } catch (SocketException e) {
SparkleHelpers.DebugInfo ("ListenerTcp", "Could not connect to " + Server + ": " + e.Message); this.is_connected = false;
this.is_connected = false;
this.is_connecting = false; this.is_connecting = false;
OnDisconnected (); this.socket.Dispose ();
OnDisconnected (e.Message);
return;
}
byte [] bytes = new byte [4096];
int bytes_read = 0;
this.last_ping = DateTime.Now;
// Wait for messages
while (this.is_connected) {
// This blocks the thread
int i = 0;
while (this.socket.Available < 1) {
Thread.Sleep (1000);
i++;
try {
// We've timed out, let's ping the server to
// see if the connection is still up
if (i == 180) {
SparkleHelpers.DebugInfo ("ListenerTcp",
"Pinging " + Server);
byte [] ping_bytes = Encoding.UTF8.GetBytes ("ping\n");
byte [] pong_bytes = new byte [4096];
lock (this.socket_lock)
this.socket.Send (ping_bytes);
if (this.socket.Receive (pong_bytes) < 1)
// 10057 means "Socket is not connected"
throw new SocketException (10057);
SparkleHelpers.DebugInfo ("ListenerTcp",
"Received pong from " + Server);
i = 0;
this.last_ping = DateTime.Now;
} else {
// Check when the last ping occured. If it's
// significantly longer than our regular interval the
// system likely woke up from sleep and we want to
// simulate a disconnect
int sleepiness = DateTime.Compare (
this.last_ping.AddMilliseconds (180 * 1000 * 1.2),
DateTime.Now
);
if (sleepiness <= 0) {
SparkleHelpers.DebugInfo ("ListenerTcp",
"System woke up from sleep");
// 10057 means "Socket is not connected"
throw new SocketException (10057);
}
}
// The ping failed: disconnect completely
} catch (SocketException) {
this.is_connected = false;
this.is_connecting = false;;
this.socket.Dispose ();
OnDisconnected ("Ping timeout");
return;
}
}
if (this.socket.Available > 0)
lock (this.socket_lock)
bytes_read = this.socket.Receive (bytes);
// Parse the received message
if (bytes_read > 0) {
string received = Encoding.UTF8.GetString (bytes);
string line = received.Substring (0, received.IndexOf ("\n"));
if (!line.Contains ("!"))
continue;
string folder_identifier = line.Substring (0, line.IndexOf ("!"));
string message = CleanMessage (line.Substring (line.IndexOf ("!") + 1));
if (!folder_identifier.Equals ("debug") &&
!String.IsNullOrEmpty (message)) {
// We have a message!
OnAnnouncement (new SparkleAnnouncement (folder_identifier, message));
}
}
} }
}) })
); );
@ -145,28 +197,29 @@ namespace SparkleLib {
} }
protected override void AlsoListenTo (string folder_identifier) protected override void AlsoListenToInternal (string folder_identifier)
{ {
SparkleHelpers.DebugInfo ("ListenerTcp",
"Subscribing to channel " + folder_identifier + " on " + Server);
string to_send = "subscribe " + folder_identifier + "\n"; string to_send = "subscribe " + folder_identifier + "\n";
try { try {
lock (this.socket_lock) { lock (this.socket_lock)
this.socket.Send (Encoding.UTF8.GetBytes (to_send)); this.socket.Send (Encoding.UTF8.GetBytes (to_send));
}
this.last_ping = DateTime.Now;
} catch (SocketException e) { } catch (SocketException e) {
SparkleHelpers.DebugInfo ("ListenerTcp",
"Could not connect to " + Server + ": " + e.Message);
this.is_connected = false; this.is_connected = false;
this.is_connecting = false; this.is_connecting = false;
OnDisconnected (); OnDisconnected (e.Message);
} }
} }
protected override void Announce (SparkleAnnouncement announcement) protected override void AnnounceInternal (SparkleAnnouncement announcement)
{ {
string to_send = "announce " + announcement.FolderIdentifier string to_send = "announce " + announcement.FolderIdentifier
+ " " + announcement.Message + "\n"; + " " + announcement.Message + "\n";
@ -175,14 +228,13 @@ namespace SparkleLib {
lock (this.socket_lock) lock (this.socket_lock)
this.socket.Send (Encoding.UTF8.GetBytes (to_send)); this.socket.Send (Encoding.UTF8.GetBytes (to_send));
} catch (SocketException e) { this.last_ping = DateTime.Now;
SparkleHelpers.DebugInfo ("ListenerTcp",
"Could not connect to " + Server + ": " + e.Message);
} catch (SocketException e) {
this.is_connected = false; this.is_connected = false;
this.is_connecting = false; this.is_connecting = false;
OnDisconnected (); OnDisconnected (e.Message);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,6 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Timers; using System.Timers;
using System.Xml;
namespace SparkleLib { namespace SparkleLib {
@ -39,45 +38,28 @@ namespace SparkleLib {
private TimeSpan short_interval = new TimeSpan (0, 0, 3, 0); private TimeSpan short_interval = new TimeSpan (0, 0, 3, 0);
private TimeSpan long_interval = new TimeSpan (0, 0, 10, 0); private TimeSpan long_interval = new TimeSpan (0, 0, 10, 0);
private SparkleWatcher watcher;
private TimeSpan poll_interval; private TimeSpan poll_interval;
private SparkleWatcher watcher;
private SparkleListenerBase listener;
private System.Timers.Timer local_timer = new System.Timers.Timer () { Interval = 0.25 * 1000 }; private System.Timers.Timer local_timer = new System.Timers.Timer () { Interval = 0.25 * 1000 };
private System.Timers.Timer remote_timer = new System.Timers.Timer () { Interval = 10 * 1000 }; private System.Timers.Timer remote_timer = new System.Timers.Timer () { Interval = 10 * 1000 };
private DateTime last_poll = DateTime.Now; private DateTime last_poll = DateTime.Now;
private List<double> sizebuffer = new List<double> (); private List<double> size_buffer = new List<double> ();
private bool has_changed = false; private Object change_lock = new Object ();
private Object change_lock = new Object (); private Object watch_lock = new Object ();
private Object watch_lock = new Object (); private double progress_percentage = 0.0;
private double progress_percentage = 0.0; private string progress_speed = "";
private string progress_speed = ""; private bool has_changed = false;
private bool is_buffering = false;
private bool server_online = true;
private SyncStatus status;
protected SparkleListenerBase listener;
protected SyncStatus status;
protected bool is_buffering = false;
protected bool server_online = true;
public readonly string LocalPath;
public readonly string Name;
public readonly Uri Url;
public abstract bool AnyDifferences { get; }
public abstract string Identifier { get; }
public abstract string CurrentRevision { get; }
public abstract bool SyncUp ();
public abstract bool SyncDown ();
public abstract double CalculateSize (DirectoryInfo parent);
public abstract bool HasUnsyncedChanges { get; set; }
public abstract List<string> ExcludePaths { get; }
public abstract double Size { get; }
public abstract double HistorySize { get; }
public delegate void SyncStatusChangedEventHandler (SyncStatus new_status); public delegate void SyncStatusChangedEventHandler (SyncStatus new_status);
public event SyncStatusChangedEventHandler SyncStatusChanged; public event SyncStatusChangedEventHandler SyncStatusChanged;
public delegate void SyncProgressChangedEventHandler (double percentage, string speed); public delegate void ProgressChangedEventHandler (double percentage, string speed);
public event SyncProgressChangedEventHandler SyncProgressChanged; public event ProgressChangedEventHandler ProgressChanged;
public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set); public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set);
public event NewChangeSetEventHandler NewChangeSet; public event NewChangeSetEventHandler NewChangeSet;
@ -92,6 +74,68 @@ namespace SparkleLib {
public event ChangesDetectedEventHandler ChangesDetected; public event ChangesDetectedEventHandler ChangesDetected;
public readonly string LocalPath;
public readonly string Name;
public readonly Uri Url;
public abstract string Identifier { get; }
public abstract string CurrentRevision { get; }
public abstract double Size { get; }
public abstract double HistorySize { get; }
public abstract List<string> ExcludePaths { get; }
public abstract bool HasUnsyncedChanges { get; set; }
public abstract bool HasLocalChanges { get; }
public abstract bool HasRemoteChanges { get; }
public abstract bool SyncUp ();
public abstract bool SyncDown ();
public abstract List<SparkleChangeSet> GetChangeSets (int count);
public bool ServerOnline {
get {
return this.server_online;
}
}
public SyncStatus Status {
get {
return this.status;
}
}
public double ProgressPercentage {
get {
return this.progress_percentage;
}
}
public string ProgressSpeed {
get {
return this.progress_speed;
}
}
public virtual string [] UnsyncedFilePaths {
get {
return new string [0];
}
}
public bool IsSyncing {
get {
return (Status == SyncStatus.SyncUp ||
Status == SyncStatus.SyncDown ||
this.is_buffering);
}
}
public bool IsBuffering {
get {
return this.is_buffering;
}
}
public SparkleRepoBase (string path) public SparkleRepoBase (string path)
{ {
LocalPath = path; LocalPath = path;
@ -121,7 +165,7 @@ namespace SparkleLib {
if (time_to_poll) { if (time_to_poll) {
this.last_poll = DateTime.Now; this.last_poll = DateTime.Now;
if (CheckForRemoteChanges ()) if (HasRemoteChanges)
SyncDownBase (); SyncDownBase ();
} }
@ -139,7 +183,7 @@ namespace SparkleLib {
{ {
// Sync up everything that changed // Sync up everything that changed
// since we've been offline // since we've been offline
if (AnyDifferences) { if (HasLocalChanges) {
DisableWatching (); DisableWatching ();
SyncUpBase (); SyncUpBase ();
@ -154,96 +198,15 @@ namespace SparkleLib {
} }
public bool ServerOnline {
get {
return this.server_online;
}
}
public SyncStatus Status {
get {
return this.status;
}
}
public double ProgressPercentage {
get {
return this.progress_percentage;
}
}
public string ProgressSpeed {
get {
return this.progress_speed;
}
}
public virtual string [] UnsyncedFilePaths {
get {
return new string [0];
}
}
public string Domain {
get {
Regex regex = new Regex (@"(@|://)([a-z0-9\.-]+)(/|:)");
Match match = regex.Match (SparkleConfig.DefaultConfig.GetUrlForFolder (Name));
if (match.Success)
return match.Groups [2].Value;
else
return null;
}
}
protected void OnConflictResolved () protected void OnConflictResolved ()
{ {
HasUnsyncedChanges = true; HasUnsyncedChanges = true; // ?
if (ConflictResolved != null) if (ConflictResolved != null)
ConflictResolved (); ConflictResolved ();
} }
public virtual bool CheckForRemoteChanges () // TODO: HasRemoteChanges { get; }
{
return true;
}
public virtual List<SparkleChangeSet> GetChangeSets (int count) {
return null;
}
public virtual bool UsesNotificationCenter {
get {
return true;
}
}
public string RemoteName {
get {
string url = SparkleConfig.DefaultConfig.GetUrlForFolder (Name);
return Path.GetFileNameWithoutExtension (url);
}
}
public bool IsBuffering {
get {
return this.is_buffering;
}
}
// Disposes all resourses of this object // Disposes all resourses of this object
public void Dispose () public void Dispose ()
{ {
@ -253,114 +216,6 @@ namespace SparkleLib {
} }
private void CreateWatcher ()
{
this.watcher = new SparkleWatcher (LocalPath);
this.watcher.ChangeEvent += delegate (FileSystemEventArgs args) {
OnFileActivity (args);
};
}
public void CreateListener ()
{
this.listener = SparkleListenerFactory.CreateListener (Name, Identifier);
if (this.listener.IsConnected) {
this.poll_interval = this.long_interval;
new Thread (new ThreadStart (delegate {
if (!IsSyncing && CheckForRemoteChanges ())
SyncDownBase ();
})).Start ();
}
// Stop polling when the connection to the irc channel is succesful
this.listener.Connected += delegate {
this.poll_interval = this.long_interval;
this.last_poll = DateTime.Now;
if (!IsSyncing) {
// Check for changes manually one more time
if (CheckForRemoteChanges ())
SyncDownBase ();
// Push changes that were made since the last disconnect
if (HasUnsyncedChanges)
SyncUpBase ();
}
};
// Start polling when the connection to the irc channel is lost
this.listener.Disconnected += delegate {
this.poll_interval = this.short_interval;
SparkleHelpers.DebugInfo (Name, "Falling back to polling");
};
// Fetch changes when there is a message in the irc channel
this.listener.Received += delegate (SparkleAnnouncement announcement) {
string identifier = Identifier;
if (announcement.FolderIdentifier.Equals (identifier) &&
!announcement.Message.Equals (CurrentRevision)) {
while (this.IsSyncing)
System.Threading.Thread.Sleep (100);
SparkleHelpers.DebugInfo ("Listener", "Syncing due to announcement");
SyncDownBase ();
} else {
if (announcement.FolderIdentifier.Equals (identifier))
SparkleHelpers.DebugInfo ("Listener", "Not syncing, message is for current revision");
}
};
// Start listening
if (!this.listener.IsConnected && !this.listener.IsConnecting)
this.listener.Connect ();
}
public bool IsSyncing {
get {
return (Status == SyncStatus.SyncUp ||
Status == SyncStatus.SyncDown ||
this.is_buffering);
}
}
private void CheckForChanges ()
{
lock (this.change_lock) {
if (this.has_changed) {
if (this.sizebuffer.Count >= 4)
this.sizebuffer.RemoveAt (0);
DirectoryInfo dir_info = new DirectoryInfo (LocalPath);
this.sizebuffer.Add (CalculateSize (dir_info));
if (this.sizebuffer.Count >= 4 &&
this.sizebuffer [0].Equals (this.sizebuffer [1]) &&
this.sizebuffer [1].Equals (this.sizebuffer [2]) &&
this.sizebuffer [2].Equals (this.sizebuffer [3])) {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes have settled.");
this.is_buffering = false;
this.has_changed = false;
DisableWatching ();
while (AnyDifferences)
SyncUpBase ();
EnableWatching ();
}
}
}
}
// Starts a timer when something changes // Starts a timer when something changes
public void OnFileActivity (FileSystemEventArgs args) public void OnFileActivity (FileSystemEventArgs args)
{ {
@ -378,7 +233,7 @@ namespace SparkleLib {
WatcherChangeTypes wct = args.ChangeType; WatcherChangeTypes wct = args.ChangeType;
if (AnyDifferences) { if (HasLocalChanges) {
this.is_buffering = true; this.is_buffering = true;
// We want to disable wathcing temporarily, but // We want to disable wathcing temporarily, but
@ -440,6 +295,44 @@ namespace SparkleLib {
} }
public void AddNote (string revision, string note)
{
string notes_path = Path.Combine (LocalPath, ".notes");
if (!Directory.Exists (notes_path))
Directory.CreateDirectory (notes_path);
// Add a timestamp in seconds since unix epoch
int timestamp = (int) (DateTime.UtcNow - new DateTime (1970, 1, 1)).TotalSeconds;
string n = Environment.NewLine;
note = "<note>" + n +
" <user>" + n +
" <name>" + SparkleConfig.DefaultConfig.User.Name + "</name>" + n +
" <email>" + SparkleConfig.DefaultConfig.User.Email + "</email>" + n +
" </user>" + n +
" <timestamp>" + timestamp + "</timestamp>" + n +
" <body>" + note + "</body>" + n +
"</note>" + n;
string note_name = revision + SHA1 (timestamp.ToString () + note);
string note_path = Path.Combine (notes_path, note_name);
StreamWriter writer = new StreamWriter (note_path);
writer.Write (note);
writer.Close ();
// The watcher doesn't like .*/ so we need to trigger
// a change manually
FileSystemEventArgs args = new FileSystemEventArgs (WatcherChangeTypes.Changed,
notes_path, note_name);
OnFileActivity (args);
SparkleHelpers.DebugInfo ("Note", "Added note to " + revision);
}
private void SyncUpBase () private void SyncUpBase ()
{ {
try { try {
@ -459,7 +352,7 @@ namespace SparkleLib {
if (SyncStatusChanged != null) if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle); SyncStatusChanged (SyncStatus.Idle);
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision)); this.listener.Announce (new SparkleAnnouncement (Identifier, CurrentRevision));
} else { } else {
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Error"); SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Error");
@ -474,7 +367,7 @@ namespace SparkleLib {
if (SyncStatusChanged != null) if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle); SyncStatusChanged (SyncStatus.Idle);
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision)); this.listener.Announce (new SparkleAnnouncement (Identifier, CurrentRevision));
} else { } else {
this.server_online = false; this.server_online = false;
@ -561,7 +454,106 @@ namespace SparkleLib {
} }
public void DisableWatching () private void CreateWatcher ()
{
this.watcher = new SparkleWatcher (LocalPath);
this.watcher.ChangeEvent += delegate (FileSystemEventArgs args) {
OnFileActivity (args);
};
}
private void CreateListener ()
{
this.listener = SparkleListenerFactory.CreateListener (Name, Identifier);
if (this.listener.IsConnected) {
this.poll_interval = this.long_interval;
new Thread (new ThreadStart (delegate {
if (!IsSyncing && HasRemoteChanges)
SyncDownBase ();
})).Start ();
}
// Stop polling when the connection to the irc channel is succesful
this.listener.Connected += delegate {
this.poll_interval = this.long_interval;
this.last_poll = DateTime.Now;
if (!IsSyncing) {
// Check for changes manually one more time
if (HasRemoteChanges)
SyncDownBase ();
// Push changes that were made since the last disconnect
if (HasUnsyncedChanges)
SyncUpBase ();
}
};
// Start polling when the connection to the irc channel is lost
this.listener.Disconnected += delegate {
this.poll_interval = this.short_interval;
SparkleHelpers.DebugInfo (Name, "Falling back to polling");
};
// Fetch changes when there is a message in the irc channel
this.listener.Received += delegate (SparkleAnnouncement announcement) {
string identifier = Identifier;
if (announcement.FolderIdentifier.Equals (identifier) &&
!announcement.Message.Equals (CurrentRevision)) {
while (this.IsSyncing)
System.Threading.Thread.Sleep (100);
SparkleHelpers.DebugInfo ("Listener", "Syncing due to announcement");
SyncDownBase ();
} else {
if (announcement.FolderIdentifier.Equals (identifier))
SparkleHelpers.DebugInfo ("Listener", "Not syncing, message is for current revision");
}
};
// Start listening
if (!this.listener.IsConnected && !this.listener.IsConnecting)
this.listener.Connect ();
}
private void CheckForChanges ()
{
lock (this.change_lock) {
if (this.has_changed) {
if (this.size_buffer.Count >= 4)
this.size_buffer.RemoveAt (0);
DirectoryInfo dir_info = new DirectoryInfo (LocalPath);
this.size_buffer.Add (CalculateSize (dir_info));
if (this.size_buffer.Count >= 4 &&
this.size_buffer [0].Equals (this.size_buffer [1]) &&
this.size_buffer [1].Equals (this.size_buffer [2]) &&
this.size_buffer [2].Equals (this.size_buffer [3])) {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes have settled.");
this.is_buffering = false;
this.has_changed = false;
DisableWatching ();
while (HasLocalChanges)
SyncUpBase ();
EnableWatching ();
}
}
}
}
protected void DisableWatching ()
{ {
lock (this.watch_lock) { lock (this.watch_lock) {
this.watcher.EnableRaisingEvents = false; this.watcher.EnableRaisingEvents = false;
@ -570,7 +562,7 @@ namespace SparkleLib {
} }
public void EnableWatching () protected void EnableWatching ()
{ {
lock (this.watch_lock) { lock (this.watch_lock) {
this.watcher.EnableRaisingEvents = true; this.watcher.EnableRaisingEvents = true;
@ -579,17 +571,39 @@ namespace SparkleLib {
} }
private DateTime progress_last_change = DateTime.Now;
private TimeSpan progress_change_interval = new TimeSpan (0, 0, 0, 1);
protected void OnProgressChanged (double progress_percentage, string progress_speed)
{
if (DateTime.Compare (this.progress_last_change,
DateTime.Now.Subtract (this.progress_change_interval)) < 0) {
if (ProgressChanged != null) {
if (progress_percentage == 100.0)
progress_percentage = 99.0;
this.progress_percentage = progress_percentage;
this.progress_speed = progress_speed;
this.progress_last_change = DateTime.Now;
ProgressChanged (progress_percentage, progress_speed);
}
}
}
// Create an initial change set when the // Create an initial change set when the
// user has fetched an empty remote folder // user has fetched an empty remote folder
public virtual void CreateInitialChangeSet () private void CreateInitialChangeSet ()
{ {
string file_path = Path.Combine (LocalPath, "SparkleShare.txt"); string file_path = Path.Combine (LocalPath, "SparkleShare.txt");
TextWriter writer = new StreamWriter (file_path); TextWriter writer = new StreamWriter (file_path);
writer.WriteLine ("Congratulations, you've successfully created a SparkleShare repository!"); writer.WriteLine ("Congratulations, you've successfully created a SparkleShare repository!");
writer.WriteLine (""); writer.WriteLine ("");
writer.WriteLine ("Any files you add or change in this folder will be automatically synced to "); writer.WriteLine ("Any files you add or change in this folder will be automatically synced to ");
writer.WriteLine (SparkleConfig.DefaultConfig.GetUrlForFolder (Name) + " and everyone connected to it."); writer.WriteLine (Url + " and everyone connected to it.");
// TODO: Url property? ^
writer.WriteLine (""); writer.WriteLine ("");
writer.WriteLine ("SparkleShare is a Free and Open Source software program that helps people "); writer.WriteLine ("SparkleShare is a Free and Open Source software program that helps people ");
@ -600,66 +614,8 @@ namespace SparkleLib {
writer.WriteLine (""); writer.WriteLine ("");
writer.Close (); writer.Close ();
}
SyncUp ();
public void AddNote (string revision, string note)
{
string notes_path = Path.Combine (LocalPath, ".notes");
if (!Directory.Exists (notes_path))
Directory.CreateDirectory (notes_path);
// Add a timestamp in seconds since unix epoch
int timestamp = (int) (DateTime.UtcNow - new DateTime (1970, 1, 1)).TotalSeconds;
string n = Environment.NewLine;
note = "<note>" + n +
" <user>" + n +
" <name>" + SparkleConfig.DefaultConfig.User.Name + "</name>" + n +
" <email>" + SparkleConfig.DefaultConfig.User.Email + "</email>" + n +
" </user>" + n +
" <timestamp>" + timestamp + "</timestamp>" + n +
" <body>" + note + "</body>" + n +
"</note>" + n;
string note_name = revision + SHA1 (timestamp.ToString () + note);
string note_path = Path.Combine (notes_path, note_name);
StreamWriter writer = new StreamWriter (note_path);
writer.Write (note);
writer.Close ();
// The watcher doesn't like .*/ so we need to trigger
// a change manually
FileSystemEventArgs args = new FileSystemEventArgs (WatcherChangeTypes.Changed,
notes_path, note_name);
OnFileActivity (args);
SparkleHelpers.DebugInfo ("Note", "Added note to " + revision);
}
private DateTime progress_last_change = DateTime.Now;
private TimeSpan progress_change_interval = new TimeSpan (0, 0, 0, 1);
protected void OnSyncProgressChanged (double progress_percentage, string progress_speed)
{
if (DateTime.Compare (this.progress_last_change,
DateTime.Now.Subtract (this.progress_change_interval)) < 0) {
if (SyncProgressChanged != null) {
if (progress_percentage == 100.0)
progress_percentage = 99.0;
this.progress_percentage = progress_percentage;
this.progress_speed = progress_speed;
this.progress_last_change = DateTime.Now;
SyncProgressChanged (progress_percentage, progress_speed);
}
}
} }
@ -671,5 +627,35 @@ namespace SparkleLib {
Byte[] encoded_bytes = sha1.ComputeHash (bytes); Byte[] encoded_bytes = sha1.ComputeHash (bytes);
return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", "");
} }
// Recursively gets a folder's size in bytes
private double CalculateSize (DirectoryInfo parent)
{
if (!Directory.Exists (parent.ToString ()))
return 0;
double size = 0;
if (ExcludePaths.Contains (parent.Name))
return 0;
try {
foreach (FileInfo file in parent.GetFiles()) {
if (!file.Exists)
return 0;
size += file.Length;
}
foreach (DirectoryInfo directory in parent.GetDirectories ())
size += CalculateSize (directory);
} catch (Exception) {
return 0;
}
return size;
}
} }
} }

View file

@ -19,12 +19,11 @@ using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using MonoMac.Foundation;
using MonoMac.AppKit; using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime; using MonoMac.ObjCRuntime;
using MonoMac.WebKit; using MonoMac.WebKit;
namespace SparkleShare { namespace SparkleShare {
public class SparkleAbout : NSWindow { public class SparkleAbout : NSWindow {
@ -54,9 +53,12 @@ namespace SparkleShare {
BackingType = NSBackingStore.Buffered; BackingType = NSBackingStore.Buffered;
CreateAbout (); CreateAbout ();
OrderFrontRegardless ();
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this); MakeKeyAndOrderFront (this);
OrderFrontRegardless ();
Program.UI.UpdateDockIconVisibility (); Program.UI.UpdateDockIconVisibility ();
Controller.NewVersionEvent += delegate (string new_version) { Controller.NewVersionEvent += delegate (string new_version) {

View file

@ -44,8 +44,8 @@ namespace SparkleShare {
string content_path = string content_path =
Directory.GetParent (System.AppDomain.CurrentDomain.BaseDirectory).ToString (); Directory.GetParent (System.AppDomain.CurrentDomain.BaseDirectory).ToString ();
string app_path = Directory.GetParent (content_path).ToString (); string app_path = Directory.GetParent (content_path).ToString ();
string growl_path = Path.Combine (app_path, "Frameworks", "Growl.framework", "Growl"); string growl_path = Path.Combine (app_path, "Frameworks", "Growl.framework", "Growl");
// Needed for Growl // Needed for Growl
@ -54,7 +54,7 @@ namespace SparkleShare {
// Let's use the bundled git first // Let's use the bundled git first
SparkleBackend.DefaultBackend.Path = SparkleGit.Path =
Path.Combine (NSBundle.MainBundle.ResourcePath, Path.Combine (NSBundle.MainBundle.ResourcePath,
"git", "libexec", "git-core", "git"); "git", "libexec", "git-core", "git");
@ -68,7 +68,13 @@ namespace SparkleShare {
{ {
base.Initialize (); base.Initialize ();
this.watcher.Changed += delegate (string path) { this.watcher.Changed += delegate (object sender, SparkleMacWatcherEventArgs args) {
string path = args.Path;
// Don't even bother with paths in .git/
if (path.Contains (".git"))
return;
string repo_name; string repo_name;
if (path.Contains ("/")) if (path.Contains ("/"))
@ -77,17 +83,20 @@ namespace SparkleShare {
repo_name = path; repo_name = path;
// Ignore changes in the root of each subfolder, these // Ignore changes in the root of each subfolder, these
// are already handled bu the repository // are already handled by the repository
if (Path.GetFileNameWithoutExtension (path).Equals (repo_name)) if (Path.GetFileNameWithoutExtension (path).Equals (repo_name))
return; return;
repo_name = repo_name.Trim ("/".ToCharArray ()); repo_name = repo_name.Trim ("/".ToCharArray ());
FileSystemEventArgs args = new FileSystemEventArgs (WatcherChangeTypes.Changed, FileSystemEventArgs fse_args = new FileSystemEventArgs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, path), Path.GetFileName (path)); WatcherChangeTypes.Changed,
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, path),
Path.GetFileName (path)
);
foreach (SparkleRepoBase repo in Repositories) { foreach (SparkleRepoBase repo in Repositories) {
if (repo.Name.Equals (repo_name)) if (repo.Name.Equals (repo_name))
repo.OnFileActivity (args); repo.OnFileActivity (fse_args);
} }
}; };
} }

View file

@ -134,7 +134,8 @@ namespace SparkleShare {
this.progress_indicator = new NSProgressIndicator () { this.progress_indicator = new NSProgressIndicator () {
Style = NSProgressIndicatorStyle.Spinning, Style = NSProgressIndicatorStyle.Spinning,
Frame = new RectangleF (this.web_view.Frame.Width / 2 - 10, this.web_view.Frame.Height / 2 + 10, 20, 20) Frame = new RectangleF (this.web_view.Frame.Width / 2 - 10,
this.web_view.Frame.Height / 2 + 10, 20, 20)
}; };
this.progress_indicator.StartAnimation (this); this.progress_indicator.StartAnimation (this);
@ -226,15 +227,24 @@ namespace SparkleShare {
html = html.Replace ("<!-- $a-color -->", "#0085cf"); html = html.Replace ("<!-- $a-color -->", "#0085cf");
html = html.Replace ("<!-- $a-hover-color -->", "#009ff8"); html = html.Replace ("<!-- $a-hover-color -->", "#009ff8");
html = html.Replace ("<!-- $no-buddy-icon-background-image -->", html = html.Replace ("<!-- $no-buddy-icon-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "avatar-default.png")); "file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps","avatar-default.png"));
html = html.Replace ("<!-- $document-added-background-image -->", html = html.Replace ("<!-- $document-added-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "document-added-12.png")); "file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "document-added-12.png"));
html = html.Replace ("<!-- $document-deleted-background-image -->", html = html.Replace ("<!-- $document-deleted-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "document-deleted-12.png")); "file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "document-deleted-12.png"));
html = html.Replace ("<!-- $document-edited-background-image -->", html = html.Replace ("<!-- $document-edited-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "document-edited-12.png")); "file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "document-edited-12.png"));
html = html.Replace ("<!-- $document-moved-background-image -->", html = html.Replace ("<!-- $document-moved-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "document-moved-12.png")); "file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "document-moved-12.png"));
InvokeOnMainThread (delegate { InvokeOnMainThread (delegate {
if (this.progress_indicator.Superview == ContentView) if (this.progress_indicator.Superview == ContentView)

View file

@ -14,88 +14,216 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Originally taken from:
// https://github.com/jesse99/Continuum/blob/master/source/shared/DirectoryWatcher.cs
// Modified to use MonoMac and integrate into SparkleShare
// Copyright (C) 2008 Jesse Jones
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Timers; using System.Timers;
using MonoMac.AppKit;
using MonoMac.Foundation;
namespace SparkleShare { namespace SparkleShare {
public class SparkleMacWatcher { [Serializable]
public sealed class SparkleMacWatcherEventArgs : EventArgs {
public delegate void ChangedEventHandler (string path); public string Path { get; private set; }
public event ChangedEventHandler Changed;
private FileSystemInfo last_changed;
private Thread thread; public SparkleMacWatcherEventArgs (string path)
private int poll_count = 0; {
Path = path;
}
}
public sealed class SparkleMacWatcher : IDisposable
{
public event EventHandler<SparkleMacWatcherEventArgs> Changed;
public string Path { get; private set; }
[Flags]
[Serializable]
private enum FSEventStreamCreateFlags : uint
{
kFSEventStreamCreateFlagNone = 0x00000000,
kFSEventStreamCreateFlagUseCFTypes = 0x00000001,
kFSEventStreamCreateFlagNoDefer = 0x00000002,
kFSEventStreamCreateFlagWatchRoot = 0x00000004,
}
private DateTime last_found_timestamp;
private IntPtr m_stream;
private FSEventStreamCallback m_callback; // need to keep a reference around so that it isn't GC'ed
private static readonly IntPtr kCFRunLoopDefaultMode = (new NSString ("kCFRunLoopDefaultMode")).Handle;
private ulong kFSEventStreamEventIdSinceNow = 0xFFFFFFFFFFFFFFFFUL;
private delegate void FSEventStreamCallback (
IntPtr streamRef,
IntPtr clientCallBackInfo,
int numEvents,
IntPtr eventPaths,
IntPtr eventFlags,
IntPtr eventIds);
~SparkleMacWatcher ()
{
Dispose (false);
}
public SparkleMacWatcher (string path) public SparkleMacWatcher (string path)
{ {
this.thread = new Thread (new ThreadStart (delegate { Path = path;
DateTime timestamp; m_callback = DoCallback;
DirectoryInfo parent = new DirectoryInfo (path);
this.last_changed = new DirectoryInfo (path);
while (true) { NSString [] s = new NSString [1];
timestamp = this.last_changed.LastWriteTime; s [0] = new NSString (path);
GetLastChange (parent); NSArray path_p = NSArray.FromNSObjects (s);
if (DateTime.Compare (this.last_changed.LastWriteTime, timestamp) != 0) { m_stream = FSEventStreamCreate ( // note that the stream will always be valid
string relative_path = this.last_changed.FullName.Substring (path.Length + 1); IntPtr.Zero, // allocator
m_callback, // callback
IntPtr.Zero, // context
path_p.Handle, // pathsToWatch
kFSEventStreamEventIdSinceNow, // sinceWhen
2, // latency (in seconds)
FSEventStreamCreateFlags.kFSEventStreamCreateFlagNone); // flags
if (Changed != null) FSEventStreamScheduleWithRunLoop (
Changed (relative_path); m_stream, // streamRef
} CFRunLoopGetMain(), // runLoop
kCFRunLoopDefaultMode); // runLoopMode
Thread.Sleep (7500); bool started = FSEventStreamStart (m_stream);
this.poll_count++; if (!started) {
} GC.SuppressFinalize (this);
})); throw new InvalidOperationException ("Failed to start FSEvent stream for " + path);
this.thread.Start ();
}
private void GetLastChange (DirectoryInfo parent)
{
try {
if (DateTime.Compare (parent.LastWriteTime, this.last_changed.LastWriteTime) > 0)
this.last_changed = parent;
foreach (DirectoryInfo info in parent.GetDirectories ()) {
if (!info.FullName.Contains ("/.")) {
if (DateTime.Compare (info.LastWriteTime, this.last_changed.LastWriteTime) > 0)
this.last_changed = info;
GetLastChange (info);
}
}
if (this.poll_count >= 8) {
foreach (FileInfo info in parent.GetFiles ()) {
if (!info.FullName.Contains ("/.")) {
if (DateTime.Compare (info.LastWriteTime, this.last_changed.LastWriteTime) > 0)
this.last_changed = info;
}
}
this.poll_count = 0;
}
} catch (Exception) {
// Don't care...
} }
} }
public void Dispose () public void Dispose ()
{ {
this.thread.Join (); Dispose (true);
this.thread.Abort (); GC.SuppressFinalize (this);
} }
private void Dispose (bool disposing)
{
if (m_stream != IntPtr.Zero) {
FSEventStreamStop (m_stream);
FSEventStreamInvalidate (m_stream);
FSEventStreamRelease (m_stream);
m_stream = IntPtr.Zero;
}
}
private void checkDirectory (string dir)
{
DirectoryInfo parent = new DirectoryInfo (dir);
if (!parent.FullName.Contains ("/.") &&
DateTime.Compare (parent.LastWriteTime, this.last_found_timestamp) > 0) {
last_found_timestamp = parent.LastWriteTime;
}
}
private void DoCallback (IntPtr streamRef, IntPtr clientCallBackInfo,
int numEvents, IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds)
{
int bytes = Marshal.SizeOf (typeof (IntPtr));
string [] paths = new string [numEvents];
for (int i = 0; i < numEvents; ++i) {
IntPtr p = Marshal.ReadIntPtr (eventPaths, i * bytes);
paths [i] = Marshal.PtrToStringAnsi (p);
checkDirectory (paths [i]);
}
var handler = Changed;
if (handler != null) {
string path = paths [0];
path = path.Substring (Path.Length);
path = path.Trim ("/".ToCharArray ());
handler (this, new SparkleMacWatcherEventArgs (path));
}
GC.KeepAlive (this);
}
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
private extern static IntPtr CFRunLoopGetMain ();
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
private extern static IntPtr FSEventStreamCreate (
IntPtr allocator,
FSEventStreamCallback callback,
IntPtr context,
IntPtr pathsToWatch,
ulong sinceWhen,
double latency,
FSEventStreamCreateFlags flags);
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
private extern static void FSEventStreamScheduleWithRunLoop (
IntPtr streamRef,
IntPtr runLoop,
IntPtr runLoopMode);
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
[return: MarshalAs (UnmanagedType.U1)]
private extern static bool FSEventStreamStart (
IntPtr streamRef);
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
private extern static void FSEventStreamStop (
IntPtr streamRef);
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
private extern static void FSEventStreamInvalidate (
IntPtr streamRef);
[DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
private extern static void FSEventStreamRelease (
IntPtr streamRef);
} }
} }

View file

@ -11,7 +11,7 @@
<RootNamespace>SparkleShare</RootNamespace> <RootNamespace>SparkleShare</RootNamespace>
<AssemblyName>SparkleShare</AssemblyName> <AssemblyName>SparkleShare</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ReleaseVersion>0.8.1</ReleaseVersion> <ReleaseVersion>0.8.2</ReleaseVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -59,7 +59,7 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
</Reference> </Reference>
<Reference Include="System.Net" /> <Reference Include="System.Net" />
<Reference Include="SparkleLib, Version=0.8.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="SparkleLib, Version=0.8.1.0, Culture=neutral, PublicKeyToken=null">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\bin\SparkleLib.dll</HintPath> <HintPath>..\..\bin\SparkleLib.dll</HintPath>
</Reference> </Reference>

View file

@ -16,6 +16,6 @@ Global
EndGlobalSection EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = SparkleShare.csproj StartupItem = SparkleShare.csproj
version = 0.8.1 version = 0.8.2
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -33,6 +33,7 @@ namespace SparkleShare {
public SparkleStatusIconController Controller = new SparkleStatusIconController (); public SparkleStatusIconController Controller = new SparkleStatusIconController ();
// TODO: Fix case
private Timer Animation; private Timer Animation;
private int FrameNumber; private int FrameNumber;
private string StateText; private string StateText;

View file

@ -39,7 +39,5 @@ include $(top_srcdir)/build/build.mk
bin_SCRIPTS = sparkleshare bin_SCRIPTS = sparkleshare
dist_man_MANS = $(top_srcdir)/man/sparkleshare.1
Applicationsdir = $(datadir)/applications Applicationsdir = $(datadir)/applications
dist_Applications_DATA = sparkleshare.desktop dist_Applications_DATA = sparkleshare.desktop

View file

@ -23,7 +23,6 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Mono.Unix; using Mono.Unix;
//using Mono.Unix.Native;
using SparkleLib; using SparkleLib;
namespace SparkleShare { namespace SparkleShare {
@ -88,10 +87,10 @@ namespace SparkleShare {
Console.WriteLine (_("This is free software, and you are welcome to redistribute it ")); Console.WriteLine (_("This is free software, and you are welcome to redistribute it "));
Console.WriteLine (_("under certain conditions. Please read the GNU GPLv3 for details.")); Console.WriteLine (_("under certain conditions. Please read the GNU GPLv3 for details."));
Console.WriteLine (" "); Console.WriteLine (" ");
Console.WriteLine (_("SparkleShare automatically syncs Git repositories in ")); Console.WriteLine (_("SparkleShare is a collaboration and sharing tool that is "));
Console.WriteLine (_("the ~/SparkleShare folder with their remote origins.")); Console.WriteLine (_("designed to keep things simple and to stay out of your way."));
Console.WriteLine (" "); Console.WriteLine (" ");
Console.WriteLine (_("Usage: sparkleshare [start|stop|restart] [OPTION]...")); Console.WriteLine (_("Usage: sparkleshare [start|stop|restart|version] [OPTION]..."));
Console.WriteLine (_("Sync SparkleShare folder with remote repositories.")); Console.WriteLine (_("Sync SparkleShare folder with remote repositories."));
Console.WriteLine (" "); Console.WriteLine (" ");
Console.WriteLine (_("Arguments:")); Console.WriteLine (_("Arguments:"));
@ -107,24 +106,5 @@ namespace SparkleShare {
Console.WriteLine (_("SparkleShare " + Defines.VERSION)); Console.WriteLine (_("SparkleShare " + Defines.VERSION));
Environment.Exit (0); Environment.Exit (0);
} }
// Strange magic needed by SetProcessName ()
// [DllImport ("libc")]
// private static extern int prctl (int option, byte [] arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5);
// Sets the Unix process name to 'sparkleshare' instead of 'mono'
/* private static void SetProcessName (string name)
{
try {
if (prctl (15, Encoding.ASCII.GetBytes (name + "\0"), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) != 0)
throw new ApplicationException ("Error setting process name: " +
Mono.Unix.Native.Stdlib.GetLastError ());
} catch (EntryPointNotFoundException) {
Console.WriteLine ("SetProcessName: Entry point not found");
}
} */
} }
} }

View file

@ -42,9 +42,6 @@ namespace SparkleShare {
public double ProgressPercentage = 0.0; public double ProgressPercentage = 0.0;
public string ProgressSpeed = ""; public string ProgressSpeed = "";
public event OnQuitWhileSyncingHandler OnQuitWhileSyncing;
public delegate void OnQuitWhileSyncingHandler ();
public event FolderFetchedEventHandler FolderFetched; public event FolderFetchedEventHandler FolderFetched;
public delegate void FolderFetchedEventHandler (string [] warnings); public delegate void FolderFetchedEventHandler (string [] warnings);
@ -132,10 +129,9 @@ namespace SparkleShare {
}; };
SparkleInviteListener invite_listener = new SparkleInviteListener (1986); SparkleInviteListener invite_listener = new SparkleInviteListener (1987);
invite_listener.InviteReceived += delegate (SparkleInvite invite) { invite_listener.InviteReceived += delegate (SparkleInvite invite) {
if (OnInvite != null && !FirstRun) if (OnInvite != null && !FirstRun)
OnInvite (invite); OnInvite (invite);
}; };
@ -303,8 +299,8 @@ namespace SparkleShare {
if (DateTime.Compare (existing_set.Timestamp, change_set.Timestamp) < 1) { if (DateTime.Compare (existing_set.Timestamp, change_set.Timestamp) < 1) {
existing_set.FirstTimestamp = existing_set.Timestamp; existing_set.FirstTimestamp = existing_set.Timestamp;
existing_set.Timestamp = change_set.Timestamp; existing_set.Timestamp = change_set.Timestamp;
existing_set.Revision = change_set.Revision; existing_set.Revision = change_set.Revision;
} else { } else {
existing_set.FirstTimestamp = change_set.Timestamp; existing_set.FirstTimestamp = change_set.Timestamp;
@ -351,37 +347,40 @@ namespace SparkleShare {
} else { } else {
if (change_set.Edited.Count > 0) { if (change_set.Edited.Count > 0) {
foreach (string file_path in change_set.Edited) { foreach (string file_path in change_set.Edited) {
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, event_entry += "<dd class='document edited'>";
change_set.Folder, file_path}.Combine ();
event_entry += FormatBreadCrumbs (
if (File.Exists (absolute_file_path)) Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
event_entry += "<dd class='document edited'><a href='" + absolute_file_path + "'>" + file_path + "</a></dd>"; file_path
else );
event_entry += "<dd class='document edited'>" + file_path + "</dd>";
event_entry += "</dd>";
} }
} }
if (change_set.Added.Count > 0) { if (change_set.Added.Count > 0) {
foreach (string file_path in change_set.Added) { foreach (string file_path in change_set.Added) {
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, event_entry += "<dd class='document added'>";
change_set.Folder, file_path}.Combine ();
event_entry += FormatBreadCrumbs (
if (File.Exists (absolute_file_path)) Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
event_entry += "<dd class='document added'><a href='" + absolute_file_path + "'>" + file_path + "</a></dd>"; file_path
else );
event_entry += "<dd class='document added'>" + file_path + "</dd>";
event_entry += "</dd>";
} }
} }
if (change_set.Deleted.Count > 0) { if (change_set.Deleted.Count > 0) {
foreach (string file_path in change_set.Deleted) { foreach (string file_path in change_set.Deleted) {
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, event_entry += "<dd class='document deleted'>";
change_set.Folder, file_path}.Combine ();
event_entry += FormatBreadCrumbs (
if (File.Exists (absolute_file_path)) Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
event_entry += "<dd class='document deleted'><a href='" + absolute_file_path + "'>" + file_path + "</a></dd>"; file_path
else );
event_entry += "<dd class='document deleted'>" + file_path + "</dd>";
event_entry += "</dd>";
} }
} }
@ -390,21 +389,19 @@ namespace SparkleShare {
foreach (string file_path in change_set.MovedFrom) { foreach (string file_path in change_set.MovedFrom) {
string to_file_path = change_set.MovedTo [i]; string to_file_path = change_set.MovedTo [i];
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, event_entry += "<dd class='document moved'>";
change_set.Folder, file_path}.Combine (); event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
file_path
);
string absolute_to_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, event_entry += "<br>";
change_set.Folder, to_file_path}.Combine (); event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
to_file_path
);
if (File.Exists (absolute_file_path)) event_entry += "</dd>";
event_entry += "<dd class='document moved'><a href='" + absolute_file_path + "'>" + file_path + "</a><br/>";
else
event_entry += "<dd class='document moved'>" + file_path + "<br/>";
if (File.Exists (absolute_to_file_path))
event_entry += "<a href='" + absolute_to_file_path + "'>" + to_file_path + "</a></dd>";
else
event_entry += to_file_path + "</dd>";
i++; i++;
} }
@ -620,7 +617,7 @@ namespace SparkleShare {
} }
}; };
repo.SyncProgressChanged += delegate (double percentage, string speed) { repo.ProgressChanged += delegate (double percentage, string speed) {
ProgressPercentage = percentage; ProgressPercentage = percentage;
ProgressSpeed = speed; ProgressSpeed = speed;
@ -785,13 +782,6 @@ namespace SparkleShare {
} }
public bool BackendIsPresent {
get {
return SparkleBackend.DefaultBackend.IsPresent;
}
}
// Looks up the user's name from the global configuration // Looks up the user's name from the global configuration
public string UserName public string UserName
{ {
@ -1099,16 +1089,6 @@ namespace SparkleShare {
} }
// Creates an MD5 hash of input
private string GetMD5 (string s)
{
MD5 md5 = new MD5CryptoServiceProvider ();
Byte[] bytes = ASCIIEncoding.Default.GetBytes (s);
Byte[] encoded_bytes = md5.ComputeHash (bytes);
return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", "");
}
// Checks whether there are any folders syncing and // Checks whether there are any folders syncing and
// quits if safe // quits if safe
public void TryQuit () public void TryQuit ()
@ -1118,9 +1098,6 @@ namespace SparkleShare {
repo.Status == SyncStatus.SyncDown || repo.Status == SyncStatus.SyncDown ||
repo.IsBuffering) { repo.IsBuffering) {
if (OnQuitWhileSyncing != null)
OnQuitWhileSyncing ();
return; return;
} }
} }
@ -1166,6 +1143,55 @@ namespace SparkleShare {
int number = 3 + int.Parse (numbers); int number = 3 + int.Parse (numbers);
return this.tango_palette [number % this.tango_palette.Length]; return this.tango_palette [number % this.tango_palette.Length];
} }
// Creates an MD5 hash of input
private string GetMD5 (string s)
{
MD5 md5 = new MD5CryptoServiceProvider ();
Byte[] bytes = ASCIIEncoding.Default.GetBytes (s);
Byte[] encoded_bytes = md5.ComputeHash (bytes);
return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", "");
}
private string FormatBreadCrumbs (string path_root, string path)
{
string link = "";
string [] crumbs = path.Split (Path.DirectorySeparatorChar);
int i = 0;
string new_path_root = path_root;
bool previous_was_folder = false;
foreach (string crumb in crumbs) {
if (string.IsNullOrEmpty (crumb))
continue;
string crumb_path = Path.Combine (new_path_root, crumb);
if (Directory.Exists (crumb_path)) {
link += "<a href='" + crumb_path + "'>" + crumb + Path.DirectorySeparatorChar + "</a>";
previous_was_folder = true;
} else if (File.Exists (crumb_path)) {
link += "<a href='" + crumb_path + "'>" + crumb + "</a>";
previous_was_folder = false;
} else {
if (i > 0 && !previous_was_folder)
link += Path.DirectorySeparatorChar;
link += crumb;
previous_was_folder = false;
}
new_path_root = Path.Combine (new_path_root, crumb);
i++;
}
return link;
}
} }

View file

@ -60,7 +60,37 @@ namespace SparkleShare {
public SparkleInvite (string xml_file_path) public SparkleInvite (string xml_file_path)
{ {
// TODO XmlDocument xml_document = new XmlDocument ();
XmlNode node;
string host = "", path = "", token = "";
try {
xml_document.Load (xml_file_path);
node = xml_document.SelectSingleNode ("/sparkleshare/invite/host/text()");
if (node != null) { host = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/path/text()");
if (node != null) { path = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/token/text()");
if (node != null) { token = node.Value; }
} catch (XmlException e) {
SparkleHelpers.DebugInfo ("Invite", "Invalid XML: " + e.Message);
return;
}
if (path.StartsWith ("/"))
path = path.Substring (1);
if (!host.EndsWith ("/"))
host = host + "/";
FullAddress = new Uri ("ssh://" + host + path);
Token = token;
} }
} }
@ -168,9 +198,8 @@ namespace SparkleShare {
XmlDocument xml_document = new XmlDocument (); XmlDocument xml_document = new XmlDocument ();
XmlNode node; XmlNode node;
string host = "";
string path = ""; string host = "", path = "", token = "";
string token = "";
try { try {
xml_document.LoadXml (invite_xml); xml_document.LoadXml (invite_xml);
@ -185,7 +214,7 @@ namespace SparkleShare {
if (node != null) { token = node.Value; } if (node != null) { token = node.Value; }
} catch (XmlException e) { } catch (XmlException e) {
SparkleHelpers.DebugInfo ("Invite", "Not valid XML: " + received_message + " " + e.Message); SparkleHelpers.DebugInfo ("Invite", "Invalid XML: " + received_message + " " + e.Message);
return; return;
} }

View file

@ -16,56 +16,83 @@
using System; using System;
using System.IO;
using System.Xml; using System.Xml;
namespace SparkleShare { namespace SparkleShare {
public class SparklePlugin { public class SparklePlugin {
public string Name; public string Name {
public string Description; get {
public string ImagePath; return GetValue ("info", "name");
public string Backend; }
}
public string Address; public string Description {
public string AddressExample; get {
public string Path; return GetValue ("info", "description");
public string PathExample; }
}
public string ImagePath {
get {
return System.IO.Path.Combine (
this.plugin_directory,
GetValue ("info", "icon")
);
}
}
public string Backend {
get {
return GetValue ("info", "backend");
}
}
public string Address {
get {
return GetValue ("address", "value");
}
}
public string AddressExample {
get {
return GetValue ("address", "example");
}
}
public string Path {
get {
return GetValue ("path", "value");
}
}
public string PathExample {
get {
return GetValue ("path", "example");
}
}
private XmlDocument xml = new XmlDocument ();
private string plugin_directory;
public SparklePlugin (string plugin_path) public SparklePlugin (string plugin_path)
{ {
string plugin_directory = System.IO.Path.GetDirectoryName (plugin_path); this.plugin_directory = System.IO.Path.GetDirectoryName (plugin_path);
this.xml.Load (plugin_path);
}
XmlDocument xml = new XmlDocument ();
xml.Load (plugin_path);
XmlNode node; private string GetValue (string a, string b)
{
XmlNode node = this.xml.SelectSingleNode (
"/sparkleshare/plugin/" + a + "/" + b + "/text()");
node = xml.SelectSingleNode ("/sparkleshare/plugin/info/name/text()"); if (node != null)
if (node != null) { Name = node.Value; } return node.Value;
else
node = xml.SelectSingleNode ("/sparkleshare/plugin/info/description/text()"); return null;
if (node != null) { Description = node.Value; }
node = xml.SelectSingleNode ("/sparkleshare/plugin/info/icon/text()");
if (node != null) { ImagePath = System.IO.Path.Combine (plugin_directory, node.Value); }
node = xml.SelectSingleNode ("/sparkleshare/plugin/info/backend/text()");
if (node != null) { Backend = node.Value; }
node = xml.SelectSingleNode ("/sparkleshare/plugin/address/value/text()");
if (node != null) { Address = node.Value; }
node = xml.SelectSingleNode ("/sparkleshare/plugin/address/example/text()");
if (node != null) { AddressExample = node.Value; }
node = xml.SelectSingleNode ("/sparkleshare/plugin/path/value/text()");
if (node != null) { Path = node.Value; }
node = xml.SelectSingleNode ("/sparkleshare/plugin/path/example/text()");
if (node != null) { PathExample = node.Value; }
} }
} }
} }

View file

@ -33,12 +33,11 @@ namespace SparkleShare {
public SparkleStatusIconController Controller = new SparkleStatusIconController (); public SparkleStatusIconController Controller = new SparkleStatusIconController ();
// TODO: fix case private Timer animation;
private Timer Animation; private Gdk.Pixbuf [] animation_frames;
private Gdk.Pixbuf [] AnimationFrames; private int frame_number;
private int FrameNumber; private string state_text;
private string StateText; private Menu menu;
private Menu Menu;
private MenuItem quit_item; private MenuItem quit_item;
#if HAVE_APP_INDICATOR #if HAVE_APP_INDICATOR
@ -56,8 +55,8 @@ namespace SparkleShare {
public SparkleStatusIcon () public SparkleStatusIcon ()
{ {
AnimationFrames = CreateAnimationFrames (); CreateAnimationFrames ();
Animation = CreateAnimation (); CreateAnimation ();
#if HAVE_APP_INDICATOR #if HAVE_APP_INDICATOR
this.indicator = new ApplicationIndicator ("sparkleshare", this.indicator = new ApplicationIndicator ("sparkleshare",
@ -70,13 +69,13 @@ namespace SparkleShare {
this.status_icon.Activate += ShowMenu; // Primary mouse button click this.status_icon.Activate += ShowMenu; // Primary mouse button click
this.status_icon.PopupMenu += ShowMenu; // Secondary mouse button click this.status_icon.PopupMenu += ShowMenu; // Secondary mouse button click
this.status_icon.Pixbuf = AnimationFrames [0]; this.status_icon.Pixbuf = this.animation_frames [0];
#endif #endif
if (Controller.Folders.Length == 0) if (Controller.Folders.Length == 0)
StateText = _("Welcome to SparkleShare!"); this.state_text = _("Welcome to SparkleShare!");
else else
StateText = _("Up to date") + Controller.FolderSize; this.state_text = _("Up to date") + Controller.FolderSize;
CreateMenu (); CreateMenu ();
@ -85,7 +84,7 @@ namespace SparkleShare {
Application.Invoke (delegate { Application.Invoke (delegate {
if (this.quit_item != null) { if (this.quit_item != null) {
this.quit_item.Sensitive = quit_item_enabled; this.quit_item.Sensitive = quit_item_enabled;
Menu.ShowAll (); this.menu.ShowAll ();
} }
}); });
}; };
@ -95,17 +94,17 @@ namespace SparkleShare {
switch (state) { switch (state) {
case IconState.Idle: case IconState.Idle:
Animation.Stop (); this.animation.Stop ();
if (Controller.Folders.Length == 0) if (Controller.Folders.Length == 0)
StateText = _("Welcome to SparkleShare!"); this.state_text = _("Welcome to SparkleShare!");
else else
StateText = _("Up to date") + Controller.FolderSize; this.state_text = _("Up to date") + Controller.FolderSize;
#if HAVE_APP_INDICATOR #if HAVE_APP_INDICATOR
this.indicator.IconName = "process-syncing-sparkleshare-i"; this.indicator.IconName = "process-syncing-sparkleshare-i";
#else #else
this.status_icon.Pixbuf = AnimationFrames [0]; this.status_icon.Pixbuf = this.animation_frames [0];
#endif #endif
UpdateStateText (); UpdateStateText ();
@ -115,22 +114,22 @@ namespace SparkleShare {
case IconState.Syncing: case IconState.Syncing:
StateText = _("Syncing… ") + this.state_text = _("Syncing… ") +
Controller.ProgressPercentage + "% " + Controller.ProgressPercentage + "% " +
Controller.ProgressSpeed; Controller.ProgressSpeed;
UpdateStateText (); UpdateStateText ();
if (!Animation.Enabled) if (!this.animation.Enabled)
Animation.Start (); this.animation.Start ();
break; break;
case IconState.Error: case IconState.Error:
Animation.Stop (); this.animation.Stop ();
StateText = _("Not everything is synced"); this.state_text = _("Not everything is synced");
UpdateStateText (); UpdateStateText ();
CreateMenu (); CreateMenu ();
@ -143,7 +142,7 @@ namespace SparkleShare {
break; break;
} }
Menu.ShowAll (); this.menu.ShowAll ();
}); });
}; };
} }
@ -151,47 +150,43 @@ namespace SparkleShare {
// Slices up the graphic that contains the // Slices up the graphic that contains the
// animation frames. // animation frames.
private Gdk.Pixbuf [] CreateAnimationFrames () private void CreateAnimationFrames ()
{ {
Gdk.Pixbuf [] animation_frames = new Gdk.Pixbuf [5]; this.animation_frames = new Gdk.Pixbuf [5];
Gdk.Pixbuf frames_pixbuf = SparkleUIHelpers.GetIcon ("process-syncing-sparkleshare", 24); Gdk.Pixbuf frames_pixbuf = SparkleUIHelpers.GetIcon ("process-syncing-sparkleshare", 24);
for (int i = 0; i < animation_frames.Length; i++) for (int i = 0; i < this.animation_frames.Length; i++)
animation_frames [i] = new Gdk.Pixbuf (frames_pixbuf, (i * 24), 0, 24, 24); animation_frames [i] = new Gdk.Pixbuf (frames_pixbuf, (i * 24), 0, 24, 24);
return animation_frames;
} }
// Creates the Animation that handles the syncing animation // Creates the animation that handles the syncing animation
private Timer CreateAnimation () private void CreateAnimation ()
{ {
FrameNumber = 0; this.frame_number = 0;
Timer Animation = new Timer () { this.animation = new Timer () {
Interval = 35 Interval = 35
}; };
Animation.Elapsed += delegate { this.animation.Elapsed += delegate {
if (FrameNumber < AnimationFrames.Length - 1) if (this.frame_number < this.animation_frames.Length - 1)
FrameNumber++; this.frame_number++;
else else
FrameNumber = 0; this.frame_number = 0;
string icon_name = "process-syncing-sparkleshare-"; string icon_name = "process-syncing-sparkleshare-";
for (int i = 0; i <= FrameNumber; i++) for (int i = 0; i <= this.frame_number; i++)
icon_name += "i"; icon_name += "i";
Application.Invoke (delegate { Application.Invoke (delegate {
#if HAVE_APP_INDICATOR #if HAVE_APP_INDICATOR
this.indicator.IconName = icon_name; this.indicator.IconName = icon_name;
#else #else
this.status_icon.Pixbuf = AnimationFrames [FrameNumber]; this.status_icon.Pixbuf = this.animation_frames [this.frame_number];
#endif #endif
}); });
}; };
return Animation;
} }
@ -199,15 +194,15 @@ namespace SparkleShare {
// user clicks the status icon // user clicks the status icon
public void CreateMenu () public void CreateMenu ()
{ {
Menu = new Menu (); this.menu = new Menu ();
// The menu item showing the status and size of the SparkleShare folder // The menu item showing the status and size of the SparkleShare folder
MenuItem status_menu_item = new MenuItem (StateText) { MenuItem status_menu_item = new MenuItem (this.state_text) {
Sensitive = false Sensitive = false
}; };
Menu.Add (status_menu_item); this.menu.Add (status_menu_item);
Menu.Add (new SeparatorMenuItem ()); this.menu.Add (new SeparatorMenuItem ());
ImageMenuItem folder_item = new SparkleMenuItem ("SparkleShare"){ ImageMenuItem folder_item = new SparkleMenuItem ("SparkleShare"){
Image = new Image (SparkleUIHelpers.GetIcon ("folder-sparkleshare", 16)) Image = new Image (SparkleUIHelpers.GetIcon ("folder-sparkleshare", 16))
@ -217,7 +212,7 @@ namespace SparkleShare {
Program.Controller.OpenSparkleShareFolder (); Program.Controller.OpenSparkleShareFolder ();
}; };
Menu.Add (folder_item); this.menu.Add (folder_item);
if (Program.Controller.Folders.Count > 0) { if (Program.Controller.Folders.Count > 0) {
@ -239,7 +234,7 @@ namespace SparkleShare {
}; };
subfolder_item.Activated += OpenFolderDelegate (folder_name); subfolder_item.Activated += OpenFolderDelegate (folder_name);
Menu.Add (subfolder_item); this.menu.Add (subfolder_item);
} }
} else { } else {
@ -247,10 +242,10 @@ namespace SparkleShare {
Sensitive = false Sensitive = false
}; };
Menu.Add (no_folders_item); this.menu.Add (no_folders_item);
} }
Menu.Add (new SeparatorMenuItem ()); this.menu.Add (new SeparatorMenuItem ());
// Opens the wizard to add a new remote folder // Opens the wizard to add a new remote folder
MenuItem sync_item = new MenuItem (_("Add Hosted Project…")); MenuItem sync_item = new MenuItem (_("Add Hosted Project…"));
@ -274,8 +269,8 @@ namespace SparkleShare {
}); });
}; };
Menu.Add (sync_item); this.menu.Add (sync_item);
Menu.Add (new SeparatorMenuItem ()); this.menu.Add (new SeparatorMenuItem ());
MenuItem recent_events_item = new MenuItem (_("Open Recent Events")); MenuItem recent_events_item = new MenuItem (_("Open Recent Events"));
@ -291,7 +286,7 @@ namespace SparkleShare {
}); });
}; };
Menu.Add (recent_events_item); this.menu.Add (recent_events_item);
MenuItem notify_item; MenuItem notify_item;
@ -305,8 +300,8 @@ namespace SparkleShare {
CreateMenu (); CreateMenu ();
}; };
Menu.Add (notify_item); this.menu.Add (notify_item);
Menu.Add (new SeparatorMenuItem ()); this.menu.Add (new SeparatorMenuItem ());
// A menu item that takes the user to http://www.sparkleshare.org/ // A menu item that takes the user to http://www.sparkleshare.org/
MenuItem about_item = new MenuItem (_("About SparkleShare")); MenuItem about_item = new MenuItem (_("About SparkleShare"));
@ -321,8 +316,8 @@ namespace SparkleShare {
}); });
}; };
Menu.Add (about_item); this.menu.Add (about_item);
Menu.Add (new SeparatorMenuItem ()); this.menu.Add (new SeparatorMenuItem ());
// A menu item that quits the application // A menu item that quits the application
this.quit_item = new MenuItem (_("Quit")) { this.quit_item = new MenuItem (_("Quit")) {
@ -333,11 +328,11 @@ namespace SparkleShare {
Program.Controller.Quit (); Program.Controller.Quit ();
}; };
Menu.Add (this.quit_item); this.menu.Add (this.quit_item);
Menu.ShowAll (); this.menu.ShowAll ();
#if HAVE_APP_INDICATOR #if HAVE_APP_INDICATOR
this.indicator.Menu = Menu; this.indicator.Menu = this.menu;
#endif #endif
} }
@ -354,15 +349,15 @@ namespace SparkleShare {
public void UpdateStateText () public void UpdateStateText ()
{ {
((Menu.Children [0] as MenuItem).Child as Label).Text = StateText; ((this.menu.Children [0] as MenuItem).Child as Label).Text = this.state_text;
Menu.ShowAll (); this.menu.ShowAll ();
} }
#if !HAVE_APP_INDICATOR #if !HAVE_APP_INDICATOR
// Makes the menu visible // Makes the menu visible
private void ShowMenu (object o, EventArgs args) private void ShowMenu (object o, EventArgs args)
{ {
Menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime); this.menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime);
} }

View file

@ -69,12 +69,9 @@ namespace SparkleShare {
Setup = new SparkleSetup (); Setup = new SparkleSetup ();
Setup.Controller.ShowSetupPage (); Setup.Controller.ShowSetupPage ();
} }
Program.Controller.OnQuitWhileSyncing += delegate {
// TODO: Pop up a warning when quitting whilst syncing
};
} }
// Runs the application // Runs the application
public void Run () public void Run ()
{ {

View file

@ -42,7 +42,7 @@ stop() {
rm -f ${pidfile} rm -f ${pidfile}
echo "Done." echo "Done."
else else
echo "SparkleShare is not running, removing stale pid file." echo "SparkleShare is not running, removing stale pid file..."
rm -f ${pidfile} rm -f ${pidfile}
fi fi
else else
@ -64,14 +64,10 @@ case $1 in
help|--help|-h) help|--help|-h)
mono "@expanded_libdir@/@PACKAGE@/SparkleShare.exe" --help mono "@expanded_libdir@/@PACKAGE@/SparkleShare.exe" --help
;; ;;
-d|--disable-gui) version|--version|-v)
mono "@expanded_libdir@/@PACKAGE@/SparkleShare.exe" --disable-gui
;;
-v|--version)
mono "@expanded_libdir@/@PACKAGE@/SparkleShare.exe" --version mono "@expanded_libdir@/@PACKAGE@/SparkleShare.exe" --version
;; ;;
*) *)
echo "Usage: sparkleshare {start|stop|restart|help}" echo "Usage: sparkleshare {start|stop|restart|help|version}"
;; ;;
esac esac

View file

@ -1,9 +1,9 @@
dnl Process this file with autoconf to produce a configure script. dnl Process this file with autoconf to produce a configure script.
m4_define([sparkleshare_version], m4_define([sparkleshare_version],
[0.8.1]) [0.8.2])
m4_define([sparkleshare_asm_version], m4_define([sparkleshare_asm_version],
[0.8.1]) [0.8.2])
AC_PREREQ([2.54]) AC_PREREQ([2.54])
AC_INIT([SparkleShare], sparkleshare_version) AC_INIT([SparkleShare], sparkleshare_version)
@ -154,7 +154,6 @@ data/Makefile
data/icons/Makefile data/icons/Makefile
data/html/Makefile data/html/Makefile
data/plugins/Makefile data/plugins/Makefile
help/Makefile
SparkleLib/AssemblyInfo.cs SparkleLib/AssemblyInfo.cs
SparkleLib/Defines.cs SparkleLib/Defines.cs
SparkleLib/Makefile SparkleLib/Makefile
@ -176,7 +175,6 @@ Configuration:
Build Gtk+ UI : ${enable_gtkui} Build Gtk+ UI : ${enable_gtkui}
Nautilus 2.x plugin : ${have_nautilus2_python} Nautilus 2.x plugin : ${have_nautilus2_python}
Nautilus 3.x plugin : ${have_nautilus3_python} Nautilus 3.x plugin : ${have_nautilus3_python}
User Help : ${enable_user_help} (requires gnome-doc-utils >= 0.17.3)
" "

View file

@ -139,6 +139,7 @@
a:hover { a:hover {
color: <!-- $a-hover-color -->; color: <!-- $a-hover-color -->;
text-decoration: underline;
} }
.event-timestamp { .event-timestamp {

View file

@ -1,32 +0,0 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:e="http://projectmallard.org/experimental/"
type="topic" style="task"
id="accounts">
<info>
<link type="guide" xref="index#account"/>
<link type="seealso" xref=""/>
<desc>Add the location of your remote folders.</desc>
<revision pkgversion="0.1" version="0.1" date="2010-08-9" status="stub"/>
<credit type="author">
<name>Paul Cutler</name>
<email>pcutler@gnome.org</email>
</credit>
<!--
<copyright>
<year>2010</year>
<name>GNOME Documentation Project</name>
</copyright>
-->
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude"/>
</info>
<title>Account Setup</title>
<p>Insert how to setup your accounts here)
</p>
<p>Insert more help here, if needed.
</p>
</page>

View file

@ -1,32 +0,0 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:e="http://projectmallard.org/experimental/"
type="guide" style="2column"
id="advanced">
<info>
<link type="guide" xref="index#advanced"/>
<desc>Get help for advanced actions.</desc>
<revision pkgversion="0.1" version="0.1" date="2010-08-29" status="draft"/>
<credit type="author">
<name>Paul Cutler</name>
<email>pcutler@gnome.org</email>
</credit>
<!--
<copyright>
<year>2010</year>
<name>GNOME Documentation Project</name>
</copyright>
-->
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude"/>
</info>
<title>Advanced Options and Help</title>
<section id="tbd" style="2column">
<info>
<title type="link">TBD</title>
</info>
<title>TBD</title>
</section>
</page>

View file

@ -1,40 +0,0 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:e="http://projectmallard.org/experimental/"
type="guide"
id="index">
<info>
<revision pkgversion="0.1" version="0.1" date="2010-08-29"
status="incomplete"/>
<credit type="author">
<name>Paul Cutler</name>
<email>pcutler@gnome.org</email>
</credit>
<!--
<copyright>
<year>2010</year>
<name>GNOME Documentation Project</name>
</copyright>
-->
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude" />
</info>
<title>SparkleShare</title>
<section id="account" style="2column">
<title>Account Setup</title>
</section>
<section id="share" style="2column">
<title>Sync and Share Files</title>
</section>
<section id="advanced" style="2column">
<title>Advanced options and help</title>
</section>
<section id="problems">
<title>Common Problems</title>
</section>
</page>

View file

@ -1,42 +0,0 @@
<page xmlns="http://projectmallard.org/1.0/"
type="topic"
id="introduction">
<info>
<link type="guide" xref="index"/>
<revision pkgversion="0.1" version="0.1" date="2010-08-29" status="draft"/>
<desc>
Introduction to <app>SparkleShare</app>.
</desc>
<credit type="author">
<name>Paul Cutler</name>
<email>pcutler@gnome.org</email>
</credit>
<!--
<copyright>
<year>2010</year>
<name>GNOME Documentation Project</name>
</copyright>
-->
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude" />
</info>
<title>Introduction</title>
<p>
<app>SparkleShare</app> is an application that allows you to easily
sync and share your files and folders. SparkeShare uses the
distributed version control system <app>Git</app> to keep a record
of all the changes in your files, making it easy to easily go back
to an earlier version of the file if you make a mistake.
</p>
<figure>
<title><gui>SparkleShare</gui> screenshot</title>
<desc><app>SparkleShare</app></desc>
<media type="image" src="figures/sparkleshare.png" mime="image/png" style="right">
<p><app>Sparkleshare</app></p>
</media>
</figure>
</page>

View file

@ -1,9 +0,0 @@
<license xmlns="http://projectmallard.org/1.0/"
href="http://creativecommons.org/licenses/by-sa/3.0/">
<p>This work is licensed under a
<link href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons
Attribution-Share Alike 3.0 Unported License</link>.</p>
<p>As a special exception, the copyright holders give you permission to copy,
modify, and distribute the example code contained in this document under the
terms of your choosing, without restriction.</p>
</license>

View file

@ -1,32 +0,0 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:e="http://projectmallard.org/experimental/"
type="topic" style="task"
id="share">
<info>
<link type="guide" xref="index#share"/>
<link type="seealso" xref=""/>
<desc>Sync and share your folders and files.</desc>
<revision pkgversion="1.6" version="0.1" date="2010-07-11" status="draft"/>
<credit type="author">
<name>Paul Cutler</name>
<email>pcutler@gnome.org</email>
</credit>
<!--
<copyright>
<year>2010</year>
<name>GNOME Documentation Project</name>
</copyright>
-->
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude"/>
</info>
<title>Sync and share your files and folders</title>
<p>Insert help here.
</p>
<p>
</p>
</page>

View file

@ -1,17 +0,0 @@
if HAVE_GNOME_DOC_UTILS
include $(top_srcdir)/gnome-doc-utils.make
DOC_ID = sparkleshare
DOC_INCLUDES = legal.xml
DOC_PAGES = account-creation.page \
advanced.page \
index.page \
introduction.page \
share.page
DOC_LINGUAS =
dist-hook: doc-dist-hook
endif

View file

@ -1,36 +0,0 @@
.TH sparkleshare 1 "August 16, 2010" "version 0.2" "USER COMMANDS"
.SH NAME
sparkleshare \- sharing work made easy
.SH SYNOPSIS
.B sparkleshare
[start|stop|restart|help]
.SH DESCRIPTION
A file sharing system for the desktop that retains a full history of
changes made, and allows work to be sychronized across many systems.
Based on the underlying `git' version control system, sparkleshare is
fast and secure, but the interface is straightforward enough for all
to use.
.SH OPTIONS
.TP
start
Start the sparkleshare system, presenting an introductory dialog if
this is the first time that it has been run.
.TP
stop
Stop sparkleshare, halting all current file sharing and no longer watch
for changes in the configured shares.
.TP
restart
Restart sparkleshare.
.SH RETURN VALUES
Currently sparkleshare only ever returns 0 on exit.
.SH FILES
.TP
~/SparkleShare
Directories set up to be shared are placed under this directory.
.SH BUGS
SparkleShare is currently under development, it is likely that there
are a number of bugs present in the system. Since the underlying
file sharing is simply based on `git', even if SparkleShare itself fails,
the data is retrievable in an open format. On-disk corruption is
extremely unlikely.