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  DemoRecord demo;
57  QString demoName;
58  QString inGamePassword;
59  ServerPtr server;
60  QWidget *parentWidget;
61  bool passwordsAlreadySet;
62 
63  DemoStore demoStore;
64 
65  // For missing wads dialog
66  QDialogButtonBox *buttonBox;
67  QDialogButtonBox::StandardButton lastButtonClicked;
68 };
69 
70 DPointered(JoinCommandLineBuilder)
71 
73  DemoRecord demo, QWidget *parentWidget)
74 {
75  d->demoStore = DemoStore();
76  d->configurationError = false;
77  d->demo = demo;
78  d->demoName = d->demoStore.mkDemoFullPath(demo, *server->plugin(), true);
79  d->parentWidget = parentWidget;
80  d->passwordsAlreadySet = false;
81  d->server = server;
82 }
83 
84 JoinCommandLineBuilder::~JoinCommandLineBuilder()
85 {
86 }
87 
88 bool JoinCommandLineBuilder::buildServerConnectParams(ServerConnectParams &params)
89 {
90  if (d->server->isLockedAnywhere())
91  {
92  if (!d->passwordsAlreadySet)
93  {
94  PasswordDlg password(d->server);
95  int ret = password.exec();
96  if (ret != QDialog::Accepted)
97  {
98  return false;
99  }
100  d->connectPassword = password.connectPassword();
101  d->inGamePassword = password.inGamePassword();
102  d->passwordsAlreadySet = true;
103  }
104  params.setConnectPassword(d->connectPassword);
105  params.setInGamePassword(d->inGamePassword);
106  }
107 
108  if (!d->demoName.isEmpty())
109  {
110  params.setDemoName(d->demoName);
111  }
112  else if (d->demo != DemoRecord::NoDemo)
113  {
114  // If demo is set for recording, but the path is not set, then
115  // something went wrong.
116  d->error = tr("Demo set for recording, but couldn't prepare its save path.\n\n%1")
117  .arg(d->demoStore.root().absolutePath());
118  return false;
119  }
120  return true;
121 }
122 
123 const CommandLineInfo &JoinCommandLineBuilder::builtCommandLine() const
124 {
125  return d->cli;
126 }
127 
128 bool JoinCommandLineBuilder::checkServerStatus()
129 {
130  // Remember to check REFRESHING status first!
131  if (d->server->isRefreshing())
132  {
133  d->error = tr("This server is still refreshing.\nPlease wait until it is finished.");
134  gLog << tr("Attempted to obtain a join command line for a \"%1\" "
135  "server that is under refresh.").arg(d->server->addressWithPort());
136  return false;
137  }
138  // Fail if Doomseeker couldn't get data on this server.
139  else if (!d->server->isKnown())
140  {
141  d->error = tr("Data for this server is not available.\nOperation failed.");
142  gLog << tr("Attempted to obtain a join command line for an unknown server \"%1\"").arg(
143  d->server->addressWithPort());
144  return false;
145  }
146  return true;
147 }
148 
149 bool JoinCommandLineBuilder::checkWadseekerValidity(QWidget *parent)
150 {
151  Q_UNUSED(parent);
152  QString targetDirPath = gDoomseekerTemplatedPathResolver().resolve(gConfig.wadseeker.targetDirectory);
153  QDir targetDir(targetDirPath);
154  QFileInfo targetDirFileInfo(targetDirPath);
155 
156  if (targetDirPath.isEmpty() || !targetDir.exists() || !targetDirFileInfo.isWritable())
157  {
158  return false;
159  }
160 
161  return true;
162 }
163 
164 const QString &JoinCommandLineBuilder::error() const
165 {
166  return d->error;
167 }
168 
169 void JoinCommandLineBuilder::failBuild()
170 {
171  d->cli = CommandLineInfo();
172  emit commandLineBuildFinished();
173 }
174 
175 void JoinCommandLineBuilder::handleError(const JoinError &error)
176 {
177  if (!error.error().isEmpty())
178  {
179  d->error = error.error();
180  }
181  else
182  {
183  d->error = tr("Unknown error.");
184  }
185  d->configurationError = (error.type() == JoinError::ConfigurationError);
186 
187  gLog << tr("Error when obtaining join parameters for server "
188  "\"%1\", game \"%2\": %3").arg(d->server->name()).arg(
189  d->server->engineName()).arg(d->error);
190 }
191 
192 MissingWadsDialog::MissingWadsProceed JoinCommandLineBuilder::handleMissingWads(const JoinError &error)
193 {
194  QList<PWad> missingWads;
195  if (!error.missingIwad().isEmpty())
196  {
197  missingWads << error.missingIwad();
198  }
199  if (!error.missingWads().isEmpty())
200  {
201  missingWads << error.missingWads();
202  }
203 
204  MissingWadsDialog dialog(missingWads, error.incompatibleWads(), d->server->plugin(), d->parentWidget);
205  if (dialog.exec() == QDialog::Accepted)
206  {
207  if (dialog.decision() == MissingWadsDialog::Install)
208  {
209  if (!gWadseekerShow->checkWadseekerValidity(d->parentWidget))
210  {
211  return MissingWadsDialog::Cancel;
212  }
213  WadseekerInterface *wadseeker = WadseekerInterface::create(d->server);
214  this->connect(wadseeker, SIGNAL(finished(int)), SLOT(onWadseekerDone(int)));
215  wadseeker->setWads(dialog.filesToDownload());
216  wadseeker->setAttribute(Qt::WA_DeleteOnClose);
217  wadseeker->show();
218  }
219  }
220  return dialog.decision();
221 }
222 
223 bool JoinCommandLineBuilder::isConfigurationError() const
224 {
225  return d->configurationError;
226 }
227 
228 void JoinCommandLineBuilder::missingWadsClicked(QAbstractButton *button)
229 {
230  d->lastButtonClicked = d->buttonBox->standardButton(button);
231 }
232 
234 {
235  assert(d->server != nullptr);
236  d->cli = CommandLineInfo();
237 
238  if (d->demo == DemoRecord::Managed)
239  {
240  if (!d->demoStore.ensureStorageExists(d->parentWidget))
241  {
242  failBuild();
243  return;
244  }
245  }
246 
247  if (!checkServerStatus())
248  {
249  failBuild();
250  return;
251  }
252 
253  ServerConnectParams params;
254  if (!buildServerConnectParams(params))
255  {
256  failBuild();
257  return;
258  }
259  GameClientRunner *gameRunner = d->server->gameRunner();
260  JoinError joinError = gameRunner->createJoinCommandLine(d->cli, params);
261  delete gameRunner;
262 
263  switch (joinError.type())
264  {
266  failBuild();
267  return;
268  case JoinError::ConfigurationError:
269  case JoinError::Critical:
270  {
271  handleError(joinError);
272  failBuild();
273  return;
274  }
275 
277  {
278  if (tryToInstallGame())
279  {
281  }
282  else
283  {
284  failBuild();
285  }
286  return;
287  }
288 
289  case JoinError::MissingWads:
290  {
291  MissingWadsDialog::MissingWadsProceed proceed =
292  handleMissingWads(joinError);
293  switch (proceed)
294  {
295  case MissingWadsDialog::Cancel:
296  failBuild();
297  return;
298  case MissingWadsDialog::Ignore:
299  break;
300  case MissingWadsDialog::Install:
301  // async process; will call slot
302  return;
303  default:
304  gLog << "Bug: not sure how to proceed after \"MissingWads\".";
305  failBuild();
306  return;
307  }
308  [[gnu::fallthrough]];
309  }
310 
311  case JoinError::NoError:
312  if (d->demo == DemoRecord::Managed)
313  {
314  d->demoStore.saveDemoMetaData(d->demoName, *d->server->plugin(),
315  d->server->iwad(), d->server->wads(), true, d->server->gameVersion());
316  }
317  break;
318 
319  default:
320  gLog << "JoinCommandLineBuilder - unhandled JoinError type!";
321  break;
322  }
323 
324  emit commandLineBuildFinished();
325 }
326 
327 void JoinCommandLineBuilder::onWadseekerDone(int result)
328 {
329  if (result == QDialog::Accepted)
330  {
332  }
333  else
334  {
335  failBuild();
336  }
337 }
338 
339 ServerPtr JoinCommandLineBuilder::server() const
340 {
341  return d->server;
342 }
343 
344 void JoinCommandLineBuilder::setPasswords(const QString &connectPassword, const QString &inGamePassword)
345 {
346  d->passwordsAlreadySet = !(connectPassword.isNull() && inGamePassword.isNull());
347  if (!connectPassword.isNull())
348  d->connectPassword = connectPassword;
349  if (!inGamePassword.isNull())
350  d->inGamePassword = inGamePassword;
351 }
352 
353 bool JoinCommandLineBuilder::tryToInstallGame()
354 {
355  Message message = d->server->clientExe()->install(gApp->mainWindowAsQWidget());
356  if (message.isError())
357  {
358  QMessageBox::critical(gApp->mainWindowAsQWidget(), tr("Game installation failure"),
359  message.contents(), QMessageBox::Ok);
360  }
361  return message.type() == Message::Type::SUCCESSFUL;
362 }