1 /*
2 
3     Copyright (C) 2001 The Kompany
4           2001-2003	Ilya Konstantinov <kde-devel@future.shiny.co.il>
5           2001-2008	Marcus Meissner <marcus@jet.franken.de>
6           2012		Marcus Meissner <marcus@jet.franken.de>
7 
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with this program; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 
22 */
23 
24 // remove comment to enable debugging
25 // #undef QT_NO_DEBUG
26 #include "kamera.h"
27 
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <signal.h>
35 #include <errno.h>
36 
37 #include <QFile>
38 #include <QTextStream>
39 #include <QDebug>
40 
41 #include <QCoreApplication>
42 #include <QStandardPaths>
43 
44 #include <KLocalizedString>
45 #include <KConfigGroup>
46 #include <KProtocolInfo>
47 
48 #include <kio/global.h>
49 #include <kio/slaveconfig.h>
50 
51 #include <config-kamera.h>
52 
53 #define tocstr(x) ((x).toLocal8Bit())
54 
55 #define MAXIDLETIME   30      /* seconds */
56 
57 Q_LOGGING_CATEGORY(KAMERA_KIOSLAVE, "kamera.kio")
58 
59 using namespace KIO;
60 
61 // Pseudo plugin class to embed meta data
62 class KIOPluginForMetaData : public QObject
63 {
64     Q_OBJECT
65     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.camera" FILE "camera.json")
66 };
67 
68 extern "C"
69 {
70     Q_DECL_EXPORT int kdemain(int argc, char **argv);
71 
72 #ifdef HAVE_GPHOTO2_5
73     static void frontendCameraStatus(GPContext *context, const char *status, void *data);
74     static unsigned int frontendProgressStart(
75         GPContext *context, float totalsize, const char *status,
76         void *data
77     );
78 #else
79     static void frontendCameraStatus(GPContext *context, const char *format, va_list args, void *data);
80     static unsigned int frontendProgressStart(
81         GPContext *context, float totalsize, const char *format,
82         va_list args, void *data
83     );
84 #endif
85     static void frontendProgressUpdate(
86         GPContext *context, unsigned int id, float current, void *data
87     );
88 }
89 
kdemain(int argc,char ** argv)90 int kdemain(int argc, char **argv)
91 {
92     QCoreApplication app(argc, argv);
93 
94     QCoreApplication::setApplicationName(QStringLiteral("kio_kamera"));
95     KLocalizedString::setApplicationDomain("kio_kamera");
96 
97 #ifdef DEBUG_KAMERA_KIO
98     QLoggingCategory::setFilterRules(QStringLiteral("kamera.kio.debug = true"));
99 #endif
100 
101     if(argc != 4) {
102         qCDebug(KAMERA_KIOSLAVE) << "Usage: kio_kamera protocol "
103                  "domain-socket1 domain-socket2";
104         exit(-1);
105     }
106 
107     KameraProtocol slave(argv[2], argv[3]);
108 
109     slave.dispatchLoop();
110 
111     return 0;
112 }
113 
path_quote(QString path)114 static QString path_quote(QString path)   { return path.replace(QStringLiteral("/"),QStringLiteral("%2F")).replace(QStringLiteral(" "),QStringLiteral("%20")); }
path_unquote(QString path)115 static QString path_unquote(QString path) { return path.replace(QStringLiteral("%2F"),QStringLiteral("/")).replace(QStringLiteral("%20"),QStringLiteral(" ")); }
116 
KameraProtocol(const QByteArray & pool,const QByteArray & app)117 KameraProtocol::KameraProtocol(const QByteArray &pool, const QByteArray &app)
118 : SlaveBase("camera", pool, app),
119 m_camera(nullptr)
120 {
121     // attempt to initialize libgphoto2 and chosen camera (requires locking)
122     // (will init m_camera, since the m_camera's configuration is empty)
123     m_camera = nullptr;
124     m_file = nullptr;
125     m_config = new KConfig(KProtocolInfo::config(QStringLiteral("camera")), KConfig::SimpleConfig);
126     m_context = gp_context_new();
127     actiondone = true;
128     cameraopen = false;
129     m_lockfile = QStandardPaths::writableLocation(QStandardPaths::TempLocation)
130             + "/kamera";
131     idletime = 0;
132 }
133 
134 // This handler is getting called every second. We use it to do the
135 // delayed close of the camera.
136 // Logic is:
137 // 	- No more requests in the queue (signaled by actiondone) AND
138 //		- We are MAXIDLETIME seconds idle OR
139 //		- Another slave wants to have access to the camera.
140 //
141 // The existence of a lockfile is used to signify "please give up camera".
142 //
special(const QByteArray &)143 void KameraProtocol::special(const QByteArray&) {
144     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::special() at " << getpid()
145                              << ". idletime: " << idletime;
146 
147     if (!actiondone && cameraopen) {
148         struct stat	stbuf;
149         if ((-1!=::stat(m_lockfile.toUtf8(),&stbuf)) || (idletime++ >= MAXIDLETIME)) {
150             qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::special() closing camera.";
151             closeCamera();
152             setTimeoutSpecialCommand(-1);
153         } else {
154             // continue to wait
155             setTimeoutSpecialCommand(1);
156         }
157     } else {
158         // We let it run until the slave gets no actions anymore.
159         setTimeoutSpecialCommand(1);
160     }
161     actiondone = false;
162 }
163 
~KameraProtocol()164 KameraProtocol::~KameraProtocol()
165 {
166     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::~KameraProtocol()";
167     delete m_config;
168     if(m_camera) {
169         closeCamera();
170         gp_camera_free(m_camera);
171         m_camera = NULL;
172     }
173 }
174 
175 // initializes the camera for usage -
176 // should be done before operations over the wire
openCamera(QString & str)177 bool KameraProtocol::openCamera(QString &str) {
178     idletime = 0;
179     actiondone = true;
180     if (!m_camera) {
181         reparseConfiguration();
182     } else {
183         if (!cameraopen) {
184             int ret, tries = 15;
185             qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::openCamera at "
186                                      << getpid();
187             // Gets this far.
188             while (tries--) {
189                 ret = gp_camera_init(m_camera, m_context);
190                 if (	(ret == GP_ERROR_IO_USB_CLAIM) ||
191                     (ret == GP_ERROR_IO_LOCK)) {
192                     // just create / touch if not there
193                     int fd = ::open(m_lockfile.toUtf8(),O_CREAT|O_WRONLY,0600);
194                     if (fd != -1) ::close(fd);
195                     ::sleep(1);
196                     qCDebug(KAMERA_KIOSLAVE) << "openCamera at " << getpid()
197                                              << "- busy, ret " << ret
198                                              << ", trying again.";
199                     continue;
200                 }
201                 if (ret == GP_OK) break;
202                 str = gp_result_as_string(ret);
203                 return false;
204             }
205             ::remove(m_lockfile.toUtf8());
206             setTimeoutSpecialCommand(1);
207             qCDebug(KAMERA_KIOSLAVE) << "openCamera succeeded at " << getpid();
208             cameraopen = true;
209         }
210     }
211     return true;
212 }
213 
214 // should be done after operations over the wire
closeCamera(void)215 void KameraProtocol::closeCamera(void)
216 {
217     int gpr;
218 
219     if (!m_camera) {
220         return;
221     }
222 
223     if ((gpr=gp_camera_exit(m_camera,m_context))!=GP_OK) {
224         qCDebug(KAMERA_KIOSLAVE) << "closeCamera failed with "
225                                  << gp_result_as_string(gpr);
226     }
227     // HACK: gp_camera_exit() in gp 2.0 does not close the port if there
228     //       is no camera_exit function.
229     gp_port_close(m_camera->port);
230     cameraopen = false;
231     current_camera = QStringLiteral("");
232     current_port = QStringLiteral("");
233     return;
234 }
235 
fix_foldername(const QString & ofolder)236 static QString fix_foldername(const QString &ofolder) {
237     QString folder = ofolder;
238     if (folder.length() > 1) {
239         while ((folder.length()>1) && (folder.right(1) == QStringLiteral("/")))
240             folder = folder.left(folder.length()-1);
241     }
242     if (folder.length() == 0) {
243         folder = QStringLiteral("/");
244     }
245     return folder;
246 }
247 
248 // The KIO slave "get" function (starts a download from the camera)
249 // The actual returning of the data is done in the frontend callback functions.
get(const QUrl & url)250 void KameraProtocol::get(const QUrl &url)
251 {
252     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::get(" << url.path() << ")";
253     QString directory, file;
254     CameraFileType fileType;
255     int gpr;
256 
257     split_url2camerapath(url.path(), directory, file);
258     if(!openCamera()) {
259         error(KIO::ERR_DOES_NOT_EXIST, url.path());
260         return;
261     }
262 
263 
264 #define GPHOTO_TEXT_FILE(xx) \
265     if (!directory.compare(QStringLiteral("/")) && !file.compare(#xx ".txt")) { \
266         CameraText xx; \
267         gpr = gp_camera_get_##xx(m_camera,  &xx, m_context); \
268         if (gpr != GP_OK) { \
269             error(KIO::ERR_DOES_NOT_EXIST, url.path()); \
270             return; \
271         } \
272         QByteArray chunkDataBuffer = QByteArray::fromRawData(xx.text, strlen(xx.text)); \
273         data(chunkDataBuffer); \
274         processedSize(strlen(xx.text)); \
275         chunkDataBuffer.clear(); \
276         finished(); \
277         return; \
278     }
279 
280     GPHOTO_TEXT_FILE(about);
281     GPHOTO_TEXT_FILE(manual);
282     GPHOTO_TEXT_FILE(summary);
283 
284 #undef GPHOTO_TEXT_FILE
285     // Q_EMIT info message
286         // WARNING Fix this
287     //infoMessage( i18n("Retrieving data from camera <b>%1</b>", current_camera) );
288 
289     // Note: There's no need to re-read directory for each get() anymore
290     gp_file_new(&m_file);
291 
292     // Q_EMIT the total size (we must do it before sending data to allow preview)
293     CameraFileInfo info;
294 
295     gpr = gp_camera_file_get_info(m_camera,
296                                   tocstr(fix_foldername(directory)),
297                                   tocstr(file),
298                                   &info,
299                                   m_context);
300     if (gpr != GP_OK) {
301         gp_file_unref(m_file);
302         if ((gpr == GP_ERROR_FILE_NOT_FOUND) ||
303                 (gpr == GP_ERROR_DIRECTORY_NOT_FOUND)) {
304             error(KIO::ERR_DOES_NOT_EXIST, url.path());
305         } else {
306             error(KIO::ERR_UNKNOWN,
307                   QString::fromLocal8Bit(gp_result_as_string(gpr)));
308         }
309         return;
310     }
311 
312     // at last, a proper API to determine whether a thumbnail was requested.
313     if(cameraSupportsPreview() && metaData(QStringLiteral("thumbnail")) == QStringLiteral("1")) {
314         qCDebug(KAMERA_KIOSLAVE) << "get() retrieving the thumbnail";
315         fileType = GP_FILE_TYPE_PREVIEW;
316         if (info.preview.fields & GP_FILE_INFO_SIZE) {
317             totalSize(info.preview.size);
318         }
319         if (info.preview.fields & GP_FILE_INFO_TYPE) {
320             mimeType(info.preview.type);
321         }
322     } else {
323         qCDebug(KAMERA_KIOSLAVE) << "get() retrieving the full-scale photo";
324         fileType = GP_FILE_TYPE_NORMAL;
325         if (info.file.fields & GP_FILE_INFO_SIZE) {
326             totalSize(info.file.size);
327         }
328         if (info.preview.fields & GP_FILE_INFO_TYPE) {
329             mimeType(info.file.type);
330         }
331     }
332 
333     // fetch the data
334     m_fileSize = 0;
335     gpr = gp_camera_file_get(m_camera,
336                              tocstr(fix_foldername(directory)),
337                              tocstr(file),
338                              fileType,
339                              m_file,
340                              m_context
341                              );
342     if ((gpr == GP_ERROR_NOT_SUPPORTED) && (fileType == GP_FILE_TYPE_PREVIEW)) {
343         // If we get here, the file info command information
344         // will either not be used, or still valid.
345         fileType = GP_FILE_TYPE_NORMAL;
346         gpr = gp_camera_file_get(m_camera,
347                                  tocstr(fix_foldername(directory)),
348                                  tocstr(file),
349                                  fileType,
350                                  m_file,
351                                  m_context
352                                  );
353     }
354     switch(gpr) {
355         case GP_OK:
356             break;
357         case GP_ERROR_FILE_NOT_FOUND:
358         case GP_ERROR_DIRECTORY_NOT_FOUND:
359             gp_file_unref(m_file);
360             m_file = nullptr;
361             error(KIO::ERR_DOES_NOT_EXIST, url.fileName());
362             return ;
363         default:
364             gp_file_unref(m_file);
365             m_file = nullptr;
366             error(KIO::ERR_UNKNOWN,
367                   QString::fromLocal8Bit(gp_result_as_string(gpr)));
368             return;
369     }
370     // Q_EMIT the mimetype
371     // NOTE: we must first get the file, so that CameraFile->name would be set
372     const char *fileMimeType;
373     gp_file_get_mime_type(m_file, &fileMimeType);
374     mimeType(fileMimeType);
375 
376     // We need to pass left over data here. Some camera drivers do not
377     // implement progress callbacks!
378     const char *fileData;
379     long unsigned int fileSize;
380     // This merely returns us a pointer to gphoto's internal data
381     // buffer -- there's no expensive memcpy
382     gpr = gp_file_get_data_and_size(m_file, &fileData, &fileSize);
383     if (gpr != GP_OK) {
384         qCDebug(KAMERA_KIOSLAVE) << "get():: get_data_and_size failed.";
385         gp_file_free(m_file);
386         m_file = NULL;
387         error(KIO::ERR_UNKNOWN,
388               QString::fromLocal8Bit(gp_result_as_string(gpr)));
389         return;
390     }
391     // make sure we're not sending zero-sized chunks (=EOF)
392     // also make sure we send only if the progress did not send the data
393     // already.
394     if ((fileSize > 0)  && (fileSize - m_fileSize)>0) {
395         unsigned long written = 0;
396         QByteArray chunkDataBuffer;
397 
398         // We need to split it up here. Someone considered it funny
399         // to discard any data() larger than 16MB.
400         //
401         // So nearly any Movie will just fail....
402         while (written < fileSize-m_fileSize) {
403             unsigned long towrite = 1024*1024; // 1MB
404 
405             if (towrite > fileSize-m_fileSize-written) {
406                 towrite = fileSize-m_fileSize-written;
407             }
408             chunkDataBuffer = QByteArray::fromRawData(
409                         fileData + m_fileSize + written, towrite);
410             processedSize(m_fileSize + written + towrite);
411             data(chunkDataBuffer);
412             chunkDataBuffer.clear();
413             written += towrite;
414         }
415         m_fileSize = fileSize;
416         setFileSize(fileSize);
417     }
418 
419     finished();
420     gp_file_unref(m_file); /* just unref, might be stored in fs */
421     m_file = NULL;
422 }
423 
424 // The KIO slave "stat" function.
stat(const QUrl & url)425 void KameraProtocol::stat(const QUrl &url)
426 {
427     qCDebug(KAMERA_KIOSLAVE) << "stat(\"" << url.path() << "\")";
428 
429     if (url.path().isEmpty()) {
430         QUrl rooturl(url);
431 
432         qCDebug(KAMERA_KIOSLAVE) << "redirecting to /";
433         rooturl.setPath(QStringLiteral("/"));
434         redirection(rooturl);
435         finished();
436         return;
437     }
438     if(url.path() == QStringLiteral("/"))
439         statRoot();
440     else
441         statRegular(url);
442 }
443 
444 // Implements stat("/") -- which always returns the same value.
statRoot(void)445 void KameraProtocol::statRoot(void)
446 {
447     KIO::UDSEntry entry;
448 
449     entry.fastInsert( KIO::UDSEntry::UDS_NAME, QString::fromLocal8Bit("/"));
450 
451     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR);
452 
453     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH));
454     statEntry(entry);
455     finished();
456     // If we just do this call, timeout right away if no other requests are
457     // pending. This is for the kdemm autodetection using media://camera
458     idletime = MAXIDLETIME;
459 }
460 
split_url2camerapath(const QString & url,QString & directory,QString & file)461 void KameraProtocol::split_url2camerapath(const QString &url,
462     QString &directory,
463     QString &file
464 ) {
465     QStringList	components, camarr;
466     QString		cam, camera, port;
467 
468     components	= url.split(QLatin1Char('/'), Qt::SkipEmptyParts);
469     if (components.size() == 0) {
470         return;
471     }
472     cam	= path_unquote(components.takeFirst());
473     if (!cam.isEmpty()) {
474         camarr  = cam.split(QLatin1Char('@'));
475         camera  = path_unquote(camarr.takeFirst());
476         port    = path_unquote(camarr.takeLast());
477         setCamera (camera, port);
478     }
479     if (components.isEmpty())  {
480         directory = QStringLiteral("/");
481         return;
482     }
483     file		= path_unquote(components.takeLast());
484     directory 	= path_unquote(QStringLiteral("/")+components.join(QLatin1Char('/')));
485 }
486 
487 // Implements a regular stat() of a file / directory, returning all we know about it
statRegular(const QUrl & xurl)488 void KameraProtocol::statRegular(const QUrl &xurl)
489 {
490     KIO::UDSEntry entry;
491     QString	directory, file;
492     int gpr;
493 
494     qCDebug(KAMERA_KIOSLAVE) << "statRegular(\"" << xurl.path() << "\")";
495 
496     split_url2camerapath(xurl.path(), directory, file);
497 
498     if (openCamera() == false) {
499         error(KIO::ERR_DOES_NOT_EXIST, xurl.path());
500         return;
501     }
502 
503     if (directory == QLatin1String("/")) {
504         KIO::UDSEntry entry;
505 
506 #define GPHOTO_TEXT_FILE(xx) \
507         if (!file.compare(#xx".txt")) { \
508             CameraText xx; \
509             gpr = gp_camera_get_about(m_camera,  &xx, m_context); \
510             if (gpr != GP_OK) { \
511                 error(KIO::ERR_DOES_NOT_EXIST, xurl.fileName()); \
512                 return; \
513             } \
514             translateTextToUDS(entry,#xx".txt",xx.text); \
515             statEntry(entry); \
516             finished(); \
517             return; \
518         }
519         GPHOTO_TEXT_FILE(about);
520         GPHOTO_TEXT_FILE(manual);
521         GPHOTO_TEXT_FILE(summary);
522 #undef GPHOTO_TEXT_FILE
523 
524         QString xname = current_camera + QLatin1Char('@') + current_port;
525         entry.fastInsert( KIO::UDSEntry::UDS_NAME, path_quote(xname));
526         entry.fastInsert( KIO::UDSEntry::UDS_DISPLAY_NAME, current_camera);
527         entry.fastInsert( KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR);
528         entry.fastInsert( KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH));
529         statEntry(entry);
530         finished();
531         return;
532     }
533 
534     // Is "url" a directory?
535     CameraList *dirList;
536     gp_list_new(&dirList);
537     qCDebug(KAMERA_KIOSLAVE) << "statRegular() Requesting directories list for "
538                              << directory;
539 
540     gpr = gp_camera_folder_list_folders(m_camera,
541                                         tocstr(fix_foldername(directory)),
542                                         dirList,
543                                         m_context);
544     if (gpr != GP_OK) {
545         if ((gpr == GP_ERROR_FILE_NOT_FOUND) ||
546                 (gpr == GP_ERROR_DIRECTORY_NOT_FOUND)) {
547             error(KIO::ERR_DOES_NOT_EXIST, xurl.path());
548         } else {
549             error(KIO::ERR_UNKNOWN,
550                   QString::fromLocal8Bit(gp_result_as_string(gpr)));
551         }
552         gp_list_free(dirList);
553         return;
554     }
555 
556     const char *name;
557     for(int i = 0; i < gp_list_count(dirList); i++) {
558         gp_list_get_name(dirList, i, &name);
559         if (file.compare(name) == 0) {
560             gp_list_free(dirList);
561             KIO::UDSEntry entry;
562             translateDirectoryToUDS(entry, file);
563             statEntry(entry);
564             finished();
565             return;
566         }
567     }
568     gp_list_free(dirList);
569 
570     // Is "url" a file?
571     CameraFileInfo info;
572     gpr = gp_camera_file_get_info(m_camera,
573                                   tocstr(fix_foldername(directory)),
574                                   tocstr(file),
575                                   &info,
576                                   m_context
577                                   );
578     if (gpr != GP_OK) {
579         if ((gpr == GP_ERROR_FILE_NOT_FOUND) ||
580                 (gpr == GP_ERROR_DIRECTORY_NOT_FOUND)) {
581             error(KIO::ERR_DOES_NOT_EXIST, xurl.path());
582         } else {
583             error(KIO::ERR_UNKNOWN,
584                   QString::fromLocal8Bit(gp_result_as_string(gpr)));
585         }
586         return;
587     }
588     translateFileToUDS(entry, info, file);
589     statEntry(entry);
590     finished();
591 }
592 
593 // The KIO slave "del" function.
del(const QUrl & url,bool isFile)594 void KameraProtocol::del(const QUrl &url, bool isFile)
595 {
596     QString directory, file;
597     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::del(" << url.path() << ")";
598 
599     split_url2camerapath (url.path(), directory, file);
600     if(!openCamera()) {
601         error(KIO::ERR_CANNOT_DELETE, file);
602         return;
603     }
604     if (!cameraSupportsDel()) {
605         error(KIO::ERR_CANNOT_DELETE, file);
606         return;
607     }
608     if(isFile){
609         CameraList *list;
610         gp_list_new(&list);
611         int ret;
612 
613         ret = gp_camera_file_delete(m_camera,
614                                     tocstr(fix_foldername(directory)),
615                                     tocstr(file),
616                                     m_context);
617 
618         if(ret != GP_OK) {
619             error(KIO::ERR_CANNOT_DELETE, file);
620         } else {
621             finished();
622         }
623     }
624 }
625 
626 // The KIO slave "listDir" function.
listDir(const QUrl & yurl)627 void KameraProtocol::listDir(const QUrl &yurl)
628 {
629     QString directory, file;
630     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::listDir(" << yurl.path() << ")";
631 
632     split_url2camerapath(yurl.path(), directory, file);
633 
634     if (!file.isEmpty()) {
635         if (directory == QLatin1Char('/')) {
636             directory = QLatin1Char('/') + file;
637         } else {
638             directory = directory + QLatin1Char('/') + file;
639         }
640     }
641 
642     if (yurl.path() == QLatin1Char('/')) {
643         QUrl xurl;
644         // List the available cameras
645         QStringList groupList = m_config->groupList();
646         qCDebug(KAMERA_KIOSLAVE) << "Found cameras: " << groupList.join(QStringLiteral(", "));
647         QStringList::Iterator it;
648         KIO::UDSEntry entry;
649 
650 
651         /*
652          * What we do:
653          * - Autodetect cameras and remember them with their ports.
654          * - List all saved and possible offline cameras.
655          * - List all autodetected and not yet printed cameras.
656          */
657         QMap<QString,QString>	ports, names;
658         QMap<QString,int>	modelcnt;
659 
660         /* Autodetect USB cameras ... */
661         GPContext *glob_context = NULL;
662         int i, count;
663         CameraList *list;
664         CameraAbilitiesList *al;
665         GPPortInfoList *il;
666 
667         gp_list_new (&list);
668         gp_abilities_list_new (&al);
669         gp_abilities_list_load (al, glob_context);
670         gp_port_info_list_new (&il);
671         gp_port_info_list_load (il);
672         gp_abilities_list_detect (al, il, list, glob_context);
673         gp_abilities_list_free (al);
674         gp_port_info_list_free (il);
675 
676         count = gp_list_count (list);
677 
678         for (i = 0 ; i<count ; i++) {
679             const char *model, *value;
680 
681             gp_list_get_name  (list, i, &model);
682             gp_list_get_value (list, i, &value);
683 
684             ports[value] = model;
685             // NOTE: We might get different ports than usb: later!
686             if (strcmp(value,"usb:") != 0) {
687                 names[model] = value;
688             }
689 
690             /* Save them, even though we can autodetect them for
691              * offline listing.
692              */
693 #if 0
694             KConfigGroup cg(m_config, model);
695             cg.writeEntry("Model", model);
696             cg.writeEntry("Path", value);
697 #endif
698             modelcnt[model]++;
699         }
700         gp_list_free (list);
701 
702         /* Avoid duplicated entry, that is a camera with both
703          * port usb: and usb:001,042 entries. */
704         if (ports.contains(QStringLiteral("usb:")) &&
705             names.contains(ports[QStringLiteral("usb:")]) &&
706                 names[ports[QStringLiteral("usb:")]] != QStringLiteral("usb:")) {
707             ports.remove(QStringLiteral("usb:"));
708         }
709 
710         for (it = groupList.begin(); it != groupList.end(); it++) {
711             QString m_cfgPath;
712             if (*it == QStringLiteral("<default>")) {
713                 continue;
714             }
715 
716             KConfigGroup cg(m_config, *it);
717             m_cfgPath = cg.readEntry("Path");
718 
719             // we autodetected those ...
720             if (m_cfgPath.contains(QLatin1String("usb:"))) {
721                 cg.deleteGroup();
722                 continue;
723             }
724 
725             QString xname;
726 
727             entry.clear();
728             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR);
729             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS,
730                     (S_IRUSR | S_IRGRP | S_IROTH |S_IWUSR | S_IWGRP | S_IWOTH));
731             xname = (*it)+'@'+m_cfgPath;
732             entry.fastInsert(KIO::UDSEntry::UDS_NAME,path_quote(xname));
733             // do not confuse regular users with the @usb...
734             entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME,*it);
735             listEntry(entry);
736         }
737 
738         QMap<QString,QString>::iterator portsit;
739 
740         for (portsit = ports.begin(); portsit != ports.end(); portsit++) {
741             entry.clear();
742             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR);
743             // do not confuse regular users with the @usb...
744             entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME,portsit.value());
745             entry.fastInsert(KIO::UDSEntry::UDS_NAME,
746                     path_quote(portsit.value()+QLatin1Char('@')+portsit.key()));
747 
748             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS,
749                     (S_IRUSR | S_IRGRP | S_IROTH |S_IWUSR | S_IWGRP | S_IWOTH));
750             listEntry(entry);
751         }
752         finished();
753         return;
754     }
755 
756     if (directory.isEmpty()) {
757         QUrl rooturl(yurl);
758 
759         qCDebug(KAMERA_KIOSLAVE) << "redirecting to /";
760         if (!current_camera.isEmpty() && !current_port.isEmpty()) {
761             rooturl.setPath(QLatin1Char('/')+current_camera+QLatin1Char('@')+current_port+QLatin1Char('/'));
762         } else {
763             rooturl.setPath(QStringLiteral("/"));
764         }
765         redirection(rooturl);
766         finished();
767         return;
768     }
769 
770     if (!openCamera()) {
771         error(KIO::ERR_CANNOT_READ, yurl.path());
772         return;
773     }
774 
775     CameraList *dirList;
776     CameraList *fileList;
777     CameraList *specialList;
778     gp_list_new(&dirList);
779     gp_list_new(&fileList);
780     gp_list_new(&specialList);
781     int gpr;
782 
783     if (!directory.compare(QStringLiteral("/"))) {
784         CameraText text;
785         if (GP_OK == gp_camera_get_manual(m_camera, &text, m_context)) {
786             gp_list_append(specialList,"manual.txt",NULL);
787         }
788         if (GP_OK == gp_camera_get_about(m_camera, &text, m_context)) {
789             gp_list_append(specialList,"about.txt",NULL);
790         }
791         if (GP_OK == gp_camera_get_summary(m_camera, &text, m_context)) {
792             gp_list_append(specialList,"summary.txt",NULL);
793         }
794     }
795 
796     gpr = readCameraFolder(directory, dirList, fileList);
797     if(gpr != GP_OK) {
798         qCDebug(KAMERA_KIOSLAVE) << "read Camera Folder failed:"
799                                  << gp_result_as_string(gpr);
800         gp_list_free(dirList);
801         gp_list_free(fileList);
802         gp_list_free(specialList);
803         error(KIO::ERR_SLAVE_DEFINED, i18n("Could not read. Reason: %1",
804                     QString::fromLocal8Bit(gp_result_as_string(gpr))));
805         return;
806     }
807 
808     totalSize(gp_list_count(specialList) +
809               gp_list_count(dirList) +
810               gp_list_count(fileList));
811 
812     KIO::UDSEntry entry;
813     const char *name;
814 
815     for(int i = 0; i < gp_list_count(dirList); ++i) {
816         gp_list_get_name(dirList, i, &name);
817         translateDirectoryToUDS(entry, QString::fromLocal8Bit(name));
818         listEntry(entry);
819     }
820 
821     CameraFileInfo info;
822 
823     for(int i = 0; i < gp_list_count(fileList); ++i) {
824         gp_list_get_name(fileList, i, &name);
825         // we want to know more info about files (size, type...)
826         gp_camera_file_get_info(m_camera,
827                                 tocstr(directory),
828                                 name,
829                                 &info,
830                                 m_context);
831         translateFileToUDS(entry, info, QString::fromLocal8Bit(name));
832         listEntry(entry);
833     }
834     if (!directory.compare(QStringLiteral("/"))) {
835         CameraText text;
836         if (GP_OK == gp_camera_get_manual(m_camera, &text, m_context)) {
837             translateTextToUDS(entry, QStringLiteral("manual.txt"), text.text);
838             listEntry(entry);
839         }
840         if (GP_OK == gp_camera_get_about(m_camera, &text, m_context)) {
841             translateTextToUDS(entry, QStringLiteral("about.txt"), text.text);
842             listEntry(entry);
843         }
844         if (GP_OK == gp_camera_get_summary(m_camera, &text, m_context)) {
845             translateTextToUDS(entry, QStringLiteral("summary.txt"), text.text);
846             listEntry(entry);
847         }
848     }
849 
850 
851     gp_list_free(fileList);
852     gp_list_free(dirList);
853     gp_list_free(specialList);
854 
855     finished();
856 }
857 
setCamera(const QString & camera,const QString & port)858 void KameraProtocol::setCamera(const QString& camera, const QString& port)
859 {
860     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::setCamera(" << camera
861                              << ", " << port << ")";
862     int gpr, idx;
863 
864     if (!camera.isEmpty() && !port.isEmpty()) {
865         if (	m_camera &&
866             (current_camera == camera) &&
867             (current_port == port)
868         ) {
869             qCDebug(KAMERA_KIOSLAVE) << "Configuration is same, nothing to do.";
870             return;
871         }
872         if (m_camera) {
873             qCDebug(KAMERA_KIOSLAVE) << "Configuration change detected";
874             closeCamera();
875             gp_camera_unref(m_camera);
876             m_camera = nullptr;
877                         // WARNING Fix this
878             //infoMessage( i18n("Reinitializing camera") );
879         } else {
880             qCDebug(KAMERA_KIOSLAVE) << "Initializing camera";
881                         // WARNING Fix this
882             //infoMessage( i18n("Initializing camera") );
883         }
884         // fetch abilities
885         CameraAbilitiesList *abilities_list;
886         gp_abilities_list_new(&abilities_list);
887         gp_abilities_list_load(abilities_list, m_context);
888         idx = gp_abilities_list_lookup_model(abilities_list, tocstr(camera));
889         if (idx < 0) {
890             gp_abilities_list_free(abilities_list);
891             qCDebug(KAMERA_KIOSLAVE) << "Unable to get abilities for model: "
892                                      << camera;
893             error(KIO::ERR_UNKNOWN,
894                   QString::fromLocal8Bit(gp_result_as_string(idx)));
895             return;
896         }
897         gp_abilities_list_get_abilities(abilities_list, idx, &m_abilities);
898         gp_abilities_list_free(abilities_list);
899 
900         // fetch port
901         GPPortInfoList *port_info_list;
902         GPPortInfo port_info;
903         gp_port_info_list_new(&port_info_list);
904         gp_port_info_list_load(port_info_list);
905         idx = gp_port_info_list_lookup_path(port_info_list, tocstr(port));
906 
907         /* Handle erronously passed usb:XXX,YYY */
908         if ((idx < 0) && port.startsWith(QStringLiteral("usb:"))) {
909             idx = gp_port_info_list_lookup_path(port_info_list, "usb:");
910         }
911         if (idx < 0) {
912             gp_port_info_list_free(port_info_list);
913             qCDebug(KAMERA_KIOSLAVE) << "Unable to get port info for path: "
914                                      << port;
915             error(KIO::ERR_UNKNOWN,
916                   QString::fromLocal8Bit(gp_result_as_string(idx)));
917             return;
918         }
919         gp_port_info_list_get_info(port_info_list, idx, &port_info);
920 
921         current_camera	= camera;
922         current_port	= port;
923         // create a new camera object
924         gpr = gp_camera_new(&m_camera);
925         if(gpr != GP_OK) {
926             gp_port_info_list_free(port_info_list);
927             error(KIO::ERR_UNKNOWN,
928                   QString::fromLocal8Bit(gp_result_as_string(gpr)));
929             return;
930         }
931 
932         // register gphoto2 callback functions
933         gp_context_set_status_func(m_context, frontendCameraStatus, this);
934         gp_context_set_progress_funcs(m_context,
935                                       frontendProgressStart,
936                                       frontendProgressUpdate,
937                                       nullptr,
938                                       this
939                                       );
940         // gp_camera_set_message_func(m_camera, ..., this)
941 
942         // set model and port
943         gp_camera_set_abilities(m_camera, m_abilities);
944         gp_camera_set_port_info(m_camera, port_info);
945         gp_camera_set_port_speed(m_camera, 0); // TODO: the value needs to be configurable
946         qCDebug(KAMERA_KIOSLAVE) << "Opening camera model " << camera
947                                  << " at " << port;
948 
949         gp_port_info_list_free(port_info_list);
950 
951         QString errstr;
952         if (!openCamera(errstr)) {
953             if (m_camera) {
954                 gp_camera_unref(m_camera);
955             }
956             m_camera = nullptr;
957             qCDebug(KAMERA_KIOSLAVE) << "Unable to init camera: " << errstr;
958             error(KIO::ERR_SERVICE_NOT_AVAILABLE, errstr);
959             return;
960         }
961     }
962 }
963 
reparseConfiguration(void)964 void KameraProtocol::reparseConfiguration(void)
965 {
966     // we have no global config, do we?
967 }
968 
969 // translate a simple text to a UDS entry
translateTextToUDS(KIO::UDSEntry & udsEntry,const QString & fn,const char * text)970 void KameraProtocol::translateTextToUDS(KIO::UDSEntry &udsEntry,
971     const QString &fn,
972     const char *text)
973 {
974     udsEntry.clear();
975     udsEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFREG);
976     udsEntry.fastInsert(KIO::UDSEntry::UDS_NAME,path_quote(fn));
977     udsEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME,fn);
978     udsEntry.fastInsert(KIO::UDSEntry::UDS_SIZE,strlen(text));
979     udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH));
980     udsEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("text/plain"));
981 }
982 
983 // translate a CameraFileInfo to a UDSFieldType
984 // which we can return as a directory listing entry
translateFileToUDS(KIO::UDSEntry & udsEntry,const CameraFileInfo & info,const QString & name)985 void KameraProtocol::translateFileToUDS(KIO::UDSEntry &udsEntry,
986                                         const CameraFileInfo &info,
987                                         const QString &name)
988 {
989 
990     udsEntry.clear();
991 
992     udsEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFREG);
993     udsEntry.fastInsert(KIO::UDSEntry::UDS_NAME,path_quote(name));
994     udsEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME,name);
995 
996     if (info.file.fields & GP_FILE_INFO_SIZE) {
997         udsEntry.fastInsert(KIO::UDSEntry::UDS_SIZE,info.file.size);
998     }
999 
1000     if (info.file.fields & GP_FILE_INFO_MTIME) {
1001         udsEntry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME,info.file.mtime);
1002     } else {
1003         udsEntry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME,time(nullptr));
1004     }
1005 
1006     if (info.file.fields & GP_FILE_INFO_TYPE) {
1007         udsEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE,
1008                         QString::fromLatin1(info.file.type));
1009     }
1010 
1011     if (info.file.fields & GP_FILE_INFO_PERMISSIONS) {
1012         udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS,
1013                         ((info.file.permissions & GP_FILE_PERM_READ) ?
1014                              (S_IRUSR | S_IRGRP | S_IROTH) : 0)
1015                         );
1016     } else {
1017         udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS,S_IRUSR | S_IRGRP | S_IROTH);
1018     }
1019 
1020     // TODO: We do not handle info.preview in any way
1021 }
1022 
1023 // translate a directory name to a UDSFieldType
1024 // which we can return as a directory listing entry
translateDirectoryToUDS(KIO::UDSEntry & udsEntry,const QString & dirname)1025 void KameraProtocol::translateDirectoryToUDS(KIO::UDSEntry &udsEntry,
1026                                              const QString &dirname)
1027 {
1028 
1029     udsEntry.clear();
1030 
1031     udsEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR);
1032     udsEntry.fastInsert(KIO::UDSEntry::UDS_NAME,path_quote(dirname));
1033     udsEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, dirname);
1034     udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS,
1035             S_IRUSR | S_IRGRP | S_IROTH |S_IWUSR | S_IWGRP |
1036             S_IWOTH | S_IXUSR | S_IXOTH | S_IXGRP);
1037     udsEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
1038 }
1039 
cameraSupportsDel(void)1040 bool KameraProtocol::cameraSupportsDel(void)
1041 {
1042         return (m_abilities.file_operations & GP_FILE_OPERATION_DELETE);
1043 }
1044 
cameraSupportsPut(void)1045 bool KameraProtocol::cameraSupportsPut(void)
1046 {
1047         return (m_abilities.folder_operations & GP_FOLDER_OPERATION_PUT_FILE);
1048 }
1049 
cameraSupportsPreview(void)1050 bool KameraProtocol::cameraSupportsPreview(void)
1051 {
1052     return (m_abilities.file_operations & GP_FILE_OPERATION_PREVIEW);
1053 }
1054 
readCameraFolder(const QString & folder,CameraList * dirList,CameraList * fileList)1055 int KameraProtocol::readCameraFolder(const QString &folder,
1056                                      CameraList *dirList,
1057                                      CameraList *fileList)
1058 {
1059     qCDebug(KAMERA_KIOSLAVE) << "KameraProtocol::readCameraFolder("
1060                              << folder << ")";
1061 
1062     int gpr;
1063     if((gpr = gp_camera_folder_list_folders(m_camera, tocstr(folder), dirList, m_context)) != GP_OK) {
1064         return gpr;
1065     }
1066     if((gpr = gp_camera_folder_list_files(m_camera, tocstr(folder), fileList, m_context)) != GP_OK) {
1067         return gpr;
1068     }
1069     return GP_OK;
1070 }
1071 
frontendProgressUpdate(GPContext *,unsigned int,float,void * data)1072 void frontendProgressUpdate(
1073     GPContext * /*context*/, unsigned int /*id*/, float /*current*/, void *data
1074 ) {
1075     KameraProtocol *object = (KameraProtocol*)data;
1076 
1077     // This code will get the last chunk of data retrieved from the
1078     // camera and pass it to KIO, to allow progressive display
1079     // of the downloaded photo.
1080 
1081     const char *fileData = nullptr;
1082     long unsigned int fileSize = 0;
1083 
1084     // This merely returns us a pointer to gphoto's internal data
1085     // buffer -- there's no expensive memcpy
1086     if (!object->getFile()) {
1087         return;
1088     }
1089     gp_file_get_data_and_size(object->getFile(), &fileData, &fileSize);
1090     // make sure we're not sending zero-sized chunks (=EOF)
1091     if (fileSize > 0) {
1092         // XXX using assign() here causes segfault, prolly because
1093         // gp_file_free is called before chunkData goes out of scope
1094         QByteArray chunkDataBuffer = QByteArray::fromRawData(
1095                     fileData + object->getFileSize(),
1096                     fileSize - object->getFileSize());
1097         // Note: this will fail with sizes > 16MB ...
1098         object->data(chunkDataBuffer);
1099         object->processedSize(fileSize);
1100         chunkDataBuffer.clear();
1101         object->setFileSize(fileSize);
1102     }
1103 }
1104 
frontendProgressStart(GPContext *,float totalsize,const char * status,void * data)1105 unsigned int frontendProgressStart(
1106     GPContext * /*context*/, float totalsize,
1107 #ifdef HAVE_GPHOTO2_5
1108     const char *status,
1109 #else
1110     const char *format, va_list args,
1111 #endif
1112     void *data
1113 ) {
1114     KameraProtocol *object = (KameraProtocol*)data;
1115 #ifndef HAVE_GPHOTO2_5
1116     char *status;
1117 
1118     /* We must copy the va_list to walk it twice, or all hell
1119      * breaks loose on non-i386 platforms.
1120      */
1121 #if defined(HAVE_VA_COPY) || defined(HAVE___VA_COPY)
1122     va_list xvalist;
1123 # ifdef HAVE_VA_COPY
1124     va_copy(xvalist, args);
1125 # elif HAVE___VA_COPY
1126     __va_copy(xvalist, args);
1127 # endif
1128     int size=vsnprintf(NULL, 0, format, xvalist);
1129     if(size<=0)
1130         return GP_OK; // vsnprintf is broken, better don't do anything.
1131 
1132     status=new char[size+1];
1133 # ifdef HAVE_VA_COPY
1134     va_copy(xvalist, args);
1135 # elif HAVE___VA_COPY
1136     __va_copy(xvalist, args);
1137 # endif
1138     vsnprintf(status, size+1, format, xvalist);
1139 #else
1140     /* We cannot copy the va_list, so make sure we
1141      * walk it just _once_.
1142      */
1143     status=new char[300];
1144     vsnprintf(status, 300, format, args);
1145 #endif
1146 
1147     object->infoMessage(QString::fromLocal8Bit(status));
1148     delete [] status;
1149 #else
1150     /* libgphoto2 2.5 has resolved this already, no need for print */
1151     object->infoMessage(QString::fromLocal8Bit(status));
1152 #endif
1153     object->totalSize((KIO::filesize_t)totalsize); // hack: call slot directly
1154     return GP_OK;
1155 }
1156 
1157 // this callback function is activated on every status message from gphoto2
frontendCameraStatus(GPContext *,const char * status,void * data)1158 static void frontendCameraStatus(
1159     GPContext * /*context*/,
1160 #ifdef HAVE_GPHOTO2_5
1161     const char *status,
1162 #else
1163     const char *format, va_list args,
1164 #endif
1165     void *data
1166 ) {
1167     KameraProtocol *object = (KameraProtocol*)data;
1168 #ifndef HAVE_GPHOTO2_5
1169     char *status;
1170 
1171     /* We must copy the va_list to walk it twice, or all hell
1172      * breaks loose on non-i386 platforms.
1173      */
1174 #if defined(HAVE_VA_COPY) || defined(HAVE___VA_COPY)
1175     va_list xvalist;
1176 # ifdef HAVE_VA_COPY
1177     va_copy(xvalist, args);
1178 # elif HAVE___VA_COPY
1179     __va_copy(xvalist, args);
1180 # endif
1181     int size=vsnprintf(NULL, 0, format, xvalist);
1182     if(size<=0)
1183         return; // vsnprintf is broken, better don't do anything.
1184 
1185     status=new char[size+1];
1186 # ifdef HAVE_VA_COPY
1187     va_copy(xvalist, args);
1188 # elif HAVE___VA_COPY
1189     __va_copy(xvalist, args);
1190 # endif
1191     vsnprintf(status, size+1, format, xvalist);
1192 #else
1193     /* We cannot copy the va_list, so make sure we
1194      * walk it just _once_.
1195      */
1196     status=new char[300];
1197     vsnprintf(status, 300, format, args);
1198 #endif
1199     object->infoMessage(QString::fromLocal8Bit(status));
1200     delete [] status;
1201 #else
1202     object->infoMessage(QString::fromLocal8Bit(status));
1203 #endif
1204 }
1205 
1206 #include "kamera.moc"
1207