Fix 'occurred' typo
[libt2n] / src / server.cpp
1 /*
2 Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4 The software in this package is distributed under the GNU General
5 Public License version 2 (with a special exception described below).
6
7 A copy of GNU General Public License (GPL) is included in this distribution,
8 in the file COPYING.GPL.
9
10 As a special exception, if other files instantiate templates or use macros
11 or inline functions from this file, or you compile this file and link it
12 with other works to produce a work based on this file, this file
13 does not by itself cause the resulting work to be covered
14 by the GNU General Public License.
15
16 However the source code for this file must still be made available
17 in accordance with section (3) of the GNU General Public License.
18
19 This exception does not invalidate any other reasons why a work based
20 on this file might be covered by the GNU General Public License.
21 */
22
23 #include <sstream>
24 #include <stdexcept>
25 #include <time.h>
26
27 #include <boost/bind.hpp>
28
29 #include "server.hxx"
30 #include "log.hxx"
31
32 namespace libt2n
33 {
34
35 server_connection::server_connection(int _timeout)
36     : connection_id(0)
37     , my_server(NULL)
38     , connection()
39 {
40     set_timeout(_timeout);
41     reset_timeout();
42 }
43
44 /**
45  * Destructor
46  */
47 server_connection::~server_connection()
48 {
49 }
50
51 /// get pointer to logging stream, returns NULL if no logging needed
52 std::ostream* server_connection::get_logstream(log_level_values level)
53 {
54     if (my_server != NULL)
55     {
56         std::ostream* ostr=my_server->get_logstream(level);
57         if (ostr != NULL)
58             (*ostr) << "connection " << get_id() << ": ";
59         return ostr;
60     }
61     else
62         return NULL;
63 }
64
65 /// check if timeout is expired, close connection if so
66 void server_connection::check_timeout()
67 {
68     if (timeout != -1 && last_action_time+timeout < time(NULL))
69     {
70         LOGSTREAM(debug,"timeout on connection " << connection_id << ", closing");
71         this->close();
72     }
73 }
74
75 /// reset the timeout, e.g. if something is received
76 void server_connection::reset_timeout()
77 {
78     last_action_time=time(NULL);
79 }
80
81 /** @brief add a callback to one connection
82
83     @param event event the function will be called at
84     @param func functor (see boost::function) that will be called
85 */
86 void server_connection::add_callback(callback_event_type event, const boost::function<void ()>& func)
87 {
88     if (event == new_connection)
89         throw std::logic_error("new_connection callback not allowed for server_connections");
90
91     connection::add_callback(event,func);
92 }
93
94 server::server()
95     : callbacks(__events_end)
96 {
97     set_default_timeout(30);
98     set_logging(NULL,none);
99     next_id=1;
100 }
101
102 server::~server()
103 {
104     std::map<unsigned int, server_connection*>::iterator ie=connections.end();
105     for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; i++)
106         delete i->second;
107
108     connections.clear();
109 }
110
111 /**
112  * Close all open connections
113  */
114 void server::close()
115 {
116     std::map<unsigned int, server_connection*>::iterator ie=connections.end();
117     for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; ++i)
118         i->second->close();
119 }
120
121 /** @brief add a callback
122
123     @param event event the function will be called at
124     @param func functor (see boost::function) that will be called
125
126     @note use boost::bind to bind to member functions like this:
127         s.add_callback(new_connection,boost::bind(&my_class::func_to_call_back, boost::ref(*this), _1));
128 */
129 void server::add_callback(callback_event_type event, const boost::function<void (unsigned int)>& func)
130 {
131     callbacks[event].push_back(func);
132
133     // add callback to all existing connections
134     if (event != new_connection)
135     {
136         std::map<unsigned int, server_connection*>::iterator ie=connections.end();
137         for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; i++)
138             i->second->add_callback(event,boost::bind(func, i->first));
139     }
140 }
141
142
143 /** @brief an event occurred, call all server-side callbacks
144
145     @param event event that occurred
146     @param conn_id connection-id parameter that will be given to the callback-function
147 */
148 void server::do_callbacks(callback_event_type event, unsigned int conn_id)
149 {
150     std::list<boost::function<void (unsigned int)> >::iterator i,ie=callbacks[event].end();
151     for (i=callbacks[event].begin(); i != ie; i++)
152         (*i)(conn_id);
153 }
154
155 /// add a new connection to the server
156 unsigned int server::add_connection(server_connection* newconn)
157 {
158     unsigned int cid=next_id++;
159     newconn->set_id(cid);
160     newconn->set_server(this);
161     connections[cid]=newconn;
162
163     // add all callbacks except new_connection
164     for(int e=connection_closed; e != __events_end; e++)
165     {
166         std::list<boost::function<void (unsigned int)> >::iterator i,ie=callbacks[e].end();
167         for (i=callbacks[e].begin(); i != ie; i++)
168             newconn->add_callback(static_cast<callback_event_type>(e),bind(*i,cid));
169     }
170
171     LOGSTREAM(debug,"new connection accepted, id: " << cid);
172
173     do_callbacks(new_connection,cid);
174
175     return cid;
176 }
177
178 /// activate logging to the given stream. everything above the given level is logged.
179 void server::set_logging(std::ostream *_logstream, log_level_values _log_level)
180 {
181     log_level=_log_level;
182     logstream=_logstream;
183 }
184
185 /**
186     @brief Gets a connection by id
187     
188     @param conn_id Connection ID
189     
190     @retval Pointer to connection object
191 */
192 server_connection* server::get_connection(unsigned int conn_id)
193 {
194     std::map<unsigned int, server_connection*>::iterator p=connections.find(conn_id);
195     if (p==connections.end())
196         return NULL;
197     else
198         return p->second;
199 }
200
201 /// check for timeouts, remove closed connections. don't forget to call this from time to time.
202 void server::cleanup()
203 {
204     std::map<unsigned int, server_connection*>::iterator ie=connections.end();
205     for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; i++)
206         i->second->check_timeout();
207
208     for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie;)
209     {
210         if (i->second->is_closed() && !i->second->packet_available())
211         {
212             // closed and no usable data in buffer -> remove
213             LOGSTREAM(debug,"removing connection " << i->first << " because it is closed and no more data waiting");
214
215             delete i->second;
216             connections.erase(i);
217             i=connections.begin();
218             ie=connections.end();
219         }
220         else
221             i++;
222     }
223 }
224
225 /** @brief get a complete data packet from any client. The packet is removed from the
226             connection buffer.
227     @param[out] data the data package
228     @param[out] conn_id the connection id we got this packet from
229     @retval true if packet found
230 */
231 bool server::get_packet(std::string& data, unsigned int& conn_id)
232 {
233     // todo: this is somehow unfair: the first connections in the map get checked more
234     // often than the others and can thus block them out
235
236     std::map<unsigned int, server_connection*>::iterator ie=connections.end();
237     for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; i++)
238         if (i->second->get_packet(data))
239         {
240             LOGSTREAM(debug,"got packet (" << data.size() << " bytes) from connection " << i->first);
241
242             conn_id=i->first;
243             return true;
244         }
245
246     return false;
247 }
248
249 /// get pointer to logging stream, returns NULL if no logging needed
250 std::ostream* server::get_logstream(log_level_values level)
251 {
252     if (logstream && log_level >= level)
253         return logstream;
254     else
255         return NULL;
256 }
257 };