~ubuntu-branches/ubuntu/precise/stellarium/precise

« back to all changes in this revision

Viewing changes to src/core/MultiLevelJsonBase.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Cédric Delfosse
  • Date: 2009-03-13 20:07:22 UTC
  • mfrom: (1.1.8 upstream) (4.1.2 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090313200722-l66s4zy2s3e8up0s
Tags: 0.10.2-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2008 Fabien Chereau
 
3
 *
 
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.
 
8
 *
 
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.
 
13
 *
 
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.
 
17
 */
 
18
 
 
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"
 
26
 
 
27
#include <QDebug>
 
28
#include <QFile>
 
29
#include <QFileInfo>
 
30
#include <QHttp>
 
31
#include <QUrl>
 
32
#include <QBuffer>
 
33
#include <QThread>
 
34
#include <QNetworkAccessManager>
 
35
#include <QNetworkRequest>
 
36
#include <QNetworkReply>
 
37
#include <stdexcept>
 
38
 
 
39
// #if QT_VERSION >= 0x040500
 
40
// #include <QNetworkDiskCache>
 
41
// #include <QDesktopServices>
 
42
// #endif
 
43
 
 
44
// Init statics
 
45
QNetworkAccessManager* MultiLevelJsonBase::networkAccessManager = NULL;
 
46
 
 
47
QNetworkAccessManager& MultiLevelJsonBase::getNetworkAccessManager()
 
48
{
 
49
        if (networkAccessManager==NULL)
 
50
        {
 
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())
 
56
//              {
 
57
//                      cachePath = StelApp::getInstance().getFileMgr().getUserDir()+"/cache";
 
58
//              }
 
59
//              cache->setCacheDirectory(cachePath+"/JSONCache");
 
60
//              networkAccessManager->setCache(cache);
 
61
// #endif
 
62
                connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)), &StelApp::getInstance(), SLOT(reportFileDownloadFinished(QNetworkReply*)));
 
63
        }
 
64
        return *networkAccessManager;
 
65
}
 
66
 
 
67
/*************************************************************************
 
68
  Class used to load a JSON file in a thread
 
69
 *************************************************************************/
 
70
class JsonLoadThread : public QThread
 
71
{
 
72
        public:
 
73
                JsonLoadThread(MultiLevelJsonBase* atile, QByteArray content, bool aqZcompressed=false, bool agzCompressed=false) : QThread((QObject*)atile),
 
74
                        tile(atile), data(content), qZcompressed(aqZcompressed), gzCompressed(agzCompressed){;}
 
75
                virtual void run();
 
76
        private:
 
77
                MultiLevelJsonBase* tile;
 
78
                QByteArray data;
 
79
                const bool qZcompressed;
 
80
                const bool gzCompressed;
 
81
};
 
82
 
 
83
void JsonLoadThread::run()
 
84
{
 
85
        try
 
86
        {
 
87
                QBuffer buf(&data);
 
88
                buf.open(QIODevice::ReadOnly);
 
89
                tile->temporaryResultMap = MultiLevelJsonBase::loadFromJSON(buf, qZcompressed, gzCompressed);
 
90
        }
 
91
        catch (std::runtime_error e)
 
92
        {
 
93
                qWarning() << "WARNING : Can't parse loaded JSON description: " << e.what();
 
94
                tile->errorOccured = true;
 
95
        }
 
96
}
 
97
 
 
98
MultiLevelJsonBase::MultiLevelJsonBase(MultiLevelJsonBase* parent) : QObject(parent)
 
99
{
 
100
        errorOccured = false;
 
101
        httpReply = NULL;
 
102
        downloading = false;
 
103
        loadThread = NULL;
 
104
        loadingState = false;
 
105
        lastPercent = 0;
 
106
        // Avoid tiles to be deleted just after constructed
 
107
        timeWhenDeletionScheduled = -1.;
 
108
        deletionDelay = 2.;
 
109
        
 
110
        if (parent!=NULL)
 
111
        {
 
112
                deletionDelay = parent->deletionDelay;
 
113
        }
 
114
}
 
115
 
 
116
void MultiLevelJsonBase::initFromUrl(const QString& url)
 
