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 "datapaths.h"
29 #include "gamedemo.h"
30 #include "gui/passworddlg.h"
31 #include "gui/wadseekerinterface.h"
32 #include "gui/wadseekershow.h"
33 #include "ini/settingsproviderqt.h"
34 #include "log.h"
35 #include "plugins/engineplugin.h"
36 #include "serverapi/exefile.h"
37 #include "serverapi/gameclientrunner.h"
38 #include "serverapi/message.h"
39 #include "serverapi/server.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 = 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 (!checkServerStatus())
228  {
229  failBuild();
230  return;
231  }
232 
233  ServerConnectParams params;
234  if (!buildServerConnectParams(params))
235  {
236  failBuild();
237  return;
238  }
239  GameClientRunner *gameRunner = d->server->gameRunner();
240  JoinError joinError = gameRunner->createJoinCommandLine(d->cli, params);
241  delete gameRunner;
242 
243  switch (joinError.type())
244  {
246  failBuild();
247  return;
248  case JoinError::ConfigurationError:
249  case JoinError::Critical:
250  {
251  handleError(joinError);
252  failBuild();
253  return;
254  }
255 
257  {
258  if (tryToInstallGame())
259  {
261  }
262  else
263  {
264  failBuild();
265  }
266  return;
267  }
268 
269  case JoinError::MissingWads:
270  {
271  MissingWadsDialog::MissingWadsProceed proceed =
272  handleMissingWads(joinError);
273  switch (proceed)
274  {
275  case MissingWadsDialog::Cancel:
276  failBuild();
277  return;
278  case MissingWadsDialog::Ignore:
279  break;
280  case MissingWadsDialog::Install:
281  // async process; will call slot
282  return;
283  default:
284  gLog << "Bug: not sure how to proceed after \"MissingWads\".";
285  failBuild();
286  return;
287  }
288  [[gnu::fallthrough]];
289  }
290 
291  case JoinError::NoError:
292  if (d->demo == GameDemo::Managed)
293  {
294  GameDemo::saveDemoMetaData(d->demoName, *d->server->plugin(),
295  d->server->iwad(), d->server->wads());
296  }
297  break;
298 
299  default:
300  gLog << "JoinCommandLineBuilder - unhandled JoinError type!";
301  break;
302  }
303 
304  emit commandLineBuildFinished();
305 }
306 
307 void JoinCommandLineBuilder::onWadseekerDone(int result)
308 {
309  qDebug() << "onWadseekerDone:" << result;
310  if (result == QDialog::Accepted)
311  {
313  }
314  else
315  {
316  failBuild();
317  }
318 }
319 
320 ServerPtr JoinCommandLineBuilder::server() const
321 {
322  return d->server;
323 }
324 
325 void JoinCommandLineBuilder::setPasswords(const QString &connectPassword, const QString &inGamePassword)
326 {
327  d->passwordsAlreadySet = !(connectPassword.isNull() && inGamePassword.isNull());
328  if (!connectPassword.isNull())
329  d->connectPassword = connectPassword;
330  if (!inGamePassword.isNull())
331  d->inGamePassword = inGamePassword;
332 }
333 
334 bool JoinCommandLineBuilder::tryToInstallGame()
335 {
336  Message message = d->server->clientExe()->install(gApp->mainWindowAsQWidget());
337  if (message.isError())
338  {
339  QMessageBox::critical(gApp->mainWindowAsQWidget(), tr("Game installation failure"),
340  message.contents(), QMessageBox::Ok);
341  }
342  return message.type() == Message::Type::SUCCESSFUL;
343 }