servatrice/cockatrice/src/spoilerbackgroundupdater.cpp
Zach H d19744236e
Automatic Spoiler Season (#2991)
* 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
2018-01-10 13:27:43 -05:00

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();
}