117
{
 
118
        const MultiLevelJsonBase* parent = qobject_cast<MultiLevelJsonBase*>(QObject::parent());
 
119
        contructorUrl = url;
 
120
        if (!url.startsWith("http://") && (parent==NULL || !parent->getBaseUrl().startsWith("http://")))
 
121
        {
 
122
                // Assume a local file
 
123
                QString fileName;
 
124
                try
 
125
                {
 
126
                        fileName = StelApp::getInstance().getFileMgr().findFile(url);
 
127
                }
 
128
                catch (std::runtime_error e)
 
129
                {
 
130
                        try
 
131
                        {
 
132
                                if (parent==NULL)
 
133
                                        throw std::runtime_error("NULL parent");
 
134
                                fileName = StelApp::getInstance().getFileMgr().findFile(parent->getBaseUrl()+url);
 
135
                        }
 
136
                        catch (std::runtime_error e)
 
137
                        {
 
138
                                qWarning() << "WARNING : Can't find JSON description: " << url << ": " << e.what();
 
139
                                errorOccured = true;
 
140
                                return;
 
141
                        }
 
142
                }
 
143
                QFileInfo finf(fileName);
 
144
                baseUrl = finf.absolutePath()+'/';
 
145
                QFile f(fileName);
 
146
                f.open(QIODevice::ReadOnly);
 
147
                const bool compressed = fileName.endsWith(".qZ");
 
148
                const bool gzCompressed = fileName.endsWith(".gz");
 
149
                try
 
150
                {
 
151
                        loadFromQVariantMap(loadFromJSON(f, compressed, gzCompressed));
 
152
                }
 
153
                catch (std::runtime_error e)
 
154
                {
 
155
                        qWarning() << "WARNING : Can't parse JSON description: " << fileName << ": " << e.what();
 
156
                        errorOccured = true;
 
157
                        f.close();
 
158
                        return;
 
159
                }
 
160
                f.close();
 
161
        }
 
162
        else
 
163
        {
 
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;
 
167
                QUrl qurl;
 
168
                if (url.startsWith("http://"))
 
169
                {
 
170
                        qurl.setUrl(url);
 
171
                }
 
172
                else
 
173
                {
 
174
                        Q_ASSERT(parent->getBaseUrl().startsWith("http://"));
 
175
                        qurl.setUrl(parent->getBaseUrl()+url);
 
176
                }
 
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()));
 
187
                downloading = true;
 
188
                QString turl = qurl.toString();
 
189
                baseUrl = turl.left(turl.lastIndexOf('/')+1);
 
190
        }
 
191
}
 
192
 
 
193
// Constructor from a map used for JSON files with more than 1 level
 
194
void MultiLevelJsonBase::initFromQVariantMap(const QVariantMap& map)
 
195
{
 
196
        const MultiLevelJsonBase* parent = qobject_cast<MultiLevelJsonBase*>(QObject::parent());
 
197
        if (parent!=NULL)
 
198
        {
 
199
                baseUrl = parent->getBaseUrl();
 
200
                contructorUrl = parent->contructorUrl + "/?";
 
201
        }
 
202
        loadFromQVariantMap(map);
 
203
        downloading = false;
 
204
}
 
205
        
 
206
// Destructor
 
207
MultiLevelJsonBase::~MultiLevelJsonBase()
 
208
{
 
209
        if (httpReply)
 
210
        {
 
211
                //qDebug() << "Abort: " << httpReply->request().url().path();
 
212
                //httpReply->abort();
 
213
                
 
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();
 
217
                httpReply = NULL;
 
218
        }
 
219
        if (loadThread && loadThread->isRunning())
 
220
        {
 
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)
 
225
                {
 
226
                        loadThread->terminate();
 
227
                        //loadThread->wait(2000);
 
228
                }
 
229
        }
 
230
        foreach (MultiLevelJsonBase* tile, subTiles)
 
231
        {
 
232
                tile->deleteLater();
 
233
        }
 
234
        subTiles.clear();
 
235
}
 
236
 
 
237
 
 
238
void MultiLevelJsonBase::scheduleChildsDeletion()
 
239
{
 
240
        foreach (MultiLevelJsonBase* tile, subTiles)
 
241
        {
 
242
                if (tile->timeWhenDeletionScheduled<0)
 
243
                        tile->timeWhenDeletionScheduled = StelApp::getInstance().getTotalRunTime();
 
244
        }
 
245
}
 
246
 
 
247
// If a deletion was scheduled, cancel it.
 
248
void MultiLevelJsonBase::cancelDeletion()
 
249
{
 
250
        timeWhenDeletionScheduled=-1.;
 
251
        foreach (MultiLevelJsonBase* tile, subTiles)
 
252
        {
 
253
                tile->cancelDeletion();
 
254
        }
 
255
}
 
256
        
 
257
// Load the tile information from a JSON file
 
258
QVariantMap MultiLevelJsonBase::loadFromJSON(QIODevice& input, bool qZcompressed, bool gzCompressed)
 
259
{
 
260
        StelJsonParser parser;
 
261
        QVariantMap map;
 
262
        if (qZcompressed && input.size()>0)
 
263
        {
 
264
                QByteArray ar = qUncompress(input.readAll());
 
265
                input.close();
 
266
                QBuffer buf(&ar);
 
267
                buf.open(QIODevice::ReadOnly);
 
268
                map = parser.parse(buf).toMap();
 
269
                buf.close();
 
270
        }
 
271
        else if (gzCompressed)
 
272
        {
 
273
                QIODevice* d = KFilterDev::device(&input, "application/x-gzip", false);
 
274
                d->open(QIODevice::ReadOnly);
 
275
                map = parser.parse(*d).toMap();
 
276
                d->close();
 
277
                delete d;
 
278
        }
 
279
        else
 
280
        {       
 
281
                map = parser.parse(input).toMap();
 
282
        }
 
283
        
 
284
        if (map.isEmpty())
 
285
                throw std::runtime_error("empty JSON file, cannot load");
 
286
        return map;
 
287
}
 
