File indexing completed on 2024-04-28 05:26:01

0001 // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 #include "contactimageprovider.h"
0005 
0006 #include <Akonadi/ContactSearchJob>
0007 #include <KIO/TransferJob>
0008 #include <QApplication>
0009 #include <QDebug>
0010 #include <QDir>
0011 #include <QFileInfo>
0012 #include <QStandardPaths>
0013 #include <QThread>
0014 
0015 #include <KLocalizedString>
0016 #include <kjob.h>
0017 
0018 QQuickImageResponse *ContactImageProvider::requestImageResponse(const QString &email, const QSize &requestedSize)
0019 {
0020     return new ThumbnailResponse(email, requestedSize);
0021 }
0022 
0023 ThumbnailResponse::ThumbnailResponse(QString email, QSize size)
0024     : m_email(std::move(email))
0025     , requestedSize(size)
0026     , localFile(QStringLiteral("%1/contact_picture_provider/%2.png").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), m_email))
0027     , errorStr(QStringLiteral("Image request hasn't started"))
0028 {
0029     QImage cachedImage;
0030     if (cachedImage.load(localFile)) {
0031         m_image = cachedImage;
0032         errorStr.clear();
0033         Q_EMIT finished();
0034         return;
0035     }
0036 
0037     // Execute a request on the main thread asynchronously
0038     moveToThread(QApplication::instance()->thread());
0039     QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
0040 }
0041 
0042 void ThumbnailResponse::startRequest()
0043 {
0044     job = new Akonadi::ContactSearchJob();
0045     job->setQuery(Akonadi::ContactSearchJob::Email, m_email.toLower(), Akonadi::ContactSearchJob::ExactMatch);
0046 
0047     // Runs in the main thread, not QML thread
0048     Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
0049 
0050     // Connect to any possible outcome including abandonment
0051     // to make sure the QML thread is not left stuck forever.
0052     connect(job, &Akonadi::ContactSearchJob::finished, this, &ThumbnailResponse::prepareResult);
0053 }
0054 
0055 bool ThumbnailResponse::searchPhoto(const KContacts::AddresseeList &list)
0056 {
0057     bool foundPhoto = false;
0058     for (const KContacts::Addressee &addressee : list) {
0059         const KContacts::Picture photo = addressee.photo();
0060         if (!photo.isEmpty()) {
0061             m_photo = photo;
0062             foundPhoto = true;
0063             break;
0064         }
0065     }
0066     return foundPhoto;
0067 }
0068 
0069 void ThumbnailResponse::prepareResult()
0070 {
0071     Q_ASSERT(QThread::currentThread() == job->thread());
0072     auto searchJob = static_cast<Akonadi::ContactSearchJob *>(job);
0073     {
0074         QWriteLocker _(&lock);
0075         if (job->error() == KJob::NoError) {
0076             bool ok = false;
0077             const int contactSize(searchJob->contacts().size());
0078             if (contactSize >= 1) {
0079                 if (contactSize > 1) {
0080                     qWarning() << " more than 1 contact was found we return first contact";
0081                 }
0082 
0083                 const KContacts::Addressee addressee = searchJob->contacts().at(0);
0084                 if (searchPhoto(searchJob->contacts())) {
0085                     // We have a data raw => we can update message
0086                     if (m_photo.isIntern()) {
0087                         m_image = m_photo.data();
0088                         ok = true;
0089                     } else {
0090                         const QUrl url = QUrl::fromUserInput(m_photo.url(), QString(), QUrl::AssumeLocalFile);
0091                         if (!url.isEmpty()) {
0092                             if (url.isLocalFile()) {
0093                                 if (m_image.load(url.toLocalFile())) {
0094                                     ok = true;
0095                                 }
0096                             } else {
0097                                 QByteArray imageData;
0098                                 KIO::TransferJob *jobTransfert = KIO::get(url, KIO::NoReload);
0099                                 QObject::connect(jobTransfert, &KIO::TransferJob::data, [&imageData](KIO::Job *, const QByteArray &data) {
0100                                     imageData.append(data);
0101                                 });
0102                                 if (jobTransfert->exec()) {
0103                                     if (m_image.loadFromData(imageData)) {
0104                                         ok = true;
0105                                     }
0106                                 }
0107                             }
0108                         }
0109                     }
0110                 }
0111             }
0112             QString localPath = QFileInfo(localFile).absolutePath();
0113             QDir dir;
0114             if (!dir.exists(localPath)) {
0115                 dir.mkpath(localPath);
0116             }
0117 
0118             m_image.save(localFile);
0119 
0120             if (ok) {
0121                 errorStr.clear();
0122             } else {
0123                 errorStr = QStringLiteral("No image found");
0124             }
0125         } else if (job->error() == Akonadi::Job::UserCanceled) {
0126             errorStr = i18n("Image request has been cancelled");
0127         } else {
0128             errorStr = job->errorString();
0129             qWarning() << "ThumbnailResponse: no valid image for" << m_email << "-" << errorStr;
0130         }
0131         job = nullptr;
0132     }
0133     Q_EMIT finished();
0134 }
0135 
0136 void ThumbnailResponse::doCancel()
0137 {
0138     // Runs in the main thread, not QML thread
0139     if (job) {
0140         Q_ASSERT(QThread::currentThread() == job->thread());
0141         job->kill();
0142     }
0143 }
0144 
0145 QQuickTextureFactory *ThumbnailResponse::textureFactory() const
0146 {
0147     QReadLocker _(&lock);
0148     return QQuickTextureFactory::textureFactoryForImage(m_image);
0149 }
0150 
0151 QString ThumbnailResponse::errorString() const
0152 {
0153     QReadLocker _(&lock);
0154     return errorStr;
0155 }
0156 
0157 void ThumbnailResponse::cancel()
0158 {
0159     QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel, Qt::QueuedConnection);
0160 }