DaZeus  2.0
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
network.cpp
Go to the documentation of this file.
1 
6 #include "network.h"
7 #include "server.h"
8 #include "utils.h"
9 #include <stdio.h>
10 #include <sys/select.h>
11 
12 std::string dazeus::Network::toString(const Network *n)
13 {
14  std::stringstream res;
15  res << "Network[";
16  if(n == 0) {
17  res << "0";
18  } else {
19  const NetworkConfig *nc = n->config();
20  res << nc->displayName << ", " << Server::toString(n->activeServer());
21  }
22  res << "]";
23 
24  return res.str();
25 }
26 
31 : activeServer_(0)
32 , config_(c)
33 , undesirables_()
34 , deleteServer_(false)
35 , identifiedUsers_()
36 , knownUsers_()
37 , topics_()
38 , networkListeners_()
39 , nick_(c->nickName)
40 , deadline_(0)
41 , nextPongDeadline_(0)
42 {}
43 
44 
49 {
50  disconnectFromNetwork();
51 }
52 
53 
58 {
59  return activeServer_;
60 }
61 
62 
67 {
68  return config_->autoConnect;
69 }
70 
71 void dazeus::Network::action( std::string destination, std::string message )
72 {
73  if( !activeServer_ )
74  return;
75  activeServer_->ctcpAction( destination, message );
76 }
77 
78 void dazeus::Network::names( std::string channel )
79 {
80  if( !activeServer_ )
81  return;
82  activeServer_->names( channel );
83 }
84 
85 std::vector<std::string> dazeus::Network::joinedChannels() const
86 {
87  std::vector<std::string> res;
88  std::map<std::string,std::vector<std::string> >::const_iterator it;
89  for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
90  res.push_back(it->first);
91  }
92  return res;
93 }
94 
95 std::map<std::string,std::string> dazeus::Network::topics() const
96 {
97  return topics_;
98 }
99 
100 std::map<std::string,dazeus::Network::ChannelMode> dazeus::Network::usersInChannel(std::string channel) const
101 {
102  std::map<std::string, ChannelMode> res;
103  std::map<std::string, std::vector<std::string> >::const_iterator it;
104  for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
105  if(it->first != channel)
106  continue;
107  std::vector<std::string> users = it->second;
108  std::vector<std::string>::const_iterator it2;
109  for(it2 = users.begin(); it2 != users.end(); ++it2) {
110  // TODO: remember the mode
111  res[*it2] = dazeus::Network::UserMode;
112  }
113  }
114  return res;
115 }
116 
124 namespace dazeus {
125 struct ServerSorter {
126  private:
127  Network *n_;
128  std::map<const ServerConfig*, int> randomValues;
129 
130  public:
131  ServerSorter(Network *n) : n_(n) {}
132  bool operator()(const ServerConfig *c1, const ServerConfig *c2) {
133  assert(c1->network == c2->network);
134  assert(c1->network == n_->config());
135 
136  int prio1 = c1->priority + n_->serverUndesirability(c1);
137  int prio2 = c2->priority + n_->serverUndesirability(c2);
138 
139  if(prio1 != prio2) {
140  return prio1 < prio2;
141  }
142 
143  // Assign random values to ServerConfigs having the same
144  // corrected prio's. These need to be stored because a
145  // comparator must be consistent and transitive, so
146  // subsequent calls to the operator()() function must agree
147  // with the return value of this call.
148  if(!contains(randomValues, c1)) {
149  randomValues[c1] = rand();
150  }
151  if(!contains(randomValues, c2)) {
152  randomValues[c2] = rand();
153  }
154  return randomValues[c1] < randomValues[c2];
155  }
156 };
157 }
158 
160 {
161  return config_;
162 }
163 
164 void dazeus::Network::connectToNetwork( bool reconnect )
165 {
166  if( !reconnect && activeServer_ )
167  return;
168 
169  printf("Connecting to network: %s\n", dazeus::Network::toString(this).c_str());
170 
171  // Check if there *is* a server to use
172  if( servers().size() == 0 )
173  {
174  printf("Trying to connect to network '%s', but there are no servers to connect to!\n",
175  config_->displayName.c_str());
176  return;
177  }
178 
179  // Find the best server to use
180 
181  // First, sort the list by priority and earlier failures
182  std::vector<ServerConfig*> sortedServers = servers();
183  std::sort( sortedServers.begin(), sortedServers.end(), ServerSorter(this) );
184 
185  // Then, take the first one and create a Server around it
186  ServerConfig *best = sortedServers[0];
187 
188  // And set it as the active server, and connect.
189  connectToServer( best, true );
190 }
191 
192 
193 void dazeus::Network::connectToServer( ServerConfig *server, bool reconnect )
194 {
195  if( !reconnect && activeServer_ )
196  return;
197  assert(knownUsers_.size() == 0);
198  assert(identifiedUsers_.size() == 0);
199 
200  if( activeServer_ )
201  {
202  activeServer_->disconnectFromServer( SwitchingServersReason );
203  // TODO: maybe deleteLater?
204  delete(activeServer_);
205  }
206 
207  activeServer_ = new Server( server, this );
208  activeServer_->connectToServer();
209  if(config_->connectTimeout > 0) {
210  deadline_ = time(NULL) + config_->connectTimeout;
211  }
212 }
213 
214 void dazeus::Network::joinedChannel(const std::string &user, const std::string &receiver)
215 {
216  if(user == nick_ && !contains_ci(knownUsers_, receiver)) {
217  knownUsers_[receiver] = std::vector<std::string>();
218  }
219  std::vector<std::string> users = find_ci(knownUsers_, receiver)->second;
220  if(!contains_ci(users, user))
221  find_ci(knownUsers_, receiver)->second.push_back(user);
222 }
223 
224 void dazeus::Network::partedChannel(const std::string &user, const std::string &, const std::string &receiver)
225 {
226  if(user == nick_) {
227  erase_ci(knownUsers_, receiver);
228  } else {
229  erase_ci(find_ci(knownUsers_, receiver)->second, user);
230  }
231  if(!isKnownUser(user)) {
232  erase_ci(identifiedUsers_, user);
233  }
234 }
235 
236 void dazeus::Network::slotQuit(const std::string &origin, const std::string&, const std::string &)
237 {
238  std::map<std::string,std::vector<std::string> >::iterator it;
239  for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
240  erase_ci(it->second, origin);
241  }
242  if(!isKnownUser(origin)) {
243  erase_ci(identifiedUsers_, origin);
244  }
245 }
246 
247 void dazeus::Network::slotNickChanged( const std::string &origin, const std::string &nick, const std::string & )
248 {
249  erase_ci(identifiedUsers_, origin);
250  erase_ci(identifiedUsers_, nick);
251 
252  if(nick_ == origin)
253  nick_ = nick;
254 
255  std::map<std::string,std::vector<std::string> >::iterator it;
256  for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
257  if(contains_ci(it->second, origin)) {
258  erase_ci(it->second, origin);
259  it->second.push_back(nick);
260  }
261  }
262 }
263 
264 void dazeus::Network::kickedChannel(const std::string&, const std::string &user, const std::string&, const std::string &receiver)
265 {
266  if(user == nick_) {
267  erase_ci(knownUsers_, receiver);
268  } else {
269  erase_ci(find_ci(knownUsers_, receiver)->second, user);
270  }
271  if(!isKnownUser(user)) {
272  erase_ci(identifiedUsers_, user);
273  }
274 }
275 
276 void dazeus::Network::onFailedConnection()
277 {
278  fprintf(stderr, "Connection failed on %s\n", dazeus::Network::toString(this).c_str());
279 
280  identifiedUsers_.clear();
281  knownUsers_.clear();
282 
283  slotIrcEvent("DISCONNECT", "", std::vector<std::string>());
284 
285  // Flag old server as undesirable
286  // Don't destroy it here yet; it is still in the stack. It will be destroyed
287  // in processDescriptors().
288  flagUndesirableServer( activeServer_->config() );
289  deleteServer_ = true;
290 }
291 
292 
293 void dazeus::Network::ctcp( std::string destination, std::string message )
294 {
295  if( !activeServer_ )
296  return;
297  activeServer_->ctcpAction( destination, message );
298 }
299 
300 
305 {
306  if( activeServer_ == 0 )
307  return;
308 
309  identifiedUsers_.clear();
310  knownUsers_.clear();
311 
312  activeServer_->disconnectFromServer( reason );
313  // TODO: maybe deleteLater?
314  delete activeServer_;
315  activeServer_ = 0;
316 }
317 
318 
319 void dazeus::Network::joinChannel( std::string channel )
320 {
321  if( !activeServer_ )
322  return;
323  activeServer_->join( channel );
324 }
325 
326 
327 void dazeus::Network::leaveChannel( std::string channel )
328 {
329  if( !activeServer_ )
330  return;
331  activeServer_->part( channel );
332 }
333 
334 
335 void dazeus::Network::say( std::string destination, std::string message )
336 {
337  if( !activeServer_ )
338  return;
339  activeServer_->message( destination, message );
340 }
341 
342 
343 void dazeus::Network::sendWhois( std::string destination )
344 {
345  activeServer_->whois(destination);
346 }
347 
348 const std::vector<dazeus::ServerConfig*> &dazeus::Network::servers() const
349 {
350  return config_->servers;
351 }
352 
353 
354 std::string dazeus::Network::nick() const
355 {
356  return nick_;
357 }
358 
359 
360 
361 std::string dazeus::Network::networkName() const
362 {
363  return config()->name;
364 }
365 
366 
367 
369 {
370  return contains(undesirables_, sc) ? undesirables_.find(sc)->second : 0;
371 }
372 
374 {
375  if(contains(undesirables_, sc))
376  undesirables_[sc] = undesirables_[sc] + 1;
377  else undesirables_[sc] = 1;
378 }
379 
381 {
382  undesirables_.erase(sc);
383 }
384 
385 bool dazeus::Network::isIdentified(const std::string &user) const {
386  return contains_ci(identifiedUsers_, user);
387 }
388 
389 bool dazeus::Network::isKnownUser(const std::string &user) const {
390  std::map<std::string,std::vector<std::string> >::const_iterator it;
391  for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
392  if(contains_ci(it->second, user)) {
393  return true;
394  }
395  }
396  return false;
397 }
398 
399 void dazeus::Network::slotWhoisReceived(const std::string &, const std::string &nick, bool identified) {
400  if(!identified) {
401  erase_ci(identifiedUsers_, nick);
402  } else if(identified && !contains_ci(identifiedUsers_, nick) && isKnownUser(nick)) {
403  identifiedUsers_.push_back(nick);
404  }
405 }
406 
407 void dazeus::Network::slotNamesReceived(const std::string&, const std::string &channel, const std::vector<std::string> &names, const std::string & ) {
408  assert(contains_ci(knownUsers_, channel));
409  std::vector<std::string> &users = find_ci(knownUsers_, channel)->second;
410  std::vector<std::string>::const_iterator it;
411  for(it = names.begin(); it != names.end(); ++it) {
412  std::string n = *it;
413  unsigned int nickStart;
414  for(nickStart = 0; nickStart < n.length(); ++nickStart) {
415  if(n[nickStart] != '@' && n[nickStart] != '~' && n[nickStart] != '+'
416  && n[nickStart] != '%' && n[nickStart] != '!') {
417  break;
418  }
419  }
420  n = n.substr(nickStart);
421  if(!contains_ci(users, n))
422  users.push_back(n);
423  }
424 }
425 
426 void dazeus::Network::slotTopicChanged(const std::string&, const std::string &channel, const std::string &topic) {
427  topics_[channel] = topic;
428 }
429 
430 void dazeus::Network::slotIrcEvent(const std::string &event, const std::string &origin, const std::vector<std::string> &params) {
431  std::string receiver;
432  if(params.size() > 0)
433  receiver = params[0];
434 
435  if(event != "ERROR") {
436  // a signal from the server means all is OK
437  deadline_ = 0;
438  }
439 
440 #define MIN(a) if(params.size() < a) { fprintf(stderr, "Too few parameters for event %s\n", event.c_str()); return; }
441  if(event == "CONNECT") {
442  nextPongDeadline_ = time(NULL) + 30;
443  serverIsActuallyOkay(activeServer_->config());
444  } else if(event == "JOIN") {
445  MIN(1);
446  joinedChannel(origin, receiver);
447  } else if(event == "PART") {
448  MIN(1);
449  partedChannel(origin, std::string(), receiver);
450  } else if(event == "KICK") {
451  MIN(2);
452  kickedChannel(origin, params[1], std::string(), receiver);
453  } else if(event == "QUIT") {
454  std::string message;
455  if(params.size() > 0) {
456  message = params[0];
457  }
458  slotQuit(origin, message, receiver);
459  } else if(event == "NICK") {
460  MIN(1);
461  slotNickChanged(origin, params[0], receiver);
462  } else if(event == "TOPIC") {
463  MIN(2);
464  slotTopicChanged(origin, params[0], params[1]);
465  }
466 #undef MIN
467  std::vector<NetworkListener*>::iterator nlit;
468  for(nlit = networkListeners_.begin(); nlit != networkListeners_.end();
469  nlit++) {
470  (*nlit)->ircEvent(event, origin, params, this);
471  }
472 }
473 
474 void dazeus::Network::addDescriptors(fd_set *in_set, fd_set *out_set, int *maxfd) {
475  activeServer_->addDescriptors(in_set, out_set, maxfd);
476 }
477 
478 void dazeus::Network::processDescriptors(fd_set *in_set, fd_set *out_set) {
479  if(deleteServer_) {
480  deleteServer_ = false;
481  delete activeServer_;
482  activeServer_ = 0;
483  connectToNetwork();
484  return;
485  }
486  activeServer_->processDescriptors(in_set, out_set);
487 }
488 
490  if(deadline_ > time(NULL)) {
491  return; // deadline is set, not passed
492  }
493  if(deadline_ == 0) {
494  if(time(NULL) > nextPongDeadline_) {
495  // We've passed the nextPongDeadline, send the next PING
496  nextPongDeadline_ = time(NULL) + 30;
497  deadline_ = time(NULL) + config_->pongTimeout;
498  activeServer_->ping();
499  }
500  return;
501  }
502 
503  // Connection didn't finish in time, disconnect and try again
504  flagUndesirableServer(activeServer()->config());
505  activeServer_->disconnectFromServer(dazeus::Network::TimeoutReason);
506  onFailedConnection();
507  // no need to delete the server later, though: we can do it from here
508  // TODO: use smart ptrs so this mess gets elegant by refcounting
509  deleteServer_ = false;
510  delete activeServer_;
511  activeServer_ = 0;
512  connectToNetwork(true);
513 }
514 
516  std::vector<Network*> nets;
517  nets << this;
518  dazeus::Network::run(nets);
519 }
520 
521 void dazeus::Network::run(std::vector<Network*> networks) {
522  fd_set sockets, out_sockets;
523  int highest;
524  std::vector<Network*>::const_iterator nit;
525  struct timeval timeout;
526  timeout.tv_sec = 1;
527  timeout.tv_usec = 0;
528  while(1) {
529  highest = 0;
530  FD_ZERO(&sockets);
531  FD_ZERO(&out_sockets);
532  for(nit = networks.begin(); nit != networks.end(); ++nit) {
533  if((*nit)->activeServer()) {
534  int ircmaxfd = 0;
535  (*nit)->addDescriptors(&sockets, &out_sockets, &ircmaxfd);
536  if(ircmaxfd > highest)
537  highest = ircmaxfd;
538  }
539  }
540 
541  // If all networks are disconnected, nothing will happen
542  // anymore; even the listeners won't be triggered anymore.
543  // In such a case, break the event loop
544  if(highest == 0) {
545  return;
546  }
547 
548  int socks = select(highest + 1, &sockets, &out_sockets, NULL, &timeout);
549  if(socks < 0) {
550  perror("select() failed");
551  return;
552  }
553  else if(socks == 0) {
554  continue;
555  }
556  for(nit = networks.begin(); nit != networks.end(); ++nit) {
557  if((*nit)->activeServer()) {
558  (*nit)->processDescriptors(&sockets, &out_sockets);
559  }
560  }
561  }
562 }