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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2003-01-21
0007  * Description : Gphoto2 camera interface
0008  *
0009  * SPDX-FileCopyrightText: 2003-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  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "gpcamera.h"
0018 
0019 // C ANSI includes
0020 
0021 extern "C"
0022 {
0023 #ifndef Q_CC_MSVC
0024 #   include <unistd.h>
0025 #   include <utime.h>
0026 #else
0027 #   include <sys/utime.h>
0028 #endif
0029 }
0030 
0031 // C++ includes
0032 
0033 #include <cstdio>
0034 #include <iostream>
0035 
0036 // Qt includes
0037 
0038 #include <QString>
0039 #include <QStringList>
0040 #include <QImage>
0041 #include <QPixmap>
0042 #include <QFile>
0043 #include <QScopedPointer>
0044 #include <QDateTime>
0045 #include <QTextDocument>
0046 #include <QCryptographicHash>
0047 
0048 // KDE includes
0049 
0050 #include <klocalizedstring.h>
0051 
0052 // Local includes
0053 
0054 #include "digikam_debug.h"
0055 #include "digikam_config.h"
0056 #include "dfileoperations.h"
0057 #include "dmetadata.h"
0058 
0059 //#define GPHOTO2_DEBUG 1
0060 
0061 #ifdef HAVE_GPHOTO2
0062 
0063 // LibGphoto2 includes
0064 
0065 extern "C"
0066 {
0067 #include <gphoto2.h>
0068 }
0069 
0070 #endif // HAVE_GPHOTO2
0071 
0072 namespace Digikam
0073 {
0074 
0075 class Q_DECL_HIDDEN GPStatus
0076 {
0077 public:
0078 
0079     GPStatus()
0080     {
0081 
0082 #ifdef HAVE_GPHOTO2
0083 
0084         context = gp_context_new();
0085         cancel  = false;
0086         gp_context_set_cancel_func(context, cancel_func, nullptr);
0087 
0088 #   ifdef GPHOTO2_DEBUG
0089 
0090         gp_context_set_progress_funcs(context, start_func, update_func, stop_func, 0);
0091         gp_context_set_error_func(context, error_func, 0);
0092         gp_context_set_status_func(context, status_func, 0);
0093 
0094 #   endif // GPHOTO2_DEBUG
0095 
0096 #endif // HAVE_GPHOTO2
0097 
0098     }
0099 
0100     ~GPStatus()
0101     {
0102 
0103 #ifdef HAVE_GPHOTO2
0104 
0105         gp_context_unref(context);
0106         cancel = false;
0107 
0108 #endif // HAVE_GPHOTO2
0109 
0110     }
0111 
0112     static bool cancel;
0113 
0114 #ifdef HAVE_GPHOTO2
0115 
0116     GPContext*  context;
0117 
0118     static GPContextFeedback cancel_func(GPContext*, void*)
0119     {
0120         return (cancel ? GP_CONTEXT_FEEDBACK_CANCEL :
0121                 GP_CONTEXT_FEEDBACK_OK);
0122     }
0123 
0124 #   ifdef GPHOTO2_DEBUG
0125 
0126     static void error_func(GPContext*, const char* msg, void*)
0127     {
0128         qCDebug(DIGIKAM_IMPORTUI_LOG) << "error:" << msg;
0129     }
0130 
0131     static void status_func(GPContext*, const char* msg, void*)
0132     {
0133         qCDebug(DIGIKAM_IMPORTUI_LOG) << "status:" << msg;
0134     }
0135 
0136     static unsigned int start_func(GPContext*, float target, const char *text, void *data)
0137     {
0138         Q_UNUSED(data);
0139         qCDebug(DIGIKAM_IMPORTUI_LOG) << "start:" << target << "- text:" << text;
0140         return 0;
0141 
0142     }
0143 
0144     static void update_func(GPContext*, unsigned int id, float target, void *data)
0145     {
0146         Q_UNUSED(data);
0147         qCDebug(DIGIKAM_IMPORTUI_LOG) << "update:" << id << "- target:" << target;
0148     }
0149 
0150     static void stop_func(GPContext*, unsigned int id, void *data)
0151     {
0152         Q_UNUSED(data);
0153         qCDebug(DIGIKAM_IMPORTUI_LOG) << "stop:" << id;
0154     }
0155 
0156 #   endif // GPHOTO2_DEBUG
0157 
0158 #endif // HAVE_GPHOTO2
0159 
0160 };
0161 
0162 bool GPStatus::cancel = false;
0163 
0164 // ---------------------------------------------------------------------------
0165 
0166 class Q_DECL_HIDDEN GPCamera::Private
0167 {
0168 
0169 public:
0170 
0171     explicit Private()
0172 
0173 #ifdef HAVE_GPHOTO2
0174 
0175         : cameraInitialized(false),
0176           camera           (nullptr),
0177           status           (nullptr)
0178 
0179 #endif // HAVE_GPHOTO2
0180 
0181     {
0182     }
0183 
0184 #ifdef HAVE_GPHOTO2
0185 
0186     bool            cameraInitialized;
0187 
0188     Camera*         camera;
0189 
0190     CameraAbilities cameraAbilities;
0191 
0192     GPStatus*       status;
0193 
0194 #endif // HAVE_GPHOTO2
0195 
0196 };
0197 
0198 // ---------------------------------------------------------------------------
0199 
0200 GPCamera::GPCamera(const QString& title, const QString& model,
0201                    const QString& port, const QString& path)
0202     : DKCamera(title, model, port, path),
0203       d       (new Private)
0204 {
0205 }
0206 
0207 GPCamera::~GPCamera()
0208 {
0209 
0210 #ifdef HAVE_GPHOTO2
0211 
0212     if (d->status)
0213     {
0214         gp_context_unref(d->status->context);
0215         d->status = nullptr;
0216     }
0217 
0218     if (d->camera)
0219     {
0220         gp_camera_unref(d->camera);
0221         d->camera = nullptr;
0222     }
0223 
0224 #endif // HAVE_GPHOTO2
0225 
0226     delete d;
0227 }
0228 
0229 DKCamera::CameraDriverType GPCamera::cameraDriverType()
0230 {
0231     return DKCamera::GPhotoDriver;
0232 }
0233 
0234 QByteArray GPCamera::cameraMD5ID()
0235 {
0236     QByteArray md5data;
0237 
0238 #ifdef HAVE_GPHOTO2
0239 
0240     QString    camData;
0241 
0242     // We don't use camera title from digiKam settings panel to compute MD5 fingerprint,
0243     // because it can be changed by users between session.
0244 
0245     camData.append(model());
0246 
0247     // TODO is it really necessary to have a path here? I think model+filename+size+ctime should be enough to give unique fingerprint
0248     // while still allowing you to move files around in the camera if needed
0249 
0250     camData.append(path());
0251     QCryptographicHash md5(QCryptographicHash::Md5);
0252     md5.addData(camData.toUtf8());
0253     md5data = md5.result().toHex();
0254 
0255 #endif // HAVE_GPHOTO2
0256 
0257     return md5data;
0258 }
0259 
0260 bool GPCamera::doConnect()
0261 {
0262 
0263 #ifdef HAVE_GPHOTO2
0264 
0265     int errorCode;
0266 
0267     // -- first step - setup the camera --------------------
0268 
0269     if (d->camera)
0270     {
0271         gp_camera_unref(d->camera);
0272         d->camera = nullptr;
0273     }
0274 
0275     CameraAbilitiesList* abilList = nullptr;
0276     GPPortInfoList*      infoList = nullptr;
0277     GPPortInfo           info;
0278 
0279     gp_camera_new(&d->camera);
0280 
0281     delete d->status;
0282     d->status = nullptr;
0283     d->status = new GPStatus();
0284 
0285     gp_abilities_list_new(&abilList);
0286     gp_abilities_list_load(abilList, d->status->context);
0287     gp_port_info_list_new(&infoList);
0288     gp_port_info_list_load(infoList);
0289 
0290     int modelNum     = gp_abilities_list_lookup_model(abilList, m_model.toLatin1().constData());
0291     int portNum      = gp_port_info_list_lookup_path(infoList, m_port.toLatin1().constData());
0292 
0293     gp_abilities_list_get_abilities(abilList, modelNum, &d->cameraAbilities);
0294 
0295     errorCode    = gp_camera_set_abilities(d->camera, d->cameraAbilities);
0296 
0297     if (errorCode != GP_OK)
0298     {
0299         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera Abilities!";
0300         printGphotoErrorDescription(errorCode);
0301         gp_camera_unref(d->camera);
0302         d->camera = nullptr;
0303         gp_abilities_list_free(abilList);
0304         gp_port_info_list_free(infoList);
0305         return false;
0306     }
0307 
0308     if (m_model != QLatin1String("Directory Browse"))
0309     {
0310         gp_port_info_list_get_info(infoList, portNum, &info);
0311         errorCode = gp_camera_set_port_info(d->camera, info);
0312 
0313         if (errorCode != GP_OK)
0314         {
0315             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera port!";
0316             printGphotoErrorDescription(errorCode);
0317             gp_camera_unref(d->camera);
0318             d->camera = nullptr;
0319             gp_abilities_list_free(abilList);
0320             gp_port_info_list_free(infoList);
0321             return false;
0322         }
0323     }
0324 
0325     gp_abilities_list_free(abilList);
0326     gp_port_info_list_free(infoList);
0327 
0328     if (d->cameraAbilities.file_operations &
0329         GP_FILE_OPERATION_PREVIEW)
0330     {
0331         m_thumbnailSupport = true;
0332     }
0333 
0334     if (d->cameraAbilities.file_operations &
0335         GP_FILE_OPERATION_DELETE)
0336     {
0337         m_deleteSupport = true;
0338     }
0339 
0340     if (d->cameraAbilities.folder_operations &
0341         GP_FOLDER_OPERATION_PUT_FILE)
0342     {
0343         m_uploadSupport = true;
0344     }
0345 
0346     if (d->cameraAbilities.folder_operations &
0347         GP_FOLDER_OPERATION_MAKE_DIR)
0348     {
0349         m_mkDirSupport = true;
0350     }
0351 
0352     if (d->cameraAbilities.folder_operations &
0353         GP_FOLDER_OPERATION_REMOVE_DIR)
0354     {
0355         m_delDirSupport = true;
0356     }
0357 
0358     if (d->cameraAbilities.operations &
0359         GP_OPERATION_CAPTURE_IMAGE)
0360     {
0361         m_captureImageSupport = true;
0362     }
0363 
0364     if (d->cameraAbilities.operations &
0365         GP_OPERATION_CAPTURE_PREVIEW)
0366     {
0367         m_captureImagePreviewSupport = true;
0368     }
0369 
0370     // -- Try and initialize the camera to see if its connected -----------------
0371 
0372     errorCode = gp_camera_init(d->camera, d->status->context);
0373 
0374     if (errorCode != GP_OK)
0375     {
0376         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to initialize camera!";
0377         printGphotoErrorDescription(errorCode);
0378         gp_camera_unref(d->camera);
0379         d->camera = nullptr;
0380         return false;
0381     }
0382 
0383     d->cameraInitialized = true;
0384 
0385     return true;
0386 
0387 #else
0388 
0389     return false;
0390 
0391 #endif // HAVE_GPHOTO2
0392 
0393 }
0394 
0395 void GPCamera::cancel()
0396 {
0397 
0398 #ifdef HAVE_GPHOTO2
0399 
0400 /*
0401     TODO what to do on cancel, if there's nothing to cancel?
0402 
0403     if (!d->status)
0404     {
0405         return;
0406     }
0407 */
0408 
0409     d->status->cancel = true;
0410 
0411 #endif // HAVE_GPHOTO2
0412 
0413 }
0414 
0415 bool GPCamera::getFreeSpace(qint64& bytesSize, qint64& bytesAvail)
0416 {
0417 
0418 #ifdef HAVE_GPHOTO2
0419 
0420     // NOTE: This method depends of libgphoto2 2.4.0
0421 
0422     int                       nrofsinfos;
0423     CameraStorageInformation* sinfos = nullptr;
0424 
0425     d->status->cancel = false;
0426     int errorCode     = gp_camera_get_storageinfo(d->camera, &sinfos, &nrofsinfos, d->status->context);
0427 
0428     if (errorCode != GP_OK)
0429     {
0430         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Getting storage information not supported for this camera!";
0431         printGphotoErrorDescription(errorCode);
0432         return false;
0433     }
0434 
0435     for (int i = 0 ; i < nrofsinfos ; ++i)
0436     {
0437         if (sinfos[i].fields & GP_STORAGEINFO_FILESYSTEMTYPE)
0438         {
0439             switch (sinfos[i].fstype)
0440             {
0441                 case GP_STORAGEINFO_FST_UNDEFINED:
0442                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: undefined";
0443                     break;
0444                 case GP_STORAGEINFO_FST_GENERICFLAT:
0445                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: flat, all in one directory";
0446                     break;
0447                 case GP_STORAGEINFO_FST_GENERICHIERARCHICAL:
0448                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: generic tree hierarchy";
0449                     break;
0450                 case GP_STORAGEINFO_FST_DCF:
0451                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "DCIM style storage";
0452                     break;
0453             }
0454         }
0455 
0456         if (sinfos[i].fields & GP_STORAGEINFO_LABEL)
0457         {
0458             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage label: " << QString::fromUtf8(sinfos[i].label);
0459         }
0460 
0461         if (sinfos[i].fields & GP_STORAGEINFO_DESCRIPTION)
0462         {
0463             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage description: " << QString::fromUtf8(sinfos[i].description);
0464         }
0465 
0466         if (sinfos[i].fields & GP_STORAGEINFO_BASE)
0467         {
0468             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage base-dir: " << QString::fromUtf8(sinfos[i].basedir);
0469 
0470             // TODO in order for this to work, the upload dialog needs to be fixed.
0471 /*
0472             if (nrofsinfos == 1)
0473             {
0474                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Only one storage, so setting storage directory to" << sinfos[i].basedir;
0475                 m_path = QString(sinfos[i].basedir);
0476             }
0477 */
0478         }
0479 
0480         if (sinfos[i].fields & GP_STORAGEINFO_ACCESS)
0481         {
0482             switch (sinfos[i].access)
0483             {
0484                 case GP_STORAGEINFO_AC_READWRITE:
0485                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: R/W";
0486                     break;
0487 
0488                 case GP_STORAGEINFO_AC_READONLY:
0489                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: RO";
0490                     break;
0491 
0492                 case GP_STORAGEINFO_AC_READONLY_WITH_DELETE:
0493                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: RO + Del";
0494                     break;
0495 
0496                 default:
0497                     break;
0498             }
0499         }
0500 
0501         if (sinfos[i].fields & GP_STORAGEINFO_STORAGETYPE)
0502         {
0503             switch (sinfos[i].type)
0504             {
0505                 case GP_STORAGEINFO_ST_FIXED_ROM:
0506                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: fixed ROM";
0507                     break;
0508 
0509                 case GP_STORAGEINFO_ST_REMOVABLE_ROM:
0510                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: removable ROM";
0511                     break;
0512 
0513                 case GP_STORAGEINFO_ST_FIXED_RAM:
0514                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: fixed RAM";
0515                     break;
0516 
0517                 case GP_STORAGEINFO_ST_REMOVABLE_RAM:
0518                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: removable RAM";
0519                     break;
0520 
0521                 case GP_STORAGEINFO_ST_UNKNOWN:
0522                 default:
0523                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: unknown";
0524                     break;
0525             }
0526         }
0527 
0528         if (sinfos[i].fields & GP_STORAGEINFO_MAXCAPACITY)
0529         {
0530             bytesSize += sinfos[i].capacitykbytes * 1024;
0531             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage capacity: " << bytesSize;
0532         }
0533 
0534         if (sinfos[i].fields & GP_STORAGEINFO_FREESPACEKBYTES)
0535         {
0536             bytesAvail += sinfos[i].freekbytes * 1024;
0537             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage free-space: " << bytesAvail;
0538         }
0539     }
0540 
0541     return true;
0542 
0543 #else
0544 
0545     Q_UNUSED(bytesSize);
0546     Q_UNUSED(bytesAvail);
0547     return false;
0548 
0549 #endif // HAVE_GPHOTO2
0550 
0551 }
0552 
0553 bool GPCamera::getPreview(QImage& preview)
0554 {
0555 
0556 #ifdef HAVE_GPHOTO2
0557 
0558     int               errorCode;
0559     CameraFile*       cfile = nullptr;
0560     const char*       data  = nullptr;
0561     unsigned long int size;
0562 
0563     d->status->cancel = false;
0564     gp_file_new(&cfile);
0565 
0566     errorCode = gp_camera_capture_preview(d->camera, cfile, d->status->context);
0567 
0568     if (errorCode != GP_OK)
0569     {
0570         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to initialize camera preview mode!";
0571         printGphotoErrorDescription(errorCode);
0572         gp_file_unref(cfile);
0573         return false;
0574     }
0575 
0576     errorCode = gp_file_get_data_and_size(cfile, &data, &size);
0577 
0578     if (errorCode != GP_OK)
0579     {
0580         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get preview from camera!";
0581         printGphotoErrorDescription(errorCode);
0582         gp_file_unref(cfile);
0583         return false;
0584     }
0585 
0586     preview.loadFromData((const uchar*)data, (uint)size);
0587 
0588     gp_file_unref(cfile);
0589     return true;
0590 
0591 #else
0592 
0593     Q_UNUSED(preview);
0594     return false;
0595 
0596 #endif // HAVE_GPHOTO2
0597 
0598 }
0599 
0600 bool GPCamera::capture(CamItemInfo& itemInfo)
0601 {
0602 
0603 #ifdef HAVE_GPHOTO2
0604 
0605     int            errorCode;
0606     CameraFilePath path;
0607 
0608     d->status->cancel = false;
0609     errorCode         = gp_camera_capture(d->camera, GP_CAPTURE_IMAGE, &path, d->status->context);
0610 
0611     if (errorCode != GP_OK)
0612     {
0613         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to take camera capture!";
0614         printGphotoErrorDescription(errorCode);
0615         return false;
0616     }
0617 
0618     // Get new camera item information.
0619 
0620     itemInfo.folder = QString::fromUtf8(path.folder);
0621     itemInfo.name   = QString::fromUtf8(path.name);
0622 
0623     CameraFileInfo info;
0624     errorCode       = gp_camera_file_get_info(d->camera, QFile::encodeName(itemInfo.folder).constData(),
0625                                               QFile::encodeName(itemInfo.name).constData(), &info,
0626                                               d->status->context);
0627 
0628     if (errorCode != GP_OK)
0629     {
0630         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item information!";
0631         printGphotoErrorDescription(errorCode);
0632         return false;
0633     }
0634 
0635     itemInfo.ctime            = QDateTime();
0636     itemInfo.mime             = QString();
0637     itemInfo.size             = -1;
0638     itemInfo.width            = -1;
0639     itemInfo.height           = -1;
0640     itemInfo.downloaded       = CamItemInfo::DownloadUnknown;
0641     itemInfo.readPermissions  = -1;
0642     itemInfo.writePermissions = -1;
0643 
0644     /* The mime type returned by Gphoto2 is dummy with all RAW files.
0645     if (info.file.fields & GP_FILE_INFO_TYPE)
0646         itemInfo.mime = info.file.type;*/
0647 
0648     itemInfo.mime = mimeType(itemInfo.name.section(QLatin1Char('.'), -1).toLower());
0649 
0650     if (info.file.fields & GP_FILE_INFO_MTIME)
0651     {
0652         itemInfo.ctime = QDateTime::fromSecsSinceEpoch(info.file.mtime);
0653     }
0654 
0655     if (info.file.fields & GP_FILE_INFO_SIZE)
0656     {
0657         itemInfo.size = info.file.size;
0658     }
0659 
0660     if (info.file.fields & GP_FILE_INFO_WIDTH)
0661     {
0662         itemInfo.width = info.file.width;
0663     }
0664 
0665     if (info.file.fields & GP_FILE_INFO_HEIGHT)
0666     {
0667         itemInfo.height = info.file.height;
0668     }
0669 
0670     if (info.file.fields & GP_FILE_INFO_STATUS)
0671     {
0672         if (info.file.status == GP_FILE_STATUS_DOWNLOADED)
0673         {
0674             itemInfo.downloaded = CamItemInfo::DownloadedYes;
0675         }
0676         else
0677         {
0678             itemInfo.downloaded = CamItemInfo::DownloadedNo;
0679         }
0680     }
0681 
0682     if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
0683     {
0684         if (info.file.permissions & GP_FILE_PERM_READ)
0685         {
0686             itemInfo.readPermissions = 1;
0687         }
0688         else
0689         {
0690             itemInfo.readPermissions = 0;
0691         }
0692 
0693         if (info.file.permissions & GP_FILE_PERM_DELETE)
0694         {
0695             itemInfo.writePermissions = 1;
0696         }
0697         else
0698         {
0699             itemInfo.writePermissions = 0;
0700         }
0701     }
0702 
0703     return true;
0704 
0705 #else
0706 
0707     Q_UNUSED(itemInfo);
0708     return false;
0709 
0710 #endif // HAVE_GPHOTO2
0711 
0712 }
0713 
0714 bool GPCamera::getFolders(const QString& folder)
0715 {
0716 
0717 #ifdef HAVE_GPHOTO2
0718 
0719     int         errorCode;
0720     CameraList* clist = nullptr;
0721     gp_list_new(&clist);
0722 
0723     d->status->cancel = false;
0724     errorCode         = gp_camera_folder_list_folders(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context);
0725 
0726     if (errorCode != GP_OK)
0727     {
0728         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folders list from camera!";
0729         printGphotoErrorDescription(errorCode);
0730         gp_list_unref(clist);
0731         return false;
0732     }
0733 
0734     QStringList subFolderList;
0735     int count = gp_list_count(clist);
0736 
0737     if (count < 1)
0738     {
0739         return true;
0740     }
0741 
0742     for (int i = 0 ; i < count ; ++i)
0743     {
0744         const char* subFolder = nullptr;
0745         errorCode             = gp_list_get_name(clist, i, &subFolder);
0746 
0747         if (errorCode != GP_OK)
0748         {
0749             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder name from camera!";
0750             printGphotoErrorDescription(errorCode);
0751             gp_list_unref(clist);
0752             return false;
0753         }
0754 
0755         subFolderList.append(folder + QFile::decodeName(subFolder) + QLatin1Char('/'));
0756     }
0757 
0758     gp_list_unref(clist);
0759 
0760     Q_EMIT signalFolderList(subFolderList);
0761 
0762     return true;
0763 
0764 #else
0765 
0766     Q_UNUSED(folder);
0767     return false;
0768 
0769 #endif // HAVE_GPHOTO2
0770 
0771 }
0772 
0773 // TODO unused, remove?
0774 bool GPCamera::getItemsList(const QString& folder, QStringList& itemsList)
0775 {
0776 
0777 #ifdef HAVE_GPHOTO2
0778 
0779     int         errorCode;
0780     CameraList* clist = nullptr;
0781     const char* cname = nullptr;
0782 
0783     gp_list_new(&clist);
0784 
0785     d->status->cancel = false;
0786     errorCode         = gp_camera_folder_list_files(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context);
0787 
0788     if (errorCode != GP_OK)
0789     {
0790         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder files list from camera!";
0791         printGphotoErrorDescription(errorCode);
0792         gp_list_unref(clist);
0793         return false;
0794     }
0795 
0796     int count = gp_list_count(clist);
0797 
0798     for (int i = 0 ; i < count ; ++i)
0799     {
0800         errorCode = gp_list_get_name(clist, i, &cname);
0801 
0802         if (errorCode != GP_OK)
0803         {
0804             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get file name from camera!";
0805             printGphotoErrorDescription(errorCode);
0806             gp_list_unref(clist);
0807             return false;
0808         }
0809 
0810         itemsList.append(QFile::decodeName(cname));
0811     }
0812 
0813     gp_list_unref(clist);
0814 
0815     return true;
0816 
0817 #else
0818 
0819     Q_UNUSED(folder);
0820     Q_UNUSED(itemsList);
0821 
0822     return false;
0823 
0824 #endif // HAVE_GPHOTO2
0825 
0826 }
0827 
0828 bool GPCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& items)
0829 {
0830 
0831 #ifdef HAVE_GPHOTO2
0832 
0833     int         errorCode;
0834     CameraList* clist = nullptr;
0835     const char* cname = nullptr;
0836 
0837     gp_list_new(&clist);
0838 
0839     d->status->cancel = false;
0840     errorCode         = gp_camera_folder_list_files(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context);
0841 
0842     if (errorCode != GP_OK)
0843     {
0844         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder files list from camera!";
0845         printGphotoErrorDescription(errorCode);
0846         gp_list_unref(clist);
0847         return false;
0848     }
0849 
0850     int count = gp_list_count(clist);
0851 
0852     for (int i = 0 ; i < count ; ++i)
0853     {
0854         errorCode = gp_list_get_name(clist, i, &cname);
0855 
0856         if (errorCode != GP_OK)
0857         {
0858             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get file name from camera!";
0859             printGphotoErrorDescription(errorCode);
0860             gp_list_unref(clist);
0861             return false;
0862         }
0863 
0864         // TODO for further speed-up, getItemInfoInternal call could be called separately when needed
0865         CamItemInfo info;
0866         getItemInfoInternal(folder, QFile::decodeName(cname), info, useMetadata);
0867         items.append(info);
0868     }
0869 
0870     gp_list_unref(clist);
0871 
0872     return true;
0873 
0874 #else
0875 
0876     Q_UNUSED(folder);
0877     Q_UNUSED(useMetadata);
0878     Q_UNUSED(items);
0879 
0880     return false;
0881 
0882 #endif // HAVE_GPHOTO2
0883 
0884 }
0885 
0886 void GPCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata)
0887 {
0888 
0889 #ifdef HAVE_GPHOTO2
0890 
0891     getItemInfoInternal(folder, itemName, info, useMetadata);
0892 
0893 #else
0894     Q_UNUSED(folder);
0895     Q_UNUSED(itemName);
0896     Q_UNUSED(info);
0897     Q_UNUSED(useMetadata);
0898 
0899 #endif // HAVE_GPHOTO2
0900 
0901 }
0902 
0903 void GPCamera::getItemInfoInternal(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata)
0904 {
0905 
0906 #ifdef HAVE_GPHOTO2
0907 
0908     info.folder          = folder;
0909     info.name            = itemName;
0910     d->status->cancel    = false;
0911     info.previewPossible = false;
0912 
0913     CameraFileInfo cfinfo;
0914     gp_camera_file_get_info(d->camera, QFile::encodeName(info.folder).constData(),
0915                             QFile::encodeName(info.name).constData(), &cfinfo, d->status->context);
0916 
0917     // if preview has size field, it's a valid preview most likely, otherwise we'll skip it later on
0918 
0919     if (cfinfo.preview.fields & GP_FILE_INFO_SIZE)
0920     {
0921         info.previewPossible = true;
0922     }
0923 
0924     if (cfinfo.file.fields & GP_FILE_INFO_STATUS)
0925     {
0926         if (cfinfo.file.status == GP_FILE_STATUS_DOWNLOADED)
0927         {
0928             info.downloaded = CamItemInfo::DownloadedYes;
0929         }
0930     }
0931 
0932     if (cfinfo.file.fields & GP_FILE_INFO_SIZE)
0933     {
0934         info.size = cfinfo.file.size;
0935     }
0936 
0937     if (cfinfo.file.fields & GP_FILE_INFO_PERMISSIONS)
0938     {
0939         if (cfinfo.file.permissions & GP_FILE_PERM_READ)
0940         {
0941             info.readPermissions = 1;
0942         }
0943         else
0944         {
0945             info.readPermissions = 0;
0946         }
0947 
0948         if (cfinfo.file.permissions & GP_FILE_PERM_DELETE)
0949         {
0950             info.writePermissions = 1;
0951         }
0952         else
0953         {
0954             info.writePermissions = 0;
0955         }
0956     }
0957 
0958 /*
0959         The mime type returned by Gphoto2 is dummy with all RAW files.
0960 
0961         if (cfinfo.file.fields & GP_FILE_INFO_TYPE)
0962         {
0963             info.mime = cfinfo.file.type;
0964         }
0965 */
0966 
0967     info.mime = mimeType(info.name.section(QLatin1Char('.'), -1).toLower());
0968 
0969     if (!info.mime.isEmpty())
0970     {
0971         if (useMetadata)
0972         {
0973             // Try to use file metadata
0974             QScopedPointer<DMetadata> meta(new DMetadata);
0975             getMetadata(folder, itemName, *meta);
0976             fillItemInfoFromMetadata(info, *meta);
0977 
0978             // Fall back to camera file system info
0979 
0980             if (info.ctime.isNull())
0981             {
0982                 if (cfinfo.file.fields & GP_FILE_INFO_MTIME)
0983                 {
0984                     info.ctime = QDateTime::fromSecsSinceEpoch(cfinfo.file.mtime);
0985                 }
0986                 else
0987                 {
0988                     info.ctime = QDateTime::currentDateTime();
0989                 }
0990 
0991                 info.ctime.setTimeSpec(Qt::UTC);
0992             }
0993         }
0994         else
0995         {
0996             // Only use properties provided by camera.
0997             if (cfinfo.file.fields & GP_FILE_INFO_MTIME)
0998             {
0999                 info.ctime = QDateTime::fromSecsSinceEpoch(cfinfo.file.mtime);
1000             }
1001             else
1002             {
1003                 info.ctime = QDateTime::currentDateTime();
1004             }
1005 
1006             info.ctime.setTimeSpec(Qt::UTC);
1007 
1008             if (cfinfo.file.fields & GP_FILE_INFO_WIDTH)
1009             {
1010                 info.width = cfinfo.file.width;
1011             }
1012 
1013             if (cfinfo.file.fields & GP_FILE_INFO_HEIGHT)
1014             {
1015                 info.height = cfinfo.file.height;
1016             }
1017         }
1018     }
1019 
1020 #else
1021 
1022     Q_UNUSED(folder);
1023     Q_UNUSED(itemName);
1024     Q_UNUSED(info);
1025     Q_UNUSED(useMetadata);
1026 
1027 #endif // HAVE_GPHOTO2
1028 
1029 }
1030 
1031 bool GPCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail)
1032 {
1033 #ifdef HAVE_GPHOTO2
1034 
1035     int                errorCode;
1036     CameraFile*        cfile = nullptr;
1037     const char*        data  = nullptr;
1038     unsigned long int  size;
1039 
1040     gp_file_new(&cfile);
1041 
1042     d->status->cancel = false;
1043     errorCode         = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(),
1044                                            QFile::encodeName(itemName).constData(),
1045                                            GP_FILE_TYPE_PREVIEW,
1046                                            cfile, d->status->context);
1047 
1048     if (errorCode != GP_OK)
1049     {
1050         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!" << folder << itemName;
1051         printGphotoErrorDescription(errorCode);
1052         gp_file_unref(cfile);
1053         return false;
1054     }
1055 
1056     errorCode = gp_file_get_data_and_size(cfile, &data, &size);
1057 
1058     if (errorCode != GP_OK)
1059     {
1060         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get thumbnail from camera item!" << folder << itemName;
1061         printGphotoErrorDescription(errorCode);
1062         gp_file_unref(cfile);
1063         return false;
1064     }
1065 
1066     thumbnail.loadFromData((const uchar*) data, (uint) size);
1067 
1068     gp_file_unref(cfile);
1069     return !thumbnail.isNull();
1070 
1071 #else
1072 
1073     Q_UNUSED(folder);
1074     Q_UNUSED(itemName);
1075     Q_UNUSED(thumbnail);
1076 
1077     return false;
1078 
1079 #endif // HAVE_GPHOTO2
1080 
1081 }
1082 
1083 bool GPCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta)
1084 {
1085 #ifdef HAVE_GPHOTO2
1086 
1087     int               errorCode;
1088     CameraFile*       cfile = nullptr;
1089     const char*       data  = nullptr;
1090     unsigned long int size;
1091 
1092     gp_file_new(&cfile);
1093 
1094     d->status->cancel = false;
1095     errorCode         = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(),
1096                                            QFile::encodeName(itemName).constData(),
1097                                            GP_FILE_TYPE_EXIF,
1098                                            cfile, d->status->context);
1099 
1100     if (errorCode != GP_OK)
1101     {
1102         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!";
1103         printGphotoErrorDescription(errorCode);
1104         gp_file_unref(cfile);
1105         return false;
1106     }
1107 
1108     errorCode = gp_file_get_data_and_size(cfile, &data, &size);
1109 
1110     if (errorCode != GP_OK)
1111     {
1112         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get Exif data from camera item!";
1113         printGphotoErrorDescription(errorCode);
1114         gp_file_unref(cfile);
1115         return false;
1116     }
1117 
1118     QByteArray exifData(data, size);
1119 
1120     gp_file_unref(cfile);
1121 
1122     // Sometimes, GPhoto2 drivers return complete APP1 JFIF section. Exiv2 cannot
1123     // decode (yet) exif metadata from APP1. We will find Exif header to get data at this place
1124     // to please with Exiv2...
1125 
1126     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Size of Exif metadata from camera = " << exifData.size();
1127 
1128     if (!exifData.isEmpty())
1129     {
1130         char exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
1131         int i             = exifData.indexOf(*exifHeader);
1132 
1133         if (i != -1)
1134         {
1135             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Exif header found at position " << i;
1136             i = i + sizeof(exifHeader);
1137             QByteArray data;
1138             data.resize(exifData.size() - i);
1139             memcpy(data.data(), exifData.data() + i, data.size());
1140             meta.setExif(data);
1141             return true;
1142         }
1143     }
1144 
1145     return false;
1146 
1147 #else
1148 
1149     Q_UNUSED(folder);
1150     Q_UNUSED(itemName);
1151     Q_UNUSED(meta);
1152 
1153     return false;
1154 
1155 #endif // HAVE_GPHOTO2
1156 }
1157 
1158 bool GPCamera::downloadItem(const QString& folder, const QString& itemName,
1159                             const QString& saveFile)
1160 {
1161 #ifdef HAVE_GPHOTO2
1162 
1163     int         errorCode;
1164     CameraFile* cfile = nullptr;
1165 
1166     d->status->cancel = false;
1167     QFile file(saveFile);
1168 
1169     if (!file.open(QIODevice::ReadWrite))
1170     {
1171         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to open file" << file.fileName() << file.errorString();
1172         return false;
1173     }
1174 
1175     // dup fd, passing fd control to gphoto2 later
1176     int handle = dup(file.handle());
1177 
1178     if (handle == -1)
1179     {
1180         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to dup file descriptor";
1181         return false;
1182     }
1183 
1184     errorCode = gp_file_new_from_fd(&cfile, handle);
1185 
1186     if (errorCode != GP_OK)
1187     {
1188         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!";
1189         printGphotoErrorDescription(errorCode);
1190         return false;
1191     }
1192 
1193     errorCode = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(),
1194                                    QFile::encodeName(itemName).constData(),
1195                                    GP_FILE_TYPE_NORMAL, cfile,
1196                                    d->status->context);
1197 
1198     if (errorCode != GP_OK)
1199     {
1200         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!";
1201         printGphotoErrorDescription(errorCode);
1202         gp_file_unref(cfile);
1203         return false;
1204     }
1205 
1206     time_t mtime;
1207     errorCode = gp_file_get_mtime(cfile, &mtime);
1208 
1209     file.close();
1210 
1211     if ((errorCode == GP_OK) && mtime)
1212     {
1213         DFileOperations::setModificationTime(saveFile, QDateTime::fromSecsSinceEpoch(mtime));
1214     }
1215 
1216     gp_file_unref(cfile);
1217 
1218     return true;
1219 
1220 #else
1221 
1222     Q_UNUSED(folder);
1223     Q_UNUSED(itemName);
1224     Q_UNUSED(saveFile);
1225 
1226     return false;
1227 
1228 #endif // HAVE_GPHOTO2
1229 
1230 }
1231 
1232 bool GPCamera::setLockItem(const QString& folder, const QString& itemName, bool lock)
1233 {
1234 #ifdef HAVE_GPHOTO2
1235 
1236     int errorCode;
1237 
1238     d->status->cancel = false;
1239     CameraFileInfo info;
1240     errorCode         = gp_camera_file_get_info(d->camera, QFile::encodeName(folder).constData(),
1241                                                 QFile::encodeName(itemName).constData(), &info, d->status->context);
1242 
1243     if (errorCode != GP_OK)
1244     {
1245         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item properties!";
1246         printGphotoErrorDescription(errorCode);
1247         return false;
1248     }
1249 
1250     if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
1251     {
1252         if (lock)
1253         {
1254             // Lock the file to set read only flag
1255             info.file.permissions = (CameraFilePermissions)GP_FILE_PERM_READ;
1256         }
1257         else
1258         {
1259             // Unlock the file to set read/write flag
1260             info.file.permissions = (CameraFilePermissions)(GP_FILE_PERM_READ | GP_FILE_PERM_DELETE);
1261         }
1262     }
1263 
1264     // Some gphoto2 drivers need to have only the right flag at on to process properties update in camera.
1265     info.file.fields    = GP_FILE_INFO_PERMISSIONS;
1266     info.preview.fields = GP_FILE_INFO_NONE;
1267     info.audio.fields   = GP_FILE_INFO_NONE;
1268 
1269     errorCode = gp_camera_file_set_info(d->camera, QFile::encodeName(folder).constData(),
1270                                         QFile::encodeName(itemName).constData(), info, d->status->context);
1271 
1272     if (errorCode != GP_OK)
1273     {
1274         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera item lock properties!";
1275         printGphotoErrorDescription(errorCode);
1276         return false;
1277     }
1278 
1279     return true;
1280 
1281 #else
1282 
1283     Q_UNUSED(folder);
1284     Q_UNUSED(itemName);
1285     Q_UNUSED(lock);
1286 
1287     return false;
1288 
1289 #endif // HAVE_GPHOTO2
1290 
1291 }
1292 
1293 bool GPCamera::deleteItem(const QString& folder, const QString& itemName)
1294 {
1295 #ifdef HAVE_GPHOTO2
1296 
1297     d->status->cancel = false;
1298     int errorCode     = gp_camera_file_delete(d->camera, QFile::encodeName(folder).constData(),
1299                                               QFile::encodeName(itemName).constData(),
1300                                               d->status->context);
1301 
1302     if (errorCode != GP_OK)
1303     {
1304         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to delete camera item!";
1305         printGphotoErrorDescription(errorCode);
1306         return false;
1307     }
1308 
1309     return true;
1310 
1311 #else
1312 
1313     Q_UNUSED(folder);
1314     Q_UNUSED(itemName);
1315 
1316     return false;
1317 
1318 #endif // HAVE_GPHOTO2
1319 
1320 }
1321 
1322 // TODO fix to go through all folders
1323 // TODO this was never even used.
1324 bool GPCamera::deleteAllItems(const QString& folder)
1325 {
1326 #ifdef HAVE_GPHOTO2
1327 
1328     int         errorCode;
1329     QStringList folderList;
1330 
1331     d->status->cancel = false;
1332     errorCode         = gp_camera_folder_delete_all(d->camera, QFile::encodeName(folder).constData(),
1333                                                     d->status->context);
1334 
1335     if (errorCode != GP_OK)
1336     {
1337         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to delete camera folder!";
1338         printGphotoErrorDescription(errorCode);
1339         return false;
1340     }
1341 
1342     return true;
1343 
1344 #else
1345 
1346     Q_UNUSED(folder);
1347 
1348     return false;
1349 
1350 #endif // HAVE_GPHOTO2
1351 
1352 }
1353 
1354 bool GPCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& itemInfo)
1355 {
1356 #ifdef HAVE_GPHOTO2
1357 
1358     int         errorCode;
1359     CameraFile* cfile = nullptr;
1360     errorCode         = gp_file_new(&cfile);
1361     d->status->cancel = false;
1362 
1363     if (errorCode != GP_OK)
1364     {
1365         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to init new camera file instance!";
1366         printGphotoErrorDescription(errorCode);
1367         return false;
1368     }
1369 
1370     errorCode = gp_file_open(cfile, QFile::encodeName(localFile).constData());
1371 
1372     if (errorCode != GP_OK)
1373     {
1374         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to open file!";
1375         printGphotoErrorDescription(errorCode);
1376         gp_file_unref(cfile);
1377         return false;
1378     }
1379 
1380     errorCode = gp_file_set_name(cfile, QFile::encodeName(itemName).constData());
1381 
1382     if (errorCode != GP_OK)
1383     {
1384         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to rename item from camera!";
1385         printGphotoErrorDescription(errorCode);
1386         gp_file_unref(cfile);
1387         return false;
1388     }
1389 
1390 #   ifdef HAVE_GPHOTO25
1391 
1392     errorCode = gp_camera_folder_put_file(d->camera,
1393                                           QFile::encodeName(folder).constData(),
1394                                           QFile::encodeName(itemName).constData(),
1395                                           GP_FILE_TYPE_NORMAL,
1396                                           cfile,
1397                                           d->status->context);
1398 #   else
1399 
1400     errorCode = gp_camera_folder_put_file(d->camera,
1401                                           QFile::encodeName(folder).constData(),
1402                                           cfile,
1403                                           d->status->context);
1404 #   endif
1405 
1406     if (errorCode != GP_OK)
1407     {
1408         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to upload item to camera!";
1409         printGphotoErrorDescription(errorCode);
1410         gp_file_unref(cfile);
1411         return false;
1412     }
1413 
1414     // Get new camera item information.
1415 
1416     itemInfo.name   = itemName;
1417     itemInfo.folder = folder;
1418 
1419     CameraFileInfo info;
1420     errorCode       = gp_camera_file_get_info(d->camera, QFile::encodeName(folder).constData(),
1421                                               QFile::encodeName(itemName).constData(), &info, d->status->context);
1422 
1423     if (errorCode != GP_OK)
1424     {
1425         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item information!";
1426         printGphotoErrorDescription(errorCode);
1427         gp_file_unref(cfile);
1428         return false;
1429     }
1430 
1431     itemInfo.ctime            = QDateTime();
1432     itemInfo.mime             = QString();
1433     itemInfo.size             = -1;
1434     itemInfo.width            = -1;
1435     itemInfo.height           = -1;
1436     itemInfo.downloaded       = CamItemInfo::DownloadUnknown;
1437     itemInfo.readPermissions  = -1;
1438     itemInfo.writePermissions = -1;
1439 
1440     /* The mime type returned by Gphoto2 is dummy with all RAW files.
1441     if (info.file.fields & GP_FILE_INFO_TYPE)
1442         itemInfo.mime = info.file.type;
1443     */
1444 
1445     itemInfo.mime = mimeType(itemInfo.name.section(QLatin1Char('.'), -1).toLower());
1446 
1447     if (info.file.fields & GP_FILE_INFO_MTIME)
1448     {
1449         itemInfo.ctime = QDateTime::fromSecsSinceEpoch(info.file.mtime);
1450     }
1451 
1452     if (info.file.fields & GP_FILE_INFO_SIZE)
1453     {
1454         itemInfo.size = info.file.size;
1455     }
1456 
1457     if (info.file.fields & GP_FILE_INFO_WIDTH)
1458     {
1459         itemInfo.width = info.file.width;
1460     }
1461 
1462     if (info.file.fields & GP_FILE_INFO_HEIGHT)
1463     {
1464         itemInfo.height = info.file.height;
1465     }
1466 
1467     if (info.file.fields & GP_FILE_INFO_STATUS)
1468     {
1469         if (info.file.status == GP_FILE_STATUS_DOWNLOADED)
1470         {
1471             itemInfo.downloaded = CamItemInfo::DownloadedYes;
1472         }
1473         else
1474         {
1475             itemInfo.downloaded = CamItemInfo::DownloadedNo;
1476         }
1477     }
1478 
1479     if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
1480     {
1481         if (info.file.permissions & GP_FILE_PERM_READ)
1482         {
1483             itemInfo.readPermissions = 1;
1484         }
1485         else
1486         {
1487             itemInfo.readPermissions = 0;
1488         }
1489 
1490         if (info.file.permissions & GP_FILE_PERM_DELETE)
1491         {
1492             itemInfo.writePermissions = 1;
1493         }
1494         else
1495         {
1496             itemInfo.writePermissions = 0;
1497         }
1498     }
1499 
1500     gp_file_unref(cfile);
1501 
1502     return true;
1503 
1504 #else
1505 
1506     Q_UNUSED(folder);
1507     Q_UNUSED(itemName);
1508     Q_UNUSED(localFile);
1509     Q_UNUSED(itemInfo);
1510 
1511     return false;
1512 
1513 #endif // HAVE_GPHOTO2
1514 
1515 }
1516 
1517 bool GPCamera::cameraSummary(QString& summary)
1518 {
1519 #ifdef HAVE_GPHOTO2
1520 
1521     int        errorCode;
1522     CameraText sum;
1523 
1524     d->status->cancel = false;
1525     errorCode         = gp_camera_get_summary(d->camera, &sum, d->status->context);
1526 
1527     if (errorCode != GP_OK)
1528     {
1529         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera summary!";
1530         printGphotoErrorDescription(errorCode);
1531         return false;
1532     }
1533 
1534     // we do not expect title/model/etc. to contain newlines,
1535     // so we just escape HTML characters
1536     summary =  i18nc("@info List of device properties",
1537                      "Title: \"%1\"\n"
1538                      "Model: \"%2\"\n"
1539                      "Port: \"%3\"\n"
1540                      "Path: \"%4\"\n\n",
1541                      title(), model(), port(), path());
1542 
1543     summary += i18nc("@info List of supported device operations",
1544                      "Thumbnails: \"%1\"\n"
1545                      "Capture image: \"%2\"\n"
1546                      "Delete items: \"%3\"\n"
1547                      "Upload items: \"%4\"\n"
1548                      "Create directories: \"%5\"\n"
1549                      "Delete Directories: \"%6\"\n\n",
1550                      thumbnailSupport()    ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1551                      captureImageSupport() ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1552                      deleteSupport()       ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1553                      uploadSupport()       ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1554                      mkDirSupport()        ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1555                      delDirSupport()       ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"));
1556 
1557     // here we need to make sure whitespace and newlines
1558     // are converted to HTML properly
1559     summary.append(Qt::convertFromPlainText(QString::fromLocal8Bit(sum.text), Qt::WhiteSpacePre));
1560 
1561     return true;
1562 
1563 #else
1564 
1565     Q_UNUSED(summary);
1566 
1567     return false;
1568 
1569 #endif // HAVE_GPHOTO2
1570 
1571 }
1572 
1573 bool GPCamera::cameraManual(QString& manual)
1574 {
1575 #ifdef HAVE_GPHOTO2
1576 
1577     int        errorCode;
1578     CameraText man;
1579 
1580     d->status->cancel = false;
1581     errorCode         = gp_camera_get_manual(d->camera, &man, d->status->context);
1582 
1583     if (errorCode != GP_OK)
1584     {
1585         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera manual!";
1586         printGphotoErrorDescription(errorCode);
1587         return false;
1588     }
1589 
1590     // I guess manual is plain text and not HTML?
1591     // Can't test it. (Michael G. Hansen)
1592     manual = Qt::convertFromPlainText(QString::fromLocal8Bit(man.text), Qt::WhiteSpacePre);
1593 
1594     return true;
1595 
1596 #else
1597 
1598     Q_UNUSED(manual);
1599 
1600     return false;
1601 
1602 #endif // HAVE_GPHOTO2
1603 
1604 }
1605 
1606 bool GPCamera::cameraAbout(QString& about)
1607 {
1608 #ifdef HAVE_GPHOTO2
1609 
1610     int        errorCode;
1611     CameraText abt;
1612 
1613     d->status->cancel = false;
1614     errorCode         = gp_camera_get_about(d->camera, &abt, d->status->context);
1615 
1616     if (errorCode != GP_OK)
1617     {
1618         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get information about camera!";
1619         printGphotoErrorDescription(errorCode);
1620         return false;
1621     }
1622 
1623     // here we need to make sure whitespace and newlines
1624     // are converted to HTML properly
1625     about = Qt::convertFromPlainText(QString::fromLocal8Bit(abt.text), Qt::WhiteSpacePre);
1626     about.append(QString::fromUtf8("<br/><br/>To report problems about this driver, please contact "
1627                  "the gphoto2 team at:<br/><br/>http://gphoto.org/bugs"));      // krazy:exclude=insecurenet
1628 
1629     return true;
1630 
1631 #else
1632 
1633     Q_UNUSED(about);
1634 
1635     return false;
1636 
1637 #endif // HAVE_GPHOTO2
1638 
1639 }
1640 
1641 // -- Static methods ---------------------------------------------------------------------
1642 
1643 void GPCamera::printGphotoErrorDescription(int errorCode)
1644 {
1645 #ifdef HAVE_GPHOTO2
1646 
1647     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Libgphoto2 error: " << gp_result_as_string(errorCode)
1648                           << " (" << errorCode << ")";
1649 
1650 #else
1651 
1652     Q_UNUSED(errorCode);
1653 
1654 #endif // HAVE_GPHOTO2
1655 
1656 }
1657 
1658 void GPCamera::getSupportedCameras(int& count, QStringList& clist)
1659 {
1660 
1661 #ifdef HAVE_GPHOTO2
1662 
1663     clist.clear();
1664     count                         = 0;
1665 
1666     CameraAbilities      abil;
1667     CameraAbilitiesList* abilList = nullptr;
1668     GPContext*           context  = nullptr;
1669     context                       = gp_context_new();
1670 
1671     gp_abilities_list_new(&abilList);
1672     gp_abilities_list_load(abilList, context);
1673 
1674     count                         = gp_abilities_list_count(abilList);
1675 
1676     if (count < 0)
1677     {
1678         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get list of cameras!";
1679         printGphotoErrorDescription(count);
1680         gp_context_unref(context);
1681         return;
1682     }
1683     else
1684     {
1685         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Number of cameras supported:" << count;
1686 
1687         for (int i = 0 ; i < count ; ++i)
1688         {
1689             gp_abilities_list_get_abilities(abilList, i, &abil);
1690             const char* cname = abil.model;
1691             clist.append(QString::fromLocal8Bit(cname));
1692         }
1693     }
1694 
1695     gp_abilities_list_free(abilList);
1696     gp_context_unref(context);
1697 
1698 #else
1699 
1700     Q_UNUSED(count);
1701     Q_UNUSED(clist);
1702     qCDebug(DIGIKAM_IMPORTUI_LOG) << "GPCamera::getSupportedCamerasNumber(): function not available!";
1703 
1704 #endif // HAVE_GPHOTO2
1705 
1706 }
1707 
1708 void GPCamera::getSupportedPorts(QStringList& plist)
1709 {
1710 
1711 #ifdef HAVE_GPHOTO2
1712 
1713     GPPortInfoList* list = nullptr;
1714     GPPortInfo      info;
1715 
1716     plist.clear();
1717 
1718     gp_port_info_list_new(&list);
1719     gp_port_info_list_load(list);
1720 
1721     int numPorts = gp_port_info_list_count(list);
1722 
1723     if (numPorts < 0)
1724     {
1725         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get list of ports!";
1726         printGphotoErrorDescription(numPorts);
1727         gp_port_info_list_free(list);
1728         return;
1729     }
1730     else
1731     {
1732         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Number of ports supported:" << numPorts;
1733 
1734         for (int i = 0 ; i < numPorts ; ++i)
1735         {
1736             gp_port_info_list_get_info(list, i, &info);
1737 
1738 #   ifdef HAVE_GPHOTO25
1739 
1740             char* xpath = nullptr;
1741             gp_port_info_get_name (info, &xpath);
1742             plist.append(QString::fromUtf8(xpath));
1743 
1744 #   else
1745 
1746             plist.append(QString::fromUtf8(info.path));
1747 
1748 #   endif
1749 
1750         }
1751     }
1752 
1753     gp_port_info_list_free(list);
1754 
1755 #else
1756 
1757     Q_UNUSED(plist);
1758     qCDebug(DIGIKAM_IMPORTUI_LOG) << "GPCamera::getSupportedPorts(): function not available!";
1759 
1760 #endif // HAVE_GPHOTO2
1761 
1762 }
1763 
1764 void GPCamera::getCameraSupportedPorts(const QString& model, QStringList& plist)
1765 {
1766 
1767 #ifdef HAVE_GPHOTO2
1768 
1769     int i                         = 0;
1770     plist.clear();
1771 
1772     CameraAbilities      abilities;
1773     CameraAbilitiesList* abilList = nullptr;
1774     GPContext*           context  = nullptr;
1775     context                       = gp_context_new();
1776 
1777     gp_abilities_list_new(&abilList);
1778     gp_abilities_list_load(abilList, context);
1779     i = gp_abilities_list_lookup_model(abilList, model.toLocal8Bit().data());
1780     gp_abilities_list_get_abilities(abilList, i, &abilities);
1781     gp_abilities_list_free(abilList);
1782 
1783     if (abilities.port & GP_PORT_SERIAL)
1784     {
1785         plist.append(QLatin1String("serial"));
1786     }
1787 
1788     if (abilities.port & GP_PORT_PTPIP)
1789     {
1790         plist.append(QLatin1String("ptpip"));
1791     }
1792 
1793     if (abilities.port & GP_PORT_USB)
1794     {
1795         plist.append(QLatin1String("usb"));
1796     }
1797 
1798     gp_context_unref(context);
1799 
1800 #else
1801 
1802     Q_UNUSED(model);
1803     Q_UNUSED(plist);
1804     qCDebug(DIGIKAM_IMPORTUI_LOG) << "GPCamera::getCameraSupportedPorts(): function not available!";
1805 
1806 #endif // HAVE_GPHOTO2
1807 
1808 }
1809 
1810 int GPCamera::autoDetect(QString& model, QString& port)
1811 {
1812 
1813 #ifdef HAVE_GPHOTO2
1814 
1815     CameraList*          camList   = nullptr;
1816     CameraAbilitiesList* abilList  = nullptr;
1817     GPPortInfoList*      infoList  = nullptr;
1818     const char*          camModel_ = nullptr, *camPort_ = nullptr;
1819     GPContext*           context   = nullptr;
1820     context                        = gp_context_new();
1821 
1822     gp_list_new(&camList);
1823 
1824     gp_abilities_list_new(&abilList);
1825     gp_abilities_list_load(abilList, context);
1826     gp_port_info_list_new(&infoList);
1827     gp_port_info_list_load(infoList);
1828     gp_abilities_list_detect(abilList, infoList, camList, context);
1829     gp_abilities_list_free(abilList);
1830     gp_port_info_list_free(infoList);
1831 
1832     gp_context_unref(context);
1833 
1834     int count = gp_list_count(camList);
1835 
1836     if (count <= 0)
1837     {
1838         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1839         printGphotoErrorDescription(count);
1840         gp_list_free(camList);
1841         return -1;
1842     }
1843 
1844     camModel_ = nullptr;
1845     camPort_  = nullptr;
1846 
1847     for (int i = 0 ; i < count ; ++i)
1848     {
1849         if (gp_list_get_name(camList, i, &camModel_) != GP_OK)
1850         {
1851             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1852             gp_list_free(camList);
1853             return -1;
1854         }
1855 
1856         if (gp_list_get_value(camList, i, &camPort_) != GP_OK)
1857         {
1858             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1859             gp_list_free(camList);
1860             return -1;
1861         }
1862 
1863         if (camModel_ && camPort_)
1864         {
1865             model = QLatin1String(camModel_);
1866             port  = QLatin1String(camPort_);
1867             gp_list_free(camList);
1868             return 0;
1869         }
1870     }
1871 
1872     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1873     gp_list_free(camList);
1874 
1875 #else
1876 
1877     Q_UNUSED(model);
1878     Q_UNUSED(port);
1879     qCDebug(DIGIKAM_IMPORTUI_LOG) << "GPCamera::autoDetect(): function not available!";
1880 
1881 #endif // HAVE_GPHOTO2
1882 
1883     return -1;
1884 }
1885 
1886 bool GPCamera::findConnectedUsbCamera(int vendorId, int productId, QString& model, QString& port)
1887 {
1888 
1889 #ifdef HAVE_GPHOTO2
1890 
1891     CameraAbilitiesList* abilList = nullptr;
1892     GPPortInfoList*      list     = nullptr;
1893     GPContext*           context  = nullptr;
1894     CameraList*          camList  = nullptr;
1895     bool                 success  = false;
1896     // get name and port of detected camera
1897     const char* model_str         = nullptr;
1898     const char* port_str          = nullptr;
1899     context                       = gp_context_new();
1900 
1901     // get list of all ports
1902     gp_port_info_list_new(&list);
1903     gp_port_info_list_load(list);
1904 
1905     gp_abilities_list_new(&abilList);
1906     // get list of all supported cameras
1907     gp_abilities_list_load(abilList, context);
1908 
1909     // autodetect all cameras, then match the list to the passed in USB ids
1910     gp_list_new (&camList);
1911     gp_abilities_list_detect(abilList, list, camList, context);
1912     gp_context_unref(context);
1913 
1914     int count = gp_list_count(camList);
1915     int cnt   = 0;
1916 
1917     for (int i = 0 ; i < count ; ++i)
1918     {
1919         const char* xmodel = nullptr;
1920         gp_list_get_name(camList, i, &xmodel);
1921         int model          = gp_abilities_list_lookup_model (abilList, xmodel);
1922         CameraAbilities ab;
1923         gp_abilities_list_get_abilities(abilList, model, &ab);
1924 
1925         if (ab.port != GP_PORT_USB)
1926             continue;
1927 
1928         /* KDE provides us USB Vendor and Product, but we might just
1929          * have covered this via a class match. Check class matched
1930          * cameras also for matchingo USB vendor/product id
1931          */
1932         if (ab.usb_vendor == 0)
1933         {
1934             int ret;
1935             GPPortInfo info;
1936             const char* xport = nullptr;
1937             GPPort* gpport    = nullptr;
1938 
1939             /* get the port path so we only look at this bus position */
1940             gp_list_get_value(camList, i, &xport);
1941             ret = gp_port_info_list_lookup_path (list, xport);
1942 
1943             if (ret < GP_OK) /* should not happen */
1944                 continue;
1945 
1946             /* get the lowlevel port info  for the path
1947              */
1948             gp_port_info_list_get_info(list, ret, &info);
1949 
1950             /* open lowlevel driver interface briefly to search */
1951             gp_port_new(&gpport);
1952             gp_port_set_info(gpport, info);
1953 
1954             /* And now call into the lowlevel usb driver to see if the bus position
1955              * has that specific vendor/product id
1956              */
1957             if (gp_port_usb_find_device(gpport, vendorId, productId) == GP_OK)
1958             {
1959                 ab.usb_vendor  = vendorId;
1960                 ab.usb_product = productId;
1961             }
1962 
1963             gp_port_free (gpport);
1964         }
1965 
1966         if (ab.usb_vendor != vendorId)
1967             continue;
1968 
1969         if (ab.usb_product != productId)
1970             continue;
1971 
1972         /* keep it, and continue iterating, in case we find another one
1973          */
1974         gp_list_get_name (camList, i, &model_str);
1975         gp_list_get_value(camList, i, &port_str);
1976 
1977         cnt++;
1978     }
1979 
1980     gp_port_info_list_free(list);
1981     gp_abilities_list_free(abilList);
1982 
1983     if (cnt > 0)
1984     {
1985        if (cnt > 1)
1986        {
1987           qCWarning(DIGIKAM_IMPORTUI_LOG) << "More than one camera detected on port " << port
1988                                           << ". Due to restrictions in the GPhoto2 API, "
1989                                           << "only the first camera is used.";
1990        }
1991 
1992        model   = QLatin1String(model_str);
1993        port    = QLatin1String(port_str);
1994        success = true;
1995     }
1996     else
1997     {
1998        qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get information for the listed camera";
1999     }
2000 
2001     gp_list_free(camList);
2002     return success;
2003 
2004 #else
2005 
2006     Q_UNUSED(vendorId);
2007     Q_UNUSED(productId);
2008     Q_UNUSED(model);
2009     Q_UNUSED(port);
2010     qCDebug(DIGIKAM_IMPORTUI_LOG) << "GPCamera::findConnectedUsbCamera(): function not available!";
2011 
2012     return false;
2013 
2014 #endif // HAVE_GPHOTO2
2015 
2016 }
2017 
2018 } // namespace Digikam
2019 
2020 #include "moc_gpcamera.cpp"