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 updateInProgress;
82  bool inFallbackMode;
83 
84  bool isParsing() const
85  {
86  return parserThread != nullptr && parserThread->isRunning();
87  }
88 };
89 
90 DPointeredNoCopy(IP2CLoader)
91 
92 
93 IP2CLoader::IP2CLoader(IP2C &ip2c, QObject *parent)
94  : QObject(parent)
95 {
96  d->ip2c = &ip2c;
97  d->parserThread = nullptr;
98  d->updateInProgress = false;
99  d->inFallbackMode = false;
100 
101  d->ip2cUpdater.reset(new IP2CUpdater());
102  connect(d->ip2cUpdater.data(), &IP2CUpdater::databaseDownloadFinished,
103  this, &IP2CLoader::onUpdateFinished);
104  connect(d->ip2cUpdater.data(), &IP2CUpdater::downloadProgress,
105  this, &IP2CLoader::downloadProgress);
106  connect(d->ip2cUpdater.data(), &IP2CUpdater::updateNeeded,
107  this, &IP2CLoader::onUpdateNeeded);
108 }
109 
110 void IP2CLoader::load()
111 {
112  if (gConfig.doomseeker.bIP2CountryAutoUpdate)
113  d->ip2cUpdater->needsUpdate(DoomseekerFilePaths::ip2cDatabaseAny());
114 
115  if (d->isParsing())
116  return;
117 
118  QString filePath = DoomseekerFilePaths::ip2cDatabaseAny();
119  if (!filePath.isEmpty())
120  {
121  d->inFallbackMode = false;
122  d->ip2c->setDataAccessLockEnabled(true);
123  ip2cReadDatabase(filePath);
124  }
125  else
126  {
127  if (!gConfig.doomseeker.bIP2CountryAutoUpdate)
128  {
129  gLog << tr("Did not find any IP2C database. IP2C functionality will be disabled.");
130  gLog << tr("You may install an IP2C database from the \"File\" menu.");
131  }
132  ip2cJobsFinished();
133  }
134 }
135 
136 void IP2CLoader::onUpdateNeeded(int status)
137 {
138  if (status == IP2CUpdater::UpdateNeeded)
139  {
140  update();
141  }
142  else
143  {
144  switch (status)
145  {
146  case IP2CUpdater::UpToDate:
147  gLog << tr("IP2C update not needed.");
148  break;
149  case IP2CUpdater::UpdateCheckError:
150  gLog << tr("IP2C update errored. See log for details.");
151  break;
152  default:
153  gLog << tr("IP2C update bugged out.");
154  break;
155  }
156  ip2cJobsFinished();
157  }
158 }
159 
160 void IP2CLoader::update()
161 {
162  d->updateInProgress = true;
163  if (!d->isParsing())
164  {
165  gLog << tr("Starting IP2C update.");
166  IP2C::instance()->setDataAccessLockEnabled(true);
167  d->ip2cUpdater->downloadDatabase(DoomseekerFilePaths::ip2cDatabase());
168  }
169 }
170 
171 void IP2CLoader::onUpdateFinished(const QByteArray &downloadedData)
172 {
173  d->updateInProgress = false;
174  if (!downloadedData.isEmpty())
175  {
176  gLog << tr("IP2C database finished downloading.");
177  QString filePath = DoomseekerFilePaths::ip2cDatabase();
178  d->ip2cUpdater->getRollbackData(filePath);
179  if (!d->ip2cUpdater->saveDownloadedData(filePath))
180  gLog << tr("Unable to save IP2C database at path: %1").arg(filePath);
181  ip2cReadDatabase(filePath);
182  }
183  else
184  {
185  gLog << tr("IP2C download has failed.");
186  ip2cJobsFinished();
187  }
188 }
189 
190 void IP2CLoader::onParsingFinished()
191 {
192  auto finally = [this]() {
193  if (d->updateInProgress) {
194  // Retrigger the update.
195  QTimer::singleShot(0, this, &IP2CLoader::update);
196  }
197  this->ip2cJobsFinished();
198  };
199 
200  IP2CParserThread *parserThread = d->parserThread;
201  if (parserThread == nullptr) {
202  gLog << tr("IP2C unable to retrieve the parsing result.");
203  finally();
204  return;
205  }
206  d->parserThread = nullptr; // object deleted by the deleteLater() slot
207  QString filePath = DoomseekerFilePaths::ip2cDatabase();
208  if (!parserThread->success)
209  {
210  if (d->inFallbackMode)
211  {
212  gLog << tr("Failed to read the IP2C fallback. Stopping.");
213  finally();
214  return;
215  }
216  gLog << tr("Failed to read the IP2C database. Reverting...");
217 
218  d->inFallbackMode = true;
219  if (d->ip2cUpdater == nullptr || !d->ip2cUpdater->hasRollbackData())
220  {
221  gLog << tr("IP2C revert attempt failed. Nothing to go back to.");
222 
223  // Delete file in this case.
224  QFile file(filePath);
225  file.remove();
226 
227  QString preinstalledDbPath = DoomseekerFilePaths::ip2cDatabaseAny();
228  if (!preinstalledDbPath.isEmpty())
229  {
230  gLog << tr("Trying to use the preinstalled IP2C database.");
231  ip2cReadDatabase(preinstalledDbPath);
232  }
233  else
234  {
235  finally();
236  }
237  }
238  else
239  {
240  // Revert to old content.
241  d->ip2cUpdater->rollback(filePath);
242 
243  // Must succeed now.
244  ip2cReadDatabase(filePath);
245  }
246  }
247  else
248  {
249  const IP2CParser &parser = parserThread->parser;
250  gLog << tr("IP2C parsing finished.");
251  d->ip2c->setRangesDatabase(parser.ranges());
252  d->ip2c->setLicence(parser.licences());
253  d->ip2c->setUrl(parser.url());
254  finally();
255  }
256 }
257 
258 void IP2CLoader::ip2cJobsFinished()
259 {
260  if (!d->ip2cUpdater->isWorking() && !d->isParsing() && !d->updateInProgress)
261  {
262  d->ip2c->setDataAccessLockEnabled(false);
263  emit finished();
264  }
265 }
266 
267 void IP2CLoader::ip2cReadDatabase(const QString &filePath)
268 {
269  gLog << tr("IP2C database is being read. This may take some time.");
270 
271  if (d->parserThread != nullptr) {
272  d->parserThread->disconnect(this);
273  }
274 
275  d->parserThread = new IP2CParserThread(filePath);
276  connect(d->parserThread, &IP2CParserThread::finished,
277  this, &IP2CLoader::onParsingFinished);
278  connect(d->parserThread, &IP2CParserThread::finished,
279  d->parserThread, &QObject::deleteLater);
280  d->parserThread->start();
281 }
282 
283 #include "ip2cloader.moc"