Can load remote images and cache them locally.

This commit is contained in:
Pierre HUBERT 2018-12-18 18:31:51 +01:00
parent 6e7645f17b
commit 0148f7aaa5
11 changed files with 379 additions and 3 deletions

View File

@ -31,7 +31,9 @@ SOURCES += \
helpers/conversationhelper.cpp \
data/conversationmessage.cpp \
widgets/conversationmessagewidget.cpp \
data/conversationmessageslist.cpp
data/conversationmessageslist.cpp \
helpers/imageloadhelper.cpp \
utils/filesutils.cpp
HEADERS += \
helpers/accounthelper.h \
@ -64,7 +66,10 @@ HEADERS += \
helpers/conversationhelper.h \
data/conversationmessage.h \
widgets/conversationmessagewidget.h \
data/conversationmessageslist.h
data/conversationmessageslist.h \
helpers/imageloadhelper.h \
utils/filesutils.h \
data/qlabelholder.h
FORMS += \
widgets/loginwidget.ui \

View File

@ -35,4 +35,9 @@
#define CONVERSATION_MESSAGE_MIN_LENGTH 3
#define CONVERSATION_MESSAGES_REFRESH_INTERVAL 1000
/**
* Images load manager information
*/
#define REMOTE_IMAGES_CACHE_DIRECTORY "remote_images"
#endif // CONFIG_H

41
data/qlabelholder.h Normal file
View File

@ -0,0 +1,41 @@
/**
* QLabelHolder
*
* This file should be referenced by ImageLoadHelper ONLY !!!
*
* @author Pierre HUBERT
*/
#pragma once
#include <QObject>
#include <QLabel>
/**
* This class is used to avoid memory leak if attempting to
* apply downloaded image to a deleted label
*/
class QLabelHolder : public QObject
{
Q_OBJECT
public:
QLabelHolder(QLabel *label) {
mLabel = label;
connect(label, &QLabel::destroyed, this, &QLabelHolder::deleted);
}
QLabel *label(){
return mLabel;
}
private slots:
void deleted(){
mLabel = nullptr;
}
private:
//Private fields
QLabel *mLabel;
};

163
helpers/imageloadhelper.cpp Normal file
View File

@ -0,0 +1,163 @@
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QCryptographicHash>
#include <QMap>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QImage>
#include <QImageReader>
#include <QPixmap>
#include <QLabel>
#include "imageloadhelper.h"
#include "../config.h"
#include "../utils/filesutils.h"
//This header should be called only from this file
// (QObject subclass seems not to be declarable in CPP files)
#include "../data/qlabelholder.h"
/**
* The list of pending labels for an image
*/
static QMap<QString, QList<QLabelHolder*>> privateList;
/**
* Network Access Manager used to download images
*/
static QNetworkAccessManager accessManager;
/**
* The first and the last instance of this object
*/
static ImageLoadHelper *mHelper = nullptr;
ImageLoadHelper::ImageLoadHelper(QObject *parent) : QObject(parent)
{
//Nothing yet
}
void ImageLoadHelper::Load(QLabel *label, const QString &url)
{
//Construct object if required
if(mHelper == nullptr)
mHelper = new ImageLoadHelper();
//Check if the image has already been downloaded
if(IsDownloaded(url)){
ApplyImage(label, url);
return;
}
//Check if download is already running
if(privateList.contains(url)){
QList<QLabelHolder *> swapList = privateList.take(url);
swapList.append(new QLabelHolder(label));
privateList.insert(url, swapList);
return;
}
//If we get there, we have to launch the download of the image
QList<QLabelHolder *> list;
list.append(new QLabelHolder(label));
privateList.insert(url, list);
Download(url);
}
void ImageLoadHelper::NetworkError()
{
qWarning("Image Load Helper error : Network error while connecting to server!");
}
void ImageLoadHelper::NetworkRequestFinished()
{
//If the request is a success, we can save the image, else we can not do anything
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
reply->deleteLater();
if(reply->error() != QNetworkReply::NoError){
qWarning("Can not process image because we encountered an error while downloading it!");
return;
}
//Save the image
QString url = reply->url().toString();
QImageReader reader(reply);
QImage image = reader.read();
//Check if the image could not be decode
if(image.isNull()){
qWarning("Downloaded image from %s is not valid!", url.toStdString().c_str());
return;
}
//Save the image
QString file_path = GetImageStoragePath(url);
if(!image.save(file_path, "PNG")){
qWarning("Could not save image from %s to %s !", url.toStdString().c_str(), file_path.toStdString().c_str());
return;
}
//Process the list of awaiting labels
QPixmap pixmap = QPixmap::fromImage(image);
QList<QLabelHolder *> labels = privateList.take(url);
for(QLabelHolder *currLabel : labels)
if(currLabel->label() != nullptr)
ApplyImage(currLabel->label(), pixmap);
}
void ImageLoadHelper::SSLErrors()
{
qWarning("Image Load Helper error : SSL error while connecting to server!");
}
void ImageLoadHelper::Download(const QString &url)
{
qWarning("Download image located at URL: %s", url.toStdString().c_str());
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", "ComunicMessages Images Downloader 1.0");
QNetworkReply *reply = accessManager.get(request);
connect(reply, &QNetworkReply::finished, mHelper, &ImageLoadHelper::NetworkRequestFinished);
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), mHelper, SLOT(NetworkError()));
connect(reply, &QNetworkReply::sslErrors, mHelper, &ImageLoadHelper::SSLErrors);
}
void ImageLoadHelper::ApplyImage(QLabel *label, const QString &url)
{
ApplyImage(label, QPixmap(GetImageStoragePath(url)));
}
void ImageLoadHelper::ApplyImage(QLabel *label, const QPixmap &pixmap)
{
label->setPixmap(pixmap);
}
bool ImageLoadHelper::IsDownloaded(const QString &url)
{
return QFile(GetImageStoragePath(url)).exists();
}
QString ImageLoadHelper::GetImageStoragePath(const QString &url)
{
//Containing directory
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
+ QDir::separator() + REMOTE_IMAGES_CACHE_DIRECTORY;
//Check the directory exists. If not, create it
if(!FilesUtils::CreateDirectoryIfNotExists(path))
qFatal("Could not create images cache directory.");
//Add separator to path
path += QDir::separator();
//File name
path += QString(QCryptographicHash::hash(url.toStdString().c_str(), QCryptographicHash::Md5).toHex());
return path;
}

