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