Search code examples
qtqmlqtquick2cache-control

How to control caching of HTTP resources in QML?


Consider the following piece of code, which fetches an RSS feed and then displays the images within as a slideshow which loops forever:

import QtQuick 2.2
import QtQuick.XmlListModel 2.0

Rectangle {
    id: window
    color: "black"
    width: 800
    height: 480

    PathView {
        anchors.fill: parent

        highlightRangeMode: PathView.StrictlyEnforceRange
        preferredHighlightBegin: 0.5
        preferredHighlightEnd: 0.5
        highlightMoveDuration: 500
        snapMode: PathView.SnapOneItem
        pathItemCount: 3 // only show previous, current, next

        path: Path { // horizontal
            startX: -width; startY: height/2
            PathLine{x: width*2; y: height/2}
        }


        model: XmlListModel {
            source: "http://feeds.bbci.co.uk/news/business/rss.xml"
            query: "/rss/channel/item"
            namespaceDeclarations: "declare namespace media = 'http://search.yahoo.com/mrss/';"
            XmlRole { name: "image"; query: "media:thumbnail/@url/string()" }
        }


        delegate: Image {
            width: PathView.view.width
            height: PathView.view.height
            source: image
            fillMode: Image.PreserveAspectCrop
        }


        Timer { // automatically loop through the images
            interval: 1000; running: true; repeat: true;
            onTriggered: {
                parent.incrementCurrentIndex()
            }
        }


        Timer {
            interval: 600000; running: true; repeat: true;
            onTriggered: parent.model.reload()
        }

    }
}

This code loads the images from the web as it needs them. However, once an image is no longer displayed it discards the data. The next time the slideshow loops around the image will be reloaded from the web. As a result, the code hits the remote image server once per second for as long as it is running, downloading 50-300KB each time.

The code runs on an embedded system with not much RAM, so caching the decoded image data by keeping the delegate when it is not on the path is not an option.

Instead the caching should be done at the HTTP level, storing the original downloaded files. It should therefore obey the HTTP cache control headers.

The caching should be done in memory only as the system has only a small flash disk.

How can I implement this in Qt? I assume it will involve C++ code, that is fine.


Solution

  • To control the caching behaviour when QML fetches a network resource you would subclass QQmlNetworkAccessManagerFactory and have it create QNetworkAccessManagers with a cache attached. Then you attach the factory to your QQmlEngine:

    class MyNAMFactory : public QQmlNetworkAccessManagerFactory
    {
    public:
        virtual QNetworkAccessManager *create(QObject *parent);
    };
    
    QNetworkAccessManager *MyNAMFactory::create(QObject *parent)
    {
        QNetworkAccessManager *nam = new QNetworkAccessManager(parent);
        nam->setCache(new QNetworkDiskCache(parent));
        return nam;
    }
    
    int main(int argc, char **argv)
    {
        QGuiApplication app(argc, argv);
        QQuickView view;
        view.engine()->setNetworkAccessManagerFactory(new MyNAMFactory);
        view.setSource(QUrl("qrc:///main.qml"));
        view.show();
    
        return app.exec();
    }
    

    Caches must implement the QAbstractNetworkCache interface. Qt has one built in cache type, QNetworkDiskCache which, as the name implies, saves the cache to disk. There is no built-in class for in-memory caching, but it would be fairly easy to implement one by using a QHash to store the URLs, data, and metadata.