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