2
* Copyright (C) 2008 Fabien Chereau
4
* This program is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU General Public License
6
* as published by the Free Software Foundation; either version 2
7
* of the License, or (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
#include "MultiLevelJsonBase.hpp"
20
#include "StelJsonParser.hpp"
21
#include "StelApp.hpp"
22
#include "StelFileMgr.hpp"
23
#include "StelProjector.hpp"
24
#include "StelCore.hpp"
25
#include "kfilterdev.h"
34
#include <QNetworkAccessManager>
35
#include <QNetworkRequest>
36
#include <QNetworkReply>
39
// #if QT_VERSION >= 0x040500
40
// #include <QNetworkDiskCache>
41
// #include <QDesktopServices>
45
QNetworkAccessManager* MultiLevelJsonBase::networkAccessManager = NULL;
47
QNetworkAccessManager& MultiLevelJsonBase::getNetworkAccessManager()
49
if (networkAccessManager==NULL)
51
networkAccessManager = new QNetworkAccessManager(&StelApp::getInstance());
52
// #if QT_VERSION >= 0x040500
53
// QNetworkDiskCache* cache = new QNetworkDiskCache(networkAccessManager);
54
// QString cachePath = QDesktopServices::storageLocation(QDesktopServices::CacheLocation);
55
// if (cachePath.isEmpty())
57
// cachePath = StelApp::getInstance().getFileMgr().getUserDir()+"/cache";
59
// cache->setCacheDirectory(cachePath+"/JSONCache");
60
// networkAccessManager->setCache(cache);
62
connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)), &StelApp::getInstance(), SLOT(reportFileDownloadFinished(QNetworkReply*)));
64
return *networkAccessManager;
67
/*************************************************************************
68
Class used to load a JSON file in a thread
69
*************************************************************************/
70
class JsonLoadThread : public QThread
73
JsonLoadThread(MultiLevelJsonBase* atile, QByteArray content, bool aqZcompressed=false, bool agzCompressed=false) : QThread((QObject*)atile),
74
tile(atile), data(content), qZcompressed(aqZcompressed), gzCompressed(agzCompressed){;}
77
MultiLevelJsonBase* tile;
79
const bool qZcompressed;
80
const bool gzCompressed;
83
void JsonLoadThread::run()
88
buf.open(QIODevice::ReadOnly);
89
tile->temporaryResultMap = MultiLevelJsonBase::loadFromJSON(buf, qZcompressed, gzCompressed);
91
catch (std::runtime_error e)
93
qWarning() << "WARNING : Can't parse loaded JSON description: " << e.what();
94
tile->errorOccured = true;
98
MultiLevelJsonBase::MultiLevelJsonBase(MultiLevelJsonBase* parent) : QObject(parent)
100
errorOccured = false;
104
loadingState = false;
106
// Avoid tiles to be deleted just after constructed
107
timeWhenDeletionScheduled = -1.;
112
deletionDelay = parent->deletionDelay;
116
void MultiLevelJsonBase::initFromUrl(const QString& url)
118
const MultiLevelJsonBase* parent = qobject_cast<MultiLevelJsonBase*>(QObject::parent());
120
if (!url.startsWith("http://") && (parent==NULL || !parent->getBaseUrl().startsWith("http://")))
122
// Assume a local file
126
fileName = StelApp::getInstance().getFileMgr().findFile(url);
128
catch (std::runtime_error e)
133
throw std::runtime_error("NULL parent");
134
fileName = StelApp::getInstance().getFileMgr().findFile(parent->getBaseUrl()+url);
136
catch (std::runtime_error e)
138
qWarning() << "WARNING : Can't find JSON description: " << url << ": " << e.what();
143
QFileInfo finf(fileName);
144
baseUrl = finf.absolutePath()+'/';
146
f.open(QIODevice::ReadOnly);
147
const bool compressed = fileName.endsWith(".qZ");
148
const bool gzCompressed = fileName.endsWith(".gz");
151
loadFromQVariantMap(loadFromJSON(f, compressed, gzCompressed));
153
catch (std::runtime_error e)
155
qWarning() << "WARNING : Can't parse JSON description: " << fileName << ": " << e.what();
164
// Use a very short deletion delay to ensure that tile which are outside screen are discared before they are even downloaded
165
// This is useful to reduce bandwidth when the user moves rapidely
166
deletionDelay = 0.001;
168
if (url.startsWith("http://"))
174
Q_ASSERT(parent->getBaseUrl().startsWith("http://"));
175
qurl.setUrl(parent->getBaseUrl()+url);
177
Q_ASSERT(httpReply==NULL);
178
QNetworkRequest req(qurl);
179
req.setRawHeader("User-Agent", StelApp::getApplicationName().toAscii());
180
httpReply = getNetworkAccessManager().get(req);
181
//qDebug() << "Started downloading " << httpReply->request().url().path();
182
Q_ASSERT(httpReply->error()==QNetworkReply::NoError);
183
//qDebug() << httpReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
184
connect(httpReply, SIGNAL(finished()), this, SLOT(downloadFinished()));
185
//connect(httpReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
186
//connect(httpReply, SIGNAL(destroyed()), this, SLOT(replyDestroyed()));
188
QString turl = qurl.toString();
189
baseUrl = turl.left(turl.lastIndexOf('/')+1);
193
// Constructor from a map used for JSON files with more than 1 level
194
void MultiLevelJsonBase::initFromQVariantMap(const QVariantMap& map)
196
const MultiLevelJsonBase* parent = qobject_cast<MultiLevelJsonBase*>(QObject::parent());
199
baseUrl = parent->getBaseUrl();
200
contructorUrl = parent->contructorUrl + "/?";
202
loadFromQVariantMap(map);
207
MultiLevelJsonBase::~MultiLevelJsonBase()
211
//qDebug() << "Abort: " << httpReply->request().url().path();
212
//httpReply->abort();
214
// This line should not be commented, but I have to keep it because of a Qt bug.
215
// It cause a nasty memory leak, but prevents an even more nasty
216
//httpReply->deleteLater();
219
if (loadThread && loadThread->isRunning())
221
//qDebug() << "--> Abort thread " << contructorUrl;
222
disconnect(loadThread, SIGNAL(finished()), this, SLOT(jsonLoadFinished()));
223
// The thread is currently running, it needs to be properly stopped
224
if (loadThread->wait(1)==false)
226
loadThread->terminate();
227
//loadThread->wait(2000);
230
foreach (MultiLevelJsonBase* tile, subTiles)
238
void MultiLevelJsonBase::scheduleChildsDeletion()
240
foreach (MultiLevelJsonBase* tile, subTiles)
242
if (tile->timeWhenDeletionScheduled<0)
243
tile->timeWhenDeletionScheduled = StelApp::getInstance().getTotalRunTime();
247
// If a deletion was scheduled, cancel it.
248
void MultiLevelJsonBase::cancelDeletion()
250
timeWhenDeletionScheduled=-1.;
251
foreach (MultiLevelJsonBase* tile, subTiles)
253
tile->cancelDeletion();
257
// Load the tile information from a JSON file
258
QVariantMap MultiLevelJsonBase::loadFromJSON(QIODevice& input, bool qZcompressed, bool gzCompressed)
260
StelJsonParser parser;
262
if (qZcompressed && input.size()>0)
264
QByteArray ar = qUncompress(input.readAll());
267
buf.open(QIODevice::ReadOnly);
268
map = parser.parse(buf).toMap();
271
else if (gzCompressed)
273
QIODevice* d = KFilterDev::device(&input, "application/x-gzip", false);
274
d->open(QIODevice::ReadOnly);
275
map = parser.parse(*d).toMap();
281
map = parser.parse(input).toMap();
285
throw std::runtime_error("empty JSON file, cannot load");
290
// Called when the download for the JSON file terminated
291
void MultiLevelJsonBase::downloadFinished()
293
//qDebug() << "Finished downloading " << httpReply->request().url().path();
294
Q_ASSERT(downloading);
295
if (httpReply->error()!=QNetworkReply::NoError)
297
if (httpReply->error()!=QNetworkReply::OperationCanceledError)
298
qWarning() << "WARNING : Problem while downloading JSON description for " << httpReply->request().url().path() << ": "<< httpReply->errorString();
300
httpReply->deleteLater();
303
timeWhenDeletionScheduled = StelApp::getInstance().getTotalRunTime();
307
QByteArray content = httpReply->readAll();
308
if (content.isEmpty())
310
qWarning() << "WARNING : empty JSON description for " << httpReply->request().url().path();
312
httpReply->deleteLater();
318
const bool qZcompressed = httpReply->request().url().path().endsWith(".qZ");
319
const bool gzCompressed = httpReply->request().url().path().endsWith(".gz");
320
httpReply->deleteLater();
323
Q_ASSERT(loadThread==NULL);
324
loadThread = new JsonLoadThread(this, content, qZcompressed, gzCompressed);
325
connect(loadThread, SIGNAL(finished()), this, SLOT(jsonLoadFinished()));
326
loadThread->start(QThread::LowestPriority);
329
// Called when the element is fully loaded from the JSON file
330
void MultiLevelJsonBase::jsonLoadFinished()
338
loadFromQVariantMap(temporaryResultMap);
342
// Delete all the subtiles which were not displayed since more than lastDrawTrigger seconds
343
void MultiLevelJsonBase::deleteUnusedSubTiles()
345
if (subTiles.isEmpty())
347
const double now = StelApp::getInstance().getTotalRunTime();
348
bool deleteAll = true;
349
foreach (MultiLevelJsonBase* tile, subTiles)
351
if (tile->timeWhenDeletionScheduled<0 || (now-tile->timeWhenDeletionScheduled)<deletionDelay)
359
//qDebug() << "Delete all tiles for " << this << ": " << contructorUrl;
360
foreach (MultiLevelJsonBase* tile, subTiles)
366
// Nothing to delete at this level, propagate
367
foreach (MultiLevelJsonBase* tile, subTiles)
368
tile->deleteUnusedSubTiles();
372
void MultiLevelJsonBase::updatePercent(int tot, int toBeLoaded)
374
if (tot+toBeLoaded==0)
376
if (loadingState==true)
379
emit(loadingStateChanged(false));
384
int p = (int)(100.f*tot/(tot+toBeLoaded));
391
if (loadingState==true)
394
emit(loadingStateChanged(false));
400
if (loadingState==false)
403
emit(loadingStateChanged(true));
409
emit(percentLoadedChanged(p));