1 #include "spoilerbackgroundupdater.h"
2
3 #include "carddatabase.h"
4 #include "main.h"
5 #include "settingscache.h"
6 #include "window_main.h"
7
8 #include <QApplication>
9 #include <QCryptographicHash>
10 #include <QDateTime>
11 #include <QDebug>
12 #include <QFile>
13 #include <QLocale>
14 #include <QMessageBox>
15 #include <QNetworkReply>
16 #include <QUrl>
17 #include <QtConcurrent>
18
19 #define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
20 #define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
21
SpoilerBackgroundUpdater(QObject * apParent)22 SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
23 {
24 isSpoilerDownloadEnabled = SettingsCache::instance().getDownloadSpoilersStatus();
25 if (isSpoilerDownloadEnabled) {
26 // Start the process of checking if we're in spoiler season
27 // File exists means we're in spoiler season
28 startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false);
29 } else {
30 qDebug() << "Spoilers Disabled";
31 }
32 }
33
startSpoilerDownloadProcess(QString url,bool saveResults)34 void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool saveResults)
35 {
36 auto spoilerURL = QUrl(url);
37 downloadFromURL(spoilerURL, saveResults);
38 }
39
downloadFromURL(QUrl url,bool saveResults)40 void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
41 {
42 auto *nam = new QNetworkAccessManager(this);
43 QNetworkReply *reply = nam->get(QNetworkRequest(url));
44
45 if (saveResults) {
46 // This will write out to the file (used for spoiler.xml)
47 connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
48 } else {
49 // This will check the status (used to see if we're in spoiler season or not)
50 connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled()));
51 }
52 }
53
actDownloadFinishedSpoilersFile()54 void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
55 {
56 // Check for server reply
57 auto *reply = dynamic_cast<QNetworkReply *>(sender());
58 QNetworkReply::NetworkError errorCode = reply->error();
59
60 if (errorCode == QNetworkReply::NoError) {
61 spoilerData = reply->readAll();
62
63 // Save the spoiler.xml file to the disk
64 saveDownloadedFile(spoilerData);
65
66 reply->deleteLater();
67 emit spoilerCheckerDone();
68 } else {
69 qDebug() << "Error downloading spoilers file" << errorCode;
70 emit spoilerCheckerDone();
71 }
72 }
73
deleteSpoilerFile()74 bool SpoilerBackgroundUpdater::deleteSpoilerFile()
75 {
76 QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
77 QFileInfo fi(fileName);
78 QDir fileDir(fi.path());
79 QFile file(fileName);
80
81 // Delete the spoiler.xml file
82 if (file.exists() && file.remove()) {
83 qDebug() << "Deleting spoiler.xml";
84 return true;
85 }
86
87 qDebug() << "Error: Spoiler.xml not found or not deleted";
88 return false;
89 }
90
actCheckIfSpoilerSeasonEnabled()91 void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
92 {
93 auto *response = dynamic_cast<QNetworkReply *>(sender());
94 QNetworkReply::NetworkError errorCode = response->error();
95
96 if (errorCode == QNetworkReply::ContentNotFoundError) {
97 // Spoiler season is offline at this point, so the spoiler.xml file can be safely deleted
98 // The user should run Oracle to get the latest card information
99 if (deleteSpoilerFile() && trayIcon) {
100 trayIcon->showMessage(tr("Spoilers season has ended"), tr("Deleting spoiler.xml. Please run Oracle"));
101 }
102
103 qDebug() << "Spoiler Season Offline";
104 emit spoilerCheckerDone();
105 } else if (errorCode == QNetworkReply::NoError) {
106 qDebug() << "Spoiler Service Online";
107 startSpoilerDownloadProcess(SPOILERS_URL, true);
108 } else if (errorCode == QNetworkReply::HostNotFoundError) {
109 if (trayIcon) {
110 trayIcon->showMessage(tr("Spoilers download failed"), tr("No internet connection"));
111 }
112
113 qDebug() << "Spoiler download failed due to no internet connection";
114 emit spoilerCheckerDone();
115 } else {
116 if (trayIcon) {
117 trayIcon->showMessage(tr("Spoilers download failed"), tr("Error") + " " + errorCode);
118 }
119
120 qDebug() << "Spoiler download failed with reason" << errorCode;
121 emit spoilerCheckerDone();
122 }
123 }
124
saveDownloadedFile(QByteArray data)125 bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
126 {
127 QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
128 QFileInfo fi(fileName);
129 QDir fileDir(fi.path());
130
131 if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
132 return false;
133 }
134
135 // Check if the data matches. If it does, then spoilers are up to date.
136 if (getHash(fileName) == getHash(data)) {
137 if (trayIcon) {
138 trayIcon->showMessage(tr("Spoilers already up to date"), tr("No new spoilers added"));
139 }
140
141 qDebug() << "Spoilers Up to Date";
142 return false;
143 }
144
145 QFile file(fileName);
146 if (!file.open(QIODevice::WriteOnly)) {
147 qDebug() << "Spoiler Service Error: File open (w) failed for" << fileName;
148 file.close();
149 return false;
150 }
151
152 if (file.write(data) == -1) {
153 qDebug() << "Spoiler Service Error: File write (w) failed for" << fileName;
154 file.close();
155 return false;
156 }
157
158 file.close();
159
160 // Data written, so reload the card database
161 qDebug() << "Spoiler Service Data Written";
162 QtConcurrent::run(db, &CardDatabase::loadCardDatabases);
163
164 // If the user has notifications enabled, let them know
165 // when the database was last updated
166 if (trayIcon) {
167 QList<QByteArray> lines = data.split('\n');
168
169 foreach (QByteArray line, lines) {
170 if (line.contains("Created At:")) {
171 QString timeStamp = QString(line).replace("Created At:", "").trimmed();
172 timeStamp.chop(6); // Remove " (UTC)"
173
174 auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss");
175 utcTime.setTimeSpec(Qt::UTC);
176
177 QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");
178
179 trayIcon->showMessage(tr("Spoilers have been updated!"), tr("Last change:") + " " + localTime);
180 emit spoilersUpdatedSuccessfully();
181 return true;
182 }
183 }
184 }
185
186 return true;
187 }
188
getHash(const QString fileName)189 QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName)
190 {
191 QFile file(fileName);
192
193 if (file.open(QFile::ReadOnly)) {
194 // Only read the first 512 bytes (enough to get the "created" tag)
195 const QByteArray bytes = file.read(512);
196
197 QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
198 hash.addData(bytes);
199
200 qDebug() << "File Hash =" << hash.result();
201
202 file.close();
203 return hash.result();
204 } else {
205 qDebug() << "getHash ReadOnly failed!";
206 file.close();
207 return QByteArray();
208 }
209 }
210
getHash(QByteArray data)211 QByteArray SpoilerBackgroundUpdater::getHash(QByteArray data)
212 {
213 // Only read the first 512 bytes (enough to get the "created" tag)
214 const QByteArray bytes = data.left(512);
215
216 QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
217 hash.addData(bytes);
218
219 qDebug() << "Data Hash =" << hash.result();
220
221 return hash.result();
222 }
223