ip2cupdater.cpp
1 //------------------------------------------------------------------------------
2 // ip2cupdater.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 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "ip2cupdater.h"
24 
25 #include <QCryptographicHash>
26 #include <QFile>
27 #include <QNetworkAccessManager>
28 #include <QTemporaryFile>
29 #include <zlib.h>
30 
31 #include "log.h"
32 #include "version.h"
33 
34 IP2CUpdater::IP2CUpdater(QObject *parent)
35  : QObject(parent)
36 {
37  pCurrentNetworkReply = NULL;
38  pNetworkAccessManager = new QNetworkAccessManager();
39 }
40 
41 IP2CUpdater::~IP2CUpdater()
42 {
43  abort();
44 
45  if (pNetworkAccessManager != NULL)
46  {
47  pNetworkAccessManager->deleteLater();
48  }
49 }
50 
51 void IP2CUpdater::abort()
52 {
53  if (pCurrentNetworkReply != NULL)
54  {
55  pCurrentNetworkReply->disconnect();
56  pCurrentNetworkReply->abort();
57  pCurrentNetworkReply->deleteLater();
58  }
59  pCurrentNetworkReply = NULL;
60 }
61 
62 void IP2CUpdater::checksumDownloadFinished()
63 {
64  if (handleRedirect(*pCurrentNetworkReply, SLOT(checksumDownloadFinished())))
65  {
66  return;
67  }
68 
69  if (pCurrentNetworkReply->error() != QNetworkReply::NoError)
70  {
71  gLog << tr("IP2C checksum check network error: %1").arg(pCurrentNetworkReply->errorString());
72  abort();
73  emit updateNeeded(UpdateCheckError);
74  return;
75  }
76 
77  QByteArray remoteMd5 = pCurrentNetworkReply->readAll();
78  remoteMd5 = remoteMd5.trimmed();
79 
80  pCurrentNetworkReply->deleteLater();
81  pCurrentNetworkReply = NULL;
82 
83  QByteArray localMd5;
84  QFile file(this->lastAsyncCallPath);
85  if (file.open(QIODevice::ReadOnly))
86  {
87  localMd5 = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
88  localMd5 = localMd5.toHex().toLower();
89  }
90 
91  gLog << tr("Comparing IP2C hashes: local = %1, remote = %2").arg(
92  QString(localMd5)).arg(QString(remoteMd5));
93  bool needed = localMd5 != remoteMd5;
94  if (needed)
95  {
96  gLog << tr("IP2C update needed.");
97  }
98  emit updateNeeded(needed ? UpdateNeeded : UpToDate);
99 }
100 
101 const QUrl IP2CUpdater::dbChecksumUrl()
102 {
103  return QUrl("https://doomseeker.drdteam.org/ip2c/md5");
104 }
105 
106 const QUrl IP2CUpdater::dbDownloadUrl()
107 {
108  return QUrl("https://doomseeker.drdteam.org/ip2c/geolite2.gz");
109 }
110 
111 void IP2CUpdater::downloadDatabase(const QString &savePath)
112 {
113  this->lastAsyncCallPath = savePath;
114  get(dbDownloadUrl(), SLOT(downloadFinished()));
115 }
116 
117 void IP2CUpdater::downloadFinished()
118 {
119  if (handleRedirect(*pCurrentNetworkReply, SLOT(downloadFinished())))
120  {
121  return;
122  }
123 
124  QByteArray data = pCurrentNetworkReply->readAll();
125 
126  pCurrentNetworkReply->deleteLater();
127  pCurrentNetworkReply = NULL;
128 
129  // First we need to write it to a temporary file
130  QTemporaryFile tmpFile;
131  if(tmpFile.open())
132  {
133  tmpFile.write(data);
134 
135  QString tmpFilePath = tmpFile.fileName();
136 
137  QByteArray uncompressedData;
138  gzFile gz = gzopen(tmpFilePath.toUtf8().constData(), "rb");
139  if(gz != NULL)
140  {
141  static const int CHUNK_SIZE = 128 * 1024;
142  char chunk[CHUNK_SIZE];
143  int bytesRead = 0;
144  while((bytesRead = gzread(gz, chunk, CHUNK_SIZE)) != 0)
145  {
146  uncompressedData.append(QByteArray(chunk, bytesRead));
147  }
148  gzclose(gz);
149 
150  retrievedData = uncompressedData;
151  }
152  }
153 
154  emit databaseDownloadFinished(retrievedData);
155 }
156 
157 bool IP2CUpdater::handleRedirect(QNetworkReply &reply, const char *finishedSlot)
158 {
159  QUrl possibleRedirectUrl = reply.attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
160  QUrl url = reply.request().url();
161  if (!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != url)
162  {
163  // Redirect.
164  if (possibleRedirectUrl.isRelative())
165  {
166  possibleRedirectUrl = url.resolved(possibleRedirectUrl);
167  }
168  get(possibleRedirectUrl, finishedSlot);
169  return true;
170  }
171  return false;
172 }
173 
174 void IP2CUpdater::get(const QUrl &url, const char *finishedSlot)
175 {
176  abort();
177  retrievedData.clear();
178 
179  QNetworkRequest request;
180  request.setUrl(url);
181  request.setRawHeader("User-Agent", Version::userAgent().toUtf8());
182 
183  pCurrentNetworkReply = pNetworkAccessManager->get(request);
184  this->connect(pCurrentNetworkReply, SIGNAL(downloadProgress(qint64, qint64)),
185  SIGNAL(downloadProgress(qint64, qint64)));
186  this->connect(pCurrentNetworkReply, SIGNAL(finished()), finishedSlot);
187 }
188 
189 bool IP2CUpdater::getRollbackData(const QString &databasePath)
190 {
191  rollbackData.clear();
192  QFile file(databasePath);
193  if (file.open(QIODevice::ReadOnly))
194  {
195  rollbackData = file.readAll();
196  file.close();
197  return true;
198  }
199  return false;
200 }
201 
202 bool IP2CUpdater::isWorking() const
203 {
204  return pCurrentNetworkReply != NULL;
205 }
206 
207 void IP2CUpdater::needsUpdate(const QString& filePath)
208 {
209  QFile file(filePath);
210  if (!file.exists())
211  {
212  emit updateNeeded(UpdateNeeded);
213  return;
214  }
215 
216  this->lastAsyncCallPath = filePath;
217  gLog << tr("Checking if IP2C database at '%1' needs updating.").arg(filePath);
218  get(dbChecksumUrl(), SLOT(checksumDownloadFinished()));
219 }
220 
221 bool IP2CUpdater::rollback(const QString &savePath)
222 {
223  bool bSuccess = save(rollbackData, savePath);
224  rollbackData.clear();
225 
226  return bSuccess;
227 }
228 
229 bool IP2CUpdater::save(const QByteArray& saveWhat, const QString &savePath)
230 {
231  if (saveWhat.isEmpty())
232  return false;
233 
234  QFile file(savePath);
235  if (file.open(QIODevice::WriteOnly))
236  {
237  file.write(saveWhat);
238  file.close();
239  return true;
240  }
241  return false;
242 }
243 
244 bool IP2CUpdater::saveDownloadedData(const QString &savePath)
245 {
246  return save(retrievedData, savePath);
247 }
bool saveDownloadedData(const QString &savePath)
Saves recently downloaded data to the specified file.
static QString userAgent()
WWW User Agent used for HTTP communications.
Definition: version.cpp:64
bool getRollbackData(const QString &databasePath)
Obtains rollback data from specified file.
void needsUpdate(const QString &filePath)
Checks if IP2C file must be updated.
bool rollback(const QString &savePath)
Saves rollback data to the specified file. This data must be first obtained through the getRollbackDa...