connectionhandler.cpp
1 //------------------------------------------------------------------------------
2 // connectionhandler.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) 2012 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 // "Zalewa" <zalewapl@gmail.com>
23 //------------------------------------------------------------------------------
24 #include "connectionhandler.h"
25 
26 #include "application.h"
27 #include "apprunner.h"
28 #include "configuration/doomseekerconfig.h"
29 #include "gamedemo.h"
30 #include "gui/configuration/doomseekerconfigurationdialog.h"
31 #include "gui/configuration/gameconfigerrorbox.h"
32 #include "gui/mainwindow.h"
33 #include "joincommandlinebuilder.h"
34 #include "log.h"
35 #include "plugins/engineplugin.h"
36 #include "plugins/pluginloader.h"
37 #include "refresher/canrefreshserver.h"
38 #include "refresher/refresher.h"
39 #include "serverapi/gameclientrunner.h"
40 #include "serverapi/message.h"
41 #include "serverapi/server.h"
42 #include "strings.hpp"
43 #include <QDesktopServices>
44 #include <QMessageBox>
45 #include <QTimer>
46 #include <QUrl>
47 
48 DClass<ConnectionHandler>
49 {
50 public:
56  bool forceRefresh;
57  ServerPtr server;
58  QWidget *parentWidget;
59 };
60 
61 DPointered(ConnectionHandler)
62 
63 ConnectionHandler::ConnectionHandler(ServerPtr server, QWidget *parentWidget)
64  : QObject(parentWidget)
65 {
66  d->forceRefresh = false;
67  d->server = server;
68  d->parentWidget = parentWidget;
69 }
70 
71 ConnectionHandler::~ConnectionHandler()
72 {
73 }
74 
75 void ConnectionHandler::checkResponse(const ServerPtr &server, int response)
76 {
77  server->disconnect(this);
78  if (response != Server::RESPONSE_GOOD)
79  {
80  switch (response)
81  {
83  QMessageBox::critical(d->parentWidget, tr("Doomseeker - join server"),
84  tr("Connection to server timed out."));
85  break;
86  default:
87  QMessageBox::critical(d->parentWidget, tr("Doomseeker - join server"),
88  tr("An error occured while trying to connect to server."));
89  break;
90  }
91  finish(response);
92  return;
93  }
94 
95  // Since we're potentially arriving from a deeply nested network recv,
96  // it will be best to give the call stack a breather and continue execution
97  // from the next iteration of the main loop.
98  // This fixes a crash reported as #3268.
99  QTimer::singleShot(0, this, SLOT(buildJoinCommandLine()));
100 }
101 
102 ConnectionHandler *ConnectionHandler::connectByUrl(const QUrl &url)
103 {
104  gLog << QString("Attempting to connect to server: %1").arg(url.toString());
105 
106  // Locate plugin by scheme
107  const EnginePlugin *handler = nullptr;
108  // For compatibility with IDE's zds://.../<two character> scheme
109  bool zdsScheme = url.scheme().compare("zds", Qt::CaseInsensitive) == 0;
110  for (unsigned int i = 0; i < gPlugins->numPlugins(); ++i)
111  {
112  const EnginePlugin *plugin = gPlugins->plugin(i)->info();
113  if (plugin->data()->scheme.compare(url.scheme(), Qt::CaseInsensitive) == 0 ||
114  (zdsScheme && plugin->data()->scheme.left(2).compare(url.path().mid(1), Qt::CaseInsensitive) == 0))
115  {
116  handler = plugin;
117  break;
118  }
119  }
120  if (handler == nullptr)
121  {
122  gLog << "Scheme not recognized starting normally.";
123  return nullptr;
124  }
125 
126  unsigned short port = url.port(handler->data()->defaultServerPort);
127  QString address;
128  // We can get the port through QUrl so we'll just create a temporary variable here.
129  unsigned short tmp;
130  Strings::translateServerAddress(url.host(), address, tmp, QString("localhost:10666"));
131 
132  // Create the server object
133  ServerPtr server = handler->server(QHostAddress(address), port);
134  ConnectionHandler *connectionHandler = new ConnectionHandler(server, nullptr);
135  connectionHandler->d->forceRefresh = true;
136 
137  return connectionHandler;
138 }
139 
140 void ConnectionHandler::finish(int response)
141 {
142  emit finished(response);
143 }
144 
145 void ConnectionHandler::buildJoinCommandLine()
146 {
147  JoinCommandLineBuilder *builder = new JoinCommandLineBuilder(d->server,
148  gConfig.doomseeker.bRecordDemo ? GameDemo::Managed : GameDemo::NoDemo,
149  d->parentWidget);
150  this->connect(builder, SIGNAL(commandLineBuildFinished()), SLOT(onCommandLineBuildFinished()));
151  builder->obtainJoinCommandLine();
152 }
153 
154 void ConnectionHandler::onCommandLineBuildFinished()
155 {
156  auto builder = static_cast<JoinCommandLineBuilder *>(sender());
157  CommandLineInfo builtCli = builder->builtCommandLine();
158  if (builtCli.isValid())
159  {
160  runCommandLine(builtCli);
161  }
162  else if (!builder->error().isEmpty())
163  {
164  GameConfigErrorBox::show(d->server->plugin(),
165  tr("Doomseeker - join game"), builder->error(),
166  builder->isConfigurationError());
167  }
168  builder->deleteLater();
169  finish(Server::RESPONSE_GOOD);
170 }
171 
172 void ConnectionHandler::runCommandLine(const CommandLineInfo &cli)
173 {
174  Message message = AppRunner::runExecutable(cli);
175  if (message.isError())
176  {
177  gLog << tr(R"(Error while launching game "%2" for server "%1": %3)")
178  .arg(d->server->name()).arg(d->server->engineName()).arg(message.contents());
179  QMessageBox::critical(d->parentWidget, tr("Doomseeker - launch game"), message.contents());
180  }
181 }
182 
183 void ConnectionHandler::refreshToJoin()
184 {
185  // If the data we have is old we should refresh first to check if we can
186  // still properly join the server.
187  CanRefreshServer refreshCheck(d->server.data());
188  if (d->forceRefresh || (refreshCheck.shouldRefresh() && gConfig.doomseeker.bQueryBeforeLaunch))
189  {
190  this->connect(d->server.data(), SIGNAL(updated(ServerPtr,int)),
191  SLOT(checkResponse(ServerPtr,int)));
192  gRefresher->registerServer(d->server.data());
193  }
194  else
195  checkResponse(d->server, Server::RESPONSE_GOOD);
196 }
197 
198 void ConnectionHandler::run()
199 {
200  refreshToJoin();
201 }
202 
203 // -------------------------- URL Handler -------------------------------------
204 
205 PluginUrlHandler *PluginUrlHandler::instance = nullptr;
206 
207 void PluginUrlHandler::registerAll()
208 {
209  for (unsigned int i = 0; i < gPlugins->numPlugins(); ++i)
210  registerScheme(gPlugins->plugin(i)->info()->data()->scheme);
211 
212  // IDE compatibility
213  registerScheme("zds");
214 }
215 
216 void PluginUrlHandler::registerScheme(const QString &scheme)
217 {
218  if (!instance)
219  instance = new PluginUrlHandler();
220 
221  QDesktopServices::setUrlHandler(scheme, instance, "handleUrl");
222 }
223 
224 void PluginUrlHandler::handleUrl(const QUrl &url)
225 {
226  if (QMessageBox::question(nullptr, tr("Connect to server"),
227  tr("Do you want to connect to the server at %1?").arg(url.toString()),
228  QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)
229  {
230  ConnectionHandler::connectByUrl(url);
231  }
232 }