From 5f36f8a9e081b42a2abf85654d70e7ae05408951 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sat, 14 May 2011 18:10:24 +0100 Subject: [PATCH] Replace abstract out Listener by ListenerBase + ListenerIrc --- SparkleLib/Makefile.am | 3 +- SparkleLib/SparkleLib.csproj | 5 +- SparkleLib/SparkleListener.cs | 129 ------------------------ SparkleLib/SparkleListenerBase.cs | 99 ++++++++++++++++++ SparkleLib/SparkleListenerIrc.cs | 161 ++++++++++++++++++++++++++++++ SparkleLib/SparkleRepo.cs | 76 ++++---------- 6 files changed, 286 insertions(+), 187 deletions(-) delete mode 100644 SparkleLib/SparkleListener.cs create mode 100644 SparkleLib/SparkleListenerBase.cs create mode 100644 SparkleLib/SparkleListenerIrc.cs diff --git a/SparkleLib/Makefile.am b/SparkleLib/Makefile.am index bfe7c9ee..e0886078 100644 --- a/SparkleLib/Makefile.am +++ b/SparkleLib/Makefile.am @@ -11,7 +11,8 @@ SOURCES = \ SparkleFetcher.cs \ SparkleGit.cs \ SparkleHelpers.cs \ - SparkleListener.cs \ + SparkleListenerBase.cs \ + SparkleListenerIrc.cs \ SparkleOptions.cs \ SparklePaths.cs \ SparkleRepo.cs diff --git a/SparkleLib/SparkleLib.csproj b/SparkleLib/SparkleLib.csproj index 76dfe1fb..1f437b48 100644 --- a/SparkleLib/SparkleLib.csproj +++ b/SparkleLib/SparkleLib.csproj @@ -46,7 +46,8 @@ - + + @@ -65,4 +66,4 @@ - \ No newline at end of file + diff --git a/SparkleLib/SparkleListener.cs b/SparkleLib/SparkleListener.cs deleted file mode 100644 index 9d740816..00000000 --- a/SparkleLib/SparkleListener.cs +++ /dev/null @@ -1,129 +0,0 @@ -// SparkleShare, an instant update workflow to Git. -// Copyright (C) 2010 Hylke Bons -// -// 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 . - - -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 ("-", ""); - } - - } - -} diff --git a/SparkleLib/SparkleListenerBase.cs b/SparkleLib/SparkleListenerBase.cs new file mode 100644 index 00000000..8fbe1d9d --- /dev/null +++ b/SparkleLib/SparkleListenerBase.cs @@ -0,0 +1,99 @@ +// SparkleShare, an instant update workflow to Git. +// Copyright (C) 2010 Hylke Bons +// +// 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 . + + +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 AnnounceQueue = new List (); + + // 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); + } + } +} + diff --git a/SparkleLib/SparkleListenerIrc.cs b/SparkleLib/SparkleListenerIrc.cs new file mode 100644 index 00000000..ee9880e1 --- /dev/null +++ b/SparkleLib/SparkleListenerIrc.cs @@ -0,0 +1,161 @@ +// SparkleShare, an instant update workflow to Git. +// Copyright (C) 2010 Hylke Bons +// +// 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 . + + +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 (); + + 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 ("-", ""); + } + + } + +} + diff --git a/SparkleLib/SparkleRepo.cs b/SparkleLib/SparkleRepo.cs index e1b8d5e7..bbb819bd 100644 --- a/SparkleLib/SparkleRepo.cs +++ b/SparkleLib/SparkleRepo.cs @@ -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 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 (); + this.sizebuffer = new List (); // 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); } };