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"