joincommandlinebuilder.cpp
1 //------------------------------------------------------------------------------
2 // joincommandlinebuilder.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) 2014 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "joincommandlinebuilder.h"
24 
25 #include "apprunner.h"
26 #include "datapaths.h"
27 #include "log.h"
28 #include "configuration/doomseekerconfig.h"
29 #include "gui/passworddlg.h"
30 #include "gui/wadseekerinterface.h"
31 #include "gui/wadseekershow.h"
32 #include "ini/settingsproviderqt.h"
33 #include "plugins/engineplugin.h"
34 #include "serverapi/exefile.h"
35 #include "serverapi/gameclientrunner.h"
36 #include "serverapi/message.h"
37 #include "serverapi/server.h"
38 #include "application.h"
39 #include "gamedemo.h"
40 
41 #include <wadseeker/wadseeker.h>
42 #include <QDialogButtonBox>
43 #include <QGridLayout>
44 #include <QLabel>
45 #include <QListWidget>
46 #include <QMessageBox>
47 #include <cassert>
48 
49 DClass<JoinCommandLineBuilder>
50 {
51  public:
52  CommandLineInfo cli;
53  bool configurationError;
54  QString connectPassword;
55  QString error;
56  GameDemo demo;
57  QString demoName;
58  QString inGamePassword;
59  ServerPtr server;
60  QWidget *parentWidget;
61  bool passwordsAlreadySet;
62 
63  // For missing wads dialog
64  QDialogButtonBox *buttonBox;
65  QDialogButtonBox::StandardButton lastButtonClicked;
66 };
67 
68 DPointered(JoinCommandLineBuilder)
69 
71  GameDemo demo, QWidget *parentWidget)
72 {
73  d->configurationError = false;
74  d->demo = demo;
75  d->demoName = GameDemo::mkDemoFullPath(demo, *server->plugin());
76  d->parentWidget = parentWidget;
77  d->passwordsAlreadySet = false;
78  d->server = server;
79 }
80 
81 JoinCommandLineBuilder::~JoinCommandLineBuilder()
82 {
83 }
84 
85 void JoinCommandLineBuilder::allDownloadableWads(const JoinError &joinError, QStringList &required, QStringList &optional)
86 {
87  if (!joinError.missingIwad().isEmpty())
88  {
89  required << joinError.missingIwad();
90  }
91 
92  const QList<PWad> missingWads = joinError.missingWads();
93  foreach(const PWad &wad, missingWads)
94  {
95  if(wad.isOptional())
96  optional.append(wad.name());
97  else
98  required.append(wad.name());
99  }
100  required = Wadseeker::filterAllowedOnlyWads(required);
101  optional = Wadseeker::filterAllowedOnlyWads(optional);
102 }
103 
104 bool JoinCommandLineBuilder::buildServerConnectParams(ServerConnectParams &params)
105 {
106  if (d->server->isLockedAnywhere())
107  {
108  if (!d->passwordsAlreadySet)
109  {
110  PasswordDlg password(d->server);
111  int ret = password.exec();
112  if (ret != QDialog::Accepted)
113  {
114  return false;
115  }
116  d->connectPassword = password.connectPassword();
117  d->inGamePassword = password.inGamePassword();
118  d->passwordsAlreadySet = true;
119  }
120  params.setConnectPassword(d->connectPassword);
121  params.setInGamePassword(d->inGamePassword);
122  }
123 
124  if (!d->demoName.isEmpty())
125  {
126  params.setDemoName(d->demoName);
127  }
128  return true;
129 }
130 
131 const CommandLineInfo &JoinCommandLineBuilder::builtCommandLine() const
132 {
133  return d->cli;
134 }
135 
136 bool JoinCommandLineBuilder::checkServerStatus()
137 {
138  // Remember to check REFRESHING status first!
139  if (d->server->isRefreshing())
140  {
141  d->error = tr("This server is still refreshing.\nPlease wait until it is finished.");
142  gLog << tr("Attempted to obtain a join command line for a \"%1\" "
143  "server that is under refresh.").arg(d->server->addressWithPort());
144  return false;
145  }
146  // Fail if Doomseeker couldn't get data on this server.
147  else if (!d->server->isKnown())
148  {
149  d->error = tr("Data for this server is not available.\nOperation failed.");
150  gLog << tr("Attempted to obtain a join command line for an unknown server \"%1\"").arg(
151  d->server->addressWithPort());
152  return false;
153  }
154  return true;
155 }
156 
157 bool JoinCommandLineBuilder::checkWadseekerValidity(QWidget *parent)
158 {
159  QString targetDirPath = gConfig.wadseeker.targetDirectory;
160  QDir targetDir(targetDirPath);
161  QFileInfo targetDirFileInfo(targetDirPath);
162 
163  if (targetDirPath.isEmpty() || !targetDir.exists() || !targetDirFileInfo.isWritable())
164  {
165  return false;
166  }
167 
168  return true;
169 }
170 
171 const QString &JoinCommandLineBuilder::error() const
172 {
173  return d->error;
174 }
175 
176 void JoinCommandLineBuilder::failBuild()
177 {
178  d->cli = CommandLineInfo();
179  emit commandLineBuildFinished();
180 }
181 
182 void JoinCommandLineBuilder::handleError(const JoinError &error)
183 {
184  if (!error.error().isEmpty())
185  {
186  d->error = error.error();
187  }
188  else
189  {
190  d->error = tr("Unknown error.");
191  }
192  d->configurationError = (error.type() == JoinError::ConfigurationError);
193 
194  gLog << tr("Error when obtaining join parameters for server "
195  "\"%1\", game \"%2\": %3").arg(d->server->name()).arg(
196  d->server->engineName()).arg(d->error);
197 }
198 
199 MissingWadsDialog::MissingWadsProceed JoinCommandLineBuilder::handleMissingWads(const JoinError &error)
200 {
201  QList<PWad> missingWads;
202  if (!error.missingIwad().isEmpty())
203  {
204  missingWads << error.missingIwad();
205  }
206  if (!error.missingWads().isEmpty())
207  {
208  missingWads << error.missingWads();
209  }
210  MissingWadsDialog dialog(missingWads, d->parentWidget);
211  if (dialog.exec() == QDialog::Accepted)
212  {
213  if (dialog.decision() == MissingWadsDialog::Install)
214  {
215  if (!gWadseekerShow->checkWadseekerValidity(d->parentWidget))
216  {
217  return MissingWadsDialog::Cancel;
218  }
219  WadseekerInterface *wadseeker = WadseekerInterface::create(d->server);
220  this->connect(wadseeker, SIGNAL(finished(int)), SLOT(onWadseekerDone(int)));
221  wadseeker->setWads(dialog.filesToDownload());
222  wadseeker->setAttribute(Qt::WA_DeleteOnClose);
223  wadseeker->show();
224  }
225  }
226  return dialog.decision();
227 }
228 
229 bool JoinCommandLineBuilder::isConfigurationError() const
230 {
231  return d->configurationError;
232 }
233 
234 void JoinCommandLineBuilder::missingWadsClicked(QAbstractButton *button)
235 {
236  d->lastButtonClicked = d->buttonBox->standardButton(button);
237 }
238 
240 {
241  assert(d->server != NULL);
242  d->cli = CommandLineInfo();
243 
244  if (!checkServerStatus())
245  {
246  failBuild();
247  return;
248  }
249 
250  ServerConnectParams params;
251  if (!buildServerConnectParams(params))
252  {
253  failBuild();
254  return;
255  }
256  GameClientRunner* gameRunner = d->server->gameRunner();
257  JoinError joinError = gameRunner->createJoinCommandLine(d->cli, params);
258  delete gameRunner;
259 
260  switch (joinError.type())
261  {
263  failBuild();
264  return;
265  case JoinError::ConfigurationError:
266  case JoinError::Critical:
267  {
268  handleError(joinError);
269  failBuild();
270  return;
271  }
272 
274  {
275  if (tryToInstallGame())
276  {
278  }
279  else
280  {
281  failBuild();
282  }
283  return;
284  }
285 
286  case JoinError::MissingWads:
287  {
288  MissingWadsDialog::MissingWadsProceed proceed =
289  handleMissingWads(joinError);
290  switch (proceed)
291  {
292  case MissingWadsDialog::Cancel:
293  failBuild();
294  return;
295  case MissingWadsDialog::Ignore:
296  break;
297  case MissingWadsDialog::Install:
298  // async process; will call slot
299  return;
300  default:
301  gLog << "Bug: not sure how to proceed after \"MissingWads\".";
302  failBuild();
303  return;
304  }
305  // Intentional fall through
306  }
307 
308  case JoinError::NoError:
309  if (d->demo == GameDemo::Managed)
310  {
311  GameDemo::saveDemoMetaData(d->demoName, *d->server->plugin(),
312  d->server->iwad(), d->server->wads());
313  }
314  break;
315 
316  default:
317  gLog << "JoinCommandLineBuilder - unhandled JoinError type!";
318  break;
319  }
320 
321  emit commandLineBuildFinished();
322 }
323 
324 void JoinCommandLineBuilder::onWadseekerDone(int result)
325 {
326  qDebug() << "onWadseekerDone:" << result;
327  if (result == QDialog::Accepted)
328  {
330  }
331  else
332  {
333  failBuild();
334  }
335 }
336 
337 ServerPtr JoinCommandLineBuilder::server() const
338 {
339  return d->server;
340 }
341 
342 void JoinCommandLineBuilder::setPasswords(const QString &connectPassword, const QString &inGamePassword)
343 {
344  d->passwordsAlreadySet = !(connectPassword.isNull() && inGamePassword.isNull());
345  if(!connectPassword.isNull())
346  d->connectPassword = connectPassword;
347  if(!inGamePassword.isNull())
348  d->inGamePassword = inGamePassword;
349 }
350 
351 bool JoinCommandLineBuilder::tryToInstallGame()
352 {
353  Message message = d->server->clientExe()->install(gApp->mainWindowAsQWidget());
354  if (message.isError())
355  {
356  QMessageBox::critical(gApp->mainWindowAsQWidget(), tr("Game installation failure"),
357  message.contents(), QMessageBox::Ok);
358  }
359  return message.type() == Message::Type::SUCCESSFUL;
360 }
PWAD hosted on a server.
Structure holding parameters for application launch.
Definition: apprunner.h:37
Message object used to pass messages throughout the Doomseeker&#39;s system.
Definition: message.h:63
A DTO for GameClientRunner; exchanges information between main program and plugins, and allows future extensions.
bool isError() const
True if type() is equal to or greater than CUSTOM_ERROR.
Definition: message.cpp:104
void setPasswords(const QString &connectPassword=QString(), const QString &inGamePassword=QString())
Sets the connect/ingame password and bypasses the prompt. Set passwords to a null string to unset...
const QList< PWad > & missingWads() const
Definition: joinerror.cpp:109
Generates command line for joining specified server.
Aborts without printing error.
Definition: joinerror.h:55
Indicator of error for the server join process.
Definition: joinerror.h:41
JoinError createJoinCommandLine(CommandLineInfo &cli, const ServerConnectParams &params)
Fills out CommandLineInfo object that allows client executables to be launched.
const QString & missingIwad() const
Definition: joinerror.cpp:104
void obtainJoinCommandLine()
Runs asynchronously and emits commandLineBuildFinished() when done.
void setWads(const QStringList &wads)
Sets WADs to seek.
Wadseeker dialog box, only one instance is allowed.
Game executable was not found but it can be automatically installed by the plugin.
Definition: joinerror.h:60
bool isOptional() const
Is this WAD required to join the server?
static const unsigned SUCCESSFUL
Message indicates that the operation was successful.
Definition: message.h:98
unsigned type() const
Message::Type.
Definition: message.cpp:124
QString contents() const
Customized displayable contents of this Message.
Definition: message.cpp:87
Creates command line that launches the client executable of the game and connects it to a server...
const QString & name() const
File name of the WAD.