1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2003-01-21
7  * Description : Gphoto2 camera interface
8  *
9  * Copyright (C) 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10  * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C) 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "gpcamera.h"
27 
28 // C ANSI includes
29 
30 extern "C"
31 {
32 #ifndef Q_CC_MSVC
33 #   include <unistd.h>
34 #   include <utime.h>
35 #else
36 #   include <sys/utime.h>
37 #endif
38 }
39 
40 // C++ includes
41 
42 #include <cstdio>
43 #include <iostream>
44 
45 // Qt includes
46 
47 #include <QString>
48 #include <QStringList>
49 #include <QImage>
50 #include <QPixmap>
51 #include <QFile>
52 #include <QScopedPointer>
53 #include <QDateTime>
54 #include <QTextDocument>
55 #include <QCryptographicHash>
56 
57 // KDE includes
58 
59 #include <klocalizedstring.h>
60 
61 // Local includes
62 
63 #include "digikam_debug.h"
64 #include "digikam_config.h"
65 #include "dfileoperations.h"
66 #include "dmetadata.h"
67 
68 //#define GPHOTO2_DEBUG 1
69 
70 #ifdef HAVE_GPHOTO2
71 
72 // LibGphoto2 includes
73 
74 extern "C"
75 {
76 #include <gphoto2.h>
77 }
78 
79 #endif // HAVE_GPHOTO2
80 
81 namespace Digikam
82 {
83 
84 class Q_DECL_HIDDEN GPStatus
85 {
86 public:
87 
GPStatus()88     GPStatus()
89     {
90 
91 #ifdef HAVE_GPHOTO2
92 
93         context = gp_context_new();
94         cancel  = false;
95         gp_context_set_cancel_func(context, cancel_func, nullptr);
96 
97 #   ifdef GPHOTO2_DEBUG
98 
99         gp_context_set_progress_funcs(context, start_func, update_func, stop_func, 0);
100         gp_context_set_error_func(context, error_func, 0);
101         gp_context_set_status_func(context, status_func, 0);
102 
103 #   endif // GPHOTO2_DEBUG
104 
105 #endif // HAVE_GPHOTO2
106 
107     }
108 
~GPStatus()109     ~GPStatus()
110     {
111 
112 #ifdef HAVE_GPHOTO2
113 
114         gp_context_unref(context);
115         cancel = false;
116 
117 #endif // HAVE_GPHOTO2
118 
119     }
120 
121     static bool cancel;
122 
123 #ifdef HAVE_GPHOTO2
124 
125     GPContext*  context;
126 
cancel_func(GPContext *,void *)127     static GPContextFeedback cancel_func(GPContext*, void*)
128     {
129         return (cancel ? GP_CONTEXT_FEEDBACK_CANCEL :
130                 GP_CONTEXT_FEEDBACK_OK);
131     }
132 
133 #   ifdef GPHOTO2_DEBUG
134 
error_func(GPContext *,const char * msg,void *)135     static void error_func(GPContext*, const char* msg, void*)
136     {
137         qCDebug(DIGIKAM_IMPORTUI_LOG) << "error:" << msg;
138     }
139 
status_func(GPContext *,const char * msg,void *)140     static void status_func(GPContext*, const char* msg, void*)
141     {
142         qCDebug(DIGIKAM_IMPORTUI_LOG) << "status:" << msg;
143     }
144 
start_func(GPContext *,float target,const char * text,void * data)145     static unsigned int start_func(GPContext*, float target, const char *text, void *data)
146     {
147         Q_UNUSED(data);
148         qCDebug(DIGIKAM_IMPORTUI_LOG) << "start:" << target << "- text:" << text;
149         return 0;
150 
151     }
152 
update_func(GPContext *,unsigned int id,float target,void * data)153     static void update_func(GPContext*, unsigned int id, float target, void *data)
154     {
155         Q_UNUSED(data);
156         qCDebug(DIGIKAM_IMPORTUI_LOG) << "update:" << id << "- target:" << target;
157     }
158 
stop_func(GPContext *,unsigned int id,void * data)159     static void stop_func(GPContext*, unsigned int id, void *data)
160     {
161         Q_UNUSED(data);
162         qCDebug(DIGIKAM_IMPORTUI_LOG) << "stop:" << id;
163     }
164 
165 #   endif // GPHOTO2_DEBUG
166 
167 #endif // HAVE_GPHOTO2
168 
169 };
170 
171 bool GPStatus::cancel = false;
172 
173 // ---------------------------------------------------------------------------
174 
175 class Q_DECL_HIDDEN GPCamera::Private
176 {
177 
178 public:
179 
Private()180     explicit Private()
181 
182 #ifdef HAVE_GPHOTO2
183 
184         : cameraInitialized(false),
185           camera           (nullptr),
186           status           (nullptr)
187 
188 #endif // HAVE_GPHOTO2
189 
190     {
191     }
192 
193 #ifdef HAVE_GPHOTO2
194 
195     bool            cameraInitialized;
196 
197     Camera*         camera;
198 
199     CameraAbilities cameraAbilities;
200 
201     GPStatus*       status;
202 
203 #endif // HAVE_GPHOTO2
204 
205 };
206 
207 // ---------------------------------------------------------------------------
208 
GPCamera(const QString & title,const QString & model,const QString & port,const QString & path)209 GPCamera::GPCamera(const QString& title, const QString& model,
210                    const QString& port, const QString& path)
211     : DKCamera(title, model, port, path),
212       d       (new Private)
213 {
214 }
215 
~GPCamera()216 GPCamera::~GPCamera()
217 {
218 
219 #ifdef HAVE_GPHOTO2
220 
221     if (d->status)
222     {
223         gp_context_unref(d->status->context);
224         d->status = nullptr;
225     }
226 
227     if (d->camera)
228     {
229         gp_camera_unref(d->camera);
230         d->camera = nullptr;
231     }
232 
233 #endif // HAVE_GPHOTO2
234 
235     delete d;
236 }
237 
cameraDriverType()238 DKCamera::CameraDriverType GPCamera::cameraDriverType()
239 {
240     return DKCamera::GPhotoDriver;
241 }
242 
cameraMD5ID()243 QByteArray GPCamera::cameraMD5ID()
244 {
245     QByteArray md5data;
246 
247 #ifdef HAVE_GPHOTO2
248 
249     QString    camData;
250 
251     // We don't use camera title from digiKam settings panel to compute MD5 fingerprint,
252     // because it can be changed by users between session.
253 
254     camData.append(model());
255 
256     // TODO is it really necessary to have a path here? I think model+filename+size+ctime should be enough to give unique fingerprint
257     // while still allowing you to move files around in the camera if needed
258 
259     camData.append(path());
260     QCryptographicHash md5(QCryptographicHash::Md5);
261     md5.addData(camData.toUtf8());
262     md5data = md5.result().toHex();
263 
264 #endif // HAVE_GPHOTO2
265 
266     return md5data;
267 }
268 
doConnect()269 bool GPCamera::doConnect()
270 {
271 
272 #ifdef HAVE_GPHOTO2
273 
274     int errorCode;
275 
276     // -- first step - setup the camera --------------------
277 
278     if (d->camera)
279     {
280         gp_camera_unref(d->camera);
281         d->camera = nullptr;
282     }
283 
284     CameraAbilitiesList* abilList = nullptr;
285     GPPortInfoList*      infoList = nullptr;
286     GPPortInfo           info;
287 
288     gp_camera_new(&d->camera);
289 
290     delete d->status;
291     d->status = nullptr;
292     d->status = new GPStatus();
293 
294     gp_abilities_list_new(&abilList);
295     gp_abilities_list_load(abilList, d->status->context);
296     gp_port_info_list_new(&infoList);
297     gp_port_info_list_load(infoList);
298 
299     int modelNum     = gp_abilities_list_lookup_model(abilList, m_model.toLatin1().constData());
300     int portNum      = gp_port_info_list_lookup_path(infoList, m_port.toLatin1().constData());
301 
302     gp_abilities_list_get_abilities(abilList, modelNum, &d->cameraAbilities);
303 
304     errorCode    = gp_camera_set_abilities(d->camera, d->cameraAbilities);
305 
306     if (errorCode != GP_OK)
307     {
308         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera Abilities!";
309         printGphotoErrorDescription(errorCode);
310         gp_camera_unref(d->camera);
311         d->camera = nullptr;
312         gp_abilities_list_free(abilList);
313         gp_port_info_list_free(infoList);
314         return false;
315     }
316 
317     if (m_model != QLatin1String("Directory Browse"))
318     {
319         gp_port_info_list_get_info(infoList, portNum, &info);
320         errorCode = gp_camera_set_port_info(d->camera, info);
321 
322         if (errorCode != GP_OK)
323         {
324             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera port!";
325             printGphotoErrorDescription(errorCode);
326             gp_camera_unref(d->camera);
327             d->camera = nullptr;
328             gp_abilities_list_free(abilList);
329             gp_port_info_list_free(infoList);
330             return false;
331         }
332     }
333 
334     gp_abilities_list_free(abilList);
335     gp_port_info_list_free(infoList);
336 
337     if (d->cameraAbilities.file_operations &
338         GP_FILE_OPERATION_PREVIEW)
339     {
340         m_thumbnailSupport = true;
341     }
342 
343     if (d->cameraAbilities.file_operations &
344         GP_FILE_OPERATION_DELETE)
345     {
346         m_deleteSupport = true;
347     }
348 
349     if (d->cameraAbilities.folder_operations &
350         GP_FOLDER_OPERATION_PUT_FILE)
351     {
352         m_uploadSupport = true;
353     }
354 
355     if (d->cameraAbilities.folder_operations &
356         GP_FOLDER_OPERATION_MAKE_DIR)
357     {
358         m_mkDirSupport = true;
359     }
360 
361     if (d->cameraAbilities.folder_operations &
362         GP_FOLDER_OPERATION_REMOVE_DIR)
363     {
364         m_delDirSupport = true;
365     }
366 
367     if (d->cameraAbilities.operations &
368         GP_OPERATION_CAPTURE_IMAGE)
369     {
370         m_captureImageSupport = true;
371     }
372 
373     if (d->cameraAbilities.operations &
374         GP_OPERATION_CAPTURE_PREVIEW)
375     {
376         m_captureImagePreviewSupport = true;
377     }
378 
379     // -- Try and initialize the camera to see if its connected -----------------
380 
381     errorCode = gp_camera_init(d->camera, d->status->context);
382 
383     if (errorCode != GP_OK)
384     {
385         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to initialize camera!";
386         printGphotoErrorDescription(errorCode);
387         gp_camera_unref(d->camera);
388         d->camera = nullptr;
389         return false;
390     }
391 
392     d->cameraInitialized = true;
393 
394     return true;
395 
396 #else
397 
398     return false;
399 
400 #endif // HAVE_GPHOTO2
401 
402 }
403 
cancel()404 void GPCamera::cancel()
405 {
406 
407 #ifdef HAVE_GPHOTO2
408 
409 /*
410     TODO what to do on cancel, if there's nothing to cancel?
411 
412     if (!d->status)
413     {
414         return;
415     }
416 */
417 
418     d->status->cancel = true;
419 
420 #endif // HAVE_GPHOTO2
421 
422 }
423 
getFreeSpace(unsigned long & kBSize,unsigned long & kBAvail)424 bool GPCamera::getFreeSpace(unsigned long& kBSize, unsigned long& kBAvail)
425 {
426 
427 #ifdef HAVE_GPHOTO2
428 
429     // NOTE: This method depends of libgphoto2 2.4.0
430 
431     int                       nrofsinfos;
432     CameraStorageInformation* sinfos = nullptr;
433 
434     d->status->cancel = false;
435     int errorCode     = gp_camera_get_storageinfo(d->camera, &sinfos, &nrofsinfos, d->status->context);
436 
437     if (errorCode != GP_OK)
438     {
439         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Getting storage information not supported for this camera!";
440         printGphotoErrorDescription(errorCode);
441         return false;
442     }
443 
444     for (int i = 0 ; i < nrofsinfos ; ++i)
445     {
446         if (sinfos[i].fields & GP_STORAGEINFO_FILESYSTEMTYPE)
447         {
448             switch (sinfos[i].fstype)
449             {
450                 case GP_STORAGEINFO_FST_UNDEFINED:
451                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: undefined";
452                     break;
453                 case GP_STORAGEINFO_FST_GENERICFLAT:
454                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: flat, all in one directory";
455                     break;
456                 case GP_STORAGEINFO_FST_GENERICHIERARCHICAL:
457                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: generic tree hierarchy";
458                     break;
459                 case GP_STORAGEINFO_FST_DCF:
460                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "DCIM style storage";
461                     break;
462             }
463         }
464 
465         if (sinfos[i].fields & GP_STORAGEINFO_LABEL)
466         {
467             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage label: " << QString::fromUtf8(sinfos[i].label);
468         }
469 
470         if (sinfos[i].fields & GP_STORAGEINFO_DESCRIPTION)
471         {
472             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage description: " << QString::fromUtf8(sinfos[i].description);
473         }
474 
475         if (sinfos[i].fields & GP_STORAGEINFO_BASE)
476         {
477             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage base-dir: " << QString::fromUtf8(sinfos[i].basedir);
478 
479             // TODO in order for this to work, the upload dialog needs to be fixed.
480 /*
481             if (nrofsinfos == 1)
482             {
483                 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Only one storage, so setting storage directory to" << sinfos[i].basedir;
484                 m_path = QString(sinfos[i].basedir);
485             }
486 */
487         }
488 
489         if (sinfos[i].fields & GP_STORAGEINFO_ACCESS)
490         {
491             switch (sinfos[i].access)
492             {
493                 case GP_STORAGEINFO_AC_READWRITE:
494                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: R/W";
495                     break;
496 
497                 case GP_STORAGEINFO_AC_READONLY:
498                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: RO";
499                     break;
500 
501                 case GP_STORAGEINFO_AC_READONLY_WITH_DELETE:
502                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: RO + Del";
503                     break;
504 
505                 default:
506                     break;
507             }
508         }
509 
510         if (sinfos[i].fields & GP_STORAGEINFO_STORAGETYPE)
511         {
512             switch (sinfos[i].type)
513             {
514                 case GP_STORAGEINFO_ST_FIXED_ROM:
515                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: fixed ROM";
516                     break;
517 
518                 case GP_STORAGEINFO_ST_REMOVABLE_ROM:
519                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: removable ROM";
520                     break;
521 
522                 case GP_STORAGEINFO_ST_FIXED_RAM:
523                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: fixed RAM";
524                     break;
525 
526                 case GP_STORAGEINFO_ST_REMOVABLE_RAM:
527                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: removable RAM";
528                     break;
529 
530                 case GP_STORAGEINFO_ST_UNKNOWN:
531                 default:
532                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: unknown";
533                     break;
534             }
535         }
536 
537         if (sinfos[i].fields & GP_STORAGEINFO_MAXCAPACITY)
538         {
539             kBSize += sinfos[i].capacitykbytes;
540             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage capacity: " << kBSize;
541         }
542 
543         if (sinfos[i].fields & GP_STORAGEINFO_FREESPACEKBYTES)
544         {
545             kBAvail += sinfos[i].freekbytes;
546             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage free-space: " << kBAvail;
547         }
548      }
549 
550 
551     return true;
552 
553 #else
554 
555     Q_UNUSED(kBSize);
556     Q_UNUSED(kBAvail);
557     return false;
558 
559 #endif // HAVE_GPHOTO2
560 
561 }
562 
getPreview(QImage & preview)563 bool GPCamera::getPreview(QImage& preview)
564 {
565 
566 #ifdef HAVE_GPHOTO2
567 
568     int               errorCode;
569     CameraFile*       cfile = nullptr;
570     const char*       data  = nullptr;
571     unsigned long int size;
572 
573     d->status->cancel = false;
574     gp_file_new(&cfile);
575 
576     errorCode = gp_camera_capture_preview(d->camera, cfile, d->status->context);
577 
578     if (errorCode != GP_OK)
579     {
580         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to initialize camera preview mode!";
581         printGphotoErrorDescription(errorCode);
582         gp_file_unref(cfile);
583         return false;
584     }
585 
586     errorCode = gp_file_get_data_and_size(cfile, &data, &size);
587 
588     if (errorCode != GP_OK)
589     {
590         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get preview from camera!";
591         printGphotoErrorDescription(errorCode);
592         gp_file_unref(cfile);
593         return false;
594     }
595 
596     preview.loadFromData((const uchar*) data, (uint) size);
597 
598     gp_file_unref(cfile);
599     return true;
600 
601 #else
602 
603     Q_UNUSED(preview);
604     return false;
605 
606 #endif // HAVE_GPHOTO2
607 
608 }
609 
capture(CamItemInfo & itemInfo)610 bool GPCamera::capture(CamItemInfo& itemInfo)
611 {
612 
613 #ifdef HAVE_GPHOTO2
614 
615     int            errorCode;
616     CameraFilePath path;
617 
618     d->status->cancel = false;
619     errorCode         = gp_camera_capture(d->camera, GP_CAPTURE_IMAGE, &path, d->status->context);
620 
621     if (errorCode != GP_OK)
622     {
623         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to take camera capture!";
624         printGphotoErrorDescription(errorCode);
625         return false;
626     }
627 
628     // Get new camera item information.
629 
630     itemInfo.folder = QString::fromUtf8(path.folder);
631     itemInfo.name   = QString::fromUtf8(path.name);
632 
633     CameraFileInfo info;
634     errorCode       = gp_camera_file_get_info(d->camera, QFile::encodeName(itemInfo.folder).constData(),
635                                               QFile::encodeName(itemInfo.name).constData(), &info,
636                                               d->status->context);
637 
638     if (errorCode != GP_OK)
639     {
640         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item information!";
641         printGphotoErrorDescription(errorCode);
642         return false;
643     }
644 
645     itemInfo.ctime            = QDateTime();
646     itemInfo.mime             = QString();
647     itemInfo.size             = -1;
648     itemInfo.width            = -1;
649     itemInfo.height           = -1;
650     itemInfo.downloaded       = CamItemInfo::DownloadUnknown;
651     itemInfo.readPermissions  = -1;
652     itemInfo.writePermissions = -1;
653 
654     /* The mime type returned by Gphoto2 is dummy with all RAW files.
655     if (info.file.fields & GP_FILE_INFO_TYPE)
656         itemInfo.mime = info.file.type;*/
657 
658     itemInfo.mime = mimeType(itemInfo.name.section(QLatin1Char('.'), -1).toLower());
659 
660     if (info.file.fields & GP_FILE_INFO_MTIME)
661     {
662         itemInfo.ctime = QDateTime::fromSecsSinceEpoch(info.file.mtime);
663     }
664 
665     if (info.file.fields & GP_FILE_INFO_SIZE)
666     {
667         itemInfo.size = info.file.size;
668     }
669 
670     if (info.file.fields & GP_FILE_INFO_WIDTH)
671     {
672         itemInfo.width = info.file.width;
673     }
674 
675     if (info.file.fields & GP_FILE_INFO_HEIGHT)
676     {
677         itemInfo.height = info.file.height;
678     }
679 
680     if (info.file.fields & GP_FILE_INFO_STATUS)
681     {
682         if (info.file.status == GP_FILE_STATUS_DOWNLOADED)
683         {
684             itemInfo.downloaded = CamItemInfo::DownloadedYes;
685         }
686         else
687         {
688             itemInfo.downloaded = CamItemInfo::DownloadedNo;
689         }
690     }
691 
692     if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
693     {
694         if (info.file.permissions & GP_FILE_PERM_READ)
695         {
696             itemInfo.readPermissions = 1;
697         }
698         else
699         {
700             itemInfo.readPermissions = 0;
701         }
702 
703         if (info.file.permissions & GP_FILE_PERM_DELETE)
704         {
705             itemInfo.writePermissions = 1;
706         }
707         else
708         {
709             itemInfo.writePermissions = 0;
710         }
711     }
712 
713     return true;
714 
715 #else
716 
717     Q_UNUSED(itemInfo);
718     return false;
719 
720 #endif // HAVE_GPHOTO2
721 
722 }
723 
getFolders(const QString & folder)724 bool GPCamera::getFolders(const QString& folder)
725 {
726 
727 #ifdef HAVE_GPHOTO2
728 
729     int         errorCode;
730     CameraList* clist = nullptr;
731     gp_list_new(&clist);
732 
733     d->status->cancel = false;
734     errorCode         = gp_camera_folder_list_folders(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context);
735 
736     if (errorCode != GP_OK)
737     {
738         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folders list from camera!";
739         printGphotoErrorDescription(errorCode);
740         gp_list_unref(clist);
741         return false;
742     }
743 
744     QStringList subFolderList;
745     int count = gp_list_count(clist);
746 
747     if (count < 1)
748     {
749         return true;
750     }
751 
752     for (int i = 0 ; i < count ; ++i)
753     {
754         const char* subFolder = nullptr;
755         errorCode             = gp_list_get_name(clist, i, &subFolder);
756 
757         if (errorCode != GP_OK)
758         {
759             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder name from camera!";
760             printGphotoErrorDescription(errorCode);
761             gp_list_unref(clist);
762             return false;
763         }
764 
765         subFolderList.append(folder + QFile::decodeName(subFolder) + QLatin1Char('/'));
766     }
767 
768     gp_list_unref(clist);
769 
770     emit signalFolderList(subFolderList);
771 
772     return true;
773 
774 #else
775 
776     Q_UNUSED(folder);
777     return false;
778 
779 #endif // HAVE_GPHOTO2
780 
781 }
782 
783 // TODO unused, remove?
getItemsList(const QString & folder,QStringList & itemsList)784 bool GPCamera::getItemsList(const QString& folder, QStringList& itemsList)
785 {
786 
787 #ifdef HAVE_GPHOTO2
788 
789     int         errorCode;
790     CameraList* clist = nullptr;
791     const char* cname = nullptr;
792 
793     gp_list_new(&clist);
794 
795     d->status->cancel = false;
796     errorCode         = gp_camera_folder_list_files(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context);
797 
798     if (errorCode != GP_OK)
799     {
800         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder files list from camera!";
801         printGphotoErrorDescription(errorCode);
802         gp_list_unref(clist);
803         return false;
804     }
805 
806     int count = gp_list_count(clist);
807 
808     for (int i = 0 ; i < count ; ++i)
809     {
810         errorCode = gp_list_get_name(clist, i, &cname);
811 
812         if (errorCode != GP_OK)
813         {
814             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get file name from camera!";
815             printGphotoErrorDescription(errorCode);
816             gp_list_unref(clist);
817             return false;
818         }
819 
820         itemsList.append(QFile::decodeName(cname));
821     }
822 
823     gp_list_unref(clist);
824 
825     return true;
826 
827 #else
828 
829     Q_UNUSED(folder);
830     Q_UNUSED(itemsList);
831 
832     return false;
833 
834 #endif // HAVE_GPHOTO2
835 
836 }
837 
getItemsInfoList(const QString & folder,bool useMetadata,CamItemInfoList & items)838 bool GPCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& items)
839 {
840 
841 #ifdef HAVE_GPHOTO2
842 
843     int         errorCode;
844     CameraList* clist = nullptr;
845     const char* cname = nullptr;
846 
847     gp_list_new(&clist);
848 
849     d->status->cancel = false;
850     errorCode         = gp_camera_folder_list_files(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context);
851 
852     if (errorCode != GP_OK)
853     {
854         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder files list from camera!";
855         printGphotoErrorDescription(errorCode);
856         gp_list_unref(clist);
857         return false;
858     }
859 
860     int count = gp_list_count(clist);
861 
862     for (int i = 0 ; i < count ; ++i)
863     {
864         errorCode = gp_list_get_name(clist, i, &cname);
865 
866         if (errorCode != GP_OK)
867         {
868             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get file name from camera!";
869             printGphotoErrorDescription(errorCode);
870             gp_list_unref(clist);
871             return false;
872         }
873 
874         // TODO for further speed-up, getItemInfoInternal call could be called separately when needed
875         CamItemInfo info;
876         getItemInfoInternal(folder, QFile::decodeName(cname), info, useMetadata);
877         items.append(info);
878     }
879 
880     gp_list_unref(clist);
881 
882     return true;
883 
884 #else
885 
886     Q_UNUSED(folder);
887     Q_UNUSED(useMetadata);
888     Q_UNUSED(items);
889 
890     return false;
891 
892 #endif // HAVE_GPHOTO2
893 
894 }
895 
getItemInfo(const QString & folder,const QString & itemName,CamItemInfo & info,bool useMetadata)896 void GPCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata)
897 {
898 
899 #ifdef HAVE_GPHOTO2
900 
901     getItemInfoInternal(folder, itemName, info, useMetadata);
902 
903 #else
904     Q_UNUSED(folder);
905     Q_UNUSED(itemName);
906     Q_UNUSED(info);
907     Q_UNUSED(useMetadata);
908 
909 #endif // HAVE_GPHOTO2
910 
911 }
912 
getItemInfoInternal(const QString & folder,const QString & itemName,CamItemInfo & info,bool useMetadata)913 void GPCamera::getItemInfoInternal(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata)
914 {
915 
916 #ifdef HAVE_GPHOTO2
917 
918     info.folder          = folder;
919     info.name            = itemName;
920     d->status->cancel    = false;
921     info.previewPossible = false;
922 
923     CameraFileInfo cfinfo;
924     gp_camera_file_get_info(d->camera, QFile::encodeName(info.folder).constData(),
925                             QFile::encodeName(info.name).constData(), &cfinfo, d->status->context);
926 
927     // if preview has size field, it's a valid preview most likely, otherwise we'll skip it later on
928 
929     if (cfinfo.preview.fields & GP_FILE_INFO_SIZE)
930     {
931         info.previewPossible = true;
932     }
933 
934     if (cfinfo.file.fields & GP_FILE_INFO_STATUS)
935     {
936         if (cfinfo.file.status == GP_FILE_STATUS_DOWNLOADED)
937         {
938             info.downloaded = CamItemInfo::DownloadedYes;
939         }
940     }
941 
942     if (cfinfo.file.fields & GP_FILE_INFO_SIZE)
943     {
944         info.size = cfinfo.file.size;
945     }
946 
947     if (cfinfo.file.fields & GP_FILE_INFO_PERMISSIONS)
948     {
949         if (cfinfo.file.permissions & GP_FILE_PERM_READ)
950         {
951             info.readPermissions = 1;
952         }
953         else
954         {
955             info.readPermissions = 0;
956         }
957 
958         if (cfinfo.file.permissions & GP_FILE_PERM_DELETE)
959         {
960             info.writePermissions = 1;
961         }
962         else
963         {
964             info.writePermissions = 0;
965         }
966     }
967 
968 /*
969         The mime type returned by Gphoto2 is dummy with all RAW files.
970 
971         if (cfinfo.file.fields & GP_FILE_INFO_TYPE)
972         {
973             info.mime = cfinfo.file.type;
974         }
975 */
976 
977     info.mime = mimeType(info.name.section(QLatin1Char('.'), -1).toLower());
978 
979     if (!info.mime.isEmpty())
980     {
981         if (useMetadata)
982         {
983             // Try to use file metadata
984             QScopedPointer<DMetadata> meta(new DMetadata);
985             getMetadata(folder, itemName, *meta);
986             fillItemInfoFromMetadata(info, *meta);
987 
988             // Fall back to camera file system info
989 
990             if (info.ctime.isNull())
991             {
992                 if (cfinfo.file.fields & GP_FILE_INFO_MTIME)
993                 {
994                     info.ctime = QDateTime::fromSecsSinceEpoch(cfinfo.file.mtime);
995                 }
996                 else
997                 {
998                     info.ctime = QDateTime::currentDateTime();
999                 }
1000             }
1001         }
1002         else
1003         {
1004             // Only use properties provided by camera.
1005             if (cfinfo.file.fields & GP_FILE_INFO_MTIME)
1006             {
1007                 info.ctime = QDateTime::fromSecsSinceEpoch(cfinfo.file.mtime);
1008             }
1009             else
1010             {
1011                 info.ctime = QDateTime::currentDateTime();
1012             }
1013 
1014             if (cfinfo.file.fields & GP_FILE_INFO_WIDTH)
1015             {
1016                 info.width = cfinfo.file.width;
1017             }
1018 
1019             if (cfinfo.file.fields & GP_FILE_INFO_HEIGHT)
1020             {
1021                 info.height = cfinfo.file.height;
1022             }
1023         }
1024     }
1025 
1026 #else
1027 
1028     Q_UNUSED(folder);
1029     Q_UNUSED(itemName);
1030     Q_UNUSED(info);
1031     Q_UNUSED(useMetadata);
1032 
1033 #endif // HAVE_GPHOTO2
1034 
1035 }
1036 
getThumbnail(const QString & folder,const QString & itemName,QImage & thumbnail)1037 bool GPCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail)
1038 {
1039 #ifdef HAVE_GPHOTO2
1040 
1041     int                errorCode;
1042     CameraFile*        cfile = nullptr;
1043     const char*        data  = nullptr;
1044     unsigned long int  size;
1045 
1046     gp_file_new(&cfile);
1047 
1048     d->status->cancel = false;
1049     errorCode         = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(),
1050                                            QFile::encodeName(itemName).constData(),
1051                                            GP_FILE_TYPE_PREVIEW,
1052                                            cfile, d->status->context);
1053 
1054     if (errorCode != GP_OK)
1055     {
1056         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!" << folder << itemName;
1057         printGphotoErrorDescription(errorCode);
1058         gp_file_unref(cfile);
1059         return false;
1060     }
1061 
1062     errorCode = gp_file_get_data_and_size(cfile, &data, &size);
1063 
1064     if (errorCode != GP_OK)
1065     {
1066         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get thumbnail from camera item!" << folder << itemName;
1067         printGphotoErrorDescription(errorCode);
1068         gp_file_unref(cfile);
1069         return false;
1070     }
1071 
1072     thumbnail.loadFromData((const uchar*) data, (uint) size);
1073 
1074     gp_file_unref(cfile);
1075     return !thumbnail.isNull();
1076 
1077 #else
1078 
1079     Q_UNUSED(folder);
1080     Q_UNUSED(itemName);
1081     Q_UNUSED(thumbnail);
1082 
1083     return false;
1084 
1085 #endif // HAVE_GPHOTO2
1086 
1087 }
1088 
getMetadata(const QString & folder,const QString & itemName,DMetadata & meta)1089 bool GPCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta)
1090 {
1091 #ifdef HAVE_GPHOTO2
1092 
1093     int               errorCode;
1094     CameraFile*       cfile = nullptr;
1095     const char*       data  = nullptr;
1096     unsigned long int size;
1097 
1098     gp_file_new(&cfile);
1099 
1100     d->status->cancel = false;
1101     errorCode         = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(),
1102                                            QFile::encodeName(itemName).constData(),
1103                                            GP_FILE_TYPE_EXIF,
1104                                            cfile, d->status->context);
1105 
1106     if (errorCode != GP_OK)
1107     {
1108         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!";
1109         printGphotoErrorDescription(errorCode);
1110         gp_file_unref(cfile);
1111         return false;
1112     }
1113 
1114     errorCode = gp_file_get_data_and_size(cfile, &data, &size);
1115 
1116     if (errorCode != GP_OK)
1117     {
1118         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get Exif data from camera item!";
1119         printGphotoErrorDescription(errorCode);
1120         gp_file_unref(cfile);
1121         return false;
1122     }
1123 
1124     QByteArray exifData(data, size);
1125 
1126     gp_file_unref(cfile);
1127 
1128     // Sometimes, GPhoto2 drivers return complete APP1 JFIF section. Exiv2 cannot
1129     // decode (yet) exif metadata from APP1. We will find Exif header to get data at this place
1130     // to please with Exiv2...
1131 
1132     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Size of Exif metadata from camera = " << exifData.size();
1133 
1134     if (!exifData.isEmpty())
1135     {
1136         char exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
1137         int i             = exifData.indexOf(*exifHeader);
1138 
1139         if (i != -1)
1140         {
1141             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Exif header found at position " << i;
1142             i = i + sizeof(exifHeader);
1143             QByteArray data;
1144             data.resize(exifData.size() - i);
1145             memcpy(data.data(), exifData.data() + i, data.size());
1146             meta.setExif(data);
1147             return true;
1148         }
1149     }
1150 
1151     return false;
1152 
1153 #else
1154 
1155     Q_UNUSED(folder);
1156     Q_UNUSED(itemName);
1157     Q_UNUSED(meta);
1158 
1159     return false;
1160 
1161 #endif // HAVE_GPHOTO2
1162 }
1163 
downloadItem(const QString & folder,const QString & itemName,const QString & saveFile)1164 bool GPCamera::downloadItem(const QString& folder, const QString& itemName,
1165                             const QString& saveFile)
1166 {
1167 #ifdef HAVE_GPHOTO2
1168 
1169     int         errorCode;
1170     CameraFile* cfile = nullptr;
1171 
1172     d->status->cancel = false;
1173     QFile file(saveFile);
1174 
1175     if (!file.open(QIODevice::ReadWrite))
1176     {
1177         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to open file" << file.fileName() << file.errorString();
1178         return false;
1179     }
1180 
1181     // dup fd, passing fd control to gphoto2 later
1182     int handle = dup(file.handle());
1183 
1184     if (handle == -1)
1185     {
1186         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to dup file descriptor";
1187         return false;
1188     }
1189 
1190     errorCode = gp_file_new_from_fd(&cfile, handle);
1191 
1192     if (errorCode != GP_OK)
1193     {
1194         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!";
1195         printGphotoErrorDescription(errorCode);
1196         return false;
1197     }
1198 
1199     errorCode = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(),
1200                                    QFile::encodeName(itemName).constData(),
1201                                    GP_FILE_TYPE_NORMAL, cfile,
1202                                    d->status->context);
1203 
1204     if (errorCode != GP_OK)
1205     {
1206         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!";
1207         printGphotoErrorDescription(errorCode);
1208         gp_file_unref(cfile);
1209         return false;
1210     }
1211 
1212     time_t mtime;
1213     errorCode = gp_file_get_mtime(cfile, &mtime);
1214 
1215     file.close();
1216 
1217     if ((errorCode == GP_OK) && mtime)
1218     {
1219         DFileOperations::setModificationTime(saveFile, QDateTime::fromSecsSinceEpoch(mtime));
1220     }
1221 
1222     gp_file_unref(cfile);
1223 
1224     return true;
1225 
1226 #else
1227 
1228     Q_UNUSED(folder);
1229     Q_UNUSED(itemName);
1230     Q_UNUSED(saveFile);
1231 
1232     return false;
1233 
1234 #endif // HAVE_GPHOTO2
1235 
1236 }
1237 
setLockItem(const QString & folder,const QString & itemName,bool lock)1238 bool GPCamera::setLockItem(const QString& folder, const QString& itemName, bool lock)
1239 {
1240 #ifdef HAVE_GPHOTO2
1241 
1242     int errorCode;
1243 
1244     d->status->cancel = false;
1245     CameraFileInfo info;
1246     errorCode         = gp_camera_file_get_info(d->camera, QFile::encodeName(folder).constData(),
1247                                                 QFile::encodeName(itemName).constData(), &info, d->status->context);
1248 
1249     if (errorCode != GP_OK)
1250     {
1251         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item properties!";
1252         printGphotoErrorDescription(errorCode);
1253         return false;
1254     }
1255 
1256     if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
1257     {
1258         if (lock)
1259         {
1260             // Lock the file to set read only flag
1261             info.file.permissions = (CameraFilePermissions)GP_FILE_PERM_READ;
1262         }
1263         else
1264         {
1265             // Unlock the file to set read/write flag
1266             info.file.permissions = (CameraFilePermissions)(GP_FILE_PERM_READ | GP_FILE_PERM_DELETE);
1267         }
1268     }
1269 
1270     // Some gphoto2 drivers need to have only the right flag at on to process properties update in camera.
1271     info.file.fields    = GP_FILE_INFO_PERMISSIONS;
1272     info.preview.fields = GP_FILE_INFO_NONE;
1273     info.audio.fields   = GP_FILE_INFO_NONE;
1274 
1275     errorCode = gp_camera_file_set_info(d->camera, QFile::encodeName(folder).constData(),
1276                                         QFile::encodeName(itemName).constData(), info, d->status->context);
1277 
1278     if (errorCode != GP_OK)
1279     {
1280         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera item lock properties!";
1281         printGphotoErrorDescription(errorCode);
1282         return false;
1283     }
1284 
1285     return true;
1286 
1287 #else
1288 
1289     Q_UNUSED(folder);
1290     Q_UNUSED(itemName);
1291     Q_UNUSED(lock);
1292 
1293     return false;
1294 
1295 #endif // HAVE_GPHOTO2
1296 
1297 }
1298 
deleteItem(const QString & folder,const QString & itemName)1299 bool GPCamera::deleteItem(const QString& folder, const QString& itemName)
1300 {
1301 #ifdef HAVE_GPHOTO2
1302 
1303     d->status->cancel = false;
1304     int errorCode     = gp_camera_file_delete(d->camera, QFile::encodeName(folder).constData(),
1305                                               QFile::encodeName(itemName).constData(),
1306                                               d->status->context);
1307 
1308     if (errorCode != GP_OK)
1309     {
1310         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to delete camera item!";
1311         printGphotoErrorDescription(errorCode);
1312         return false;
1313     }
1314 
1315     return true;
1316 
1317 #else
1318 
1319     Q_UNUSED(folder);
1320     Q_UNUSED(itemName);
1321 
1322     return false;
1323 
1324 #endif // HAVE_GPHOTO2
1325 
1326 }
1327 
1328 // TODO fix to go through all folders
1329 // TODO this was never even used.
deleteAllItems(const QString & folder)1330 bool GPCamera::deleteAllItems(const QString& folder)
1331 {
1332 #ifdef HAVE_GPHOTO2
1333 
1334     int         errorCode;
1335     QStringList folderList;
1336 
1337     d->status->cancel = false;
1338     errorCode         = gp_camera_folder_delete_all(d->camera, QFile::encodeName(folder).constData(),
1339                                                     d->status->context);
1340 
1341     if (errorCode != GP_OK)
1342     {
1343         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to delete camera folder!";
1344         printGphotoErrorDescription(errorCode);
1345         return false;
1346     }
1347 
1348     return true;
1349 
1350 #else
1351 
1352     Q_UNUSED(folder);
1353 
1354     return false;
1355 
1356 #endif // HAVE_GPHOTO2
1357 
1358 }
1359 
uploadItem(const QString & folder,const QString & itemName,const QString & localFile,CamItemInfo & itemInfo)1360 bool GPCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& itemInfo)
1361 {
1362 #ifdef HAVE_GPHOTO2
1363 
1364     int         errorCode;
1365     CameraFile* cfile = nullptr;
1366     errorCode         = gp_file_new(&cfile);
1367     d->status->cancel = false;
1368 
1369     if (errorCode != GP_OK)
1370     {
1371         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to init new camera file instance!";
1372         printGphotoErrorDescription(errorCode);
1373         return false;
1374     }
1375 
1376     errorCode = gp_file_open(cfile, QFile::encodeName(localFile).constData());
1377 
1378     if (errorCode != GP_OK)
1379     {
1380         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to open file!";
1381         printGphotoErrorDescription(errorCode);
1382         gp_file_unref(cfile);
1383         return false;
1384     }
1385 
1386     errorCode = gp_file_set_name(cfile, QFile::encodeName(itemName).constData());
1387 
1388     if (errorCode != GP_OK)
1389     {
1390         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to rename item from camera!";
1391         printGphotoErrorDescription(errorCode);
1392         gp_file_unref(cfile);
1393         return false;
1394     }
1395 
1396 #   ifdef HAVE_GPHOTO25
1397 
1398     errorCode = gp_camera_folder_put_file(d->camera,
1399                                           QFile::encodeName(folder).constData(),
1400                                           QFile::encodeName(itemName).constData(),
1401                                           GP_FILE_TYPE_NORMAL,
1402                                           cfile,
1403                                           d->status->context);
1404 #   else
1405 
1406     errorCode = gp_camera_folder_put_file(d->camera,
1407                                           QFile::encodeName(folder).constData(),
1408                                           cfile,
1409                                           d->status->context);
1410 #   endif
1411 
1412     if (errorCode != GP_OK)
1413     {
1414         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to upload item to camera!";
1415         printGphotoErrorDescription(errorCode);
1416         gp_file_unref(cfile);
1417         return false;
1418     }
1419 
1420     // Get new camera item information.
1421 
1422     itemInfo.name   = itemName;
1423     itemInfo.folder = folder;
1424 
1425     CameraFileInfo info;
1426     errorCode       = gp_camera_file_get_info(d->camera, QFile::encodeName(folder).constData(),
1427                                               QFile::encodeName(itemName).constData(), &info, d->status->context);
1428 
1429     if (errorCode != GP_OK)
1430     {
1431         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item information!";
1432         printGphotoErrorDescription(errorCode);
1433         gp_file_unref(cfile);
1434         return false;
1435     }
1436 
1437     itemInfo.ctime            = QDateTime();
1438     itemInfo.mime             = QString();
1439     itemInfo.size             = -1;
1440     itemInfo.width            = -1;
1441     itemInfo.height           = -1;
1442     itemInfo.downloaded       = CamItemInfo::DownloadUnknown;
1443     itemInfo.readPermissions  = -1;
1444     itemInfo.writePermissions = -1;
1445 
1446     /* The mime type returned by Gphoto2 is dummy with all RAW files.
1447     if (info.file.fields & GP_FILE_INFO_TYPE)
1448         itemInfo.mime = info.file.type;
1449     */
1450 
1451     itemInfo.mime = mimeType(itemInfo.name.section(QLatin1Char('.'), -1).toLower());
1452 
1453     if (info.file.fields & GP_FILE_INFO_MTIME)
1454     {
1455         itemInfo.ctime = QDateTime::fromSecsSinceEpoch(info.file.mtime);
1456     }
1457 
1458     if (info.file.fields & GP_FILE_INFO_SIZE)
1459     {
1460         itemInfo.size = info.file.size;
1461     }
1462 
1463     if (info.file.fields & GP_FILE_INFO_WIDTH)
1464     {
1465         itemInfo.width = info.file.width;
1466     }
1467 
1468     if (info.file.fields & GP_FILE_INFO_HEIGHT)
1469     {
1470         itemInfo.height = info.file.height;
1471     }
1472 
1473     if (info.file.fields & GP_FILE_INFO_STATUS)
1474     {
1475         if (info.file.status == GP_FILE_STATUS_DOWNLOADED)
1476         {
1477             itemInfo.downloaded = CamItemInfo::DownloadedYes;
1478         }
1479         else
1480         {
1481             itemInfo.downloaded = CamItemInfo::DownloadedNo;
1482         }
1483     }
1484 
1485     if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
1486     {
1487         if (info.file.permissions & GP_FILE_PERM_READ)
1488         {
1489             itemInfo.readPermissions = 1;
1490         }
1491         else
1492         {
1493             itemInfo.readPermissions = 0;
1494         }
1495 
1496         if (info.file.permissions & GP_FILE_PERM_DELETE)
1497         {
1498             itemInfo.writePermissions = 1;
1499         }
1500         else
1501         {
1502             itemInfo.writePermissions = 0;
1503         }
1504     }
1505 
1506     gp_file_unref(cfile);
1507 
1508     return true;
1509 
1510 #else
1511 
1512     Q_UNUSED(folder);
1513     Q_UNUSED(itemName);
1514     Q_UNUSED(localFile);
1515     Q_UNUSED(itemInfo);
1516 
1517     return false;
1518 
1519 #endif // HAVE_GPHOTO2
1520 
1521 }
1522 
cameraSummary(QString & summary)1523 bool GPCamera::cameraSummary(QString& summary)
1524 {
1525 #ifdef HAVE_GPHOTO2
1526 
1527     int        errorCode;
1528     CameraText sum;
1529 
1530     d->status->cancel = false;
1531     errorCode         = gp_camera_get_summary(d->camera, &sum, d->status->context);
1532 
1533     if (errorCode != GP_OK)
1534     {
1535         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera summary!";
1536         printGphotoErrorDescription(errorCode);
1537         return false;
1538     }
1539 
1540     // we do not expect titel/model/etc. to contain newlines,
1541     // so we just escape HTML characters
1542     summary =  i18nc("@info List of device properties",
1543                      "Title: \"%1\"\n"
1544                      "Model: \"%2\"\n"
1545                      "Port: \"%3\"\n"
1546                      "Path: \"%4\"\n\n",
1547                      title(), model(), port(), path());
1548 
1549     summary += i18nc("@info List of supported device operations",
1550                      "Thumbnails: \"%1\"\n"
1551                      "Capture image: \"%2\"\n"
1552                      "Delete items: \"%3\"\n"
1553                      "Upload items: \"%4\"\n"
1554                      "Create directories: \"%5\"\n"
1555                      "Delete Directories: \"%6\"\n\n",
1556                      thumbnailSupport()    ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1557                      captureImageSupport() ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1558                      deleteSupport()       ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1559                      uploadSupport()       ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1560                      mkDirSupport()        ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"),
1561                      delDirSupport()       ? i18nc("@info: gphoto backend feature", "yes") : i18nc("@info: gphoto backend feature", "no"));
1562 
1563     // here we need to make sure whitespace and newlines
1564     // are converted to HTML properly
1565     summary.append(Qt::convertFromPlainText(QString::fromLocal8Bit(sum.text), Qt::WhiteSpacePre));
1566 
1567     return true;
1568 
1569 #else
1570 
1571     Q_UNUSED(summary);
1572 
1573     return false;
1574 
1575 #endif // HAVE_GPHOTO2
1576 
1577 }
1578 
cameraManual(QString & manual)1579 bool GPCamera::cameraManual(QString& manual)
1580 {
1581 #ifdef HAVE_GPHOTO2
1582 
1583     int        errorCode;
1584     CameraText man;
1585 
1586     d->status->cancel = false;
1587     errorCode         = gp_camera_get_manual(d->camera, &man, d->status->context);
1588 
1589     if (errorCode != GP_OK)
1590     {
1591         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera manual!";
1592         printGphotoErrorDescription(errorCode);
1593         return false;
1594     }
1595 
1596     // I guess manual is plain text and not HTML?
1597     // Can't test it. (Michael G. Hansen)
1598     manual = Qt::convertFromPlainText(QString::fromLocal8Bit(man.text), Qt::WhiteSpacePre);
1599 
1600     return true;
1601 
1602 #else
1603 
1604     Q_UNUSED(manual);
1605 
1606     return false;
1607 
1608 #endif // HAVE_GPHOTO2
1609 
1610 }
1611 
cameraAbout(QString & about)1612 bool GPCamera::cameraAbout(QString& about)
1613 {
1614 #ifdef HAVE_GPHOTO2
1615 
1616     int        errorCode;
1617     CameraText abt;
1618 
1619     d->status->cancel = false;
1620     errorCode         = gp_camera_get_about(d->camera, &abt, d->status->context);
1621 
1622     if (errorCode != GP_OK)
1623     {
1624         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get information about camera!";
1625         printGphotoErrorDescription(errorCode);
1626         return false;
1627     }
1628 
1629     // here we need to make sure whitespace and newlines
1630     // are converted to HTML properly
1631     about = Qt::convertFromPlainText(QString::fromLocal8Bit(abt.text), Qt::WhiteSpacePre);
1632     about.append(QString::fromUtf8("<br/><br/>To report problems about this driver, please contact "
1633                  "the gphoto2 team at:<br/><br/>http://gphoto.org/bugs"));      // krazy:exclude=insecurenet
1634 
1635     return true;
1636 
1637 #else
1638 
1639     Q_UNUSED(about);
1640 
1641     return false;
1642 
1643 #endif // HAVE_GPHOTO2
1644 
1645 }
1646 
1647 // -- Static methods ---------------------------------------------------------------------
1648 
printGphotoErrorDescription(int errorCode)1649 void GPCamera::printGphotoErrorDescription(int errorCode)
1650 {
1651 #ifdef HAVE_GPHOTO2
1652 
1653     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Libgphoto2 error: " << gp_result_as_string(errorCode)
1654                           << " (" << errorCode << ")";
1655 
1656 #else
1657 
1658     Q_UNUSED(errorCode);
1659 
1660 #endif // HAVE_GPHOTO2
1661 
1662 }
1663 
getSupportedCameras(int & count,QStringList & clist)1664 void GPCamera::getSupportedCameras(int& count, QStringList& clist)
1665 {
1666 #ifdef HAVE_GPHOTO2
1667 
1668     clist.clear();
1669     count                         = 0;
1670 
1671     CameraAbilities      abil;
1672     CameraAbilitiesList* abilList = nullptr;
1673     GPContext*           context  = nullptr;
1674     context                       = gp_context_new();
1675 
1676     gp_abilities_list_new(&abilList);
1677     gp_abilities_list_load(abilList, context);
1678 
1679     count                         = gp_abilities_list_count(abilList);
1680 
1681     if (count < 0)
1682     {
1683         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get list of cameras!";
1684         printGphotoErrorDescription(count);
1685         gp_context_unref(context);
1686         return;
1687     }
1688     else
1689     {
1690         for (int i = 0 ; i < count ; ++i)
1691         {
1692             gp_abilities_list_get_abilities(abilList, i, &abil);
1693             const char* cname = abil.model;
1694             clist.append(QString::fromLocal8Bit(cname));
1695         }
1696     }
1697 
1698     gp_abilities_list_free(abilList);
1699     gp_context_unref(context);
1700 
1701 #else
1702 
1703     Q_UNUSED(count);
1704     Q_UNUSED(clist);
1705 
1706 #endif // HAVE_GPHOTO2
1707 
1708 }
1709 
getSupportedPorts(QStringList & plist)1710 void GPCamera::getSupportedPorts(QStringList& plist)
1711 {
1712 #ifdef HAVE_GPHOTO2
1713 
1714     GPPortInfoList* list = nullptr;
1715     GPPortInfo      info;
1716 
1717     plist.clear();
1718 
1719     gp_port_info_list_new(&list);
1720     gp_port_info_list_load(list);
1721 
1722     int numPorts = gp_port_info_list_count(list);
1723 
1724     if (numPorts < 0)
1725     {
1726         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get list of port!";
1727         printGphotoErrorDescription(numPorts);
1728         gp_port_info_list_free(list);
1729         return;
1730     }
1731     else
1732     {
1733         for (int i = 0 ; i < numPorts ; ++i)
1734         {
1735             gp_port_info_list_get_info(list, i, &info);
1736 
1737 #   ifdef HAVE_GPHOTO25
1738 
1739             char* xpath = nullptr;
1740             gp_port_info_get_name (info, &xpath);
1741             plist.append(QString::fromUtf8(xpath));
1742 
1743 #   else
1744 
1745             plist.append(QString::fromUtf8(info.path));
1746 
1747 #   endif
1748 
1749         }
1750     }
1751 
1752     gp_port_info_list_free(list);
1753 
1754 #else
1755 
1756     Q_UNUSED(plist);
1757 
1758 #endif // HAVE_GPHOTO2
1759 
1760 }
1761 
getCameraSupportedPorts(const QString & model,QStringList & plist)1762 void GPCamera::getCameraSupportedPorts(const QString& model, QStringList& plist)
1763 {
1764 #ifdef HAVE_GPHOTO2
1765 
1766     int i                         = 0;
1767     plist.clear();
1768 
1769     CameraAbilities      abilities;
1770     CameraAbilitiesList* abilList = nullptr;
1771     GPContext*           context  = nullptr;
1772     context                       = gp_context_new();
1773 
1774     gp_abilities_list_new(&abilList);
1775     gp_abilities_list_load(abilList, context);
1776     i = gp_abilities_list_lookup_model(abilList, model.toLocal8Bit().data());
1777     gp_abilities_list_get_abilities(abilList, i, &abilities);
1778     gp_abilities_list_free(abilList);
1779 
1780     if (abilities.port & GP_PORT_SERIAL)
1781     {
1782         plist.append(QLatin1String("serial"));
1783     }
1784 
1785     if (abilities.port & GP_PORT_PTPIP)
1786     {
1787         plist.append(QLatin1String("ptpip"));
1788     }
1789 
1790     if (abilities.port & GP_PORT_USB)
1791     {
1792         plist.append(QLatin1String("usb"));
1793     }
1794 
1795     gp_context_unref(context);
1796 
1797 #else
1798 
1799     Q_UNUSED(model);
1800     Q_UNUSED(plist);
1801 
1802 #endif // HAVE_GPHOTO2
1803 
1804 }
1805 
autoDetect(QString & model,QString & port)1806 int GPCamera::autoDetect(QString& model, QString& port)
1807 {
1808 #ifdef HAVE_GPHOTO2
1809 
1810     CameraList*          camList   = nullptr;
1811     CameraAbilitiesList* abilList  = nullptr;
1812     GPPortInfoList*      infoList  = nullptr;
1813     const char*          camModel_ = nullptr, *camPort_ = nullptr;
1814     GPContext*           context   = nullptr;
1815     context                        = gp_context_new();
1816 
1817     gp_list_new(&camList);
1818 
1819     gp_abilities_list_new(&abilList);
1820     gp_abilities_list_load(abilList, context);
1821     gp_port_info_list_new(&infoList);
1822     gp_port_info_list_load(infoList);
1823     gp_abilities_list_detect(abilList, infoList, camList, context);
1824     gp_abilities_list_free(abilList);
1825     gp_port_info_list_free(infoList);
1826 
1827     gp_context_unref(context);
1828 
1829     int count = gp_list_count(camList);
1830 
1831     if (count <= 0)
1832     {
1833         qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1834         printGphotoErrorDescription(count);
1835         gp_list_free(camList);
1836         return -1;
1837     }
1838 
1839     camModel_ = nullptr;
1840     camPort_  = nullptr;
1841 
1842     for (int i = 0 ; i < count ; ++i)
1843     {
1844         if (gp_list_get_name(camList, i, &camModel_) != GP_OK)
1845         {
1846             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1847             gp_list_free(camList);
1848             return -1;
1849         }
1850 
1851         if (gp_list_get_value(camList, i, &camPort_) != GP_OK)
1852         {
1853             qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1854             gp_list_free(camList);
1855             return -1;
1856         }
1857 
1858         if (camModel_ && camPort_)
1859         {
1860             model = QLatin1String(camModel_);
1861             port  = QLatin1String(camPort_);
1862             gp_list_free(camList);
1863             return 0;
1864         }
1865     }
1866 
1867     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!";
1868     gp_list_free(camList);
1869 
1870 #else
1871 
1872     Q_UNUSED(model);
1873     Q_UNUSED(port);
1874 
1875 #endif // HAVE_GPHOTO2
1876 
1877     return -1;
1878 }
1879 
findConnectedUsbCamera(int vendorId,int productId,QString & model,QString & port)1880 bool GPCamera::findConnectedUsbCamera(int vendorId, int productId, QString& model, QString& port)
1881 {
1882 #ifdef HAVE_GPHOTO2
1883 
1884     CameraAbilitiesList* abilList = nullptr;
1885     GPPortInfoList*      list     = nullptr;
1886     GPContext*           context  = nullptr;
1887     CameraList*          camList  = nullptr;
1888     bool                 success  = false;
1889     // get name and port of detected camera
1890     const char* model_str         = nullptr;
1891     const char* port_str          = nullptr;
1892     context                       = gp_context_new();
1893 
1894     // get list of all ports
1895     gp_port_info_list_new(&list);
1896     gp_port_info_list_load(list);
1897 
1898     gp_abilities_list_new(&abilList);
1899     // get list of all supported cameras
1900     gp_abilities_list_load(abilList, context);
1901 
1902     // autodetect all cameras, then match the list to the passed in USB ids
1903     gp_list_new (&camList);
1904     gp_abilities_list_detect(abilList, list, camList, context);
1905     gp_context_unref(context);
1906 
1907     int count = gp_list_count(camList);
1908     int cnt   = 0;
1909 
1910     for (int i = 0 ; i < count ; ++i)
1911     {
1912         const char* xmodel = nullptr;
1913         gp_list_get_name(camList, i, &xmodel);
1914         int model          = gp_abilities_list_lookup_model (abilList, xmodel);
1915         CameraAbilities ab;
1916         gp_abilities_list_get_abilities(abilList, model, &ab);
1917 
1918         if (ab.port != GP_PORT_USB)
1919             continue;
1920 
1921         /* KDE provides us USB Vendor and Product, but we might just
1922          * have covered this via a class match. Check class matched
1923          * cameras also for matchingo USB vendor/product id
1924          */
1925         if (ab.usb_vendor == 0)
1926         {
1927             int ret;
1928             GPPortInfo info;
1929             const char* xport = nullptr;
1930             GPPort* gpport    = nullptr;
1931 
1932             /* get the port path so we only look at this bus position */
1933             gp_list_get_value(camList, i, &xport);
1934             ret = gp_port_info_list_lookup_path (list, xport);
1935 
1936             if (ret < GP_OK) /* should not happen */
1937                 continue;
1938 
1939             /* get the lowlevel port info  for the path
1940              */
1941             gp_port_info_list_get_info(list, ret, &info);
1942 
1943             /* open lowlevel driver interface briefly to search */
1944             gp_port_new(&gpport);
1945             gp_port_set_info(gpport, info);
1946 
1947             /* And now call into the lowlevel usb driver to see if the bus position
1948              * has that specific vendor/product id
1949              */
1950             if (gp_port_usb_find_device(gpport, vendorId, productId) == GP_OK)
1951             {
1952                 ab.usb_vendor  = vendorId;
1953                 ab.usb_product = productId;
1954             }
1955 
1956             gp_port_free (gpport);
1957         }
1958 
1959         if (ab.usb_vendor != vendorId)
1960             continue;
1961 
1962         if (ab.usb_product != productId)
1963             continue;
1964 
1965         /* keep it, and continue iterating, in case we find another one
1966          */
1967         gp_list_get_name (camList, i, &model_str);
1968         gp_list_get_value(camList, i, &port_str);
1969 
1970         cnt++;
1971     }
1972 
1973     gp_port_info_list_free(list);
1974     gp_abilities_list_free(abilList);
1975 
1976     if (cnt > 0)
1977     {
1978        if (cnt > 1)
1979        {
1980           qCWarning(DIGIKAM_IMPORTUI_LOG) << "More than one camera detected on port " << port
1981                                   << ". Due to restrictions in the GPhoto2 API, "
1982                                   << "only the first camera is used.";
1983        }
1984 
1985        model   = QLatin1String(model_str);
1986        port    = QLatin1String(port_str);
1987        success = true;
1988     }
1989     else
1990     {
1991        qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get information for the listed camera";
1992     }
1993 
1994     gp_list_free(camList);
1995     return success;
1996 
1997 #else
1998 
1999     Q_UNUSED(vendorId);
2000     Q_UNUSED(productId);
2001     Q_UNUSED(model);
2002     Q_UNUSED(port);
2003 
2004     return false;
2005 
2006 #endif // HAVE_GPHOTO2
2007 
2008 }
2009 
2010 } // namespace Digikam
2011