QDeclarativeImageProvider for caching images on file?
-
Hi,
I currently populate a list of items from a JSON feed where each item includes a name and an imageUrl. Obviously, at the moment the image is always downloaded as I use a standard Image element and pass it the imageUrl.
I am trying to determine the best way to implement persistent image caching so that when the app is launched, it will display the cached image (if available) without requiring network access.
I am currently thinking the best way to do this would be to save the images to disk then - in the Image element - I either specify the standard imageUrl if there is no cached image or I specify the url to the file on disk if it has been cached (e.g "file:///cachedFileName.png").
Has anyone else tried to do this? Are there any examples that I have missed that demonstrate how to do this? If I have to implement it from scratch, would it be idiomatic to use a QDeclarativeImageProvider to provide this behaviour?
Thanks in advance,
Paul Drummond. -
There are two approaches that I know.
First one, is to use "QAbstractNetworkCache":http://doc.qt.nokia.com/4.7/qabstractnetworkcache.html#public-functions together with "QDeclarativeNetworkAccessManagerFactory":http://doc.qt.nokia.com/4.7/qdeclarativenetworkaccessmanagerfactory.html.
Second, like you mentioned is to use QDeclarativeImageProvider, I have sort of implemented caching, (in my case I have used url as a unique identifier, something like source: 'image://myimageprovider/uniqueurl.png', then in image provider, doing check if image is in cache if it's not download from 'http://uniqueurl.png'). But you have to be carefull because you will want QDeclarativeImageProvider to be QImage based (so it will not block the UI), and on top of that, network access is nonblocking/async in Qt, so you have to do some blocking. I opened "thread":http://developer.qt.nokia.com/forums/viewthread/5125/ here about that
I think first solution is more robust, but there is not very good documentation about QAbstractNetworkCache.
-
Hi,
The first approach mentioned by minimoog77 is what qmlviewer uses -- you can search for QNetworkDiskCache in tools/qml/qmlruntime.cpp to see how it is used there.
Regards,
Michael -
Well, at the moment QNetworkDiskCache implementation is kind a slow on mobile devices, that's why I suggested to reimplement QAbstractNetworkCache.
But, the good news is that, there is new implementation (more mobile friendly) of QNetworkDiskCache in the repo, but I don't know when it will be released (4.8 maybe?).
-
I have had no luck with QDeclarativeImageProvider - I definitely think this class wasn't designed for downloading images!
I am now looking at QNetworkDiskCache just as a proof of concept, not really worried if it's slow at this stage. I just want the simplest test possible. For my first attempt I'm ommitting the factory and initialising the cache like this:
@
QNetworkDiskCache *diskCache = new QNetworkDiskCache;
diskCache->setCacheDirectory("cacheDir");
viewer.engine()->networkAccessManager()->setCache(diskCache);
@I'm not sure why I would need a factory instead of initialising it this way?
The problem is I don't know how to get QML to actually use the cache? I'm trying to find the "magic" in the qmlviewer source but so far, no luck.
Thanks,
Paul. -
If you look at "documentation":http://doc.qt.nokia.com/4.7/qdeclarativeengine.html#networkAccessManager you see that if QNAM was not created by factory returned QNAM will have no proxy or cache set.
So you must use factory.
@
#include <QDeclarativeNetworkAccessManagerFactory>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include <QDesktopServices>class NAMFactory : public QDeclarativeNetworkAccessManagerFactory
{
public:
QNetworkAccessManager* create(QObject *parent) {
QNetworkAccessManager *nam = new QNetworkAccessManager(parent);QNetworkDiskCache *diskCache = new QNetworkDiskCache(nam); diskCache->setCacheDirectory(QDesktopServices::storageLocation(QDesktopServices::CacheLocation)); diskCache->setMaximumCacheSize(CACHE_SIZE); nam->setCache(diskCache); return nam; }
};@
And then
@qml_engine()->setNetworkAccessManagerFactory(new NAMFactory);@
-
I did read the docs for that method, but it's still returning a reference to a single QNAM so it should be possible to call setDiskCache() on it, right? Anyway, I am now using your NAMFactory implementation (thanks for the providing that by the way) and QML still doesn't seem be using the cache.
Should QML Image elements (for example) automagically use the cache rather than making a network request? It would be great if it worked that way but it all sounds way too straight forward to me ;-)
-
[quote author="pdrummond" date="1304594266"]I did read the docs for that method, but it's still returning a reference to a single QNAM so it should be possible to call setDiskCache() on it, right?[/quote] But you don't know how QNAM was created...
[quote]Anyway, I am now using your NAMFactory implementation (thanks for the providing that by the way) and QML still doesn't seem be using the cache.[/quote]
I don't know why, but you must set nam factory before loading qml file(s). It's working for me.
[quote]
Should QML Image elements (for example) automagically use the cache rather than making a network request? It would be great if it worked that way but it all sounds way too straight forward to me ;-)[/quote]Image element doesn't know if image is loaded from cache or network. That's the job of QNAM and QNetworkDiskCache. Image element will make network request, which would go through QNAM, and QNetworkDisckCache will check if image is in the cache, if not, it will be loaded through network, otherwise QNAM will return image from cache. -
Thanks for explaining - it all makes sense. I do set the factory before loading the main QML file but it's still not working for me. When I disconnect the wifi on my development machine I get the following error on the QML Image element:
@
QML QDeclarativeImage_QML_24: Host xxx.co.uk not found
@which sounds like it's ignoring the cache. Do you know if there are any settings or flags that need to be set on QNAM? I see from the QNetworkDiscCache docs that in C++ it is necessary to configure QNetworkRequests as follows:
@
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
@I assume QML does this by default?
-
I just tested my application...
There is a big listview of with 200 images elements (all downloaded from network). WiFi is turned on. Next, I have done scrolling through list, so that images are cached. Now, WiFi is turned off. Images that were instanced in listview are showed. So image caching is working.
I don't know where is your problem, but I would check what is the cache directory, and see if directory is created (if not, you should create the directory in NAMFactory). If directory is created there should be some cache files.
-
Hmmm. I think the caching you are seeing when you scroll is not the Disk Cache. I'm sure I have been able to do the same scroll test without having a Disk Cache in place. I will test that again to be sure...
A better test would be to have a ListView where the items are locally sourced but they include an image that is loaded from the network. When you run the app for the first time, the images will be downloaded, then you shut down the app, turn wifi off, re-run the app and the images should still be displayed because they are automatically sourced from the disk cache.
-
[quote author="pdrummond" date="1304604386"]
A better test would be to have a ListView where the items are locally sourced but they include an image that is loaded from the network. When you run the app for the first time, the images will be downloaded, then you shut down the app, turn wifi off, re-run the app and the images should still be displayed because they are automatically sourced from the disk cache. [/quote]
That was the test. And not by turn wifi off, I unplugged adsl modem. :) I am pretty sure caching is working.And the cache is in C:\Users\myusername\AppData\Local\cache on Win7.
-
Do your images load if you turn off WiFi then run the app?
-
What I have been telling you, :) they load. Cache folder exists with http, https, prepaped folders. In http folder there are cache files cache_*.cache. Cache items contains the header and the compressed image (but I don't think images are compressed).
-
Sorry, I just couldn't tell from your posts whether your tests were the same as mine. I am going to write a simple test case and maybe file a bug report on this. Caching simply doesn't work for me :-(
-
Sorry to hear that caching for you doesn't work.
What's the cache directory? Is it created?
-
Hey, did you get this to work? I would really appreciate the solution! Thanks!
-
[quote author="jkosonen" date="1313401377"]Hey, did you get this to work? I would really appreciate the solution! Thanks![/quote]
It's working for me see https://github.com/minimoog/qtwitdget/blob/master/namfactory.h
-
I talked to some of the Qt Dev's on IRC and they were very helpful. In the end it was clear that QNetworkDiskCache isn't designed to solve this problem.
So I had no other choice than to hack together my own CachedImage class in C++. I can't make the code available as it was done for a client and also the code is very specialised and not very clean. But if anyone is doing the same thing and have any questions on my implementation I'd be happy to answer them here.
-
[quote author="pdrummond" date="1313407193"]I talked to some of the Qt Dev's on IRC and they were very helpful. In the end it was clear that QNetworkDiskCache isn't designed to solve this problem.
So I had no other choice than to hack together my own CachedImage class in C++. I can't make the code available as it was done for a client and also the code is very specialised and not very clean. But if anyone is doing the same thing and have any questions on my implementation I'd be happy to answer them here.[/quote]
I would like to have that kind of an solution, but no idea where to start. Shame that you can't publish the code..