File indexing completed on 2025-01-19 03:52:59
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2013-11-18 0007 * Description : a tool to export items to Google web services 0008 * 0009 * SPDX-FileCopyrightText: 2013 by Pankaj Kumar <me at panks dot me> 0010 * SPDX-FileCopyrightText: 2013-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 "gdtalker.h" 0017 0018 // Qt includes 0019 0020 #include <QMimeDatabase> 0021 #include <QApplication> 0022 #include <QJsonDocument> 0023 #include <QJsonParseError> 0024 #include <QJsonObject> 0025 #include <QJsonValue> 0026 #include <QJsonArray> 0027 #include <QByteArray> 0028 #include <QFileInfo> 0029 #include <QMessageBox> 0030 #include <QUrlQuery> 0031 0032 // KDE includes 0033 0034 #include <klocalizedstring.h> 0035 0036 // Local includes 0037 0038 #include "wstoolutils.h" 0039 #include "digikam_version.h" 0040 #include "gswindow.h" 0041 #include "gdmpform.h" 0042 #include "digikam_debug.h" 0043 #include "previewloadthread.h" 0044 #include "dmetadata.h" 0045 0046 using namespace Digikam; 0047 0048 namespace DigikamGenericGoogleServicesPlugin 0049 { 0050 0051 static bool gdriveLessThan(const GSFolder& p1, const GSFolder& p2) 0052 { 0053 return (p1.title.toLower() < p2.title.toLower()); 0054 } 0055 0056 class Q_DECL_HIDDEN GDTalker::Private 0057 { 0058 public: 0059 0060 enum State 0061 { 0062 GD_LOGOUT = -1, 0063 GD_LISTFOLDERS = 0, 0064 GD_CREATEFOLDER, 0065 GD_ADDPHOTO, 0066 GD_USERNAME, 0067 }; 0068 0069 public: 0070 0071 explicit Private() 0072 : apiUrl(QLatin1String("https://www.googleapis.com/drive/v2/%1")), 0073 uploadUrl(QLatin1String("https://www.googleapis.com/upload/drive/v2/files")), 0074 rootid(QLatin1String("root")), 0075 rootfoldername(QLatin1String("GoogleDrive Root")), 0076 state(GD_LOGOUT), 0077 listPhotoId(QStringList()) 0078 { 0079 } 0080 0081 public: 0082 0083 QString apiUrl; 0084 QString uploadUrl; 0085 QString rootid; 0086 QString rootfoldername; 0087 QString username; 0088 State state; 0089 QStringList listPhotoId; 0090 }; 0091 0092 GDTalker::GDTalker(QWidget* const parent) 0093 : GSTalkerBase(parent, QStringList(QLatin1String("https://www.googleapis.com/auth/drive")), QLatin1String("GoogleDrive")), 0094 d(new Private) 0095 { 0096 connect(m_service->networkAccessManager(), SIGNAL(finished(QNetworkReply*)), 0097 this, SLOT(slotFinished(QNetworkReply*))); 0098 0099 connect(this, SIGNAL(signalReadyToUpload()), 0100 this, SLOT(slotUploadPhoto())); 0101 } 0102 0103 GDTalker::~GDTalker() 0104 { 0105 if (m_reply) 0106 { 0107 m_reply->abort(); 0108 } 0109 0110 WSToolUtils::removeTemporaryDir("google"); 0111 0112 delete d; 0113 } 0114 0115 /** 0116 * Gets username 0117 */ 0118 void GDTalker::getUserName() 0119 { 0120 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getUserName"; 0121 0122 QUrl url(d->apiUrl.arg(QLatin1String("about"))); 0123 0124 m_reply = m_service->get(url); 0125 0126 d->state = Private::GD_USERNAME; 0127 0128 Q_EMIT signalBusy(true); 0129 } 0130 0131 /** 0132 * Gets list of folder of user in json format 0133 */ 0134 void GDTalker::listFolders() 0135 { 0136 QUrl url(d->apiUrl.arg(QLatin1String("files"))); 0137 0138 QUrlQuery q; 0139 q.addQueryItem(QLatin1String("q"), QLatin1String("mimeType = 'application/vnd.google-apps.folder'")); 0140 0141 url.setQuery(q); 0142 0143 m_reply = m_service->get(url); 0144 0145 d->state = Private::GD_LISTFOLDERS; 0146 0147 Q_EMIT signalBusy(true); 0148 } 0149 0150 /** 0151 * Creates folder inside any folder(of which id is passed) 0152 */ 0153 void GDTalker::createFolder(const QString& title, const QString& id) 0154 { 0155 if (m_reply) 0156 { 0157 m_reply->abort(); 0158 m_reply = nullptr; 0159 } 0160 0161 QUrl url(d->apiUrl.arg(QLatin1String("files"))); 0162 QByteArray data; 0163 data += "{\"title\":\""; 0164 data += title.toLatin1(); 0165 data += "\",\r\n"; 0166 data += "\"parents\":"; 0167 data += "[{"; 0168 data += "\"id\":\""; 0169 data += id.toLatin1(); 0170 data += "\"}],\r\n"; 0171 data += "\"mimeType\":"; 0172 data += "\"application/vnd.google-apps.folder\""; 0173 data += "}\r\n"; 0174 0175 // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "data:" << data; 0176 0177 m_reply = m_service->post(url, data); 0178 0179 d->state = Private::GD_CREATEFOLDER; 0180 0181 Q_EMIT signalBusy(true); 0182 } 0183 0184 bool GDTalker::addPhoto(const QString& imgPath, const GSPhoto& info, 0185 const QString& id, bool original, bool rescale, int maxDim, int imageQuality) 0186 { 0187 if (m_reply) 0188 { 0189 m_reply->abort(); 0190 m_reply = nullptr; 0191 } 0192 0193 Q_EMIT signalBusy(true); 0194 0195 QString path = imgPath; 0196 0197 QMimeDatabase mimeDB; 0198 0199 if (!original && mimeDB.mimeTypeForFile(imgPath).name().startsWith(QLatin1String("image/"))) 0200 { 0201 QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); 0202 0203 if (image.isNull()) 0204 { 0205 image.load(imgPath); 0206 } 0207 0208 if (image.isNull()) 0209 { 0210 Q_EMIT signalBusy(false); 0211 return false; 0212 } 0213 0214 path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(imgPath) 0215 .baseName().trimmed() + QLatin1String(".jpg")); 0216 0217 if (rescale && (image.width() > maxDim || image.height() > maxDim)) 0218 { 0219 image = image.scaled(maxDim,maxDim,Qt::KeepAspectRatio,Qt::SmoothTransformation); 0220 } 0221 0222 image.save(path, "JPEG", imageQuality); 0223 0224 QScopedPointer<DMetadata> meta(new DMetadata); 0225 0226 if (meta->load(imgPath)) 0227 { 0228 meta->setItemDimensions(image.size()); 0229 meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL); 0230 meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); 0231 meta->save(path, true); 0232 } 0233 } 0234 0235 GDMPForm form; 0236 form.addPair(QUrl::fromLocalFile(path).fileName(), info.description, path, id); 0237 0238 if (!form.addFile(path)) 0239 { 0240 Q_EMIT signalBusy(false); 0241 return false; 0242 } 0243 0244 form.finish(); 0245 0246 QUrl url(d->uploadUrl); 0247 0248 QUrlQuery q; 0249 q.addQueryItem(QLatin1String("uploadType"), QLatin1String("multipart")); 0250 0251 url.setQuery(q); 0252 0253 QNetworkRequest netRequest(url); 0254 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); 0255 netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); 0256 netRequest.setRawHeader("Host", "www.googleapis.com"); 0257 0258 m_reply = m_service->networkAccessManager()->post(netRequest, form.formData()); 0259 0260 d->state = Private::GD_ADDPHOTO; 0261 0262 return true; 0263 } 0264 0265 void GDTalker::slotFinished(QNetworkReply* reply) 0266 { 0267 if (reply != m_reply) 0268 { 0269 return; 0270 } 0271 0272 m_reply = nullptr; 0273 0274 if (reply->error() != QNetworkReply::NoError) 0275 { 0276 Q_EMIT signalBusy(false); 0277 QMessageBox::critical(QApplication::activeWindow(), 0278 i18nc("@title:window", "Error"), reply->errorString()); 0279 0280 reply->deleteLater(); 0281 return; 0282 } 0283 0284 QByteArray buffer = reply->readAll(); 0285 0286 switch (d->state) 0287 { 0288 case (Private::GD_LOGOUT): 0289 break; 0290 case (Private::GD_LISTFOLDERS): 0291 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_LISTFOLDERS"; 0292 parseResponseListFolders(buffer); 0293 break; 0294 case (Private::GD_CREATEFOLDER): 0295 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_CREATEFOLDER"; 0296 parseResponseCreateFolder(buffer); 0297 break; 0298 case (Private::GD_ADDPHOTO): 0299 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_ADDPHOTO"; // << buffer; 0300 parseResponseAddPhoto(buffer); 0301 break; 0302 case (Private::GD_USERNAME): 0303 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_USERNAME"; // << buffer; 0304 parseResponseUserName(buffer); 0305 break; 0306 default: 0307 break; 0308 } 0309 0310 reply->deleteLater(); 0311 } 0312 0313 void GDTalker::slotUploadPhoto() 0314 { 0315 qCDebug(DIGIKAM_WEBSERVICES_LOG) << d->listPhotoId.join(QLatin1String(", ")); 0316 Q_EMIT signalUploadPhotoDone(1, QString(), d->listPhotoId); 0317 } 0318 0319 void GDTalker::parseResponseUserName(const QByteArray& data) 0320 { 0321 QJsonParseError err; 0322 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0323 0324 if (err.error != QJsonParseError::NoError) 0325 { 0326 Q_EMIT signalBusy(false); 0327 return; 0328 } 0329 0330 QJsonObject jsonObject = doc.object(); 0331 qCDebug(DIGIKAM_WEBSERVICES_LOG)<<"User Name is: " << jsonObject[QLatin1String("name")].toString(); 0332 QString temp = jsonObject[QLatin1String("name")].toString(); 0333 0334 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in parseResponseUserName"; 0335 0336 Q_EMIT signalBusy(false); 0337 Q_EMIT signalSetUserName(temp); 0338 } 0339 0340 void GDTalker::parseResponseListFolders(const QByteArray& data) 0341 { 0342 QJsonParseError err; 0343 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0344 0345 qCDebug(DIGIKAM_WEBSERVICES_LOG) << doc; 0346 0347 if (err.error != QJsonParseError::NoError) 0348 { 0349 Q_EMIT signalBusy(false); 0350 Q_EMIT signalListAlbumsDone(0,i18n("Failed to list folders"),QList<GSFolder>()); 0351 return; 0352 } 0353 0354 QJsonObject jsonObject = doc.object(); 0355 QJsonArray jsonArray = jsonObject[QLatin1String("items")].toArray(); 0356 0357 QList<GSFolder> albumList; 0358 GSFolder fps; 0359 fps.id = d->rootid; 0360 fps.title = d->rootfoldername; 0361 albumList.append(fps); 0362 0363 Q_FOREACH (const QJsonValue& value, jsonArray) 0364 { 0365 QJsonObject obj = value.toObject(); 0366 0367 // Verify if album is in trash 0368 QJsonObject labels = obj[QLatin1String("labels")].toObject(); 0369 bool trashed = labels[QLatin1String("trashed")].toBool(); 0370 0371 // Verify if album is editable 0372 bool editable = obj[QLatin1String("editable")].toBool(); 0373 0374 /* Verify if album is visualized in a folder inside My Drive 0375 * If parents is empty, album is shared by another person and not added to My Drive yet 0376 */ 0377 QJsonArray parents = obj[QLatin1String("parents")].toArray(); 0378 0379 fps.id = obj[QLatin1String("id")].toString(); 0380 fps.title = obj[QLatin1String("title")].toString(); 0381 0382 if (editable && !trashed && !parents.isEmpty()) 0383 { 0384 albumList.append(fps); 0385 } 0386 } 0387 0388 std::sort(albumList.begin(), albumList.end(), gdriveLessThan); 0389 0390 Q_EMIT signalBusy(false); 0391 Q_EMIT signalListAlbumsDone(1, QString(), albumList); 0392 } 0393 0394 void GDTalker::parseResponseCreateFolder(const QByteArray& data) 0395 { 0396 QJsonParseError err; 0397 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0398 0399 if (err.error != QJsonParseError::NoError) 0400 { 0401 Q_EMIT signalBusy(false); 0402 return; 0403 } 0404 0405 QJsonObject jsonObject = doc.object(); 0406 QString temp = jsonObject[QLatin1String("alternateLink")].toString(); 0407 bool success = false; 0408 0409 if (!(QString::compare(temp, QLatin1String(""), Qt::CaseInsensitive) == 0)) 0410 success = true; 0411 0412 Q_EMIT signalBusy(false); 0413 0414 if (!success) 0415 { 0416 Q_EMIT signalCreateFolderDone(0,i18n("Failed to create folder")); 0417 } 0418 else 0419 { 0420 Q_EMIT signalCreateFolderDone(1,QString()); 0421 } 0422 } 0423 0424 void GDTalker::parseResponseAddPhoto(const QByteArray& data) 0425 { 0426 QJsonParseError err; 0427 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0428 0429 if (err.error != QJsonParseError::NoError) 0430 { 0431 Q_EMIT signalBusy(false); 0432 return; 0433 } 0434 0435 QJsonObject jsonObject = doc.object(); 0436 QString altLink = jsonObject[QLatin1String("alternateLink")].toString(); 0437 QString photoId = jsonObject[QLatin1String("id")].toString(); 0438 bool success = false; 0439 0440 if (!(QString::compare(altLink, QLatin1String(""), Qt::CaseInsensitive) == 0)) 0441 success = true; 0442 0443 Q_EMIT signalBusy(false); 0444 0445 if (!success) 0446 { 0447 Q_EMIT signalAddPhotoDone(0, i18n("Failed to upload photo")); 0448 } 0449 else 0450 { 0451 d->listPhotoId << photoId; 0452 Q_EMIT signalAddPhotoDone(1, QString()); 0453 } 0454 } 0455 0456 void GDTalker::cancel() 0457 { 0458 if (m_reply) 0459 { 0460 m_reply->abort(); 0461 m_reply = nullptr; 0462 } 0463 0464 Q_EMIT signalBusy(false); 0465 } 0466 0467 } // namespace DigikamGenericGoogleServicesPlugin 0468 0469 #include "moc_gdtalker.cpp"