/SparkleLib/SparkleListenerTcp.cs
C# | 259 lines | 163 code | 66 blank | 30 comment | 21 complexity | 11772c06b6ceefae4bf0a4bef6699ebd MD5 | raw file
1// SparkleShare, a collaboration and sharing tool. 2// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com> 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Lesser General Public License as 6// published by the Free Software Foundation, either version 3 of the 7// License, or (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU General Public License for more details. 13// 14// You should have received a copy of the GNU General Public License 15// along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 18using System; 19using System.Net.Sockets; 20using System.Text; 21using System.Threading; 22 23namespace SparkleLib { 24 25 public class SparkleListenerTcp : SparkleListenerBase { 26 27 private Socket socket; 28 private Thread thread; 29 private bool is_connected = false; 30 private bool is_connecting = false; 31 private DateTime last_ping = DateTime.Now; 32 33 34 public SparkleListenerTcp (Uri server, string folder_identifier) : base (server, folder_identifier) 35 { 36 } 37 38 39 public override bool IsConnected { 40 get { 41 return this.is_connected; 42 } 43 } 44 45 46 public override bool IsConnecting { 47 get { 48 return this.is_connecting; 49 } 50 } 51 52 53 // Starts a new thread and listens to the channel 54 public override void Connect () 55 { 56 this.is_connecting = true; 57 58 this.thread = new Thread (() => { 59 int port = Server.Port; 60 61 if (port < 0) 62 port = 443; 63 64 try { 65 this.socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { 66 ReceiveTimeout = 5 * 1000, 67 SendTimeout = 5 * 1000 68 }; 69 70 // Try to connect to the server 71 this.socket.Connect (Server.Host, port); 72 73 this.is_connecting = false; 74 this.is_connected = true; 75 76 OnConnected (); 77 78 } catch (Exception e) { 79 this.is_connected = false; 80 this.is_connecting = false; 81 82 if (this.socket != null) 83 this.socket.Close (); 84 85 OnDisconnected (SparkleLib.DisconnectReason.TimeOut, e.Message); 86 return; 87 } 88 89 90 byte [] bytes = new byte [4096]; 91 int bytes_read = 0; 92 this.last_ping = DateTime.Now; 93 94 // Wait for messages 95 while (this.is_connected) { 96 try { 97 int i = 0; 98 int timeout = 300; 99 DisconnectReason reason = DisconnectReason.TimeOut; 100 101 // This blocks the thread 102 while (this.socket.Available < 1) { 103 try { 104 // We've timed out, let's ping the server to 105 // see if the connection is still up 106 if (i == timeout) { 107 SparkleLogger.LogInfo ("ListenerTcp", "Pinging " + Server); 108 109 byte [] ping_bytes = Encoding.UTF8.GetBytes ("ping\n"); 110 byte [] pong_bytes = new byte [4096]; 111 112 this.socket.Send (ping_bytes); 113 114 if (this.socket.Receive (pong_bytes) < 1) 115 // 10057 means "Socket is not connected" 116 throw new SocketException (10057); 117 118 SparkleLogger.LogInfo ("ListenerTcp", "Received pong from " + Server); 119 120 i = 0; 121 this.last_ping = DateTime.Now; 122 123 } else { 124 125 // Check when the last ping occured. If it's 126 // significantly longer than our regular interval the 127 // system likely woke up from sleep and we want to 128 // simulate a disconnect 129 int sleepiness = DateTime.Compare ( 130 this.last_ping.AddMilliseconds (timeout * 1000 * 1.2), 131 DateTime.Now 132 ); 133 134 if (sleepiness <= 0) { 135 SparkleLogger.LogInfo ("ListenerTcp", "System woke up from sleep"); 136 reason = DisconnectReason.SystemSleep; 137 138 // 10057 means "Socket is not connected" 139 throw new SocketException (10057); 140 } 141 } 142 143 // The ping failed: disconnect completely 144 } catch (SocketException e) { 145 Disconnect(reason, "Ping timeout: " + e.Message); 146 return; 147 } 148 149 Thread.Sleep (1000); 150 i++; 151 } 152 153 } catch (Exception) { 154 return; 155 } 156 157 try { 158 if (this.socket.Available > 0) 159 bytes_read = this.socket.Receive (bytes); 160 161 // Parse the received message 162 if (bytes_read > 0) { 163 string received = Encoding.UTF8.GetString (bytes); 164 string line = received.Substring (0, received.IndexOf ("\n")); 165 166 if (!line.Contains ("!")) 167 continue; 168 169 string folder_identifier = line.Substring (0, line.IndexOf ("!")); 170 string message = CleanMessage (line.Substring (line.IndexOf ("!") + 1)); 171 172 // We have a message! 173 if (!folder_identifier.Equals ("debug") && !string.IsNullOrEmpty (message)) 174 OnAnnouncement (new SparkleAnnouncement (folder_identifier, message)); 175 } 176 177 } catch (SocketException e) { 178 Disconnect (DisconnectReason.TimeOut, "Timeout during receiving: " + e.Message); 179 return; 180 } 181 } 182 }); 183 184 this.thread.Start (); 185 } 186 187 188 private void Disconnect (DisconnectReason reason, string message) 189 { 190 this.is_connected = false; 191 this.is_connecting = false; 192 193 if (this.socket != null) { 194 this.socket.Close (); 195 this.socket = null; 196 } 197 198 OnDisconnected (reason, message); 199 } 200 201 202 protected override void AlsoListenToInternal (string folder_identifier) 203 { 204 string to_send = "subscribe " + folder_identifier + "\n"; 205 206 try { 207 this.socket.Send (Encoding.UTF8.GetBytes (to_send)); 208 this.last_ping = DateTime.Now; 209 210 } catch (Exception e) { 211 this.is_connected = false; 212 this.is_connecting = false; 213 214 OnDisconnected (DisconnectReason.TimeOut, e.Message); 215 } 216 } 217 218 219 protected override void AnnounceInternal (SparkleAnnouncement announcement) 220 { 221 string to_send = "announce " + announcement.FolderIdentifier + " " + announcement.Message + "\n"; 222 223 try { 224 if (this.socket != null) 225 this.socket.Send (Encoding.UTF8.GetBytes (to_send)); 226 227 this.last_ping = DateTime.Now; 228 229 } catch (Exception e) { 230 this.is_connected = false; 231 this.is_connecting = false; 232 233 OnDisconnected (DisconnectReason.TimeOut, e.Message); 234 } 235 } 236 237 238 public override void Dispose () 239 { 240 if (this.socket != null) { 241 this.socket.Close (); 242 this.socket = null; 243 } 244 245 this.thread.Abort (); 246 this.thread.Join (); 247 248 base.Dispose (); 249 } 250 251 252 private string CleanMessage (string message) 253 { 254 message = message.Replace ("\n", ""); 255 message = message.Replace ("\0", ""); 256 return message.Trim (); 257 } 258 } 259}