* oracle now can be run in spoiler or normal mode * tests for travis * only run on relaunch * spoilers in client (not oracle now) and tray icon shows when done * spoiler status will be checked before downloading spoiler file * only download if they care about spoilers * reload db on spoiler download * manual update button, code cleanup, and fix enabling sets when new * cleanup, nullchecks, and fixes to spoiler * reload DB even if not in spoiler season; necessary as we have a check elsewhere to prevent the reload if spoiler check happens * Implement changes from 2991#issuecomment-356169374 * Change implicit nullptrs, alert on file deletion, minor changes * make reload thread safe and minor changes from 2991#issuecomment-356450302 * Fix locking * Disable update now button while process running
251 lines
7.3 KiB
C++
251 lines
7.3 KiB
C++
#include <QDateTime>
|
|
#include <QDebug>
|
|
#include <QUrl>
|
|
#include <QNetworkReply>
|
|
#include <QMessageBox>
|
|
#include <QFile>
|
|
#include <QApplication>
|
|
#include <QtConcurrent>
|
|
#include <QCryptographicHash>
|
|
|
|
#include "spoilerbackgroundupdater.h"
|
|
#include "settingscache.h"
|
|
#include "carddatabase.h"
|
|
#include "main.h"
|
|
#include "window_main.h"
|
|
|
|
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
|
|
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
|
|
|
|
SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
|
|
{
|
|
isSpoilerDownloadEnabled = settingsCache->getDownloadSpoilersStatus();
|
|
if (isSpoilerDownloadEnabled)
|
|
{
|
|
// Start the process of checking if we're in spoiler season
|
|
// File exists means we're in spoiler season
|
|
// We will load the database before attempting to download spoilers, incase they fail
|
|
QtConcurrent::run(db, &CardDatabase::loadCardDatabases);
|
|
startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false);
|
|
}
|
|
}
|
|
|
|
void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool saveResults)
|
|
{
|
|
auto spoilerURL = QUrl(url);
|
|
downloadFromURL(spoilerURL, saveResults);
|
|
}
|
|
|
|
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
|
|
{
|
|
auto *nam = new QNetworkAccessManager(this);
|
|
QNetworkReply *reply = nam->get(QNetworkRequest(url));
|
|
|
|
if (saveResults)
|
|
{
|
|
// This will write out to the file (used for spoiler.xml)
|
|
connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
|
|
}
|
|
else
|
|
{
|
|
// This will check the status (used to see if we're in spoiler season or not)
|
|
connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled()));
|
|
}
|
|
}
|
|
|
|
void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
|
|
{
|
|
// Check for server reply
|
|
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
|
QNetworkReply::NetworkError errorCode = reply->error();
|
|
|
|
if (errorCode == QNetworkReply::NoError)
|
|
{
|
|
spoilerData = reply->readAll();
|
|
|
|
// Save the spoiler.xml file to the disk
|
|
saveDownloadedFile(spoilerData);
|
|
|
|
reply->deleteLater();
|
|
emit spoilerCheckerDone();
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "Error downloading spoilers file" << errorCode;
|
|
emit spoilerCheckerDone();
|
|
}
|
|
}
|
|
|
|
bool SpoilerBackgroundUpdater::deleteSpoilerFile()
|
|
{
|
|
QString fileName = settingsCache->getSpoilerCardDatabasePath();
|
|
QFileInfo fi(fileName);
|
|
QDir fileDir(fi.path());
|
|
QFile file(fileName);
|
|
|
|
// Delete the spoiler.xml file
|
|
if (file.exists() && file.remove())
|
|
{
|
|
qDebug() << "Deleting spoiler.xml";
|
|
return true;
|
|
|
|
}
|
|
|
|
qDebug() << "Error: Spoiler.xml not found or not deleted";
|
|
return false;
|
|
}
|
|
|
|
void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
|
|
{
|
|
auto *response = dynamic_cast<QNetworkReply *>(sender());
|
|
QNetworkReply::NetworkError errorCode = response->error();
|
|
|
|
if (errorCode == QNetworkReply::ContentNotFoundError)
|
|
{
|
|
// Spoiler season is offline at this point, so the spoiler.xml file can be safely deleted
|
|
// The user should run Oracle to get the latest card information
|
|
if (deleteSpoilerFile() && trayIcon)
|
|
{
|
|
trayIcon->showMessage(tr("Spoilers season has ended"), tr("Deleting spoiler.xml. Please run Oracle"));
|
|
}
|
|
|
|
qDebug() << "Spoiler Season Offline";
|
|
emit spoilerCheckerDone();
|
|
}
|
|
else if (errorCode == QNetworkReply::NoError)
|
|
{
|
|
qDebug() << "Spoiler Service Online";
|
|
startSpoilerDownloadProcess(SPOILERS_URL, true);
|
|
}
|
|
else if (errorCode == QNetworkReply::HostNotFoundError)
|
|
{
|
|
if (trayIcon)
|
|
{
|
|
trayIcon->showMessage(tr("Spoilers download failed"), tr("No internet connection"));
|
|
}
|
|
|
|
qDebug() << "Spoiler download failed due to no internet connection";
|
|
emit spoilerCheckerDone();
|
|
}
|
|
else
|
|
{
|
|
if (trayIcon)
|
|
{
|
|
trayIcon->showMessage(tr("Spoilers download failed"), tr("Error") + " " + errorCode);
|
|
}
|
|
|
|
qDebug() << "Spoiler download failed with reason" << errorCode;
|
|
emit spoilerCheckerDone();
|
|
}
|
|
}
|
|
|
|
bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
|
{
|
|
QString fileName = settingsCache->getSpoilerCardDatabasePath();
|
|
QFileInfo fi(fileName);
|
|
QDir fileDir(fi.path());
|
|
|
|
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check if the data matches. If it does, then spoilers are up to date.
|
|
if (getHash(fileName) == getHash(data))
|
|
{
|
|
if (trayIcon)
|
|
{
|
|
trayIcon->showMessage(tr("Spoilers already up to date"), tr("No new spoilers added"));
|
|
}
|
|
|
|
qDebug() << "Spoilers Up to Date";
|
|
return false;
|
|
}
|
|
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
{
|
|
qDebug() << "Spoiler Service Error: File open (w) failed for" << fileName;
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
if (file.write(data) == -1)
|
|
{
|
|
qDebug() << "Spoiler Service Error: File write (w) failed for" << fileName;
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
file.close();
|
|
|
|
|
|
// Data written, so reload the card database
|
|
qDebug() << "Spoiler Service Data Written";
|
|
QtConcurrent::run(db, &CardDatabase::loadCardDatabases);
|
|
|
|
// If the user has notifications enabled, let them know
|
|
// when the database was last updated
|
|
if (trayIcon)
|
|
{
|
|
QList<QByteArray> lines = data.split('\n');
|
|
|
|
foreach (QByteArray line, lines)
|
|
{
|
|
if (line.indexOf("created:") > -1)
|
|
{
|
|
QString timeStamp = QString(line).replace("created:", "").trimmed();
|
|
timeStamp.chop(6); // Remove " (UTC)"
|
|
|
|
auto utcTime = QDateTime::fromString(timeStamp, QString("ddd, MMM dd yyyy, hh:mm:ss"));
|
|
utcTime.setTimeSpec(Qt::UTC);
|
|
|
|
QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");
|
|
|
|
trayIcon->showMessage(tr("Spoilers have been updated!"), tr("Last change:") + " " + localTime);
|
|
emit spoilersUpdatedSuccessfully();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName)
|
|
{
|
|
QFile file(fileName);
|
|
|
|
if (file.open(QFile::ReadOnly))
|
|
{
|
|
// Only read the first 512 bytes (enough to get the "created" tag)
|
|
const QByteArray bytes = file.read(512);
|
|
|
|
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
|
hash.addData(bytes);
|
|
|
|
qDebug() << "File Hash =" << hash.result();
|
|
|
|
file.close();
|
|
return hash.result();
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "getHash ReadOnly failed!";
|
|
file.close();
|
|
return QByteArray();
|
|
}
|
|
}
|
|
|
|
QByteArray SpoilerBackgroundUpdater::getHash(QByteArray data)
|
|
{
|
|
// Only read the first 512 bytes (enough to get the "created" tag)
|
|
const QByteArray bytes = data.left(512);
|
|
|
|
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
|
hash.addData(bytes);
|
|
|
|
qDebug() << "Data Hash =" << hash.result();
|
|
|
|
return hash.result();
|
|
}
|