Replace abstract out Listener by ListenerBase + ListenerIrc

This commit is contained in:
Hylke Bons 2011-05-14 18:10:24 +01:00
parent c76e4f2434
commit 5f36f8a9e0
6 changed files with 286 additions and 187 deletions

View file

@ -11,7 +11,8 @@ SOURCES = \
SparkleFetcher.cs \
SparkleGit.cs \
SparkleHelpers.cs \
SparkleListener.cs \
SparkleListenerBase.cs \
SparkleListenerIrc.cs \
SparkleOptions.cs \
SparklePaths.cs \
SparkleRepo.cs

View file

@ -46,7 +46,8 @@
<Compile Include="SparkleEvents.cs" />
<Compile Include="SparkleOptions.cs" />
<Compile Include="SparkleCommit.cs" />
<Compile Include="SparkleListener.cs" />
<Compile Include="SparkleListenerBase.cs" />
<Compile Include="SparkleListenerIrc.cs" />
<Compile Include="SparkleGit.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
@ -65,4 +66,4 @@
</Properties>
</MonoDevelop>
</ProjectExtensions>
</Project>
</Project>

View file

@ -1,129 +0,0 @@
// SparkleShare, an instant update workflow to Git.
// 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;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using Meebey.SmartIrc4net;
namespace SparkleLib {
public enum NotificationServerType
{
Own,
Central
}
// A persistent connection to the server that
// listens for change notifications
public class SparkleListener {
private Thread thread;
// FIXME: The IrcClient is a public property because
// extending it causes crashes
public IrcClient Client;
public readonly string Server;
public readonly string FallbackServer;
public readonly string Channel;
public readonly string Nick;
public SparkleListener (string server, string folder_name,
string user_email, NotificationServerType type)
{
if (type == NotificationServerType.Own) {
Server = server;
} else {
// 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.
Server = "204.62.14.135";
}
if (!String.IsNullOrEmpty (user_email))
Nick = SHA1 (folder_name + user_email + "sparkles");
else
Nick = SHA1 (DateTime.Now.ToString () + "sparkles");
Nick = "s" + Nick.Substring (0, 7);
Channel = "#" + SHA1 (server + folder_name + "sparkles");
Client = new IrcClient () {
PingTimeout = 180,
PingInterval = 90
};
}
// Starts a new thread and listens to the channel
public void Listen ()
{
this.thread = new Thread (
new ThreadStart (delegate {
try {
// Connect, login, and join the channel
Client.Connect (new string [] {Server}, 6667);
Client.Login (Nick, Nick);
Client.RfcJoin (Channel);
// List to the channel, this blocks the thread
Client.Listen ();
Client.Disconnect ();
} catch (Meebey.SmartIrc4net.ConnectionException e) {
Console.WriteLine ("Could not connect: " + e.Message);
}
})
);
this.thread.Start ();
}
public void Announce (string message)
{
Client.SendMessage (SendType.Message, Channel, message);
}
// Frees all resources for this Listener
public void Dispose ()
{
this.thread.Abort ();
this.thread.Join ();
}
// Creates an SHA-1 hash of input
private string SHA1 (string s)
{
SHA1 sha1 = new SHA1CryptoServiceProvider ();
Byte[] bytes = ASCIIEncoding.Default.GetBytes (s);
Byte[] encoded_bytes = sha1.ComputeHash (bytes);
return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", "");
}
}
}

View file

@ -0,0 +1,99 @@
// SparkleShare, an instant update workflow to Git.
// 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;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using Meebey.SmartIrc4net;
namespace SparkleLib {
public enum NotificationServerType
{
Own,
Central
}
// A persistent connection to the server that
// listens for change notifications
public abstract class SparkleListenerBase {
// We've connected to the server
public event ConnectedEventHandler Connected;
public delegate void ConnectedEventHandler ();
// We've disconnected from the server
public event DisconnectedEventHandler Disconnected;
public delegate void DisconnectedEventHandler ();
// We've been notified about a remote
// change by the channel
public event RemoteChangeEventHandler RemoteChange;
public delegate void RemoteChangeEventHandler (string change_id);
// Starts listening for remote changes
public abstract void Connect ();
// Announces to the channel that
// we've pushed changes to the server
public abstract void Announce (string message);
// Release all resources
public abstract void Dispose ();
public abstract bool IsConnected { get; }
public string Server;
public string Channel;
// Announcements that weren't sent off
// because we were disconnected
public List<string> AnnounceQueue = new List<string> ();
// Announcements of remote changes that we've received
public int ChangesQueue = 0;
public SparkleListenerBase (string server, string folder_identifier, NotificationServerType type) { }
public void DecrementChangesQueue ()
{
ChangesQueue--;
}
public void OnConnected ()
{
if (Connected != null)
Connected ();
}
public void OnDisconnected ()
{
if (Disconnected != null)
Disconnected ();
}
public void OnRemoteChange (string change_id)
{
if (RemoteChange != null)
RemoteChange (change_id);
}
}
}