91
helpers/imageloadhelper.h Normal file
View File

@ -0,0 +1,91 @@
/**
* ImageLoadHelper - This helper is used to
* easily load remote images and caches them
*
* @author Pierre HUBERT
*/
#ifndef IMAGELOADHELPER_H
#define IMAGELOADHELPER_H
#include <QObject>
class QLabel;
class ImageLoadHelper : public QObject
{
Q_OBJECT
public:
/**
* Load an image into a label
*
* @param label The label where the image will be applied
* @param url The URL of the target image
*/
static void Load(QLabel *label, const QString &url);
signals:
private slots:
/**
* Slot called in case of SSL error
*/
void SSLErrors();
/**
* Slot called in case of network error
*/
void NetworkError();
/**
* Slot called when a network request finished
*/
void NetworkRequestFinished();
private:
//This constructor must be private
explicit ImageLoadHelper(QObject *parent = nullptr);
/**
* Download an image
*
* @param url The URL of the image to download
*/
static void Download(const QString &url);
/**
* Apply an image to a label
*
* @param label Target label which will receive the image
* @param url The remote URL of the image
*/
static void ApplyImage(QLabel *label, const QString &url);
/**
* Apply an image to a label
*
* @param label Target label which will receive the image
* @param pixamp The pixmap to apply to the label
*/
static void ApplyImage(QLabel *label, const QPixmap &pixmap);
/**
* Check out whether an image has been already downloaded
*
* @param url The URL of the target image
* @return TRUE for a success / FALSE else
*/
static bool IsDownloaded(const QString &url);
/**
* Get the storage path of a remote image
*
* @param url The URL of the remote image
* @return Full path to the image
*/
static QString GetImageStoragePath(const QString &url);
};
#endif // IMAGELOADHELPER_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

View File

@ -2,5 +2,6 @@
<qresource prefix="/">
<file>baseline_people_black_48dp.png</file>
<file>baseline_access_time_black_48dp.png</file>
<file>baseline_person_black_48dp.png</file>
</qresource>
</RCC>

19
utils/filesutils.cpp Normal file
View File

@ -0,0 +1,19 @@
#include <QDir>
#include "filesutils.h"
FilesUtils::FilesUtils()
{
}
bool FilesUtils::CreateDirectoryIfNotExists(const QString &path)
{
QDir dir(path);
//Check if the directory already exists
if(dir.exists())
return true;
return dir.mkpath(".");
}

28
utils/filesutils.h Normal file
View File

@ -0,0 +1,28 @@
/**
* Files utilities
*
* @author Pierre HUBERT
*/
#ifndef FILESUTILS_H
#define FILESUTILS_H
#include <QString>
class FilesUtils
{
public:
FilesUtils();
/**
* Create the specified directory and all its parents if they do not
* exists
*
* @param path The path of the folder to check
* @return TRUE if the folder exists or has been successfully created /
* FALSE else
*/
static bool CreateDirectoryIfNotExists(const QString &path);
};
#endif // FILESUTILS_H

View File

@ -2,6 +2,7 @@
#include "ui_conversationmessagewidget.h"
#include "../data/user.h"
#include "../data/conversationmessage.h"
#include "../helpers/imageloadhelper.h"
ConversationMessageWidget::ConversationMessageWidget(QWidget *parent) :
QWidget(parent),
@ -19,4 +20,5 @@ void ConversationMessageWidget::setMessage(const ConversationMessage &message, c
{
ui->nameLabel->setText(user.displayName());
ui->messageLabel->setText(message.message());
ImageLoadHelper::Load(ui->accountImageLabel, user.accountImage());
}

View File

@ -14,6 +14,25 @@
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="accountImageLabel">
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../res/ressources.qrc">:/baseline_person_black_48dp.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="nameLabel">
<property name="sizePolicy">
@ -54,6 +73,8 @@
</item>
</layout>
</widget>
<resources/>
<resources>
<include location="../res/ressources.qrc"/>
</resources>
<connections/>
</ui>