From 47dfaaa40f5ff3a719e9d2108d3355e1c0199d5b Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Wed, 14 Jul 2021 22:05:06 +0300 Subject: [PATCH] kioslave: import camera KIO slave from kde-playground Signed-off-by: Ivailo Monev --- CMakeLists.txt | 28 +- appveyor.yml | 2 +- kioslave/CMakeLists.txt | 11 +- kioslave/camera/CMakeLists.txt | 20 + kioslave/camera/camera.protocol | 16 + kioslave/camera/kio_camera.cpp | 1003 +++++++++++++++++++++++++++++++++++++++ kioslave/camera/kio_camera.h | 80 ++++ 7 files changed, 1145 insertions(+), 15 deletions(-) create mode 100644 kioslave/camera/CMakeLists.txt create mode 100644 kioslave/camera/camera.protocol create mode 100644 kioslave/camera/kio_camera.cpp create mode 100644 kioslave/camera/kio_camera.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e186209d..b0bc89fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,19 +81,19 @@ if(Q_WS_X11) add_feature_info("libxf86vmode" X11_xf86vmode_FOUND "The X video mode extension library used by powerdevil") if(NOT X11_Xdamage_FOUND) - message(FATAL_ERROR "The X11 damaged region extension library was not found. Required for compositing support in KWin.") + message(FATAL_ERROR "The X11 damaged region extension library was not found. Required for compositing support in KWin") endif() if(NOT X11_Xrender_FOUND) - message(FATAL_ERROR " The X Rendering Extension client library was not found. Required for XRender Compositing backend in KWin.") + message(FATAL_ERROR " The X Rendering Extension client library was not found. Required for XRender Compositing backend in KWin") endif() if(NOT X11_Xfixes_FOUND) - message(FATAL_ERROR "The X11 miscellaneous 'fixes' extension library was not found. Required for XRender Compositing backend in KWin.") + message(FATAL_ERROR "The X11 miscellaneous 'fixes' extension library was not found. Required for XRender Compositing backend in KWin") endif() if(NOT X11_Xrandr_FOUND) - message(FATAL_ERROR "The X11 RandR extension library was not found. Required for Multi Screen Support.") + message(FATAL_ERROR "The X11 RandR extension library was not found. Required for Multi Screen Support") endif() if(NOT X11_Xcursor_FOUND) - message(FATAL_ERROR "The X11 cursor management library was not found. Required for desktop effects support in KWin.") + message(FATAL_ERROR "The X11 cursor management library was not found. Required for desktop effects support in KWin") endif() endif(Q_WS_X11) @@ -181,7 +181,7 @@ set_package_properties(LibUSB PROPERTIES DESCRIPTION "User level access to USB devices" URL "http://libusb.sourceforge.net" TYPE OPTIONAL - PURPOSE "Provides Logitech mouse support in KControl." + PURPOSE "Provides Logitech mouse support in KControl" ) macro_optional_find_package(PCIUTILS) @@ -189,7 +189,7 @@ set_package_properties(PCIUTILS PROPERTIES DESCRIPTION "PciUtils is a library for direct access to PCI slots" URL "http://atrey.karlin.mff.cuni.cz/~mj/pciutils.shtml" TYPE OPTIONAL - PURPOSE "View PCI details in kinfocenter." + PURPOSE "View PCI details in kinfocenter" ) macro_optional_find_package(RAW1394) @@ -197,7 +197,7 @@ set_package_properties(RAW1394 PROPERTIES DESCRIPTION "library for direct access to IEEE 1394 bus" URL "http://www.linux1394.org/" TYPE OPTIONAL - PURPOSE "View FireWire devices in kinfocenter." + PURPOSE "View FireWire devices in kinfocenter" ) # we need a version of samba which has already smbc_set_context(), Alex @@ -227,12 +227,20 @@ set_package_properties(Mtp PROPERTIES PURPOSE "Needed to build the MTP kioslave" ) +macro_optional_find_package(Gphoto2 2.5) +set_package_properties(Gphoto2 PROPERTIES + DESCRIPTION "Free, redistributable, ready to use set of digital camera software applications" + URL "http://www.gphoto.org/" + TYPE OPTIONAL + PURPOSE "Needed to build camera kioslave" +) + macro_optional_find_package(BZip2) set_package_properties(BZip2 PROPERTIES DESCRIPTION "A high-quality data compressor" URL "http://www.bzip.org" TYPE OPTIONAL - PURPOSE "Provides the ability to read and write bzip2 compressed data files in the filter kioslave." + PURPOSE "Provides the ability to read and write bzip2 compressed data files in the filter kioslaves" ) macro_optional_find_package(LibLZMA) @@ -240,7 +248,7 @@ set_package_properties(LibLZMA PROPERTIES DESCRIPTION "A very high compression ratio data compressor" URL "http://tukaani.org/xz/" TYPE OPTIONAL - PURPOSE "Provides the ability to read and write xz compressed data files." + PURPOSE "Provides the ability to read and write xz compressed data files" ) macro_optional_find_package(OpenEXR) diff --git a/appveyor.yml b/appveyor.yml index 2ae4896c..283e9fba 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,7 @@ build_script: libgps-dev libusb-1.0-0-dev libssh-dev libsmbclient-dev perl-base \ libdrm-dev libraw1394-dev libsensors4-dev libgles2-mesa-dev libpam0g-dev \ libpci-dev libopenexr-dev liblzma-dev libbz2-dev libjpeg-dev \ - libdbusmenu-katie ccache + libgphoto2-dev libdbusmenu-katie ccache export PATH="/usr/lib/ccache/:$PATH" diff --git a/kioslave/CMakeLists.txt b/kioslave/CMakeLists.txt index e548019f..e69ccacd 100644 --- a/kioslave/CMakeLists.txt +++ b/kioslave/CMakeLists.txt @@ -9,11 +9,14 @@ add_subdirectory( remote ) add_subdirectory( desktop ) add_subdirectory( recentdocuments ) add_subdirectory( thumbnail ) -if (LIBSSH_FOUND) - add_subdirectory(sftp) +if(LIBSSH_FOUND) + add_subdirectory(sftp) endif() -if (MTP_FOUND) - add_subdirectory(mtp) +if(MTP_FOUND) + add_subdirectory(mtp) +endif() +if(GPHOTO2_FOUND) + add_subdirectory(camera) endif() add_subdirectory( floppy ) diff --git a/kioslave/camera/CMakeLists.txt b/kioslave/camera/CMakeLists.txt new file mode 100644 index 00000000..351c0984 --- /dev/null +++ b/kioslave/camera/CMakeLists.txt @@ -0,0 +1,20 @@ +include_directories( + ${GPHOTO2_INCLUDE_DIR} +) + +kde4_add_plugin(kio_camera kio_camera.cpp) + +target_link_libraries(kio_camera + ${KDE4_KIO_LIBS} + ${GPHOTO2_LIBRARIES} +) + +install( + TARGETS kio_camera + DESTINATION ${KDE4_PLUGIN_INSTALL_DIR} +) + +install( + FILES camera.protocol + DESTINATION ${KDE4_SERVICES_INSTALL_DIR} +) diff --git a/kioslave/camera/camera.protocol b/kioslave/camera/camera.protocol new file mode 100644 index 00000000..b6562e5b --- /dev/null +++ b/kioslave/camera/camera.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_camera +protocol=camera +input=none +output=filesystem +listing=Name,Type +reading=true +writing=false +deleting=true +source=true +makedir=false +linking=false +moving=false +Icon=camera-photo +maxInstances=1 +Class=:local diff --git a/kioslave/camera/kio_camera.cpp b/kioslave/camera/kio_camera.cpp new file mode 100644 index 00000000..c5a54288 --- /dev/null +++ b/kioslave/camera/kio_camera.cpp @@ -0,0 +1,1003 @@ +/* + + Copyright (C) 2001 The Kompany + 2001-2003 Ilya Konstantinov + 2001-2008 Marcus Meissner + 2012 Marcus Meissner + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +// remove comment to enable debugging +// #undef QT_NO_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kio_camera.h" + +#define tocstr(x) ((x).toLocal8Bit()) + +#define MAXIDLETIME 30 /* seconds */ + +using namespace KIO; + +extern "C" +{ + Q_DECL_EXPORT int kdemain(int argc, char **argv); + + static void frontendCameraStatus(GPContext *context, const char *status, void *data); + static unsigned int frontendProgressStart(GPContext *context, float totalsize, const char *status, void *data); + static void frontendProgressUpdate(GPContext *context, unsigned int id, float current, void *data); +} + +int kdemain(int argc, char **argv) +{ + KComponentData componentData("kio_kamera"); + + if (argc != 4) { + kDebug(7123) << "Usage: kio_kamera protocol " + "domain-socket1 domain-socket2" << endl; + exit(-1); + } + + KameraProtocol slave(argv[2], argv[3]); + + slave.dispatchLoop(); + + return 0; +} + +static QString path_quote(QString path) { return path.replace("/","%2F").replace(" ","%20"); } +static QString path_unquote(QString path) { return path.replace("%2F","/").replace("%20"," "); } + +KameraProtocol::KameraProtocol(const QByteArray &pool, const QByteArray &app) + : SlaveBase("camera", pool, app), + m_camera(NULL) +{ + // attempt to initialize libgphoto2 and chosen camera (requires locking) + // (will init m_camera, since the m_camera's configuration is empty) + m_camera = 0; + m_file = NULL; + m_config = new KConfig(KProtocolInfo::config("camera"), KConfig::SimpleConfig); + m_context = gp_context_new(); + actiondone = true; + cameraopen = false; + m_lockfile = KStandardDirs::locateLocal("tmp", "kamera"); + idletime = 0; +} + +// This handler is getting called every second. We use it to do the +// delayed close of the camera. +// Logic is: +// - No more requests in the queue (signaled by actiondone) AND +// - We are MAXIDLETIME seconds idle OR +// - Another slave wants to have access to the camera. +// +// The existence of a lockfile is used to signify "please give up camera". +// +void KameraProtocol::special(const QByteArray&) { + kDebug(7123) << "KameraProtocol::special() at " << getpid(); + + if (!actiondone && cameraopen) { + struct stat stbuf; + if (::stat(m_lockfile.toUtf8(), &stbuf) != -1 || idletime++ >= MAXIDLETIME) { + kDebug(7123) << "KameraProtocol::special() closing camera."; + closeCamera(); + setTimeoutSpecialCommand(-1); + } else { + // continue to wait + setTimeoutSpecialCommand(1); + } + } else { + // We let it run until the slave gets no actions anymore. + setTimeoutSpecialCommand(1); + } + actiondone = false; +} + +KameraProtocol::~KameraProtocol() +{ + kDebug(7123) << "KameraProtocol::~KameraProtocol()"; + delete m_config; + if (m_camera) { + closeCamera(); + gp_camera_free(m_camera); + m_camera = NULL; + } +} + +// initializes the camera for usage - should be done before operations over the wire +bool KameraProtocol::openCamera(QString &str) { + idletime = 0; + actiondone = true; + if (!m_camera) { + reparseConfiguration(); + } else { + if (!cameraopen) { + int gpr, tries = 15; + kDebug(7123) << "KameraProtocol::openCamera at " << getpid(); + while (tries--) { + gpr = gp_camera_init(m_camera, m_context); + if (gpr == GP_ERROR_IO_USB_CLAIM || gpr == GP_ERROR_IO_LOCK) { + // just create / touch if not there + int fd = ::open(m_lockfile.toUtf8(), O_CREAT|O_WRONLY, 0600); + if (fd != -1) { + ::close(fd); + } + ::sleep(1); + kDebug(7123) << "openCamera at " << ::getpid() << "- busy, ret " << gpr << ", trying again."; + continue; + } + if (gpr == GP_OK) { + break; + } + str = gp_result_as_string(gpr); + return false; + } + ::unlink(m_lockfile.toUtf8()); + setTimeoutSpecialCommand(1); + kDebug(7123) << "openCamera succeeded at " << getpid(); + cameraopen = true; + } + } + return true; +} + +// should be done after operations over the wire +void KameraProtocol::closeCamera(void) +{ + int gpr; + + if (!m_camera) { + return; + } + + kDebug(7123) << "KameraProtocol::closeCamera at " << getpid(); + if (gpr = gp_camera_exit(m_camera,m_context) != GP_OK) { + kDebug(7123) << "closeCamera failed with " << gp_result_as_string(gpr); + } + // HACK: gp_camera_exit() in gp 2.0 does not close the port if there + // is no camera_exit function. + gp_port_close(m_camera->port); + cameraopen = false; + current_camera = ""; + current_port = ""; + return; +} + +static QString fix_foldername(QString ofolder) { + QString folder = ofolder; + if (folder.length() > 1) { + while (folder.length() > 1 && folder.right(1) == "/") { + folder = folder.left(folder.length()-1); + } + } + if (folder.length() == 0) { + folder = "/"; + } + return folder; +} + +// The KIO slave "get" function (starts a download from the camera) +// The actual returning of the data is done in the frontend callback functions. +void KameraProtocol::get(const KUrl &url) +{ + kDebug(7123) << "KameraProtocol::get(" << url.path() << ")"; + QString directory, file; + CameraFileType fileType; + int gpr; + + split_url2camerapath(url.path(), directory, file); + if (!openCamera()) { + error(KIO::ERR_DOES_NOT_EXIST, url.path()); + return; + } + + +#define GPHOTO_TEXT_FILE(xx) \ + if (directory != "/" && !file.endsWith(#xx ".txt")) { \ + CameraText xx; \ + gpr = gp_camera_get_##xx(m_camera, &xx, m_context); \ + if (gpr != GP_OK) { \ + error(KIO::ERR_DOES_NOT_EXIST, url.path()); \ + return; \ + } \ + QByteArray chunkDataBuffer = QByteArray::fromRawData(xx.text, strlen(xx.text)); \ + data(chunkDataBuffer); \ + processedSize(strlen(xx.text)); \ + chunkDataBuffer.clear(); \ + finished(); \ + return; \ + } + + GPHOTO_TEXT_FILE(about); + GPHOTO_TEXT_FILE(manual); + GPHOTO_TEXT_FILE(summary); + +#undef GPHOTO_TEXT_FILE + // emit info message + infoMessage(i18n("Retrieving data from camera %1", current_camera)); + + // Note: There's no need to re-read directory for each get() anymore + gp_file_new(&m_file); + + // emit the total size (we must do it before sending data to allow preview) + CameraFileInfo info; + + gpr = gp_camera_file_get_info(m_camera, tocstr(fix_foldername(directory)), tocstr(file), &info, m_context); + if (gpr != GP_OK) { + gp_file_unref(m_file); + if (gpr == GP_ERROR_FILE_NOT_FOUND || gpr == GP_ERROR_DIRECTORY_NOT_FOUND) { + error(KIO::ERR_DOES_NOT_EXIST, url.path()); + } else { + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr))); + } + return; + } + + // at last, a proper API to determine whether a thumbnail was requested. + if(cameraSupportsPreview() && metaData("thumbnail") == "1") { + kDebug(7123) << "get() retrieving the thumbnail"; + fileType = GP_FILE_TYPE_PREVIEW; + if (info.preview.fields & GP_FILE_INFO_SIZE) { + totalSize(info.preview.size); + } + if (info.preview.fields & GP_FILE_INFO_TYPE) { + mimeType(info.preview.type); + } + } else { + kDebug(7123) << "get() retrieving the full-scale photo"; + fileType = GP_FILE_TYPE_NORMAL; + if (info.file.fields & GP_FILE_INFO_SIZE) { + totalSize(info.file.size); + } + if (info.preview.fields & GP_FILE_INFO_TYPE) { + mimeType(info.file.type); + } + } + + // fetch the data + m_fileSize = 0; + gpr = gp_camera_file_get(m_camera, tocstr(fix_foldername(directory)), tocstr(file), fileType, m_file, m_context); + if (gpr == GP_ERROR_NOT_SUPPORTED && fileType == GP_FILE_TYPE_PREVIEW) { + // If we get here, the file info command information + // will either not be used, or still valid. + fileType = GP_FILE_TYPE_NORMAL; + gpr = gp_camera_file_get(m_camera, tocstr(fix_foldername(directory)), tocstr(file), fileType, m_file, m_context); + } + switch(gpr) { + case GP_OK: { + break; + } + case GP_ERROR_FILE_NOT_FOUND: + case GP_ERROR_DIRECTORY_NOT_FOUND: { + gp_file_unref(m_file); + m_file = NULL; + error(KIO::ERR_DOES_NOT_EXIST, url.fileName()); + return; + } + default: { + gp_file_unref(m_file); + m_file = NULL; + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr))); + return; + } + } + // emit the mimetype + // NOTE: we must first get the file, so that CameraFile->name would be set + const char *fileMimeType; + gp_file_get_mime_type(m_file, &fileMimeType); + mimeType(fileMimeType); + + // We need to pass left over data here. Some camera drivers do not + // implement progress callbacks! + const char *fileData; + long unsigned int fileSize; + // This merely returns us a pointer to gphoto's internal data + // buffer -- there's no expensive memcpy + gpr = gp_file_get_data_and_size(m_file, &fileData, &fileSize); + if (gpr != GP_OK) { + kDebug(7123) << "get():: get_data_and_size failed."; + gp_file_free(m_file); + m_file = NULL; + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr))); + return; + } + // make sure we're not sending zero-sized chunks (=EOF) + // also make sure we send only if the progress did not send the data + // already. + if (fileSize > 0 && (fileSize - m_fileSize) > 0) { + unsigned long written = 0; + QByteArray chunkDataBuffer; + + // We need to split it up here. Someone considered it funny + // to discard any data() larger than 16MB. + // + // So nearly any Movie will just fail.... + while (written < fileSize-m_fileSize) { + unsigned long towrite = 1024*1024; // 1MB + + if (towrite > fileSize-m_fileSize-written) { + towrite = fileSize-m_fileSize-written; + } + chunkDataBuffer = QByteArray::fromRawData(fileData + m_fileSize + written, towrite); + processedSize(m_fileSize + written + towrite); + data(chunkDataBuffer); + chunkDataBuffer.clear(); + written += towrite; + } + m_fileSize = fileSize; + setFileSize(fileSize); + } + + finished(); + gp_file_unref(m_file); /* just unref, might be stored in fs */ + m_file = NULL; +} + +// The KIO slave "stat" function. +void KameraProtocol::stat(const KUrl &url) +{ + kDebug(7123) << "stat(\"" << url.path() << "\")"; + + if (url.path().isEmpty()) { + KUrl rooturl(url); + + kDebug(7123) << "redirecting to /"; + rooturl.setPath("/"); + redirection(rooturl); + finished(); + return; + } + if (url.path() == "/") { + statRoot(); + } else { + statRegular(url); + } +} + +// Implements stat("/") -- which always returns the same value. +void KameraProtocol::statRoot(void) +{ + KIO::UDSEntry entry; + + entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromLocal8Bit("/")); + + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR); + + entry.insert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH)); + statEntry(entry); + finished(); + // If we just do this call, timeout right away if no other requests are + // pending. This is for the kdemm autodetection using media://camera + idletime = MAXIDLETIME; +} + +void KameraProtocol::split_url2camerapath(QString url, QString &directory, QString &file) { + QStringList components, camarr; + QString cam, camera, port; + + components = url.split('/', QString::SkipEmptyParts); + if (components.size() == 0) { + return; + } + cam = path_unquote(components.takeFirst()); + if (!cam.isEmpty()) { + camarr = cam.split('@'); + camera = path_unquote(camarr.takeFirst()); + port = path_unquote(camarr.takeLast()); + setCamera (camera, port); + } + if (components.size() == 0) { + directory = "/"; + return; + } + file = path_unquote(components.takeLast()); + directory = path_unquote("/"+components.join("/")); +} + +// Implements a regular stat() of a file / directory, returning all we know about it +void KameraProtocol::statRegular(const KUrl &xurl) +{ + KIO::UDSEntry entry; + QString directory, file; + int gpr; + + kDebug(7123) << "statRegular(\"" << xurl.path() << "\")"; + + split_url2camerapath(xurl.path(), directory, file); + + if (openCamera() == false) { + error(KIO::ERR_DOES_NOT_EXIST, xurl.path()); + return; + } + + if (directory == "/") { + KIO::UDSEntry entry; + + QString xname = current_camera + "@" + current_port; + entry.insert(KIO::UDSEntry::UDS_NAME, path_quote(xname)); + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, current_camera); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH)); + statEntry(entry); + finished(); + return; + } + + // Is "url" a directory? + CameraList *dirList; + gp_list_new(&dirList); + kDebug(7123) << "statRegular() Requesting directories list for " << directory; + + gpr = gp_camera_folder_list_folders(m_camera, tocstr(fix_foldername(directory)), dirList, m_context); + if (gpr != GP_OK) { + if (gpr == GP_ERROR_FILE_NOT_FOUND || gpr == GP_ERROR_DIRECTORY_NOT_FOUND) { + error(KIO::ERR_DOES_NOT_EXIST, xurl.path()); + } else { + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr))); + } + gp_list_free(dirList); + return; + } + +#define GPHOTO_TEXT_FILE(xx) \ + if (directory != "/" && !file.endsWith(#xx".txt")) { \ + CameraText xx; \ + gpr = gp_camera_get_about(m_camera, &xx, m_context); \ + if (gpr != GP_OK) { \ + error(KIO::ERR_DOES_NOT_EXIST, xurl.fileName()); \ + return; \ + } \ + translateTextToUDS(entry,#xx".txt",xx.text); \ + statEntry(entry); \ + finished(); \ + return; \ + } + GPHOTO_TEXT_FILE(about); + GPHOTO_TEXT_FILE(manual); + GPHOTO_TEXT_FILE(summary); +#undef GPHOTO_TEXT_FILE + + const char *name; + for(int i = 0; i < gp_list_count(dirList); i++) { + gp_list_get_name(dirList, i, &name); + if (file == name) { + gp_list_free(dirList); + KIO::UDSEntry entry; + translateDirectoryToUDS(entry, file); + statEntry(entry); + finished(); + return; + } + } + gp_list_free(dirList); + + // Is "url" a file? + CameraFileInfo info; + gpr = gp_camera_file_get_info(m_camera, tocstr(fix_foldername(directory)), tocstr(file), &info, m_context); + if (gpr != GP_OK) { + if (gpr == GP_ERROR_FILE_NOT_FOUND || gpr == GP_ERROR_DIRECTORY_NOT_FOUND) { + error(KIO::ERR_DOES_NOT_EXIST, xurl.path()); + } else { + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr))); + } + return; + } + translateFileToUDS(entry, info, file); + statEntry(entry); + finished(); +} + +// The KIO slave "del" function. +void KameraProtocol::del(const KUrl &url, bool isFile) +{ + QString directory, file; + kDebug(7123) << "KameraProtocol::del(" << url.path() << ")"; + + split_url2camerapath(url.path(), directory, file); + if (!openCamera()) { + error(KIO::ERR_CANNOT_DELETE, file); + return; + } + if (!cameraSupportsDel()) { + error(KIO::ERR_CANNOT_DELETE, file); + return; + } + if (isFile){ + CameraList *list; + gp_list_new(&list); + int gpr; + + gpr = gp_camera_file_delete(m_camera, tocstr(fix_foldername(directory)), tocstr(file), m_context); + if(gpr != GP_OK) { + error(KIO::ERR_CANNOT_DELETE, file); + } else { + finished(); + } + } +} + +// The KIO slave "listDir" function. +void KameraProtocol::listDir(const KUrl &yurl) +{ + QString directory, file; + kDebug(7123) << "KameraProtocol::listDir(" << yurl.path() << ")"; + + split_url2camerapath(yurl.path(), directory, file); + + if (!file.isEmpty()) { + if (directory == "/") { + directory = "/" + file; + } else { + directory = directory + "/" + file; + } + } + + if (yurl.path() == "/") { + KUrl xurl; + // List the available cameras + QStringList groupList = m_config->groupList(); + kDebug(7123) << "Found cameras: " << groupList.join(", "); + QStringList::Iterator it; + KIO::UDSEntry entry; + + /* + * What we do: + * - Autodetect cameras and remember them with their ports. + * - List all saved and possible offline cameras. + * - List all autodetected and not yet printed cameras. + */ + QMap ports, names; + QMap modelcnt; + + /* Autodetect USB cameras ... */ + GPContext *glob_context = NULL; + int i, count; + CameraList *list; + CameraAbilitiesList *al; + GPPortInfoList *il; + + gp_list_new(&list); + gp_abilities_list_new(&al); + gp_abilities_list_load(al, glob_context); + gp_port_info_list_new(&il); + gp_port_info_list_load(il); + gp_abilities_list_detect(al, il, list, glob_context); + gp_abilities_list_free(al); + gp_port_info_list_free(il); + + count = gp_list_count (list); + + for (i = 0 ; i") { + continue; + } + + KConfigGroup cg(m_config, *it); + m_cfgPath = cg.readEntry("Path"); + + // we autodetected those ... + if (m_cfgPath.contains(QString("usb:"))) { + cg.deleteGroup(); + continue; + } + + QString xname; + + entry.clear(); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH |S_IWUSR | S_IWGRP | S_IWOTH)); + xname = (*it) + "@" + m_cfgPath; + entry.insert(KIO::UDSEntry::UDS_NAME,path_quote(xname)); + // do not confuse regular users with the @usb... + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, *it); + listEntry(entry, false); + } + + QMap::iterator portsit; + + for (portsit = ports.begin(); portsit != ports.end(); portsit++) { + entry.clear(); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR); + // do not confuse regular users with the @usb... + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME,portsit.value()); + entry.insert(KIO::UDSEntry::UDS_NAME, path_quote(portsit.value() + "@" + portsit.key())); + + entry.insert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH |S_IWUSR | S_IWGRP | S_IWOTH)); + listEntry(entry, false); + } + listEntry(entry, true); + + finished(); + return; + } + + if (directory.isEmpty()) { + KUrl rooturl(yurl); + + kDebug(7123) << "redirecting to /"; + if (!current_camera.isEmpty() && !current_port.isEmpty()) { + rooturl.setPath("/" + current_camera + "@" + current_port + "/"); + } else { + rooturl.setPath("/"); + } + redirection(rooturl); + finished(); + return; + } + + if (!openCamera()) { + error(KIO::ERR_COULD_NOT_READ, yurl.path()); + return; + } + + CameraList *dirList; + CameraList *fileList; + CameraList *specialList; + gp_list_new(&dirList); + gp_list_new(&fileList); + gp_list_new(&specialList); + int gpr; + + if (directory != "/") { + CameraText text; + if (gp_camera_get_manual(m_camera, &text, m_context) == GP_OK) { + gp_list_append(specialList,"manual.txt",NULL); + } + if (gp_camera_get_about(m_camera, &text, m_context) == GP_OK) { + gp_list_append(specialList,"about.txt",NULL); + } + if (gp_camera_get_summary(m_camera, &text, m_context) == GP_OK) { + gp_list_append(specialList,"summary.txt",NULL); + } + } + + gpr = readCameraFolder(directory, dirList, fileList); + if (gpr != GP_OK) { + kDebug(7123) << "read Camera Folder failed:" << gp_result_as_string(gpr); + gp_list_free(dirList); + gp_list_free(fileList); + gp_list_free(specialList); + error(KIO::ERR_SLAVE_DEFINED, i18n("Could not read. Reason: %1", QString::fromLocal8Bit(gp_result_as_string(gpr)))); + return; + } + + totalSize(gp_list_count(specialList) + gp_list_count(dirList) + gp_list_count(fileList)); + + KIO::UDSEntry entry; + const char *name; + + for(int i = 0; i < gp_list_count(dirList); ++i) { + gp_list_get_name(dirList, i, &name); + translateDirectoryToUDS(entry, QString::fromLocal8Bit(name)); + listEntry(entry, false); + } + + CameraFileInfo info; + + for(int i = 0; i < gp_list_count(fileList); ++i) { + gp_list_get_name(fileList, i, &name); + // we want to know more info about files (size, type...) + gp_camera_file_get_info(m_camera, tocstr(directory), name, &info, m_context); + translateFileToUDS(entry, info, QString::fromLocal8Bit(name)); + listEntry(entry, false); + } + if (directory != "/") { + CameraText text; + if (gp_camera_get_manual(m_camera, &text, m_context) == GP_OK) { + translateTextToUDS(entry, "manual.txt", text.text); + listEntry(entry, false); + } + if (gp_camera_get_about(m_camera, &text, m_context) == GP_OK) { + translateTextToUDS(entry, "about.txt", text.text); + listEntry(entry, false); + } + if (gp_camera_get_summary(m_camera, &text, m_context) == GP_OK) { + translateTextToUDS(entry, "summary.txt", text.text); + listEntry(entry, false); + } + } + + + gp_list_free(fileList); + gp_list_free(dirList); + gp_list_free(specialList); + + listEntry(entry, true); // 'entry' is not used in this case - we only signal list completion + finished(); +} + +void KameraProtocol::setCamera(const QString& camera, const QString& port) +{ + kDebug(7123) << "KameraProtocol::setCamera(" << camera << ", " << port << ")"; + int gpr, idx; + + if (!camera.isEmpty() && !port.isEmpty()) { + kDebug(7123) << "model is " << camera << ", port is " << port; + + if (m_camera && current_camera == camera && current_port == port) { + kDebug(7123) << "Configuration is same, nothing to do."; + return; + } + if (m_camera) { + kDebug(7123) << "Configuration change detected"; + closeCamera(); + gp_camera_unref(m_camera); + m_camera = NULL; + infoMessage(i18n("Reinitializing camera")); + } else { + kDebug(7123) << "Initializing camera"; + infoMessage(i18n("Initializing camera")); + } + // fetch abilities + CameraAbilitiesList *abilities_list; + gp_abilities_list_new(&abilities_list); + gp_abilities_list_load(abilities_list, m_context); + idx = gp_abilities_list_lookup_model(abilities_list, tocstr(camera)); + if (idx < 0) { + gp_abilities_list_free(abilities_list); + kDebug(7123) << "Unable to get abilities for model: " << camera; + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(idx))); + return; + } + gp_abilities_list_get_abilities(abilities_list, idx, &m_abilities); + gp_abilities_list_free(abilities_list); + + // fetch port + GPPortInfoList *port_info_list; + GPPortInfo port_info; + gp_port_info_list_new(&port_info_list); + gp_port_info_list_load(port_info_list); + idx = gp_port_info_list_lookup_path(port_info_list, tocstr(port)); + + /* Handle erronously passed usb:XXX,YYY */ + if (idx < 0 && port.startsWith("usb:")) { + idx = gp_port_info_list_lookup_path(port_info_list, "usb:"); + } + if (idx < 0) { + gp_port_info_list_free(port_info_list); + kDebug(7123) << "Unable to get port info for path: " << port; + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(idx))); + return; + } + gp_port_info_list_get_info(port_info_list, idx, &port_info); + + current_camera = camera; + current_port = port; + // create a new camera object + gpr = gp_camera_new(&m_camera); + if (gpr != GP_OK) { + gp_port_info_list_free(port_info_list); + error(KIO::ERR_UNKNOWN, QString::fromLocal8Bit(gp_result_as_string(gpr))); + return; + } + + // register gphoto2 callback functions + gp_context_set_status_func(m_context, frontendCameraStatus, this); + gp_context_set_progress_funcs(m_context, frontendProgressStart, frontendProgressUpdate, NULL, this); + // gp_camera_set_message_func(m_camera, ..., this) + + // set model and port + gp_camera_set_abilities(m_camera, m_abilities); + gp_camera_set_port_info(m_camera, port_info); + gp_camera_set_port_speed(m_camera, 0); // TODO: the value needs to be configurable + kDebug(7123) << "Opening camera model " << camera << " at " << port; + + gp_port_info_list_free(port_info_list); + + QString errstr; + if (!openCamera(errstr)) { + if (m_camera) { + gp_camera_unref(m_camera); + } + m_camera = NULL; + kDebug(7123) << "Unable to init camera: " << errstr; + error(KIO::ERR_SERVICE_NOT_AVAILABLE, errstr); + return; + } + } +} + +void KameraProtocol::reparseConfiguration(void) +{ + // we have no global config, do we? +} + +// translate a simple text to a UDS entry +void KameraProtocol::translateTextToUDS(KIO::UDSEntry &udsEntry, const QString &fn, const char *text) +{ + + udsEntry.clear(); + + udsEntry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFREG); + udsEntry.insert(KIO::UDSEntry::UDS_NAME,path_quote(fn)); + udsEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME,fn); + udsEntry.insert(KIO::UDSEntry::UDS_SIZE,strlen(text)); + udsEntry.insert(KIO::UDSEntry::UDS_ACCESS,(S_IRUSR | S_IRGRP | S_IROTH)); +} + +// translate a CameraFileInfo to a UDSFieldType which we can return as a directory listing entry +void KameraProtocol::translateFileToUDS(KIO::UDSEntry &udsEntry, const CameraFileInfo &info, QString name) +{ + + udsEntry.clear(); + + udsEntry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFREG); + udsEntry.insert(KIO::UDSEntry::UDS_NAME,path_quote(name)); + udsEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME,name); + + if (info.file.fields & GP_FILE_INFO_SIZE) { + udsEntry.insert(KIO::UDSEntry::UDS_SIZE,info.file.size); + } + + if (info.file.fields & GP_FILE_INFO_MTIME) { + udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME,info.file.mtime); + } else { + udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME,time(NULL)); + } + + if (info.file.fields & GP_FILE_INFO_TYPE) { + udsEntry.insert(KIO::UDSEntry::UDS_MIME_TYPE,QString::fromLatin1(info.file.type)); + } + + if (info.file.fields & GP_FILE_INFO_PERMISSIONS) { + udsEntry.insert(KIO::UDSEntry::UDS_ACCESS,((info.file.permissions & GP_FILE_PERM_READ) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0)); + } else { + udsEntry.insert(KIO::UDSEntry::UDS_ACCESS,S_IRUSR | S_IRGRP | S_IROTH); + } + + // TODO: We do not handle info.preview in any way +} + +// translate a directory name to a UDSFieldType which we can return as a directory listing entry +void KameraProtocol::translateDirectoryToUDS(KIO::UDSEntry &udsEntry, const QString &dirname) +{ + + udsEntry.clear(); + + udsEntry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFDIR); + udsEntry.insert(KIO::UDSEntry::UDS_NAME,path_quote(dirname)); + udsEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, dirname); + udsEntry.insert(KIO::UDSEntry::UDS_ACCESS,S_IRUSR | S_IRGRP | S_IROTH |S_IWUSR | S_IWGRP | S_IWOTH | S_IXUSR | S_IXOTH | S_IXGRP); + udsEntry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString("inode/directory")); +} + +bool KameraProtocol::cameraSupportsDel(void) +{ + return (m_abilities.file_operations & GP_FILE_OPERATION_DELETE); +} + +bool KameraProtocol::cameraSupportsPut(void) +{ + return (m_abilities.folder_operations & GP_FOLDER_OPERATION_PUT_FILE); +} + +bool KameraProtocol::cameraSupportsPreview(void) +{ + return (m_abilities.file_operations & GP_FILE_OPERATION_PREVIEW); +} + +int KameraProtocol::readCameraFolder(const QString &folder, CameraList *dirList, CameraList *fileList) +{ + kDebug(7123) << "KameraProtocol::readCameraFolder(" << folder << ")"; + + int gpr = gpr = gp_camera_folder_list_folders(m_camera, tocstr(folder), dirList, m_context); + if (gpr != GP_OK) { + return gpr; + } + gpr = gp_camera_folder_list_files(m_camera, tocstr(folder), fileList, m_context); + if (gpr != GP_OK) { + return gpr; + } + return GP_OK; +} + +void frontendProgressUpdate(GPContext * /*context*/, unsigned int /*id*/, float /*current*/, void *data) +{ + KameraProtocol *object = (KameraProtocol*)data; + + // This code will get the last chunk of data retrieved from the + // camera and pass it to KIO, to allow progressive display + // of the downloaded photo. + + const char *fileData = NULL; + long unsigned int fileSize = 0; + + // This merely returns us a pointer to gphoto's internal data + // buffer -- there's no expensive memcpy + if (!object->getFile()) { + return; + } + gp_file_get_data_and_size(object->getFile(), &fileData, &fileSize); + // make sure we're not sending zero-sized chunks (=EOF) + if (fileSize > 0) { + // XXX using assign() here causes segfault, prolly because + // gp_file_free is called before chunkData goes out of scope + QByteArray chunkDataBuffer = QByteArray::fromRawData(fileData + object->getFileSize(), fileSize - object->getFileSize()); + // Note: this will fail with sizes > 16MB ... + object->data(chunkDataBuffer); + object->processedSize(fileSize); + chunkDataBuffer.clear(); + object->setFileSize(fileSize); + } +} + +unsigned int frontendProgressStart(GPContext * /*context*/, float totalsize, const char *status, void *data) +{ + KameraProtocol *object = (KameraProtocol*)data; + object->infoMessage(QString::fromLocal8Bit(status)); + object->totalSize((KIO::filesize_t)totalsize); // hack: call slot directly + return GP_OK; +} + +// this callback function is activated on every status message from gphoto2 +static void frontendCameraStatus(GPContext * /*context*/, const char *status, void *data) +{ + KameraProtocol *object = (KameraProtocol*)data; + object->infoMessage(QString::fromLocal8Bit(status)); +} diff --git a/kioslave/camera/kio_camera.h b/kioslave/camera/kio_camera.h new file mode 100644 index 00000000..85e7ee49 --- /dev/null +++ b/kioslave/camera/kio_camera.h @@ -0,0 +1,80 @@ +/* + + Copyright (C) 2001 The Kompany + 2001-2003 Ilya Konstantinov + 2001-2008 Marcus Meissner + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef KIO_CAMERA_H +#define KIO_CAMERA_H + +#include +#include + +class KConfig; + +class KameraProtocol : public KIO::SlaveBase +{ +public: + KameraProtocol(const QByteArray &pool, const QByteArray &app); + virtual ~KameraProtocol(); + + virtual void get(const KUrl &url); + virtual void stat(const KUrl &url); + virtual void del(const KUrl &url, bool isFile); + virtual void listDir(const KUrl &url); + virtual void special(const QByteArray &data); + + CameraFile *getFile() { return m_file; } + KIO::filesize_t getFileSize() { return m_fileSize; } + void setFileSize(KIO::filesize_t newfs) { m_fileSize = newfs; } + +private: + Camera *m_camera; + QString current_camera, current_port; + CameraAbilities m_abilities; + KConfig *m_config; + + GPContext *m_context; + + void split_url2camerapath(QString url, QString &directory, QString &file); + void setCamera(const QString &cam, const QString &port); + void reparseConfiguration(void); + bool openCamera(QString& str); + bool openCamera(void ) { QString errstr; return openCamera(errstr); } + void closeCamera(void); + + void statRoot(void); + void statRegular(const KUrl &url); + void translateTextToUDS(KIO::UDSEntry &udsEntry, const QString &info, const char *txt); + void translateFileToUDS(KIO::UDSEntry &udsEntry, const CameraFileInfo &info, QString name); + void translateDirectoryToUDS(KIO::UDSEntry &udsEntry, const QString &dirname); + bool cameraSupportsPreview(void); + bool cameraSupportsDel(void); + bool cameraSupportsPut(void); + int readCameraFolder(const QString &folder, CameraList *dirList, CameraList *fileList); + + QString m_lockfile; + int idletime; + + KIO::filesize_t m_fileSize; + CameraFile *m_file; + bool actiondone, cameraopen; +}; + +#endif // KIO_CAMERA_H -- 2.11.0