File indexing completed on 2025-01-05 03:59:47
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2004-12-21 0007 * Description : USB Mass Storage camera interface 0008 * 0009 * SPDX-FileCopyrightText: 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com> 0010 * SPDX-FileCopyrightText: 2005-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "umscamera.h" 0017 0018 // Qt includes 0019 0020 #include <QDir> 0021 #include <QFile> 0022 #include <QFileInfo> 0023 #include <QDirIterator> 0024 #include <QTextDocument> 0025 #include <QtGlobal> 0026 #include <QScopedPointer> 0027 #include <QCryptographicHash> 0028 #include <qplatformdefs.h> 0029 0030 // KDE includes 0031 0032 #include <klocalizedstring.h> 0033 #include <kconfiggroup.h> 0034 #include <ksharedconfig.h> 0035 0036 // Solid includes 0037 0038 #if defined(Q_CC_CLANG) 0039 # pragma clang diagnostic push 0040 # pragma clang diagnostic ignored "-Wnonportable-include-path" 0041 #endif 0042 0043 #include <solid/device.h> 0044 #include <solid/storageaccess.h> 0045 #include <solid/storagedrive.h> 0046 #include <solid/storagevolume.h> 0047 0048 #if defined(Q_CC_CLANG) 0049 # pragma clang diagnostic pop 0050 #endif 0051 0052 // Local includes 0053 0054 #include "drawdecoder.h" 0055 #include "digikam_debug.h" 0056 #include "digikam_config.h" 0057 #include "dimg.h" 0058 #include "dmetadata.h" 0059 #include "itemscanner.h" 0060 #include "dfileoperations.h" 0061 0062 namespace Digikam 0063 { 0064 0065 UMSCamera::UMSCamera(const QString& title, const QString& model, 0066 const QString& port, const QString& path) 0067 : DKCamera(title, model, port, path), 0068 m_cancel(false) 0069 { 0070 getUUIDFromSolid(); 0071 } 0072 0073 UMSCamera::~UMSCamera() 0074 { 0075 } 0076 0077 /// Method not supported by UMS camera. 0078 bool UMSCamera::getPreview(QImage& /*preview*/) 0079 { 0080 return false; 0081 } 0082 0083 /// Method not supported by UMS camera. 0084 bool UMSCamera::capture(CamItemInfo& /*itemInfo*/) 0085 { 0086 return false; 0087 } 0088 0089 DKCamera::CameraDriverType UMSCamera::cameraDriverType() 0090 { 0091 return DKCamera::UMSDriver; 0092 } 0093 0094 QByteArray UMSCamera::cameraMD5ID() 0095 { 0096 QString camData; 0097 0098 // Camera media UUID is used (if available) to improve fingerprint value 0099 // registration to database. We want to be sure that MD5 sum is really unique. 0100 // We don't use camera information here. media UUID is enough because it came be 0101 // mounted by a card reader or a camera. In this case, "already downloaded" flag will 0102 // be independent of the device used to mount memory card. 0103 0104 camData.append(uuid()); 0105 QCryptographicHash md5(QCryptographicHash::Md5); 0106 md5.addData(camData.toUtf8()); 0107 0108 return md5.result().toHex(); 0109 } 0110 0111 /// NOTE: implemented in gui, outside the camera thread. 0112 bool UMSCamera::getFreeSpace(qint64& /*bytesSize*/, qint64& /*bytesAvail*/) 0113 { 0114 return false; 0115 } 0116 0117 bool UMSCamera::doConnect() 0118 { 0119 QFileInfo dir(m_path); 0120 0121 if (!dir.exists() || !dir.isReadable() || !dir.isDir()) 0122 { 0123 return false; 0124 } 0125 0126 if (dir.isWritable()) 0127 { 0128 m_deleteSupport = true; 0129 m_uploadSupport = true; 0130 m_mkDirSupport = true; 0131 m_delDirSupport = true; 0132 } 0133 else 0134 { 0135 m_deleteSupport = false; 0136 m_uploadSupport = false; 0137 m_mkDirSupport = false; 0138 m_delDirSupport = false; 0139 } 0140 0141 m_thumbnailSupport = true; // UMS camera always support thumbnails. 0142 m_captureImageSupport = false; // UMS camera never support capture mode. 0143 0144 return true; 0145 } 0146 0147 void UMSCamera::cancel() 0148 { 0149 // set the cancel flag 0150 0151 m_cancel = true; 0152 } 0153 0154 bool UMSCamera::getFolders(const QString& folder) 0155 { 0156 if (m_cancel) 0157 { 0158 return false; 0159 } 0160 0161 QDirIterator it(folder, QDir::Dirs | 0162 QDir::NoDotAndDotDot); 0163 0164 QStringList subFolderList; 0165 0166 while (it.hasNext() && !m_cancel) 0167 { 0168 subFolderList << it.next(); 0169 } 0170 0171 if (subFolderList.isEmpty()) 0172 { 0173 return true; 0174 } 0175 0176 Q_EMIT signalFolderList(subFolderList); 0177 0178 return true; 0179 } 0180 0181 bool UMSCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& infoList) 0182 { 0183 m_cancel = false; 0184 infoList.clear(); 0185 0186 if (!QFileInfo::exists(folder)) 0187 { 0188 return false; 0189 } 0190 0191 QDirIterator it(folder, QDir::Files | 0192 QDir::NoDotAndDotDot); 0193 0194 while (it.hasNext() && !m_cancel) 0195 { 0196 it.next(); 0197 CamItemInfo info; 0198 getItemInfo(folder, it.fileName(), info, useMetadata); 0199 infoList.append(info); 0200 } 0201 0202 return true; 0203 } 0204 0205 void UMSCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata) 0206 { 0207 info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0208 info.name = itemName; 0209 0210 QFileInfo fi(info.folder + info.name); 0211 info.size = fi.size(); 0212 info.readPermissions = fi.isReadable(); 0213 info.writePermissions = fi.isWritable(); 0214 info.mime = mimeType(fi.suffix().toLower()); 0215 0216 if (!info.mime.isEmpty()) 0217 { 0218 if (useMetadata) 0219 { 0220 // Try to use file metadata 0221 0222 QScopedPointer<DMetadata> meta(new DMetadata); 0223 getMetadata(folder, itemName, *meta); 0224 fillItemInfoFromMetadata(info, *meta); 0225 0226 // Fall back to file system info 0227 0228 if (info.ctime.isNull()) 0229 { 0230 info.ctime = ItemScanner::creationDateFromFilesystem(fi); 0231 } 0232 } 0233 else 0234 { 0235 // Only use file system date 0236 0237 info.ctime = ItemScanner::creationDateFromFilesystem(fi); 0238 } 0239 } 0240 0241 // if we have an image, allow previews 0242 // TODO allow video previews at some point? 0243 /* 0244 if (info.mime.startsWith(QLatin1String("image/")) || 0245 { 0246 info.mime.startsWith(QLatin1String("video/"))) 0247 } 0248 */ 0249 if (info.mime.startsWith(QLatin1String("image/"))) 0250 { 0251 info.previewPossible = true; 0252 } 0253 } 0254 0255 bool UMSCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail) 0256 { 0257 m_cancel = false; 0258 QString path = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0259 path += itemName; 0260 0261 // Try to get preview from Exif data (good quality). Can work with Raw files 0262 0263 QScopedPointer<DMetadata> metadata(new DMetadata(path)); 0264 metadata->getItemPreview(thumbnail); 0265 0266 if (!thumbnail.isNull()) 0267 { 0268 return true; 0269 } 0270 0271 // RAW files : try to extract embedded thumbnail using RawEngine 0272 0273 DRawDecoder::loadRawPreview(thumbnail, path); 0274 0275 if (!thumbnail.isNull()) 0276 { 0277 return true; 0278 } 0279 0280 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0281 KConfigGroup group = config->group(QLatin1String("Camera Settings")); 0282 bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); 0283 0284 // Try to get thumbnail from Exif data (poor quality). 0285 0286 if (!turnHighQualityThumbs) 0287 { 0288 thumbnail = metadata->getExifThumbnail(true); 0289 0290 if (!thumbnail.isNull()) 0291 { 0292 return true; 0293 } 0294 } 0295 0296 // THM files: try to get thumbnail from '.thm' files if we didn't manage to get 0297 // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. 0298 // Using this way is always speed up than ultimate loading using DImg. 0299 // Note: the thumbnail extracted with this method can be in poor quality. 0300 // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. 0301 0302 QFileInfo fi(path); 0303 0304 if (thumbnail.load(fi.path() + fi.baseName() + QLatin1String(".thm"))) // Lowercase 0305 { 0306 if (!thumbnail.isNull()) 0307 { 0308 return true; 0309 } 0310 } 0311 else if (thumbnail.load(fi.path() + fi.baseName() + QLatin1String(".THM"))) // Uppercase 0312 { 0313 if (!thumbnail.isNull()) 0314 { 0315 return true; 0316 } 0317 } 0318 0319 // Finally, we trying to get thumbnail using DImg API (slow). 0320 0321 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Use DImg loader to get thumbnail from : " << path; 0322 0323 DImg dimgThumb; 0324 0325 // skip loading the data we don't need to speed it up. 0326 0327 dimgThumb.load(path, false /*loadMetadata*/, false /*loadICCData*/, false /*loadUniqueHash*/, false /*loadHistory*/); 0328 0329 if (!dimgThumb.isNull()) 0330 { 0331 thumbnail = dimgThumb.copyQImage(); 0332 0333 return true; 0334 } 0335 0336 return false; 0337 } 0338 0339 bool UMSCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta) 0340 { 0341 QString path = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0342 QFileInfo fi, thmlo, thmup; 0343 bool ret = false; 0344 0345 fi.setFile(path + itemName); 0346 thmlo.setFile(path + fi.baseName() + QLatin1String(".thm")); 0347 thmup.setFile(path + fi.baseName() + QLatin1String(".THM")); 0348 0349 if (thmlo.exists()) 0350 { 0351 // Try thumbnail sidecar files with lowercase extension. 0352 0353 ret = meta.load(thmlo.filePath()); 0354 } 0355 else if (thmup.exists()) 0356 { 0357 // Try thumbnail sidecar files with uppercase extension. 0358 0359 ret = meta.load(thmup.filePath()); 0360 } 0361 else 0362 { 0363 // If no thumbnail sidecar file available, try to load image metadata for files. 0364 0365 ret = meta.load(fi.filePath()); 0366 } 0367 0368 return ret; 0369 } 0370 0371 bool UMSCamera::downloadItem(const QString& folder, const QString& itemName, const QString& saveFile) 0372 { 0373 m_cancel = false; 0374 QString src = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0375 src += itemName; 0376 QString dest = saveFile; 0377 0378 QFile sFile(src); 0379 QFile dFile(dest); 0380 0381 if (!sFile.open(QIODevice::ReadOnly)) 0382 { 0383 qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading:" << src; 0384 0385 return false; 0386 } 0387 0388 if (!dFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) 0389 { 0390 sFile.close(); 0391 qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing:" << dest; 0392 0393 return false; 0394 } 0395 0396 const int MAX_IPC_SIZE = (1024 * 32); 0397 QByteArray buffer(MAX_IPC_SIZE, '\0'); 0398 qint64 len; 0399 0400 while (((len = sFile.read(buffer.data(), MAX_IPC_SIZE)) != 0) && !m_cancel) 0401 { 0402 if ((len == -1) || (dFile.write(buffer.data(), len) != len)) 0403 { 0404 sFile.close(); 0405 dFile.close(); 0406 0407 return false; 0408 } 0409 } 0410 0411 sFile.close(); 0412 dFile.close(); 0413 0414 // Set the file modification time of the downloaded file to the original file. 0415 // NOTE: this behavior don't need to be managed through Setup/Metadata settings. 0416 0417 dFile.setPermissions(sFile.permissions()); 0418 DFileOperations::copyModificationTime(src, dest); 0419 0420 return true; 0421 } 0422 0423 bool UMSCamera::setLockItem(const QString& folder, const QString& itemName, bool lock) 0424 { 0425 QString src = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0426 src += itemName; 0427 0428 if (lock) 0429 { 0430 // Lock the file to set read only flag 0431 0432 if (::chmod(QFile::encodeName(src).constData(), S_IREAD) == -1) 0433 { 0434 return false; 0435 } 0436 } 0437 else 0438 { 0439 // Unlock the file to set read/write flag 0440 0441 if (::chmod(QFile::encodeName(src).constData(), S_IREAD | S_IWRITE) == -1) 0442 { 0443 return false; 0444 } 0445 } 0446 0447 return true; 0448 } 0449 0450 bool UMSCamera::deleteItem(const QString& folder, const QString& itemName) 0451 { 0452 m_cancel = false; 0453 QString path = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0454 0455 // Any camera provide THM (thumbnail) file with real image. We need to remove it also. 0456 0457 QFileInfo fi(path + itemName); 0458 0459 QFileInfo thmLo(path + fi.baseName() + QLatin1String(".thm")); // Lowercase 0460 0461 if (thmLo.exists()) 0462 { 0463 QFile::remove(thmLo.filePath()); 0464 } 0465 0466 QFileInfo thmUp(path + fi.baseName() + QLatin1String(".THM")); // Uppercase 0467 0468 if (thmUp.exists()) 0469 { 0470 QFile::remove(thmUp.filePath()); 0471 } 0472 0473 // Remove the real image. 0474 0475 return QFile::remove(path + itemName); 0476 } 0477 0478 bool UMSCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& info) 0479 { 0480 m_cancel = false; 0481 QString dest = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0482 dest += itemName; 0483 QString src = localFile; 0484 0485 QFile sFile(src); 0486 QFile dFile(dest); 0487 0488 if (!sFile.open(QIODevice::ReadOnly)) 0489 { 0490 qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; 0491 0492 return false; 0493 } 0494 0495 if (!dFile.open(QIODevice::WriteOnly)) 0496 { 0497 sFile.close(); 0498 qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; 0499 0500 return false; 0501 } 0502 0503 const int MAX_IPC_SIZE = (1024 * 32); 0504 0505 char buffer[MAX_IPC_SIZE]; 0506 0507 qint64 len; 0508 0509 while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) 0510 { 0511 if ((len == -1) || (dFile.write(buffer, (quint64)len) == -1)) 0512 { 0513 sFile.close(); 0514 dFile.close(); 0515 0516 return false; 0517 } 0518 } 0519 0520 sFile.close(); 0521 dFile.close(); 0522 0523 // Set the file modification time of the uploaded file to original file. 0524 // NOTE: this behavior don't need to be managed through Setup/Metadata settings. 0525 0526 DFileOperations::copyModificationTime(src, dest); 0527 0528 // Get new camera item information. 0529 0530 PhotoInfoContainer pInfo; 0531 QScopedPointer<DMetadata> meta(new DMetadata); 0532 QFileInfo fi(dest); 0533 QString mime = mimeType(fi.suffix().toLower()); 0534 0535 if (!mime.isEmpty()) 0536 { 0537 QSize dims; 0538 QDateTime dt; 0539 0540 // Try to load image metadata. 0541 0542 meta->load(fi.filePath()); 0543 dt = meta->getItemDateTime(); 0544 dims = meta->getItemDimensions(); 0545 pInfo = meta->getPhotographInformation(); 0546 0547 if (dt.isNull()) // fall back to file system info 0548 { 0549 dt = ItemScanner::creationDateFromFilesystem(fi); 0550 } 0551 0552 info.name = fi.fileName(); 0553 info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; 0554 info.mime = mime; 0555 info.ctime = dt; 0556 info.size = fi.size(); 0557 info.width = dims.width(); 0558 info.height = dims.height(); 0559 info.downloaded = CamItemInfo::DownloadUnknown; 0560 info.readPermissions = fi.isReadable(); 0561 info.writePermissions = fi.isWritable(); 0562 info.photoInfo = pInfo; 0563 } 0564 0565 return true; 0566 } 0567 0568 bool UMSCamera::cameraSummary(QString& summary) 0569 { 0570 summary = QString(i18nc("@info", "\"Mounted Camera\" driver for USB/IEEE1394 mass storage cameras and " 0571 "Flash disk card readers.\n\n")); 0572 0573 // we do not expect title/model/etc. to contain newlines, 0574 // so we just escape HTML characters 0575 0576 summary += i18nc("@info List of device properties", 0577 "Title: \"%1\"\n" 0578 "Model: \"%2\"\n" 0579 "Port: \"%3\"\n" 0580 "Path: \"%4\"\n" 0581 "UUID: \"%5\"\n\n", 0582 title(), model(), port(), path(), uuid()); 0583 0584 summary += i18nc("@info List of supported device operations", 0585 "Thumbnails: \"%1\"\n" 0586 "Capture image: \"%2\"\n" 0587 "Delete items: \"%3\"\n" 0588 "Upload items: \"%4\"\n" 0589 "Create directories: \"%5\"\n" 0590 "Delete directories: \"%6\"\n\n", 0591 thumbnailSupport() ? i18nc("@info: ums backend feature", "yes") : i18nc("@info: ums backend feature", "no"), 0592 captureImageSupport() ? i18nc("@info: ums backend feature", "yes") : i18nc("@info: ums backend feature", "no"), 0593 deleteSupport() ? i18nc("@info: ums backend feature", "yes") : i18nc("@info: ums backend feature", "no"), 0594 uploadSupport() ? i18nc("@info: ums backend feature", "yes") : i18nc("@info: ums backend feature", "no"), 0595 mkDirSupport() ? i18nc("@info: ums backend feature", "yes") : i18nc("@info: ums backend feature", "no"), 0596 delDirSupport() ? i18nc("@info: ums backend feature", "yes") : i18nc("@info: ums backend feature", "no")); 0597 return true; 0598 } 0599 0600 bool UMSCamera::cameraManual(QString& manual) 0601 { 0602 manual = QString(i18nc("@info", "For more information about the \"Mounted Camera\" driver, " 0603 "please read the \"Supported Digital Still " 0604 "Cameras\" section in the digiKam manual.")); 0605 return true; 0606 } 0607 0608 bool UMSCamera::cameraAbout(QString& about) 0609 { 0610 about = QString(i18nc("@info", "The \"Mounted Camera\" driver is a simple interface to a camera disk " 0611 "mounted locally on your system.\n\n" 0612 "It does not use libgphoto2 drivers.\n\n" 0613 "To report any problems with this driver, please contact the digiKam team at:\n\n" 0614 "https://www.digikam.org/?q=contact")); 0615 return true; 0616 } 0617 0618 void UMSCamera::getUUIDFromSolid() 0619 { 0620 QList<Solid::Device> devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); 0621 0622 Q_FOREACH (const Solid::Device& accessDevice, devices) 0623 { 0624 // check for StorageAccess 0625 0626 if (!accessDevice.is<Solid::StorageAccess>()) 0627 { 0628 continue; 0629 } 0630 0631 const Solid::StorageAccess* const access = accessDevice.as<Solid::StorageAccess>(); 0632 0633 if (!access->isAccessible()) 0634 { 0635 continue; 0636 } 0637 0638 // check for StorageDrive 0639 0640 Solid::Device driveDevice; 0641 0642 for (Solid::Device currentDevice = accessDevice ; 0643 currentDevice.isValid() ; 0644 currentDevice = currentDevice.parent()) 0645 { 0646 if (currentDevice.is<Solid::StorageDrive>()) 0647 { 0648 driveDevice = currentDevice; 0649 break; 0650 } 0651 } 0652 0653 if (!driveDevice.isValid()) 0654 { 0655 continue; 0656 } 0657 0658 // check for StorageVolume 0659 0660 Solid::Device volumeDevice; 0661 0662 for (Solid::Device currentDevice = accessDevice ; 0663 currentDevice.isValid() ; 0664 currentDevice = currentDevice.parent()) 0665 { 0666 if (currentDevice.is<Solid::StorageVolume>()) 0667 { 0668 volumeDevice = currentDevice; 0669 break; 0670 } 0671 } 0672 0673 if (!volumeDevice.isValid()) 0674 { 0675 continue; 0676 } 0677 0678 Solid::StorageVolume* const volume = volumeDevice.as<Solid::StorageVolume>(); 0679 0680 if (m_path.startsWith(QDir::fromNativeSeparators(access->filePath())) && 0681 (QDir::fromNativeSeparators(access->filePath()) != QLatin1String("/"))) 0682 { 0683 m_uuid = volume->uuid(); 0684 } 0685 } 0686 } 0687 0688 } // namespace Digikam 0689 0690 #include "moc_umscamera.cpp"