masterclient.cpp
1 //------------------------------------------------------------------------------
2 // masterclient.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library 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 GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; 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 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "masterclient.h"
24 
25 #include "datapaths.h"
26 #include "log.h"
27 #include "plugins/engineplugin.h"
28 #include "serverapi/message.h"
29 #include "serverapi/playerslist.h"
30 #include "serverapi/server.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 != nullptr && cache->isReadable();
54  }
55 
56  bool isCacheOpenForWriting() const
57  {
58  return cache != nullptr && 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 = nullptr;
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 != nullptr)
82  {
83  delete d->cache;
84  }
85 }
86 
87 QHostAddress MasterClient::address() const
88 {
89  return d->address;
90 }
91 
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  clearServers();
118 }
119 
121 {
122  if (plugin() == nullptr)
123  {
124  return "";
125  }
126  return plugin()->data()->name;
127 }
128 
130 {
131  return d->enabled;
132 }
133 
135 {
136  return d->timeouted;
137 }
138 
139 QString MasterClient::masterBanHelp_default() const
140 {
141  return QString();
142 }
143 
144 void MasterClient::notifyResponse(Response response)
145 {
146  switch (response)
147  {
148  default:
149  break;
150  case RESPONSE_BANNED:
151  {
152  emitBannedMessage();
153  break;
154  }
155  case RESPONSE_WAIT:
156  emit message(engineName(), tr("Could not fetch a new server list from the "
157  "master because not enough time has passed."), true);
158  readPacketCache();
159  break;
160  case RESPONSE_BAD:
161  emit message(engineName(), tr("Bad response from master server."), true);
162  readPacketCache();
163  break;
164  case RESPONSE_OLD:
165  emit message(engineName(),
166  tr("Could not fetch a new server list. The protocol you are using is too old. "
167  "An update may be available."), true);
168  break;
169  }
170 }
171 
172 int MasterClient::numServers() const
173 {
174  return d->servers.size();
175 }
176 
177 ServerPtr MasterClient::operator[](int index) const
178 {
179  return d->servers[index];
180 }
181 
182 unsigned short MasterClient::port() const
183 {
184  return d->port;
185 }
186 
187 bool MasterClient::preparePacketCache(bool write)
188 {
189  if (write ? !d->isCacheOpenForWriting() : !d->isCacheOpenForReading())
190  {
191  if (plugin() == nullptr)
192  {
193  return false;
194  }
195 
196  if (d->cache == nullptr)
197  {
198  QString cacheFile = gDefaultDataPaths->cacheLocationPath() + "/"
199  + QString(plugin()->data()->name).replace(' ', "");
200  d->cache = new QFile(cacheFile);
201  }
202  else
203  {
204  d->cache->close();
205  }
206 
207  if (!d->cache->open(write ? QIODevice::WriteOnly | QIODevice::Truncate : QIODevice::ReadOnly))
208  {
209  resetPacketCaching();
210  return false;
211  }
212  }
213  else if (!write && d->isCacheOpenForReading())
214  {
215  // If we prepare cache for reading we want to start
216  // reading from the beginning.
217  d->cache->seek(0);
218  }
219  return d->cache != nullptr;
220 }
221 
222 void MasterClient::pushPacketToCache(const QByteArray &data)
223 {
224  if (!preparePacketCache(true))
225  {
226  return;
227  }
228 
229  QDataStream strm(d->cache);
230  strm << static_cast<quint16>(data.size());
231  strm << data;
232 }
233 
234 MasterClient::Response MasterClient::readResponse(const QByteArray &data)
235 {
236  Response response = readMasterResponse(data);
237  if (response == RESPONSE_GOOD || response == RESPONSE_PENDING)
238  {
239  pushPacketToCache(data);
240  }
241  return response;
242 }
243 
244 void MasterClient::readPacketCache()
245 {
246  if (!preparePacketCache(false))
247  {
248  // Cache didn't open? Guess we just emit the signal.
249  emit listUpdated();
250  return;
251  }
252 
253  QDataStream strm(d->cache);
254  if (strm.atEnd())
255  {
256  // Can't read anything from cache. Either cache is empty
257  // or for some reason the file cursor is set to the
258  // end of the file.
259  emit listUpdated();
260  return;
261  }
262 
263  gLog << tr("Reloading master server results from cache for %1!").arg(plugin()->data()->name);
264  bool hasGood = false;
265  while (!strm.atEnd())
266  {
267  quint16 size;
268  strm >> size;
269 
270  QByteArray data(size, '\0');
271  strm >> data;
272 
273  Response response = readMasterResponse(data);
274  if (response == RESPONSE_GOOD)
275  {
276  hasGood = true;
277  }
278  if (response != RESPONSE_GOOD && response != RESPONSE_PENDING)
279  {
280  // Cache was not read properly. We need to emit the signal
281  // to notify the program that this master client finished
282  // updating.
283  emit listUpdated();
284  return;
285  }
286  }
287  if (!hasGood)
288  {
289  // Plugins are ought to emit listUpdated() only when RESPONSE_GOOD
290  // is achieved. If that's not the case, we shall emit it here
291  // to notify refreshing process that the server has completed
292  // updating.
293  emit listUpdated();
294  }
295 }
296 
297 void MasterClient::registerNewServer(ServerPtr server)
298 {
299  server->setSelf(server.toWeakRef());
300  d->servers << server;
301 }
302 
303 void MasterClient::resetPacketCaching()
304 {
305  if (d->cache != nullptr)
306  {
307  delete d->cache;
308  d->cache = nullptr;
309  }
310 }
311 
313 {
314  setTimeouted(false);
315  emptyServerList();
316  resetPacketCaching();
317 }
318 
319 bool MasterClient::sendRequest(QUdpSocket *socket)
320 {
321  if (d->address.isNull())
322  return false;
323 
324  // Make request
325  QByteArray request = createServerListRequest();
326  if (request.isEmpty())
327  return false;
328  socket->writeDatagram(request, d->address, d->port);
329  return true;
330 }
331 
332 const QList<ServerPtr> &MasterClient::servers() const
333 {
334  return d->servers;
335 }
336 
338 {
339  d->enabled = b;
340 }
341 
342 void MasterClient::setTimeouted(bool b)
343 {
344  d->timeouted = b;
345 }
346 
348 {
349  // Avoid timeouting more than once. This would cause errors.
350  if (!isTimeouted())
351  {
352  setTimeouted(true);
353 
354  emit message(tr("Master server timeout"), tr("Connection timeout (%1:%2).")
355  .arg(d->address.toString()).arg(d->port), true);
356  readPacketCache();
357 
359  }
360 }
361 
363 {
364 }
365 
366 void MasterClient::updateAddress()
367 {
368  QString host;
369  unsigned short port;
370  plugin()->masterHost(host, port);
371 
372  QHostInfo info = QHostInfo::fromName(host);
373  if (info.addresses().size() == 0)
374  return;
375 
376  d->address = info.addresses().first();
377  if (d->address.protocol() != QAbstractSocket::IPv4Protocol)
378  {
379  for (const QHostAddress &addr : info.addresses())
380  {
381  if (addr.protocol() == QAbstractSocket::IPv4Protocol)
382  d->address = addr;
383  }
384  }
385  d->port = port;
386 }