ip2cloader.cpp
1 //------------------------------------------------------------------------------
2 // ip2cloader.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) 2013 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "ip2cloader.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "doomseekerfilepaths.h"
27 #include "global.h"
28 #include "ip2c/ip2c.h"
29 #include "ip2c/ip2cparser.h"
30 #include "ip2c/ip2cupdater.h"
31 #include "log.h"
32 #include <QElapsedTimer>
33 #include <QFile>
34 #include <QTimer>
35 #include <QThread>
36 
37 namespace
38 {
39 class IP2CParserThread : public QThread
40 {
41  Q_OBJECT
42 
43 public:
44  QString filePath;
45  IP2CParser parser;
46  bool success;
47 
48  IP2CParserThread(QString filePath)
49  : filePath(filePath)
50  {}
51 
52  virtual void run() override
53  {
54  QFile file(filePath);
55  if (file.open(QIODevice::ReadOnly))
56  {
57  QElapsedTimer time;
58  time.start();
59  this->success = parser.parse(file);
60 
61  if (this->success)
62  {
63  gLog << tr("IP2C database read in %1 ms; IP ranges: %2").arg(time.elapsed()).arg(parser.ranges().size());
64  }
65  }
66  else
67  {
68  gLog << tr("Unable to open IP2C file: %1").arg(filePath);
69  this->success = false;
70  }
71  }
72 };
73 }
74 
75 DClass<IP2CLoader>
76 {
77 public:
78  IP2C *ip2c;
79  IP2CParserThread *parserThread;
80  QScopedPointer<IP2CUpdater> ip2cUpdater;
81  bool parsingInProgress;
82  bool updateInProgress;
83  bool inFallbackMode;
84 };
85 
86 DPointeredNoCopy(IP2CLoader)
87 
88 
89 IP2CLoader::IP2CLoader(IP2C &ip2c, QObject *parent)
90  : QObject(parent)
91 {
92  d->ip2c = &ip2c;
93  d->parserThread = nullptr;
94  d->parsingInProgress = false;
95  d->updateInProgress = false;
96  d->inFallbackMode = false;
97 
98  d->ip2cUpdater.reset(new IP2CUpdater());
99  connect(d->ip2cUpdater.data(), &IP2CUpdater::databaseDownloadFinished,
100  this, &IP2CLoader::onUpdateFinished);
101  connect(d->ip2cUpdater.data(), &IP2CUpdater::downloadProgress,
102  this, &IP2CLoader::downloadProgress);
103  connect(d->ip2cUpdater.data(), &IP2CUpdater::updateCheckFinished,
104  this, &IP2CLoader::onUpdateNeeded);
105 }
106 
107 void IP2CLoader::load()
108 {
109  if (gConfig.doomseeker.bIP2CountryAutoUpdate)
110  d->ip2cUpdater->startUpdateCheck(DoomseekerFilePaths::ip2cDatabaseAny());
111 
112  if (d->parsingInProgress)
113  return;
114 
115  QString filePath = DoomseekerFilePaths::ip2cDatabaseAny();
116  if (!filePath.isEmpty())
117  {
118  d->inFallbackMode = false;
119  d->ip2c->setDataAccessLockEnabled(true);
120  ip2cReadDatabase(filePath);
121  }
122  else
123  {
124  if (!gConfig.doomseeker.bIP2CountryAutoUpdate)
125  {
126  gLog << tr("Did not find any IP2C database. IP2C functionality will be disabled.");
127  gLog << tr("You may install an IP2C database from the \"File\" menu.");
128  }
129  ip2cJobsFinished();
130  }
131 }
132 
133 void IP2CLoader::onUpdateNeeded(int status)
134 {
135  if (status == IP2CUpdater::UpdateNeeded)
136  {
137  update();
138  }
139  else
140  {
141  switch (status)
142  {
143  case IP2CUpdater::UpToDate:
144  gLog << tr("IP2C update not needed.");
145  break;
146  case IP2CUpdater::UpdateCheckError:
147  gLog << tr("IP2C update errored. See log for details.");
148  break;
149  default:
150  gLog << tr("IP2C update bugged out.");
151  break;
152  }
153  ip2cJobsFinished();
154  }
155 }
156 
157 void IP2CLoader::update()
158 {
159  d->updateInProgress = true;
160  if (!d->parsingInProgress)
161  {
162  gLog << tr("Starting IP2C update.");
163  IP2C::instance()->setDataAccessLockEnabled(true);
164  d->ip2cUpdater->downloadDatabase(DoomseekerFilePaths::ip2cDatabase());
165  }
166 }
167 
168 void IP2CLoader::onUpdateFinished(const QByteArray &downloadedData)
169 {
170  d->updateInProgress = false;
171  if (!downloadedData.isEmpty())
172  {
173  gLog << tr("IP2C database finished downloading.");
174  QString filePath = DoomseekerFilePaths::ip2cDatabase();
175  d->ip2cUpdater->getRollbackData(filePath);
176  if (!d->ip2cUpdater->saveDownloadedData(filePath))
177  gLog << tr("Unable to save IP2C database at path: %1").arg(filePath);
178  ip2cReadDatabase(filePath);
179  }
180  else
181  {
182  gLog << tr("IP2C download has failed.");
183  ip2cJobsFinished();
184  }
185 }
186 
187 void IP2CLoader::onParsingFinished()
188 {
189  d->parsingInProgress = false;
190  auto finally = [this]() {
191  if (d->updateInProgress) {
192  // Retrigger the update.
193  QTimer::singleShot(0, this, &IP2CLoader::update);
194  }
195  this->ip2cJobsFinished();
196  };
197 
198  IP2CParserThread *parserThread = d->parserThread;
199  if (parserThread == nullptr) {
200  gLog << tr("IP2C unable to retrieve the parsing result.");
201  finally();
202  return;
203  }
204  d->parserThread = nullptr; // object deleted by the deleteLater() slot
205  QString filePath = DoomseekerFilePaths::ip2cDatabase();
206  if (!parserThread->success)
207  {
208  if (d->inFallbackMode)
209  {
210  gLog << tr("Failed to read the IP2C fallback. Stopping.");
211  finally();
212  return;
213  }
214  gLog << tr("Failed to read the IP2C database. Reverting...");
215 
216  d->inFallbackMode = true;
217  if (d->ip2cUpdater == nullptr || !d->ip2cUpdater->hasRollbackData())
218  {
219  gLog << tr("IP2C revert attempt failed. Nothing to go back to.");
220 
221  // Delete file in this case.
222  QFile file(filePath);
223  file.remove();
224 
225  QString preinstalledDbPath = DoomseekerFilePaths::ip2cDatabaseAny();
226  if (!preinstalledDbPath.isEmpty())
227  {
228  gLog << tr("Trying to use the preinstalled IP2C database.");
229  ip2cReadDatabase(preinstalledDbPath);
230  }
231  else
232  {
233  finally();
234  }
235  }
236  else
237  {
238  // Revert to old content.
239  d->ip2cUpdater->rollback(filePath);
240 
241  // Must succeed now.
242  ip2cReadDatabase(filePath);
243  }
244  }
245  else
246  {
247  const IP2CParser &parser = parserThread->parser;
248  gLog << tr("IP2C parsing finished.");
249  d->ip2c->setRangesDatabase(parser.ranges());
250  d->ip2c->setLicence(parser.licences());
251  d->ip2c->setUrl(parser.url());
252  finally();
253  }
254 }
255 
256 void IP2CLoader::ip2cJobsFinished()
257 {
258  if (!d->ip2cUpdater->isWorking() && !d->parsingInProgress && !d->updateInProgress)
259  {
260  d->ip2c->setDataAccessLockEnabled(false);
261  emit finished();
262  }
263 }
264 
265 void IP2CLoader::ip2cReadDatabase(const QString &filePath)
266 {
267  gLog << tr("IP2C database is being read. This may take some time.");
268 
269  if (d->parserThread != nullptr) {
270  d->parserThread->disconnect(this);
271  }
272 
273  d->parsingInProgress = true;
274  d->parserThread = new IP2CParserThread(filePath);
275  connect(d->parserThread, &IP2CParserThread::finished,
276  this, &IP2CLoader::onParsingFinished);
277  connect(d->parserThread, &IP2CParserThread::finished,
278  d->parserThread, &QObject::deleteLater);
279  d->parserThread->start();
280 }
281 
282 #include "ip2cloader.moc"