288
 
 
289
 
 
290
// Called when the download for the JSON file terminated
 
291
void MultiLevelJsonBase::downloadFinished()
 
292
{
 
293
        //qDebug() << "Finished downloading " << httpReply->request().url().path();
 
294
        Q_ASSERT(downloading);
 
295
        if (httpReply->error()!=QNetworkReply::NoError)
 
296
        {
 
297
                if (httpReply->error()!=QNetworkReply::OperationCanceledError)
 
298
                        qWarning() << "WARNING : Problem while downloading JSON description for " << httpReply->request().url().path() << ": "<< httpReply->errorString();
 
299
                errorOccured = true;
 
300
                httpReply->deleteLater();
 
301
                httpReply=NULL;
 
302
                downloading=false;
 
303
                timeWhenDeletionScheduled = StelApp::getInstance().getTotalRunTime();
 
304
                return;
 
305
        }       
 
306
        
 
307
        QByteArray content = httpReply->readAll();
 
308
        if (content.isEmpty())
 
309
        {
 
310
                qWarning() << "WARNING : empty JSON description for " << httpReply->request().url().path();
 
311
                errorOccured = true;
 
312
                httpReply->deleteLater();
 
313
                httpReply=NULL;
 
314
                downloading=false;
 
315
                return;
 
316
        }
 
317
        
 
318
        const bool qZcompressed = httpReply->request().url().path().endsWith(".qZ");
 
319
        const bool gzCompressed = httpReply->request().url().path().endsWith(".gz");
 
320
        httpReply->deleteLater();
 
321
        httpReply=NULL;
 
322
        
 
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);
 
327
}
 
328
 
 
329
// Called when the element is fully loaded from the JSON file
 
330
void MultiLevelJsonBase::jsonLoadFinished()
 
331
{
 
332
        loadThread->wait();
 
333
        delete loadThread;
 
334
        loadThread = NULL;
 
335
        downloading = false;
 
336
        if (errorOccured)
 
337
                return;
 
338
        loadFromQVariantMap(temporaryResultMap);
 
339
}
 
340
 
 
341
 
 
342
// Delete all the subtiles which were not displayed since more than lastDrawTrigger seconds
 
343
void MultiLevelJsonBase::deleteUnusedSubTiles()
 
344
{
 
345
        if (subTiles.isEmpty())
 
346
                return;
 
347
        const double now = StelApp::getInstance().getTotalRunTime();
 
348
        bool deleteAll = true;
 
349
        foreach (MultiLevelJsonBase* tile, subTiles)
 
350
        {
 
351
                if (tile->timeWhenDeletionScheduled<0 || (now-tile->timeWhenDeletionScheduled)<deletionDelay)
 
352
                {
 
353
                        deleteAll = false;
 
354
                        break;
 
355
                }
 
356
        }
 
357
        if (deleteAll==true)
 
358
        {
 
359
                //qDebug() << "Delete all tiles for " << this << ": " << contructorUrl;
 
360
                foreach (MultiLevelJsonBase* tile, subTiles)
 
361
                        tile->deleteLater();
 
362
                subTiles.clear();
 
363
        }
 
364
        else
 
365
        {
 
366
                // Nothing to delete at this level, propagate
 
367
                foreach (MultiLevelJsonBase* tile, subTiles)
 
368
                        tile->deleteUnusedSubTiles();
 
369
        }
 
370
}
 
371
        
 
372
void MultiLevelJsonBase::updatePercent(int tot, int toBeLoaded)
 
373
{
 
374
        if (tot+toBeLoaded==0)
 
375
        {
 
376
                if (loadingState==true)
 
377
                {
 
378
                        loadingState=false;
 
379
                        emit(loadingStateChanged(false));
 
380
                }
 
381
                return;
 
382
        }
 
383
 
 
384
        int p = (int)(100.f*tot/(tot+toBeLoaded));
 
385
        if (p>100)
 
386
                p=100;
 
387
        if (p<0)
 
388
                p=0;
 
389
        if (p==100 || p==0)
 
390
        {
 
391
                if (loadingState==true)
 
392
                {
 
393
                        loadingState=false;
 
394
                        emit(loadingStateChanged(false));
 
395
                }
 
396
                return;
 
397
        }
 
398
        else
 
399
        {
 
400
                if (loadingState==false)
 
401
                {
 
402
                        loadingState=true;
 
403
                        emit(loadingStateChanged(true));
 
404
                }
 
405
        }
 
406
        if (p==lastPercent)
 
407
                return;
 
408
        lastPercent=p;
 
409
        emit(percentLoadedChanged(p));
 
410
}