View file

@ -0,0 +1,161 @@
// SparkleShare, an instant update workflow to Git.
// 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;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using Meebey.SmartIrc4net;
namespace SparkleLib {
public class SparkleListenerIrc : SparkleListenerBase {
private Thread thread;
private IrcClient client;
private string nick;
public SparkleListenerIrc (string server, string folder_identifier,
NotificationServerType type) : base (server, folder_identifier, type)
{
if (type == NotificationServerType.Own) {
Server = server;
} else {
// 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
Server = "204.62.14.135";
}
// Try to get a uniqueish nickname
this.nick = SHA1 (DateTime.Now.ToString ("ffffff") + "sparkles");
// Most irc servers don't allow nicknames starting
// with a number, so prefix an alphabetic character
this.nick = "s" + this.nick.Substring (0, 7);
// Hash and salt the folder identifier, so
// nobody knows any possible folder details
Channel = "#" + SHA1 (folder_identifier + "sparkles");
this.client = new IrcClient () {
PingTimeout = 180,
PingInterval = 90
};
this.client.OnConnected += delegate {
SparkleHelpers.DebugInfo ("ListenerIrc", "Connected to " + Channel + " on " + Server);
OnConnected ();
if (AnnounceQueue.Count > 0) {
string message = AnnounceQueue [AnnounceQueue.Count - 1];
AnnounceQueue = new List<string> ();
SparkleHelpers.DebugInfo ("ListenerIrc", "Delivering queued messages...");
Announce (message);
}
};
this.client.OnDisconnected += delegate {
SparkleHelpers.DebugInfo ("ListenerIrc", "Disconnected from " + Channel + " on " + Server);
OnDisconnected ();
};
this.client.OnChannelMessage += delegate (object o, IrcEventArgs args) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Got message from " + Channel + " on " + Server);
string message = args.Data.Message.Trim ();
ChangesQueue++;
OnRemoteChange (message);
};
}
// Starts a new thread and listens to the channel
public override void Connect ()
{
SparkleHelpers.DebugInfo ("ListenerIrc", "Connecting to " + Channel + " on " + Server);
this.thread = new Thread (
new ThreadStart (delegate {
try {
// Connect, login, and join the channel
this.client.Connect (new string [] {Server}, 6667);
this.client.Login (this.nick, this.nick);
this.client.RfcJoin (Channel);
// List to the channel, this blocks the thread
this.client.Listen ();
// Disconnect when we time out
this.client.Disconnect ();
} catch (Meebey.SmartIrc4net.ConnectionException e) {
Console.WriteLine ("Could not connect: " + e.Message);
}
})
);
this.thread.Start ();
}
public override void Announce (string message)
{
if (IsConnected) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Announcing to " + Channel + " on " + Server);
this.client.SendMessage (SendType.Message, Channel, message);
} else {
SparkleHelpers.DebugInfo ("ListenerIrc", "Not connected. Queuing message");
AnnounceQueue.Add (message);
}
}
public override bool IsConnected {
get {
return this.client.IsConnected;
}
}
public override void Dispose ()
{
this.thread.Abort ();
this.thread.Join ();
}
// Creates a SHA-1 hash of input
private string SHA1 (string s)
{
SHA1 sha1 = new SHA1CryptoServiceProvider ();
Byte[] bytes = ASCIIEncoding.Default.GetBytes (s);
Byte[] encoded_bytes = sha1.ComputeHash (bytes);
return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", "");
}
}
}

View file

