Proper locking and another example
[StratumLibrary.git] / StratumLibrary / Stratum.cs
index 56520ed..30ca0bc 100644 (file)
@@ -1,17 +1,24 @@
 \feffusing System;\r
 using System.Net;\r
+using System.Threading;\r
+using System.Net.Sockets;\r
 using Newtonsoft.Json.Linq;\r
 \r
 using System.Text;\r
-using System.Threading;\r
-using System.Net.Sockets;\r
+using System.Collections.Generic;\r
 \r
 namespace Stratum\r
 {\r
+    delegate void NotificationCallback(out object[] notificationData);\r
+\r
     public class Stratum\r
     {\r
         private Socket client;\r
 \r
+        object responsesLock = new object();\r
+        private Dictionary<string, string> responses = new Dictionary<string, string>();\r
+        ManualResetEvent gotResponse = new ManualResetEvent(false);\r
+\r
         /// <summary>\r
         /// Constructor of Stratum interface class\r
         /// </summary>\r
@@ -32,10 +39,10 @@ namespace Stratum
             ConnectCallback = (IAsyncResult ar) =>\r
             {\r
                 // Retrieve socket from the state object\r
-                Socket cli = (Socket)ar.AsyncState;\r
+                Socket arClient = (Socket)ar.AsyncState;\r
 \r
                 // Complete the connection\r
-                cli.EndConnect(ar);\r
+                arClient.EndConnect(ar);\r
 \r
                 // Signal that connection has been established\r
                 connectDone.Set();\r
@@ -46,6 +53,9 @@ namespace Stratum
 \r
             // Wait for signal\r
             connectDone.WaitOne();\r
+\r
+            // Start receive handler\r
+            Receiver(client);\r
         }\r
 \r
         ~Stratum()\r
@@ -64,10 +74,10 @@ namespace Stratum
             SendCallback = (IAsyncResult ar) =>\r
             {\r
                 // Retrieve the socket from the state object\r
-                Socket cli = (Socket)ar.AsyncState;\r
+                Socket arClient = (Socket)ar.AsyncState;\r
 \r
                 // Complete sending the data to the remote device\r
-                int bytesSent = cli.EndSend(ar);\r
+                int bytesSent = arClient.EndSend(ar);\r
 \r
                 // Signal that all bytes have been sent\r
                 sendDone.Set();\r
@@ -88,6 +98,22 @@ namespace Stratum
         /// </summary>\r
         /// <typeparam name="T">Return type</typeparam>\r
         /// <param name="method">Method name</param>\r
+        /// <returns>StratumResponse object</returns>\r
+        public StratumResponse<T> Invoke<T>(string method)\r
+        {\r
+            var req = new StratumRequest()\r
+            {\r
+                Method = method,\r
+                Params = new object[] { }\r
+            };\r
+            return Invoke<T>(req);\r
+        }\r
+\r
+        /// <summary>\r
+        /// Invoke remote method\r
+        /// </summary>\r
+        /// <typeparam name="T">Return type</typeparam>\r
+        /// <param name="method">Method name</param>\r
         /// <param name="arg">Argument</param>\r
         /// <returns>StratumResponse object</returns>\r
         public StratumResponse<T> Invoke<T>(string method, object arg)\r
@@ -119,75 +145,107 @@ namespace Stratum
 \r
         private StratumResponse<T> Invoke<T>(StratumRequest stratumReq)\r
         {\r
-            StratumResponse<T> rjson = null;\r
-\r
             // Serialize stratumReq into JSON string\r
             var reqJSON = Newtonsoft.Json.JsonConvert.SerializeObject(stratumReq) + '\n';\r
+            var reqId = (string) stratumReq.Id;\r
+\r
+            string strResponse = "";\r
+            StratumResponse<T> responseObj = null;\r
 \r
             // Send JSON data to the remote device.\r
             Send(client, reqJSON);\r
 \r
+            // Wait for response\r
+            gotResponse.WaitOne();\r
+\r
+            lock (responsesLock)\r
+            {\r
+                // Deserialize the response\r
+                strResponse = responses[reqId];\r
+                responses.Remove(reqId);\r
+            }\r
+            responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject<StratumResponse<T>>(strResponse);\r
+\r
+            // Reset the state\r
+            gotResponse.Reset();\r
+\r
+            if (responseObj == null)\r
+            {\r
+                try\r
+                {\r
+                    JObject jo = Newtonsoft.Json.JsonConvert.DeserializeObject(strResponse) as JObject;\r
+                    throw new Exception(jo["Error"].ToString());\r
+                }\r
+                catch (Newtonsoft.Json.JsonSerializationException)\r
+                {\r
+                    throw new Exception("Inconsistent or empty response");\r
+                }\r
+            }\r
+\r
+            return responseObj;\r
+        }\r
+\r
+        private void Receiver(Socket client)\r
+        {\r
             // Create the reading state object.\r
             StratumReadState state = new StratumReadState(client);\r
 \r
-            // Receive event\r
-            ManualResetEvent receiveDone = new ManualResetEvent(false);\r
-\r
             Action<IAsyncResult> ReceiveCallback = null;\r
             ReceiveCallback = (IAsyncResult ar) =>\r
             {\r
                 // Retrieve the state object and the client socket \r
                 // from the asynchronous state object.\r
-                StratumReadState st = (StratumReadState)ar.AsyncState;\r
-                Socket ci = st.workSocket;\r
+                StratumReadState arStatus = (StratumReadState)ar.AsyncState;\r
+                Socket arClient = arStatus.workSocket;\r
 \r
                 // Read data from the remote device.\r
-                int bytesRead = ci.EndReceive(ar);\r
+                int bytesRead = arClient.EndReceive(ar);\r
 \r
                 if (bytesRead <= 0)\r
                     return;\r
 \r
-                lock (st.sb)\r
+                lock (arStatus.sb)\r
                 {\r
                     // There might be more data, so store the data received so far.\r
-                    st.sb.Append(Encoding.ASCII.GetString(st.buffer, 0, bytesRead));\r
+                    arStatus.sb.Append(Encoding.ASCII.GetString(arStatus.buffer, 0, bytesRead));\r
 \r
-                    if (st.buffer[bytesRead - 1] != '\n')\r
-                    {\r
-                        //  No EOL at the end of buffer, going to get the rest of data\r
-                        ci.BeginReceive(st.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), st);\r
-                    }\r
-                    else\r
+                    if (arStatus.buffer[bytesRead - 1] == '\n')\r
                     {\r
-                        string strResponse = st.sb.ToString();\r
+                        string strMessage = arStatus.sb.ToString();\r
+                        arStatus.sb.Clear();\r
 \r
-                        // Deserialize the response\r
-                        rjson = Newtonsoft.Json.JsonConvert.DeserializeObject<StratumResponse<T>>(strResponse);\r
-\r
-                        if (rjson == null)\r
+                        try\r
                         {\r
-                            try\r
+                            JObject jo = Newtonsoft.Json.JsonConvert.DeserializeObject(strMessage) as JObject;\r
+                            string requestId = (string)jo["id"];\r
+\r
+                            if (!String.IsNullOrEmpty(requestId))\r
                             {\r
-                                JObject jo = Newtonsoft.Json.JsonConvert.DeserializeObject(strResponse) as JObject;\r
-                                throw new Exception(jo["Error"].ToString());\r
+                                lock (responsesLock)\r
+                                {\r
+                                    responses.Add(requestId, strMessage);\r
+                                }\r
+\r
+                                gotResponse.Set();\r
                             }\r
-                            catch (Newtonsoft.Json.JsonSerializationException)\r
+                            else\r
                             {\r
-                                throw new Exception("Inconsistent or empty response");\r
+                                // TODO: notifications handling\r
+                                Console.WriteLine("Notification: {0}", strMessage);\r
                             }\r
                         }\r
-\r
-                        // Signal that all bytes have been received.\r
-                        receiveDone.Set();\r
+                        catch (Newtonsoft.Json.JsonSerializationException e)\r
+                        {\r
+                            // TODO: handle parse error\r
+                        }\r
                     }\r
                 }\r
+\r
+                arClient.BeginReceive(arStatus.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), arStatus);\r
             };\r
 \r
-            // Begin receiving the data from the remote device.\r
             client.BeginReceive(state.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);\r
-            receiveDone.WaitOne();\r
-\r
-            return rjson;\r
         }\r
     }\r
+\r
 }
\ No newline at end of file