File indexing completed on 2024-05-05 08:25:47
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"