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"