File indexing completed on 2025-01-05 03:53:30
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: 2015 by Shourya Singh Gupta <shouryasgupta at gmail dot com> 0011 * SPDX-FileCopyrightText: 2013-2020 by Caulier Gilles <caulier dot gilles at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "gswindow.h" 0018 0019 // Qt includes 0020 0021 #include <QWindow> 0022 #include <QMessageBox> 0023 #include <QPushButton> 0024 #include <QButtonGroup> 0025 #include <QProgressDialog> 0026 #include <QPixmap> 0027 #include <QCheckBox> 0028 #include <QStringList> 0029 #include <QSpinBox> 0030 #include <QFileInfo> 0031 #include <QDesktopServices> 0032 #include <QScopedPointer> 0033 0034 // KDE includes 0035 0036 #include <klocalizedstring.h> 0037 #include <ksharedconfig.h> 0038 #include <kconfiggroup.h> 0039 0040 // Local includes 0041 0042 #include "wstoolutils.h" 0043 #include "ditemslist.h" 0044 #include "digikam_version.h" 0045 #include "dprogresswdg.h" 0046 #include "gdtalker.h" 0047 #include "gsnewalbumdlg.h" 0048 #include "gswidget.h" 0049 #include "gptalker.h" 0050 #include "gsreplacedlg.h" 0051 #include "digikam_debug.h" 0052 #include "dfileoperations.h" 0053 0054 namespace DigikamGenericGoogleServicesPlugin 0055 { 0056 0057 class Q_DECL_HIDDEN GSWindow::Private 0058 { 0059 public: 0060 0061 explicit Private() 0062 : imagesCount (0), 0063 imagesTotal (0), 0064 renamingOpt (0), 0065 service (GoogleService::GPhotoImport), 0066 widget (nullptr), 0067 albumDlg (nullptr), 0068 gphotoAlbumDlg(nullptr), 0069 talker (nullptr), 0070 gphotoTalker (nullptr), 0071 iface (nullptr) 0072 { 0073 } 0074 0075 unsigned int imagesCount; 0076 unsigned int imagesTotal; 0077 int renamingOpt; 0078 0079 QString serviceName; 0080 QString toolName; 0081 GoogleService service; 0082 QString tmp; 0083 0084 GSWidget* widget; 0085 GSNewAlbumDlg* albumDlg; 0086 GSNewAlbumDlg* gphotoAlbumDlg; 0087 0088 GDTalker* talker; 0089 GPTalker* gphotoTalker; 0090 0091 QString currentAlbumId; 0092 QString newFolderTitle; 0093 0094 QList< QPair<QUrl, GSPhoto> > transferQueue; 0095 QList< QPair<QUrl, GSPhoto> > uploadQueue; 0096 0097 DInfoInterface* iface; 0098 }; 0099 0100 GSWindow::GSWindow(DInfoInterface* const iface, 0101 QWidget* const /*parent*/, 0102 const QString& serviceName) 0103 : WSToolDialog(nullptr, QString::fromLatin1("%1Export Dialog").arg(serviceName)), 0104 d (new Private) 0105 { 0106 d->iface = iface; 0107 d->serviceName = serviceName; 0108 0109 if (QString::compare(d->serviceName, QLatin1String("googledriveexport"), 0110 Qt::CaseInsensitive) == 0) 0111 { 0112 d->service = GoogleService::GDrive; 0113 d->toolName = QLatin1String("Google Drive"); 0114 } 0115 else if (QString::compare(d->serviceName, QLatin1String("googlephotoexport"), 0116 Qt::CaseInsensitive) == 0) 0117 { 0118 d->service = GoogleService::GPhotoExport; 0119 d->toolName = QLatin1String("Google Photos"); 0120 } 0121 else 0122 { 0123 d->service = GoogleService::GPhotoImport; 0124 d->toolName = QLatin1String("Google Photos"); 0125 } 0126 0127 d->tmp = WSToolUtils::makeTemporaryDir("google").absolutePath() + QLatin1Char('/');; 0128 d->widget = new GSWidget(this, d->iface, d->service, d->toolName); 0129 0130 setMainWidget(d->widget); 0131 setModal(false); 0132 0133 switch (d->service) 0134 { 0135 case GoogleService::GDrive: 0136 { 0137 setWindowTitle(i18nc("@title:window", "Export to Google Drive")); 0138 0139 startButton()->setText(i18nc("@action:button", "Start Upload")); 0140 startButton()->setToolTip(i18nc("@info:tooltip, button", "Start upload to Google Drive")); 0141 0142 d->widget->setMinimumSize(700, 500); 0143 0144 d->albumDlg = new GSNewAlbumDlg(this, d->serviceName, d->toolName); 0145 d->talker = new GDTalker(this); 0146 0147 connect(d->talker,SIGNAL(signalBusy(bool)), 0148 this,SLOT(slotBusy(bool))); 0149 0150 connect(d->talker,SIGNAL(signalAccessTokenObtained()), 0151 this,SLOT(slotAccessTokenObtained())); 0152 0153 connect(d->talker, SIGNAL(signalAuthenticationRefused()), 0154 this,SLOT(slotAuthenticationRefused())); 0155 0156 connect(d->talker,SIGNAL(signalSetUserName(QString)), 0157 this,SLOT(slotSetUserName(QString))); 0158 0159 connect(d->talker,SIGNAL(signalListAlbumsDone(int,QString,QList<GSFolder>)), 0160 this,SLOT(slotListAlbumsDone(int,QString,QList<GSFolder>))); 0161 0162 connect(d->talker,SIGNAL(signalCreateFolderDone(int,QString)), 0163 this,SLOT(slotCreateFolderDone(int,QString))); 0164 0165 connect(d->talker,SIGNAL(signalAddPhotoDone(int,QString)), 0166 this,SLOT(slotAddPhotoDone(int,QString))); 0167 0168 connect(d->talker, SIGNAL(signalUploadPhotoDone(int,QString,QStringList)), 0169 this, SLOT(slotUploadPhotoDone(int,QString,QStringList))); 0170 0171 readSettings(); 0172 buttonStateChange(false); 0173 0174 d->talker->doOAuth(); 0175 0176 break; 0177 } 0178 0179 case GoogleService::GPhotoImport: 0180 case GoogleService::GPhotoExport: 0181 { 0182 if (d->service == GoogleService::GPhotoExport) 0183 { 0184 setWindowTitle(i18nc("@title:window", "Export to Google Photos Service")); 0185 0186 startButton()->setText(i18nc("@action:button", "Start Upload")); 0187 startButton()->setToolTip(i18nc("@info:tooltip, button", "Start upload to Google Photos service")); 0188 0189 d->widget->setMinimumSize(700, 500); 0190 } 0191 else 0192 { 0193 setWindowTitle(i18nc("@title:window", "Import from Google Photos Service")); 0194 0195 startButton()->setText(i18nc("@action:button", "Start Download")); 0196 startButton()->setToolTip(i18nc("@info:tooltip, button", "Start download from Google Photos service")); 0197 0198 d->widget->setMinimumSize(300, 400); 0199 } 0200 0201 d->gphotoAlbumDlg = new GSNewAlbumDlg(this, d->serviceName, d->toolName); 0202 d->gphotoTalker = new GPTalker(this); 0203 0204 connect(d->gphotoTalker, SIGNAL(signalBusy(bool)), 0205 this, SLOT(slotBusy(bool))); 0206 0207 connect(d->gphotoTalker,SIGNAL(signalSetUserName(QString)), 0208 this,SLOT(slotSetUserName(QString))); 0209 0210 connect(d->gphotoTalker, SIGNAL(signalAccessTokenObtained()), 0211 this, SLOT(slotAccessTokenObtained())); 0212 0213 connect(d->gphotoTalker, SIGNAL(signalAuthenticationRefused()), 0214 this,SLOT(slotAuthenticationRefused())); 0215 0216 connect(d->gphotoTalker, SIGNAL(signalListAlbumsDone(int,QString,QList<GSFolder>)), 0217 this, SLOT(slotListAlbumsDone(int,QString,QList<GSFolder>))); 0218 0219 connect(d->gphotoTalker, SIGNAL(signalCreateAlbumDone(int,QString,QString)), 0220 this, SLOT(slotCreateFolderDone(int,QString,QString))); 0221 0222 connect(d->gphotoTalker, SIGNAL(signalAddPhotoDone(int,QString)), 0223 this, SLOT(slotAddPhotoDone(int,QString))); 0224 0225 connect(d->gphotoTalker, SIGNAL(signalUploadPhotoDone(int,QString,QStringList)), 0226 this, SLOT(slotUploadPhotoDone(int,QString,QStringList))); 0227 0228 connect(d->gphotoTalker, SIGNAL(signalGetPhotoDone(int,QString,QByteArray,QString)), 0229 this, SLOT(slotGetPhotoDone(int,QString,QByteArray,QString))); 0230 0231 readSettings(); 0232 buttonStateChange(false); 0233 0234 d->gphotoTalker->doOAuth(); 0235 0236 break; 0237 } 0238 } 0239 0240 connect(d->widget->imagesList(), SIGNAL(signalImageListChanged()), 0241 this, SLOT(slotImageListChanged())); 0242 0243 connect(d->widget->getChangeUserBtn(), SIGNAL(clicked()), 0244 this, SLOT(slotUserChangeRequest())); 0245 0246 connect(d->widget->getNewAlbmBtn(), SIGNAL(clicked()), 0247 this,SLOT(slotNewAlbumRequest())); 0248 0249 connect(d->widget->getReloadBtn(), SIGNAL(clicked()), 0250 this, SLOT(slotReloadAlbumsRequest())); 0251 0252 connect(startButton(), SIGNAL(clicked()), 0253 this, SLOT(slotStartTransfer())); 0254 0255 connect(this, SIGNAL(finished(int)), 0256 this, SLOT(slotFinished())); 0257 } 0258 0259 GSWindow::~GSWindow() 0260 { 0261 d->transferQueue.clear(); 0262 0263 delete d->gphotoTalker; 0264 delete d->talker; 0265 delete d; 0266 } 0267 0268 void GSWindow::reactivate() 0269 { 0270 d->widget->imagesList()->loadImagesFromCurrentSelection(); 0271 d->widget->progressBar()->hide(); 0272 0273 show(); 0274 } 0275 0276 void GSWindow::readSettings() 0277 { 0278 KSharedConfigPtr config = KSharedConfig::openConfig(); 0279 KConfigGroup grp; 0280 0281 switch (d->service) 0282 { 0283 case GoogleService::GDrive: 0284 grp = config->group(QLatin1String("Google Drive Settings")); 0285 break; 0286 0287 default: 0288 grp = config->group(QLatin1String("Google Photo Settings")); 0289 break; 0290 } 0291 0292 d->currentAlbumId = grp.readEntry("Current Album", QString()); 0293 0294 if (grp.readEntry("Resize", false)) 0295 { 0296 d->widget->getResizeCheckBox()->setChecked(true); 0297 d->widget->getDimensionSpB()->setEnabled(true); 0298 } 0299 else 0300 { 0301 d->widget->getResizeCheckBox()->setChecked(false); 0302 d->widget->getDimensionSpB()->setEnabled(false); 0303 } 0304 0305 d->widget->getOriginalCheckBox()->setChecked(grp.readEntry("Upload Original", false)); 0306 d->widget->getPhotoIdCheckBox()->setChecked(grp.readEntry("Write PhotoID", true)); 0307 d->widget->getDimensionSpB()->setValue(grp.readEntry("Maximum Width", 1600)); 0308 d->widget->getImgQualitySpB()->setValue(grp.readEntry("Image Quality", 90)); 0309 0310 if (d->service == GoogleService::GPhotoExport && d->widget->m_tagsBGrp) 0311 { 0312 d->widget->m_tagsBGrp->button(grp.readEntry("Tag Paths", 0))->setChecked(true); 0313 } 0314 } 0315 0316 void GSWindow::writeSettings() 0317 { 0318 KSharedConfigPtr config = KSharedConfig::openConfig(); 0319 KConfigGroup grp; 0320 0321 switch (d->service) 0322 { 0323 case GoogleService::GDrive: 0324 grp = config->group(QLatin1String("Google Drive Settings")); 0325 break; 0326 0327 default: 0328 grp = config->group(QLatin1String("Google Photo Settings")); 0329 break; 0330 } 0331 0332 grp.writeEntry("Current Album", d->currentAlbumId); 0333 grp.writeEntry("Resize", d->widget->getResizeCheckBox()->isChecked()); 0334 grp.writeEntry("Upload Original", d->widget->getOriginalCheckBox()->isChecked()); 0335 grp.writeEntry("Write PhotoID", d->widget->getPhotoIdCheckBox()->isChecked()); 0336 grp.writeEntry("Maximum Width", d->widget->getDimensionSpB()->value()); 0337 grp.writeEntry("Image Quality", d->widget->getImgQualitySpB()->value()); 0338 0339 if ((d->service == GoogleService::GPhotoExport) && d->widget->m_tagsBGrp) 0340 { 0341 grp.writeEntry("Tag Paths", d->widget->m_tagsBGrp->checkedId()); 0342 } 0343 } 0344 0345 void GSWindow::slotSetUserName(const QString& msg) 0346 { 0347 d->widget->updateLabels(msg); 0348 } 0349 0350 void GSWindow::slotListPhotosDoneForDownload(int errCode, 0351 const QString& errMsg, 0352 const QList <GSPhoto>& photosList) 0353 { 0354 disconnect(d->gphotoTalker, SIGNAL(signalListPhotosDone(int,QString,QList<GSPhoto>)), 0355 this, SLOT(slotListPhotosDoneForDownload(int,QString,QList<GSPhoto>))); 0356 0357 if (errCode == 0) 0358 { 0359 QMessageBox::critical(this, i18nc("@title:window", "Error"), 0360 i18nc("@info", "Google Photos call failed: %1\n", errMsg)); 0361 return; 0362 } 0363 0364 typedef QPair<QUrl, GSPhoto> Pair; 0365 d->transferQueue.clear(); 0366 QList<GSPhoto>::const_iterator itPWP; 0367 0368 for (itPWP = photosList.begin() ; itPWP != photosList.end() ; ++itPWP) 0369 { 0370 d->transferQueue.append(Pair((*itPWP).originalURL, (*itPWP))); 0371 } 0372 0373 if (d->transferQueue.isEmpty()) 0374 { 0375 return; 0376 } 0377 0378 d->currentAlbumId = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); 0379 d->imagesTotal = d->transferQueue.count(); 0380 d->imagesCount = 0; 0381 0382 d->widget->progressBar()->setFormat(i18nc("@info: progress bar", "%v / %m")); 0383 d->widget->progressBar()->show(); 0384 0385 d->renamingOpt = 0; 0386 0387 // start download with first photo in queue 0388 0389 downloadNextPhoto(); 0390 } 0391 0392 void GSWindow::slotListAlbumsDone(int code, const QString& errMsg, const QList <GSFolder>& list) 0393 { 0394 switch (d->service) 0395 { 0396 case GoogleService::GDrive: 0397 0398 if (code == 0) 0399 { 0400 QMessageBox::critical(this, i18nc("@title:window", "Error"), 0401 i18nc("@info", "Google Drive call failed: %1\n", errMsg)); 0402 return; 0403 } 0404 0405 d->widget->getAlbumsCoB()->clear(); 0406 0407 for (int i = 0 ; i < list.size() ; ++i) 0408 { 0409 d->widget->getAlbumsCoB()->addItem(QIcon::fromTheme(QLatin1String("system-users")), 0410 list.value(i).title, list.value(i).id); 0411 0412 if (d->widget->getAlbumTitle() == list.at(i).title) 0413 { 0414 d->currentAlbumId = list.at(i).id; 0415 d->widget->getAlbumsCoB()->setCurrentIndex(i); 0416 } 0417 else if (d->currentAlbumId == list.value(i).id) 0418 { 0419 d->widget->getAlbumsCoB()->setCurrentIndex(i); 0420 } 0421 } 0422 0423 buttonStateChange(true); 0424 d->talker->getUserName(); 0425 break; 0426 0427 default: 0428 0429 if (code == 0) 0430 { 0431 QMessageBox::critical(this, i18nc("@title:window", "Error"), 0432 i18nc("@info", "Google Photos call failed: %1\n", errMsg)); 0433 return; 0434 } 0435 0436 d->widget->getAlbumsCoB()->clear(); 0437 0438 for (int i = 0 ; i < list.size() ; ++i) 0439 { 0440 if ((d->service == GoogleService::GPhotoImport) && 0441 (list.at(i).title == QLatin1String("<auto-create>"))) 0442 { 0443 // remove <auto-create> album 0444 continue; 0445 } 0446 0447 QString albumIcon; 0448 0449 if (list.at(i).isWriteable) 0450 { 0451 albumIcon = QLatin1String("folder"); 0452 } 0453 else 0454 { 0455 albumIcon = QLatin1String("folder-locked"); 0456 } 0457 0458 d->widget->getAlbumsCoB()->addItem(QIcon::fromTheme(albumIcon), list.at(i).title, list.at(i).id); 0459 0460 if (d->widget->getAlbumTitle() == list.at(i).title) 0461 { 0462 d->currentAlbumId = list.at(i).id; 0463 d->widget->getAlbumsCoB()->setCurrentIndex(i); 0464 } 0465 else if (d->currentAlbumId == list.at(i).id) 0466 { 0467 d->widget->getAlbumsCoB()->setCurrentIndex(i); 0468 } 0469 0470 buttonStateChange(true); 0471 } 0472 break; 0473 } 0474 } 0475 0476 void GSWindow::slotBusy(bool val) 0477 { 0478 setCursor(val ? Qt::WaitCursor 0479 : Qt::ArrowCursor); 0480 0481 d->widget->imagesList()->enableControlButtons(!val); 0482 d->widget->imagesList()->enableDragAndDrop(!val); 0483 d->widget->getChangeUserBtn()->setEnabled(!val); 0484 d->widget->getOptionsBox()->setEnabled(!val); 0485 buttonStateChange(!val); 0486 } 0487 0488 void GSWindow::slotStartTransfer() 0489 { 0490 d->widget->imagesList()->clearProcessedStatus(); 0491 0492 switch (d->service) 0493 { 0494 case GoogleService::GDrive: 0495 case GoogleService::GPhotoExport: 0496 { 0497 if (d->widget->imagesList()->imageUrls().isEmpty()) 0498 { 0499 QMessageBox::critical(this, i18nc("@title:window", "Error"), 0500 i18nc("@info", "No image selected. Please select which images should be uploaded.")); 0501 return; 0502 } 0503 0504 break; 0505 } 0506 0507 case GoogleService::GPhotoImport: 0508 break; 0509 } 0510 0511 switch (d->service) 0512 { 0513 case GoogleService::GDrive: 0514 { 0515 if (!(d->talker->authenticated())) 0516 { 0517 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 0518 i18nc("@title:window", "Warning"), 0519 i18nc("@info", "Authentication failed. Click \"Continue\" to authenticate."), 0520 QMessageBox::Yes | QMessageBox::No); 0521 0522 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "Continue")); 0523 (warn->button(QMessageBox::No))->setText(i18nc("@action:button", "Cancel")); 0524 0525 if (warn->exec() == QMessageBox::Yes) 0526 { 0527 d->talker->doOAuth(); 0528 delete warn; 0529 return; 0530 } 0531 else 0532 { 0533 delete warn; 0534 return; 0535 } 0536 } 0537 0538 break; 0539 } 0540 0541 default: 0542 { 0543 d->gphotoTalker->cancel(); 0544 0545 if (!(d->gphotoTalker->authenticated())) 0546 { 0547 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 0548 i18nc("@title:window", "Warning"), 0549 i18nc("@info", "Authentication failed. Click \"Continue\" to authenticate."), 0550 QMessageBox::Yes | QMessageBox::No); 0551 0552 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "Continue")); 0553 (warn->button(QMessageBox::No))->setText(i18nc("@action:button", "Cancel")); 0554 0555 if (warn->exec() == QMessageBox::Yes) 0556 { 0557 d->gphotoTalker->doOAuth(); 0558 delete warn; 0559 return; 0560 } 0561 else 0562 { 0563 delete warn; 0564 return; 0565 } 0566 } 0567 0568 if (d->service == GoogleService::GPhotoImport) 0569 { 0570 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Google Photo Transfer invoked"; 0571 0572 // list photos of the album, then start download 0573 0574 connect(d->gphotoTalker, SIGNAL(signalListPhotosDone(int,QString,QList<GSPhoto>)), 0575 this, SLOT(slotListPhotosDoneForDownload(int,QString,QList<GSPhoto>))); 0576 0577 d->gphotoTalker->listPhotos( 0578 d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString()); 0579 0580 return; 0581 } 0582 } 0583 } 0584 0585 typedef QPair<QUrl, GSPhoto> Pair; 0586 0587 for (int i = 0 ; i < (d->widget->imagesList()->imageUrls().size()) ; ++i) 0588 { 0589 DItemInfo info(d->iface->itemInfo(d->widget->imagesList()->imageUrls().value(i))); 0590 GSPhoto temp; 0591 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in start transfer info" <<info.title() << info.comment(); 0592 0593 switch (d->service) 0594 { 0595 case GoogleService::GDrive: 0596 temp.title = info.title(); 0597 temp.description = info.comment().section(QLatin1String("\n"), 0, 0); 0598 break; 0599 0600 default: 0601 temp.title = info.name(); 0602 0603 // Google Photo doesn't support image titles. Include it in descriptions if needed. 0604 0605 QStringList descriptions({info.title()}); 0606 0607 if (info.title() != info.comment()) 0608 { 0609 descriptions << info.comment(); 0610 } 0611 0612 descriptions.removeAll(QLatin1String("")); 0613 temp.description = descriptions.join(QLatin1String("\n\n")); 0614 temp.description.replace(QLatin1Char('"'), QLatin1String("\\\"")); 0615 temp.description = temp.description.left(1000); 0616 break; 0617 } 0618 0619 temp.gpsLat.setNum(info.latitude()); 0620 temp.gpsLon.setNum(info.longitude()); 0621 temp.tags = info.tagsPath(); 0622 0623 d->transferQueue.append(Pair(d->widget->imagesList()->imageUrls().value(i), temp)); 0624 } 0625 0626 d->currentAlbumId = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); 0627 d->imagesTotal = d->transferQueue.count(); 0628 d->imagesCount = 0; 0629 0630 d->widget->progressBar()->setFormat(i18nc("@info: progress bar", "%v / %m")); 0631 d->widget->progressBar()->setMaximum(d->imagesTotal); 0632 d->widget->progressBar()->setValue(0); 0633 d->widget->progressBar()->show(); 0634 0635 switch (d->service) 0636 { 0637 case GoogleService::GDrive: 0638 d->widget->progressBar()->progressScheduled(i18nc("@info", "Google Drive export"), true, true); 0639 d->widget->progressBar()->progressThumbnailChanged( 0640 QIcon::fromTheme(QLatin1String("dk-googledrive")).pixmap(22, 22)); 0641 break; 0642 0643 default: 0644 d->widget->progressBar()->progressScheduled(i18nc("@info", "Google Photo export"), true, true); 0645 d->widget->progressBar()->progressThumbnailChanged( 0646 QIcon::fromTheme((QLatin1String("dk-googlephoto"))).pixmap(22, 22)); 0647 break; 0648 } 0649 0650 uploadNextPhoto(); 0651 } 0652 0653 void GSWindow::uploadNextPhoto() 0654 { 0655 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in upload nextphoto" << d->transferQueue.count(); 0656 0657 if (d->transferQueue.isEmpty()) 0658 { 0659 //d->widget->progressBar()->hide(); 0660 0661 d->widget->progressBar()->progressCompleted(); 0662 0663 /** 0664 * Now all raw photos have been added, 0665 * for GPhoto: prepare to upload on user account 0666 * for GDrive: get listPhotoId to write metadata and finish upload 0667 */ 0668 if (d->service == GoogleService::GPhotoExport) 0669 { 0670 Q_EMIT d->gphotoTalker->signalReadyToUpload(); 0671 } 0672 else 0673 { 0674 Q_EMIT d->talker->signalReadyToUpload(); 0675 } 0676 0677 return; 0678 } 0679 0680 typedef QPair<QUrl, GSPhoto> Pair; 0681 Pair pathComments = d->transferQueue.first(); 0682 GSPhoto info = pathComments.second; 0683 bool res = true; 0684 d->widget->imagesList()->processing(pathComments.first); 0685 0686 switch (d->service) 0687 { 0688 case GoogleService::GDrive: 0689 { 0690 res = d->talker->addPhoto(pathComments.first.toLocalFile(), 0691 info, 0692 d->currentAlbumId, 0693 d->widget->getOriginalCheckBox()->isChecked(), 0694 d->widget->getResizeCheckBox()->isChecked(), 0695 d->widget->getDimensionSpB()->value(), 0696 d->widget->getImgQualitySpB()->value()); 0697 break; 0698 } 0699 0700 case GoogleService::GPhotoExport: 0701 { 0702 bool bCancel = false; 0703 bool bAdd = true; 0704 0705 if (!info.id.isEmpty() && !info.editUrl.isEmpty()) 0706 { 0707 switch (d->renamingOpt) 0708 { 0709 case PWR_ADD_ALL: 0710 bAdd = true; 0711 break; 0712 0713 case PWR_REPLACE_ALL: 0714 bAdd = false; 0715 break; 0716 0717 default: 0718 { 0719 QPointer<ReplaceDialog> dlg = new ReplaceDialog(this, QLatin1String(""), 0720 d->iface, pathComments.first, 0721 info.thumbURL); 0722 (void)dlg->exec(); 0723 0724 switch (dlg->getResult()) 0725 { 0726 case PWR_ADD_ALL: 0727 d->renamingOpt = PWR_ADD_ALL; 0728 break; 0729 0730 case PWR_ADD: 0731 bAdd = true; 0732 break; 0733 0734 case PWR_REPLACE_ALL: 0735 d->renamingOpt = PWR_REPLACE_ALL; 0736 break; 0737 0738 case PWR_REPLACE: 0739 bAdd = false; 0740 break; 0741 0742 case PWR_CANCEL: 0743 default: 0744 bCancel = true; 0745 break; 0746 } 0747 0748 delete dlg; 0749 break; 0750 } 0751 } 0752 } 0753 0754 // adjust tags according to radio button clicked 0755 0756 if (d->widget->m_tagsBGrp) 0757 { 0758 switch (d->widget->m_tagsBGrp->checkedId()) 0759 { 0760 case GPTagLeaf: 0761 { 0762 QStringList newTags; 0763 QStringList::const_iterator itT; 0764 0765 for (itT = info.tags.constBegin() ; itT != info.tags.constEnd() ; ++itT) 0766 { 0767 QString strTmp = *itT; 0768 int idx = strTmp.lastIndexOf(QLatin1Char('/')); 0769 0770 if (idx > 0) 0771 { 0772 strTmp.remove(0, idx + 1); 0773 } 0774 0775 newTags.append(strTmp); 0776 } 0777 0778 info.tags = newTags; 0779 break; 0780 } 0781 0782 case GPTagSplit: 0783 { 0784 QSet<QString> newTagsSet; 0785 QStringList::const_iterator itT; 0786 0787 for (itT = info.tags.constBegin() ; itT != info.tags.constEnd() ; ++itT) 0788 { 0789 QStringList strListTmp = itT->split(QLatin1Char('/')); 0790 QStringList::const_iterator itT2; 0791 0792 for (itT2 = strListTmp.constBegin() ; itT2 != strListTmp.constEnd() ; ++itT2) 0793 { 0794 if (!newTagsSet.contains(*itT2)) 0795 { 0796 newTagsSet.insert(*itT2); 0797 } 0798 } 0799 } 0800 0801 info.tags.clear(); 0802 QSet<QString>::const_iterator itT3; 0803 0804 for (itT3 = newTagsSet.constBegin() ; itT3 != newTagsSet.constEnd() ; ++itT3) 0805 { 0806 info.tags.append(*itT3); 0807 } 0808 0809 break; 0810 } 0811 0812 case GPTagCombined: 0813 default: 0814 { 0815 break; 0816 } 0817 } 0818 } 0819 0820 if (bCancel) 0821 { 0822 slotTransferCancel(); 0823 res = true; 0824 } 0825 else 0826 { 0827 if (bAdd) 0828 { 0829 res = d->gphotoTalker->addPhoto(pathComments.first.toLocalFile(), 0830 info, 0831 d->currentAlbumId, 0832 d->widget->getOriginalCheckBox()->isChecked(), 0833 d->widget->getResizeCheckBox()->isChecked(), 0834 d->widget->getDimensionSpB()->value(), 0835 d->widget->getImgQualitySpB()->value()); 0836 } 0837 else 0838 { 0839 res = d->gphotoTalker->updatePhoto(pathComments.first.toLocalFile(), 0840 info, 0841 d->widget->getResizeCheckBox()->isChecked(), 0842 d->widget->getDimensionSpB()->value(), 0843 d->widget->getImgQualitySpB()->value()); 0844 } 0845 } 0846 break; 0847 } 0848 0849 case GoogleService::GPhotoImport: 0850 break; 0851 } 0852 0853 if (!res) 0854 { 0855 slotAddPhotoDone(0, QLatin1String("")); 0856 return; 0857 } 0858 } 0859 0860 void GSWindow::downloadNextPhoto() 0861 { 0862 if (d->transferQueue.isEmpty()) 0863 { 0864 d->widget->progressBar()->hide(); 0865 d->widget->progressBar()->progressCompleted(); 0866 return; 0867 } 0868 0869 d->widget->progressBar()->setMaximum(d->imagesTotal); 0870 d->widget->progressBar()->setValue(d->imagesCount); 0871 0872 QString imgPath = d->transferQueue.first().first.url(); 0873 0874 d->gphotoTalker->getPhoto(imgPath); 0875 } 0876 0877 void GSWindow::slotGetPhotoDone(int errCode, const QString& errMsg, 0878 const QByteArray& photoData, const QString& fileName) 0879 { 0880 if (d->transferQueue.isEmpty()) 0881 { 0882 return; 0883 } 0884 0885 GSPhoto item = d->transferQueue.first().second; 0886 0887 /** 0888 * (Trung) 0889 * Google Photo API now does not support title for image, 0890 * so we use the file name from the header. As fallback 0891 * the creation time for image name instead. 0892 */ 0893 QString itemName(item.title); 0894 QString suffix(item.mimeType.section(QLatin1Char('/'), -1)); 0895 0896 if (itemName.isEmpty() && !fileName.isEmpty()) 0897 { 0898 QFileInfo info(fileName); 0899 itemName = info.completeBaseName(); 0900 suffix = info.suffix(); 0901 } 0902 0903 if (itemName.isEmpty()) 0904 { 0905 itemName = QString::fromLatin1("image-%1").arg(item.creationTime); 0906 0907 // Replace colon for Windows file systems 0908 0909 itemName.replace(QLatin1Char(':'), QLatin1Char('-')); 0910 } 0911 0912 QUrl tmpUrl = QUrl::fromLocalFile(QString(d->tmp + itemName + 0913 QLatin1Char('.') + 0914 suffix)); 0915 0916 if (errCode == 1) 0917 { 0918 QString errText; 0919 QFile imgFile(tmpUrl.toLocalFile()); 0920 0921 if (!imgFile.open(QIODevice::WriteOnly)) 0922 { 0923 errText = imgFile.errorString(); 0924 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error write"; 0925 } 0926 else if (imgFile.write(photoData) != photoData.size()) 0927 { 0928 errText = imgFile.errorString(); 0929 } 0930 else 0931 { 0932 imgFile.close(); 0933 } 0934 0935 if (errText.isEmpty()) 0936 { 0937 QScopedPointer<DMetadata> meta(new DMetadata); 0938 0939 if (meta->load(tmpUrl.toLocalFile())) 0940 { 0941 if (meta->supportXmp() && meta->canWriteXmp(tmpUrl.toLocalFile())) 0942 { 0943 meta->setXmpTagString("Xmp.digiKam.picasawebGPhotoId", item.id); 0944 meta->setXmpKeywords(item.tags); 0945 } 0946 0947 if (!item.gpsLat.isEmpty() && !item.gpsLon.isEmpty()) 0948 { 0949 meta->setGPSInfo(0.0, item.gpsLat.toDouble(), item.gpsLon.toDouble()); 0950 } 0951 0952 meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); 0953 meta->save(tmpUrl.toLocalFile()); 0954 } 0955 0956 d->transferQueue.removeFirst(); 0957 d->imagesCount++; 0958 } 0959 else 0960 { 0961 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 0962 i18nc("@title:window", "Warning"), 0963 i18nc("@info", "Failed to save photo: %1\n" 0964 "Do you want to continue?", errText), 0965 QMessageBox::Yes | QMessageBox::No); 0966 0967 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "Continue")); 0968 (warn->button(QMessageBox::No))->setText(i18nc("@action:button", "Cancel")); 0969 0970 if (warn->exec() != QMessageBox::Yes) 0971 { 0972 slotTransferCancel(); 0973 delete warn; 0974 return; 0975 } 0976 0977 delete warn; 0978 } 0979 } 0980 else 0981 { 0982 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 0983 i18nc("@title:window", "Warning"), 0984 i18nc("@info", "Failed to download photo: %1\n" 0985 "Do you want to continue?", errMsg), 0986 QMessageBox::Yes | QMessageBox::No); 0987 0988 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "Continue")); 0989 (warn->button(QMessageBox::No))->setText(i18nc("@action:button", "Cancel")); 0990 0991 if (warn->exec() != QMessageBox::Yes) 0992 { 0993 slotTransferCancel(); 0994 delete warn; 0995 return; 0996 } 0997 0998 delete warn; 0999 } 1000 1001 QUrl newUrl = QUrl::fromLocalFile(QString::fromLatin1("%1/%2").arg(d->widget->getDestinationPath()) 1002 .arg(tmpUrl.fileName())); 1003 1004 newUrl = DFileOperations::getUniqueFileUrl(newUrl); 1005 1006 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "location" << newUrl; 1007 1008 if (!QFile::rename(tmpUrl.toLocalFile(), newUrl.toLocalFile())) 1009 { 1010 QMessageBox::critical(this, i18nc("@title:window", "Error"), 1011 i18nc("@info", "Failed to save image to %1", 1012 newUrl.toLocalFile())); 1013 } 1014 1015 Q_EMIT updateHostApp(newUrl); 1016 downloadNextPhoto(); 1017 } 1018 1019 void GSWindow::slotAddPhotoDone(int err, const QString& msg) 1020 { 1021 if (d->transferQueue.isEmpty()) 1022 { 1023 return; 1024 } 1025 1026 if (err == 0) 1027 { 1028 d->widget->imagesList()->processed(d->transferQueue.first().first,false); 1029 1030 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 1031 i18nc("@title:window", "Warning"), 1032 i18nc("@info", "Failed to upload photo to %1.\n%2\n" 1033 "Do you want to continue?", 1034 d->toolName, msg), 1035 QMessageBox::Yes | QMessageBox::No); 1036 1037 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "Continue")); 1038 (warn->button(QMessageBox::No))->setText(i18nc("@action:button", "Cancel")); 1039 1040 if (warn->exec() != QMessageBox::Yes) 1041 { 1042 d->transferQueue.clear(); 1043 d->widget->progressBar()->hide(); 1044 } 1045 else 1046 { 1047 d->transferQueue.removeFirst(); 1048 d->imagesTotal--; 1049 d->widget->progressBar()->setMaximum(d->imagesTotal); 1050 d->widget->progressBar()->setValue(d->imagesCount); 1051 uploadNextPhoto(); 1052 } 1053 1054 delete warn; 1055 } 1056 else 1057 { 1058 /** 1059 * (Trung) Take first item out of transferQueue and append to uploadQueue, 1060 * in order to use it again to write id in slotUploadPhotoDone 1061 */ 1062 QPair<QUrl, GSPhoto> item = d->transferQueue.first(); 1063 d->uploadQueue.append(item); 1064 1065 // Remove photo uploaded from the transfer queue 1066 1067 d->transferQueue.removeFirst(); 1068 d->imagesCount++; 1069 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In slotAddPhotoSucceeded" << d->imagesCount; 1070 d->widget->progressBar()->setMaximum(d->imagesTotal); 1071 d->widget->progressBar()->setValue(d->imagesCount); 1072 uploadNextPhoto(); 1073 } 1074 } 1075 1076 void GSWindow::slotUploadPhotoDone(int err, const QString& msg, const QStringList& listPhotoId) 1077 { 1078 if (err == 0) 1079 { 1080 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 1081 i18nc("@title:window", "Warning"), 1082 i18nc("@info", "Failed to finish uploading photo to %1.\n%2\n" 1083 "No image uploaded to your account.", 1084 d->toolName, msg), 1085 QMessageBox::Yes); 1086 1087 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "OK")); 1088 1089 d->uploadQueue.clear(); 1090 d->widget->progressBar()->hide(); 1091 1092 delete warn; 1093 } 1094 else 1095 { 1096 Q_FOREACH (const QString& photoId, listPhotoId) 1097 { 1098 // Remove image from upload list and from UI 1099 1100 QPair<QUrl, GSPhoto> item = d->uploadQueue.takeFirst(); 1101 d->widget->imagesList()->removeItemByUrl(item.first); 1102 1103 QUrl fileUrl = item.first; 1104 1105 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "photoID:" << photoId; 1106 1107 QScopedPointer<DMetadata> meta(new DMetadata); 1108 1109 if (d->widget->getPhotoIdCheckBox()->isChecked() && 1110 meta->supportXmp() && 1111 meta->canWriteXmp(fileUrl.toLocalFile()) && 1112 meta->load(fileUrl.toLocalFile()) && 1113 !photoId.isEmpty()) 1114 { 1115 meta->setXmpTagString("Xmp.digiKam.picasawebGPhotoId", photoId); 1116 meta->save(fileUrl.toLocalFile()); 1117 } 1118 } 1119 1120 if (!d->widget->imagesList()->imageUrls().isEmpty()) 1121 { 1122 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "continue to upload"; 1123 Q_EMIT d->gphotoTalker->signalReadyToUpload(); 1124 } 1125 } 1126 } 1127 1128 void GSWindow::slotImageListChanged() 1129 { 1130 startButton()->setEnabled(!(d->widget->imagesList()->imageUrls().isEmpty())); 1131 } 1132 1133 void GSWindow::slotNewAlbumRequest() 1134 { 1135 switch (d->service) 1136 { 1137 case GoogleService::GDrive: 1138 { 1139 if (!d->widget->getAlbumTitle().isEmpty()) 1140 { 1141 d->albumDlg->setAlbumSuggestion(d->widget->getAlbumTitle()); 1142 } 1143 1144 if (d->albumDlg->exec() == QDialog::Accepted) 1145 { 1146 GSFolder newFolder; 1147 d->albumDlg->getAlbumProperties(newFolder); 1148 d->currentAlbumId = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); 1149 d->talker->createFolder(newFolder.title, d->currentAlbumId); 1150 } 1151 1152 break; 1153 } 1154 1155 default: 1156 { 1157 if (!d->widget->getAlbumTitle().isEmpty()) 1158 { 1159 d->gphotoAlbumDlg->setAlbumSuggestion(d->widget->getAlbumTitle()); 1160 } 1161 1162 if (d->gphotoAlbumDlg->exec() == QDialog::Accepted) 1163 { 1164 GSFolder newFolder; 1165 d->gphotoAlbumDlg->getAlbumProperties(newFolder); 1166 d->gphotoTalker->createAlbum(newFolder); 1167 d->newFolderTitle = newFolder.title; 1168 } 1169 1170 break; 1171 } 1172 } 1173 } 1174 1175 void GSWindow::slotReloadAlbumsRequest() 1176 { 1177 switch (d->service) 1178 { 1179 case GoogleService::GDrive: 1180 d->talker->listFolders(); 1181 break; 1182 1183 case GoogleService::GPhotoImport: 1184 case GoogleService::GPhotoExport: 1185 d->gphotoTalker->listAlbums(); 1186 break; 1187 } 1188 } 1189 1190 void GSWindow::slotAccessTokenObtained() 1191 { 1192 switch (d->service) 1193 { 1194 case GoogleService::GDrive: 1195 d->talker->listFolders(); 1196 break; 1197 1198 case GoogleService::GPhotoImport: 1199 case GoogleService::GPhotoExport: 1200 d->gphotoTalker->getLoggedInUser(); 1201 break; 1202 } 1203 } 1204 1205 void GSWindow::slotAuthenticationRefused() 1206 { 1207 // QMessageBox::critical(this, i18nc("@title:window", "Error"), 1208 // i18n("An authentication error occurred: account failed to link")); 1209 1210 // Clear list albums 1211 1212 d->widget->getAlbumsCoB()->clear(); 1213 1214 // Clear user name 1215 1216 d->widget->updateLabels(QString()); 1217 1218 return; 1219 } 1220 1221 void GSWindow::slotCreateFolderDone(int code, const QString& msg, const QString& albumId) 1222 { 1223 switch (d->service) 1224 { 1225 case GoogleService::GDrive: 1226 { 1227 if (code == 0) 1228 { 1229 QMessageBox::critical(this, i18nc("@title:window", "Error"), 1230 i18nc("@info", "Google Drive call failed:\n%1", msg)); 1231 } 1232 else 1233 { 1234 d->currentAlbumId = albumId; 1235 d->talker->listFolders(); 1236 } 1237 1238 break; 1239 } 1240 1241 case GoogleService::GPhotoImport: 1242 case GoogleService::GPhotoExport: 1243 { 1244 if (code == 0) 1245 { 1246 QMessageBox::critical(this, i18nc("@title:window", "Error"), 1247 i18nc("@info", "Google Photos call failed:\n%1", msg)); 1248 } 1249 else 1250 { 1251 d->currentAlbumId = albumId; 1252 d->widget->getAlbumsCoB()->addItem(QIcon::fromTheme(QLatin1String("folder")), 1253 d->newFolderTitle, d->currentAlbumId); 1254 d->widget->getAlbumsCoB()->setCurrentIndex(d->widget->getAlbumsCoB()-> 1255 findData(d->currentAlbumId)); 1256 } 1257 1258 break; 1259 } 1260 } 1261 } 1262 1263 void GSWindow::slotTransferCancel() 1264 { 1265 d->transferQueue.clear(); 1266 d->widget->progressBar()->hide(); 1267 1268 switch (d->service) 1269 { 1270 case GoogleService::GDrive: 1271 d->talker->cancel(); 1272 break; 1273 1274 case GoogleService::GPhotoImport: 1275 case GoogleService::GPhotoExport: 1276 d->gphotoTalker->cancel(); 1277 break; 1278 } 1279 } 1280 1281 void GSWindow::slotUserChangeRequest() 1282 { 1283 QPointer<QMessageBox> warn = new QMessageBox(QMessageBox::Warning, 1284 i18nc("@title:window", "Warning"), 1285 i18nc("@info", "You will be logged out of your account, " 1286 "click \"Continue\" to authenticate for another account"), 1287 QMessageBox::Yes | QMessageBox::No); 1288 1289 (warn->button(QMessageBox::Yes))->setText(i18nc("@action:button", "Continue")); 1290 (warn->button(QMessageBox::No))->setText(i18nc("@action:button", "Cancel")); 1291 1292 if (warn->exec() == QMessageBox::Yes) 1293 { 1294 /** 1295 * We do not force user to logout from their account 1296 * We simply unlink user account and direct use to login page to login new account 1297 * (In the future, we may not unlink() user, but let them change account and 1298 * choose which one they want to use) 1299 * After unlink(), waiting actively until O2 completely unlink() account, before doOAuth() again 1300 */ 1301 switch (d->service) 1302 { 1303 case GoogleService::GDrive: 1304 d->talker->unlink(); 1305 while(d->talker->authenticated()); 1306 d->talker->doOAuth(); 1307 break; 1308 1309 case GoogleService::GPhotoImport: 1310 case GoogleService::GPhotoExport: 1311 d->gphotoTalker->unlink(); 1312 while(d->gphotoTalker->authenticated()); 1313 d->gphotoTalker->doOAuth(); 1314 break; 1315 } 1316 } 1317 1318 delete warn; 1319 } 1320 1321 void GSWindow::buttonStateChange(bool state) 1322 { 1323 d->widget->getNewAlbmBtn()->setEnabled(state); 1324 d->widget->getReloadBtn()->setEnabled(state); 1325 startButton()->setEnabled(state); 1326 } 1327 1328 void GSWindow::slotFinished() 1329 { 1330 writeSettings(); 1331 d->transferQueue.clear(); 1332 d->widget->imagesList()->listView()->clear(); 1333 } 1334 1335 void GSWindow::closeEvent(QCloseEvent* e) 1336 { 1337 if (!e) 1338 { 1339 return; 1340 } 1341 1342 slotFinished(); 1343 e->accept(); 1344 } 1345 1346 } // namespace DigikamGenericGoogleServicesPlugin 1347 1348 #include "moc_gswindow.cpp"