@ -33,9 +33,7 @@ namespace SparkleLib {
private Timer local_timer;
private FileSystemWatcher watcher;
private System.Object change_lock;
private int fetch_queue;
private int announce_queue;
private SparkleListener listener;
private SparkleListenerBase listener;
private List <double> sizebuffer;
private bool has_changed;
@ -153,8 +151,6 @@ namespace SparkleLib {
this.server_online = true;
this.has_changed = false;
this.change_lock = new Object ();
this.fetch_queue = 0;
this.announce_queue = 0;
if (IsEmpty)
this.current_hash = null;
@ -184,11 +180,13 @@ namespace SparkleLib {
this.watcher.Deleted += new FileSystemEventHandler (OnFileActivity);
this.watcher.Renamed += new RenamedEventHandler (OnFileActivity);
// Listen to the irc channel on the server...
NotificationServerType server_type;
if (UsesNotificationCenter)
this.listener = new SparkleListener (Domain, RemoteName, UserEmail, NotificationServerType.Central);
server_type = NotificationServerType.Central;
else
this.listener = new SparkleListener (Domain, RemoteName, UserEmail, NotificationServerType.Own);
server_type = NotificationServerType.Own;
this.listener = new SparkleListenerIrc (Domain, RemoteName, server_type);
// ...fetch remote changes every 60 seconds if that fails
this.remote_timer = new Timer () {
@ -199,10 +197,8 @@ namespace SparkleLib {
if (this.is_polling) {
CheckForRemoteChanges ();
if (!this.listener.Client.IsConnected) {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Trying to reconnect...");
this.listener.Listen ();
}
if (!this.listener.IsConnected)
this.listener.Connect ();
}
if (this.has_unsynced_changes)
@ -210,7 +206,7 @@ namespace SparkleLib {
};
// Stop polling when the connection to the irc channel is succesful
this.listener.Client.OnConnected += delegate {
this.listener.Connected += delegate {
this.is_polling = false;
// Check for changes manually one more time
@ -219,59 +215,34 @@ namespace SparkleLib {
// Push changes that were made since the last disconnect
if (this.has_unsynced_changes)
Push ();
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Connected. Now listening... (" + this.listener.Server + ")");
if (this.announce_queue > 0) {
this.listener.Announce (this.current_hash);
this.announce_queue = 0;
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Queued messages delivered. (" + this.listener.Server + ")");
}
};
// Start polling when the connection to the irc channel is lost
this.listener.Client.OnConnectionError += delegate {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Lost connection. Falling back to polling...");
this.is_polling = true;
};
// Start polling when the connection to the irc channel is lost
this.listener.Client.OnDisconnected += delegate {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Lost connection. Falling back to polling...");
this.listener.Disconnected += delegate {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Falling back to polling");
this.is_polling = true;
};
// Fetch changes when there is a message in the irc channel
this.listener.Client.OnChannelMessage += delegate (object o, IrcEventArgs args) {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Was notified of a remote change...");
string message = args.Data.Message.Trim ();
if (!message.Equals (this.current_hash) && message.Length == 40) {
this.fetch_queue++;
if (this.is_buffering) {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] ...but we're busy adding files. We'll fetch them later.");
} else if (!this.is_fetching) {
while (this.fetch_queue > 0) {
this.listener.RemoteChange += delegate (string change_id) {
if (!change_id.Equals (this.current_hash) && change_id.Length == 40) {
if (!this.is_fetching && !this.is_buffering) {
while (this.listener.ChangesQueue > 0) {
Fetch ();
this.fetch_queue--;
this.listener.DecrementChangesQueue ();
}
this.watcher.EnableRaisingEvents = false;
Rebase ();
this.watcher.EnableRaisingEvents = true;
}
} else {
// Not really needed as we won't be notified about our own messages
SparkleHelpers.DebugInfo ("Irc",
"[" + Name + "] False alarm, already up to date. (" + this.current_hash + ")");
}
};
// Start listening
this.listener.Listen ();
this.listener.Connect ();
this.sizebuffer = new List <double> ();
this.sizebuffer = new List <double> ();
// Keep a timer that checks if there are changes and
// whether they have settled
@ -304,7 +275,7 @@ namespace SparkleLib {
if (git.ExitCode != 0)
return;
string remote_hash = git.StandardOutput.ReadToEnd ();
string remote_hash = git.StandardOutput.ReadToEnd ().TrimEnd ();
if (!remote_hash.StartsWith (this.current_hash)) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found. (" + remote_hash + ")");
@ -760,12 +731,7 @@ namespace SparkleLib {
if (PushingFinished != null)
PushingFinished (this, args);
if (this.listener.Client.IsConnected) {
this.listener.Announce (this.current_hash);
} else {
this.announce_queue++;
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Could not deliver notification, added it to the queue");
}
this.listener.Announce (this.current_hash);
}
};