File indexing completed on 2025-01-05 03:53:26

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-17-26
0007  * Description : a tool to export items to Facebook web service
0008  *
0009  * SPDX-FileCopyrightText: 2005-2008 by Vardhman Jain <vardhman at gmail dot com>
0010  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2008-2009 by Luka Renko <lure at kubuntu dot org>
0012  * SPDX-FileCopyrightText: 2018      by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "fbwindow.h"
0019 
0020 // Qt includes
0021 
0022 #include <QWindow>
0023 #include <QComboBox>
0024 #include <QMenu>
0025 #include <QFileInfo>
0026 #include <QCheckBox>
0027 #include <QCloseEvent>
0028 #include <QSpinBox>
0029 #include <QMessageBox>
0030 #include <QPointer>
0031 
0032 // KDE includes
0033 
0034 #include <klocalizedstring.h>
0035 #include <ksharedconfig.h>
0036 #include <kconfiggroup.h>
0037 
0038 // Local includes
0039 
0040 #include "digikam_debug.h"
0041 #include "dmetadata.h"
0042 #include "ditemslist.h"
0043 #include "digikam_version.h"
0044 #include "dprogresswdg.h"
0045 #include "wstoolutils.h"
0046 #include "fbitem.h"
0047 #include "fbtalker.h"
0048 #include "fbwidget.h"
0049 #include "fbnewalbumdlg.h"
0050 #include "previewloadthread.h"
0051 
0052 namespace DigikamGenericFaceBookPlugin
0053 {
0054 
0055 class Q_DECL_HIDDEN FbWindow::Private
0056 {
0057 public:
0058 
0059     explicit Private(QWidget* const parent, DInfoInterface* const interface)
0060     {
0061         iface           = interface;
0062         widget          = new FbWidget(parent, iface, QLatin1String("Facebook"));
0063         imgList         = widget->imagesList();
0064         progressBar     = widget->progressBar();
0065         changeUserBtn   = widget->getChangeUserBtn();
0066         albumsCoB       = widget->getAlbumsCoB();
0067         newAlbumBtn     = widget->getNewAlbmBtn();
0068         reloadAlbumsBtn = widget->getReloadBtn();
0069         resizeChB       = widget->getResizeCheckBox();
0070         dimensionSpB    = widget->getDimensionSpB();
0071         imageQualitySpB = widget->getImgQualitySpB();
0072         imagesCount     = 0;
0073         imagesTotal     = 0;
0074         talker          = nullptr;
0075         albumDlg        = nullptr;
0076     }
0077 
0078     FbWidget*       widget;
0079     DItemsList*     imgList;
0080     QPushButton*    changeUserBtn;
0081     QComboBox*      albumsCoB;
0082     QPushButton*    newAlbumBtn;
0083     QPushButton*    reloadAlbumsBtn;
0084     QCheckBox*      resizeChB;
0085     QSpinBox*       dimensionSpB;
0086     QSpinBox*       imageQualitySpB;
0087     DProgressWdg*   progressBar;
0088 
0089     unsigned int    imagesCount;
0090     unsigned int    imagesTotal;
0091     QString         tmpDir;
0092     QString         tmpPath;
0093 
0094     QString         profileAID;
0095     QString         currentAlbumID;
0096 
0097     QList<QUrl>     transferQueue;
0098 
0099     FbTalker*       talker;
0100     FbNewAlbumDlg*  albumDlg;
0101 
0102     DInfoInterface* iface;
0103 };
0104 
0105 FbWindow::FbWindow(DInfoInterface* const iface, QWidget* const /*parent*/)
0106     : WSToolDialog(nullptr, QLatin1String("Facebook Export Dialog")),
0107       d           (new Private(this, iface))
0108 {
0109     d->tmpPath.clear();
0110     d->tmpDir = WSToolUtils::makeTemporaryDir("facebook").absolutePath() + QLatin1Char('/');
0111 
0112     setMainWidget(d->widget);
0113     setModal(false);
0114 
0115     setWindowTitle(i18nc("@title:window", "Export to Facebook Web Service"));
0116 
0117     startButton()->setText(i18nc("@action:button", "Start Upload"));
0118     startButton()->setToolTip(i18nc("@info:tooltip, button", "Start upload to Facebook web service"));
0119 
0120     d->widget->setMinimumSize(700, 500);
0121 
0122     d->changeUserBtn->setStyleSheet(QLatin1String("QPushButton {background-color: "
0123                                                   "#3b5998; color: #ffffff;}"));
0124     d->changeUserBtn->setIcon(QIcon::fromTheme(QLatin1String("dk-facebook-white")));
0125     d->changeUserBtn->setText(i18n("Continue with Facebook"));
0126 
0127     // ------------------------------------------------------------------------
0128 
0129     connect(d->imgList, SIGNAL(signalImageListChanged()),
0130             this, SLOT(slotImageListChanged()));
0131 
0132     connect(d->changeUserBtn, SIGNAL(clicked()),
0133             this, SLOT(slotUserChangeRequest()));
0134 
0135     connect(d->newAlbumBtn, SIGNAL(clicked()),
0136             this, SLOT(slotNewAlbumRequest()));
0137 
0138     connect(d->widget, SIGNAL(reloadAlbums(long long)),
0139             this, SLOT(slotReloadAlbumsRequest(long long)));
0140 
0141     connect(startButton(), SIGNAL(clicked()),
0142             this, SLOT(slotStartTransfer()));
0143 
0144     connect(this, SIGNAL(finished(int)),
0145             this, SLOT(slotFinished()));
0146 
0147     connect(this, SIGNAL(cancelClicked()),
0148             this, SLOT(slotCancelClicked()));
0149 
0150     d->albumDlg = new FbNewAlbumDlg(this, QLatin1String("Facebook"));
0151 
0152     // ------------------------------------------------------------------------
0153 
0154     d->talker = new FbTalker(this);
0155 
0156     connect(d->talker, SIGNAL(signalBusy(bool)),
0157             this, SLOT(slotBusy(bool)));
0158 
0159     connect(d->talker, SIGNAL(signalLoginProgress(int,int,QString)),
0160             this, SLOT(slotLoginProgress(int,int,QString)));
0161 
0162     connect(d->talker, SIGNAL(signalLoginDone(int,QString)),
0163             this, SLOT(slotLoginDone(int,QString)));
0164 
0165     connect(d->talker, SIGNAL(signalAddPhotoDone(int,QString)),
0166             this, SLOT(slotAddPhotoDone(int,QString)));
0167 
0168     connect(d->talker, SIGNAL(signalCreateAlbumDone(int,QString,QString)),
0169             this, SLOT(slotCreateAlbumDone(int,QString,QString)));
0170 
0171     connect(d->talker, SIGNAL(signalListAlbumsDone(int,QString,QList<FbAlbum>)),
0172             this, SLOT(slotListAlbumsDone(int,QString,QList<FbAlbum>)));
0173 
0174     connect(d->progressBar, SIGNAL(signalProgressCanceled()),
0175             this, SLOT(slotStopAndCloseProgressBar()));
0176 
0177     // ------------------------------------------------------------------------
0178 
0179     readSettings();
0180     buttonStateChange(false);
0181 
0182     authenticate(false);
0183 }
0184 
0185 FbWindow::~FbWindow()
0186 {
0187     WSToolUtils::removeTemporaryDir("facebook");
0188 
0189     delete d->albumDlg;
0190     delete d->talker;
0191     delete d;
0192 }
0193 
0194 void FbWindow::slotStopAndCloseProgressBar()
0195 {
0196     // Cancel the operation
0197     slotCancelClicked();
0198 
0199     // Write settings and tidy up
0200     slotFinished();
0201 
0202     // Close the dialog
0203     reject();
0204 }
0205 
0206 void FbWindow::slotFinished()
0207 {
0208     writeSettings();
0209     d->imgList->listView()->clear();
0210     d->progressBar->progressCompleted();
0211 }
0212 
0213 void FbWindow::slotCancelClicked()
0214 {
0215     setRejectButtonMode(QDialogButtonBox::Close);
0216     d->talker->cancel();
0217     d->transferQueue.clear();
0218     d->imgList->cancelProcess();
0219     d->progressBar->hide();
0220     d->progressBar->progressCompleted();
0221 }
0222 
0223 void FbWindow::closeEvent(QCloseEvent* e)
0224 {
0225     if (!e)
0226     {
0227         return;
0228     }
0229 
0230     slotFinished();
0231     e->accept();
0232 }
0233 
0234 void FbWindow::readSettings()
0235 {
0236     KSharedConfigPtr config = KSharedConfig::openConfig();
0237     KConfigGroup grp        = config->group(QLatin1String("Facebook Settings"));
0238 
0239     if (grp.readEntry("Resize", false))
0240     {
0241         d->resizeChB->setChecked(true);
0242         d->dimensionSpB->setEnabled(true);
0243     }
0244     else
0245     {
0246         d->resizeChB->setChecked(false);
0247         d->dimensionSpB->setEnabled(false);
0248     }
0249 
0250     d->currentAlbumID = grp.readEntry("Current Album", QString());
0251     d->dimensionSpB->setValue(grp.readEntry("Maximum Width", 1600));
0252     d->imageQualitySpB->setValue(grp.readEntry("Image Quality", 85));
0253 }
0254 
0255 void FbWindow::writeSettings()
0256 {
0257     KSharedConfigPtr config = KSharedConfig::openConfig();
0258     KConfigGroup grp        = config->group(QLatin1String("Facebook Settings"));
0259 
0260     grp.writeEntry("Current Album", d->currentAlbumID);
0261     grp.writeEntry("Resize",        d->resizeChB->isChecked());
0262     grp.writeEntry("Maximum Width", d->dimensionSpB->value());
0263     grp.writeEntry("Image Quality", d->imageQualitySpB->value());
0264 }
0265 
0266 void FbWindow::authenticate(bool forceLogin)
0267 {
0268     d->progressBar->show();
0269     d->progressBar->setFormat(QLatin1String(""));
0270     setRejectButtonMode(QDialogButtonBox::Cancel);
0271 
0272     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling Login method ";
0273 
0274     if (forceLogin)
0275     {
0276         d->talker->link();
0277     }
0278     else
0279     {
0280         d->talker->readSettings();
0281     }
0282 }
0283 
0284 void FbWindow::slotLoginProgress(int step, int maxStep, const QString& label)
0285 {
0286     DProgressWdg* const progressBar = d->progressBar;
0287 
0288     if (!label.isEmpty())
0289     {
0290         progressBar->setFormat(label);
0291     }
0292 
0293     if (maxStep > 0)
0294     {
0295         progressBar->setMaximum(maxStep);
0296     }
0297 
0298     progressBar->setValue(step);
0299 }
0300 
0301 void FbWindow::slotLoginDone(int errCode, const QString& errMsg)
0302 {
0303     setRejectButtonMode(QDialogButtonBox::Close);
0304     d->progressBar->hide();
0305 
0306     buttonStateChange(d->talker->linked());
0307 
0308     if (d->talker->linked())
0309     {
0310         d->changeUserBtn->setText(i18n("Log Out of Facebook"));
0311     }
0312     else
0313     {
0314         d->changeUserBtn->setText(i18n("Continue with Facebook"));
0315     }
0316 
0317     FbUser user = d->talker->getUser();
0318     setProfileAID(user.id.toLongLong());
0319 
0320     d->widget->updateLabels(user.name, user.profileURL);
0321     d->albumsCoB->clear();
0322 
0323     if (errCode == 0 && d->talker->linked())
0324     {
0325         d->albumsCoB->addItem(i18n("<i>auto create</i>"), QString());
0326         d->talker->listAlbums();    // get albums to fill combo box
0327     }
0328     else if (errCode > 0)
0329     {
0330         QMessageBox::critical(this, QString(), i18n("Facebook Call Failed: %1\n", errMsg));
0331     }
0332 }
0333 
0334 void FbWindow::slotListAlbumsDone(int errCode,
0335                                   const QString& errMsg,
0336                                   const QList<FbAlbum>& albumsList)
0337 {
0338     QString albumDebug = QLatin1String("");
0339 
0340     Q_FOREACH (const FbAlbum& album, albumsList)
0341     {
0342         albumDebug.append(QString::fromLatin1("%1: %2\n").arg(album.id).arg(album.title));
0343     }
0344 
0345     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received albums (errCode = " << errCode << ", errMsg = "
0346                                      << errMsg << "): " << albumDebug;
0347 
0348     if (errCode != 0)
0349     {
0350         QMessageBox::critical(this, QString(), i18n("Facebook Call Failed: %1\n", errMsg));
0351         return;
0352     }
0353 
0354     d->albumsCoB->clear();
0355     d->albumsCoB->addItem(i18n("<i>auto create</i>"), QString());
0356 
0357     for (int i = 0 ; i < albumsList.size() ; ++i)
0358     {
0359         QString albumIcon;
0360 
0361         switch (albumsList.at(i).privacy)
0362         {
0363             case FB_ME:
0364                 albumIcon = QLatin1String("secure-card");
0365                 break;
0366 
0367             case FB_FRIENDS:
0368                 albumIcon = QLatin1String("user-identity");
0369                 break;
0370 
0371             case FB_FRIENDS_OF_FRIENDS:
0372                 albumIcon = QLatin1String("system-users");
0373                 break;
0374 
0375             case FB_EVERYONE:
0376                 albumIcon = QLatin1String("folder-html");
0377                 break;
0378 
0379             case FB_CUSTOM:
0380                 albumIcon = QLatin1String("configure");
0381                 break;
0382         }
0383 
0384         d->albumsCoB->addItem(QIcon::fromTheme(albumIcon),
0385                               albumsList.at(i).title,
0386                               albumsList.at(i).id);
0387 
0388         if (d->currentAlbumID == albumsList.at(i).id)
0389         {
0390             d->albumsCoB->setCurrentIndex(i + 1);
0391         }
0392     }
0393 }
0394 
0395 void FbWindow::buttonStateChange(bool state)
0396 {
0397     d->newAlbumBtn->setEnabled(state);
0398     d->reloadAlbumsBtn->setEnabled(state);
0399     startButton()->setEnabled(state);
0400 }
0401 
0402 void FbWindow::slotBusy(bool val)
0403 {
0404     if (val)
0405     {
0406         setCursor(Qt::WaitCursor);
0407         d->changeUserBtn->setEnabled(false);
0408         buttonStateChange(false);
0409     }
0410     else
0411     {
0412         setCursor(Qt::ArrowCursor);
0413         d->changeUserBtn->setEnabled(true);
0414         buttonStateChange(d->talker->linked());
0415     }
0416 }
0417 
0418 void FbWindow::slotUserChangeRequest()
0419 {
0420     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request";
0421 
0422     if (d->talker->linked())
0423     {
0424         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot User Logout";
0425         d->talker->logout();
0426     }
0427     else
0428     {
0429         authenticate(true);
0430     }
0431 }
0432 
0433 void FbWindow::slotReloadAlbumsRequest(long long userID)
0434 {
0435     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Reload Albums Request for UID:" << userID;
0436 
0437     if (userID == 0)
0438     {
0439         FbUser user = d->talker->getUser();
0440         setProfileAID(user.id.toLongLong());
0441         d->talker->listAlbums(); // re-get albums from current user
0442     }
0443     else
0444     {
0445         setProfileAID(userID);
0446         d->talker->listAlbums(userID); // re-get albums for friend
0447     }
0448 }
0449 
0450 void FbWindow::slotNewAlbumRequest()
0451 {
0452     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot New Album Request";
0453 
0454     if (d->albumDlg->exec() == QDialog::Accepted)
0455     {
0456         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling New Album method";
0457         FbAlbum newAlbum;
0458         d->albumDlg->getAlbumProperties(newAlbum);
0459         d->talker->createAlbum(newAlbum);
0460     }
0461 }
0462 
0463 void FbWindow::slotStartTransfer()
0464 {
0465     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotStartTransfer invoked";
0466 
0467     d->imgList->clearProcessedStatus();
0468     d->transferQueue  = d->imgList->imageUrls();
0469 
0470     if (d->transferQueue.isEmpty())
0471     {
0472         return;
0473     }
0474 
0475     d->currentAlbumID = d->albumsCoB->itemData(d->albumsCoB->currentIndex()).toString();
0476     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "upload request got album id from widget: " << d->currentAlbumID;
0477     d->imagesTotal    = d->transferQueue.count();
0478     d->imagesCount    = 0;
0479 
0480     setRejectButtonMode(QDialogButtonBox::Cancel);
0481     d->progressBar->setFormat(i18n("%v / %m"));
0482     d->progressBar->setMaximum(d->imagesTotal);
0483     d->progressBar->setValue(0);
0484     d->progressBar->show();
0485     d->progressBar->progressScheduled(i18n("Facebook export"), true, true);
0486     d->progressBar->progressThumbnailChanged(QIcon::fromTheme(QLatin1String("dk-facebook")).pixmap(22, 22));
0487 
0488     uploadNextPhoto();
0489 }
0490 
0491 void FbWindow::setProfileAID(long long userID)
0492 {
0493     // store AID of Profile Photos album
0494     // wiki.developers.facebook.com/index.php/Profile_archive_album
0495 
0496     d->profileAID = QString::number((userID << 32) + (-3 & 0xFFFFFFFF));
0497 }
0498 
0499 QString FbWindow::getImageCaption(const QString& fileName)
0500 {
0501     DItemInfo info(d->iface->itemInfo(QUrl::fromLocalFile(fileName)));
0502 
0503     // Facebook doesn't support image titles. Include it in descriptions if needed.
0504 
0505     QStringList descriptions = QStringList() << info.title() << info.comment();
0506     descriptions.removeAll(QLatin1String(""));
0507 
0508     return descriptions.join(QLatin1String("\n\n"));
0509 }
0510 
0511 bool FbWindow::prepareImageForUpload(const QString& imgPath, QString& caption)
0512 {
0513     QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage();
0514 
0515     if (image.isNull())
0516     {
0517         image.load(imgPath);
0518     }
0519 
0520     if (image.isNull())
0521     {
0522         return false;
0523     }
0524 
0525     // get temporary file name
0526 
0527     d->tmpPath = d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + QLatin1String(".jpg");
0528 
0529     // rescale image if requested
0530 
0531     int maxDim = d->dimensionSpB->value();
0532 
0533     if (d->resizeChB->isChecked() &&
0534         (image.width() > maxDim || image.height() > maxDim))
0535     {
0536         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing to " << maxDim;
0537         image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio,
0538                              Qt::SmoothTransformation);
0539     }
0540 
0541     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Saving to temp file: " << d->tmpPath;
0542     image.save(d->tmpPath, "JPEG", d->imageQualitySpB->value());
0543 
0544     // copy meta data to temporary image
0545 
0546     QScopedPointer<DMetadata> meta(new DMetadata);
0547 
0548     if (meta->load(imgPath))
0549     {
0550         caption = getImageCaption(imgPath);
0551         meta->setItemDimensions(image.size());
0552         meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL);
0553         meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
0554         meta->save(d->tmpPath, true);
0555     }
0556     else
0557     {
0558         caption.clear();
0559     }
0560 
0561     return true;
0562 }
0563 
0564 void FbWindow::uploadNextPhoto()
0565 {
0566     if (d->transferQueue.isEmpty())
0567     {
0568         setRejectButtonMode(QDialogButtonBox::Close);
0569         d->progressBar->hide();
0570         d->progressBar->progressCompleted();
0571         return;
0572     }
0573 
0574     d->imgList->processing(d->transferQueue.first());
0575     QString imgPath = d->transferQueue.first().toLocalFile();
0576 
0577     d->progressBar->setMaximum(d->imagesTotal);
0578     d->progressBar->setValue(d->imagesCount);
0579 
0580     QString caption;
0581 
0582     if (d->resizeChB->isChecked())
0583     {
0584         if (!prepareImageForUpload(imgPath, caption))
0585         {
0586             slotAddPhotoDone(666, i18n("Cannot open file"));
0587             return;
0588         }
0589 
0590         d->talker->addPhoto(d->tmpPath, d->currentAlbumID, caption);
0591     }
0592     else
0593     {
0594         caption = getImageCaption(imgPath);
0595         d->tmpPath.clear();
0596         d->talker->addPhoto(imgPath, d->currentAlbumID, caption);
0597     }
0598 }
0599 
0600 void FbWindow::slotAddPhotoDone(int errCode, const QString& errMsg)
0601 {
0602     // Remove temporary file if it was used
0603 
0604     if (!d->tmpPath.isEmpty())
0605     {
0606         QFile::remove(d->tmpPath);
0607         d->tmpPath.clear();
0608     }
0609 
0610     d->imgList->processed(d->transferQueue.first(), (errCode == 0));
0611 
0612     if (errCode == 0)
0613     {
0614         d->transferQueue.removeFirst();
0615         d->imagesCount++;
0616     }
0617     else
0618     {
0619         if (QMessageBox::question(this, i18nc("@title:window", "Uploading Failed"),
0620                                   i18n("Failed to upload photo into Facebook: %1\n"
0621                                        "Do you want to continue?", errMsg))
0622             != QMessageBox::Yes)
0623         {
0624             setRejectButtonMode(QDialogButtonBox::Close);
0625             d->progressBar->hide();
0626             d->progressBar->progressCompleted();
0627             d->transferQueue.clear();
0628             return;
0629         }
0630     }
0631 
0632     uploadNextPhoto();
0633 }
0634 
0635 void FbWindow::slotCreateAlbumDone(int errCode, const QString& errMsg, const QString& newAlbumID)
0636 {
0637     if (errCode != 0)
0638     {
0639         QMessageBox::critical(this, QString(), i18n("Facebook Call Failed: %1", errMsg));
0640         return;
0641     }
0642 
0643     // reload album list and automatically select new album
0644 
0645     d->currentAlbumID = newAlbumID;
0646     d->talker->listAlbums();
0647 }
0648 
0649 void FbWindow::slotImageListChanged()
0650 {
0651     startButton()->setEnabled(!(d->imgList->imageUrls().isEmpty()));
0652 }
0653 
0654 } // namespace DigikamGenericFaceBookPlugin
0655 
0656 #include "moc_fbwindow.cpp"