From 359ec616f9959eb80580f22d015d7fc1507a0e5f Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sun, 22 May 2011 01:02:16 +0100 Subject: [PATCH] listener: use one connection with multiple channels per server --- SparkleLib/SparkleFetcherMercurial.cs | 11 +++ SparkleLib/SparkleListenerBase.cs | 127 +++++++++++++++++++------- SparkleLib/SparkleListenerIrc.cs | 59 ++++++------ SparkleLib/SparkleRepoBase.cs | 17 ++-- SparkleLib/SparkleRepoGit.cs | 12 +-- SparkleLib/SparkleRepoMercurial.cs | 94 +++++++++++++++++-- SparkleShare/SparkleController.cs | 5 - 7 files changed, 236 insertions(+), 89 deletions(-) diff --git a/SparkleLib/SparkleFetcherMercurial.cs b/SparkleLib/SparkleFetcherMercurial.cs index 08057ecd..9e926ccc 100644 --- a/SparkleLib/SparkleFetcherMercurial.cs +++ b/SparkleLib/SparkleFetcherMercurial.cs @@ -85,6 +85,17 @@ namespace SparkleLib { writer.WriteLine (config); writer.Close (); + string style_file_path = SparkleHelpers.CombineMore (base.target_folder, ".hg", "log.style"); + + string style = "changeset = \"{file_mods}{file_adds}{file_dels}\"" + n + + "file_add = \"A {file_add}\\n\"" + n + + "file_mod = \"M {file_mod}\\n\"" + n + + "file_del = \"D {file_del}\\n\"" + n; + + writer = new StreamWriter (style_file_path); + writer.WriteLine (style); + writer.Close (); + SparkleHelpers.DebugInfo ("Config", "Added configuration to '" + repo_config_file_path + "'"); } diff --git a/SparkleLib/SparkleListenerBase.cs b/SparkleLib/SparkleListenerBase.cs index ebec807c..c46b8a6f 100644 --- a/SparkleLib/SparkleListenerBase.cs +++ b/SparkleLib/SparkleListenerBase.cs @@ -27,6 +27,52 @@ 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 listeners; + + public static SparkleListenerIrc CreateIrcListener (string server, string folder_identifier, + NotificationServerType type) + { + if (listeners == null) + listeners = new List (); + + // 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 + if (type == NotificationServerType.Central) + server = "204.62.14.135"; + + foreach (SparkleListenerBase listener in listeners) { + if (listener.Server.Equals (server)) { + SparkleHelpers.DebugInfo ("ListenerFactory", "Refered to existing listener for " + server); + listener.AlsoListenTo (folder_identifier); + return (SparkleListenerIrc) listener; + } + } + + SparkleHelpers.DebugInfo ("ListenerFactory", "Issued new listener for " + server); + listeners.Add (new SparkleListenerIrc (server, folder_identifier, type)); + return (SparkleListenerIrc) listeners [listeners.Count - 1]; + } + } + + // A persistent connection to the server that // listens for change notifications public abstract class SparkleListenerBase { @@ -42,33 +88,34 @@ namespace SparkleLib { // We've been notified about a remote // change by the channel public event RemoteChangeEventHandler RemoteChange; - public delegate void RemoteChangeEventHandler (string change_id); + public delegate void RemoteChangeEventHandler (SparkleAnnouncement announcement); + - // Starts listening for remote changes public abstract void Connect (); - - private void AnnounceBase (string message) { - if (IsConnected) { - SparkleHelpers.DebugInfo ("Listener", "Announcing to " + this.channel + " on " + this.server); - Announce (message); - } else { - SparkleHelpers.DebugInfo ("Listener", "Not connected. Queuing message"); - this.announce_queue.Add (message); - } - } - - // Announces to the channel that - // we've pushed changes to the server - public abstract void Announce (string message); - - // Release all resources + public abstract void Announce (SparkleAnnouncement announcent); + public abstract void AlsoListenTo (string folder_identifier); public abstract void Dispose (); - public abstract bool IsConnected { get; } + // Announcements that weren't sent off // because we were disconnected - protected List announce_queue = new List (); + protected List announce_queue = new List (); + protected string server; + protected List channels = new List (); + protected int changes_queue = 0; + protected bool is_connecting; + + + public SparkleListenerBase (string server, string folder_identifier, NotificationServerType type) { } + + + public string Server { + get { + return this.server; + } + } + // Announcements of remote changes that we've received public int ChangesQueue { @@ -77,33 +124,48 @@ namespace SparkleLib { } } - protected string server; - protected string channel; - protected int changes_queue = 0; - public SparkleListenerBase (string server, string folder_identifier, NotificationServerType type) { } + public bool IsConnecting { + get { + return this.is_connecting; + } + } + + + public void AnnounceBase (SparkleAnnouncement announcement) { + if (IsConnected) { + SparkleHelpers.DebugInfo ("Listener", "Announcing to " + announcement.FolderIdentifier + " on " + this.server); + Announce (announcement); + } else { + SparkleHelpers.DebugInfo ("Listener", "Not connected to " + this.server + ". Queuing message"); + this.announce_queue.Add (announcement); + } + } + public void DecrementChangesQueue () { this.changes_queue--; } + public void OnConnected () { - SparkleHelpers.DebugInfo ("Listener", "Connected"); + SparkleHelpers.DebugInfo ("Listener", "Connected to " + Server); if (Connected != null) Connected (); if (this.announce_queue.Count > 0) { - string message = this.announce_queue [this.announce_queue.Count - 1]; - this.announce_queue = new List (); - SparkleHelpers.DebugInfo ("Listener", "Delivering queued messages..."); - AnnounceBase (message); + foreach (SparkleAnnouncement announcement in this.announce_queue) + AnnounceBase (announcement); + + this.announce_queue = new List (); } } + public void OnDisconnected () { SparkleHelpers.DebugInfo ("Listener", "Disonnected"); @@ -112,14 +174,15 @@ namespace SparkleLib { Disconnected (); } - public void OnRemoteChange (string change_id) + + public void OnRemoteChange (SparkleAnnouncement announcement) { - SparkleHelpers.DebugInfo ("Listener", "Got message from " + this.channel + " on " + this.server); + SparkleHelpers.DebugInfo ("Listener", "Got message from " + announcement.FolderIdentifier + " on " + this.server); this.changes_queue++; if (RemoteChange != null) - RemoteChange (change_id); + RemoteChange (announcement); } } } diff --git a/SparkleLib/SparkleListenerIrc.cs b/SparkleLib/SparkleListenerIrc.cs index 1a689a72..6d8d944c 100644 --- a/SparkleLib/SparkleListenerIrc.cs +++ b/SparkleLib/SparkleListenerIrc.cs @@ -31,19 +31,10 @@ namespace SparkleLib { private string nick; - public SparkleListenerIrc (string server, string folder_identifier, - NotificationServerType type) : base (server, folder_identifier, type) + public SparkleListenerIrc (string server, string folder_identifier, NotificationServerType type) : + base (server, folder_identifier, type) { - if (type == NotificationServerType.Own) { - base.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 - base.server = "204.62.14.135"; - } + base.server = server; // Try to get a uniqueish nickname this.nick = SHA1 (DateTime.Now.ToString ("ffffff") + "sparkles"); @@ -52,9 +43,7 @@ namespace SparkleLib { // 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 - base.channel = "#" + SHA1 (folder_identifier + "sparkles"); + base.channels.Add ("#" + folder_identifier); this.client = new IrcClient () { PingTimeout = 180, @@ -62,6 +51,7 @@ namespace SparkleLib { }; this.client.OnConnected += delegate { + base.is_connecting = false; OnConnected (); }; @@ -71,16 +61,26 @@ namespace SparkleLib { this.client.OnChannelMessage += delegate (object o, IrcEventArgs args) { string message = args.Data.Message.Trim (); - OnRemoteChange (message); + string folder_id = args.Data.Channel.Substring (1); // remove the # + OnRemoteChange (new SparkleAnnouncement (folder_id, message)); }; } + public override bool IsConnected { + get { + return this.client.IsConnected; + } + } + + // Starts a new thread and listens to the channel public override void Connect () { - SparkleHelpers.DebugInfo ("ListenerIrc", "Connecting to " + base.channel + " on " + base.server); - + SparkleHelpers.DebugInfo ("ListenerIrc", "Connecting to " + Server); + + base.is_connecting = true; + this.thread = new Thread ( new ThreadStart (delegate { try { @@ -88,7 +88,9 @@ namespace SparkleLib { // Connect, login, and join the channel this.client.Connect (new string [] {base.server}, 6667); this.client.Login (this.nick, this.nick); - this.client.RfcJoin (base.channel); + + foreach (string channel in base.channels) + this.client.RfcJoin (channel); // List to the channel, this blocks the thread this.client.Listen (); @@ -96,7 +98,7 @@ namespace SparkleLib { // Disconnect when we time out this.client.Disconnect (); } catch (Meebey.SmartIrc4net.ConnectionException e) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Could not connect to " + base.channel + " on " + base.server + ": " + e.Message); + SparkleHelpers.DebugInfo ("ListenerIrc", "Could not connect to " + Server + ": " + e.Message); } }) ); @@ -105,16 +107,21 @@ namespace SparkleLib { } - public override void Announce (string message) + public override void AlsoListenTo (string folder_identifier) { - this.client.SendMessage (SendType.Message, base.channel, message); + string channel = "#" + folder_identifier; + base.channels.Add (channel); + this.client.RfcJoin (channel); } - public override bool IsConnected { - get { - return this.client.IsConnected; - } + public override void Announce (SparkleAnnouncement announcement) + { + string channel = "#" + announcement.FolderIdentifier; + this.client.SendMessage (SendType.Message, channel, announcement.Message); + + // Also announce to ourselves for debugging purposes + OnRemoteChange (announcement); } diff --git a/SparkleLib/SparkleRepoBase.cs b/SparkleLib/SparkleRepoBase.cs index 7180ba54..35b5b325 100644 --- a/SparkleLib/SparkleRepoBase.cs +++ b/SparkleLib/SparkleRepoBase.cs @@ -132,7 +132,7 @@ namespace SparkleLib { SyncDownBase (); } - if (this.is_polling && !this.listener.IsConnected) + if (this.is_polling && !this.listener.IsConnected && !this.listener.IsConnecting) this.listener.Connect (); if (HasUnsyncedChanges) @@ -242,7 +242,7 @@ namespace SparkleLib { server_type = NotificationServerType.Central; else server_type = NotificationServerType.Own; - this.listener = new SparkleListenerIrc (Domain, Identifier, server_type);//////////// + this.listener = SparkleListenerFactory.CreateIrcListener (Domain, Identifier, server_type); // Stop polling when the connection to the irc channel is succesful this.listener.Connected += delegate { @@ -263,9 +263,11 @@ namespace SparkleLib { }; // Fetch changes when there is a message in the irc channel - this.listener.RemoteChange += delegate (string change_id) { - if (!change_id.Equals (CurrentRevision) && change_id.Length == 40) { - if ((Status != SyncStatus.SyncUp) && (Status != SyncStatus.SyncDown) && + this.listener.RemoteChange += delegate (SparkleAnnouncement announcement) { + if (announcement.FolderIdentifier == Identifier && + !announcement.Message.Equals (CurrentRevision)) { + if ((Status != SyncStatus.SyncUp) && + (Status != SyncStatus.SyncDown) && !this.is_buffering) { while (this.listener.ChangesQueue > 0) { @@ -277,7 +279,8 @@ namespace SparkleLib { }; // Start listening - this.listener.Connect (); + if (!this.listener.IsConnected && !this.listener.IsConnecting) + this.listener.Connect (); } @@ -359,7 +362,7 @@ namespace SparkleLib { if (SyncStatusChanged != null) SyncStatusChanged (SyncStatus.Idle); - this.listener.Announce (CurrentRevision); + this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision)); } else { SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Error"); diff --git a/SparkleLib/SparkleRepoGit.cs b/SparkleLib/SparkleRepoGit.cs index 14e27eae..651771dd 100644 --- a/SparkleLib/SparkleRepoGit.cs +++ b/SparkleLib/SparkleRepoGit.cs @@ -134,7 +134,7 @@ namespace SparkleLib { git.Start (); git.WaitForExit (); - if (git.ExitCode == 0) { + if (git.ExitCode == 0) {Console.WriteLine ("REBASING"); Rebase (); return true; } else { @@ -187,11 +187,6 @@ namespace SparkleLib { } - - - - - // Stages the made changes private void Add () { @@ -232,9 +227,6 @@ namespace SparkleLib { } - - - // Merges the fetched changes private void Rebase () { @@ -271,8 +263,6 @@ namespace SparkleLib { } - - private void ResolveConflict () { // This is al list of conflict status codes that Git uses, their diff --git a/SparkleLib/SparkleRepoMercurial.cs b/SparkleLib/SparkleRepoMercurial.cs index d682eb59..51c5f56c 100644 --- a/SparkleLib/SparkleRepoMercurial.cs +++ b/SparkleLib/SparkleRepoMercurial.cs @@ -202,22 +202,100 @@ namespace SparkleLib { // TODO: Method needs to be made a lot faster public override List GetChangeSets (int count) { + if (count < 1) + count = 30; + + List change_sets = new List (); + + string style_file_path = SparkleHelpers.CombineMore (LocalPath, ".hg", "log.style"); + SparkleHg hg_log = new SparkleHg (LocalPath, "log --limit " + count + " --style " + style_file_path); + Console.OutputEncoding = System.Text.Encoding.Unicode; + hg_log.Start (); + + // Reading the standard output HAS to go before + // WaitForExit, or it will hang forever on output > 4096 bytes + string output = hg_log.StandardOutput.ReadToEnd (); + hg_log.WaitForExit (); + + string [] lines = output.Split ("\n".ToCharArray ()); + List entries = new List (); + + int j = 0; + string entry = "", last_entry = ""; + foreach (string line in lines) { + if (line.StartsWith ("changeset:") && j > 0) { + entries.Add (entry); + entry = ""; + } + + entry += line + "\n"; + j++; + + last_entry = entry; + } + + entries.Add (last_entry); + + Regex regex = new Regex (@"changeset: ([a-z0-9]{40})\n" + + "(.+) <(.+)>\n" + + "([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}) .([0-9]{4})\n" + + "", RegexOptions.Compiled); + + // TODO: Need to optimise for speed + foreach (string log_entry in entries) { + + bool is_merge_commit = false; + + Match match = regex.Match (log_entry); + + if (match.Success) { SparkleChangeSet change_set = new SparkleChangeSet (); - change_set.Revision = "test"; - change_set.UserName = "test"; - change_set.UserEmail = "test"; - change_set.IsMerge = false; + change_set.Revision = match.Groups [1].Value; + change_set.UserName = match.Groups [2].Value; + change_set.UserEmail = match.Groups [3].Value; + change_set.IsMerge = is_merge_commit; + + change_set.Timestamp = new DateTime (int.Parse (match.Groups [4].Value), + int.Parse (match.Groups [5].Value), int.Parse (match.Groups [6].Value), + int.Parse (match.Groups [7].Value), int.Parse (match.Groups [8].Value), 0); + + string [] entry_lines = log_entry.Split ("\n".ToCharArray ()); + + foreach (string entry_line in entry_lines) { + if (entry_line.StartsWith (":")) { + + string change_type = entry_line [37].ToString (); + string file_path = entry_line.Substring (39); + string to_file_path; + + if (change_type.Equals ("A")) { + change_set.Added.Add (file_path); + } else if (change_type.Equals ("M")) { + change_set.Edited.Add (file_path); + } else if (change_type.Equals ("D")) { + change_set.Deleted.Add (file_path); + } else if (change_type.Equals ("R")) { + int tab_pos = entry_line.LastIndexOf ("\t"); + file_path = entry_line.Substring (42, tab_pos - 42); + to_file_path = entry_line.Substring (tab_pos + 1); + + change_set.MovedFrom.Add (file_path); + change_set.MovedTo.Add (to_file_path); + } + } + } + + change_sets.Add (change_set); + } + } - change_set.Timestamp = DateTime.Now; - List change_sets = new List (); - change_sets.Add (change_set); return change_sets; } // Creates a pretty commit message based on what has changed - private string FormatCommitMessage () + private string FormatCommitMessage () // TODO { return "SparkleShare Hg"; } diff --git a/SparkleShare/SparkleController.cs b/SparkleShare/SparkleController.cs index 60b7a98a..d25cd2f9 100644 --- a/SparkleShare/SparkleController.cs +++ b/SparkleShare/SparkleController.cs @@ -514,15 +514,10 @@ namespace SparkleShare { if (folder_path.Equals (SparklePaths.SparkleTmpPath)) return; - Console.WriteLine (folder_path); - SparkleRepoBase repo = null; if (Directory.Exists (Path.Combine (folder_path, ".git"))) { - Console.WriteLine (folder_path + " == Git"); repo = new SparkleRepoGit (folder_path, SparkleBackend.DefaultBackend); } else if (Directory.Exists (Path.Combine (folder_path, ".hg"))) { - - Console.WriteLine (folder_path + " == Hg"); SparkleBackend hg_backend = new SparkleBackend ("Hg", new string [] {"/opt/local/bin/hg"}); repo = new SparkleRepoMercurial (folder_path, hg_backend); }