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