wadseekerinterface.cpp
1 //------------------------------------------------------------------------------
2 // wadseekerinterface.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) 2009 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "gui/wadseekerinterface.h"
24 #include "ui_wadseekerinterface.h"
25 
26 #include "application.h"
27 #include "configuration/doomseekerconfig.h"
28 #include "gui/helpers/taskbarbutton.h"
29 #include "gui/helpers/taskbarprogress.h"
30 #include "mainwindow.h"
31 #include "serverapi/server.h"
33 #include "strings.hpp"
34 #include "wadseeker/entities/checksum.h"
35 #include "wadseeker/entities/modfile.h"
36 #include "wadseeker/entities/modset.h"
37 
38 #include <QMessageBox>
39 
40 const int WadseekerInterface::UPDATE_INTERVAL_MS = 500;
41 WadseekerInterface *WadseekerInterface::currentInstance = nullptr;
42 
43 DClass<WadseekerInterface> : public Ui::WadseekerInterface
44 {
45 public:
46  bool bCompletedSuccessfully;
47  bool preventGame;
48  TaskbarButton *taskbarButton;
49  TaskbarProgress *taskbarProgress;
50 };
51 
52 DPointered(WadseekerInterface)
53 
54 WadseekerInterface::WadseekerInterface(QWidget *parent)
55  : QDialog(parent)
56 {
57  construct();
58  bAutomatic = false;
59 }
60 
61 WadseekerInterface::WadseekerInterface(ServerPtr server, QWidget *parent)
62  : QDialog(parent)
63 {
64  construct();
65  setupAutomatic();
66  d->lblTop->show();
67  d->lblTop->setText(tr("Downloading WADs for server \"%1\"").arg(server->name()));
68  setCustomSites(server->allWebSites());
69 }
70 
71 WadseekerInterface::~WadseekerInterface()
72 {
73  currentInstance = nullptr;
74 }
75 
76 void WadseekerInterface::abortService(const QString &service)
77 {
78  message(tr("Aborting service: %1").arg(service), WadseekerLib::Notice);
79  wadseeker.skipService(service);
80 }
81 
82 void WadseekerInterface::abortSite(const QUrl &url)
83 {
84  message(tr("Aborting site: %1").arg(url.toString()), WadseekerLib::Notice);
85  wadseeker.skipSiteSeek(url);
86 }
87 
88 void WadseekerInterface::accept()
89 {
90  if (isAutomatic())
91  {
92  if (d->bCompletedSuccessfully)
93  done(QDialog::Accepted);
94  }
95  else
96  {
97  if (d->leWadName->text().isEmpty())
98  return;
99  else
100  {
101  seekedWads.clear();
102  QStringList pwadNames = d->leWadName->text().split(',', QString::SkipEmptyParts);
103  for (QString pwadName : pwadNames)
104  {
105  seekedWads << pwadName.trimmed();
106  }
107  }
108  startSeeking(seekedWads);
109  }
110 }
111 
112 void WadseekerInterface::allDone(bool bSuccess)
113 {
114  setStateWaiting();
115  d->bCompletedSuccessfully = bSuccess;
116  QApplication::alert(this);
117  if (bSuccess)
118  {
119  displayMessage(tr("All done. Success."), WadseekerLib::NoticeImportant, false);
120 
121  if (isAutomatic() && !d->preventGame)
122  {
123  if (isActiveWindow())
124  done(QDialog::Accepted);
125  else
126  d->btnStartGame->show();
127  }
128  }
129  else
130  {
131  QList<PWad> failures = unsuccessfulWads();
132 
133  for (const PWad &failure : failures)
134  {
135  d->twWads->setFileFailed(failure.name());
136  }
137 
138  displayMessage(tr("All done. Fail."), WadseekerLib::CriticalError, false);
139  }
140 }
141 
142 void WadseekerInterface::connectWadseekerObject()
143 {
144  // Connect Wadseeker to the dialog box.
145  this->connect(&wadseeker, SIGNAL(allDone(bool)),
146  SLOT(allDone(bool)));
147  this->connect(&wadseeker, SIGNAL(message(const QString&,WadseekerLib::MessageType)),
148  SLOT(message(const QString&,WadseekerLib::MessageType)));
149  this->connect(&wadseeker, SIGNAL(seekStarted(const ModSet&)),
150  SLOT(seekStarted(const ModSet&)));
151  this->connect(&wadseeker, SIGNAL(fileInstalled(const ModFile&)),
152  SLOT(fileDownloadSuccessful(const ModFile&)));
153  this->connect(&wadseeker, SIGNAL(siteFinished(const QUrl&)),
154  SLOT(siteFinished(const QUrl&)));
155  this->connect(&wadseeker, SIGNAL(siteProgress(const QUrl&,qint64,qint64)),
156  SLOT(siteProgress(const QUrl&,qint64,qint64)));
157  this->connect(&wadseeker, SIGNAL(siteRedirect(const QUrl&,const QUrl&)),
158  SLOT(siteRedirect(const QUrl&,const QUrl&)));
159  this->connect(&wadseeker, SIGNAL(siteStarted(const QUrl&)),
160  SLOT(siteStarted(const QUrl&)));
161  this->connect(&wadseeker, SIGNAL(serviceStarted(QString)),
162  SLOT(serviceStarted(QString)));
163  this->connect(&wadseeker, SIGNAL(serviceFinished(QString)),
164  SLOT(serviceFinished(QString)));
165 
166  // Connect Wadseeker to the WADs table widget.
167  d->twWads->connect(&wadseeker, SIGNAL(fileDownloadFinished(const ModFile&)),
168  SLOT(setFileDownloadFinished(const ModFile&)));
169  d->twWads->connect(&wadseeker, SIGNAL(fileDownloadProgress(const ModFile&,qint64,qint64)),
170  SLOT(setFileProgress(const ModFile&,qint64,qint64)));
171  d->twWads->connect(&wadseeker, SIGNAL(fileDownloadStarted(const ModFile&,const QUrl&)),
172  SLOT(setFileUrl(const ModFile&,const QUrl&)));
173 }
174 
175 void WadseekerInterface::construct()
176 {
177  d->setupUi(this);
178  d->preventGame = false;
179  d->bCompletedSuccessfully = false;
180 
181  d->taskbarButton = new TaskbarButton(this);
182 
183  d->taskbarProgress = d->taskbarButton->progress();
184  d->taskbarProgress->setMaximum(d->pbOverallProgress->maximum());
185 
186  setStateWaiting();
187 
188  initMessageColors();
189 
190  this->setWindowIcon(QIcon(":/icon.png"));
191  d->btnStartGame->hide();
192  this->connect(&updateTimer, SIGNAL(timeout()), SLOT(registerUpdateRequest()));
193 
194  connectWadseekerObject();
195 
196  // Connect tables.
197  this->connect(d->twWads, SIGNAL(rightMouseClick(const QModelIndex&,const QPoint&)),
198  SLOT(wadsTableRightClicked(const QModelIndex&,const QPoint&)));
199 
200  bAutomatic = false;
201  bFirstShown = false;
202 
203  QStringList urlList = gConfig.wadseeker.searchURLs;
204  if (gConfig.wadseeker.bAlwaysUseDefaultSites)
205  {
206  for (int i = 0; !Wadseeker::defaultSites[i].isEmpty(); ++i)
207  urlList << Wadseeker::defaultSites[i];
208  }
209 
210  wadseeker.setPrimarySites(urlList);
211 
212  updateTimer.setSingleShot(false);
213  updateTimer.start(UPDATE_INTERVAL_MS);
214 }
215 
216 WadseekerInterface *WadseekerInterface::create(QWidget *parent)
217 {
218  if (!isInstantiated())
219  {
220  currentInstance = new WadseekerInterface(parent);
221  return currentInstance;
222  }
223  return nullptr;
224 }
225 
226 WadseekerInterface *WadseekerInterface::create(ServerPtr server, QWidget *parent)
227 {
228  if (!isInstantiated())
229  {
230  currentInstance = new WadseekerInterface(server, parent);
231  return currentInstance;
232  }
233  return nullptr;
234 }
235 
236 WadseekerInterface *WadseekerInterface::createAutoNoGame(QWidget *parent)
237 {
238  WadseekerInterface *interface = create(parent);
239  if (interface != nullptr)
240  {
241  interface->setupAutomatic();
242  interface->d->preventGame = true;
243  }
244  return interface;
245 }
246 
247 void WadseekerInterface::displayMessage(const QString &message, WadseekerLib::MessageType type, bool bPrependErrorsWithMessageType)
248 {
249  QString strProcessedMessage;
250 
251  bool bPrependWithNewline = false;
252  QString wrapHtmlLeft = "<div style=\"%1\">";
253  QString wrapHtmlRight = "</div>";
254  QString htmlStyle;
255 
256  switch (type)
257  {
258  case WadseekerLib::CriticalError:
259  htmlStyle = QString("color: %1; font-weight: bold;").arg(colorHtmlMessageFatalError);
260  bPrependWithNewline = true;
261 
262  if (bPrependErrorsWithMessageType)
263  strProcessedMessage = tr("CRITICAL ERROR: %1").arg(message);
264  else
265  strProcessedMessage = message;
266 
267  setStateWaiting();
268  break;
269 
270  case WadseekerLib::Error:
271  htmlStyle = QString("color: %1;").arg(colorHtmlMessageError);
272 
273  if (bPrependErrorsWithMessageType)
274  strProcessedMessage = tr("Error: %1").arg(message);
275  else
276  strProcessedMessage = message;
277  break;
278 
279  case WadseekerLib::Notice:
280  htmlStyle = QString("color: %1;").arg(colorHtmlMessageNotice);
281 
282  strProcessedMessage = message;
283  break;
284 
285  case WadseekerLib::NoticeImportant:
286  htmlStyle = QString("color: %1; font-weight: bold;").arg(colorHtmlMessageNotice);
287  bPrependWithNewline = true;
288 
289  strProcessedMessage = message;
290  break;
291  }
292 
293  if (bPrependWithNewline && !d->teWadseekerOutput->toPlainText().isEmpty())
294  strProcessedMessage = "<br>" + strProcessedMessage;
295 
296  wrapHtmlLeft = wrapHtmlLeft.arg(htmlStyle);
297 
298  strProcessedMessage = wrapHtmlLeft + strProcessedMessage + wrapHtmlRight;
299 
300  d->teWadseekerOutput->append(strProcessedMessage);
301 }
302 
303 void WadseekerInterface::fileDownloadSuccessful(const ModFile &filename)
304 {
305  successfulWads << filename;
306  d->twWads->setFileSuccessful(filename.fileName());
307 }
308 
309 void WadseekerInterface::initMessageColors()
310 {
311  colorHtmlMessageNotice = gConfig.wadseeker.colorMessageNotice;
312  colorHtmlMessageError = gConfig.wadseeker.colorMessageError;
313  colorHtmlMessageFatalError = gConfig.wadseeker.colorMessageCriticalError;
314 }
315 
316 bool WadseekerInterface::isInstantiated()
317 {
318  return currentInstance != nullptr;
319 }
320 
321 void WadseekerInterface::message(const QString &message, WadseekerLib::MessageType type)
322 {
323  displayMessage(message, type, true);
324 }
325 
326 void WadseekerInterface::registerUpdateRequest()
327 {
328  updateProgressBar();
329  updateTitle();
330 }
331 
332 void WadseekerInterface::reject()
333 {
334  switch (state)
335  {
336  case Downloading:
337  wadseeker.abort();
338  break;
339 
340  case Waiting:
341  this->done(Rejected);
342  break;
343  }
344 }
345 
346 void WadseekerInterface::resetTitleToDefault()
347 {
348  setWindowTitle(tr("Wadseeker"));
349 }
350 
351 void WadseekerInterface::seekStarted(const ModSet &filenames)
352 {
353  QList<PWad> wads;
354  QStringList names;
355  for (ModFile modFile : filenames.modFiles())
356  {
357  wads << modFile;
358  names << modFile.fileName();
359  }
360  d->teWadseekerOutput->clear();
361  d->pbOverallProgress->setValue(0);
362  d->taskbarProgress->setValue(0);
363  displayMessage("Seek started on filenames: " + names.join(", "), WadseekerLib::Notice, false);
364 
365  seekedWads = wads;
366  successfulWads.clear();
367  d->twSites->setRowCount(0);
368  d->twWads->setRowCount(0);
369  setStateDownloading();
370 
371  for (const PWad &wad : seekedWads)
372  {
373  d->twWads->addFile(wad.name());
374  }
375 }
376 
377 void WadseekerInterface::setStateDownloading()
378 {
379  d->btnClose->setText(tr("Abort"));
380  d->btnDownload->setEnabled(false);
381  d->taskbarProgress->show();
382  state = Downloading;
383 }
384 
385 void WadseekerInterface::setStateWaiting()
386 {
387  d->btnClose->setText(tr("Close"));
388  d->btnDownload->setEnabled(true);
389  d->taskbarProgress->hide();
390  state = Waiting;
391 }
392 
393 void WadseekerInterface::setupAutomatic()
394 {
395  bAutomatic = true;
396  d->lblTop->hide();
397  d->btnDownload->hide();
398  d->leWadName->hide();
399 }
400 
401 void WadseekerInterface::setWads(const QList<PWad> &wads)
402 {
403  seekedWads = wads;
404  if (!isAutomatic())
405  {
406  QStringList names;
407  for (PWad wad : wads)
408  {
409  names << wad.name();
410  }
411  d->leWadName->setText(names.join(", "));
412  }
413 }
414 
415 void WadseekerInterface::setupIdgames()
416 {
417  wadseeker.setIdgamesEnabled(gConfig.wadseeker.bSearchInIdgames);
418  wadseeker.setIdgamesUrl(gConfig.wadseeker.idgamesURL);
419  wadseeker.setWadArchiveEnabled(gConfig.wadseeker.bSearchInWadArchive);
420 }
421 
422 void WadseekerInterface::showEvent(QShowEvent *event)
423 {
424  Q_UNUSED(event);
425  if (!bFirstShown)
426  {
427  d->taskbarButton->setWindow(windowHandle());
428  bFirstShown = true;
429 
430  if (isAutomatic())
431  startSeeking(seekedWads);
432  }
433 }
434 
435 void WadseekerInterface::serviceStarted(const QString &service)
436 {
437  d->twSites->addService(service);
438 }
439 
440 void WadseekerInterface::serviceFinished(const QString &service)
441 {
442  d->twSites->removeService(service);
443 }
444 
445 void WadseekerInterface::siteFinished(const QUrl &site)
446 {
447  d->twSites->removeUrl(site);
448  displayMessage("Site finished: " + site.toString(), WadseekerLib::Notice, false);
449 }
450 
451 void WadseekerInterface::siteProgress(const QUrl &site, qint64 bytes, qint64 total)
452 {
453  d->twSites->setUrlProgress(site, bytes, total);
454 }
455 
456 void WadseekerInterface::siteRedirect(const QUrl &oldUrl, const QUrl &newUrl)
457 {
458  d->twSites->removeUrl(oldUrl);
459  d->twSites->addUrl(newUrl);
460  displayMessage("Site redirect: " + oldUrl.toString() + " -> " + newUrl.toString(), WadseekerLib::Notice, false);
461 }
462 
463 void WadseekerInterface::siteStarted(const QUrl &site)
464 {
465  d->twSites->addUrl(site);
466  displayMessage("Site started: " + site.toString(), WadseekerLib::Notice, false);
467 }
468 
469 void WadseekerInterface::startSeeking(const QList<PWad> &seekedFilesList)
470 {
471  if (seekedFilesList.isEmpty())
472  return;
473  d->bCompletedSuccessfully = false;
474 
475  ModSet listWads;
476  for (PWad seekedFile : seekedFilesList)
477  {
478  listWads.addModFile(seekedFile);
479  }
480 
481  setupIdgames();
482 
483  wadseeker.setTargetDirectory(gConfig.wadseeker.targetDirectory);
484  wadseeker.setCustomSites(customSites);
485  wadseeker.setMaximumConcurrentSeeks(gConfig.wadseeker.maxConcurrentSiteDownloads);
486  wadseeker.setMaximumConcurrentDownloads(gConfig.wadseeker.maxConcurrentWadDownloads);
487  wadseeker.startSeek(listWads);
488 }
489 
490 void WadseekerInterface::updateProgressBar()
491 {
492  double totalPercentage = d->twWads->totalDonePercentage();
493  auto progressBarValue = (unsigned)(totalPercentage * 100.0);
494 
495  d->pbOverallProgress->setValue(progressBarValue);
496  d->taskbarProgress->setValue(progressBarValue);
497 }
498 
499 void WadseekerInterface::updateTitle()
500 {
501  switch (state)
502  {
503  case Downloading:
504  {
505  double totalPercentage = d->twWads->totalDonePercentage();
506  if (totalPercentage < 0.0)
507  totalPercentage = 0.0;
508 
509  setWindowTitle(tr("[%1%] Wadseeker").arg(totalPercentage, 6, 'f', 2));
510  break;
511  }
512 
513  default:
514  case Waiting:
515  resetTitleToDefault();
516  break;
517  }
518 }
519 
520 QList<PWad> WadseekerInterface::unsuccessfulWads() const
521 {
522  QList<PWad> allWads = seekedWads;
523  for (PWad successfulWad : successfulWads)
524  {
525  for (int i = 0; i < allWads.size(); ++i)
526  {
527  if (allWads[i].name() == successfulWad.name())
528  {
529  allWads.removeAt(i);
530  break;
531  }
532  }
533  }
534  return allWads;
535 }
536 
537 void WadseekerInterface::wadsTableRightClicked(const QModelIndex &index, const QPoint &cursorPosition)
538 {
539  WadseekerWadsTable::ContextMenu *menu = d->twWads->contextMenu(index, cursorPosition);
540 
541  // Disable actions depending on Wadseeker's state.
542  QString fileName = d->twWads->fileNameAtRow(index.row());
543  if (!wadseeker.isDownloadingFile(fileName))
544  menu->actionSkipCurrentSite->setEnabled(false);
545 
546  QAction *pResult = menu->exec();
547 
548  if (pResult == menu->actionSkipCurrentSite)
549  {
550  QString wadName = d->twWads->fileNameAtRow(index.row());
551  d->twWads->setFileUrl(fileName, QUrl());
552 
553  wadseeker.skipFileCurrentUrl(wadName);
554  }
555  else if (pResult != nullptr)
556  QMessageBox::warning(this, tr("Context menu error"), tr("Unknown action selected."));
557 
558  delete menu;
559 }