1 module cheetah.socketclient;
2 
3 import vibe.core.net : TCPConnection;
4 import vibe.core.core : runTask;
5 import vibe.core.task : Task;
6 
7 import cheetah.socketserver;
8 import cheetah.socketeventargs;
9 import cheetah.socketeventtype;
10 import cheetah.socketeventchain;
11 
12 /// Wrapper for a tcp socket client.
13 class SocketClient(T) {
14   private:
15   /// The server the client is connected to.
16   SocketServer!T _server;
17 
18   /// The low-level tcp connection.
19   TCPConnection _connection;
20 
21   /// The reading task.
22   Task _readTask;
23 
24   /// The event args for the client.
25   SocketEventArgs!T _eventArgs;
26 
27   /// The connect events.
28   SocketEventChain!T _connectEvents;
29 
30   /// The disconnect events.
31   SocketEventChain!T _disconnectEvents;
32 
33   /// The receive events.
34   SocketEventChain!T _receiveEvents;
35 
36   /// The error events.
37   SocketEventChain!T _errorEvents;
38 
39   /// The buffer.
40   ubyte[] _buffer;
41 
42   /// The current received amount of bytes.
43   size_t _currentReceiveAmount;
44 
45   /// Boolean determining whether the client has been disconnected or not.
46   bool _disconnected;
47 
48   /// Generic data that can be associated with the client.
49   T _data;
50 
51   /// The ip address.
52   string _ipAddress;
53 
54   /// The port.
55   ushort _port;
56 
57   /// The address string.
58   string _addressString;
59 
60   /// The client id.
61   size_t _clientId;
62 
63   public:
64   /**
65   * Creates a new tcp client.
66   * Params:
67   *   server =  The server.
68   *   connection = The low-level tcp connection.
69   */
70   this(SocketServer!T server, TCPConnection connection) {
71     _server = server;
72     _connection = connection;
73     _eventArgs = new SocketEventArgs!T(_server, this);
74 
75     _ipAddress = _connection.remoteAddress.toAddressString();
76     _port = _connection.remoteAddress.port;
77     _addressString = _connection.remoteAddress.toString();
78   }
79 
80   @property {
81     /// Gets the amount of available bytes for receive.
82     size_t availableReceiveAmount() @trusted { return cast(size_t)_connection.leastSize; }
83 
84     /// Gets the current received amount of bytes.
85     size_t currentReceiveAmount() pure const @safe { return _currentReceiveAmount; }
86 
87     /// Gets the current buffer.
88     auto buffer() { return _buffer; }
89 
90     /// Gets a boolean determining whether the client has been disconnected or not.
91     bool disconnected() pure const @safe { return _disconnected; }
92 
93     /// Gets a boolean determining whether the client is connected or not.
94     bool connected() @trusted { return !_disconnected && _connection.connected; }
95 
96     /// Gets the generic data associated with the client.
97     T data() { return _data; }
98 
99     /**
100     * Sets the generic data associated with the client.
101     * Params:
102     *   newData = The generic data to associated with the client.
103     */
104     void data(T newData) {
105       _data = newData;
106     }
107 
108     /// Gets the remote address.
109     auto remoteAddress() { return _connection.remoteAddress; }
110 
111     /// Gets the remote ip address.
112     auto ipAddress() pure @safe { return _ipAddress; }
113 
114     /// Gets the remote port.
115     auto port() pure @safe { return _port; }
116 
117     /// Gets the address string.
118     auto address() pure @safe { return _addressString; }
119 
120     package(cheetah) {
121       /// Sets the client id.
122       void clientId(size_t newClientId) {
123         _clientId = newClientId;
124       }
125     }
126 
127     /// Gets the client id.
128     size_t clientId() { return _clientId; }
129   }
130 
131   /**
132   * Resets the current receive state.
133   * Params:
134   *   cachedAmount = (optional) An amount of cached bytes.
135   */
136   void resetReceive(size_t cachedAmount = 0) {
137     _currentReceiveAmount = cachedAmount;
138 
139     _buffer = _currentReceiveAmount ? _buffer[$-_currentReceiveAmount .. $] : [];
140   }
141 
142   /// Reads the current available bytes.
143   void read() {
144     read(availableReceiveAmount);
145   }
146 
147   /**
148   * Reads a specific amount of bytes.
149   * Paramms:
150   *   amount =  The amount of bytes to read.
151   */
152   void read(size_t amount) {
153     if (!amount) {
154       return;
155     }
156 
157     auto available = availableReceiveAmount;
158 
159     if (amount > available) {
160       amount = available;
161     }
162 
163     try {
164       auto temp = new ubyte[amount];
165 
166       _connection.read(temp);
167 
168       _buffer ~= temp;
169 
170       _currentReceiveAmount += amount;
171     }
172     catch (Exception e) {
173       report(e);
174     }
175 	catch (Throwable e) {
176 	  report(new Exception(e.toString()));
177 
178 	  throw e;
179     }
180   }
181 
182   /**
183   * Writes a buffer to the socket.
184   * Params:
185   *   buffer = The buffer to write.
186   */
187   void write(ubyte[] buffer) {
188     try {
189       _connection.write(buffer);
190     }
191     catch (Exception e) {
192       report(e);
193     }
194     catch (Throwable e) {
195 	  report(new Exception(e.toString()));
196 
197 	  throw e;
198     }
199   }
200 
201   /// Closes the socket.
202   void close() {
203     synchronized {
204       if (_disconnected) {
205         return;
206       }
207 
208       _disconnected = true;
209 
210       try {
211         _server.removeClient(_clientId);
212         
213         if (_connection.connected) {
214           _connection.close();
215         }
216 
217         fireEvent(SocketEventType.disconnect, _eventArgs);
218       }
219       catch (Exception e) {
220         report(e);
221       }
222 	  catch (Throwable e) {
223 	    report(new Exception(e.toString()));
224 
225 		throw e;
226 	  }
227     }
228   }
229 
230   /**
231   * Moves onto the next event handler in a chain.
232   * Params:
233   *   eventType = The event type to move onto next event handler.
234   */
235   void moveNext(SocketEventType eventType) {
236     switch (eventType) {
237       case SocketEventType.connect: {
238         _connectEvents.moveNext();
239         break;
240       }
241       case SocketEventType.disconnect: {
242         _disconnectEvents.moveNext();
243         break;
244       }
245       case SocketEventType.receive: {
246         _receiveEvents.moveNext();
247         break;
248       }
249       case SocketEventType.error: {
250         _errorEvents.moveNext();
251         break;
252       }
253 
254       default: {
255           _server.moveNext(eventType);
256           break;
257       }
258     }
259   }
260 
261 
262   package(cheetah):
263   @property {
264     /// Gets the event args of the client.
265     auto eventArgs() { return _eventArgs; }
266   }
267 
268   /**
269   * Sets up events for the client.
270   * Params:
271   *   connectEvents =     The connect events to setup.
272   *   disconnectEvents =  The disconnect events to setup.
273   *   receiveEvents =     The receive events to setup.
274   *   errorEvents =       The error events to setup.
275   */
276   void setupEvents(SocketEventChain!T connectEvents, SocketEventChain!T disconnectEvents, SocketEventChain!T receiveEvents, SocketEventChain!T errorEvents) {
277     if (connectEvents) _connectEvents = new SocketEventChain!T(connectEvents._events);
278     if (disconnectEvents) _disconnectEvents = new SocketEventChain!T(disconnectEvents._events);
279     if (receiveEvents) _receiveEvents = new SocketEventChain!T(receiveEvents._events);
280     if (errorEvents) _errorEvents = new SocketEventChain!T(errorEvents._events);
281   }
282 
283   /**
284   * Fires a socket event.
285   * Params:
286   *   eventType = The event type to fire.
287   *   e =         The event args to pass to the handler.
288   */
289   void fireEvent(SocketEventType eventType, SocketEventArgs!T e) {
290     switch (eventType) {
291       case SocketEventType.connect: {
292         if (_connectEvents) {
293           _connectEvents(e);
294           return;
295         }
296         break;
297       }
298       case SocketEventType.disconnect: {
299         if (_disconnectEvents) {
300           _disconnectEvents(e);
301           return;
302         }
303         break;
304       }
305       case SocketEventType.receive: {
306         if (_receiveEvents) {
307           _receiveEvents(e);
308           return;
309         }
310         break;
311       }
312       case SocketEventType.error: {
313         if (_errorEvents) {
314           _errorEvents(e);
315           return;
316         }
317         break;
318       }
319 
320       default: break;
321     }
322 
323     _server.fireEvent(eventType, e);
324   }
325 
326   /**
327   * Reports an error to the error handler.
328   * Params:
329   *   error =   The error to handle.
330   *   server =  The server tied to the error.
331   *   client =  The client tied to the error.
332   */
333   void report(Exception error) {
334     fireEvent(SocketEventType.error, new SocketEventArgs!T(_server, this, error));
335   }
336 
337   /// Processes the client.
338   void process() {
339     _readTask = runTask({
340       try {
341         while (connected) {
342           if (_connection.leastSize) {
343             fireEvent(SocketEventType.receive, _eventArgs);
344           }
345         }
346       }
347       catch (Exception e) {
348         report(e);
349       }
350 	  catch (Throwable e) {
351 	    report(new Exception(e.toString()));
352 
353 	    throw e;
354       }
355     });
356 
357     _readTask.join();
358 
359     close();
360   }
361 }