File indexing completed on 2025-01-05 03:53:35
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2012-02-12 0007 * Description : a tool to export images to IPFS web service 0008 * 0009 * SPDX-FileCopyrightText: 2018 by Amar Lakshya <amar dot lakshya at xaviers dot edu dot in> 0010 * SPDX-FileCopyrightText: 2018-2020 by Caulier Gilles <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "ipfstalker.h" 0017 0018 // Qt includes 0019 0020 #include <QUrl> 0021 #include <QFile> 0022 #include <QQueue> 0023 #include <QUrlQuery> 0024 #include <QFileInfo> 0025 #include <QHttpMultiPart> 0026 #include <QJsonDocument> 0027 #include <QJsonObject> 0028 #include <QNetworkReply> 0029 0030 // KDE includes 0031 0032 #include <klocalizedstring.h> 0033 0034 // Local includes 0035 0036 #include "digikam_debug.h" 0037 #include "networkmanager.h" 0038 0039 namespace DigikamGenericIpfsPlugin 0040 { 0041 0042 class Q_DECL_HIDDEN IpfsTalker::Private 0043 { 0044 public: 0045 0046 explicit Private() 0047 : ipfsUploadUrl(QLatin1String("https://api.globalupload.io/transport/add")), 0048 workTimer (0), 0049 reply (nullptr), 0050 image (nullptr), 0051 netMngr (nullptr) 0052 { 0053 } 0054 0055 // The ipfs upload url 0056 0057 const QString ipfsUploadUrl; 0058 0059 // Work queue 0060 0061 QQueue<IpfsTalkerAction> workQueue; 0062 0063 // ID of timer triggering on idle (0ms) 0064 0065 int workTimer; 0066 0067 // Current QNetworkReply 0068 0069 QNetworkReply* reply; 0070 0071 // Current image being uploaded 0072 0073 QFile* image; 0074 0075 // The QNetworkAccessManager used for connections 0076 0077 QNetworkAccessManager* netMngr; 0078 }; 0079 0080 IpfsTalker::IpfsTalker(QObject* const parent) 0081 : QObject(parent), 0082 d (new Private) 0083 { 0084 d->netMngr = NetworkManager::instance()->getNetworkManager(this); 0085 } 0086 0087 IpfsTalker::~IpfsTalker() 0088 { 0089 // Disconnect all signals as cancelAllWork may emit 0090 0091 disconnect(this, nullptr, nullptr, nullptr); 0092 cancelAllWork(); 0093 0094 delete d; 0095 } 0096 0097 unsigned int IpfsTalker::workQueueLength() 0098 { 0099 return d->workQueue.size(); 0100 } 0101 0102 void IpfsTalker::queueWork(const IpfsTalkerAction& action) 0103 { 0104 d->workQueue.enqueue(action); 0105 startWorkTimer(); 0106 } 0107 0108 void IpfsTalker::cancelAllWork() 0109 { 0110 stopWorkTimer(); 0111 0112 if (d->reply) 0113 { 0114 d->reply->abort(); 0115 } 0116 0117 // Should error be emitted for those actions? 0118 0119 while (!d->workQueue.empty()) 0120 { 0121 d->workQueue.dequeue(); 0122 } 0123 } 0124 0125 void IpfsTalker::uploadProgress(qint64 sent, qint64 total) 0126 { 0127 if (total > 0) // Don't divide by 0 0128 { 0129 Q_EMIT progress((sent * 100) / total, d->workQueue.first()); 0130 } 0131 } 0132 0133 void IpfsTalker::replyFinished() 0134 { 0135 auto* reply = d->reply; 0136 reply->deleteLater(); 0137 d->reply = nullptr; 0138 0139 if (d->image) 0140 { 0141 delete d->image; 0142 d->image = nullptr; 0143 } 0144 0145 if (d->workQueue.empty()) 0146 { 0147 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received result without request"; 0148 return; 0149 } 0150 0151 // toInt() returns 0 if conversion fails. That fits nicely already. 0152 0153 int netCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 0154 auto response = QJsonDocument::fromJson(reply->readAll()); 0155 0156 if ((netCode == 200) && !response.isEmpty()) 0157 { 0158 /* Success! */ 0159 0160 IpfsTalkerResult result; 0161 result.action = &d->workQueue.first(); 0162 0163 switch (result.action->type) 0164 { 0165 case IpfsTalkerActionType::IMG_UPLOAD: 0166 { 0167 result.image.name = response.object()[QLatin1String("Name")].toString(); 0168 result.image.size = response.object()[QLatin1String("Size")].toInt(); 0169 result.image.url = QLatin1String("https://ipfs.io/ipfs/") + response.object()[QLatin1String("Hash")].toString(); 0170 break; 0171 } 0172 0173 default: 0174 { 0175 qCWarning(DIGIKAM_WEBSERVICES_LOG) << "Unexpected action"; 0176 qCDebug(DIGIKAM_WEBSERVICES_LOG) << response.toJson(); 0177 break; 0178 } 0179 } 0180 0181 Q_EMIT success(result); 0182 } 0183 else 0184 { 0185 if (netCode == 403) 0186 { 0187 /* HTTP 403 Forbidden -> Invalid token? 0188 * That needs to be handled internally, so don't Q_EMIT progress 0189 * and keep the action in the queue for later retries. 0190 */ 0191 return; 0192 } 0193 else 0194 { 0195 /* 0196 * Failed. 0197 */ 0198 auto msg = response.object()[QLatin1String("data")] 0199 .toObject()[QLatin1String("error")] 0200 .toString(QLatin1String("Could not read response.")); 0201 0202 Q_EMIT error(msg, d->workQueue.first()); 0203 } 0204 } 0205 0206 // Next work item. 0207 0208 d->workQueue.dequeue(); 0209 startWorkTimer(); 0210 } 0211 0212 void IpfsTalker::timerEvent(QTimerEvent* event) 0213 { 0214 if (event->timerId() != d->workTimer) 0215 { 0216 QObject::timerEvent(event); 0217 return; 0218 } 0219 0220 event->accept(); 0221 0222 // One-shot only. 0223 0224 QObject::killTimer(event->timerId()); 0225 d->workTimer = 0; 0226 0227 doWork(); 0228 } 0229 0230 void IpfsTalker::startWorkTimer() 0231 { 0232 if (!d->workQueue.empty() && (d->workTimer == 0)) 0233 { 0234 d->workTimer = QObject::startTimer(0); 0235 Q_EMIT busy(true); 0236 } 0237 else 0238 { 0239 Q_EMIT busy(false); 0240 } 0241 } 0242 0243 void IpfsTalker::stopWorkTimer() 0244 { 0245 if (d->workTimer != 0) 0246 { 0247 QObject::killTimer(d->workTimer); 0248 d->workTimer = 0; 0249 } 0250 } 0251 0252 void IpfsTalker::doWork() 0253 { 0254 if (d->workQueue.empty() || (d->reply != nullptr)) 0255 { 0256 return; 0257 } 0258 0259 auto &work = d->workQueue.first(); 0260 0261 switch (work.type) 0262 { 0263 case IpfsTalkerActionType::IMG_UPLOAD: 0264 { 0265 d->image = new QFile(work.upload.imgpath); 0266 0267 if (!d->image->open(QIODevice::ReadOnly)) 0268 { 0269 delete d->image; 0270 d->image = nullptr; 0271 0272 /* Failed. */ 0273 0274 Q_EMIT error(i18n("Could not open file"), d->workQueue.first()); 0275 0276 d->workQueue.dequeue(); 0277 doWork(); 0278 return; 0279 } 0280 0281 /* Set ownership to d->image to delete that as well. */ 0282 0283 auto* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, d->image); 0284 QHttpPart title; 0285 title.setHeader(QNetworkRequest::ContentDispositionHeader, 0286 QLatin1String("form-data; name=\"keyphrase\"")); 0287 multipart->append(title); 0288 0289 QHttpPart description; 0290 description.setHeader(QNetworkRequest::ContentDispositionHeader, 0291 QLatin1String("form-data; name=\"user\"")); 0292 multipart->append(description); 0293 0294 QHttpPart image; 0295 image.setHeader(QNetworkRequest::ContentDispositionHeader, 0296 QVariant(QString::fromLatin1("form-data; name=\"file\"; filename=\"%1\"") 0297 .arg(QLatin1String(QFileInfo(work.upload.imgpath).fileName().toUtf8().toPercentEncoding())))); 0298 image.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("image/jpeg")); 0299 image.setBodyDevice(d->image); 0300 multipart->append(image); 0301 QNetworkRequest request(QUrl(d->ipfsUploadUrl)); 0302 d->reply = d->netMngr->post(request, multipart); 0303 0304 break; 0305 } 0306 } 0307 0308 if (d->reply) 0309 { 0310 connect(d->reply, &QNetworkReply::uploadProgress, 0311 this, &IpfsTalker::uploadProgress); 0312 0313 connect(d->reply, &QNetworkReply::finished, 0314 this, &IpfsTalker::replyFinished); 0315 } 0316 } 0317 0318 } // namespace DigikamGenericIpfsPlugin 0319 0320 #include "moc_ipfstalker.cpp"