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->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 }
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
const QList< PWad > & incompatibleWads() const
Definition: joinerror.cpp:116
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...
void setWads(const QList< PWad > &wads)
Sets WADs to seek.
const QList< PWad > & missingWads() const
Definition: joinerror.cpp:111
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:106
void obtainJoinCommandLine()
Runs asynchronously and emits commandLineBuildFinished() when done.
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
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...