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)
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:
- Disable the Quit menu item when a sync is going on
@ -9,7 +16,7 @@
- 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:
- Show syncing progress in the status icon

View file

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

View file

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

View file

@ -16,6 +16,7 @@
using System;
using System.IO;
using System.Diagnostics;
namespace SparkleLib {
@ -23,28 +24,58 @@ namespace SparkleLib {
public class SparkleGit : Process {
public static string ExecPath = null;
public static string Path = null;
public SparkleGit (string path, string args) : base ()
{
Path = LocateGit ();
EnableRaisingEvents = true;
StartInfo.FileName = SparkleBackend.DefaultBackend.Path;
StartInfo.FileName = Path;
StartInfo.RedirectStandardOutput = true;
StartInfo.UseShellExecute = false;
StartInfo.WorkingDirectory = path;
StartInfo.CreateNoWindow = true;
if (!string.IsNullOrEmpty (ExecPath))
StartInfo.Arguments = "--exec-path=\"" + ExecPath + "\" " + args;
else
if (string.IsNullOrEmpty (ExecPath))
StartInfo.Arguments = args;
else
StartInfo.Arguments = "--exec-path=\"" + ExecPath + "\" " + args;
}
new public void Start ()
{
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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
namespace SparkleLib {
@ -63,7 +61,7 @@ namespace SparkleLib {
public override List<string> ExcludePaths {
get {
List<string> rules = new List<string> ();
rules.Add (Path.DirectorySeparatorChar + ".git");
rules.Add (".git");
return rules;
}
@ -98,12 +96,12 @@ namespace SparkleLib {
}
private void CalculateSizes ()
private void UpdateSizes ()
{
double size = CalculateSize (
double size = CalculateSizes (
new DirectoryInfo (LocalPath));
double history_size = CalculateSize (
double history_size = CalculateSizes (
new DirectoryInfo (Path.Combine (LocalPath, ".git")));
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...");
SparkleGit git = new SparkleGit (LocalPath, "ls-remote " + Url + " master");
get {
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 ();
git.WaitForExit ();
return true;
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 + ")");
return true;
} else {
return false;
} else {
return false;
}
}
}
public override bool SyncUp ()
{
if (AnyDifferences) {
if (HasLocalChanges) {
Add ();
string message = FormatCommitMessage ();
@ -242,13 +244,13 @@ namespace SparkleLib {
if (number >= percentage) {
percentage = number;
base.OnSyncProgressChanged (percentage, speed);
base.OnProgressChanged (percentage, speed);
}
}
git.WaitForExit ();
CalculateSizes ();
UpdateSizes ();
if (git.ExitCode == 0)
return true;
@ -299,13 +301,13 @@ namespace SparkleLib {
if (number >= percentage) {
percentage = number;
base.OnSyncProgressChanged (percentage, speed);
base.OnProgressChanged (percentage, speed);
}
}
git.WaitForExit ();
CalculateSizes ();
UpdateSizes ();
if (git.ExitCode == 0) {
Rebase ();
@ -317,7 +319,7 @@ namespace SparkleLib {
}
public override bool AnyDifferences {
public override bool HasLocalChanges {
get {
PrepareDirectories (LocalPath);
@ -412,7 +414,7 @@ namespace SparkleLib {
{
DisableWatching ();
if (AnyDifferences) {
if (HasLocalChanges) {
Add ();
string commit_message = FormatCommitMessage ();
@ -427,7 +429,7 @@ namespace SparkleLib {
if (git.ExitCode != 0) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict detected. Trying to get out...");
while (AnyDifferences)
while (HasLocalChanges)
ResolveConflict ();
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
@ -653,7 +655,8 @@ namespace SparkleLib {
string to_file_path;
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")) {
change_set.Added.Add (file_path);
@ -669,6 +672,14 @@ namespace SparkleLib {
file_path = entry_line.Substring (42, tab_pos - 42);
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.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
public override double CalculateSize (DirectoryInfo parent)
private double CalculateSizes (DirectoryInfo parent)
{
if (!Directory.Exists (parent.ToString ()))
return 0;
double size = 0;
// Ignore the temporary 'rebase-apply' and '.tmp' directories. This prevents potential
// crashes when files are being queried whilst the files have already been deleted.
if (parent.Name.Equals ("rebase-apply") ||
parent.Name.Equals (".tmp"))
if (parent.Name.Equals ("rebase-apply"))
return 0;
try {
@ -846,7 +838,7 @@ namespace SparkleLib {
}
foreach (DirectoryInfo directory in parent.GetDirectories ())
size += CalculateSize (directory);
size += CalculateSizes (directory);
} catch (Exception) {
return 0;

View file

@ -6,6 +6,7 @@ SOURCES = \
Git/SparkleFetcherGit.cs \
Git/SparkleGit.cs \
Git/SparkleRepoGit.cs \
SparkleAnnouncement.cs \
SparkleBackend.cs \
SparkleChangeSet.cs \
SparkleConfig.cs \
@ -13,6 +14,7 @@ SOURCES = \
SparkleFetcherBase.cs \
SparkleHelpers.cs \
SparkleListenerBase.cs \
SparkleListenerFactory.cs \
SparkleListenerTcp.cs \
SparkleRepoBase.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 {
public 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 class SparkleBackend {
public static string Version {
get {
@ -65,7 +32,7 @@ namespace SparkleLib {
// Strange magic needed by Platform ()
[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,
@ -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),
"sparkleshare");
// TODO: declare elsewhere
public static SparkleConfig DefaultConfig = new SparkleConfig (default_config_path, "config.xml");
public static bool DebugMode = true;

View file

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

View file

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

View file

@ -16,76 +16,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Timers;
using System.Linq;
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
// listens for change notifications
public abstract class SparkleListenerBase {
@ -105,8 +40,8 @@ namespace SparkleLib {
public abstract void Connect ();
public abstract bool IsConnected { get; }
public abstract bool IsConnecting { get; }
protected abstract void Announce (SparkleAnnouncement announcent);
protected abstract void AlsoListenTo (string folder_identifier);
protected abstract void AnnounceInternal (SparkleAnnouncement announcent);
protected abstract void AlsoListenToInternal (string folder_identifier);
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 (IsConnected) {
@ -151,7 +86,7 @@ namespace SparkleLib {
"Announcing message " + announcement.Message + " to " +
announcement.FolderIdentifier + " on " + Server);
Announce (announcement);
AnnounceInternal (announcement);
AddRecentAnnouncement (announcement);
} else {
@ -170,14 +105,14 @@ namespace SparkleLib {
}
public void AlsoListenToBase (string channel)
public void AlsoListenTo (string channel)
{
if (!this.channels.Contains (channel) && IsConnected) {
SparkleHelpers.DebugInfo ("Listener",
"Subscribing to channel " + channel);
this.channels.Add (channel);
AlsoListenTo (channel);
AlsoListenToInternal (channel);
}
}
@ -202,7 +137,7 @@ namespace SparkleLib {
foreach (KeyValuePair<string, SparkleAnnouncement> item in this.queue_up) {
SparkleAnnouncement announcement = item.Value;
AnnounceBase (announcement);
Announce (announcement);
}
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)
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.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace SparkleLib {
public class SparkleListenerTcp : SparkleListenerBase {
private Socket socket;
private Object socket_lock = new Object ();
private Thread thread;
private bool is_connected = false;
private bool is_connecting = false;
private Object socket_lock = new Object ();
private bool is_connected = false;
private bool is_connecting = false;
private DateTime last_ping = DateTime.Now;
public SparkleListenerTcp (Uri server, string folder_identifier) :
@ -60,83 +57,138 @@ namespace SparkleLib {
// Starts a new thread and listens to the channel
public override void Connect ()
{
SparkleHelpers.DebugInfo ("ListenerTcp", "Connecting to " + Server.Host);
this.is_connecting = true;
this.thread = new Thread (
new ThreadStart (delegate {
int port = Server.Port;
if (port < 0)
port = 1986;
try {
// Connect and subscribe to the channel
int port = Server.Port;
if (port < 0)
port = 9999;
lock (this.socket_lock) {
this.socket = new Socket (AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp) {
ReceiveTimeout = 30 * 1000
ReceiveTimeout = 5 * 1000,
SendTimeout = 5 * 1000
};
// Try to connect to the server
this.socket.Connect (Server.Host, port);
this.is_connecting = false;
this.is_connected = true;
this.is_connected = true;
OnConnected ();
foreach (string channel in base.channels) {
SparkleHelpers.DebugInfo ("ListenerTcp", "Subscribing to channel " + channel);
this.socket.Send (Encoding.UTF8.GetBytes ("subscribe " + channel + "\n"));
}
// Subscribe to channels of interest to us
foreach (string channel in base.channels)
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) {
SparkleHelpers.DebugInfo ("ListenerTcp", "Could not connect to " + Server + ": " + e.Message);
this.is_connected = false;
this.is_connected = 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";
try {
lock (this.socket_lock) {
lock (this.socket_lock)
this.socket.Send (Encoding.UTF8.GetBytes (to_send));
}
this.last_ping = DateTime.Now;
} catch (SocketException e) {
SparkleHelpers.DebugInfo ("ListenerTcp",
"Could not connect to " + Server + ": " + e.Message);
this.is_connected = 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
+ " " + announcement.Message + "\n";
@ -175,14 +228,13 @@ namespace SparkleLib {
lock (this.socket_lock)
this.socket.Send (Encoding.UTF8.GetBytes (to_send));
} catch (SocketException e) {
SparkleHelpers.DebugInfo ("ListenerTcp",
"Could not connect to " + Server + ": " + e.Message);
this.last_ping = DateTime.Now;
} catch (SocketException e) {
this.is_connected = 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.Threading;
using System.Timers;
using System.Xml;
namespace SparkleLib {
@ -39,45 +38,28 @@ namespace SparkleLib {
private TimeSpan short_interval = new TimeSpan (0, 0, 3, 0);
private TimeSpan long_interval = new TimeSpan (0, 0, 10, 0);
private SparkleWatcher watcher;
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 remote_timer = new System.Timers.Timer () { Interval = 10 * 1000 };
private DateTime last_poll = DateTime.Now;
private List<double> sizebuffer = new List<double> ();
private bool has_changed = false;
private Object change_lock = new Object ();
private Object watch_lock = new Object ();
private double progress_percentage = 0.0;
private string progress_speed = "";
private DateTime last_poll = DateTime.Now;
private List<double> size_buffer = new List<double> ();
private Object change_lock = new Object ();
private Object watch_lock = new Object ();
private double progress_percentage = 0.0;
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 event SyncStatusChangedEventHandler SyncStatusChanged;
public delegate void SyncProgressChangedEventHandler (double percentage, string speed);
public event SyncProgressChangedEventHandler SyncProgressChanged;
public delegate void ProgressChangedEventHandler (double percentage, string speed);
public event ProgressChangedEventHandler ProgressChanged;
public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set);
public event NewChangeSetEventHandler NewChangeSet;
@ -92,6 +74,68 @@ namespace SparkleLib {
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)
{
LocalPath = path;
@ -121,7 +165,7 @@ namespace SparkleLib {
if (time_to_poll) {
this.last_poll = DateTime.Now;
if (CheckForRemoteChanges ())
if (HasRemoteChanges)
SyncDownBase ();
}
@ -139,7 +183,7 @@ namespace SparkleLib {
{
// Sync up everything that changed
// since we've been offline
if (AnyDifferences) {
if (HasLocalChanges) {
DisableWatching ();
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 ()
{
HasUnsyncedChanges = true;
HasUnsyncedChanges = true; // ?
if (ConflictResolved != null)
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
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
public void OnFileActivity (FileSystemEventArgs args)
{
@ -378,7 +233,7 @@ namespace SparkleLib {
WatcherChangeTypes wct = args.ChangeType;
if (AnyDifferences) {
if (HasLocalChanges) {
this.is_buffering = true;
// 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 ()
{
try {
@ -459,7 +352,7 @@ namespace SparkleLib {
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision));
this.listener.Announce (new SparkleAnnouncement (Identifier, CurrentRevision));
} else {
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Error");
@ -474,7 +367,7 @@ namespace SparkleLib {
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision));
this.listener.Announce (new SparkleAnnouncement (Identifier, CurrentRevision));
} else {
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) {
this.watcher.EnableRaisingEvents = false;
@ -570,7 +562,7 @@ namespace SparkleLib {
}
public void EnableWatching ()
protected void EnableWatching ()
{
lock (this.watch_lock) {
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
// user has fetched an empty remote folder
public virtual void CreateInitialChangeSet ()
private void CreateInitialChangeSet ()
{
string file_path = Path.Combine (LocalPath, "SparkleShare.txt");
TextWriter writer = new StreamWriter (file_path);
writer.WriteLine ("Congratulations, you've successfully created a SparkleShare repository!");
writer.WriteLine ("");
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.");
// TODO: Url property? ^
writer.WriteLine (Url + " and everyone connected to it.");
writer.WriteLine ("");
writer.WriteLine ("SparkleShare is a Free and Open Source software program that helps people ");
@ -600,66 +614,8 @@ namespace SparkleLib {
writer.WriteLine ("");
writer.Close ();
}
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);
}
}
SyncUp ();
}
@ -671,5 +627,35 @@ namespace SparkleLib {
Byte[] encoded_bytes = sha1.ComputeHash (bytes);
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.IO;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
using MonoMac.WebKit;
namespace SparkleShare {
public class SparkleAbout : NSWindow {
@ -54,9 +53,12 @@ namespace SparkleShare {
BackingType = NSBackingStore.Buffered;
CreateAbout ();
OrderFrontRegardless ();
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this);
OrderFrontRegardless ();
Program.UI.UpdateDockIconVisibility ();
Controller.NewVersionEvent += delegate (string new_version) {

View file

@ -44,8 +44,8 @@ namespace SparkleShare {
string content_path =
Directory.GetParent (System.AppDomain.CurrentDomain.BaseDirectory).ToString ();
string app_path = Directory.GetParent (content_path).ToString ();
string growl_path = Path.Combine (app_path, "Frameworks", "Growl.framework", "Growl");
string app_path = Directory.GetParent (content_path).ToString ();
string growl_path = Path.Combine (app_path, "Frameworks", "Growl.framework", "Growl");
// Needed for Growl
@ -54,7 +54,7 @@ namespace SparkleShare {
// Let's use the bundled git first
SparkleBackend.DefaultBackend.Path =
SparkleGit.Path =
Path.Combine (NSBundle.MainBundle.ResourcePath,
"git", "libexec", "git-core", "git");
@ -68,7 +68,13 @@ namespace SparkleShare {
{
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;
if (path.Contains ("/"))
@ -77,17 +83,20 @@ namespace SparkleShare {
repo_name = path;
// 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))
return;
repo_name = repo_name.Trim ("/".ToCharArray ());
FileSystemEventArgs args = new FileSystemEventArgs (WatcherChangeTypes.Changed,
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, path), Path.GetFileName (path));
FileSystemEventArgs fse_args = new FileSystemEventArgs (
WatcherChangeTypes.Changed,
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, path),
Path.GetFileName (path)
);
foreach (SparkleRepoBase repo in Repositories) {
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 () {
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);
@ -226,15 +227,24 @@ namespace SparkleShare {
html = html.Replace ("<!-- $a-color -->", "#0085cf");
html = html.Replace ("<!-- $a-hover-color -->", "#009ff8");
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 -->",
"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 -->",
"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 -->",
"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 -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath, "Pixmaps", "document-moved-12.png"));
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "document-moved-12.png"));
InvokeOnMainThread (delegate {
if (this.progress_indicator.Superview == ContentView)

View file

@ -14,88 +14,216 @@
// You should have received a copy of the GNU General Public License
// 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.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
using System.Timers;
using MonoMac.AppKit;
using MonoMac.Foundation;
namespace SparkleShare {
public class SparkleMacWatcher {
[Serializable]
public sealed class SparkleMacWatcherEventArgs : EventArgs {
public delegate void ChangedEventHandler (string path);
public event ChangedEventHandler Changed;
public string Path { get; private set; }
private FileSystemInfo last_changed;
private Thread thread;
private int poll_count = 0;
public SparkleMacWatcherEventArgs (string path)
{
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)
{
this.thread = new Thread (new ThreadStart (delegate {
DateTime timestamp;
DirectoryInfo parent = new DirectoryInfo (path);
this.last_changed = new DirectoryInfo (path);
Path = path;
m_callback = DoCallback;
while (true) {
timestamp = this.last_changed.LastWriteTime;
GetLastChange (parent);
NSString [] s = new NSString [1];
s [0] = new NSString (path);
NSArray path_p = NSArray.FromNSObjects (s);
if (DateTime.Compare (this.last_changed.LastWriteTime, timestamp) != 0) {
string relative_path = this.last_changed.FullName.Substring (path.Length + 1);
m_stream = FSEventStreamCreate ( // note that the stream will always be valid
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)
Changed (relative_path);
}
FSEventStreamScheduleWithRunLoop (
m_stream, // streamRef
CFRunLoopGetMain(), // runLoop
kCFRunLoopDefaultMode); // runLoopMode
Thread.Sleep (7500);
this.poll_count++;
}
}));
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...
bool started = FSEventStreamStart (m_stream);
if (!started) {
GC.SuppressFinalize (this);
throw new InvalidOperationException ("Failed to start FSEvent stream for " + path);
}
}
public void Dispose ()
{
this.thread.Join ();
this.thread.Abort ();
Dispose (true);
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>
<AssemblyName>SparkleShare</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ReleaseVersion>0.8.1</ReleaseVersion>
<ReleaseVersion>0.8.2</ReleaseVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -59,7 +59,7 @@
<SpecificVersion>False</SpecificVersion>
</Reference>
<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>
<HintPath>..\..\bin\SparkleLib.dll</HintPath>
</Reference>

View file

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

View file

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

View file

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

View file

@ -23,7 +23,6 @@ using System.Runtime.InteropServices;
using System.Text;
using Mono.Unix;
//using Mono.Unix.Native;
using SparkleLib;
namespace SparkleShare {
@ -88,10 +87,10 @@ namespace SparkleShare {
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 (" ");
Console.WriteLine (_("SparkleShare automatically syncs Git repositories in "));
Console.WriteLine (_("the ~/SparkleShare folder with their remote origins."));
Console.WriteLine (_("SparkleShare is a collaboration and sharing tool that is "));
Console.WriteLine (_("designed to keep things simple and to stay out of your way."));
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 (" ");
Console.WriteLine (_("Arguments:"));
@ -107,24 +106,5 @@ namespace SparkleShare {
Console.WriteLine (_("SparkleShare " + Defines.VERSION));
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 string ProgressSpeed = "";
public event OnQuitWhileSyncingHandler OnQuitWhileSyncing;
public delegate void OnQuitWhileSyncingHandler ();
public event FolderFetchedEventHandler FolderFetched;
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) {
if (OnInvite != null && !FirstRun)
OnInvite (invite);
};
@ -303,8 +299,8 @@ namespace SparkleShare {
if (DateTime.Compare (existing_set.Timestamp, change_set.Timestamp) < 1) {
existing_set.FirstTimestamp = existing_set.Timestamp;
existing_set.Timestamp = change_set.Timestamp;
existing_set.Revision = change_set.Revision;
existing_set.Timestamp = change_set.Timestamp;
existing_set.Revision = change_set.Revision;
} else {
existing_set.FirstTimestamp = change_set.Timestamp;
@ -351,37 +347,40 @@ namespace SparkleShare {
} else {
if (change_set.Edited.Count > 0) {
foreach (string file_path in change_set.Edited) {
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath,
change_set.Folder, file_path}.Combine ();
if (File.Exists (absolute_file_path))
event_entry += "<dd class='document edited'><a href='" + absolute_file_path + "'>" + file_path + "</a></dd>";
else
event_entry += "<dd class='document edited'>" + file_path + "</dd>";
event_entry += "<dd class='document edited'>";
event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
file_path
);
event_entry += "</dd>";
}
}
if (change_set.Added.Count > 0) {
foreach (string file_path in change_set.Added) {
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath,
change_set.Folder, file_path}.Combine ();
if (File.Exists (absolute_file_path))
event_entry += "<dd class='document added'><a href='" + absolute_file_path + "'>" + file_path + "</a></dd>";
else
event_entry += "<dd class='document added'>" + file_path + "</dd>";
event_entry += "<dd class='document added'>";
event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
file_path
);
event_entry += "</dd>";
}
}
if (change_set.Deleted.Count > 0) {
foreach (string file_path in change_set.Deleted) {
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath,
change_set.Folder, file_path}.Combine ();
if (File.Exists (absolute_file_path))
event_entry += "<dd class='document deleted'><a href='" + absolute_file_path + "'>" + file_path + "</a></dd>";
else
event_entry += "<dd class='document deleted'>" + file_path + "</dd>";
event_entry += "<dd class='document deleted'>";
event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
file_path
);
event_entry += "</dd>";
}
}
@ -390,21 +389,19 @@ namespace SparkleShare {
foreach (string file_path in change_set.MovedFrom) {
string to_file_path = change_set.MovedTo [i];
string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath,
change_set.Folder, file_path}.Combine ();
event_entry += "<dd class='document moved'>";
event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
file_path
);
string absolute_to_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath,
change_set.Folder, to_file_path}.Combine ();
event_entry += "<br>";
event_entry += FormatBreadCrumbs (
Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, change_set.Folder),
to_file_path
);
if (File.Exists (absolute_file_path))
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>";
event_entry += "</dd>";
i++;
}
@ -620,7 +617,7 @@ namespace SparkleShare {
}
};
repo.SyncProgressChanged += delegate (double percentage, string speed) {
repo.ProgressChanged += delegate (double percentage, string speed) {
ProgressPercentage = percentage;
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
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
// quits if safe
public void TryQuit ()
@ -1118,9 +1098,6 @@ namespace SparkleShare {
repo.Status == SyncStatus.SyncDown ||
repo.IsBuffering) {
if (OnQuitWhileSyncing != null)
OnQuitWhileSyncing ();
return;
}
}
@ -1166,6 +1143,55 @@ namespace SparkleShare {
int number = 3 + int.Parse (numbers);
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)
{
// 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 ();
XmlNode node;
string host = "";
string path = "";
string token = "";
string host = "", path = "", token = "";
try {
xml_document.LoadXml (invite_xml);
@ -185,7 +214,7 @@ namespace SparkleShare {
if (node != null) { token = node.Value; }
} catch (XmlException e) {
SparkleHelpers.DebugInfo ("Invite", "Not valid XML: " + received_message + " " + e.Message);
SparkleHelpers.DebugInfo ("Invite", "Invalid XML: " + received_message + " " + e.Message);
return;
}

View file

@ -16,56 +16,83 @@
using System;
using System.IO;
using System.Xml;
namespace SparkleShare {
public class SparklePlugin {
public string Name;
public string Description;
public string ImagePath;
public string Backend;
public string Name {
get {
return GetValue ("info", "name");
}
}
public string Address;
public string AddressExample;
public string Path;
public string PathExample;
public string Description {
get {
return GetValue ("info", "description");
}
}
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)
{
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) { Name = node.Value; }
node = xml.SelectSingleNode ("/sparkleshare/plugin/info/description/text()");
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; }
if (node != null)
return node.Value;
else
return null;
}
}
}

View file

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

View file

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

View file

@ -1,9 +1,9 @@
dnl Process this file with autoconf to produce a configure script.
m4_define([sparkleshare_version],
[0.8.1])
[0.8.2])
m4_define([sparkleshare_asm_version],
[0.8.1])
[0.8.2])
AC_PREREQ([2.54])
AC_INIT([SparkleShare], sparkleshare_version)
@ -154,7 +154,6 @@ data/Makefile
data/icons/Makefile
data/html/Makefile
data/plugins/Makefile
help/Makefile
SparkleLib/AssemblyInfo.cs
SparkleLib/Defines.cs
SparkleLib/Makefile
@ -176,7 +175,6 @@ Configuration:
Build Gtk+ UI : ${enable_gtkui}
Nautilus 2.x plugin : ${have_nautilus2_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 {
color: <!-- $a-hover-color -->;
text-decoration: underline;
}
.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.