demomanager.cpp
1 //------------------------------------------------------------------------------
2 // demomanager.h
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) 2011 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 
24 #include "demomanager.h"
25 #include "ui_demomanager.h"
26 #include "ini/ini.h"
27 #include "ini/settingsproviderqt.h"
28 #include "datapaths.h"
29 #include "pathfinder/pathfinder.h"
30 #include "pathfinder/wadpathfinder.h"
31 #include "plugins/engineplugin.h"
32 #include "plugins/pluginloader.h"
33 #include "serverapi/gameexeretriever.h"
34 #include "serverapi/gamecreateparams.h"
35 #include "serverapi/message.h"
36 #include "serverapi/server.h"
37 #include "serverapi/gamehost.h"
38 
39 #include <QDir>
40 #include <QFileDialog>
41 #include <QLabel>
42 #include <QMessageBox>
43 #include <QPushButton>
44 #include <QStandardItemModel>
45 
46 class Demo
47 {
48  public:
49  QString filename;
50  QString port;
51  QDateTime time;
52  QStringList wads;
53  QStringList optionalWads;
54 };
55 
56 DClass<DemoManagerDlg> : public Ui::DemoManagerDlg
57 {
58  public:
59  Demo *selectedDemo;
60  QStandardItemModel *demoModel;
61  QList<QList<Demo> > demoTree;
62 };
63 
64 DPointered(DemoManagerDlg)
65 
67 {
68  d->setupUi(this);
69  d->selectedDemo = NULL;
70 
71  d->demoModel = new QStandardItemModel();
72  adjustDemoList();
73 
74  connect(d->demoList->selectionModel(), SIGNAL( currentChanged(const QModelIndex &, const QModelIndex &) ), this, SLOT( updatePreview(const QModelIndex &) ));
75 }
76 
77 DemoManagerDlg::~DemoManagerDlg()
78 {
79 }
80 
81 void DemoManagerDlg::adjustDemoList()
82 {
83  // Get valid extensions
84  QStringList demoExtensions;
85  for(unsigned i = 0;i < gPlugins->numPlugins();++i)
86  {
87  QString ext = QString("*.%1").arg(gPlugins->info(i)->data()->demoExtension);
88 
89  if(!demoExtensions.contains(ext))
90  {
91  demoExtensions << ext;
92  }
93  }
94 
95  // In order to index the demos we'll convert the dates to integers by calculating the days until today.
96  // Also we need to convert double underscores to a single underscore
97  QDate today = QDate::currentDate();
98  QTime referenceTime(23, 59, 59);
99  QDir demosDirectory(gDefaultDataPaths->demosDirectoryPath());
100  QStringList demos = demosDirectory.entryList(demoExtensions, QDir::Files);
101  typedef QMap<int, Demo> DemoMap;
102  QMap<int, DemoMap> demoMap;
103  foreach(const QString &demoName, demos)
104  {
105  QStringList demoData;
106  QString metaData = demoName.left(demoName.lastIndexOf("."));
107  // We need to split manually to handle escaping.
108  for(int i = 0;i < metaData.length();++i)
109  {
110  if(metaData[i] == '_')
111  {
112  // If our underscore is followed by another just continue on...
113  if(i+1 < metaData.length() && metaData[i+1] == '_')
114  {
115  ++i;
116  continue;
117  }
118 
119  // Split the meta data and then restart from the beginning.
120  demoData << metaData.left(i).replace("__", "_");
121  metaData = metaData.mid(i+1);
122  i = 0;
123  }
124  }
125  // Whatever is left is a part of our data.
126  demoData << metaData.replace("__", "_");
127  if(demoData.size() < 3) // Should have at least 3 elements port, date, time[, iwad[, pwads]]
128  continue;
129 
130  QDate date = QDate::fromString(demoData[1], "dd.MM.yyyy");
131  QTime time = QTime::fromString(demoData[2], "hh.mm.ss");
132  Demo demo;
133  demo.filename = demoName;
134  demo.port = demoData[0];
135  demo.time = QDateTime(date, time);
136  if(demoData.size() >= 4)
137  demo.wads = demoData.mid(3);
138  else
139  {
140  // New format, read meta data from file!
141  QSettings settings(
142  gDefaultDataPaths->demosDirectoryPath() + QDir::separator() + demoName + ".ini",
143  QSettings::IniFormat);
144  SettingsProviderQt settingsProvider(&settings);
145  Ini metaData(&settingsProvider);
146  demo.wads << metaData.retrieveSetting("meta", "iwad");
147  QString pwads = metaData.retrieveSetting("meta", "pwads");
148  if(pwads.length() > 0)
149  demo.wads << pwads.split(";");
150  demo.optionalWads = metaData.retrieveSetting("meta", "optionalPwads").value().toStringList();
151  }
152 
153  demoMap[date.daysTo(today)][time.secsTo(referenceTime)] = demo;
154  }
155 
156  // Convert to a model
157  d->demoModel->clear();
158  d->demoTree.clear();
159  foreach(const DemoMap &demoDate, demoMap)
160  {
161  QStandardItem *item = new QStandardItem(demoDate.begin().value().time.toString("ddd. MMM d, yyyy"));
162  QList<Demo> demoDateList;
163  foreach(const Demo &demo, demoDate)
164  {
165  demoDateList << demo;
166  item->appendRow(new QStandardItem(demo.time.toString("hh:mm:ss")));
167  }
168  d->demoTree << demoDateList;
169  d->demoModel->appendRow(item);
170  }
171  d->demoList->setModel(d->demoModel);
172 }
173 
174 bool DemoManagerDlg::doRemoveDemo(const QString &file)
175 {
176  if(!QFile::remove(file))
177  QMessageBox::critical(this, tr("Unable to delete"), tr("Could not delete the selected demo."));
178  else
179  {
180  // Remove ini file as well, but don't bother warning if it can't be deleted for whatever reason
181  QFile::remove(file + ".ini");
182  d->selectedDemo = NULL;
183  return true;
184  }
185  return false;
186 }
187 
188 void DemoManagerDlg::deleteSelected()
189 {
190  if(QMessageBox::question(this, tr("Delete demo?"),
191  tr("Are you sure you want to delete the selected demo?"),
192  QMessageBox::Yes|QMessageBox::Cancel) == QMessageBox::Yes)
193  {
194  QModelIndex index = d->demoList->selectionModel()->currentIndex();
195  if(d->selectedDemo == NULL)
196  {
197  int dateRow = index.row();
198  for(int timeRow = 0;index.child(timeRow, 0).isValid();++timeRow)
199  {
200  if(doRemoveDemo(gDefaultDataPaths->demosDirectoryPath() + QDir::separator() + d->demoTree[dateRow][timeRow].filename))
201  {
202  d->demoModel->removeRow(timeRow, index);
203  d->demoTree[dateRow].removeAt(timeRow);
204  if(d->demoTree[dateRow].size() == 0)
205  {
206  d->demoModel->removeRow(dateRow);
207  d->demoTree.removeAt(dateRow);
208  break;
209  }
210 
211  // We deleted the top row, so decrement our pointer
212  --timeRow;
213  }
214  }
215  }
216  else
217  {
218  if(doRemoveDemo(gDefaultDataPaths->demosDirectoryPath() + QDir::separator() + d->selectedDemo->filename))
219  {
220  // Adjust the tree
221  int dateRow = index.parent().row();
222  int timeRow = index.row();
223 
224  d->demoModel->removeRow(timeRow, index.parent());
225  d->demoTree[dateRow].removeAt(timeRow);
226  if(d->demoTree[dateRow].size() == 0)
227  {
228  d->demoModel->removeRow(dateRow);
229  d->demoTree.removeAt(dateRow);
230  }
231  }
232  }
233  }
234 }
235 
236 void DemoManagerDlg::exportSelected()
237 {
238  if(d->selectedDemo == NULL)
239  return;
240 
241  QFileDialog saveDialog(this);
242  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
243  saveDialog.selectFile(d->selectedDemo->filename);
244  if(saveDialog.exec() == QDialog::Accepted)
245  {
246  // Copy the demo to the new location.
247  if(!QFile::copy(gDefaultDataPaths->demosDirectoryPath() + QDir::separator() + d->selectedDemo->filename, saveDialog.selectedFiles().first()))
248  QMessageBox::critical(this, tr("Unable to save"), tr("Could not write to the specified location."));
249  }
250 }
251 
252 void DemoManagerDlg::playSelected()
253 {
254  if(d->selectedDemo == NULL)
255  return;
256 
257  // Look for the plugin used to record.
258  EnginePlugin *plugin = NULL;
259  for(unsigned i = 0;i < gPlugins->numPlugins();i++)
260  {
261  if (d->selectedDemo->port == gPlugins->info(i)->data()->name)
262  {
263  plugin = gPlugins->info(i);
264  }
265  }
266  if(plugin == NULL)
267  {
268  QMessageBox::critical(this, tr("No plugin"),
269  tr("The \"%1\" plugin does not appear to be loaded.").arg(d->selectedDemo->port));
270  return;
271  }
272 
273  // Get executable path for pathfinder.
274  Message binMessage;
275  QString binPath = GameExeRetriever(*plugin->gameExe()).pathToOfflineExe(binMessage);
276 
277  // Locate all the files needed to play the demo
278  PathFinder pf;
279  pf.addPrioritySearchDir(binPath);
280  WadPathFinder wadFinder = WadPathFinder(pf);
281 
282  QStringList missingWads;
283  QStringList wadPaths;
284 
285  foreach (const QString &wad, d->selectedDemo->wads)
286  {
287  WadFindResult findResult = wadFinder.find(wad);
288  if (findResult.isValid())
289  wadPaths << findResult.path();
290  else
291  missingWads << wad;
292  }
293 
294  if(!missingWads.isEmpty())
295  {
296  QMessageBox::critical(this, tr("Files not found"),
297  tr("The following files could not be located: ") + missingWads.join(", "));
298  return;
299  }
300  QStringList optionalWadPaths;
301  foreach (const QString &wad, d->selectedDemo->optionalWads)
302  {
303  WadFindResult findResult = wadFinder.find(wad);
304  if (findResult.isValid())
305  optionalWadPaths << findResult.path();
306  }
307 
308  // Play the demo
309  GameCreateParams params;
310  params.setDemoPath(gDefaultDataPaths->demosDirectoryPath()
311  + QDir::separator() + d->selectedDemo->filename);
312  params.setIwadPath(wadPaths[0]);
313  params.setPwadsPaths(wadPaths.mid(1) + optionalWadPaths);
314  params.setHostMode(GameCreateParams::Demo);
315  params.setExecutablePath(binPath);
316 
317  GameHost* gameRunner = plugin->gameHost();
318  Message message = gameRunner->host(params);
319 
320  if (message.isError())
321  {
322  QMessageBox::critical(this, tr("Doomseeker - error"), message.contents());
323  }
324 
325  delete gameRunner;
326 }
327 
328 
329 void DemoManagerDlg::performAction(QAbstractButton *button)
330 {
331  if(button == d->buttonBox->button(QDialogButtonBox::Close))
332  reject();
333 }
334 
335 void DemoManagerDlg::updatePreview(const QModelIndex &index)
336 {
337  if(!index.isValid() || !index.parent().isValid())
338  {
339  d->preview->setText("");
340  d->selectedDemo = NULL;
341  return;
342  }
343 
344  int dateRow = index.parent().row();
345  int timeRow = index.row();
346  d->selectedDemo = &d->demoTree[dateRow][timeRow];
347 
348  QString text = "<b>" + tr("Port") + ":</b><p style=\"margin: 0px 0px 0px 10px\">" + d->selectedDemo->port + "</p>" +
349  "<b>" + tr("WADs") + ":</b><p style=\"margin: 0px 0px 0px 10px\">";
350  foreach(const QString &wad, d->selectedDemo->wads)
351  {
352  text += wad + "<br />";
353  }
354  foreach(const QString &wad, d->selectedDemo->optionalWads)
355  {
356  text += "[" + wad + "]<br />";
357  }
358  text += "</p>";
359  d->preview->setText(text);
360 }
Game parametrization data used when creating new games.
Performs a case-insensitive (OS independent) file searches.
Definition: pathfinder.h:81
Message object used to pass messages throughout the Doomseeker&#39;s system.
Definition: message.h:63
virtual GameHost * gameHost()
Creates an instance of GameHost derivative class.
A convenience wrapper class for GameExeFactory.
bool isError() const
True if type() is equal to or greater than CUSTOM_ERROR.
Definition: message.cpp:104
Configuration handler.
Definition: ini.h:69
Dialog for managing demos recorded through Doomseeker.
Definition: demomanager.h:37
Creates game servers, offline games or demo playbacks.
Definition: gamehost.h:69
Wrapper for PathFinder that specializes in findings WADs.
Definition: wadpathfinder.h:76
QString contents() const
Customized displayable contents of this Message.
Definition: message.cpp:87
Message host(const GameCreateParams &params)
Definition: gamehost.cpp:321
void addPrioritySearchDir(const QString &dir)
Definition: pathfinder.cpp:146