masterclient.cpp
1 //------------------------------------------------------------------------------
2 // masterclient.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301, USA.
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2009 "Blzut3" <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "masterclient.h"
24 
25 #include "log.h"
26 #include "plugins/engineplugin.h"
27 #include "serverapi/message.h"
28 #include "serverapi/server.h"
29 #include "serverapi/playerslist.h"
30 #include "datapaths.h"
31 
32 #include <QDataStream>
33 #include <QErrorMessage>
34 #include <QFile>
35 #include <QHostInfo>
36 #include <QMessageBox>
37 #include <QUdpSocket>
38 
39 DClass<MasterClient>
40 {
41  public:
42  QHostAddress address;
43 
44  bool timeouted;
45  bool enabled;
46  unsigned short port;
47  QList<ServerPtr> servers;
48 
49  QFile *cache;
50 
51  bool isCacheOpenForReading() const
52  {
53  return cache != NULL && cache->isReadable();
54  }
55 
56  bool isCacheOpenForWriting() const
57  {
58  return cache != NULL && cache->isWritable();
59  }
60 
61  QString (MasterClient::*masterBanHelp)() const;
62 };
63 
64 DPointered(MasterClient)
65 
66 POLYMORPHIC_DEFINE_CONST(QString, MasterClient, masterBanHelp, (), ());
67 
68 MasterClient::MasterClient()
69 {
70  d->cache = NULL;
71  d->timeouted = false;
72  d->enabled = true;
73  d->port = 0;
74  set_masterBanHelp(&MasterClient::masterBanHelp_default);
75 }
76 
77 MasterClient::~MasterClient()
78 {
80  resetPacketCaching();
81  if (d->cache != NULL)
82  {
83  delete d->cache;
84  }
85 }
86 
87 void MasterClient::clearServers()
88 {
89  d->servers.clear();
90 }
91 
92 bool MasterClient::isAddressSame(const QHostAddress &address, unsigned short port) const
93 {
94  return (d->address == address && d->port == port);
95 }
96 
97 void MasterClient::emitBannedMessage()
98 {
100  QString helpMsg = masterBanHelp();
101  if (!helpMsg.trimmed().isEmpty())
102  {
103  msg = Message(Message::Type::CUSTOM_ERROR, tr("%1 %2").arg(
104  msg.contents(), helpMsg.trimmed()));
105  }
106  emit message(engineName(), msg.contents(), true);
107  emit messageImportant(msg);
108 }
109 
111 {
112  foreach (ServerPtr server, d->servers)
113  {
114  server->disconnect();
115  }
116  d->servers.clear();
117 }
118 
120 {
121  if (plugin() == NULL)
122  {
123  return "";
124  }
125  return plugin()->data()->name;
126 }
127 
129 {
130  return d->enabled;
131 }
132 
134 {
135  return d->timeouted;
136 }
137 
138 QString MasterClient::masterBanHelp_default() const
139 {
140  return QString();
141 }
142 
143 void MasterClient::notifyResponse(Response response)
144 {
145  switch(response)
146  {
147  default:
148  break;
149  case RESPONSE_BANNED:
150  {
151  emitBannedMessage();
152  break;
153  }
154  case RESPONSE_WAIT:
155  emit message(engineName(), tr("Could not fetch a new server list from the "
156  "master because not enough time has past."), true);
157  readPacketCache();
158  break;
159  case RESPONSE_BAD:
160  emit message(engineName(), tr("Bad response from master server."), true);
161  readPacketCache();
162  break;
163  case RESPONSE_OLD:
164  emit message(engineName(),
165  tr("Could not fetch a new server list. The protocol you are using is too old. "
166  "An update may be available."), true);
167  break;
168  }
169 }
170 
171 int MasterClient::numPlayers() const
172 {
173  int players = 0;
174  foreach(ServerPtr server, d->servers)
175  {
176  if (server != NULL)
177  {
178  players += server->players().numClients();
179  }
180  }
181  return players;
182 }
183 
184 int MasterClient::numServers() const
185 {
186  return d->servers.size();
187 }
188 
189 ServerPtr MasterClient::operator[](int index) const
190 {
191  return d->servers[index];
192 }
193 
194 bool MasterClient::preparePacketCache(bool write)
195 {
196  if (write ? !d->isCacheOpenForWriting() : !d->isCacheOpenForReading())
197  {
198  if(plugin() == NULL)
199  {
200  return false;
201  }
202 
203  if(d->cache == NULL)
204  {
205  QString cacheFile = gDefaultDataPaths->programsDataDirectoryPath() + "/"
206  + QString(plugin()->data()->name).replace(' ', "");
207  d->cache = new QFile(cacheFile);
208  }
209  else
210  {
211  d->cache->close();
212  }
213 
214  if(!d->cache->open(write ? QIODevice::WriteOnly|QIODevice::Truncate : QIODevice::ReadOnly))
215  {
216  resetPacketCaching();
217  return false;
218  }
219  }
220  else if (!write && d->isCacheOpenForReading())
221  {
222  // If we prepare cache for reading we want to start
223  // reading from the beginning.
224  d->cache->seek(0);
225  }
226  return d->cache != NULL;
227 }
228 
229 void MasterClient::pushPacketToCache(const QByteArray &data)
230 {
231  if(!preparePacketCache(true))
232  {
233  return;
234  }
235 
236  QDataStream strm(d->cache);
237  strm << static_cast<quint16>(data.size());
238  strm << data;
239 }
240 
241 MasterClient::Response MasterClient::readResponse(const QByteArray &data)
242 {
243  Response response = readMasterResponse(data);
244  if (response == RESPONSE_GOOD || response == RESPONSE_PENDING)
245  {
246  pushPacketToCache(data);
247  }
248  return response;
249 }
250 
251 void MasterClient::readPacketCache()
252 {
253  if(!preparePacketCache(false))
254  {
255  // Cache didn't open? Guess we just emit the signal.
256  emit listUpdated();
257  return;
258  }
259 
260  QDataStream strm(d->cache);
261  if (strm.atEnd())
262  {
263  // Can't read anything from cache. Either cache is empty
264  // or for some reason the file cursor is set to the
265  // end of the file.
266  emit listUpdated();
267  return;
268  }
269 
270  gLog << tr("Reloading master server results from cache for %1!").arg(plugin()->data()->name);
271  bool hasGood = false;
272  while(!strm.atEnd())
273  {
274  quint16 size;
275  strm >> size;
276 
277  QByteArray data(size, '\0');
278  strm >> data;
279 
280  Response response = readMasterResponse(data);
281  if (response == RESPONSE_GOOD)
282  {
283  hasGood = true;
284  }
285  if(response != RESPONSE_GOOD && response != RESPONSE_PENDING)
286  {
287  // Cache was not read properly. We need to emit the signal
288  // to notify the program that this master client finished
289  // updating.
290  emit listUpdated();
291  return;
292  }
293  }
294  if (!hasGood)
295  {
296  // Plugins are ought to emit listUpdated() only when RESPONSE_GOOD
297  // is achieved. If that's not the case, we shall emit it here
298  // to notify refreshing process that the server has completed
299  // updating.
300  emit listUpdated();
301  }
302 }
303 
304 void MasterClient::registerNewServer(ServerPtr server)
305 {
306  server->setSelf(server.toWeakRef());
307  d->servers << server;
308 }
309 
310 void MasterClient::resetPacketCaching()
311 {
312  if(d->cache != NULL)
313  {
314  delete d->cache;
315  d->cache = NULL;
316  }
317 }
318 
320 {
321  setTimeouted(false);
322  emptyServerList();
323  resetPacketCaching();
324 }
325 
326 bool MasterClient::sendRequest(QUdpSocket *socket)
327 {
328  if(d->address.isNull())
329  return false;
330 
331  // Make request
332  QByteArray request = createServerListRequest();
333  if(request.isEmpty())
334  return false;
335  socket->writeDatagram(request, d->address, d->port);
336  return true;
337 }
338 
339 const QList<ServerPtr> &MasterClient::servers() const
340 {
341  return d->servers;
342 }
343 
345 {
346  d->enabled = b;
347 }
348 
349 void MasterClient::setTimeouted(bool b)
350 {
351  d->timeouted = b;
352 }
353 
355 {
356  // Avoid timeouting more than once. This would cause errors.
357  if (!isTimeouted())
358  {
359  setTimeouted(true);
360 
361  emit message(tr("Master server timeout"), tr("Connection timeout (%1:%2).")
362  .arg(d->address.toString()).arg(d->port), true);
363  readPacketCache();
364 
366  }
367 }
368 
370 {
371 }
372 
373 void MasterClient::updateAddress()
374 {
375  QString host;
376  unsigned short port;
377  plugin()->masterHost(host, port);
378 
379  QHostInfo info = QHostInfo::fromName(host);
380  if(info.addresses().size() == 0)
381  return;
382 
383  d->address = info.addresses().first();
384  if (d->address.protocol() != QAbstractSocket::IPv4Protocol)
385  {
386  foreach(const QHostAddress &addr, info.addresses())
387  {
388  if(addr.protocol() == QAbstractSocket::IPv4Protocol)
389  d->address = addr;
390  }
391  }
392  d->port = port;
393 }
Response readResponse(const QByteArray &data)
Calls readMasterResponse and handles packet caching.
bool sendRequest(QUdpSocket *socket)
Sends request packet through socket.
Message object used to pass messages throughout the Doomseeker's system.
Definition: message.h:62
void registerNewServer(ServerPtr server)
Registers new server with this MasterClient.
void masterHost(QString &host, unsigned short &port) const
void message(const QString &title, const QString &content, bool isError)
bool isTimeouted() const
Indicates that the server has timeouted recently.
void emptyServerList()
virtual Response readMasterResponse(const QByteArray &data)=0
Called to read and analyze the response from the MasterServer.
void setEnabled(bool b)
static const unsigned BANNED_FROM_MASTERSERVER
Information indicating that current player is banned from given server.
Definition: message.h:110
virtual void refreshStarts()
void messageImportant(const Message &message)
Signal used to forward important message to Doomseeker.
void timeoutRefresh()
Times the refreshing process out.
virtual void timeoutRefreshEx()
Reimplement this for clean up purposes.
virtual const EnginePlugin * plugin() const =0
QString masterBanHelp() const
[Virtual] Help message displayed to the user when ban is detected.
static const unsigned CUSTOM_ERROR
Programmer-defined error message.
Definition: message.h:105
bool isAddressSame(const QHostAddress &address, unsigned short port) const
Returns true if the passed address:port is the same as this master server's.
QString contents() const
Customized displayable contents of this Message.
Definition: message.cpp:87
Abstract base for all MasterClients.
Definition: masterclient.h:49
bool isEnabled() const
QString engineName() const
Extracts engine name from pluginInfo() if available.
virtual QByteArray createServerListRequest()=0
Produce contents of server list request packet that is sent to the master server. ...