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 "application.h"
26 #include "apprunner.h"
27 #include "configuration/doomseekerconfig.h"
28 #include "gamedemo.h"
29 #include "gui/passworddlg.h"
30 #include "gui/wadseekerinterface.h"
31 #include "gui/wadseekershow.h"
32 #include "ini/settingsproviderqt.h"
33 #include "log.h"
34 #include "plugins/engineplugin.h"
35 #include "serverapi/exefile.h"
36 #include "serverapi/gameclientrunner.h"
37 #include "serverapi/message.h"
38 #include "serverapi/server.h"
39 #include "templatedpathresolver.h"
40 
41 #include <cassert>
42 #include <QDialogButtonBox>
43 #include <QGridLayout>
44 #include <QLabel>
45 #include <QListWidget>
46 #include <QMessageBox>
47 #include <wadseeker/wadseeker.h>
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 bool JoinCommandLineBuilder::buildServerConnectParams(ServerConnectParams &params)
86 {
87  if (d->server->isLockedAnywhere())
88  {
89  if (!d->passwordsAlreadySet)
90  {
91  PasswordDlg password(d->server);
92  int ret = password.exec();
93  if (ret != QDialog::Accepted)
94  {
95  return false;
96  }
97  d->connectPassword = password.connectPassword();
98  d->inGamePassword = password.inGamePassword();
99  d->passwordsAlreadySet = true;
100  }
101  params.setConnectPassword(d->connectPassword);
102  params.setInGamePassword(d->inGamePassword);
103  }
104 
105  if (!d->demoName.isEmpty())
106  {
107  params.setDemoName(d->demoName);
108  }
109  return true;
110 }
111 
112 const CommandLineInfo &JoinCommandLineBuilder::builtCommandLine() const
113 {
114  return d->cli;
115 }
116 
117 bool JoinCommandLineBuilder::checkServerStatus()
118 {
119  // Remember to check REFRESHING status first!
120  if (d->server->isRefreshing())
121  {
122  d->error = tr("This server is still refreshing.\nPlease wait until it is finished.");
123  gLog << tr("Attempted to obtain a join command line for a \"%1\" "
124  "server that is under refresh.").arg(d->server->addressWithPort());
125  return false;
126  }
127  // Fail if Doomseeker couldn't get data on this server.
128  else if (!d->server->isKnown())
129  {
130  d->error = tr("Data for this server is not available.\nOperation failed.");
131  gLog << tr("Attempted to obtain a join command line for an unknown server \"%1\"").arg(
132  d->server->addressWithPort());
133  return false;
134  }
135  return true;
136 }
137 
138 bool JoinCommandLineBuilder::checkWadseekerValidity(QWidget *parent)
139 {
140  Q_UNUSED(parent);
141  QString targetDirPath = gDoomseekerTemplatedPathResolver().resolve(gConfig.wadseeker.targetDirectory);
142  QDir targetDir(targetDirPath);
143  QFileInfo targetDirFileInfo(targetDirPath);
144 
145  if (targetDirPath.isEmpty() || !targetDir.exists() || !targetDirFileInfo.isWritable())
146  {
147  return false;
148  }
149 
150  return true;
151 }
152 
153 const QString &JoinCommandLineBuilder::error() const
154 {
155  return d->error;
156 }
157 
158 void JoinCommandLineBuilder::failBuild()
159 {
160  d->cli = CommandLineInfo();
161  emit commandLineBuildFinished();
162 }
163 
164 void JoinCommandLineBuilder::handleError(const JoinError &error)
165 {
166  if (!error.error().isEmpty())
167  {
168  d->error = error.error();
169  }
170  else
171  {
172  d->error = tr("Unknown error.");
173  }
174  d->configurationError = (error.type() == JoinError::ConfigurationError);
175 
176  gLog << tr("Error when obtaining join parameters for server "
177  "\"%1\", game \"%2\": %3").arg(d->server->name()).arg(
178  d->server->engineName()).arg(d->error);
179 }
180 
181 MissingWadsDialog::MissingWadsProceed JoinCommandLineBuilder::handleMissingWads(const JoinError &error)
182 {
183  QList<PWad> missingWads;
184  if (!error.missingIwad().isEmpty())
185  {
186  missingWads << error.missingIwad();
187  }
188  if (!error.missingWads().isEmpty())
189  {
190  missingWads << error.missingWads();
191  }
192 
193  MissingWadsDialog dialog(missingWads, error.incompatibleWads(), d->server->plugin(), d->parentWidget);
194  if (dialog.exec() == QDialog::Accepted)
195  {
196  if (dialog.decision() == MissingWadsDialog::Install)
197  {
198  if (!gWadseekerShow->checkWadseekerValidity(d->parentWidget))
199  {
200  return MissingWadsDialog::Cancel;
201  }
202  WadseekerInterface *wadseeker = WadseekerInterface::create(d->server);
203  this->connect(wadseeker, SIGNAL(finished(int)), SLOT(onWadseekerDone(int)));
204  wadseeker->setWads(dialog.filesToDownload());
205  wadseeker->setAttribute(Qt::WA_DeleteOnClose);
206  wadseeker->show();
207  }
208  }
209  return dialog.decision();
210 }
211 
212 bool JoinCommandLineBuilder::isConfigurationError() const
213 {
214  return d->configurationError;
215 }
216 
217 void JoinCommandLineBuilder::missingWadsClicked(QAbstractButton *button)
218 {
219  d->lastButtonClicked = d->buttonBox->standardButton(button);
220 }
221 
223 {
224  assert(d->server != nullptr);
225  d->cli = CommandLineInfo();
226 
227  if (d->demo == GameDemo::Managed)
228  {
229  if (!GameDemo::ensureStorageExists(d->parentWidget))
230  {
231  failBuild();
232  return;
233  }
234  }
235 
236  if (!checkServerStatus())
237  {
238  failBuild();
239  return;
240  }
241 
242  ServerConnectParams params;
243  if (!buildServerConnectParams(params))
244  {
245  failBuild();
246  return;
247  }
248  GameClientRunner *gameRunner = d->server->gameRunner();
249  JoinError joinError = gameRunner->createJoinCommandLine(d->cli, params);
250  delete gameRunner;
251 
252  switch (joinError.type())
253  {
255  failBuild();
256  return;
257  case JoinError::ConfigurationError:
258  case JoinError::Critical:
259  {
260  handleError(joinError);
261  failBuild();
262  return;
263  }
264 
266  {
267  if (tryToInstallGame())
268  {
270  }
271  else
272  {
273  failBuild();
274  }
275  return;
276  }
277 
278  case JoinError::MissingWads:
279  {
280  MissingWadsDialog::MissingWadsProceed proceed =
281  handleMissingWads(joinError);
282  switch (proceed)
283  {
284  case MissingWadsDialog::Cancel:
285  failBuild();
286  return;
287  case MissingWadsDialog::Ignore:
288  break;
289  case MissingWadsDialog::Install:
290  // async process; will call slot
291  return;
292  default:
293  gLog << "Bug: not sure how to proceed after \"MissingWads\".";
294  failBuild();
295  return;
296  }
297  [[gnu::fallthrough]];
298  }
299 
300  case JoinError::NoError:
301  if (d->demo == GameDemo::Managed)
302  {
303  GameDemo::saveDemoMetaData(d->demoName, *d->server->plugin(),
304  d->server->iwad(), d->server->wads());
305  }
306  break;
307 
308  default:
309  gLog << "JoinCommandLineBuilder - unhandled JoinError type!";
310  break;
311  }
312 
313  emit commandLineBuildFinished();
314 }
315 
316 void JoinCommandLineBuilder::onWadseekerDone(int result)
317 {
318  if (result == QDialog::Accepted)
319  {
321  }
322  else
323  {
324  failBuild();
325  }
326 }
327 
328 ServerPtr JoinCommandLineBuilder::server() const
329 {
330  return d->server;
331 }
332 
333 void JoinCommandLineBuilder::setPasswords(const QString &connectPassword, const QString &inGamePassword)
334 {
335  d->passwordsAlreadySet = !(connectPassword.isNull() && inGamePassword.isNull());
336  if (!connectPassword.isNull())
337  d->connectPassword = connectPassword;
338  if (!inGamePassword.isNull())
339  d->inGamePassword = inGamePassword;
340 }
341 
342 bool JoinCommandLineBuilder::tryToInstallGame()
343 {
344  Message message = d->server->clientExe()->install(gApp->mainWindowAsQWidget());
345  if (message.isError())
346  {
347  QMessageBox::critical(gApp->mainWindowAsQWidget(), tr("Game installation failure"),
348  message.contents(), QMessageBox::Ok);
349  }
350  return message.type() == Message::Type::SUCCESSFUL;
351 }