File indexing completed on 2025-01-05 03:59:45

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2004-09-17
0007  * Description : digital camera controller
0008  *
0009  * SPDX-FileCopyrightText: 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  * SPDX-FileCopyrightText: 2006      by Stephan Kulow <coolo at kde dot org>
0013  * SPDX-FileCopyrightText: 2015      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0014  *
0015  * SPDX-License-Identifier: GPL-2.0-or-later
0016  *
0017  * ============================================================ */
0018 
0019 #include "cameracontroller.h"
0020 
0021 // Qt includes
0022 
0023 #include <QApplication>
0024 #include <QMutex>
0025 #include <QWaitCondition>
0026 #include <QVariant>
0027 #include <QImage>
0028 #include <QFile>
0029 #include <QRegularExpression>
0030 #include <QUrl>
0031 #include <QDir>
0032 #include <QMessageBox>
0033 #include <QProcess>
0034 #include <QScopedPointer>
0035 
0036 // KDE includes
0037 
0038 #include <klocalizedstring.h>
0039 
0040 // Local includes
0041 
0042 #include "digikam_debug.h"
0043 #include "digikam_config.h"
0044 #include "digikam_globals.h"
0045 #include "template.h"
0046 #include "templatemanager.h"
0047 #include "gpcamera.h"
0048 #include "umscamera.h"
0049 #include "jpegutils.h"
0050 #include "dfileoperations.h"
0051 #include "filereadwritelock.h"
0052 
0053 namespace Digikam
0054 {
0055 
0056 class Q_DECL_HIDDEN CameraCommand
0057 {
0058 public:
0059 
0060     enum Action
0061     {
0062         cam_none = 0,
0063         cam_connect,
0064         cam_cancel,
0065         cam_cameraInformation,
0066         cam_listfolders,
0067         cam_listfiles,
0068         cam_download,
0069         cam_upload,
0070         cam_delete,
0071         cam_lock,
0072         cam_thumbsinfo,
0073         cam_metadata,
0074         cam_open,
0075         cam_freeSpace,
0076         cam_preview,
0077         cam_capture
0078     };
0079 
0080     Action                  action;
0081     QMap<QString, QVariant> map;
0082 };
0083 
0084 class Q_DECL_HIDDEN CameraController::Private
0085 {
0086 public:
0087 
0088     explicit Private()
0089       : close       (false),
0090         canceled    (false),
0091         running     (false),
0092         parent      (nullptr),
0093         timer       (nullptr),
0094         camera      (nullptr)
0095     {
0096     }
0097 
0098     bool                  close;
0099     bool                  canceled;
0100     bool                  running;
0101 
0102     QStringList           folderList;
0103 
0104     QWidget*              parent;
0105 
0106     QTimer*               timer;
0107 
0108     DKCamera*             camera;
0109 
0110     QMutex                mutex;
0111     QWaitCondition        condVar;
0112 
0113     QList<CameraCommand*> cmdThumbs;
0114     QList<CameraCommand*> commands;
0115 };
0116 
0117 CameraController::CameraController(QWidget* const parent,
0118                                    const QString& title,
0119                                    const QString& model,
0120                                    const QString& port,
0121                                    const QString& path)
0122     : QThread(parent),
0123       d      (new Private)
0124 {
0125     d->parent = parent;
0126 
0127     // URL parsing
0128 
0129     if (path.startsWith(QLatin1String("camera:/")))
0130     {
0131         QUrl url(path);
0132         qCDebug(DIGIKAM_IMPORTUI_LOG) << "path " << path << " " << url <<  " " << url.host();
0133         QString xport = url.host();
0134 
0135         if (xport.startsWith(QLatin1String("usb:")))
0136         {
0137             qCDebug(DIGIKAM_IMPORTUI_LOG) << "xport " << xport;
0138             QRegularExpression x(QLatin1String("(usb:[0-9,]*)"));
0139             QRegularExpressionMatch match = x.match(xport);
0140 
0141             if (match.hasMatch())
0142             {
0143                 QString usbport = match.captured(1);
0144                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "USB " << xport << " " << usbport;
0145 
0146                 //if ((xport == usbport) || ((count == 1) && (xport == "usb:")))
0147                 //{
0148                 //   model = xmodel;
0149                 d->camera = new GPCamera(title, url.userName(), QLatin1String("usb:"), QLatin1String("/"));
0150                 //}
0151             }
0152         }
0153     }
0154 
0155     if (!d->camera)
0156     {
0157         if (model.toLower() == QLatin1String("directory browse"))
0158         {
0159             d->camera = new UMSCamera(title, model, port, path);
0160         }
0161         else
0162         {
0163             d->camera = new GPCamera(title, model, port, path);
0164         }
0165     }
0166 
0167     connect(d->camera, SIGNAL(signalFolderList(QStringList)),
0168             this, SIGNAL(signalFolderList(QStringList)));
0169 
0170     // setup inter-thread signals
0171 
0172     qRegisterMetaType<CamItemInfo>("CamItemInfo");
0173     qRegisterMetaType<CamItemInfoList>("CamItemInfoList");
0174 
0175     connect(this, SIGNAL(signalInternalDownloadFailed(QString,QString)),
0176             this, SLOT(slotDownloadFailed(QString,QString)),
0177             Qt::BlockingQueuedConnection);
0178 
0179     connect(this, SIGNAL(signalInternalUploadFailed(QString,QString,QString)),
0180             this, SLOT(slotUploadFailed(QString,QString,QString)),
0181             Qt::BlockingQueuedConnection);
0182 
0183     connect(this, SIGNAL(signalInternalDeleteFailed(QString,QString)),
0184             this, SLOT(slotDeleteFailed(QString,QString)),
0185             Qt::BlockingQueuedConnection);
0186 
0187     connect(this, SIGNAL(signalInternalLockFailed(QString,QString)),
0188             this, SLOT(slotLockFailed(QString,QString)),
0189             Qt::BlockingQueuedConnection);
0190 
0191     d->running = true;
0192 }
0193 
0194 CameraController::~CameraController()
0195 {
0196     // clear commands, stop camera
0197 
0198     slotCancel();
0199 
0200     // stop thread
0201     {
0202         QMutexLocker lock(&d->mutex);
0203         d->running = false;
0204         d->condVar.wakeAll();
0205     }
0206     wait();
0207 
0208     delete d->camera;
0209     delete d;
0210 }
0211 
0212 bool CameraController::cameraThumbnailSupport() const
0213 {
0214     if (!d->camera)
0215     {
0216         return false;
0217     }
0218 
0219     return d->camera->thumbnailSupport();
0220 }
0221 
0222 bool CameraController::cameraDeleteSupport() const
0223 {
0224     if (!d->camera)
0225     {
0226         return false;
0227     }
0228 
0229     return d->camera->deleteSupport();
0230 }
0231 
0232 bool CameraController::cameraUploadSupport() const
0233 {
0234     if (!d->camera)
0235     {
0236         return false;
0237     }
0238 
0239     return d->camera->uploadSupport();
0240 }
0241 
0242 bool CameraController::cameraMkDirSupport() const
0243 {
0244     if (!d->camera)
0245     {
0246         return false;
0247     }
0248 
0249     return d->camera->mkDirSupport();
0250 }
0251 
0252 bool CameraController::cameraDelDirSupport() const
0253 {
0254     if (!d->camera)
0255     {
0256         return false;
0257     }
0258 
0259     return d->camera->delDirSupport();
0260 }
0261 
0262 bool CameraController::cameraCaptureImageSupport() const
0263 {
0264     if (!d->camera)
0265     {
0266         return false;
0267     }
0268 
0269     return d->camera->captureImageSupport();
0270 }
0271 
0272 bool CameraController::cameraCaptureImagePreviewSupport() const
0273 {
0274     if (!d->camera)
0275     {
0276         return false;
0277     }
0278 
0279     return d->camera->captureImageSupport() && d->camera->captureImagePreviewSupport();
0280 }
0281 
0282 QString CameraController::cameraPath() const
0283 {
0284     if (!d->camera)
0285     {
0286         return QString();
0287     }
0288 
0289     return d->camera->path();
0290 }
0291 
0292 QString CameraController::cameraTitle() const
0293 {
0294     if (!d->camera)
0295     {
0296         return QString();
0297     }
0298 
0299     return d->camera->title();
0300 }
0301 
0302 DKCamera::CameraDriverType CameraController::cameraDriverType() const
0303 {
0304     if (!d->camera)
0305     {
0306         return DKCamera::UMSDriver;
0307     }
0308 
0309     return d->camera->cameraDriverType();
0310 }
0311 
0312 QByteArray CameraController::cameraMD5ID() const
0313 {
0314     if (!d->camera)
0315     {
0316         return QByteArray();
0317     }
0318 
0319     return d->camera->cameraMD5ID();
0320 }
0321 
0322 QIcon CameraController::mimeTypeThumbnail(const QString& itemName) const
0323 {
0324     if (!d->camera)
0325     {
0326         return QPixmap();
0327     }
0328 
0329     QFileInfo fi(itemName);
0330     QString mime = d->camera->mimeType(fi.suffix().toLower());
0331 
0332     if      (mime.startsWith(QLatin1String("image/x-raw")))
0333     {
0334         return QIcon::fromTheme(QLatin1String("image-x-adobe-dng"));
0335     }
0336     else if (mime.startsWith(QLatin1String("image/")))
0337     {
0338         return QIcon::fromTheme(QLatin1String("view-preview"));
0339     }
0340     else if (mime.startsWith(QLatin1String("video/")))
0341     {
0342         return QIcon::fromTheme(QLatin1String("video-x-generic"));
0343     }
0344     else if (mime.startsWith(QLatin1String("audio/")))
0345     {
0346         return QIcon::fromTheme(QLatin1String("audio-x-generic"));
0347     }
0348 
0349     return QIcon::fromTheme(QLatin1String("unknown"));
0350 }
0351 
0352 void CameraController::slotCancel()
0353 {
0354     d->canceled = true;
0355     d->camera->cancel();
0356 
0357     QMutexLocker lock(&d->mutex);
0358 
0359     qDeleteAll(d->cmdThumbs);
0360     qDeleteAll(d->commands);
0361     d->cmdThumbs.clear();
0362     d->commands.clear();
0363 }
0364 
0365 void CameraController::run()
0366 {
0367     while (d->running)
0368     {
0369         CameraCommand* command = nullptr;
0370 
0371         {
0372             QMutexLocker lock(&d->mutex);
0373 
0374             if      (!d->commands.isEmpty())
0375             {
0376                 command = d->commands.takeFirst();
0377                 Q_EMIT signalBusy(true);
0378             }
0379             else if (!d->cmdThumbs.isEmpty())
0380             {
0381                 command = d->cmdThumbs.takeFirst();
0382                 Q_EMIT signalBusy(false);
0383             }
0384             else
0385             {
0386                 Q_EMIT signalBusy(false);
0387                 d->condVar.wait(&d->mutex);
0388                 continue;
0389             }
0390         }
0391 
0392         if (command)
0393         {
0394             executeCommand(command);
0395             delete command;
0396         }
0397     }
0398 
0399     Q_EMIT signalBusy(false);
0400 }
0401 
0402 void CameraController::executeCommand(CameraCommand* const cmd)
0403 {
0404     if (!cmd)
0405     {
0406         return;
0407     }
0408 
0409     switch (cmd->action)
0410     {
0411         case (CameraCommand::cam_connect):
0412         {
0413             sendLogMsg(i18n("Connecting to camera..."));
0414 
0415             bool result = d->camera->doConnect();
0416 
0417             Q_EMIT signalConnected(result);
0418 
0419             if (result)
0420             {
0421                 d->camera->printSupportedFeatures();
0422                 sendLogMsg(i18n("Connection established."));
0423             }
0424             else
0425             {
0426                 sendLogMsg(i18n("Connection failed."));
0427             }
0428 
0429             break;
0430         }
0431 
0432         case (CameraCommand::cam_cameraInformation):
0433         {
0434             QString summary, manual, about;
0435 
0436             d->camera->cameraSummary(summary);
0437             d->camera->cameraManual(manual);
0438             d->camera->cameraAbout(about);
0439 
0440             Q_EMIT signalCameraInformation(summary, manual, about);
0441             break;
0442         }
0443 
0444         case (CameraCommand::cam_freeSpace):
0445         {
0446             qint64 bytesSize  = 0;
0447             qint64 bytesAvail = 0;
0448 
0449             if (!d->camera->getFreeSpace(bytesSize, bytesAvail))
0450             {
0451                 sendLogMsg(i18n("Failed to get free space from camera"),
0452                            DHistoryView::ErrorEntry);
0453             }
0454 
0455             Q_EMIT signalFreeSpace(bytesSize, bytesAvail);
0456             break;
0457         }
0458 
0459         case (CameraCommand::cam_preview):
0460         {
0461             QImage preview;
0462 
0463             if (!d->camera->getPreview(preview))
0464             {
0465                 sendLogMsg(i18n("Failed to get preview from camera"),
0466                            DHistoryView::ErrorEntry);
0467             }
0468 
0469             Q_EMIT signalPreview(preview);
0470             break;
0471         }
0472 
0473         case (CameraCommand::cam_capture):
0474         {
0475             CamItemInfo itemInfo;
0476 
0477             if (!d->camera->capture(itemInfo))
0478             {
0479                 sendLogMsg(i18n("Failed to process capture from camera"),
0480                            DHistoryView::ErrorEntry);
0481             }
0482 
0483             Q_EMIT signalUploaded(itemInfo);
0484             break;
0485         }
0486 
0487         case (CameraCommand::cam_listfolders):
0488         {
0489             QString folder = cmd->map[QLatin1String("folder")].toString();
0490 
0491             if (!d->camera->getFolders(folder))
0492             {
0493                 sendLogMsg(xi18n("Failed to list folder <filename>%1</filename>", folder),
0494                            DHistoryView::ErrorEntry);
0495             }
0496 
0497             break;
0498         }
0499 
0500         case (CameraCommand::cam_listfiles):
0501         {
0502             QString folder   = cmd->map[QLatin1String("folder")].toString();
0503             bool useMetadata = cmd->map[QLatin1String("useMetadata")].toBool();
0504 
0505             CamItemInfoList itemsList;
0506 
0507             if (!d->camera->getItemsInfoList(folder, useMetadata, itemsList))
0508             {
0509                 sendLogMsg(xi18n("Failed to list files in <filename>%1</filename>", folder),
0510                            DHistoryView::ErrorEntry);
0511             }
0512 
0513             // TODO would it be okay to pass this to the ImportItemModel and let it filter it for us?
0514 
0515             for (CamItemInfoList::iterator it = itemsList.begin() ; it != itemsList.end() ; )
0516             {
0517                 CamItemInfo &info = (*it);
0518 
0519                 if (info.mime.isEmpty())
0520                 {
0521                     it = itemsList.erase(it);
0522                     continue;
0523                 }
0524 
0525                 ++it;
0526             }
0527 
0528             Q_EMIT signalFileList(itemsList);
0529 
0530             break;
0531         }
0532 
0533         case (CameraCommand::cam_thumbsinfo):
0534         {
0535             QList<QVariant> list = cmd->map[QLatin1String("list")].toList();
0536             int thumbSize        = cmd->map[QLatin1String("thumbSize")].toInt();
0537 
0538             for (QList<QVariant>::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it)
0539             {
0540                 if (d->canceled)
0541                 {
0542                     break;
0543                 }
0544 
0545                 QString folder = (*it).toStringList().at(0);
0546                 QString file   = (*it).toStringList().at(1);
0547 
0548                 CamItemInfo info;
0549                 info.folder = folder;
0550                 info.name = file;
0551                 QImage thumbnail;
0552 
0553                 if (d->camera->getThumbnail(folder, file, thumbnail))
0554                 {
0555                     thumbnail = thumbnail.scaled(thumbSize, thumbSize, Qt::KeepAspectRatio,
0556                                                                        Qt::SmoothTransformation);
0557                     Q_EMIT signalThumbInfo(folder, file, info, thumbnail);
0558                 }
0559                 else
0560                 {
0561                     sendLogMsg(xi18n("Failed to get thumbnail for <filename>%1</filename>", file),
0562                                DHistoryView::ErrorEntry, folder, file);
0563                     Q_EMIT signalThumbInfoFailed(folder, file, info);
0564                 }
0565             }
0566 
0567             break;
0568         }
0569 
0570         case (CameraCommand::cam_metadata):
0571         {
0572             QString folder = cmd->map[QLatin1String("folder")].toString();
0573             QString file   = cmd->map[QLatin1String("file")].toString();
0574 
0575             QScopedPointer<DMetadata> meta(new DMetadata);
0576 
0577             if (!d->camera->getMetadata(folder, file, *meta))
0578             {
0579                 sendLogMsg(xi18n("Failed to get Metadata for <filename>%1</filename>", file),
0580                            DHistoryView::ErrorEntry, folder, file);
0581             }
0582 
0583             Q_EMIT signalMetadata(folder, file, meta.data()->data());
0584 
0585             break;
0586         }
0587 
0588         case (CameraCommand::cam_download):
0589         {
0590             QString   folder         = cmd->map[QLatin1String("folder")].toString();
0591             QString   file           = cmd->map[QLatin1String("file")].toString();
0592             QString   mime           = cmd->map[QLatin1String("mime")].toString();
0593             QString   dest           = cmd->map[QLatin1String("dest")].toString();
0594             bool      documentName   = cmd->map[QLatin1String("documentName")].toBool();
0595             bool      fixDateTime    = cmd->map[QLatin1String("fixDateTime")].toBool();
0596             QDateTime newDateTime    = cmd->map[QLatin1String("newDateTime")].toDateTime();
0597             QString   templateTitle  = cmd->map[QLatin1String("template")].toString();
0598             bool      convertJpeg    = cmd->map[QLatin1String("convertJpeg")].toBool();
0599             QString   losslessFormat = cmd->map[QLatin1String("losslessFormat")].toString();
0600             bool      backupRaw      = cmd->map[QLatin1String("backupRaw")].toBool();
0601             bool      convertDng     = cmd->map[QLatin1String("convertDng")].toBool();
0602             bool      compressDng    = cmd->map[QLatin1String("compressDng")].toBool();
0603             int       previewMode    = cmd->map[QLatin1String("previewMode")].toInt();
0604             QString   script         = cmd->map[QLatin1String("script")].toString();
0605             int       pickLabel      = cmd->map[QLatin1String("pickLabel")].toInt();
0606             int       colorLabel     = cmd->map[QLatin1String("colorLabel")].toInt();
0607             int       rating         = cmd->map[QLatin1String("rating")].toInt();
0608 
0609             // download to a temp file
0610 
0611             Q_EMIT signalDownloaded(folder, file, QString(), CamItemInfo::DownloadStarted);
0612 
0613             const QString tempTemplate(QLatin1String("%1Camera-XXXXXX.digikamtempfile.%2"));
0614             SafeTemporaryFile* const temp = new SafeTemporaryFile(tempTemplate.arg(dest).arg(file));
0615             temp->setAutoRemove(false);
0616             temp->open();
0617             QString tempFile              = temp->safeFilePath();
0618             delete temp;
0619 
0620             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Downloading: " << file << " using " << tempFile;
0621 
0622             bool result = d->camera->downloadItem(folder, file, tempFile);
0623 
0624             if      (!result || d->canceled)
0625             {
0626                 QFile::remove(tempFile);
0627 
0628                 sendLogMsg(xi18n("Failed to download <filename>%1</filename>", file),
0629                            DHistoryView::ErrorEntry, folder, file);
0630 
0631                 Q_EMIT signalDownloaded(folder, file, QString(), CamItemInfo::DownloadFailed);
0632 
0633                 break;
0634             }
0635             else if (mime == QLatin1String("image/jpeg"))
0636             {
0637                 // Possible modification operations. Only apply it to JPEG for the moment.
0638 
0639                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Set metadata from: " << file << " using " << tempFile;
0640 
0641                 QScopedPointer<DMetadata> metadata(new DMetadata(tempFile));
0642                 bool applyChanges = false;
0643 
0644                 if (documentName)
0645                 {
0646                     metadata->setExifTagString("Exif.Image.DocumentName", file);
0647                     applyChanges = true;
0648                 }
0649 
0650                 if (fixDateTime)
0651                 {
0652                     metadata->setImageDateTime(newDateTime, true);
0653                     applyChanges = true;
0654                 }
0655 
0656                 // TODO: Set image tags using DMetadata.
0657 
0658                 if (colorLabel > NoColorLabel)
0659                 {
0660                     metadata->setItemColorLabel(colorLabel);
0661                     applyChanges = true;
0662                 }
0663 
0664                 if (pickLabel > NoPickLabel)
0665                 {
0666                     metadata->setItemPickLabel(pickLabel);
0667                     applyChanges = true;
0668                 }
0669 
0670                 if (rating > RatingMin)
0671                 {
0672                     metadata->setItemRating(rating);
0673                     applyChanges = true;
0674                 }
0675 
0676                 if (!templateTitle.isEmpty())
0677                 {
0678                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Metadata template title:" << templateTitle;
0679 
0680                     if (templateTitle == Template::removeTemplateTitle())
0681                     {
0682                         metadata->removeMetadataTemplate();
0683                         applyChanges = true;
0684                     }
0685                     else
0686                     {
0687                         Template t = metadata->getMetadataTemplate();
0688                         t.merge(TemplateManager::defaultManager()->findByTitle(templateTitle));
0689 
0690                         metadata->setMetadataTemplate(t);
0691                         applyChanges = true;
0692                     }
0693                 }
0694 
0695                 if (applyChanges)
0696                 {
0697                     metadata->applyChanges();
0698                 }
0699 
0700                 // Convert JPEG file to lossless format if wanted,
0701                 // and move converted image to destination.
0702 
0703                 if (convertJpeg)
0704                 {
0705                     // When converting a file, we need to set the new format extension..
0706 
0707                     QFileInfo convInfo(file);
0708                     QString convFile               = convInfo.completeBaseName() +
0709                                                      QLatin1Char('.') + losslessFormat.toLower();
0710                     SafeTemporaryFile* const temp2 = new SafeTemporaryFile(tempTemplate.arg(dest).arg(convFile));
0711                     temp2->setAutoRemove(false);
0712                     temp2->open();
0713                     convFile                        = temp2->safeFilePath();
0714                     delete temp2;
0715 
0716                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert to LossLess: " << file;
0717 
0718                     if (!JPEGUtils::jpegConvert(tempFile, convFile, file, losslessFormat))
0719                     {
0720                         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert failed to JPEG!";
0721 
0722                         // convert failed. delete the temp file
0723 
0724                         QFile::remove(convFile);
0725 
0726                         sendLogMsg(xi18n("Failed to convert file <filename>%1</filename> to JPEG", file),
0727                                    DHistoryView::ErrorEntry, folder, file);
0728                     }
0729                     else
0730                     {
0731                         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Done, removing the temp file: " << tempFile;
0732 
0733                         // Else remove only the first temp file.
0734 
0735                         QFile::remove(tempFile);
0736                         tempFile = convFile;
0737                     }
0738                 }
0739             }
0740             else if (convertDng && (mime == QLatin1String("image/x-raw")))
0741             {
0742                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert to DNG: " << file;
0743 
0744                 QFileInfo dngInfo(file);
0745 
0746                 if  (dngInfo.suffix().toUpper() != QLatin1String("DNG"))
0747                 {
0748                     QString dngFile                = dngInfo.completeBaseName() + QLatin1String(".dng");
0749                     SafeTemporaryFile* const temp3 = new SafeTemporaryFile(tempTemplate.arg(dest).arg(dngFile));
0750                     temp3->setAutoRemove(false);
0751                     temp3->open();
0752                     dngFile                        = temp3->safeFilePath();
0753                     delete temp3;
0754 
0755                     DNGWriter dngWriter;
0756 
0757                     dngWriter.setInputFile(tempFile);
0758                     dngWriter.setOutputFile(dngFile);
0759                     dngWriter.setBackupOriginalRawFile(backupRaw);
0760                     dngWriter.setCompressLossLess(compressDng);
0761                     dngWriter.setPreviewMode(previewMode);
0762 
0763                     if (dngWriter.convert() != DNGWriter::PROCESS_COMPLETE)
0764                     {
0765                         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert failed to DNG!";
0766 
0767                         // convert failed. delete the temp file
0768 
0769                         QFile::remove(dngFile);
0770 
0771                         sendLogMsg(xi18n("Failed to convert file <filename>%1</filename> to DNG", file),
0772                                    DHistoryView::ErrorEntry, folder, file);
0773                     }
0774                     else
0775                     {
0776                         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Done, removing the temp file: " << tempFile;
0777 
0778                         // Else remove only the first temp file.
0779 
0780                         QFile::remove(tempFile);
0781                         tempFile = dngFile;
0782                     }
0783                 }
0784                 else
0785                 {
0786                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert skipped to DNG";
0787 
0788                     sendLogMsg(xi18n("Skipped to convert file <filename>%1</filename> to DNG", file),
0789                                DHistoryView::WarningEntry, folder, file);
0790                 }
0791             }
0792 
0793             // Run script
0794 
0795             if (!script.isEmpty())
0796             {
0797                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Got a script, processing:" << script;
0798 
0799                 QString s;
0800 
0801                 if (QFileInfo::exists(script))
0802                 {
0803                     QFile sFile(script);
0804 
0805                     if (!sFile.open(QIODevice::ReadOnly))
0806                     {
0807                         sendLogMsg(xi18n("Failed to open script for <filename>%1</filename>", file),
0808                                          DHistoryView::ErrorEntry,  folder, file);
0809                     }
0810                     else
0811                     {
0812                         s = QString::fromUtf8(sFile.readAll());
0813                     }
0814                 }
0815 
0816                 if (s.isEmpty())
0817                 {
0818                     s = script;
0819                 }
0820 
0821                 if (s.indexOf(QLatin1Char('%')) > -1)
0822                 {
0823                     // %filename must be replaced before %file
0824 
0825                     QFileInfo fileInfo(tempFile);
0826                     s.replace(QLatin1String("%orgfilename"), file,                Qt::CaseSensitive);
0827                     s.replace(QLatin1String("%filename"),    fileInfo.fileName(), Qt::CaseSensitive);
0828                     s.replace(QLatin1String("%orgpath"),     folder,              Qt::CaseSensitive);
0829                     s.replace(QLatin1String("%path"),        fileInfo.path(),     Qt::CaseSensitive);
0830                     s.replace(QLatin1String("%file"),        tempFile,            Qt::CaseSensitive);
0831                 }
0832                 else
0833                 {
0834                     if      (s.endsWith(QLatin1String("\r\n")))
0835                     {
0836                         s.chop(2);
0837                     }
0838                     else if (s.endsWith(QLatin1String("\n")))
0839                     {
0840                         s.chop(1);
0841                     }
0842 
0843                     s.append(QLatin1String(" \"") + tempFile + QLatin1String("\""));
0844                 }
0845 
0846                 // Start the process
0847 
0848                 QProcess process;
0849                 process.setProcessChannelMode(QProcess::SeparateChannels);
0850                 process.setProcessEnvironment(adjustedEnvironmentForAppImage());
0851 
0852 #ifdef Q_OS_WIN
0853 
0854                 QString dir                   = QDir::temp().path();
0855                 SafeTemporaryFile* const temp = new SafeTemporaryFile(dir + QLatin1String("/ImportScript-XXXXXX.cmd"));
0856                 temp->setAutoRemove(false);
0857                 temp->open();
0858                 QString scriptPath            = temp->safeFilePath();
0859 
0860                 // Crash fix: a QTemporaryFile is not properly closed until its destructor is called.
0861 
0862                 delete temp;
0863 
0864                 QFile tempFile(scriptPath);
0865 
0866                 if (tempFile.open(QIODevice::WriteOnly))
0867                 {
0868                     tempFile.write(s.toUtf8());
0869                     tempFile.close();
0870                 }
0871 
0872                 process.start(QLatin1String("cmd.exe"), QStringList() << QLatin1String("/C") << scriptPath);
0873 
0874 #else
0875 
0876                 process.start(QLatin1String("/bin/bash"), QStringList() << QLatin1String("-c") << s);
0877 
0878 #endif
0879                 if (!process.waitForFinished(60000))
0880                 {
0881                     sendLogMsg(xi18n("Timeout from script for <filename>%1</filename>", file),
0882                                      DHistoryView::ErrorEntry,  folder, file);
0883                     process.kill();
0884                 }
0885 
0886                 if (process.exitCode() != 0)
0887                 {
0888                     sendLogMsg(xi18n("Failed to run script for <filename>%1</filename>", file),
0889                                      DHistoryView::ErrorEntry,  folder, file);
0890                 }
0891 
0892                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "stdout" << process.readAllStandardOutput();
0893                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "stderr" << process.readAllStandardError();
0894 
0895 #ifdef Q_OS_WIN
0896 
0897                 tempFile.remove();
0898 
0899 #endif
0900 
0901             }
0902 
0903             Q_EMIT signalDownloaded(folder, file, tempFile, CamItemInfo::DownloadedYes);
0904 
0905             break;
0906         }
0907 
0908         case (CameraCommand::cam_upload):
0909         {
0910             QString folder = cmd->map[QLatin1String("destFolder")].toString();
0911 
0912             // We will using the same source file name to create the dest file
0913             // name in camera.
0914 
0915             QString file   = cmd->map[QLatin1String("destFile")].toString();
0916 
0917             // The source file path to download in camera.
0918 
0919             QString src    = cmd->map[QLatin1String("srcFilePath")].toString();
0920 
0921             CamItemInfo itemsInfo;
0922 
0923             bool result    = d->camera->uploadItem(folder, file, src, itemsInfo);
0924 
0925             if (result)
0926             {
0927                 Q_EMIT signalUploaded(itemsInfo);
0928             }
0929             else
0930             {
0931                 Q_EMIT signalInternalUploadFailed(folder, file, src);
0932             }
0933 
0934             break;
0935         }
0936 
0937         case (CameraCommand::cam_delete):
0938         {
0939             QString folder = cmd->map[QLatin1String("folder")].toString();
0940             QString file   = cmd->map[QLatin1String("file")].toString();
0941             bool result    = d->camera->deleteItem(folder, file);
0942 
0943             if (result)
0944             {
0945                 Q_EMIT signalDeleted(folder, file, true);
0946             }
0947             else
0948             {
0949                 Q_EMIT signalInternalDeleteFailed(folder, file);
0950             }
0951 
0952             break;
0953         }
0954 
0955         case (CameraCommand::cam_lock):
0956         {
0957             QString folder = cmd->map[QLatin1String("folder")].toString();
0958             QString file   = cmd->map[QLatin1String("file")].toString();
0959             bool    lock   = cmd->map[QLatin1String("lock")].toBool();
0960             bool result    = d->camera->setLockItem(folder, file, lock);
0961 
0962             if (result)
0963             {
0964                 Q_EMIT signalLocked(folder, file, true);
0965             }
0966             else
0967             {
0968                 Q_EMIT signalInternalLockFailed(folder, file);
0969             }
0970 
0971             break;
0972         }
0973 
0974         default:
0975         {
0976             qCWarning(DIGIKAM_IMPORTUI_LOG) << " unknown action specified";
0977             break;
0978         }
0979     }
0980 }
0981 
0982 void CameraController::sendLogMsg(const QString& msg, DHistoryView::EntryType type,
0983                                   const QString& folder, const QString& file)
0984 {
0985     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Log (" << file << " " << folder << ": " << msg;
0986 
0987     if (!d->canceled)
0988     {
0989         Q_EMIT signalLogMsg(msg, type, folder, file);
0990     }
0991 }
0992 
0993 void CameraController::slotDownloadFailed(const QString& folder, const QString& file)
0994 {
0995     sendLogMsg(xi18n("Failed to download <filename>%1</filename>", file),
0996                DHistoryView::ErrorEntry, folder, file);
0997 
0998     if (!d->canceled)
0999     {
1000         if (queueIsEmpty())
1001         {
1002             QMessageBox::critical(d->parent, qApp->applicationName(),
1003                                   i18n("Failed to download file <b>%1</b>.", file));
1004         }
1005         else
1006         {
1007             const QString msg = i18n("Failed to download file <b>%1</b>. Do you want to continue?", file);
1008             int result        = QMessageBox::warning(d->parent, qApp->applicationName(),
1009                                                      msg, QMessageBox::Yes | QMessageBox::Cancel);
1010 
1011             if (result != QMessageBox::Yes)
1012             {
1013                 slotCancel();
1014             }
1015         }
1016     }
1017 }
1018 
1019 void CameraController::slotUploadFailed(const QString& folder, const QString& file, const QString& src)
1020 {
1021     Q_UNUSED(folder);
1022     Q_UNUSED(src);
1023 
1024     sendLogMsg(xi18n("Failed to upload <filename>%1</filename>", file),
1025                DHistoryView::ErrorEntry);
1026 
1027     if (!d->canceled)
1028     {
1029         if (queueIsEmpty())
1030         {
1031             QMessageBox::critical(d->parent, qApp->applicationName(),
1032                                   i18n("Failed to upload file <b>%1</b>.", file));
1033         }
1034         else
1035         {
1036             const QString msg = i18n("Failed to upload file <b>%1</b>. Do you want to continue?", file);
1037             int result        = QMessageBox::warning(d->parent, qApp->applicationName(),
1038                                                      msg, QMessageBox::Yes | QMessageBox::Cancel);
1039 
1040             if (result != QMessageBox::Yes)
1041             {
1042                 slotCancel();
1043             }
1044         }
1045     }
1046 }
1047 
1048 void CameraController::slotDeleteFailed(const QString& folder, const QString& file)
1049 {
1050     Q_EMIT signalDeleted(folder, file, false);
1051     sendLogMsg(xi18n("Failed to delete <filename>%1</filename>", file),
1052                DHistoryView::ErrorEntry, folder, file);
1053 
1054     if (!d->canceled)
1055     {
1056         if (queueIsEmpty())
1057         {
1058             QMessageBox::critical(d->parent, qApp->applicationName(),
1059                                   i18n("Failed to delete file <b>%1</b>.", file));
1060         }
1061         else
1062         {
1063             const QString msg = i18n("Failed to delete file <b>%1</b>. Do you want to continue?", file);
1064             int result        = QMessageBox::warning(d->parent, qApp->applicationName(),
1065                                                      msg, QMessageBox::Yes | QMessageBox::Cancel);
1066 
1067             if (result != QMessageBox::Yes)
1068             {
1069                 slotCancel();
1070             }
1071         }
1072     }
1073 }
1074 
1075 void CameraController::slotLockFailed(const QString& folder, const QString& file)
1076 {
1077     Q_EMIT signalLocked(folder, file, false);
1078     sendLogMsg(xi18n("Failed to lock <filename>%1</filename>", file),
1079                DHistoryView::ErrorEntry, folder, file);
1080 
1081     if (!d->canceled)
1082     {
1083         if (queueIsEmpty())
1084         {
1085             QMessageBox::critical(d->parent, qApp->applicationName(),
1086                                   i18n("Failed to toggle lock file <b>%1</b>.", file));
1087         }
1088         else
1089         {
1090             const QString msg = i18n("Failed to toggle lock file <b>%1</b>. Do you want to continue?", file);
1091             int result        = QMessageBox::warning(d->parent, qApp->applicationName(),
1092                                                      msg, QMessageBox::Yes | QMessageBox::Cancel);
1093 
1094             if (result != QMessageBox::Yes)
1095             {
1096                 slotCancel();
1097             }
1098         }
1099     }
1100 }
1101 
1102 void CameraController::addCommand(CameraCommand* const cmd)
1103 {
1104     QMutexLocker lock(&d->mutex);
1105 
1106     if (cmd->action == CameraCommand::cam_thumbsinfo)
1107     {
1108         d->cmdThumbs << cmd;
1109     }
1110     else
1111     {
1112         d->commands << cmd;
1113     }
1114 
1115     d->condVar.wakeAll();
1116 }
1117 
1118 void CameraController::moveThumbsInfo(CameraCommand* const cmd)
1119 {
1120     QMutexLocker lock(&d->mutex);
1121 
1122     int index = d->cmdThumbs.indexOf(cmd);
1123 
1124     if (index > 0)
1125     {
1126         d->cmdThumbs.move(index, 0);
1127     }
1128 }
1129 
1130 bool CameraController::queueIsEmpty() const
1131 {
1132     QMutexLocker lock(&d->mutex);
1133 
1134     return (d->commands.isEmpty() && d->cmdThumbs.isEmpty());
1135 }
1136 
1137 void CameraController::slotConnect()
1138 {
1139     d->canceled              = false;
1140     CameraCommand* const cmd = new CameraCommand;
1141     cmd->action              = CameraCommand::cam_connect;
1142     addCommand(cmd);
1143 }
1144 
1145 void CameraController::listRootFolder(bool useMetadata)
1146 {
1147     listFolders(d->camera->path());
1148     listFiles(d->camera->path(), useMetadata);
1149 }
1150 
1151 void CameraController::listFolders(const QString& folder)
1152 {
1153     d->canceled              = false;
1154     CameraCommand* const cmd = new CameraCommand;
1155     cmd->action              = CameraCommand::cam_listfolders;
1156     cmd->map.insert(QLatin1String("folder"), QVariant(folder));
1157 
1158     addCommand(cmd);
1159 }
1160 
1161 void CameraController::listFiles(const QString& folder, bool useMetadata)
1162 {
1163     d->canceled              = false;
1164     CameraCommand* const cmd = new CameraCommand;
1165     cmd->action              = CameraCommand::cam_listfiles;
1166     cmd->map.insert(QLatin1String("folder"),      QVariant(folder));
1167     cmd->map.insert(QLatin1String("useMetadata"), QVariant(useMetadata));
1168     addCommand(cmd);
1169 }
1170 
1171 CameraCommand* CameraController::getThumbsInfo(const CamItemInfoList& list, int thumbSize)
1172 {
1173     d->canceled              = false;
1174     CameraCommand* const cmd = new CameraCommand;
1175     cmd->action              = CameraCommand::cam_thumbsinfo;
1176 
1177     QList<QVariant> itemsList;
1178 
1179     Q_FOREACH (const CamItemInfo& info, list)
1180     {
1181         itemsList.append(QStringList() << info.folder << info.name);
1182     }
1183 
1184     cmd->map.insert(QLatin1String("list"),      QVariant(itemsList));
1185     cmd->map.insert(QLatin1String("thumbSize"), QVariant(thumbSize));
1186     addCommand(cmd);
1187 
1188     return cmd;
1189 }
1190 
1191 void CameraController::getMetadata(const QString& folder, const QString& file)
1192 {
1193     d->canceled              = false;
1194     CameraCommand* const cmd = new CameraCommand;
1195     cmd->action              = CameraCommand::cam_metadata;
1196     cmd->map.insert(QLatin1String("folder"), QVariant(folder));
1197     cmd->map.insert(QLatin1String("file"),   QVariant(file));
1198     addCommand(cmd);
1199 }
1200 
1201 void CameraController::getCameraInformation()
1202 {
1203     d->canceled              = false;
1204     CameraCommand* const cmd = new CameraCommand;
1205     cmd->action              = CameraCommand::cam_cameraInformation;
1206     addCommand(cmd);
1207 }
1208 
1209 void CameraController::getFreeSpace()
1210 {
1211     d->canceled              = false;
1212     CameraCommand* const cmd = new CameraCommand;
1213     cmd->action              = CameraCommand::cam_freeSpace;
1214     addCommand(cmd);
1215 }
1216 
1217 void CameraController::getPreview()
1218 {
1219     d->canceled              = false;
1220     CameraCommand* const cmd = new CameraCommand;
1221     cmd->action              = CameraCommand::cam_preview;
1222     addCommand(cmd);
1223 }
1224 
1225 void CameraController::capture()
1226 {
1227     d->canceled              = false;
1228     CameraCommand* const cmd = new CameraCommand;
1229     cmd->action              = CameraCommand::cam_capture;
1230     addCommand(cmd);
1231 }
1232 
1233 void CameraController::upload(const QFileInfo& srcFileInfo, const QString& destFile, const QString& destFolder)
1234 {
1235     d->canceled              = false;
1236     CameraCommand* const cmd = new CameraCommand;
1237     cmd->action              = CameraCommand::cam_upload;
1238     cmd->map.insert(QLatin1String("srcFilePath"), QVariant(srcFileInfo.filePath()));
1239     cmd->map.insert(QLatin1String("destFile"),    QVariant(destFile));
1240     cmd->map.insert(QLatin1String("destFolder"),  QVariant(destFolder));
1241     addCommand(cmd);
1242     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Uploading '" << srcFileInfo.filePath()
1243                                   << "' into camera : '" << destFolder
1244                                   << "' (" << destFile << ")";
1245 }
1246 
1247 void CameraController::download(const DownloadSettingsList& list)
1248 {
1249     Q_FOREACH (const DownloadSettings& downloadSettings, list)
1250     {
1251         download(downloadSettings);
1252     }
1253 }
1254 
1255 void CameraController::download(const DownloadSettings& downloadSettings)
1256 {
1257     d->canceled              = false;
1258     CameraCommand* const cmd = new CameraCommand;
1259     cmd->action              = CameraCommand::cam_download;
1260     cmd->map.insert(QLatin1String("folder"),            QVariant(downloadSettings.folder));
1261     cmd->map.insert(QLatin1String("file"),              QVariant(downloadSettings.file));
1262     cmd->map.insert(QLatin1String("mime"),              QVariant(downloadSettings.mime));
1263     cmd->map.insert(QLatin1String("dest"),              QVariant(downloadSettings.dest));
1264     cmd->map.insert(QLatin1String("documentName"),      QVariant(downloadSettings.documentName));
1265     cmd->map.insert(QLatin1String("fixDateTime"),       QVariant(downloadSettings.fixDateTime));
1266     cmd->map.insert(QLatin1String("newDateTime"),       QVariant(downloadSettings.newDateTime));
1267     cmd->map.insert(QLatin1String("template"),          QVariant(downloadSettings.templateTitle));
1268     cmd->map.insert(QLatin1String("convertJpeg"),       QVariant(downloadSettings.convertJpeg));
1269     cmd->map.insert(QLatin1String("losslessFormat"),    QVariant(downloadSettings.losslessFormat));
1270     cmd->map.insert(QLatin1String("backupRaw"),         QVariant(downloadSettings.backupRaw));
1271     cmd->map.insert(QLatin1String("convertDng"),        QVariant(downloadSettings.convertDng));
1272     cmd->map.insert(QLatin1String("compressDng"),       QVariant(downloadSettings.compressDng));
1273     cmd->map.insert(QLatin1String("previewMode"),       QVariant(downloadSettings.previewMode));
1274     cmd->map.insert(QLatin1String("script"),            QVariant(downloadSettings.script));
1275     cmd->map.insert(QLatin1String("pickLabel"),         QVariant(downloadSettings.pickLabel));
1276     cmd->map.insert(QLatin1String("colorLabel"),        QVariant(downloadSettings.colorLabel));
1277     cmd->map.insert(QLatin1String("rating"),            QVariant(downloadSettings.rating));
1278 /*
1279     cmd->map.insert(QLatin1String("tagIds"),            QVariant(downloadSettings.tagIds));
1280 */
1281     addCommand(cmd);
1282 }
1283 
1284 void CameraController::deleteFile(const QString& folder, const QString& file)
1285 {
1286     d->canceled              = false;
1287     CameraCommand* const cmd = new CameraCommand;
1288     cmd->action              = CameraCommand::cam_delete;
1289     cmd->map.insert(QLatin1String("folder"), QVariant(folder));
1290     cmd->map.insert(QLatin1String("file"),   QVariant(file));
1291     addCommand(cmd);
1292 }
1293 
1294 void CameraController::lockFile(const QString& folder, const QString& file, bool locked)
1295 {
1296     d->canceled              = false;
1297     CameraCommand* const cmd = new CameraCommand;
1298     cmd->action              = CameraCommand::cam_lock;
1299     cmd->map.insert(QLatin1String("folder"), QVariant(folder));
1300     cmd->map.insert(QLatin1String("file"),   QVariant(file));
1301     cmd->map.insert(QLatin1String("lock"),   QVariant(locked));
1302     addCommand(cmd);
1303 }
1304 
1305 void CameraController::openFile(const QString& folder, const QString& file)
1306 {
1307     d->canceled              = false;
1308     CameraCommand* const cmd = new CameraCommand;
1309     cmd->action              = CameraCommand::cam_open;
1310     cmd->map.insert(QLatin1String("folder"), QVariant(folder));
1311     cmd->map.insert(QLatin1String("file"),   QVariant(file));
1312     cmd->map.insert(QLatin1String("dest"),   QVariant(QDir::tempPath() + QLatin1Char('/') + file));
1313     addCommand(cmd);
1314 }
1315 
1316 } // namespace Digikam
1317 
1318 #include "moc_cameracontroller.cpp"