mirror of
				https://gitlab.com/comunic/comunicmessages
				synced 2025-11-04 04:04:10 +00:00 
			
		
		
		
	Can load remote images and cache them locally.
This commit is contained in:
		@@ -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 \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								config.h
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								config.h
									
									
									
									
									
								
							@@ -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
									
								
							
							
						
						
									
										41
									
								
								data/qlabelholder.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										163
									
								
								helpers/imageloadhelper.cpp
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										91
									
								
								helpers/imageloadhelper.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								res/baseline_person_black_48dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								res/baseline_person_black_48dp.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 544 B  | 
@@ -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
									
								
							
							
						
						
									
										19
									
								
								utils/filesutils.cpp
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										28
									
								
								utils/filesutils.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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());
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user