File indexing completed on 2024-04-28 04:18:58

0001 /*
0002     SPDX-FileCopyrightText: 2001 The Kompany
0003     SPDX-FileCopyrightText: 2001-2003 Ilya Konstantinov <kde-devel@future.shiny.co.il>
0004     SPDX-FileCopyrightText: 2001-2008 Marcus Meissner <marcus@jet.franken.de>
0005     SPDX-FileCopyrightText: 2012 Marcus Meissner <marcus@jet.franken.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 // remove comment to enable debugging
0011 // #undef QT_NO_DEBUG
0012 #include "kamera.h"
0013 
0014 #include <cerrno>
0015 #include <csignal>
0016 #include <cstdio>
0017 #include <cstdlib>
0018 #include <fcntl.h>
0019 #include <sys/stat.h>
0020 #include <sys/types.h>
0021 #include <unistd.h>
0022 
0023 #include <QCoreApplication>
0024 #include <QStandardPaths>
0025 #include <QUrl>
0026 
0027 #include <KConfig>
0028 #include <KConfigGroup>
0029 #include <KLocalizedString>
0030 #include <KProtocolInfo>
0031 
0032 #include "kameralist.h"
0033 #include <kio_kamera_log.h>
0034 
0035 #define tocstr(x) ((x).toLocal8Bit())
0036 
0037 #define MAXIDLETIME 30 /* seconds */
0038 
0039 using namespace KIO;
0040 
0041 // Pseudo plugin class to embed meta data
0042 class KIOPluginForMetaData : public QObject
0043 {
0044     Q_OBJECT
0045     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.camera" FILE "camera.json")
0046 };
0047 
0048 extern "C" {
0049 Q_DECL_EXPORT int kdemain(int argc, char **argv);
0050 
0051 static void frontendCameraStatus(GPContext *context, const char *status, void *data);
0052 static unsigned int frontendProgressStart(GPContext *context, float totalsize, const char *status, void *data);
0053 static void frontendProgressUpdate(GPContext *context, unsigned int id, float current, void *data);
0054 }
0055 
0056 int kdemain(int argc, char **argv)
0057 {
0058     QCoreApplication app(argc, argv);
0059 
0060     QCoreApplication::setApplicationName(QStringLiteral("kio_kamera"));
0061     KLocalizedString::setApplicationDomain(QByteArrayLiteral("kio_kamera"));
0062 
0063 #ifdef DEBUG_KAMERA_KIO
0064     QLoggingCategory::setFilterRules(QStringLiteral("kf.kio.workers.camera.debug = true"));
0065 #endif
0066 
0067     if (argc != 4) {
0068         qCDebug(KIO_KAMERA_LOG) << "Usage: kio_kamera protocol "
0069                                    "domain-socket1 domain-socket2";
0070         exit(-1);
0071     }
0072 
0073     KameraProtocol slave(argv[2], argv[3]);
0074 
0075     slave.dispatchLoop();
0076 
0077     return 0;
0078 }
0079 
0080 static QString path_quote(QString path)
0081 {
0082     return path.replace(QStringLiteral("/"), QStringLiteral("%2F")).replace(QStringLiteral(" "), QStringLiteral("%20"));
0083 }
0084 static QString path_unquote(QString path)
0085 {
0086     return path.replace(QStringLiteral("%2F"), QStringLiteral("/")).replace(QStringLiteral("%20"), QStringLiteral(" "));
0087 }
0088 
0089 KameraProtocol::KameraProtocol(const QByteArray &pool, const QByteArray &app)
0090     : WorkerBase("camera", pool, app)
0091     , m_camera(nullptr)
0092 {
0093     // attempt to initialize libgphoto2 and chosen camera (requires locking)
0094     // (will init m_camera, since the m_camera's configuration is empty)
0095     m_camera = nullptr;
0096     m_file = nullptr;
0097     m_config = new KConfig(KProtocolInfo::config(QStringLiteral("camera")), KConfig::SimpleConfig);
0098     m_context = gp_context_new();
0099     actiondone = true;
0100     cameraopen = false;
0101     m_lockfile = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/kamera";
0102     idletime = 0;
0103 }
0104 
0105 // This handler is getting called every second. We use it to do the
0106 // delayed close of the camera.
0107 // Logic is:
0108 //  - No more requests in the queue (signaled by actiondone) AND
0109 //      - We are MAXIDLETIME seconds idle OR
0110 //      - Another slave wants to have access to the camera.
0111 //
0112 // The existence of a lockfile is used to signify "please give up camera".
0113 //
0114 KIO::WorkerResult KameraProtocol::special(const QByteArray &)
0115 {
0116     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::special() at " << getpid() << ". idletime: " << idletime;
0117 
0118     if (!actiondone && cameraopen) {
0119         struct stat stbuf;
0120         if ((-1 != ::stat(m_lockfile.toUtf8(), &stbuf)) || (idletime++ >= MAXIDLETIME)) {
0121             qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::special() closing camera.";
0122             closeCamera();
0123             setTimeoutSpecialCommand(-1);
0124         } else {
0125             // continue to wait
0126             setTimeoutSpecialCommand(1);
0127         }
0128     } else {
0129         // We let it run until the slave gets no actions anymore.
0130         setTimeoutSpecialCommand(1);
0131     }
0132     actiondone = false;
0133     return KIO::WorkerResult::pass();
0134 }
0135 
0136 KameraProtocol::~KameraProtocol()
0137 {
0138     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::~KameraProtocol()";
0139     delete m_config;
0140     if (m_camera) {
0141         closeCamera();
0142         gp_camera_free(m_camera);
0143         m_camera = nullptr;
0144     }
0145 }
0146 
0147 // initializes the camera for usage -
0148 // should be done before operations over the wire
0149 bool KameraProtocol::openCamera(QString &str)
0150 {
0151     idletime = 0;
0152     actiondone = true;
0153     if (!m_camera) {
0154         reparseConfiguration();
0155     } else {
0156         if (!cameraopen) {
0157             int ret, tries = 15;
0158             qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::openCamera at " << getpid();
0159             // Gets this far.
0160             while (tries--) {
0161                 ret = gp_camera_init(m_camera, m_context);
0162                 if ((ret == GP_ERROR_IO_USB_CLAIM) || (ret == GP_ERROR_IO_LOCK)) {
0163                     // just create / touch if not there
0164                     int fd = ::open(m_lockfile.toUtf8(), O_CREAT | O_WRONLY, 0600);
0165                     if (fd != -1)
0166                         ::close(fd);
0167                     ::sleep(1);
0168                     qCDebug(KIO_KAMERA_LOG) << "openCamera at " << getpid() << "- busy, ret " << ret << ", trying again.";
0169                     continue;
0170                 }
0171                 if (ret == GP_OK)
0172                     break;
0173                 str = gp_result_as_string(ret);
0174                 return false;
0175             }
0176             ::remove(m_lockfile.toUtf8());
0177             setTimeoutSpecialCommand(1);
0178             qCDebug(KIO_KAMERA_LOG) << "openCamera succeeded at " << getpid();
0179             cameraopen = true;
0180         }
0181     }
0182     return true;
0183 }
0184 
0185 // should be done after operations over the wire
0186 void KameraProtocol::closeCamera()
0187 {
0188     int gpr;
0189 
0190     if (!m_camera) {
0191         return;
0192     }
0193 
0194     if ((gpr = gp_camera_exit(m_camera, m_context)) != GP_OK) {
0195         qCDebug(KIO_KAMERA_LOG) << "closeCamera failed with " << gp_result_as_string(gpr);
0196     }
0197     // HACK: gp_camera_exit() in gp 2.0 does not close the port if there
0198     //       is no camera_exit function.
0199     gp_port_close(m_camera->port);
0200     cameraopen = false;
0201     current_camera = QStringLiteral("");
0202     current_port = QStringLiteral("");
0203     return;
0204 }
0205 
0206 static QString fix_foldername(const QString &ofolder)
0207 {
0208     QString folder = ofolder;
0209     if (folder.length() > 1) {
0210         while ((folder.length() > 1) && (folder.right(1) == QStringLiteral("/")))
0211             folder = folder.left(folder.length() - 1);
0212     }
0213     if (folder.length() == 0) {
0214         folder = QStringLiteral("/");
0215     }
0216     return folder;
0217 }
0218 
0219 // The KIO slave "get" function (starts a download from the camera)
0220 // The actual returning of the data is done in the frontend callback functions.
0221 KIO::WorkerResult KameraProtocol::get(const QUrl &url)
0222 {
0223     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::get(" << url.path() << ")";
0224     QString directory, file;
0225     CameraFileType fileType;
0226     int gpr;
0227 
0228     auto splitResult = split_url2camerapath(url.path(), directory, file);
0229 
0230     if (!splitResult.success()) {
0231         return splitResult;
0232     }
0233 
0234     if (!openCamera()) {
0235         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0236     }
0237 
0238 #define GPHOTO_TEXT_FILE(xx)                                                                                                                                   \
0239     if (!directory.compare(QStringLiteral("/")) && !file.compare(#xx ".txt")) {                                                                                \
0240         CameraText xx;                                                                                                                                         \
0241         gpr = gp_camera_get_##xx(m_camera, &xx, m_context);                                                                                                    \
0242         if (gpr != GP_OK) {                                                                                                                                    \
0243             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());                                                                               \
0244         }                                                                                                                                                      \
0245         QByteArray chunkDataBuffer = QByteArray::fromRawData(xx.text, strlen(xx.text));                                                                        \
0246         data(chunkDataBuffer);                                                                                                                                 \
0247         processedSize(strlen(xx.text));                                                                                                                        \
0248         chunkDataBuffer.clear();                                                                                                                               \
0249         return KIO::WorkerResult::pass();                                                                                                                      \
0250     }
0251 
0252     GPHOTO_TEXT_FILE(about);
0253     GPHOTO_TEXT_FILE(manual);
0254     GPHOTO_TEXT_FILE(summary);
0255 
0256 #undef GPHOTO_TEXT_FILE
0257     // Q_EMIT info message
0258     // WARNING Fix this
0259     // infoMessage( i18n("Retrieving data from camera <b>%1</b>", current_camera) );
0260 
0261     // Note: There's no need to re-read directory for each get() anymore
0262     gp_file_new(&m_file);
0263 
0264     // Q_EMIT the total size (we must do it before sending data to allow preview)
0265     CameraFileInfo info;
0266 
0267     gpr = gp_camera_file_get_info(m_camera, tocstr(fix_foldername(directory)), tocstr(file), &info, m_context);
0268     if (gpr != GP_OK) {
0269         gp_file_unref(m_file);
0270         if ((gpr == GP_ERROR_FILE_NOT_FOUND) || (gpr == GP_ERROR_DIRECTORY_NOT_FOUND)) {
0271             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0272         } else {
0273             return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr)));
0274         }
0275     }
0276 
0277     // at last, a proper API to determine whether a thumbnail was requested.
0278     if (cameraSupportsPreview() && metaData(QStringLiteral("thumbnail")) == QStringLiteral("1")) {
0279         qCDebug(KIO_KAMERA_LOG) << "get() retrieving the thumbnail";
0280         fileType = GP_FILE_TYPE_PREVIEW;
0281         if (info.preview.fields & GP_FILE_INFO_SIZE) {
0282             totalSize(info.preview.size);
0283         }
0284         if (info.preview.fields & GP_FILE_INFO_TYPE) {
0285             mimeType(info.preview.type);
0286         }
0287     } else {
0288         qCDebug(KIO_KAMERA_LOG) << "get() retrieving the full-scale photo";
0289         fileType = GP_FILE_TYPE_NORMAL;
0290         if (info.file.fields & GP_FILE_INFO_SIZE) {
0291             totalSize(info.file.size);
0292         }
0293         if (info.preview.fields & GP_FILE_INFO_TYPE) {
0294             mimeType(info.file.type);
0295         }
0296     }
0297 
0298     // fetch the data
0299     m_fileSize = 0;
0300     gpr = gp_camera_file_get(m_camera, tocstr(fix_foldername(directory)), tocstr(file), fileType, m_file, m_context);
0301     if ((gpr == GP_ERROR_NOT_SUPPORTED) && (fileType == GP_FILE_TYPE_PREVIEW)) {
0302         // If we get here, the file info command information
0303         // will either not be used, or still valid.
0304         fileType = GP_FILE_TYPE_NORMAL;
0305         gpr = gp_camera_file_get(m_camera, tocstr(fix_foldername(directory)), tocstr(file), fileType, m_file, m_context);
0306     }
0307     switch (gpr) {
0308     case GP_OK:
0309         break;
0310     case GP_ERROR_FILE_NOT_FOUND:
0311     case GP_ERROR_DIRECTORY_NOT_FOUND:
0312         gp_file_unref(m_file);
0313         m_file = nullptr;
0314         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.fileName());
0315     default:
0316         gp_file_unref(m_file);
0317         m_file = nullptr;
0318         return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr)));
0319     }
0320     // Q_EMIT the mimetype
0321     // NOTE: we must first get the file, so that CameraFile->name would be set
0322     const char *fileMimeType;
0323     gp_file_get_mime_type(m_file, &fileMimeType);
0324     mimeType(fileMimeType);
0325 
0326     // We need to pass left over data here. Some camera drivers do not
0327     // implement progress callbacks!
0328     const char *fileData;
0329     long unsigned int fileSize;
0330     // This merely returns us a pointer to gphoto's internal data
0331     // buffer -- there's no expensive memcpy
0332     gpr = gp_file_get_data_and_size(m_file, &fileData, &fileSize);
0333     if (gpr != GP_OK) {
0334         qCDebug(KIO_KAMERA_LOG) << "get():: get_data_and_size failed.";
0335         gp_file_free(m_file);
0336         m_file = nullptr;
0337         return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr)));
0338     }
0339     // make sure we're not sending zero-sized chunks (=EOF)
0340     // also make sure we send only if the progress did not send the data
0341     // already.
0342     if ((fileSize > 0) && (fileSize - m_fileSize) > 0) {
0343         unsigned long written = 0;
0344         QByteArray chunkDataBuffer;
0345 
0346         // We need to split it up here. Someone considered it funny
0347         // to discard any data() larger than 16MB.
0348         //
0349         // So nearly any Movie will just fail....
0350         while (written < fileSize - m_fileSize) {
0351             unsigned long towrite = 1024 * 1024; // 1MB
0352 
0353             if (towrite > fileSize - m_fileSize - written) {
0354                 towrite = fileSize - m_fileSize - written;
0355             }
0356             chunkDataBuffer = QByteArray::fromRawData(fileData + m_fileSize + written, towrite);
0357             processedSize(m_fileSize + written + towrite);
0358             data(chunkDataBuffer);
0359             chunkDataBuffer.clear();
0360             written += towrite;
0361         }
0362         m_fileSize = fileSize;
0363         setFileSize(fileSize);
0364     }
0365 
0366     gp_file_unref(m_file); /* just unref, might be stored in fs */
0367     m_file = nullptr;
0368     return KIO::WorkerResult::pass();
0369 }
0370 
0371 // The KIO slave "stat" function.
0372 KIO::WorkerResult KameraProtocol::stat(const QUrl &url)
0373 {
0374     qCDebug(KIO_KAMERA_LOG) << "stat(\"" << url.path() << "\")";
0375 
0376     if (url.path().isEmpty()) {
0377         QUrl rooturl(url);
0378 
0379         qCDebug(KIO_KAMERA_LOG) << "redirecting to /";
0380         rooturl.setPath(QStringLiteral("/"));
0381         redirection(rooturl);
0382         return KIO::WorkerResult::pass();
0383     }
0384 
0385     if (url.path() == QStringLiteral("/"))
0386         return statRoot();
0387     else
0388         return statRegular(url);
0389 
0390     return KIO::WorkerResult::pass();
0391 }
0392 
0393 // Implements stat("/") -- which always returns the same value.
0394 KIO::WorkerResult KameraProtocol::statRoot()
0395 {
0396     KIO::UDSEntry entry;
0397 
0398     entry.fastInsert(KIO::UDSEntry::UDS_NAME, QString::fromLocal8Bit("/"));
0399 
0400     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0401 
0402     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH));
0403     statEntry(entry);
0404     // If we just do this call, timeout right away if no other requests are
0405     // pending. This is for the kdemm autodetection using media://camera
0406     idletime = MAXIDLETIME;
0407 
0408     return KIO::WorkerResult::pass();
0409 }
0410 
0411 KIO::WorkerResult KameraProtocol::split_url2camerapath(const QString &url, QString &directory, QString &file)
0412 {
0413     QStringList components, camarr;
0414     QString cam, camera, port;
0415     KIO::WorkerResult result = KIO::WorkerResult::pass();
0416 
0417     components = url.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0418     if (components.size() == 0) {
0419         return KIO::WorkerResult::pass();
0420     }
0421     cam = path_unquote(components.takeFirst());
0422     if (!cam.isEmpty()) {
0423         if (!cam.contains(QLatin1Char('@'))) {
0424             return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url);
0425         }
0426 
0427         camarr = cam.split(QLatin1Char('@'));
0428         camera = path_unquote(camarr.takeFirst());
0429         port = path_unquote(camarr.takeLast());
0430         result = setCamera(camera, port);
0431     }
0432     if (components.isEmpty()) {
0433         directory = QStringLiteral("/");
0434         return result;
0435     }
0436 
0437     file = path_unquote(components.takeLast());
0438     directory = path_unquote(QStringLiteral("/") + components.join(QLatin1Char('/')));
0439 
0440     return result;
0441 }
0442 
0443 // Implements a regular stat() of a file / directory, returning all we know about it
0444 KIO::WorkerResult KameraProtocol::statRegular(const QUrl &xurl)
0445 {
0446     KIO::UDSEntry entry;
0447     QString directory, file;
0448     int gpr;
0449 
0450     qCDebug(KIO_KAMERA_LOG) << "statRegular(\"" << xurl.path() << "\")";
0451 
0452     auto splitResult = split_url2camerapath(xurl.path(), directory, file);
0453 
0454     if (!splitResult.success()) {
0455         return splitResult;
0456     }
0457 
0458     if (openCamera() == false) {
0459         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, xurl.path());
0460     }
0461 
0462     if (directory == QLatin1String("/")) {
0463         KIO::UDSEntry entry;
0464 
0465 #define GPHOTO_TEXT_FILE(xx)                                                                                                                                   \
0466     if (!file.compare(#xx ".txt")) {                                                                                                                           \
0467         CameraText xx;                                                                                                                                         \
0468         gpr = gp_camera_get_about(m_camera, &xx, m_context);                                                                                                   \
0469         if (gpr != GP_OK) {                                                                                                                                    \
0470             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, xurl.fileName());                                                                          \
0471         }                                                                                                                                                      \
0472         translateTextToUDS(entry, #xx ".txt", xx.text);                                                                                                        \
0473         statEntry(entry);                                                                                                                                      \
0474         return KIO::WorkerResult::pass();                                                                                                                      \
0475     }
0476         GPHOTO_TEXT_FILE(about);
0477         GPHOTO_TEXT_FILE(manual);
0478         GPHOTO_TEXT_FILE(summary);
0479 #undef GPHOTO_TEXT_FILE
0480 
0481         QString xname = current_camera + QLatin1Char('@') + current_port;
0482         entry.fastInsert(KIO::UDSEntry::UDS_NAME, path_quote(xname));
0483         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, current_camera);
0484         entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0485         entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH));
0486         statEntry(entry);
0487         return KIO::WorkerResult::pass();
0488     }
0489 
0490     // Is "url" a directory?
0491     KameraList dirList;
0492     qCDebug(KIO_KAMERA_LOG) << "statRegular() Requesting directories list for " << directory;
0493 
0494     gpr = gp_camera_folder_list_folders(m_camera, tocstr(fix_foldername(directory)), dirList, m_context);
0495     if (gpr != GP_OK) {
0496         if ((gpr == GP_ERROR_FILE_NOT_FOUND) || (gpr == GP_ERROR_DIRECTORY_NOT_FOUND)) {
0497             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, xurl.path());
0498         } else {
0499             return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr)));
0500         }
0501     }
0502 
0503     const char *name;
0504     for (int i = 0; i < gp_list_count(dirList); i++) {
0505         gp_list_get_name(dirList, i, &name);
0506         if (file.compare(name) == 0) {
0507             KIO::UDSEntry entry;
0508             translateDirectoryToUDS(entry, file);
0509             statEntry(entry);
0510             return KIO::WorkerResult::pass();
0511         }
0512     }
0513 
0514     // Is "url" a file?
0515     CameraFileInfo info;
0516     gpr = gp_camera_file_get_info(m_camera, tocstr(fix_foldername(directory)), tocstr(file), &info, m_context);
0517     if (gpr != GP_OK) {
0518         if ((gpr == GP_ERROR_FILE_NOT_FOUND) || (gpr == GP_ERROR_DIRECTORY_NOT_FOUND)) {
0519             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, xurl.path());
0520         } else {
0521             return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr)));
0522         }
0523     }
0524     translateFileToUDS(entry, info, file);
0525     statEntry(entry);
0526     return KIO::WorkerResult::pass();
0527 }
0528 
0529 // The KIO slave "del" function.
0530 KIO::WorkerResult KameraProtocol::del(const QUrl &url, bool isFile)
0531 {
0532     QString directory, file;
0533     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::del(" << url.path() << ")";
0534 
0535     auto splitResult = split_url2camerapath(url.path(), directory, file);
0536 
0537     if (!splitResult.success()) {
0538         return splitResult;
0539     }
0540 
0541     if (!openCamera()) {
0542         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_DELETE, file);
0543     }
0544     if (!cameraSupportsDel()) {
0545         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_DELETE, file);
0546     }
0547 
0548     if (isFile) {
0549         int ret = gp_camera_file_delete(m_camera, tocstr(fix_foldername(directory)), tocstr(file), m_context);
0550 
0551         if (ret != GP_OK) {
0552             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_DELETE, file);
0553         } else {
0554             return KIO::WorkerResult::pass();
0555         }
0556     }
0557 
0558     return KIO::WorkerResult::pass();
0559 }
0560 
0561 // The KIO slave "listDir" function.
0562 KIO::WorkerResult KameraProtocol::listDir(const QUrl &yurl)
0563 {
0564     QString directory, file;
0565     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::listDir(" << yurl.path() << ")";
0566 
0567     auto splitResult = split_url2camerapath(yurl.path(), directory, file);
0568 
0569     if (!splitResult.success()) {
0570         return splitResult;
0571     }
0572 
0573     if (!file.isEmpty()) {
0574         if (directory == QLatin1Char('/')) {
0575             directory = QLatin1Char('/') + file;
0576         } else {
0577             directory = directory + QLatin1Char('/') + file;
0578         }
0579     }
0580 
0581     if (yurl.path() == QLatin1Char('/')) {
0582         QUrl xurl;
0583         // List the available cameras
0584         QStringList groupList = m_config->groupList();
0585         qCDebug(KIO_KAMERA_LOG) << "Found cameras: " << groupList.join(QStringLiteral(", "));
0586         QStringList::Iterator it;
0587         KIO::UDSEntry entry;
0588 
0589         /*
0590          * What we do:
0591          * - Autodetect cameras and remember them with their ports.
0592          * - List all saved and possible offline cameras.
0593          * - List all autodetected and not yet printed cameras.
0594          */
0595         QMap<QString, QString> ports, names;
0596         QMap<QString, int> modelcnt;
0597 
0598         /* Autodetect USB cameras ... */
0599         GPContext *glob_context = nullptr;
0600         int i, count;
0601         KameraList list;
0602         CameraAbilitiesList *al;
0603         GPPortInfoList *il;
0604 
0605         gp_abilities_list_new(&al);
0606         gp_abilities_list_load(al, glob_context);
0607         gp_port_info_list_new(&il);
0608         gp_port_info_list_load(il);
0609         gp_abilities_list_detect(al, il, list, glob_context);
0610         gp_abilities_list_free(al);
0611         gp_port_info_list_free(il);
0612 
0613         count = gp_list_count(list);
0614 
0615         for (i = 0; i < count; i++) {
0616             const char *model, *value;
0617 
0618             gp_list_get_name(list, i, &model);
0619             gp_list_get_value(list, i, &value);
0620 
0621             ports[value] = model;
0622             // NOTE: We might get different ports than usb: later!
0623             if (strcmp(value, "usb:") != 0) {
0624                 names[model] = value;
0625             }
0626 
0627             /* Save them, even though we can autodetect them for
0628              * offline listing.
0629              */
0630 #if 0
0631             KConfigGroup cg(m_config, model);
0632             cg.writeEntry("Model", model);
0633             cg.writeEntry("Path", value);
0634 #endif
0635             modelcnt[model]++;
0636         }
0637 
0638         /* Avoid duplicated entry, that is a camera with both
0639          * port usb: and usb:001,042 entries. */
0640         if (ports.contains(QStringLiteral("usb:")) && names.contains(ports[QStringLiteral("usb:")])
0641             && names[ports[QStringLiteral("usb:")]] != QStringLiteral("usb:")) {
0642             ports.remove(QStringLiteral("usb:"));
0643         }
0644 
0645         for (it = groupList.begin(); it != groupList.end(); it++) {
0646             QString m_cfgPath;
0647             if (*it == QStringLiteral("<default>")) {
0648                 continue;
0649             }
0650 
0651             KConfigGroup cg(m_config, *it);
0652             m_cfgPath = cg.readEntry("Path");
0653 
0654             // we autodetected those ...
0655             if (m_cfgPath.contains(QLatin1String("usb:"))) {
0656                 cg.deleteGroup();
0657                 continue;
0658             }
0659 
0660             QString xname;
0661 
0662             entry.clear();
0663             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0664             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH));
0665             xname = (*it) + '@' + m_cfgPath;
0666             entry.fastInsert(KIO::UDSEntry::UDS_NAME, path_quote(xname));
0667             // do not confuse regular users with the @usb...
0668             entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, *it);
0669             listEntry(entry);
0670         }
0671 
0672         QMap<QString, QString>::iterator portsit;
0673 
0674         for (portsit = ports.begin(); portsit != ports.end(); portsit++) {
0675             entry.clear();
0676             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0677             // do not confuse regular users with the @usb...
0678             entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, portsit.value());
0679             entry.fastInsert(KIO::UDSEntry::UDS_NAME, path_quote(portsit.value() + QLatin1Char('@') + portsit.key()));
0680 
0681             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH));
0682             listEntry(entry);
0683         }
0684         return KIO::WorkerResult::pass();
0685     }
0686 
0687     if (directory.isEmpty()) {
0688         QUrl rooturl(yurl);
0689 
0690         qCDebug(KIO_KAMERA_LOG) << "redirecting to /";
0691         if (!current_camera.isEmpty() && !current_port.isEmpty()) {
0692             rooturl.setPath(QLatin1Char('/') + current_camera + QLatin1Char('@') + current_port + QLatin1Char('/'));
0693         } else {
0694             rooturl.setPath(QStringLiteral("/"));
0695         }
0696         redirection(rooturl);
0697         return KIO::WorkerResult::pass();
0698     }
0699 
0700     if (!openCamera()) {
0701         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_READ, yurl.path());
0702     }
0703 
0704     KameraList dirList;
0705     KameraList fileList;
0706     KameraList specialList;
0707     int gpr;
0708 
0709     if (!directory.compare(QStringLiteral("/"))) {
0710         CameraText text;
0711         if (GP_OK == gp_camera_get_manual(m_camera, &text, m_context)) {
0712             gp_list_append(specialList, "manual.txt", nullptr);
0713         }
0714         if (GP_OK == gp_camera_get_about(m_camera, &text, m_context)) {
0715             gp_list_append(specialList, "about.txt", nullptr);
0716         }
0717         if (GP_OK == gp_camera_get_summary(m_camera, &text, m_context)) {
0718             gp_list_append(specialList, "summary.txt", nullptr);
0719         }
0720     }
0721 
0722     gpr = readCameraFolder(directory, dirList, fileList);
0723     if (gpr != GP_OK) {
0724         qCDebug(KIO_KAMERA_LOG) << "read Camera Folder failed:" << gp_result_as_string(gpr);
0725         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Could not read. Reason: %1", QString::fromLocal8Bit(gp_result_as_string(gpr))));
0726     }
0727 
0728     totalSize(gp_list_count(specialList) + gp_list_count(dirList) + gp_list_count(fileList));
0729 
0730     KIO::UDSEntry entry;
0731     const char *name;
0732 
0733     for (int i = 0; i < gp_list_count(dirList); ++i) {
0734         gp_list_get_name(dirList, i, &name);
0735         translateDirectoryToUDS(entry, QString::fromLocal8Bit(name));
0736         listEntry(entry);
0737     }
0738 
0739     CameraFileInfo info;
0740 
0741     for (int i = 0; i < gp_list_count(fileList); ++i) {
0742         gp_list_get_name(fileList, i, &name);
0743         // we want to know more info about files (size, type...)
0744         gp_camera_file_get_info(m_camera, tocstr(directory), name, &info, m_context);
0745         translateFileToUDS(entry, info, QString::fromLocal8Bit(name));
0746         listEntry(entry);
0747     }
0748     if (!directory.compare(QStringLiteral("/"))) {
0749         CameraText text;
0750         if (GP_OK == gp_camera_get_manual(m_camera, &text, m_context)) {
0751             translateTextToUDS(entry, QStringLiteral("manual.txt"), text.text);
0752             listEntry(entry);
0753         }
0754         if (GP_OK == gp_camera_get_about(m_camera, &text, m_context)) {
0755             translateTextToUDS(entry, QStringLiteral("about.txt"), text.text);
0756             listEntry(entry);
0757         }
0758         if (GP_OK == gp_camera_get_summary(m_camera, &text, m_context)) {
0759             translateTextToUDS(entry, QStringLiteral("summary.txt"), text.text);
0760             listEntry(entry);
0761         }
0762     }
0763 
0764     return KIO::WorkerResult::pass();
0765 }
0766 
0767 KIO::WorkerResult KameraProtocol::setCamera(const QString &camera, const QString &port)
0768 {
0769     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::setCamera(" << camera << ", " << port << ")";
0770     int gpr, idx;
0771 
0772     if (!camera.isEmpty() && !port.isEmpty()) {
0773         if (m_camera && (current_camera == camera) && (current_port == port)) {
0774             qCDebug(KIO_KAMERA_LOG) << "Configuration is same, nothing to do.";
0775             return KIO::WorkerResult::pass();
0776         }
0777         if (m_camera) {
0778             qCDebug(KIO_KAMERA_LOG) << "Configuration change detected";
0779             closeCamera();
0780             gp_camera_unref(m_camera);
0781             m_camera = nullptr;
0782             // WARNING Fix this
0783             // infoMessage( i18n("Reinitializing camera") );
0784         } else {
0785             qCDebug(KIO_KAMERA_LOG) << "Initializing camera";
0786             // WARNING Fix this
0787             // infoMessage( i18n("Initializing camera") );
0788         }
0789         // fetch abilities
0790         CameraAbilitiesList *abilities_list;
0791         gp_abilities_list_new(&abilities_list);
0792         gp_abilities_list_load(abilities_list, m_context);
0793         idx = gp_abilities_list_lookup_model(abilities_list, tocstr(camera));
0794         if (idx < 0) {
0795             gp_abilities_list_free(abilities_list);
0796             qCDebug(KIO_KAMERA_LOG) << "Unable to get abilities for model: " << camera;
0797             return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(idx)));
0798         }
0799         gp_abilities_list_get_abilities(abilities_list, idx, &m_abilities);
0800         gp_abilities_list_free(abilities_list);
0801 
0802         // fetch port
0803         GPPortInfoList *port_info_list;
0804         GPPortInfo port_info;
0805         gp_port_info_list_new(&port_info_list);
0806         gp_port_info_list_load(port_info_list);
0807         idx = gp_port_info_list_lookup_path(port_info_list, tocstr(port));
0808 
0809         /* Handle erronously passed usb:XXX,YYY */
0810         if ((idx < 0) && port.startsWith(QStringLiteral("usb:"))) {
0811             idx = gp_port_info_list_lookup_path(port_info_list, "usb:");
0812         }
0813         if (idx < 0) {
0814             gp_port_info_list_free(port_info_list);
0815             qCDebug(KIO_KAMERA_LOG) << "Unable to get port info for path: " << port;
0816             return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(idx)));
0817         }
0818         gp_port_info_list_get_info(port_info_list, idx, &port_info);
0819 
0820         current_camera = camera;
0821         current_port = port;
0822         // create a new camera object
0823         gpr = gp_camera_new(&m_camera);
0824         if (gpr != GP_OK) {
0825             gp_port_info_list_free(port_info_list);
0826             return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr)));
0827         }
0828 
0829         // register gphoto2 callback functions
0830         gp_context_set_status_func(m_context, frontendCameraStatus, this);
0831         gp_context_set_progress_funcs(m_context, frontendProgressStart, frontendProgressUpdate, nullptr, this);
0832         // gp_camera_set_message_func(m_camera, ..., this)
0833 
0834         // set model and port
0835         gp_camera_set_abilities(m_camera, m_abilities);
0836         gp_camera_set_port_info(m_camera, port_info);
0837         gp_camera_set_port_speed(m_camera, 0); // TODO: the value needs to be configurable
0838         qCDebug(KIO_KAMERA_LOG) << "Opening camera model " << camera << " at " << port;
0839 
0840         gp_port_info_list_free(port_info_list);
0841 
0842         QString errstr;
0843         if (!openCamera(errstr)) {
0844             if (m_camera) {
0845                 gp_camera_unref(m_camera);
0846             }
0847             m_camera = nullptr;
0848             qCDebug(KIO_KAMERA_LOG) << "Unable to init camera: " << errstr;
0849             return KIO::WorkerResult::fail(KIO::ERR_SERVICE_NOT_AVAILABLE, errstr);
0850         }
0851     }
0852 
0853     return KIO::WorkerResult::pass();
0854 }
0855 
0856 void KameraProtocol::reparseConfiguration()
0857 {
0858     // we have no global config, do we?
0859 }
0860 
0861 // translate a simple text to a UDS entry
0862 void KameraProtocol::translateTextToUDS(KIO::UDSEntry &udsEntry, const QString &fn, const char *text)
0863 {
0864     udsEntry.clear();
0865     udsEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
0866     udsEntry.fastInsert(KIO::UDSEntry::UDS_NAME, path_quote(fn));
0867     udsEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, fn);
0868     udsEntry.fastInsert(KIO::UDSEntry::UDS_SIZE, strlen(text));
0869     udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH));
0870     udsEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("text/plain"));
0871 }
0872 
0873 // translate a CameraFileInfo to a UDSFieldType
0874 // which we can return as a directory listing entry
0875 void KameraProtocol::translateFileToUDS(KIO::UDSEntry &udsEntry, const CameraFileInfo &info, const QString &name)
0876 {
0877     udsEntry.clear();
0878 
0879     udsEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
0880     udsEntry.fastInsert(KIO::UDSEntry::UDS_NAME, path_quote(name));
0881     udsEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, name);
0882 
0883     if (info.file.fields & GP_FILE_INFO_SIZE) {
0884         udsEntry.fastInsert(KIO::UDSEntry::UDS_SIZE, info.file.size);
0885     }
0886 
0887     if (info.file.fields & GP_FILE_INFO_MTIME) {
0888         udsEntry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, info.file.mtime);
0889     } else {
0890         udsEntry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, time(nullptr));
0891     }
0892 
0893     if (info.file.fields & GP_FILE_INFO_TYPE) {
0894         udsEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1(info.file.type));
0895     }
0896 
0897     if (info.file.fields & GP_FILE_INFO_PERMISSIONS) {
0898         udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS, ((info.file.permissions & GP_FILE_PERM_READ) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0));
0899     } else {
0900         udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH);
0901     }
0902 
0903     // TODO: We do not handle info.preview in any way
0904 }
0905 
0906 // translate a directory name to a UDSFieldType
0907 // which we can return as a directory listing entry
0908 void KameraProtocol::translateDirectoryToUDS(KIO::UDSEntry &udsEntry, const QString &dirname)
0909 {
0910     udsEntry.clear();
0911 
0912     udsEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0913     udsEntry.fastInsert(KIO::UDSEntry::UDS_NAME, path_quote(dirname));
0914     udsEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, dirname);
0915     udsEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH | S_IXUSR | S_IXOTH | S_IXGRP);
0916     udsEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
0917 }
0918 
0919 bool KameraProtocol::cameraSupportsDel()
0920 {
0921     return (m_abilities.file_operations & GP_FILE_OPERATION_DELETE);
0922 }
0923 
0924 bool KameraProtocol::cameraSupportsPut()
0925 {
0926     return (m_abilities.folder_operations & GP_FOLDER_OPERATION_PUT_FILE);
0927 }
0928 
0929 bool KameraProtocol::cameraSupportsPreview()
0930 {
0931     return (m_abilities.file_operations & GP_FILE_OPERATION_PREVIEW);
0932 }
0933 
0934 int KameraProtocol::readCameraFolder(const QString &folder, CameraList *dirList, CameraList *fileList)
0935 {
0936     qCDebug(KIO_KAMERA_LOG) << "KameraProtocol::readCameraFolder(" << folder << ")";
0937 
0938     int gpr;
0939     if ((gpr = gp_camera_folder_list_folders(m_camera, tocstr(folder), dirList, m_context)) != GP_OK) {
0940         return gpr;
0941     }
0942     if ((gpr = gp_camera_folder_list_files(m_camera, tocstr(folder), fileList, m_context)) != GP_OK) {
0943         return gpr;
0944     }
0945     return GP_OK;
0946 }
0947 
0948 void frontendProgressUpdate(GPContext * /*context*/, unsigned int /*id*/, float /*current*/, void *data)
0949 {
0950     auto object = (KameraProtocol *)data;
0951 
0952     // This code will get the last chunk of data retrieved from the
0953     // camera and pass it to KIO, to allow progressive display
0954     // of the downloaded photo.
0955 
0956     const char *fileData = nullptr;
0957     long unsigned int fileSize = 0;
0958 
0959     // This merely returns us a pointer to gphoto's internal data
0960     // buffer -- there's no expensive memcpy
0961     if (!object->getFile()) {
0962         return;
0963     }
0964     gp_file_get_data_and_size(object->getFile(), &fileData, &fileSize);
0965     // make sure we're not sending zero-sized chunks (=EOF)
0966     if (fileSize > 0) {
0967         // XXX using assign() here causes segfault, prolly because
0968         // gp_file_free is called before chunkData goes out of scope
0969         QByteArray chunkDataBuffer = QByteArray::fromRawData(fileData + object->getFileSize(), fileSize - object->getFileSize());
0970         // Note: this will fail with sizes > 16MB ...
0971         object->data(chunkDataBuffer);
0972         object->processedSize(fileSize);
0973         chunkDataBuffer.clear();
0974         object->setFileSize(fileSize);
0975     }
0976 }
0977 
0978 unsigned int frontendProgressStart(GPContext * /*context*/, float totalsize, const char *status, void *data)
0979 {
0980     auto object = (KameraProtocol *)data;
0981     /* libgphoto2 2.5 has resolved this already, no need for print */
0982     object->infoMessage(QString::fromLocal8Bit(status));
0983     object->totalSize((KIO::filesize_t)totalsize); // hack: call slot directly
0984     return GP_OK;
0985 }
0986 
0987 // this callback function is activated on every status message from gphoto2
0988 static void frontendCameraStatus(GPContext * /*context*/, const char *status, void *data)
0989 {
0990     auto object = (KameraProtocol *)data;
0991     object->infoMessage(QString::fromLocal8Bit(status));
0992 }
0993 
0994 #include "kamera.moc"