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 QHostAddress MasterClient::address() const
88 {
89  return d->address;
90 }
91 
92 void MasterClient::clearServers()
93 {
94  d->servers.clear();
95 }
96 
97 bool MasterClient::isAddressSame(const QHostAddress &address, unsigned short port) const
98 {
99  return (d->address.toIPv4Address() == address.toIPv4Address() && d->port == port);
100 }
101 
102 void MasterClient::emitBannedMessage()
103 {
105  QString helpMsg = masterBanHelp();
106  if (!helpMsg.trimmed().isEmpty())
107  {
108  msg = Message(Message::Type::CUSTOM_ERROR, tr("%1 %2").arg(
109  msg.contents(), helpMsg.trimmed()));
110  }
111  emit message(engineName(), msg.contents(), true);
112  emit messageImportant(msg);
113 }
114 
116 {
117  foreach (ServerPtr server, d->servers)
118  {
119  server->disconnect();
120  }
121  d->servers.clear();
122 }
123 
125 {
126  if (plugin() == NULL)
127  {
128  return "";
129  }
130  return plugin()->data()->name;
131 }
132 
134 {
135  return d->enabled;
136 }
137 
139 {
140  return d->timeouted;
141 }
142 
143 QString MasterClient::masterBanHelp_default() const
144 {
145  return QString();
146 }
147 
148 void MasterClient::notifyResponse(Response response)
149 {
150  switch(response)
151  {
152  default:
153  break;
154  case RESPONSE_BANNED:
155  {
156  emitBannedMessage();
157  break;
158  }
159  case RESPONSE_WAIT:
160  emit message(engineName(), tr("Could not fetch a new server list from the "
161  "master because not enough time has past."), true);
162  readPacketCache();
163  break;
164  case RESPONSE_BAD:
165  emit message(engineName(), tr("Bad response from master server."), true);
166  readPacketCache();
167  break;
168  case RESPONSE_OLD:
169  emit message(engineName(),
170  tr("Could not fetch a new server list. The protocol you are using is too old. "
171  "An update may be available."), true);
172  break;
173  }
174 }
175 
176 int MasterClient::numServers() const
177 {
178  return d->servers.size();
179 }
180 
181 ServerPtr MasterClient::operator[](int index) const
182 {
183  return d->servers[index];
184 }
185 
186 unsigned short MasterClient::port() const
187 {
188  return d->port;
189 }
190 
191 bool MasterClient::preparePacketCache(bool write)
192 {
193  if (write ? !d->isCacheOpenForWriting() : !d->isCacheOpenForReading())
194  {
195  if(plugin() == NULL)
196  {
197  return false;
198  }
199 
200  if(d->cache == NULL)
201  {
202  QString cacheFile = gDefaultDataPaths->programsDataDirectoryPath() + "/"
203  + QString(plugin()->data()->name).replace(' ', "");
204  d->cache = new QFile(cacheFile);
205  }
206  else
207  {
208  d->cache->close();
209  }
210 
211  if(!d->cache->open(write ? QIODevice::WriteOnly|QIODevice::Truncate : QIODevice::ReadOnly))
212  {
213  resetPacketCaching();
214  return false;
215  }
216  }
217  else if (!write && d->isCacheOpenForReading())
218  {
219  // If we prepare cache for reading we want to start
220  // reading from the beginning.
221  d->cache->seek(0);
222  }
223  return d->cache != NULL;
224 }
225 
226 void MasterClient::pushPacketToCache(const QByteArray &data)
227 {
228  if(!preparePacketCache(true))
229  {
230  return;
231  }
232 
233  QDataStream strm(d->cache);
234  strm << static_cast<quint16>(data.size());
235  strm << data;
236 }
237 
238 MasterClient::Response MasterClient::readResponse(const QByteArray &data)
239 {
240  Response response = readMasterResponse(data);
241  if (response == RESPONSE_GOOD || response == RESPONSE_PENDING)
242  {
243  pushPacketToCache(data);
244  }
245  return response;
246 }
247 
248 void MasterClient::readPacketCache()
249 {
250  if(!preparePacketCache(false))
251  {
252  // Cache didn't open? Guess we just emit the signal.
253  emit listUpdated();
254  return;
255  }
256 
257  QDataStream strm(d->cache);
258  if (strm.atEnd())
259  {
260  // Can't read anything from cache. Either cache is empty
261  // or for some reason the file cursor is set to the
262  // end of the file.
263  emit listUpdated();
264  return;
265  }
266 
267  gLog << tr("Reloading master server results from cache for %1!").arg(plugin()->data()->name);
268  bool hasGood = false;
269  while(!strm.atEnd())
270  {
271  quint16 size;
272  strm >> size;
273 
274  QByteArray data(size, '\0');
275  strm >> data;
276 
277  Response response = readMasterResponse(data);
278  if (response == RESPONSE_GOOD)
279  {
280  hasGood = true;
281  }
282  if(response != RESPONSE_GOOD && response != RESPONSE_PENDING)
283  {
284  // Cache was not read properly. We need to emit the signal
285  // to notify the program that this master client finished
286  // updating.
287  emit listUpdated();
288  return;
289  }
290  }
291  if (!hasGood)
292  {
293  // Plugins are ought to emit listUpdated() only when RESPONSE_GOOD
294  // is achieved. If that's not the case, we shall emit it here
295  // to notify refreshing process that the server has completed
296  // updating.
297  emit listUpdated();
298  }
299 }
300 
301 void MasterClient::registerNewServer(ServerPtr server)
302 {
303  server->setSelf(server.toWeakRef());
304  d->servers << server;
305 }
306 
307 void MasterClient::resetPacketCaching()
308 {
309  if(d->cache != NULL)
310  {
311  delete d->cache;
312  d->cache = NULL;
313  }
314 }
315 
317 {
318  setTimeouted(false);
319  emptyServerList();
320  resetPacketCaching();
321 }
322 
323 bool MasterClient::sendRequest(QUdpSocket *socket)
324 {
325  if(d->address.isNull())
326  return false;
327 
328  // Make request
329  QByteArray request = createServerListRequest();
330  if(request.isEmpty())
331  return false;
332  socket->writeDatagram(request, d->address, d->port);
333  return true;
334 }
335 
336 const QList<ServerPtr> &MasterClient::servers() const
337 {
338  return d->servers;
339 }
340 
342 {
343  d->enabled = b;
344 }
345 
346 void MasterClient::setTimeouted(bool b)
347 {
348  d->timeouted = b;
349 }
350 
352 {
353  // Avoid timeouting more than once. This would cause errors.
354  if (!isTimeouted())
355  {
356  setTimeouted(true);
357 
358  emit message(tr("Master server timeout"), tr("Connection timeout (%1:%2).")
359  .arg(d->address.toString()).arg(d->port), true);
360  readPacketCache();
361 
363  }
364 }
365 
367 {
368 }
369 
370 void MasterClient::updateAddress()
371 {
372  QString host;
373  unsigned short port;
374  plugin()->masterHost(host, port);
375 
376  QHostInfo info = QHostInfo::fromName(host);
377  if(info.addresses().size() == 0)
378  return;
379 
380  d->address = info.addresses().first();
381  if (d->address.protocol() != QAbstractSocket::IPv4Protocol)
382  {
383  foreach(const QHostAddress &addr, info.addresses())
384  {
385  if(addr.protocol() == QAbstractSocket::IPv4Protocol)
386  d->address = addr;
387  }
388  }
389  d->port = port;
390 }
Response readResponse(const QByteArray &data)
Calls readMasterResponse and handles packet caching.
unsigned short port() const
Network port of the master server.
bool sendRequest(QUdpSocket *socket)
Sends request packet through socket.
Message object used to pass messages throughout the Doomseeker&#39;s system.
Definition: message.h:63
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.
QHostAddress address() const
Address of the master server.
void setEnabled(bool b)
static const unsigned BANNED_FROM_MASTERSERVER
Information indicating that current player is banned from given server.
Definition: message.h:111
virtual const EnginePlugin * plugin() const =0
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.
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:106
bool isAddressSame(const QHostAddress &address, unsigned short port) const
Returns true if the passed address:port is the same as this master server&#39;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. ...