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