File indexing completed on 2024-09-15 10:31:43
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 }