diff options
author | Martin Jones <martin.jones@nokia.com> | 2010-01-29 01:58:52 (GMT) |
---|---|---|
committer | Martin Jones <martin.jones@nokia.com> | 2010-01-29 01:58:52 (GMT) |
commit | 741d21719b942faa22a4bdf1a951d82d9fe73a68 (patch) | |
tree | 83b7edcff3fe208b311442490da708a651c8a640 /src | |
parent | 7081c571a33480cb6bb3db7391c9570e4b4e4be1 (diff) | |
download | Qt-741d21719b942faa22a4bdf1a951d82d9fe73a68.zip Qt-741d21719b942faa22a4bdf1a951d82d9fe73a68.tar.gz Qt-741d21719b942faa22a4bdf1a951d82d9fe73a68.tar.bz2 |
Move image network access into a separate thread, with decoding.
Diffstat (limited to 'src')
-rw-r--r-- | src/declarative/qml/qml.pri | 11 | ||||
-rw-r--r-- | src/declarative/qml/qmlengine.cpp | 62 | ||||
-rw-r--r-- | src/declarative/qml/qmlengine.h | 8 | ||||
-rw-r--r-- | src/declarative/qml/qmlengine_p.h | 3 | ||||
-rw-r--r-- | src/declarative/util/qmlpixmapcache.cpp | 321 | ||||
-rw-r--r-- | src/declarative/util/qmlpixmapcache_p.h | 10 |
6 files changed, 279 insertions, 136 deletions
diff --git a/src/declarative/qml/qml.pri b/src/declarative/qml/qml.pri index 6f2f57f..cd2fbff 100644 --- a/src/declarative/qml/qml.pri +++ b/src/declarative/qml/qml.pri @@ -1,5 +1,4 @@ INCLUDEPATH += $$PWD - SOURCES += \ $$PWD/qmlparser.cpp \ $$PWD/qmlinstruction.cpp \ @@ -50,8 +49,8 @@ SOURCES += \ $$PWD/qmlvaluetypescriptclass.cpp \ $$PWD/qmltypenamescriptclass.cpp \ $$PWD/qmllistscriptclass.cpp \ - $$PWD/qmlworkerscript.cpp - + $$PWD/qmlworkerscript.cpp \ + $$PWD/qmlnetworkaccessmanagerfactory.cpp HEADERS += \ $$PWD/qmlparser_p.h \ $$PWD/qmlglobal_p.h \ @@ -117,10 +116,8 @@ HEADERS += \ $$PWD/qmllistscriptclass_p.h \ $$PWD/qmlworkerscript_p.h \ $$PWD/qmlscriptclass_p.h \ - $$PWD/qmlguard_p.h - + $$PWD/qmlguard_p.h \ + $$PWD/qmlnetworkaccessmanagerfactory.h QT += sql - include(parser/parser.pri) include(rewriter/rewriter.pri) - diff --git a/src/declarative/qml/qmlengine.cpp b/src/declarative/qml/qmlengine.cpp index 5a40468..b3ac1bb 100644 --- a/src/declarative/qml/qmlengine.cpp +++ b/src/declarative/qml/qmlengine.cpp @@ -64,6 +64,7 @@ #include "qmlworkerscript_p.h" #include "qmlcomponent_p.h" #include "qmlscriptclass_p.h" +#include "qmlnetworkaccessmanagerfactory.h" #include <qfxperf_p_p.h> @@ -121,7 +122,8 @@ QmlEnginePrivate::QmlEnginePrivate(QmlEngine *e) contextClass(0), sharedContext(0), sharedScope(0), objectClass(0), valueTypeClass(0), globalClass(0), cleanup(0), erroredBindings(0), inProgressCreations(0), scriptEngine(this), workerScriptEngine(0), componentAttacheds(0), inBeginCreate(false), - networkAccessManager(0), typeManager(e), uniqueId(1) + networkAccessManager(0), networkAccessManagerFactory(0), accessManagerValid(false), + typeManager(e), uniqueId(1) { globalClass = new QmlGlobalScriptClass(&scriptEngine); fileImportPath.append(QLibraryInfo::location(QLibraryInfo::DataPath)+QDir::separator()+QLatin1String("qml")); @@ -386,34 +388,64 @@ QmlContext *QmlEngine::rootContext() } /*! - Sets the common QNetworkAccessManager, \a network, used by all QML elements - instantiated by this engine. + Sets the \a factory to use for creating QNetworkAccessManager(s). + + QNetworkAccessManager is used for all network access by QML. + By implementing a factory it is possible to create custom + QNetworkAccessManager with specialized caching, proxy and + cookie support. +*/ +void QmlEngine::setNetworkAccessManagerFactory(QmlNetworkAccessManagerFactory *factory) +{ + Q_D(QmlEngine); + d->networkAccessManagerFactory = factory; +} - If the parent of \a network is this engine, the engine takes ownership and - will delete it as needed. Otherwise, ownership remains with the caller. +/*! + Returns the current QmlNetworkAccessManagerFactory. - This method should only be called before any QmlComponents are instantiated. + \sa setNetworkAccessManagerFactory() */ -void QmlEngine::setNetworkAccessManager(QNetworkAccessManager *network) +QmlNetworkAccessManagerFactory *QmlEngine::networkAccessManagerFactory() const +{ + Q_D(const QmlEngine); + return d->networkAccessManagerFactory; +} + +void QmlEngine::namInvalidated() { Q_D(QmlEngine); - if (d->networkAccessManager && d->networkAccessManager->parent() == this) - delete d->networkAccessManager; - d->networkAccessManager = network; + d->accessManagerValid = false; } /*! - Returns the common QNetworkAccessManager used by all QML elements + Returns a common QNetworkAccessManager which can be used by any QML element instantiated by this engine. - The default implements no caching, cookiejar, etc., just a default - QNetworkAccessManager. + If a QmlNetworkAccessManagerFactory has been set and a QNetworkAccessManager + has not yet been created, the QmlNetworkAccessManagerFactory will be used + to create the QNetworkAccessManager; otherwise the returned QNetworkAccessManager + will have no proxy or cache set. + + \sa setNetworkAccessManagerFactory() */ QNetworkAccessManager *QmlEngine::networkAccessManager() const { Q_D(const QmlEngine); - if (!d->networkAccessManager) - d->networkAccessManager = new QNetworkAccessManager(const_cast<QmlEngine*>(this)); + if (!d->accessManagerValid) { + delete d->networkAccessManagerFactory; + d->networkAccessManagerFactory = 0; + } + if (!d->networkAccessManager) { + if (d->networkAccessManagerFactory) { + connect(d->networkAccessManagerFactory, SIGNAL(invalidated()) + , this, SLOT(namInvalidated()), Qt::UniqueConnection); + d->networkAccessManager = d->networkAccessManagerFactory->create(const_cast<QmlEngine*>(this)); + } else { + d->networkAccessManager = new QNetworkAccessManager(const_cast<QmlEngine*>(this)); + } + d->accessManagerValid = true; + } return d->networkAccessManager; } diff --git a/src/declarative/qml/qmlengine.h b/src/declarative/qml/qmlengine.h index 7cbbcac..b9ec277 100644 --- a/src/declarative/qml/qmlengine.h +++ b/src/declarative/qml/qmlengine.h @@ -63,6 +63,7 @@ class QUrl; class QScriptEngine; class QScriptContext; class QNetworkAccessManager; +class QmlNetworkAccessManagerFactory; class Q_DECLARATIVE_EXPORT QmlEngine : public QObject { Q_PROPERTY(QString offlineStoragePath READ offlineStoragePath WRITE setOfflineStoragePath) @@ -77,7 +78,9 @@ public: void addImportPath(const QString& dir); - void setNetworkAccessManager(QNetworkAccessManager *); + void setNetworkAccessManagerFactory(QmlNetworkAccessManagerFactory *); + QmlNetworkAccessManagerFactory *networkAccessManagerFactory() const; + QNetworkAccessManager *networkAccessManager() const; void setOfflineStoragePath(const QString& dir); @@ -92,6 +95,9 @@ public: Q_SIGNALS: void quit (); +private Q_SLOTS: + void namInvalidated(); + private: Q_DECLARE_PRIVATE(QmlEngine) }; diff --git a/src/declarative/qml/qmlengine_p.h b/src/declarative/qml/qmlengine_p.h index 85e8c80..6f62b40 100644 --- a/src/declarative/qml/qmlengine_p.h +++ b/src/declarative/qml/qmlengine_p.h @@ -92,6 +92,7 @@ class QmlValueTypeScriptClass; class QScriptEngineDebugger; class QNetworkReply; class QNetworkAccessManager; +class QmlNetworkAccessManagerFactory; class QmlAbstractBinding; class QScriptDeclarativeClass; class QmlTypeNameScriptClass; @@ -209,6 +210,8 @@ public: bool inBeginCreate; mutable QNetworkAccessManager *networkAccessManager; + mutable QmlNetworkAccessManagerFactory *networkAccessManagerFactory; + mutable bool accessManagerValid; QmlCompositeTypeManager typeManager; QStringList fileImportPath; diff --git a/src/declarative/util/qmlpixmapcache.cpp b/src/declarative/util/qmlpixmapcache.cpp index 4c1d448..c058408 100644 --- a/src/declarative/util/qmlpixmapcache.cpp +++ b/src/declarative/util/qmlpixmapcache.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include "qmlpixmapcache_p.h" +#include "qmlnetworkaccessmanagerfactory.h" #include "qfxperf_p_p.h" @@ -57,6 +58,15 @@ #include <QWaitCondition> #include <QtCore/qdebug.h> #include <private/qobject_p.h> +#include <QSslError> + +#ifdef Q_OS_LINUX +#include <pthread.h> +#include <linux/sched.h> +#endif + +// Maximum number of simultaneous image requests to send. +static const int maxImageRequestCount = 8; QT_BEGIN_NAMESPACE @@ -69,105 +79,231 @@ class QmlImageReader : public QThread { Q_OBJECT public: - QmlImageReader(QObject *parent=0); + QmlImageReader(QmlEngine *eng); ~QmlImageReader(); - void read(QmlPixmapReply *rep); + QmlPixmapReply *getImage(const QUrl &url); void cancel(QmlPixmapReply *rep); + static QmlImageReader *instance(QmlEngine *engine); + protected: void run(); + bool event(QEvent *event); -private: - struct Job { - Job() : reply(0), error(false) {} - QmlPixmapReply *reply; - QImage img; - bool error; - }; +private slots: + void networkRequestDone(); + void namInvalidated() { + accessManagerValid = false; + } - void loadImage(Job &job); +private: + QNetworkAccessManager *networkAccessManager() { + if (!accessManagerValid) { + delete accessManager; + accessManager = 0; + } + if (!accessManager) { + if (engine && engine->networkAccessManagerFactory()) { + connect(engine->networkAccessManagerFactory(), SIGNAL(invalidated()) + , this, SLOT(namInvalidated()), Qt::UniqueConnection); + accessManager = engine->networkAccessManagerFactory()->create(this); + } else { + accessManager = new QNetworkAccessManager(this); + } + accessManagerValid = true; + } + return accessManager; + } - QList<Job> jobs; + QList<QmlPixmapReply*> jobs; + QList<QmlPixmapReply*> cancelled; + QHash<QNetworkReply*,QmlPixmapReply*> replies; + QNetworkAccessManager *accessManager; + bool accessManagerValid; + QmlEngine *engine; QMutex mutex; - QWaitCondition haveJob; - bool quit; + static QHash<QmlEngine *,QmlImageReader*> readers; }; -class QmlImageDecodeEvent : public QEvent +QHash<QmlEngine *,QmlImageReader*> QmlImageReader::readers; + +class QmlImageReaderEvent : public QEvent { public: - QmlImageDecodeEvent(bool err, QImage &img) : QEvent(QEvent::User), error(err), image(img) {} + enum ReadError { NoError, Loading, Decoding }; - bool error; + QmlImageReaderEvent(QmlImageReaderEvent::ReadError err, QImage &img) + : QEvent(QEvent::User), error(err), image(img) {} + + ReadError error; QImage image; }; -Q_GLOBAL_STATIC(QmlImageReader, qmlImageReader) - -QmlImageReader::QmlImageReader(QObject *parent) : QThread(parent), quit(false) +QmlImageReader::QmlImageReader(QmlEngine *eng) + : QThread(eng), accessManager(0), accessManagerValid(false), engine(eng) { start(QThread::LowPriority); } QmlImageReader::~QmlImageReader() { - quit = true; - haveJob.wakeOne(); } -void QmlImageReader::read(QmlPixmapReply *reply) +QmlImageReader *QmlImageReader::instance(QmlEngine *engine) +{ + QmlImageReader *reader = readers.value(engine); + if (!reader) { + static QMutex rmutex; + rmutex.lock(); + reader = new QmlImageReader(engine); + readers.insert(engine, reader); + rmutex.unlock(); + } + + return reader; +} + +QmlPixmapReply *QmlImageReader::getImage(const QUrl &url) { mutex.lock(); - Job job; - job.reply = reply; - jobs.append(job); + QmlPixmapReply *reply = new QmlPixmapReply(engine, url); + jobs.append(reply); if (jobs.count() == 1) - haveJob.wakeOne(); + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); mutex.unlock(); + return reply; } void QmlImageReader::cancel(QmlPixmapReply *reply) { mutex.lock(); - QList<Job>::iterator it = jobs.begin(); - while (it != jobs.end()) { - if ((*it).reply == reply) { - jobs.erase(it); - break; + if (reply->isLoading()) { + // Already requested. Add to cancel list to be cancelled in reader thread. + cancelled.append(reply); + if (cancelled.count() == 1) + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + } else { + // Not yet processed - just remove from waiting list + QList<QmlPixmapReply*>::iterator it = jobs.begin(); + while (it != jobs.end()) { + QmlPixmapReply *job = *it; + if (job == reply) { + jobs.erase(it); + break; + } + ++it; } - ++it; } mutex.unlock(); } -void QmlImageReader::loadImage(Job &job) +void QmlImageReader::run() { - QImageReader imgio(job.reply->device()); - if (imgio.read(&job.img)) { - job.error = false; - } else { - job.error = true; - qWarning() << imgio.errorString(); +#ifdef Q_OS_LINUX + struct sched_param param; + int policy; + + pthread_getschedparam(pthread_self(), &policy, ¶m); + pthread_setschedparam(pthread_self(), SCHED_IDLE, ¶m); +#endif + + exec(); +} + +bool QmlImageReader::event(QEvent *event) +{ + if (event->type() == QEvent::User) { + static int replyDownloadProgress = -1; + static int replyFinished = -1; + static int downloadProgress = -1; + static int thisNetworkRequestDone = -1; + + if (replyDownloadProgress == -1) { + replyDownloadProgress = QNetworkReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); + replyFinished = QNetworkReply::staticMetaObject.indexOfSignal("finished()"); + downloadProgress = QmlPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); + thisNetworkRequestDone = QmlImageReader::staticMetaObject.indexOfSlot("networkRequestDone()"); + } + + while (1) { + mutex.lock(); + + if (cancelled.count()) { + for (int i = 0; i < cancelled.count(); ++i) { + QmlPixmapReply *job = cancelled.at(i); + QNetworkReply *reply = replies.key(job, 0); + if (reply && reply->isRunning()) { + replies.remove(reply); + reply->close(); + job->release(true); + } + } + cancelled.clear(); + } + + if (!accessManagerValid) { + // throw away existing requests and reschedule. + QHash<QNetworkReply*,QmlPixmapReply*>::iterator it = replies.begin(); + for (; it != replies.end(); ++it) { + delete it.key(); + jobs.prepend(*it); + } + replies.clear(); + } + + if (!jobs.count() || replies.count() > maxImageRequestCount) { + mutex.unlock(); + break; + } + + QmlPixmapReply *runningJob = jobs.takeFirst(); + runningJob->addRef(); + runningJob->setLoading(); + QUrl url = runningJob->url(); + mutex.unlock(); + + // fetch + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + QNetworkReply *reply = networkAccessManager()->get(req); + + QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress); + QMetaObject::connect(reply, replyFinished, this, thisNetworkRequestDone); + + replies.insert(reply, runningJob); + } + return true; } + + return QObject::event(event); } -void QmlImageReader::run() +void QmlImageReader::networkRequestDone() { - while (1) { - mutex.lock(); - if (!jobs.count()) - haveJob.wait(&mutex); - if (quit) - break; - Job runningJob = jobs.takeFirst(); - runningJob.reply->addRef(); - mutex.unlock(); - - loadImage(runningJob); - QCoreApplication::postEvent(runningJob.reply, new QmlImageDecodeEvent(runningJob.error, runningJob.img)); + QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); + QmlPixmapReply *job = replies.take(reply); + if (job) { + QImage image; + QmlImageReaderEvent::ReadError error; + if (reply->error()) { + error = QmlImageReaderEvent::Loading; + } else { + QImageReader imgio(reply); + if (imgio.read(&image)) { + error = QmlImageReaderEvent::NoError; + } else { + error = QmlImageReaderEvent::Decoding; + } + } + // send completion event to the QmlPixmapReply + QCoreApplication::postEvent(job, new QmlImageReaderEvent(error, image)); } + // kick off event loop again if we have dropped below max request count + if (replies.count() == maxImageRequestCount) + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + reply->deleteLater(); } static bool readImage(QIODevice *dev, QPixmap *pixmap) @@ -228,46 +364,26 @@ class QmlPixmapReplyPrivate : public QObjectPrivate Q_DECLARE_PUBLIC(QmlPixmapReply) public: - QmlPixmapReplyPrivate(const QUrl &u, QNetworkReply *r) - : QObjectPrivate(), refCount(1), url(u), reply(r), status(QmlPixmapReply::Loading) { + QmlPixmapReplyPrivate(QmlEngine *e, const QUrl &u) + : QObjectPrivate(), refCount(1), url(u), status(QmlPixmapReply::Loading), loading(false), engine(e) { } int refCount; QUrl url; - QNetworkReply *reply; QPixmap pixmap; // ensure reference to pixmap so QPixmapCache does not discard - QImage image; QmlPixmapReply::Status status; + bool loading; + QmlEngine *engine; }; -QmlPixmapReply::QmlPixmapReply(const QUrl &url, QNetworkReply *reply) - : QObject(*new QmlPixmapReplyPrivate(url, reply), 0) +QmlPixmapReply::QmlPixmapReply(QmlEngine *engine, const QUrl &url) + : QObject(*new QmlPixmapReplyPrivate(engine, url), 0) { - Q_D(QmlPixmapReply); - - static int replyDownloadProgress = -1; - static int replyFinished = -1; - static int thisDownloadProgress = -1; - static int thisNetworkRequestDone = -1; - - if (replyDownloadProgress == -1) { - replyDownloadProgress = QNetworkReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); - replyFinished = QNetworkReply::staticMetaObject.indexOfSignal("finished()"); - thisDownloadProgress = QmlPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); - thisNetworkRequestDone = QmlPixmapReply::staticMetaObject.indexOfSlot("networkRequestDone()"); - } - - QMetaObject::connect(d->reply, replyDownloadProgress, this, thisDownloadProgress, Qt::DirectConnection); - QMetaObject::connect(d->reply, replyFinished, this, thisNetworkRequestDone); } QmlPixmapReply::~QmlPixmapReply() { - Q_D(QmlPixmapReply); - if (d->status == Decoding) - qmlImageReader()->cancel(this); - delete d->reply; } const QUrl &QmlPixmapReply::url() const @@ -276,36 +392,16 @@ const QUrl &QmlPixmapReply::url() const return d->url; } -void QmlPixmapReply::networkRequestDone() -{ - Q_D(QmlPixmapReply); - if (d->reply->error()) { - d->pixmap = QPixmap(); - d->status = Error; - QByteArray key = d->url.toEncoded(QUrl::FormattingOption(0x100)); - QString strKey = QString::fromLatin1(key.constData(), key.count()); - QPixmapCache::insert(strKey, d->pixmap); - qWarning() << "Network error loading" << d->url << d->reply->errorString(); - emit finished(); - } else { - qmlImageReader()->read(this); - d->status = Decoding; - } -} - bool QmlPixmapReply::event(QEvent *event) { Q_D(QmlPixmapReply); if (event->type() == QEvent::User) { + d->loading = false; if (!release(true)) { - QmlImageDecodeEvent *de = static_cast<QmlImageDecodeEvent*>(event); - d->status = de->error ? Error : Ready; - if (d->status == Ready) { + QmlImageReaderEvent *de = static_cast<QmlImageReaderEvent*>(event); + d->status = (de->error == QmlImageReaderEvent::NoError) ? Ready : Error; + if (d->status == Ready) d->pixmap = QPixmap::fromImage(de->image); - d->image = QImage(); - } else { - qWarning() << "Error decoding" << d->url; - } QByteArray key = d->url.toEncoded(QUrl::FormattingOption(0x100)); QString strKey = QString::fromLatin1(key.constData(), key.count()); QPixmapCache::insert(strKey, d->pixmap); @@ -323,10 +419,16 @@ QmlPixmapReply::Status QmlPixmapReply::status() const return d->status; } -QIODevice *QmlPixmapReply::device() +bool QmlPixmapReply::isLoading() const +{ + Q_D(const QmlPixmapReply); + return d->loading; +} + +void QmlPixmapReply::setLoading() { Q_D(QmlPixmapReply); - return d->reply; + d->loading = true; } void QmlPixmapReply::addRef() @@ -342,11 +444,17 @@ bool QmlPixmapReply::release(bool defer) --d->refCount; if (d->refCount == 0) { qmlActivePixmapReplies()->remove(d->url); + if (d->status == Loading && !d->loading) + QmlImageReader::instance(d->engine)->cancel(this); if (defer) deleteLater(); else delete this; return true; + } else if (d->refCount == 1 && d->loading) { + // The only reference left is the reader thread. + qmlActivePixmapReplies()->remove(d->url); + QmlImageReader::instance(d->engine)->cancel(this); } return false; @@ -418,9 +526,8 @@ QmlPixmapReply *QmlPixmapCache::request(QmlEngine *engine, const QUrl &url) { QmlPixmapReplyHash::Iterator iter = qmlActivePixmapReplies()->find(url); if (iter == qmlActivePixmapReplies()->end()) { - QNetworkRequest req(url); - req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - QmlPixmapReply *item = new QmlPixmapReply(url, engine->networkAccessManager()->get(req)); + QmlImageReader *reader = QmlImageReader::instance(engine); + QmlPixmapReply *item = reader->getImage(url); iter = qmlActivePixmapReplies()->insert(url, item); } else { (*iter)->addRef(); diff --git a/src/declarative/util/qmlpixmapcache_p.h b/src/declarative/util/qmlpixmapcache_p.h index 711e902..0140352 100644 --- a/src/declarative/util/qmlpixmapcache_p.h +++ b/src/declarative/util/qmlpixmapcache_p.h @@ -59,10 +59,10 @@ class Q_DECLARATIVE_EXPORT QmlPixmapReply : public QObject { Q_OBJECT public: - QmlPixmapReply(const QUrl &url, QNetworkReply *reply); + QmlPixmapReply(QmlEngine *engine, const QUrl &url); ~QmlPixmapReply(); - enum Status { Ready, Error, Unrequested, Loading, Decoding }; + enum Status { Ready, Error, Unrequested, Loading }; Status status() const; const QUrl &url() const; @@ -75,12 +75,10 @@ protected: bool event(QEvent *event); private: - QIODevice *device(); void addRef(); bool release(bool defer=false); - -private Q_SLOTS: - void networkRequestDone(); + bool isLoading() const; + void setLoading(); private: Q_DISABLE_COPY(QmlPixmapReply) |