23 #include "gui/wadseekerinterface.h"
24 #include "ui_wadseekerinterface.h"
26 #include "application.h"
27 #include "configuration/doomseekerconfig.h"
28 #include "gui/commongui.h"
29 #include "gui/helpers/taskbarbutton.h"
30 #include "gui/helpers/taskbarprogress.h"
31 #include "mainwindow.h"
32 #include "serverapi/server.h"
34 #include "strings.hpp"
35 #include "templatedpathresolver.h"
36 #include "wadseeker/entities/checksum.h"
37 #include "wadseeker/entities/modfile.h"
38 #include "wadseeker/entities/modset.h"
40 #include <QFontDatabase>
41 #include <QMessageBox>
42 #include <QPushButton>
59 LogLevel operator[](
const WadseekerLib::MessageType type)
const
61 return this->map.value(type, this->unknown);
64 void insert(WadseekerLib::MessageType type, LogLevel level)
66 this->map.insert(type, level);
71 for (LogLevel &lvl : this->map)
72 lvl.tag = lvl.tag.trimmed();
74 for (
const LogLevel &lvl : this->map)
75 longest = std::max<int>(lvl.tag.length(), longest);
76 for (LogLevel &lvl : this->map)
78 while (lvl.tag.length() < longest)
81 this->unknown = LogLevel{ QString(longest,
'?'), QString() };
85 QMap<WadseekerLib::MessageType, LogLevel> map;
90 const int WadseekerInterface::UPDATE_INTERVAL_MS = 500;
93 DClass<WadseekerInterface> :
public Ui::WadseekerInterface
96 QPushButton *btnAbort;
97 QPushButton *btnClose;
100 LogLevelMap loglevels;
102 bool bCompletedSuccessfully;
117 WadseekerInterface::WadseekerInterface(ServerPtr server, QWidget *parent)
123 d->lblTop->setText(tr(
"Downloading WADs for server \"%1\"").arg(server->name()));
124 setCustomSites(server->allWebSites());
127 WadseekerInterface::~WadseekerInterface()
129 currentInstance =
nullptr;
132 void WadseekerInterface::abortService(
const QString &service)
134 appendLog(tr(
"Aborting service: %1").arg(service), WadseekerLib::Notice, d->name);
135 wadseeker.skipService(service);
138 void WadseekerInterface::abortSite(
const QUrl &url)
140 appendLog(tr(
"Aborting site: %1").arg(url.toString()), WadseekerLib::Notice, d->name);
141 wadseeker.skipSiteSeek(url);
144 void WadseekerInterface::accept()
148 if (d->bCompletedSuccessfully)
149 done(QDialog::Accepted);
153 if (d->leWadName->text().isEmpty())
158 QStringList pwadNames = d->leWadName->text().split(
',', Qt::SkipEmptyParts);
159 for (QString pwadName : pwadNames)
161 seekedWads << pwadName.trimmed();
164 startSeeking(seekedWads);
168 void WadseekerInterface::appendLog(
const QString &message, WadseekerLib::MessageType type,
const QString &source)
170 static const int MAX_SOURCE_LEN = QString(
"Doomseeker").length();
171 static const QString lineStart =
"<span style=\"%1; white-space: pre-wrap;\">";
172 static const QString lineEnd =
"</span><br>\n";
173 const LogLevel level = d->loglevels[type];
176 for (
const QString &line : message.split(
"\n"))
178 log += lineStart.arg(level.style)
179 + QString(
"%1|%2| %3").arg(source.left(MAX_SOURCE_LEN), -MAX_SOURCE_LEN).arg(level.tag, line)
183 d->teWadseekerOutput->moveCursor(QTextCursor::End);
184 d->teWadseekerOutput->insertHtml(log);
187 void WadseekerInterface::allDone(
bool bSuccess)
190 d->bCompletedSuccessfully = bSuccess;
191 QApplication::alert(
this);
194 appendLog(tr(
"All done. Success."), WadseekerLib::NoticeImportant, d->name);
196 if (isAutomatic() && !d->preventGame)
198 if (isActiveWindow())
199 done(QDialog::Accepted);
201 d->btnStartGame->show();
206 QList<PWad> failures = unsuccessfulWads();
208 for (
const PWad &failure : failures)
210 d->twWads->setFileFailed(failure.name());
213 appendLog(tr(
"All done. Fail."), WadseekerLib::CriticalError, d->name);
217 void WadseekerInterface::connectWadseekerObject()
220 connect(&wadseeker, &Wadseeker::allDone,
221 this, &WadseekerInterface::allDone);
222 connect(&wadseeker, &Wadseeker::message,
223 this, [
this](
const QString &msg, WadseekerLib::MessageType type)
224 { this->appendLog(msg, type, tr(
"Wadseeker")); });
225 connect(&wadseeker, &Wadseeker::seekStarted,
226 this, &WadseekerInterface::seekStarted);
227 connect(&wadseeker, &Wadseeker::fileInstalled,
228 this, &WadseekerInterface::fileDownloadSuccessful);
229 connect(&wadseeker, &Wadseeker::siteFinished,
230 this, &WadseekerInterface::siteFinished);
231 connect(&wadseeker, &Wadseeker::siteProgress,
232 this, &WadseekerInterface::siteProgress);
233 connect(&wadseeker, &Wadseeker::siteRedirect,
234 this, &WadseekerInterface::siteRedirect);
235 connect(&wadseeker, &Wadseeker::siteStarted,
236 this, &WadseekerInterface::siteStarted);
237 connect(&wadseeker, &Wadseeker::serviceStarted,
238 this, &WadseekerInterface::serviceStarted);
239 connect(&wadseeker, &Wadseeker::serviceFinished,
240 this, &WadseekerInterface::serviceFinished);
243 connect(&wadseeker, &Wadseeker::fileDownloadFinished,
244 d->twWads, &WadseekerWadsTable::setFileDownloadFinished);
245 connect(&wadseeker, &Wadseeker::fileDownloadProgress,
246 d->twWads, &WadseekerWadsTable::setFileProgress);
247 connect(&wadseeker, &Wadseeker::fileDownloadStarted,
251 void WadseekerInterface::construct()
255 d->name =
"Doomseeker";
257 d->teWadseekerOutput->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
258 d->preventGame =
false;
259 d->bCompletedSuccessfully =
false;
260 d->btnAbort = d->buttonBox->button(QDialogButtonBox::Abort);
261 d->btnClose = d->buttonBox->button(QDialogButtonBox::Close);
265 d->taskbarProgress = d->taskbarButton->progress();
266 d->taskbarProgress->setMaximum(d->pbOverallProgress->maximum());
272 this->setWindowIcon(QIcon(
":/icon.png"));
273 d->btnStartGame->hide();
274 connect(&this->updateTimer, &QTimer::timeout,
275 this, &WadseekerInterface::registerUpdateRequest);
277 connectWadseekerObject();
280 connect(d->twWads, &QWidget::customContextMenuRequested,
281 this, &WadseekerInterface::showWadsTableContextMenu);
286 QStringList urlList = gConfig.wadseeker.searchURLs;
287 if (gConfig.wadseeker.bAlwaysUseDefaultSites)
289 for (
int i = 0; !Wadseeker::defaultSites[i].isEmpty(); ++i)
290 urlList << Wadseeker::defaultSites[i];
293 wadseeker.setPrimarySites(urlList);
295 updateTimer.setSingleShot(
false);
296 updateTimer.start(UPDATE_INTERVAL_MS);
301 if (!isInstantiated())
304 return currentInstance;
311 if (!isInstantiated())
314 return currentInstance;
322 if (interface !=
nullptr)
324 interface->setupAutomatic();
325 interface->d->preventGame =
true;
330 void WadseekerInterface::fileDownloadSuccessful(
const ModFile &filename)
332 successfulWads << filename;
333 d->twWads->setFileSuccessful(filename.fileName());
336 void WadseekerInterface::initMessageColors()
338 this->colorHtmlMessageNotice = gConfig.wadseeker.colorMessageNotice;
339 this->colorHtmlMessageError = gConfig.wadseeker.colorMessageError;
340 this->colorHtmlMessageFatalError = gConfig.wadseeker.colorMessageCriticalError;
341 this->colorHtmlMessageNavigation = gConfig.wadseeker.colorMessageNavigation;
343 d->loglevels = LogLevelMap();
348 d->loglevels.insert(WadseekerLib::CriticalError, {
350 QString(
"color: %1; font-weight: bold;").arg(colorHtmlMessageFatalError),
352 d->loglevels.insert(WadseekerLib::Error, {
354 QString(
"color: %1;").arg(colorHtmlMessageError),
356 d->loglevels.insert(WadseekerLib::Notice, {
358 QString(
"color: %1;").arg(colorHtmlMessageNotice),
360 d->loglevels.insert(WadseekerLib::NoticeImportant, {
362 QString(
"color: %1; font-weight: bold;").arg(colorHtmlMessageNotice),
364 d->loglevels.insert(WadseekerLib::Navigation, {
366 QString(
"color: %1;").arg(colorHtmlMessageNavigation),
368 d->loglevels.refit();
371 bool WadseekerInterface::isInstantiated()
373 return currentInstance !=
nullptr;
376 void WadseekerInterface::registerUpdateRequest()
382 void WadseekerInterface::reject()
391 this->done(Rejected);
396 void WadseekerInterface::resetTitleToDefault()
398 setWindowTitle(tr(
"Wadseeker"));
401 void WadseekerInterface::seekStarted(
const ModSet &filenames)
405 for (ModFile modFile : filenames.modFiles())
408 names << modFile.fileName();
410 d->teWadseekerOutput->clear();
411 d->pbOverallProgress->setValue(0);
412 d->taskbarProgress->setValue(0);
413 appendLog(
"Seek started on filenames: " + names.join(
", "), WadseekerLib::NoticeImportant, d->name);
416 successfulWads.clear();
417 d->twSites->setRowCount(0);
418 d->twWads->setRowCount(0);
419 setStateDownloading();
421 for (
const PWad &wad : seekedWads)
423 d->twWads->addFile(wad.name());
427 void WadseekerInterface::setStateDownloading()
431 d->btnDownload->setEnabled(
false);
432 d->taskbarProgress->show();
436 void WadseekerInterface::setStateWaiting()
440 d->btnDownload->setEnabled(
true);
441 d->taskbarProgress->hide();
445 void WadseekerInterface::setupAutomatic()
449 d->btnDownload->hide();
450 d->leWadName->hide();
459 for (
PWad wad : wads)
463 d->leWadName->setText(names.join(
", "));
467 void WadseekerInterface::setupIdgames()
469 wadseeker.setIdgamesEnabled(gConfig.wadseeker.bSearchInIdgames);
470 wadseeker.setIdgamesUrl(gConfig.wadseeker.idgamesURL);
473 void WadseekerInterface::showEvent(QShowEvent *event)
478 d->taskbarButton->setWindow(windowHandle());
482 startSeeking(seekedWads);
486 void WadseekerInterface::serviceStarted(
const QString &service)
488 d->twSites->addService(service);
491 void WadseekerInterface::serviceFinished(
const QString &service)
493 d->twSites->removeService(service);
496 void WadseekerInterface::siteFinished(
const QUrl &site)
498 d->twSites->removeUrl(site);
501 void WadseekerInterface::siteProgress(
const QUrl &site, qint64 bytes, qint64 total)
503 d->twSites->setUrlProgress(site, bytes, total);
506 void WadseekerInterface::siteRedirect(
const QUrl &oldUrl,
const QUrl &newUrl)
508 d->twSites->removeUrl(oldUrl);
509 d->twSites->addUrl(newUrl);
512 void WadseekerInterface::siteStarted(
const QUrl &site)
514 d->twSites->addUrl(site);
517 void WadseekerInterface::startSeeking(
const QList<PWad> &seekedFilesList)
519 if (seekedFilesList.isEmpty())
521 d->bCompletedSuccessfully =
false;
524 for (
PWad seekedFile : seekedFilesList)
526 listWads.addModFile(seekedFile);
532 wadseeker.setCustomSites(customSites);
533 wadseeker.setMaximumConcurrentSeeks(gConfig.wadseeker.maxConcurrentSiteDownloads);
534 wadseeker.setMaximumConcurrentDownloads(gConfig.wadseeker.maxConcurrentWadDownloads);
535 wadseeker.startSeek(listWads);
538 void WadseekerInterface::updateProgressBar()
540 double totalPercentage = d->twWads->totalDonePercentage();
541 auto progressBarValue = (unsigned)(totalPercentage * 100.0);
543 d->pbOverallProgress->setValue(progressBarValue);
544 d->taskbarProgress->setValue(progressBarValue);
547 void WadseekerInterface::updateTitle()
553 double totalPercentage = d->twWads->totalDonePercentage();
554 if (totalPercentage < 0.0)
555 totalPercentage = 0.0;
557 setWindowTitle(tr(
"[%1%] Wadseeker").arg(totalPercentage, 6,
'f', 2));
563 resetTitleToDefault();
568 QList<PWad> WadseekerInterface::unsuccessfulWads()
const
570 QList<PWad> allWads = seekedWads;
571 for (
PWad successfulWad : successfulWads)
573 for (
int i = 0; i < allWads.size(); ++i)
575 if (allWads[i].name() == successfulWad.name())
585 void WadseekerInterface::showWadsTableContextMenu(
const QPoint &position)
587 const QModelIndex index = d->twWads->indexAt(position);
591 QString fileName = d->twWads->fileNameAtRow(index.row());
592 if (!wadseeker.isDownloadingFile(fileName))
593 menu->actionSkipCurrentSite->setEnabled(
false);
595 QAction *pResult = menu->exec();
597 if (pResult == menu->actionSkipCurrentSite)
599 QString wadName = d->twWads->fileNameAtRow(index.row());
600 d->twWads->setFileUrl(fileName, QUrl());
602 wadseeker.skipFileCurrentUrl(wadName);
604 else if (pResult !=
nullptr)
605 QMessageBox::warning(
this, tr(
"Context menu error"), tr(
"Unknown action selected."));