diff --git a/mtp/CMakeLists.txt b/mtp/CMakeLists.txt index 75987e76..3a1c01b6 100644 --- a/mtp/CMakeLists.txt +++ b/mtp/CMakeLists.txt @@ -1,24 +1,24 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kio5_mtp\") add_definitions(-DQT_NO_CAST_FROM_ASCII) +add_subdirectory(shared) +add_subdirectory(kiod_module) + set(kio_mtp_PART_SRCS - devicecache.cpp - filecache.cpp kio_mtp.cpp - kio_mtp_helpers.cpp ) -include_directories(${MTP_INCLUDE_DIR}) +include_directories("shared") add_library(kio_mtp MODULE ${kio_mtp_PART_SRCS} ) -target_link_libraries(kio_mtp ${MTP_LIBRARIES} Qt5::Core KF5::KIOCore KF5::I18n KF5::Solid Qt5::Network) +target_link_libraries(kio_mtp kmtp Qt5::Core KF5::KIOCore KF5::I18n KF5::DBusAddons) set_target_properties(kio_mtp PROPERTIES OUTPUT_NAME "mtp") install(TARGETS kio_mtp DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kio) ########### install files ############### install( FILES mtp-network.desktop DESTINATION ${DATA_INSTALL_DIR}/konqueror/dirtree/remote ) install( FILES solid_mtp.desktop DESTINATION ${DATA_INSTALL_DIR}/solid/actions ) install( FILES mtp.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) install( FILES mtp-network.desktop DESTINATION ${DATA_INSTALL_DIR}/remoteview ) diff --git a/mtp/devicecache.cpp b/mtp/devicecache.cpp deleted file mode 100644 index 02adaf7d..00000000 --- a/mtp/devicecache.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - Cache for recently used devices. - Copyright (C) 2012 Philipp Schmidt - - 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, see . -*/ - -#include "devicecache.h" -#include "kio_mtp_helpers.h" - -// #include -// #include - -#include -#include -#include - -/** - * Creates a Cached Device that has a predefined lifetime (default: 10000 msec)s - * The lifetime is reset every time the device is accessed. After it expires it - * will be released. - * - * @param device The LIBMTP_mtpdevice_t pointer to cache - * @param udi The UDI of the new device to cache - */ -CachedDevice::CachedDevice(LIBMTP_mtpdevice_t *device, LIBMTP_raw_device_t *rawdevice, const QString udi, qint32 timeout) -{ - this->timeout = timeout; - this->mtpdevice = device; - this->rawdevice = *rawdevice; - this->udi = udi; - - char *deviceName = LIBMTP_Get_Friendlyname(device); - char *deviceModel = LIBMTP_Get_Modelname(device); - - // prefer friendly devicename over model - if (!deviceName) { - name = QString::fromUtf8(deviceModel); - } else { - name = QString::fromUtf8(deviceName); - } - - qCDebug(LOG_KIO_MTP) << "Created device " << name << " with udi=" << udi << " and timeout " << timeout; -} - -CachedDevice::~CachedDevice() -{ - LIBMTP_Release_Device(mtpdevice); -} - -LIBMTP_mtpdevice_t *CachedDevice::getDevice() -{ - LIBMTP_mtpdevice_t *device = mtpdevice; - if (!device->storage) { - qCDebug(LOG_KIO_MTP) << "reopen mtpdevice if we have no storage found"; - LIBMTP_Release_Device(mtpdevice); - mtpdevice = LIBMTP_Open_Raw_Device_Uncached(&rawdevice); - } - - return mtpdevice; -} - -const QString CachedDevice::getName() -{ - return name; -} -const QString CachedDevice::getUdi() -{ - return udi; -} - -DeviceCache::DeviceCache(qint32 timeout, QObject *parent) - : QEventLoop(parent) -{ - this->timeout = timeout; - - notifier = Solid::DeviceNotifier::instance(); - - connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); - connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString))); - - foreach(Solid::Device solidDevice, Solid::Device::listFromType(Solid::DeviceInterface::PortableMediaPlayer, QString())) { - checkDevice(solidDevice); - } -} - -DeviceCache::~DeviceCache() -{ - processEvents(); - - // Release devices - foreach(QString udi, udiCache.keys()) { - deviceRemoved(udi); - } -} - -void DeviceCache::checkDevice(Solid::Device solidDevice) -{ - if (!udiCache.contains(solidDevice.udi())) { - qCDebug(LOG_KIO_MTP) << "new device, getting raw devices"; - - Solid::GenericInterface *iface = solidDevice.as(); - if (!iface) { - qCDebug( LOG_KIO_MTP ) << "Solid device " << solidDevice.udi() << " has NOT a Solid::GenericInterface"; - return; - } - - const QMap &properties = iface->allProperties(); - const uint32_t solidBusNum = properties.value ( QLatin1String ( "BUSNUM" ) ).toUInt(); - const uint32_t solidDevNum = properties.value ( QLatin1String ( "DEVNUM" ) ).toUInt(); - - LIBMTP_raw_device_t *rawdevices = nullptr; - int numrawdevices; - LIBMTP_error_number_t err; - - - err = LIBMTP_Detect_Raw_Devices(&rawdevices, &numrawdevices); - switch (err) { - case LIBMTP_ERROR_CONNECTING: - qCWarning(LOG_KIO_MTP) << "There has been an error connecting to the devices"; - break; - case LIBMTP_ERROR_MEMORY_ALLOCATION: - qCWarning(LOG_KIO_MTP) << "Encountered a Memory Allocation Error"; - break; - case LIBMTP_ERROR_NONE: { - qCDebug(LOG_KIO_MTP) << "No Error, continuing"; - - for (int i = 0; i < numrawdevices; i++) { - LIBMTP_raw_device_t *rawDevice = &rawdevices[i]; - uint32_t rawBusNum = rawDevice->bus_location; - uint32_t rawDevNum = rawDevice->devnum; - - if (rawBusNum == solidBusNum && rawDevNum == solidDevNum) { - qCDebug(LOG_KIO_MTP) << "Found device matching the Solid description"; - - LIBMTP_mtpdevice_t *mtpDevice = LIBMTP_Open_Raw_Device_Uncached(rawDevice); - - if (mtpDevice && udiCache.find(solidDevice.udi()) == udiCache.end()) { - CachedDevice *cDev = new CachedDevice(mtpDevice, rawDevice, solidDevice.udi(), timeout); - udiCache.insert(solidDevice.udi(), cDev); - nameCache.insert(cDev->getName(), cDev); - } - } - } - } - break; - case LIBMTP_ERROR_GENERAL: - default: - qCWarning(LOG_KIO_MTP) << "Unknown connection error"; - break; - } - free(rawdevices); - } -} - -void DeviceCache::deviceAdded(const QString &udi) -{ - qCDebug(LOG_KIO_MTP) << "New device attached with udi=" << udi << ". Checking if PortableMediaPlayer..."; - - Solid::Device device(udi); - if (device.isDeviceInterface(Solid::DeviceInterface::PortableMediaPlayer)) { - qCDebug(LOG_KIO_MTP) << "SOLID: New Device with udi=" << udi << "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"; - - checkDevice(device); - } -} - -void DeviceCache::deviceRemoved(const QString &udi) -{ - if (udiCache.contains(udi)) { - qCDebug(LOG_KIO_MTP) << "SOLID: Device with udi=" << udi << " removed. ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"; - - CachedDevice *cDev = udiCache.value(udi); - - udiCache.remove(cDev->getUdi()); - nameCache.remove(cDev->getName()); - delete cDev; - } -} - -QHash DeviceCache::getAll() -{ - qCDebug(LOG_KIO_MTP) << "getAll()"; - - processEvents(); - - return nameCache; -} - -bool DeviceCache::contains(QString string, bool isUdi) -{ - processEvents(); - - if (isUdi) { - return udiCache.find(string) != udiCache.end(); - } else { - return nameCache.find(string) != nameCache.end(); - } -} - -CachedDevice *DeviceCache::get(const QString &string, bool isUdi) -{ - processEvents(); - - if (isUdi) { - return udiCache.value(string); - } else { - return nameCache.value(string); - } -} - -int DeviceCache::size() -{ - processEvents(); - - return nameCache.size(); -} - diff --git a/mtp/devicecache.h b/mtp/devicecache.h deleted file mode 100644 index 639064d3..00000000 --- a/mtp/devicecache.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - Cache for recently used devices. - Copyright (C) 2012 Philipp Schmidt - - 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, see . -*/ - -#ifndef KIO_MTP_DEVICE_CACHE_H -#define KIO_MTP_DEVICE_CACHE_H - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -Q_DECLARE_LOGGING_CATEGORY(LOG_KIO_MTP) - -class CachedDevice : public QObject -{ -Q_OBJECT - -public: - explicit CachedDevice(LIBMTP_mtpdevice_t *device, LIBMTP_raw_device_t *rawdevice, const QString udi, qint32 timeout); - ~CachedDevice() override; - - LIBMTP_mtpdevice_t *getDevice(); - const QString getName(); - const QString getUdi(); - -private: - qint32 timeout; - QTimer *timer; - LIBMTP_mtpdevice_t *mtpdevice; - LIBMTP_raw_device_t rawdevice; - - QString name; - QString udi; -}; - -class DeviceCache : public QEventLoop -{ -Q_OBJECT - -public: - explicit DeviceCache(qint32 timeout, QObject *parent = nullptr); - ~DeviceCache() override; - - QHash< QString, CachedDevice * > getAll(); - CachedDevice *get(const QString &string, bool isUdi = false); - bool contains(QString string, bool isUdi = false); - int size(); - -private slots: - void deviceAdded(const QString &udi); - void deviceRemoved(const QString &udi); - -private: - void checkDevice(Solid::Device solidDevice); - - /** - * Fields in order: Devicename (QString), expiration Timer, pointer to device - */ - QHash< QString, CachedDevice * > nameCache, udiCache; - Solid::DeviceNotifier *notifier; - qint32 timeout; -}; - -#endif // KIO_MTP_DEVICE_CACHE_H diff --git a/mtp/filecache.cpp b/mtp/filecache.cpp deleted file mode 100644 index 86d68b1a..00000000 --- a/mtp/filecache.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - Cache for recent files accessed. - Copyright (C) 2012 Philipp Schmidt - - 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. -*/ - -#include "filecache.h" - -#include - -FileCache::FileCache(QObject *parent) - : QObject(parent) -{ -} - -uint32_t FileCache::queryPath(const QString &path, int timeToLive) -{ - qCDebug(LOG_KIO_MTP) << "Querying" << path; - - QPair< QDateTime, uint32_t > item = cache.value(path); - - if (item.second != 0) { - QDateTime dateTime = QDateTime::currentDateTime(); - - if (item.first > dateTime) { - qCDebug(LOG_KIO_MTP) << "Found item with ttl:" << item.first << "- now:" << dateTime; - - item.first = dateTime.addSecs(timeToLive); - - qCDebug(LOG_KIO_MTP) << "Reset item ttl:" << item.first; - - cache.insert(path, item); - - return item.second; - } else { - qCDebug(LOG_KIO_MTP) << "Item too old (" << item.first << "), removed. Current Time: " << dateTime; - - cache.remove(path); - return 0; - } - } - - return 0; -} - -void FileCache::addPath(const QString &path, uint32_t id, int timeToLive) -{ - QDateTime dateTime = QDateTime::currentDateTime(); - dateTime = dateTime.addSecs(timeToLive); - - QPair< QDateTime, uint32_t > item(dateTime, id); - - cache.insert(path, item); -} - -void FileCache::removePath(const QString &path) -{ - cache.remove(path); -} - diff --git a/mtp/filecache.h b/mtp/filecache.h deleted file mode 100644 index 4e02ca13..00000000 --- a/mtp/filecache.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Cache for recent files accessed. - Copyright (C) 2012 Philipp Schmidt - - 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_MTP_FILE_CACHE_H -#define KIO_MTP_FILE_CACHE_H - -#define KIO_MTP 7000 - -#include - -#include -#include -#include -#include - -Q_DECLARE_LOGGING_CATEGORY(LOG_KIO_MTP) - -/** - * @class FileCache Implements a time based cache for file ids, mapping their path to their ID. Does _not_ store the device they are on. - */ -class FileCache : public QObject -{ -Q_OBJECT - -public: - explicit FileCache(QObject *parent = nullptr); - - /** - * Returns the ID of the item at the given path, else 0. - * Automatically discards old items. - * - * @param path The Path to query the cache for - * @return The ID of the Item if it exists, else 0 - */ - uint32_t queryPath(const QString &path, int timeToLive = 60); - - /** - * Adds a Path to the Cache with the given id and ttl. - * - * @param path The path of the file/folder - * @param id The file ID on the storage - * @param timeToLive The time in seconds the entry should be valid - */ - void addPath(const QString &path, uint32_t id, int timeToLive = 60); - - /** - * Remove the given path from the cache, i.e. if it got deleted - * - * @param path The path that should be removed - */ - void removePath(const QString &path); - -// private slots: -// void insertItem( const QString& path, QPair item ); -// void removeItem( const QString& path ); -// -// signals: -// void s_insertItem( const QString& path, QPair item ); -// void s_removeItem( const QString& path ); - -private: - QHash > cache; -}; - -#endif // KIO_MTP_FILE_CACHE_H diff --git a/mtp/kio_mtp.cpp b/mtp/kio_mtp.cpp index d6de4fd5..2f775121 100644 --- a/mtp/kio_mtp.cpp +++ b/mtp/kio_mtp.cpp @@ -1,959 +1,866 @@ /* * Main implementation for KIO-MTP * Copyright (C) 2012 Philipp Schmidt + * Copyright (C) 2018 Andreas Krutzler * * 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. */ #include "kio_mtp.h" -#include "kio_mtp_helpers.h" // #include #include #include #include #include #include #include #include #include -#include -#include +#include "kmtpdeviceinterface.h" +#include "kmtpstorageinterface.h" + +static UDSEntry getEntry(const KMTPDeviceInterface *device) +{ + UDSEntry entry; + entry.fastInsert(UDSEntry::UDS_NAME, device->friendlyName()); + entry.fastInsert(UDSEntry::UDS_ICON_NAME, QStringLiteral("multimedia-player")); + entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); + entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); + return entry; +} + +static UDSEntry getEntry(const KMTPStorageInterface *storage) +{ + UDSEntry entry; + entry.fastInsert(UDSEntry::UDS_NAME, storage->description()); + entry.fastInsert(UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media")); + entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); + return entry; +} + +static UDSEntry getEntry(const KMTPFile &file) +{ + UDSEntry entry; + entry.fastInsert(UDSEntry::UDS_NAME, file.filename()); + if (file.isFolder()) { + entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); + } else { + entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); + entry.fastInsert(UDSEntry::UDS_SIZE, file.filesize()); + } + entry.fastInsert(UDSEntry::UDS_MIME_TYPE, file.filetype()); + entry.fastInsert(UDSEntry::UDS_INODE, file.itemId()); + entry.fastInsert(UDSEntry::UDS_ACCESS_TIME, file.modificationdate()); + entry.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, file.modificationdate()); + entry.fastInsert(UDSEntry::UDS_CREATION_TIME, file.modificationdate()); + return entry; +} + +static QString urlDirectory(const QUrl &url, bool appendTrailingSlash = false) +{ + if (!appendTrailingSlash) { + return url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename).path(); + } + return url.adjusted(QUrl::RemoveFilename).path(); +} + +static QString urlFileName(const QUrl &url) +{ + return url.fileName(); +} + +static QString convertPath(const QString &slavePath) +{ + return slavePath.section(QLatin1Char('/'), 3, -1, QString::SectionIncludeLeadingSep); +} ////////////////////////////////////////////////////////////////////////////// ///////////////////////////// Slave Implementation /////////////////////////// ////////////////////////////////////////////////////////////////////////////// Q_LOGGING_CATEGORY(LOG_KIO_MTP, "kde.kio-mtp") extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); app.setApplicationName(QLatin1String("kio_mtp")); if (argc != 4) { fprintf(stderr, "Usage: kio_mtp protocol domain-socket1 domain-socket2\n"); exit(-1); } MTPSlave slave(argv[2], argv[3]); slave.dispatchLoop(); qCDebug(LOG_KIO_MTP) << "Slave EventLoop ended"; return 0; } MTPSlave::MTPSlave(const QByteArray &pool, const QByteArray &app) : SlaveBase("mtp", pool, app) { - LIBMTP_Init(); - qCDebug(LOG_KIO_MTP) << "Slave started"; - - deviceCache = new DeviceCache(60000); - fileCache = new FileCache(this); - - qCDebug(LOG_KIO_MTP) << "Caches created"; + qCDebug(LOG_KIO_MTP) << "Connected to kiod5 module:" << m_kmtpDaemon.isValid(); } MTPSlave::~MTPSlave() { qCDebug(LOG_KIO_MTP) << "Slave destroyed"; - delete fileCache; - delete deviceCache; -} - -/** - * @brief Get's the correct object from the device. - * @param pathItems A QStringList containing the items of the filepath - * @return QPair with the object and its device. pair.first is a nullpointer if the object doesn't exist or for root or, depending on the pathItems size device (1), storage (2) or file (>=3) - */ -QPair MTPSlave::getPath(const QString &path) -{ - QStringList pathItems = path.split(QLatin1Char('/'), QString::SkipEmptyParts); - - qCDebug(LOG_KIO_MTP) << path << pathItems.size(); - - QPair ret; - - // Don' handle the root directory - if (pathItems.size() <= 0) { - return ret; - } - - if (deviceCache->contains(pathItems.at(0))) { - LIBMTP_mtpdevice_t *device = deviceCache->get(pathItems.at(0))->getDevice(); - - // return specific device - if (pathItems.size() == 1) { - ret.first = device; - ret.second = device; - - qCDebug(LOG_KIO_MTP) << "returning LIBMTP_mtpdevice_t"; - } - - if (pathItems.size() > 2) { - // Query Cache after we have the device - uint32_t c_fileID = fileCache->queryPath(path); - if (c_fileID != 0) { - qCDebug(LOG_KIO_MTP) << "Match found in cache, checking device"; - - LIBMTP_file_t *file = LIBMTP_Get_Filemetadata(device, c_fileID); - if (file) { - qCDebug(LOG_KIO_MTP) << "Found file in cache"; - ret.first = file; - ret.second = device; - - qCDebug(LOG_KIO_MTP) << "returning LIBMTP_file_t from cache"; - - return ret; - } - } - // Query cache for parent - else if (pathItems.size() > 3) { - QString parentPath = convertToPath(pathItems, pathItems.size() - 1); - uint32_t c_parentID = fileCache->queryPath(parentPath); - - qCDebug(LOG_KIO_MTP) << "Match for parent found in cache, checking device. Parent id = " << c_parentID; - - LIBMTP_file_t *parent = LIBMTP_Get_Filemetadata(device, c_parentID); - if (parent) { - qCDebug(LOG_KIO_MTP) << "Found parent in cache"; -// fileCache->addPath( parentPath, c_parentID ); - - QMap files = getFiles(device, parent->storage_id, c_parentID); - - for (QMap::iterator it = files.begin(); it != files.end(); ++it) { - QString filePath = parentPath; - filePath.append(QString::fromUtf8("/")).append(it.key()); - fileCache->addPath(filePath, it.value()->item_id); - } - - if (files.contains(pathItems.last())) { - LIBMTP_file_t *file = files.value(pathItems.last()); - - ret.first = file; - ret.second = device; - - qCDebug(LOG_KIO_MTP) << "returning LIBMTP_file_t from cached parent" ; - - fileCache->addPath(path, file->item_id); - } - - return ret; - } - } - } - - QMap storages = getDevicestorages(device); - - if (pathItems.size() > 1 && storages.contains(pathItems.at(1))) { - LIBMTP_devicestorage_t *storage = storages.value(pathItems.at(1)); - - if (pathItems.size() == 2) { - ret.first = storage; - ret.second = device; - - qCDebug(LOG_KIO_MTP) << "returning LIBMTP_devicestorage_t"; - - return ret; - } - - int currentLevel = 2, currentParent = 0xFFFFFFFF; - - QMap files; - - // traverse further while depth not reached - while (currentLevel < pathItems.size()) { - files = getFiles(device, storage->id, currentParent); - - if (files.contains(pathItems.at(currentLevel))) { - currentParent = files.value(pathItems.at(currentLevel))->item_id; - } else { - qCDebug(LOG_KIO_MTP) << "returning LIBMTP_file_t using tree walk"; - - return ret; - } - currentLevel++; - } - - ret.first = LIBMTP_Get_Filemetadata(device, currentParent); - ret.second = device; - - fileCache->addPath(path, currentParent); - } - } - - return ret; } int MTPSlave::checkUrl(const QUrl &url, bool redirect) { - qCDebug(LOG_KIO_MTP) << url; - if (url.path().startsWith(QLatin1String("udi=")) && redirect) { - QString udi = url.adjusted(QUrl::StripTrailingSlash).path().remove(0, 4); + const QString udi = url.adjusted(QUrl::StripTrailingSlash).path().remove(0, 4); qCDebug(LOG_KIO_MTP) << "udi = " << udi; - if (deviceCache->contains(udi, true)) { + const KMTPDeviceInterface *device = m_kmtpDaemon.deviceFromUdi(udi); + if (device) { QUrl newUrl; - newUrl.setScheme(QLatin1String("mtp")); - newUrl.setPath(QLatin1Char('/') + deviceCache->get(udi, true)->getName()); + newUrl.setScheme(QStringLiteral("mtp")); + newUrl.setPath(QLatin1Char('/') + device->friendlyName()); redirection(newUrl); return 1; } else { return 2; } } else if (url.path().startsWith(QLatin1Char('/'))) { return 0; } return -1; } -QString MTPSlave::urlDirectory(const QUrl &url, bool appendTrailingSlash) -{ - if (!appendTrailingSlash) { - return url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename).path(); - } - return url.adjusted(QUrl::RemoveFilename).path(); -} - -QString MTPSlave::urlFileName(const QUrl &url) -{ - return url.fileName(); -} - void MTPSlave::listDir(const QUrl &url) { - qCDebug(LOG_KIO_MTP) << url.path(); - - int check = checkUrl(url); + const int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.path()); return; default: error(ERR_MALFORMED_URL, url.path()); return; } - QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - - UDSEntry entry; + // list '.' entry, otherwise files cannot be pasted to empty folders + KIO::UDSEntry entry; + entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); + entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); + entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); + listEntry(entry); + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); // list devices - if (pathItems.size() == 0) { + if (pathItems.isEmpty()) { qCDebug(LOG_KIO_MTP) << "Root directory, listing devices"; - totalSize(deviceCache->size()); - - foreach(CachedDevice * cachedDevice, deviceCache->getAll().values()) { - LIBMTP_mtpdevice_t *device = cachedDevice->getDevice(); - getEntry(entry, device); + totalSize(filesize_t(m_kmtpDaemon.devices().size())); - listEntry(entry); - entry.clear(); + const auto devices = m_kmtpDaemon.devices(); + for (const KMTPDeviceInterface *device : devices) { + listEntry(getEntry(device)); } - qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Devices"; + qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Devices:" << m_kmtpDaemon.devices().size(); finished(); + return; } - // traverse into device - else if (deviceCache->contains(pathItems.at(0))) { - QPair pair = getPath(url.path()); - UDSEntry entry; - - if (pair.first) { - LIBMTP_mtpdevice_t *device = pair.second; - - // Device, list storages - if (pathItems.size() == 1) { - QMap storages = getDevicestorages(device); - qCDebug(LOG_KIO_MTP) << "Listing storages for device " << pathItems.at(0); - totalSize(storages.size()); + // traverse into device + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { + // list storages + if (pathItems.size() == 1) { + qCDebug(LOG_KIO_MTP) << "Listing storages for device " << pathItems.first(); - if (storages.size() > 0) { - foreach(const QString & storageName, storages.keys()) { - getEntry(entry, storages.value(storageName)); + const auto storages = mtpDevice->storages(); + totalSize(filesize_t(storages.size())); - listEntry(entry); - entry.clear(); - } - - finished(); - qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Storages"; - } else { - warning(i18n("No Storages found. Make sure your device is unlocked and has MTP enabled in its USB connection settings.")); + if (storages.count() > 0) { + for (KMTPStorageInterface *storage : storages) { + listEntry(getEntry(storage)); } - } - // Storage, list files and folders of storage root - else { - QMap files; - - if (pathItems.size() == 2) { - qCDebug(LOG_KIO_MTP) << "Getting storage root listing"; - LIBMTP_devicestorage_t *storage = (LIBMTP_devicestorage_t *)pair.first; - - qCDebug(LOG_KIO_MTP) << "We have a storage:" << (storage == nullptr); - - files = getFiles(device, storage->id); - } else { - LIBMTP_file_t *parent = (LIBMTP_file_t *)pair.first; - - files = getFiles(device, parent->storage_id, parent->item_id); + finished(); + qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Storages:" << storages.count(); + } else { + warning(i18n("No storages found. Make sure your device is unlocked and has MTP enabled in its USB connection settings.")); + } + } else { + // list files and folders + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + int result; + const QString path = convertPath(url.path()); + const KMTPFileList files = storage->getFilesAndFolders(path, result); + + switch (result) { + case 0: + break; + case 2: + error(ERR_IS_FILE, url.path()); + return; + default: + // path not found + error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); + return; } - for (QMap::iterator it = files.begin(); it != files.end(); ++it) { - LIBMTP_file_t *file = it.value(); - - QString filePath; - if (url.path().endsWith(QLatin1Char('/'))) { - filePath = url.path().append(it.key()); - } else { - filePath = url.path().append(QLatin1Char('/')).append(it.key()); - } - - fileCache->addPath(filePath, file->item_id); - - getEntry(entry, file); - - listEntry(entry); - entry.clear(); + for (const KMTPFile &file : files) { + listEntry(getEntry(file)); } finished(); - - qCDebug(LOG_KIO_MTP) << "[SUCCESS] Files"; + qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Files:" << files.count(); + } else { + // storage not found + error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); + qCDebug(LOG_KIO_MTP) << "[ERROR] :: Storage"; } - } else { - error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); - qCDebug(LOG_KIO_MTP) << "[ERROR]"; } } else { + // device not found error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); - qCDebug(LOG_KIO_MTP) << "[ERROR]"; + qCDebug(LOG_KIO_MTP) << "[ERROR] :: Device"; } } void MTPSlave::stat(const QUrl &url) { - qCDebug(LOG_KIO_MTP) << url.path(); - - int check = checkUrl(url); + const int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.path()); return; default: error(ERR_MALFORMED_URL, url.path()); return; } - QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - - QPair pair = getPath(url.path()); + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); UDSEntry entry; + // root + if (pathItems.size() < 1) { + entry.fastInsert(UDSEntry::UDS_NAME, QLatin1String("mtp:///")); + entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); + entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); + } else { + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { - if (pair.first) { - // Root - if (pathItems.size() < 1) { - entry.insert(UDSEntry::UDS_NAME, QLatin1String("mtp:///")); - entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); - entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); - } - // Device - else if (pathItems.size() < 2) { - getEntry(entry, pair.second); - } - // Storage - else if (pathItems.size() < 3) { - getEntry(entry, (LIBMTP_devicestorage_t *) pair.first); - } - // Folder/File - else { - getEntry(entry, (LIBMTP_file_t *) pair.first); + // device + if (pathItems.size() < 2) { + entry = getEntry(mtpDevice); + } else { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + + // storage + if (pathItems.size() < 3) { + entry = getEntry(storage); + } + // folder/file + else { + entry = getEntry(storage->getFileMetadata(convertPath(url.path()))); + } + } else { + error(ERR_DOES_NOT_EXIST, url.path()); + return; + } + } + } else { + error(ERR_DOES_NOT_EXIST, url.path()); + return; } } + statEntry(entry); finished(); } void MTPSlave::mimetype(const QUrl &url) { - int check = checkUrl(url); + const int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.path()); return; default: error(ERR_MALFORMED_URL, url.path()); return; } - qCDebug(LOG_KIO_MTP) << url.path(); - - QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - - QPair pair = getPath(url.path()); - - if (pair.first) { -// NOTE the difference between calling mimetype and mimeType - if (pathItems.size() > 2) { - mimeType(getMimetype(((LIBMTP_file_t *) pair.first)->filetype)); - } else { - mimeType(QString::fromLatin1("inode/directory")); + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + if (pathItems.size() > 2) { + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + const KMTPFile file = storage->getFileMetadata(convertPath(url.path())); + // NOTE the difference between calling mimetype and mimeType + mimeType(file.filetype()); + return; + } } } else { - error(ERR_DOES_NOT_EXIST, url.path()); + mimeType(QStringLiteral("inode/directory")); return; } + + error(ERR_DOES_NOT_EXIST, url.path()); } -void MTPSlave::put(const QUrl &url, int, JobFlags flags) +void MTPSlave::get(const QUrl &url) { - int check = checkUrl(url); + const int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } - qCDebug(LOG_KIO_MTP) << url.path(); + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - QStringList destItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + // file + if (pathItems.size() > 2) { - // Can't copy to root or device, needs storage - if (destItems.size() < 2) { - error(ERR_UNSUPPORTED_ACTION, url.path()); - return; - } + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + const QString path = convertPath(url.path()); + const KMTPFile source = storage->getFileMetadata(path); + if (!source.isValid()) { + error(KIO::ERR_DOES_NOT_EXIST, url.path()); + return; + } - if (!(flags & KIO::Overwrite) && getPath(url.path()).first) { - error(ERR_FILE_ALREADY_EXIST, url.path()); - return; - } + mimeType(source.filetype()); + totalSize(source.filesize()); - destItems.takeLast(); + int result = storage->getFileToHandler(path); + if (result) { + error(KIO::ERR_CANNOT_READ, url.path()); + return; + } - QPair pair = getPath(urlDirectory(url)); + QEventLoop loop; + connect(storage, &KMTPStorageInterface::dataReady, this, [this] (const QByteArray &data) { + MTPSlave::data(data); + }); + connect(storage, &KMTPStorageInterface::copyFinished, &loop, &QEventLoop::exit); + result = loop.exec(); - if (!pair.first) { - error(ERR_DOES_NOT_EXIST, url.path()); + qCDebug(LOG_KIO_MTP) << "data received"; + + if (result) { + error(ERR_CANNOT_READ, url.path()); + return; + } + + data(QByteArray()); + finished(); + return; + } + } + } else { + error(ERR_UNSUPPORTED_ACTION, url.path()); return; } + error(ERR_CANNOT_READ, url.path()); +} - LIBMTP_mtpdevice_t *device = pair.second; - LIBMTP_file_t *parent = (LIBMTP_file_t *) pair.first; - if (parent->filetype != LIBMTP_FILETYPE_FOLDER) { - error(ERR_IS_FILE, urlDirectory(url)); +void MTPSlave::put(const QUrl &url, int, JobFlags flags) +{ + const int check = checkUrl(url); + switch (check) { + case 0: + break; + default: + error(ERR_MALFORMED_URL, url.path()); return; } - // We did get a total size from the application - if (hasMetaData(QLatin1String("sourceSize"))) { - qCDebug(LOG_KIO_MTP) << "direct put"; - - LIBMTP_file_t *file = LIBMTP_new_file_t(); - file->parent_id = parent->item_id; - file->filename = strdup(urlFileName(url).toUtf8().data()); - file->filetype = getFiletype(urlFileName(url)); - file->filesize = metaData(QLatin1String("sourceSize")).toULongLong(); - file->modificationdate = QDateTime::currentDateTime().toTime_t(); - file->storage_id = parent->storage_id; + const QStringList destItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - qCDebug(LOG_KIO_MTP) << "Sending file" << file->filename; - - int ret = LIBMTP_Send_File_From_Handler(device, &dataGet, this, file, &dataProgress, this); - LIBMTP_destroy_file_t(file); - - if (ret != 0) { - error(KIO::ERR_COULD_NOT_WRITE, urlFileName(url)); - LIBMTP_Dump_Errorstack(device); - LIBMTP_Clear_Errorstack(device); - return; - } + // can't copy to root or device, needs storage + if (destItems.size() < 2) { + error(ERR_UNSUPPORTED_ACTION, url.path()); + return; } - // We need to get the entire file first, then we can upload - else { - qCDebug(LOG_KIO_MTP) << "use temp file"; - QTemporaryFile temp; + // we need to get the entire file first, then we can upload + qCDebug(LOG_KIO_MTP) << "use temp file"; + + QTemporaryFile temp; + if (temp.open()) { QByteArray buffer; int len = 0; do { dataReq(); len = readData(buffer); temp.write(buffer); } while (len > 0); - QFileInfo info(temp); - - LIBMTP_file_t *file = LIBMTP_new_file_t(); - file->parent_id = parent->item_id; - file->filename = strdup(urlFileName(url).toUtf8().data()); - file->filetype = getFiletype(urlFileName(url)); - file->filesize = info.size(); - file->modificationdate = QDateTime::currentDateTime().toTime_t(); - file->storage_id = parent->storage_id; - - int ret = LIBMTP_Send_File_From_File_Descriptor(device, temp.handle(), file, nullptr, nullptr); - LIBMTP_destroy_file_t(file); - - if (ret != 0) { - error(KIO::ERR_COULD_NOT_WRITE, urlFileName(url)); - return; - } - finished(); - } -} - -void MTPSlave::get(const QUrl &url) -{ - int check = checkUrl(url); - switch (check) { - case 0: - break; - default: - error(ERR_MALFORMED_URL, url.path()); - return; - } - - qCDebug(LOG_KIO_MTP) << url.path(); - - QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - - // File - if (pathItems.size() > 2) { - QPair pair = getPath(url.path()); + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(destItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(destItems.at(1)); + if (storage) { + const QString destinationPath = convertPath(url.path()); + + // check if the file already exists on the device + const KMTPFile destinationFile = storage->getFileMetadata(destinationPath); + if (destinationFile.isValid()) { + if (flags & KIO::Overwrite) { + // delete existing file on the device + const int result = storage->deleteObject(destinationPath); + if (result) { + error(ERR_CANNOT_DELETE, url.path()); + return; + } + } else { + error(ERR_FILE_ALREADY_EXIST, url.path()); + return; + } + } - if (pair.first) { - LIBMTP_file_t *file = (LIBMTP_file_t *) pair.first; + totalSize(quint64(temp.size())); - mimeType(getMimetype(file->filetype)); - totalSize(file->filesize); + QDBusUnixFileDescriptor descriptor(temp.handle()); + int result = storage->sendFileFromFileDescriptor(descriptor, destinationPath); + if (result) { + error(KIO::ERR_CANNOT_WRITE, urlFileName(url)); + return; + } - LIBMTP_mtpdevice_t *device = pair.second; + result = waitForCopyOperation(storage); + processedSize(quint64(temp.size())); + temp.close(); + + switch (result) { + case 0: + break; + case 2: + error(ERR_IS_FILE, urlDirectory(url)); + return; + default: + error(KIO::ERR_CANNOT_WRITE, urlFileName(url)); + return; + } - int ret = LIBMTP_Get_File_To_Handler(device, file->item_id, &dataPut, this, &dataProgress, this); - if (ret != 0) { - error(ERR_COULD_NOT_READ, url.path()); + qCDebug(LOG_KIO_MTP) << "data sent"; + finished(); return; } - data(QByteArray()); - finished(); - } else { - error(ERR_DOES_NOT_EXIST, url.path()); } - } else { - error(ERR_UNSUPPORTED_ACTION, url.path()); } + + error(KIO::ERR_CANNOT_WRITE, urlFileName(url)); } void MTPSlave::copy(const QUrl &src, const QUrl &dest, int, JobFlags flags) { - qCDebug(LOG_KIO_MTP) << src.path() << dest.path(); - if (src.scheme() == QLatin1String("mtp") && dest.scheme() == QLatin1String("mtp")) { qCDebug(LOG_KIO_MTP) << "Copy on device: Not supported"; // MTP doesn't support moving files directly on the device, so we have to download and then upload... error(ERR_UNSUPPORTED_ACTION, i18n("Cannot copy/move files on the device itself")); + return; } else if (src.scheme() == QLatin1String("file") && dest.scheme() == QLatin1String("mtp")) { - int check = checkUrl(dest); + // copy from filesystem to the device + + const int check = checkUrl(dest); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, dest.path()); return; } QStringList destItems = dest.path().split(QLatin1Char('/') , QString::SkipEmptyParts); - // Can't copy to root or device, needs storage + // can't copy to root or device, needs storage if (destItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, dest.path()); return; } qCDebug(LOG_KIO_MTP) << "Copy file " << urlFileName(src) << "from filesystem to device" << urlDirectory(src, true) << urlDirectory(dest, true); - if (!(flags & KIO::Overwrite) && getPath(dest.path()).first) { - error(ERR_FILE_ALREADY_EXIST, dest.path()); - return; - } - - destItems.takeLast(); - - QPair pair = getPath(urlDirectory(dest)); + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(destItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(destItems.at(1)); + if (storage) { + const QString destinationPath = convertPath(dest.path()); + + // check if the file already exists on the device + const KMTPFile destinationFile = storage->getFileMetadata(destinationPath); + if (destinationFile.isValid()) { + if (flags & KIO::Overwrite) { + // delete existing file on the device + const int result = storage->deleteObject(destinationPath); + if (result) { + error(ERR_CANNOT_DELETE, dest.path()); + return; + } + } else { + error(ERR_FILE_ALREADY_EXIST, dest.path()); + return; + } + } - if (!pair.first) { - error(ERR_DOES_NOT_EXIST, urlDirectory(dest, true)); - return; - } + QFile srcFile(src.path()); + if (!srcFile.open(QIODevice::ReadOnly)) { + error(KIO::ERR_CANNOT_OPEN_FOR_READING, src.path()); + return; + } - LIBMTP_mtpdevice_t *device = pair.second; + qCDebug(LOG_KIO_MTP) << "Sending file" << srcFile.fileName() << "with size" << srcFile.size(); - uint32_t parent_id = 0xFFFFFFFF, storage_id = 0; + totalSize(quint64(srcFile.size())); - if (destItems.size() == 2) { - LIBMTP_devicestorage_t *storage = (LIBMTP_devicestorage_t *) pair.first; + QDBusUnixFileDescriptor descriptor(srcFile.handle()); + int result = storage->sendFileFromFileDescriptor(descriptor, destinationPath); + if (result) { + error(KIO::ERR_CANNOT_WRITE, urlFileName(dest)); + return; + } - storage_id = storage->id; - } else { - LIBMTP_file_t *parent = (LIBMTP_file_t *) pair.first; + result = waitForCopyOperation(storage); + processedSize(quint64(srcFile.size())); + srcFile.close(); - storage_id = parent->storage_id; - parent_id = parent->item_id; + if (result) { + error(KIO::ERR_CANNOT_WRITE, urlFileName(dest)); + return; + } - if (parent->filetype != LIBMTP_FILETYPE_FOLDER) { - error(ERR_IS_FILE, urlDirectory(dest)); + qCDebug(LOG_KIO_MTP) << "Sent file"; + finished(); return; } } + error(KIO::ERR_CANNOT_WRITE, urlFileName(src)); - QFileInfo source(src.path()); - - LIBMTP_file_t *file = LIBMTP_new_file_t(); - file->parent_id = parent_id; - file->filename = strdup(urlFileName(dest).toUtf8().data()); - file->filetype = getFiletype(urlFileName(dest)); - file->filesize = source.size(); - file->modificationdate = source.lastModified().toTime_t(); - file->storage_id = storage_id; - - qCDebug(LOG_KIO_MTP) << "Sending file" << file->filename << "with size" << file->filesize; - - totalSize(source.size()); - - int ret = LIBMTP_Send_File_From_File(device, src.path().toUtf8().data(), file, (LIBMTP_progressfunc_t) &dataProgress, this); - LIBMTP_destroy_file_t(file); - - if (ret != 0) { - error(KIO::ERR_COULD_NOT_WRITE, urlFileName(dest)); - LIBMTP_Dump_Errorstack(device); - LIBMTP_Clear_Errorstack(device); - return; - } - - qCDebug(LOG_KIO_MTP) << "Sent file"; } else if (src.scheme() == QLatin1String("mtp") && dest.scheme() == QLatin1String("file")) { - int check = checkUrl(src); + // copy from the device to filesystem + + const int check = checkUrl(src); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, src.path()); return; } - qCDebug(LOG_KIO_MTP) << "Copy file " << urlFileName(src) << "from device to filesystem" << urlDirectory(src, true) << urlDirectory(dest, true); + qCDebug(LOG_KIO_MTP) << "Copy file" << urlFileName(src) << "from device to filesystem" << urlDirectory(src, true) << urlDirectory(dest, true); QFileInfo destination(dest.path()); if (!(flags & KIO::Overwrite) && destination.exists()) { error(ERR_FILE_ALREADY_EXIST, dest.path()); return; } - QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + const QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - // Can't copy to root or device, needs storage + // can't copy to root or device, needs storage if (srcItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, src.path()); return; } - QPair pair = getPath(src.path()); - if (!pair.first) { - error(ERR_COULD_NOT_READ, src.path()); - return; - } + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(srcItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(srcItems.at(1)); + if (storage) { - LIBMTP_mtpdevice_t *device = pair.second; - LIBMTP_file_t *source = (LIBMTP_file_t *) pair.first; - if (source->filetype == LIBMTP_FILETYPE_FOLDER) { - error(ERR_IS_DIRECTORY, urlDirectory(src)); - return; - } + QFile destFile(dest.path()); + if (!destFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + error(KIO::ERR_WRITE_ACCESS_DENIED, dest.path()); + return; + } - qCDebug(LOG_KIO_MTP) << "Getting file" << source->filename << urlFileName(dest) << source->filesize; + const KMTPFile source = storage->getFileMetadata(convertPath(src.path())); + if (!source.isValid()) { + error(KIO::ERR_DOES_NOT_EXIST, src.path()); + return; + } - totalSize(source->filesize); + totalSize(source.filesize()); - int ret = LIBMTP_Get_File_To_File(device, source->item_id, dest.path().toUtf8().data(), (LIBMTP_progressfunc_t) &dataProgress, this); - if (ret != 0) { - error(KIO::ERR_COULD_NOT_WRITE, urlFileName(dest)); - LIBMTP_Dump_Errorstack(device); - LIBMTP_Clear_Errorstack(device); - return; - } + QDBusUnixFileDescriptor descriptor(destFile.handle()); + int result = storage->getFileToFileDescriptor(descriptor, convertPath(src.path())); + if (result) { + error(KIO::ERR_CANNOT_READ, urlFileName(src)); + return; + } - struct utimbuf *times = (utimbuf *) malloc(sizeof(utimbuf)); - times->actime = QDateTime::currentDateTime().toTime_t(); - times->modtime = source->modificationdate; + result = waitForCopyOperation(storage); + processedSize(quint64(source.filesize())); + destFile.close(); - int result = utime(dest.path().toUtf8().data(), times); + if (result) { + error(KIO::ERR_CANNOT_READ, urlFileName(src)); + return; + } - free(times); + // set correct modification time + struct ::utimbuf times; + times.actime = QDateTime::currentDateTime().toTime_t(); + times.modtime = source.modificationdate(); - qCDebug(LOG_KIO_MTP) << "Sent file"; + ::utime(dest.path().toUtf8().data(), ×); + qCDebug(LOG_KIO_MTP) << "Received file"; + finished(); + return; + } + } + error(KIO::ERR_CANNOT_READ, urlFileName(src)); } - finished(); } void MTPSlave::mkdir(const QUrl &url, int) { - int check = checkUrl(url); + const int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } - qCDebug(LOG_KIO_MTP) << url.path(); - - QStringList pathItems = url.path().split(QLatin1Char('/') , QString::SkipEmptyParts); - int pathDepth = pathItems.size(); - - if (pathItems.size() > 2 && !getPath(url.path()).first) { - char *dirName = strdup(pathItems.takeLast().toUtf8().data()); - - LIBMTP_mtpdevice_t *device; - LIBMTP_file_t *file; - LIBMTP_devicestorage_t *storage; - int ret = 0; - - QPair pair = getPath(urlDirectory(url)); - - if (pathDepth == 3) { - //the folder need to be created straight to a storage device - storage = (LIBMTP_devicestorage_t *) pair.first; - device = pair.second; - ret = LIBMTP_Create_Folder(device, dirName, 0xFFFFFFFF, storage->id); - } else if (pair.first) { - file = (LIBMTP_file_t *) pair.first; - device = pair.second; - - if (file && file->filetype == LIBMTP_FILETYPE_FOLDER) { - qCDebug(LOG_KIO_MTP) << "Found parent" << file->item_id << file->filename; - qCDebug(LOG_KIO_MTP) << "Attempting to create folder" << dirName; - - ret = LIBMTP_Create_Folder(device, dirName, file->item_id, file->storage_id); - + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + if (pathItems.size() > 2) { + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + // TODO: folder already exists + const quint32 itemId = storage->createFolder(convertPath(url.path())); + if (itemId) { + finished(); + return; + } } } - if (ret != 0) { - fileCache->addPath(url.path(), ret); - finished(); - return; - } else { - LIBMTP_Dump_Errorstack(device); - LIBMTP_Clear_Errorstack(device); - } - } else { - error(ERR_DIR_ALREADY_EXIST, url.path()); - return; } - - error(ERR_COULD_NOT_MKDIR, url.path()); + error(ERR_CANNOT_MKDIR, url.path()); } void MTPSlave::del(const QUrl &url, bool) { - int check = checkUrl(url); + const int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } - qCDebug(LOG_KIO_MTP) << url.path(); - - QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - - if (pathItems.size() < 2) { - error(ERR_CANNOT_DELETE, url.path()); - return; - } - - QPair pair = getPath(url.path()); - - LIBMTP_file_t *file = (LIBMTP_file_t *) pair.first; - - int ret = LIBMTP_Delete_Object(pair.second, file->item_id); - - LIBMTP_destroy_file_t (file); - - if (ret != 0) { - error(ERR_CANNOT_DELETE, url.path()); - return; + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + if (pathItems.size() >= 2) { + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + const int result = storage->deleteObject(convertPath(url.path())); + if (!result) { + finished(); + return; + } + } + } } - - fileCache->removePath(url.path()); - finished(); + error(ERR_CANNOT_DELETE, url.path()); } void MTPSlave::rename(const QUrl &src, const QUrl &dest, JobFlags flags) { int check = checkUrl(src); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, src.path()); return; } check = checkUrl(dest); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, dest.path()); return; } - qCDebug(LOG_KIO_MTP) << src.path(); - - QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - QPair pair = getPath(src.path()); + if (src.scheme() != QLatin1String("mtp")) { + // Kate: when editing files directly on the device and the user wants to save the changes, + // Kate tries to move the file from /tmp/xxx to the MTP device which is not supported. + // The ERR_UNSUPPORTED_ACTION error tells Kate to copy the file instead of moving. + error(ERR_UNSUPPORTED_ACTION, src.path()); + return; + } - if (pair.first) { - // Rename Device + const QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(srcItems.first()); + if (mtpDevice) { + // rename Device if (srcItems.size() == 1) { - LIBMTP_Set_Friendlyname(pair.second, urlFileName(dest).toUtf8().data()); + const int result = mtpDevice->setFriendlyName(urlFileName(dest)); + if (!result) { + finished(); + return; + } } - // Rename Storage + // rename Storage else if (srcItems.size() == 2) { error(ERR_CANNOT_RENAME, src.path()); return; } else { - LIBMTP_file_t *destination = (LIBMTP_file_t *) getPath(dest.path()).first; - LIBMTP_file_t *source = (LIBMTP_file_t *) pair.first; - - if (!(flags & KIO::Overwrite) && destination) { - if (destination->filetype == LIBMTP_FILETYPE_FOLDER) { - error(ERR_DIR_ALREADY_EXIST, dest.path()); - } else { - error(ERR_FILE_ALREADY_EXIST, dest.path()); + // rename file or folder + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(srcItems.at(1)); + if (storage) { + + // check if the file already exists on the device + const QString destinationPath = convertPath(dest.path()); + const KMTPFile destinationFile = storage->getFileMetadata(destinationPath); + if (destinationFile.isValid()) { + if (flags & KIO::Overwrite) { + // delete existing file on the device + const int result = storage->deleteObject(destinationPath); + if (result) { + error(ERR_CANNOT_DELETE, dest.path()); + return; + } + } else { + error(ERR_FILE_ALREADY_EXIST, dest.path()); + return; + } } - return; - } - int ret = LIBMTP_Set_File_Name(pair.second, source, urlFileName(dest).toUtf8().data()); - - if (ret != 0) { - error(ERR_CANNOT_RENAME, src.path()); - return; - } else { - fileCache->addPath(dest.path(), source->item_id); - fileCache->removePath(src.path()); + const int result = storage->setFileName(convertPath(src.path()), dest.fileName()); + if (!result) { + finished(); + return; + } } - - LIBMTP_destroy_file_t (source); } - - finished(); - } else { - error(ERR_DOES_NOT_EXIST, src.path()); } + error(ERR_CANNOT_RENAME, src.path()); } void MTPSlave::virtual_hook(int id, void *data) { switch(id) { - case SlaveBase::GetFileSystemFreeSpace: { - QUrl *url = static_cast(data); - fileSystemFreeSpace(*url); - } break; - default: - SlaveBase::virtual_hook(id, data); + case SlaveBase::GetFileSystemFreeSpace: { + QUrl *url = static_cast(data); + fileSystemFreeSpace(*url); + } break; + default: + SlaveBase::virtual_hook(id, data); } } void MTPSlave::fileSystemFreeSpace(const QUrl &url) { qCDebug(LOG_KIO_MTP) << "fileSystemFreeSpace:" << url; const int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.toDisplayString()); return; default: error(ERR_MALFORMED_URL, url.toDisplayString()); return; } - const auto path = url.path(); + const QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); - const auto storagePath = path.section(QLatin1Char('/'), 0, 2, QString::SectionIncludeLeadingSep); - if (storagePath.count(QLatin1Char('/')) != 2) { // /{device}/{storage} - error(KIO::ERR_COULD_NOT_STAT, url.toDisplayString()); - return; - } - - const auto pair = getPath(storagePath); - auto storage = (LIBMTP_devicestorage_t *)pair.first; - if (!storage) { - error(KIO::ERR_COULD_NOT_STAT, url.toDisplayString()); - return; + // Storage + if (pathItems.size() > 1) { + const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first()); + if (mtpDevice) { + const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1)); + if (storage) { + setMetaData(QStringLiteral("total"), QString::number(storage->maxCapacity())); + setMetaData(QStringLiteral("available"), QString::number(storage->freeSpaceInBytes())); + finished(); + return; + } + } } + error(KIO::ERR_COULD_NOT_STAT, url.toDisplayString()); +} - setMetaData(QStringLiteral("total"), QString::number(storage->MaxCapacity)); - setMetaData(QStringLiteral("available"), QString::number(storage->FreeSpaceInBytes)); - - finished(); +int MTPSlave::waitForCopyOperation(const KMTPStorageInterface *storage) +{ + QEventLoop loop; + connect(storage, &KMTPStorageInterface::copyProgress, this, [this] (qulonglong sent, qulonglong total) { + Q_UNUSED(total) + processedSize(sent); + }); + + // any chance to 'miss' the copyFinished signal and dead lock the slave? + connect(storage, &KMTPStorageInterface::copyFinished, &loop, &QEventLoop::exit); + return loop.exec(); } diff --git a/mtp/kio_mtp.h b/mtp/kio_mtp.h index 20439b58..fed83166 100644 --- a/mtp/kio_mtp.h +++ b/mtp/kio_mtp.h @@ -1,95 +1,101 @@ /* * Main implementation for KIO-MTP * Copyright (C) 2012 Philipp Schmidt + * Copyright (C) 2018 Andreas Krutzler * * 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_MTP_H #define KIO_MTP_H #include #include #include #include #include #include #include -#include // #include -#include "filecache.h" -#include "devicecache.h" #include #include #define MAX_XFER_BUF_SIZE 16348 #define KIO_MTP 7000 +#include + +class KMTPDeviceInterface; +class KMTPStorageInterface; +class KMTPFile; using namespace KIO; Q_DECLARE_LOGGING_CATEGORY(LOG_KIO_MTP) class MTPSlave : public QObject, public KIO::SlaveBase { Q_OBJECT public: /* * Overwritten KIO-functions, see "kio_mtp.cpp" */ MTPSlave(const QByteArray &pool, const QByteArray &app); ~MTPSlave() override; void listDir(const QUrl &url) override; void stat(const QUrl &url) override; void mimetype(const QUrl &url) override; void get(const QUrl &url) override; void put(const QUrl &url, int, JobFlags flags) override; void copy(const QUrl &src, const QUrl &dest, int, JobFlags flags) override; void mkdir(const QUrl &url, int) override; void del(const QUrl &url, bool) override; void rename(const QUrl &src, const QUrl &dest, JobFlags flags) override; // private slots: // // void test(); protected: void virtual_hook(int id, void *data) override; private: /** * Check if it is a valid url or an udi. * * @param url The url to checkUrl * @param redirect If udi= should be redirected or just return false * @return 0 if valid, 1 if udi and redirected, 2 if udi but invalid device, -1 else */ int checkUrl(const QUrl &url, bool redirect = true); - static QString urlDirectory(const QUrl &url, bool appendTrailingSlash = false); - static QString urlFileName(const QUrl &url); void fileSystemFreeSpace(const QUrl &url); - FileCache *fileCache; - DeviceCache *deviceCache; - QPair getPath(const QString &path); + /** + * @brief Waits for a pending copy operation to finish while updating its progress. + * @param storage + * @return The result of the operation, usually 0 if valid + */ + int waitForCopyOperation(const KMTPStorageInterface *storage); + + KMTPDInterface m_kmtpDaemon; }; #endif // KIO_MTP_H diff --git a/mtp/kio_mtp_helpers.cpp b/mtp/kio_mtp_helpers.cpp deleted file mode 100644 index 620d1b65..00000000 --- a/mtp/kio_mtp_helpers.cpp +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Helper implementations for KIO-MTP - * Copyright (C) 2013 Philipp Schmidt - * - * 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. - */ - -#include "kio_mtp_helpers.h" - -int dataProgress(uint64_t const sent, uint64_t const, void const *const priv) -{ - ((MTPSlave *) priv)->processedSize(sent); - - return 0; -} - -/** - * MTPDataPutFunc callback function, "puts" data from the device somewhere else - */ -uint16_t dataPut(void *, void *priv, uint32_t sendlen, unsigned char *data, uint32_t *putlen) -{ - qCDebug(LOG_KIO_MTP) << "transferring" << sendlen << "bytes to data()"; - - ((MTPSlave *) priv)->data(QByteArray((char *) data, (int) sendlen)); - *putlen = sendlen; - - return LIBMTP_HANDLER_RETURN_OK; -} - -/** - * MTPDataGetFunc callback function, "gets" data and puts it on the device - */ -uint16_t dataGet(void *, void *priv, uint32_t, unsigned char *data, uint32_t *gotlen) -{ - ((MTPSlave *) priv)->dataReq(); - - QByteArray buffer; - *gotlen = ((MTPSlave *) priv)->readData(buffer); - - qCDebug(LOG_KIO_MTP) << "transferring" << *gotlen << "bytes to data()"; - - data = (unsigned char *) buffer.data(); - - return LIBMTP_HANDLER_RETURN_OK; -} - -QString convertToPath(const QStringList &pathItems, const int elements) -{ - QString path; - - for (int i = 0; i < elements && elements <= pathItems.size(); i++) { - path.append(QLatin1Char('/')); - path.append(pathItems.at(i)); - } - - return path; -} - -QString getMimetype(LIBMTP_filetype_t filetype) -{ - switch (filetype) { - case LIBMTP_FILETYPE_FOLDER: - return QLatin1String("inode/directory"); - - case LIBMTP_FILETYPE_WAV: - return QLatin1String("audio/wav"); - case LIBMTP_FILETYPE_MP3: - return QLatin1String("audio/x-mp3"); - case LIBMTP_FILETYPE_WMA: - return QLatin1String("audio/x-ms-wma"); - case LIBMTP_FILETYPE_OGG: - return QLatin1String("audio/x-vorbis+ogg"); - case LIBMTP_FILETYPE_AUDIBLE: - return QLatin1String(""); - case LIBMTP_FILETYPE_MP4: - return QLatin1String("audio/mp4"); - case LIBMTP_FILETYPE_UNDEF_AUDIO: - return QLatin1String(""); - case LIBMTP_FILETYPE_WMV: - return QLatin1String("video/x-ms-wmv"); - case LIBMTP_FILETYPE_AVI: - return QLatin1String("video/x-msvideo"); - case LIBMTP_FILETYPE_MPEG: - return QLatin1String("video/mpeg"); - case LIBMTP_FILETYPE_ASF: - return QLatin1String("video/x-ms-asf"); - case LIBMTP_FILETYPE_QT: - return QLatin1String("video/quicktime"); - case LIBMTP_FILETYPE_UNDEF_VIDEO: - return QLatin1String(""); - case LIBMTP_FILETYPE_JPEG: - return QLatin1String("image/jpeg"); - case LIBMTP_FILETYPE_JFIF: - return QLatin1String(""); - case LIBMTP_FILETYPE_TIFF: - return QLatin1String("image/tiff"); - case LIBMTP_FILETYPE_BMP: - return QLatin1String("image/bmp"); - case LIBMTP_FILETYPE_GIF: - return QLatin1String("image/gif"); - case LIBMTP_FILETYPE_PICT: - return QLatin1String("image/x-pict"); - case LIBMTP_FILETYPE_PNG: - return QLatin1String("image/png"); - case LIBMTP_FILETYPE_VCALENDAR1: - return QLatin1String("text/x-vcalendar"); - case LIBMTP_FILETYPE_VCALENDAR2: - return QLatin1String("text/x-vcalendar"); - case LIBMTP_FILETYPE_VCARD2: - return QLatin1String("text/x-vcard"); - case LIBMTP_FILETYPE_VCARD3: - return QLatin1String("text/x-vcard"); - case LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT: - return QLatin1String("image/x-wmf"); - case LIBMTP_FILETYPE_WINEXEC: - return QLatin1String("application/x-ms-dos-executable"); - case LIBMTP_FILETYPE_TEXT: - return QLatin1String("text/plain"); - case LIBMTP_FILETYPE_HTML: - return QLatin1String("text/html"); - case LIBMTP_FILETYPE_FIRMWARE: - return QLatin1String(""); - case LIBMTP_FILETYPE_AAC: - return QLatin1String("audio/aac"); - case LIBMTP_FILETYPE_MEDIACARD: - return QLatin1String(""); - case LIBMTP_FILETYPE_FLAC: - return QLatin1String("audio/flac"); - case LIBMTP_FILETYPE_MP2: - return QLatin1String("video/mpeg"); - case LIBMTP_FILETYPE_M4A: - return QLatin1String("audio/mp4"); - case LIBMTP_FILETYPE_DOC: - return QLatin1String("application/msword"); - case LIBMTP_FILETYPE_XML: - return QLatin1String("text/xml"); - case LIBMTP_FILETYPE_XLS: - return QLatin1String("application/vnd.ms-excel"); - case LIBMTP_FILETYPE_PPT: - return QLatin1String("application/vnd.ms-powerpoint"); - case LIBMTP_FILETYPE_MHT: - return QLatin1String(""); - case LIBMTP_FILETYPE_JP2: - return QLatin1String("image/jpeg2000"); - case LIBMTP_FILETYPE_JPX: - return QLatin1String("application/x-jbuilder-project"); - case LIBMTP_FILETYPE_UNKNOWN: - return QLatin1String(""); - - default: - return QLatin1String(""); - } -} - -LIBMTP_filetype_t getFiletype(const QString &filename) -{ - LIBMTP_filetype_t filetype; - - QString ptype = filename.split(QLatin1Char('.')).last(); - - /* This need to be kept constantly updated as new file types arrive. */ - if (ptype == QLatin1String("wav")) { - filetype = LIBMTP_FILETYPE_WAV; - } else if (ptype == QLatin1String("mp3")) { - filetype = LIBMTP_FILETYPE_MP3; - } else if (ptype == QLatin1String("wma")) { - filetype = LIBMTP_FILETYPE_WMA; - } else if (ptype == QLatin1String("ogg")) { - filetype = LIBMTP_FILETYPE_OGG; - } else if (ptype == QLatin1String("mp4")) { - filetype = LIBMTP_FILETYPE_MP4; - } else if (ptype == QLatin1String("wmv")) { - filetype = LIBMTP_FILETYPE_WMV; - } else if (ptype == QLatin1String("avi")) { - filetype = LIBMTP_FILETYPE_AVI; - } else if (ptype == QLatin1String("mpeg") || - ptype == QLatin1String("mpg")) { - filetype = LIBMTP_FILETYPE_MPEG; - } else if (ptype == QLatin1String("asf")) { - filetype = LIBMTP_FILETYPE_ASF; - } else if (ptype == QLatin1String("qt") || - ptype == QLatin1String("mov")) { - filetype = LIBMTP_FILETYPE_QT; - } else if (ptype == QLatin1String("wma")) { - filetype = LIBMTP_FILETYPE_WMA; - } else if (ptype == QLatin1String("jpg") || - ptype == QLatin1String("jpeg")) { - filetype = LIBMTP_FILETYPE_JPEG; - } else if (ptype == QLatin1String("jfif")) { - filetype = LIBMTP_FILETYPE_JFIF; - } else if (ptype == QLatin1String("tif") || - ptype == QLatin1String("tiff")) { - filetype = LIBMTP_FILETYPE_TIFF; - } else if (ptype == QLatin1String("bmp")) { - filetype = LIBMTP_FILETYPE_BMP; - } else if (ptype == QLatin1String("gif")) { - filetype = LIBMTP_FILETYPE_GIF; - } else if (ptype == QLatin1String("pic") || - ptype == QLatin1String("pict")) { - filetype = LIBMTP_FILETYPE_PICT; - } else if (ptype == QLatin1String("png")) { - filetype = LIBMTP_FILETYPE_PNG; - } else if (ptype == QLatin1String("wmf")) { - filetype = LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT; - } else if (ptype == QLatin1String("ics")) { - filetype = LIBMTP_FILETYPE_VCALENDAR2; - } else if (ptype == QLatin1String("exe") || - ptype == QLatin1String("com") || - ptype == QLatin1String("bat") || - ptype == QLatin1String("dll") || - ptype == QLatin1String("sys")) { - filetype = LIBMTP_FILETYPE_WINEXEC; - } else if (ptype == QLatin1String("aac")) { - filetype = LIBMTP_FILETYPE_AAC; - } else if (ptype == QLatin1String("mp2")) { - filetype = LIBMTP_FILETYPE_MP2; - } else if (ptype == QLatin1String("flac")) { - filetype = LIBMTP_FILETYPE_FLAC; - } else if (ptype == QLatin1String("m4a")) { - filetype = LIBMTP_FILETYPE_M4A; - } else if (ptype == QLatin1String("doc")) { - filetype = LIBMTP_FILETYPE_DOC; - } else if (ptype == QLatin1String("xml")) { - filetype = LIBMTP_FILETYPE_XML; - } else if (ptype == QLatin1String("xls")) { - filetype = LIBMTP_FILETYPE_XLS; - } else if (ptype == QLatin1String("ppt")) { - filetype = LIBMTP_FILETYPE_PPT; - } else if (ptype == QLatin1String("mht")) { - filetype = LIBMTP_FILETYPE_MHT; - } else if (ptype == QLatin1String("jp2")) { - filetype = LIBMTP_FILETYPE_JP2; - } else if (ptype == QLatin1String("jpx")) { - filetype = LIBMTP_FILETYPE_JPX; - } else if (ptype == QLatin1String("bin")) { - filetype = LIBMTP_FILETYPE_FIRMWARE; - } else if (ptype == QLatin1String("vcf")) { - filetype = LIBMTP_FILETYPE_VCARD3; - } else { - /* Tagging as unknown file type */ - filetype = LIBMTP_FILETYPE_UNKNOWN; - } - - return filetype; -} - -QMap getDevicestorages(LIBMTP_mtpdevice_t *&device) -{ - qCDebug(LOG_KIO_MTP) << "[ENTER]" << (device == nullptr); - - QMap storages; - if (device) { - for (LIBMTP_devicestorage_t *storage = device->storage; storage != nullptr; storage = storage->next) { - // char *storageIdentifier = storage->VolumeIdentifier; - char *storageDescription = storage->StorageDescription; - - QString storagename; - // if ( !storageIdentifier ) - storagename = QString::fromUtf8(storageDescription); - // else - // storagename = QString::fromUtf8 ( storageIdentifier ); - - qCDebug(LOG_KIO_MTP) << "found storage" << storagename; - - storages.insert(storagename, storage); - } - } - - qCDebug(LOG_KIO_MTP) << "[EXIT]" << storages.size(); - - return storages; -} - -QMap getFiles(LIBMTP_mtpdevice_t *&device, uint32_t storage_id, uint32_t parent_id) -{ - qCDebug(LOG_KIO_MTP) << "getFiles() for parent" << parent_id; - - QMap fileMap; - - LIBMTP_file_t *files = LIBMTP_Get_Files_And_Folders(device, storage_id, parent_id), *file; - for (file = files; file != nullptr; file = file->next) { - fileMap.insert(QString::fromUtf8(file->filename), file); - // qCDebug(LOG_KIO_MTP) << "found file" << file->filename; - } - - qCDebug(LOG_KIO_MTP) << "[EXIT]"; - - return fileMap; -} - -void getEntry(UDSEntry &entry, LIBMTP_mtpdevice_t *device) -{ - char *charName = LIBMTP_Get_Friendlyname(device); - char *charModel = LIBMTP_Get_Modelname(device); - - // prefer friendly devicename over model - QString deviceName; - if (!charName) { - deviceName = QString::fromUtf8(charModel); - } else { - deviceName = QString::fromUtf8(charName); - } - - entry.insert(UDSEntry::UDS_NAME, deviceName); - entry.insert(UDSEntry::UDS_ICON_NAME, QLatin1String("multimedia-player")); - entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); - entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); -} - -void getEntry(UDSEntry &entry, const LIBMTP_devicestorage_t *storage) -{ -// char *charIdentifier = storage->VolumeIdentifier; - char *charDescription = storage->StorageDescription; - - QString storageName; -// if ( !charIdentifier ) - storageName = QString::fromUtf8(charDescription); -// else -// storageName = QString::fromUtf8 ( charIdentifier ); - - entry.insert(UDSEntry::UDS_NAME, storageName); - entry.insert(UDSEntry::UDS_ICON_NAME, QLatin1String("drive-removable-media")); - entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); -} - -void getEntry(UDSEntry &entry, const LIBMTP_file_t *file) -{ - entry.insert(UDSEntry::UDS_NAME, QString::fromUtf8(file->filename)); - if (file->filetype == LIBMTP_FILETYPE_FOLDER) { - entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); - entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); - } else { - entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFREG); - entry.insert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); - entry.insert(UDSEntry::UDS_SIZE, file->filesize); - entry.insert(UDSEntry::UDS_MIME_TYPE, getMimetype(file->filetype)); - } - entry.insert(UDSEntry::UDS_INODE, file->item_id); - entry.insert(UDSEntry::UDS_ACCESS_TIME, file->modificationdate); - entry.insert(UDSEntry::UDS_MODIFICATION_TIME, file->modificationdate); - entry.insert(UDSEntry::UDS_CREATION_TIME, file->modificationdate); -} diff --git a/mtp/kio_mtp_helpers.h b/mtp/kio_mtp_helpers.h deleted file mode 100644 index 673a3b5a..00000000 --- a/mtp/kio_mtp_helpers.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Helper implementations for KIO-MTP - * Copyright (C) 2013 Philipp Schmidt - * - * 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_MTP_HELPERS_H -#define KIO_MTP_HELPERS_H - -#include "kio_mtp.h" - -#include -#include - -Q_DECLARE_LOGGING_CATEGORY(LOG_KIO_MTP) - -int dataProgress(uint64_t const sent, uint64_t const, void const *const priv); -uint16_t dataPut(void *, void *priv, uint32_t sendlen, unsigned char *data, uint32_t *putlen); -uint16_t dataGet(void *, void *priv, uint32_t, unsigned char *data, uint32_t *gotlen); - -QString convertToPath(const QStringList &pathItems, const int elements); - -QString getMimetype(LIBMTP_filetype_t filetype); -LIBMTP_filetype_t getFiletype(const QString &filename); - -QMap getDevicestorages(LIBMTP_mtpdevice_t *&device); -QMap getFiles(LIBMTP_mtpdevice_t *&device, uint32_t storage_id, uint32_t parent_id = 0xFFFFFFFF); - -void getEntry(UDSEntry &entry, LIBMTP_mtpdevice_t *device); -void getEntry(UDSEntry &entry, const LIBMTP_devicestorage_t *storage); -void getEntry(UDSEntry &entry, const LIBMTP_file_t *file); - -#endif // KIO_MTP_HELPERS_H diff --git a/mtp/kiod_module/CMakeLists.txt b/mtp/kiod_module/CMakeLists.txt new file mode 100644 index 00000000..15647213 --- /dev/null +++ b/mtp/kiod_module/CMakeLists.txt @@ -0,0 +1,19 @@ +add_definitions(-DQT_NO_CAST_FROM_ASCII) + +set(kiod_kmtpd_SRCS + kmtpd.cpp + mtpdevice.cpp + mtpstorage.cpp +) + +include_directories(${MTP_INCLUDE_DIR} "../shared") + +qt5_add_dbus_adaptor(kiod_kmtpd_SRCS ../shared/org.kde.kmtp.daemon.xml kmtpd.h KMTPd) +qt5_add_dbus_adaptor(kiod_kmtpd_SRCS ../shared/org.kde.kmtp.device.xml mtpdevice.h MTPDevice) +qt5_add_dbus_adaptor(kiod_kmtpd_SRCS ../shared/org.kde.kmtp.storage.xml mtpstorage.h MTPStorage) + +kcoreaddons_add_plugin(kmtpd INSTALL_NAMESPACE "kf5/kiod" JSON kmtpd.json SOURCES ${kiod_kmtpd_SRCS}) + +target_link_libraries(kmtpd ${MTP_LIBRARIES} kmtp Qt5::Core KF5::CoreAddons KF5::DBusAddons KF5::Solid) + +kdbusaddons_generate_dbus_service_file(kiod5 org.kde.kmtp.daemon ${KDE_INSTALL_FULL_LIBEXECDIR_KF5}) diff --git a/mtp/kiod_module/kmtpd.cpp b/mtp/kiod_module/kmtpd.cpp new file mode 100644 index 00000000..92ca1150 --- /dev/null +++ b/mtp/kiod_module/kmtpd.cpp @@ -0,0 +1,171 @@ +/* + This file is part of the MTP KIOD module, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + 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, see . +*/ + +#include "kmtpd.h" + +#include +#include + +#include +#include +#include + +#include "daemonadaptor.h" + +K_PLUGIN_FACTORY_WITH_JSON(MtpdFactory, "kmtpd.json", registerPlugin(); ) + +Q_LOGGING_CATEGORY(LOG_KIOD_KMTPD, "kf5.kiod.kmtpd") + +KMTPd::KMTPd(QObject *parent, const QList ¶meters) + : KDEDModule(parent) +{ + Q_UNUSED(parameters) + + LIBMTP_Init(); + + // search for already connected devices + for (const Solid::Device &solidDevice : Solid::Device::listFromType(Solid::DeviceInterface::PortableMediaPlayer)) { + checkDevice(solidDevice); + } + + auto notifier = Solid::DeviceNotifier::instance(); + connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &KMTPd::deviceAdded); + connect(notifier, &Solid::DeviceNotifier::deviceRemoved, this, &KMTPd::deviceRemoved); + + new DaemonAdaptor(this); + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kiod5")); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kmtpd"), this); +} + +KMTPd::~KMTPd() +{ + // Release devices + for (const MTPDevice *device : qAsConst(m_devices)) { + deviceRemoved(device->udi()); + } +} + +QString KMTPd::version() const +{ + return QStringLiteral(LIBMTP_VERSION_STRING); +} + +void KMTPd::checkDevice(const Solid::Device &solidDevice) +{ + if (!deviceFromUdi(solidDevice.udi())) { + qCDebug(LOG_KIOD_KMTPD) << "new device, getting raw devices"; + + const Solid::GenericInterface *iface = solidDevice.as(); + if (!iface) { + qCDebug(LOG_KIOD_KMTPD) << "Solid device " << solidDevice.udi() << " has NOT a Solid::GenericInterface"; + return; + } + + const QMap &properties = iface->allProperties(); + const quint32 solidBusNum = properties.value(QStringLiteral("BUSNUM")).toUInt(); + const quint32 solidDevNum = properties.value(QStringLiteral("DEVNUM")).toUInt(); + + LIBMTP_raw_device_t *rawdevices = nullptr; + int numrawdevices; + LIBMTP_error_number_t err; + + err = LIBMTP_Detect_Raw_Devices(&rawdevices, &numrawdevices); + switch (err) { + case LIBMTP_ERROR_CONNECTING: + qCWarning(LOG_KIOD_KMTPD) << "There has been an error connecting to the devices"; + break; + case LIBMTP_ERROR_MEMORY_ALLOCATION: + qCWarning(LOG_KIOD_KMTPD) << "Encountered a Memory Allocation Error"; + break; + case LIBMTP_ERROR_NONE: { + qCDebug(LOG_KIOD_KMTPD) << "No Error, continuing"; + + for (int i = 0; i < numrawdevices; i++) { + LIBMTP_raw_device_t *rawDevice = &rawdevices[i]; + uint32_t rawBusNum = rawDevice->bus_location; + uint32_t rawDevNum = rawDevice->devnum; + + if (rawBusNum == solidBusNum && rawDevNum == solidDevNum) { + qCDebug(LOG_KIOD_KMTPD) << "Found device matching the Solid description"; + + LIBMTP_mtpdevice_t *mtpDevice = LIBMTP_Open_Raw_Device_Uncached(rawDevice); + if (mtpDevice) { + MTPDevice *device = new MTPDevice(QStringLiteral("/modules/kmtpd/device%1").arg(m_devices.count()), mtpDevice, rawDevice, solidDevice.udi(), m_timeout); + m_devices.append(device); + emit devicesChanged(); + } else { + qCWarning(LOG_KIOD_KMTPD) << "LIBMTP_Open_Raw_Device_Uncached: Could not open MTP device"; + } + } + } + } + break; + case LIBMTP_ERROR_GENERAL: + default: + qCWarning(LOG_KIOD_KMTPD) << "Unknown connection error"; + break; + } + free(rawdevices); + } +} + +MTPDevice *KMTPd::deviceFromUdi(const QString &udi) const +{ + auto deviceIt = std::find_if(m_devices.constBegin(), m_devices.constEnd(), [udi] (const MTPDevice *device) { + return device->udi() == udi; + }); + + return deviceIt == m_devices.constEnd() ? nullptr : *deviceIt; +} + +QList KMTPd::listDevices() const +{ + QList list; + for (const auto &device : m_devices) { + list.append(QDBusObjectPath(device->dbusObjectName())); + } + + return list; +} + +void KMTPd::deviceAdded(const QString &udi) +{ + qCDebug(LOG_KIOD_KMTPD) << "New device attached with udi=" << udi << ". Checking if PortableMediaPlayer..."; + + const Solid::Device device(udi); + if (device.isDeviceInterface(Solid::DeviceInterface::PortableMediaPlayer)) { + qCDebug(LOG_KIOD_KMTPD) << "SOLID: New Device with udi=" << udi; + + checkDevice(device); + } +} + +void KMTPd::deviceRemoved(const QString &udi) +{ + MTPDevice *device = deviceFromUdi(udi); + if (device) { + qCDebug(LOG_KIOD_KMTPD) << "SOLID: Device with udi=" << udi << " removed."; + + m_devices.removeOne(device); + delete device; + emit devicesChanged(); + } +} + +#include "kmtpd.moc" diff --git a/mtp/kiod_module/kmtpd.h b/mtp/kiod_module/kmtpd.h new file mode 100644 index 00000000..3d5e40c3 --- /dev/null +++ b/mtp/kiod_module/kmtpd.h @@ -0,0 +1,63 @@ +/* + This file is part of the MTP KIOD module, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + 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, see . +*/ + +#ifndef KMTPD_H +#define KMTPD_H + +#include +#include + +#include + +#include "mtpdevice.h" + +Q_DECLARE_LOGGING_CATEGORY(LOG_KIOD_KMTPD) + +class KMTPd : public KDEDModule +{ + Q_OBJECT + Q_PROPERTY(QString version READ version CONSTANT) + +public: + explicit KMTPd(QObject *parent, const QList ¶meters); + ~KMTPd() override; + + QString version() const; + +private: + void checkDevice(const Solid::Device &solidDevice); + MTPDevice *deviceFromUdi(const QString &udi) const; + + QList m_devices; + qint32 m_timeout; + +public slots: + // D-Bus methods + QList listDevices() const; + +private slots: + void deviceAdded(const QString &udi); + void deviceRemoved(const QString &udi); + +signals: + // D-Bus signals + void devicesChanged(); +}; + +#endif // KMTPD_H diff --git a/mtp/kiod_module/kmtpd.json b/mtp/kiod_module/kmtpd.json new file mode 100644 index 00000000..f486ad6f --- /dev/null +++ b/mtp/kiod_module/kmtpd.json @@ -0,0 +1,12 @@ +{ + "KPlugin": { + "Description": "TODO", + "Name": "KDE MTP daemon", + "ServiceTypes": [ + "KDEDModule" + ] + }, + "X-KDE-DBus-ServiceName": "kmtpd", + "X-KDE-Kded-autoload": false, + "X-KDE-Kded-load-on-demand": true +} diff --git a/mtp/kiod_module/mtpdevice.cpp b/mtp/kiod_module/mtpdevice.cpp new file mode 100644 index 00000000..571eb6b5 --- /dev/null +++ b/mtp/kiod_module/mtpdevice.cpp @@ -0,0 +1,127 @@ +/* + This file is part of the MTP KIOD module, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + 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, see . +*/ + +#include "mtpdevice.h" + +#include + +#include +#include +#include + +#include "mtpstorage.h" + +// D-Bus adaptors +#include "deviceadaptor.h" + +/** + * Creates a Cached Device that has a predefined lifetime (default: 10000 msec)s + * The lifetime is reset every time the device is accessed. After it expires it + * will be released. + * + * @param device The LIBMTP_mtpdevice_t pointer to cache + * @param udi The UDI of the new device to cache + */ +MTPDevice::MTPDevice(const QString &dbusObjectPath, LIBMTP_mtpdevice_t *device, LIBMTP_raw_device_t *rawdevice, const QString &udi, qint32 timeout, QObject *parent) + : QObject(parent), + m_dbusObjectName(dbusObjectPath), + m_timeout(timeout), + m_mtpdevice(device), + m_rawdevice(*rawdevice), + m_udi(udi) +{ + const char *deviceName = LIBMTP_Get_Friendlyname(device); + const char *deviceModel = LIBMTP_Get_Modelname(device); + + // prefer friendly devicename over model + if (!deviceName) { + m_friendlyName = QString::fromUtf8(deviceModel); + } else { + m_friendlyName = QString::fromUtf8(deviceName); + } + + qCDebug(LOG_KIOD_KMTPD) << "Created device " << m_friendlyName << " with udi=" << udi << " and timeout " << timeout; + + new DeviceAdaptor(this); + QDBusConnection::sessionBus().registerObject(m_dbusObjectName, this); + + int index = 0; + for (LIBMTP_devicestorage_t *storage = device->storage; storage != nullptr; storage = storage->next) { + m_storages.append(new MTPStorage(QStringLiteral("%1/storage%2").arg(m_dbusObjectName).arg(index++), storage, this)); + } +} + +MTPDevice::~MTPDevice() +{ + qCDebug(LOG_KIOD_KMTPD) << "release device:" << m_friendlyName; + LIBMTP_Release_Device(m_mtpdevice); +} + +LIBMTP_mtpdevice_t *MTPDevice::getDevice() +{ + if (!m_mtpdevice->storage) { + qCDebug(LOG_KIOD_KMTPD) << "no storage found: reopen mtpdevice"; + LIBMTP_Release_Device(m_mtpdevice); + m_mtpdevice = LIBMTP_Open_Raw_Device_Uncached(&m_rawdevice); + } + + return m_mtpdevice; +} + +QString MTPDevice::dbusObjectName() const +{ + return m_dbusObjectName; +} + +QString MTPDevice::udi() const +{ + return m_udi; +} + +QString MTPDevice::friendlyName() const +{ + return m_friendlyName; +} + +int MTPDevice::setFriendlyName(const QString &friendlyName) +{ + if (m_friendlyName == friendlyName) { + return 1; + } + + const int result = LIBMTP_Set_Friendlyname(m_mtpdevice, friendlyName.toUtf8().constData()); + if (!result) { + m_friendlyName = friendlyName; + emit friendlyNameChanged(m_friendlyName); + + } + return result; +} + +QList MTPDevice::listStorages() const +{ + QList list; + list.reserve(m_storages.count()); + for (const MTPStorage *storage : m_storages) { + list.append(QDBusObjectPath(storage->dbusObjectPath())); + } + return list; +} + +#include "moc_mtpdevice.cpp" diff --git a/mtp/kiod_module/mtpdevice.h b/mtp/kiod_module/mtpdevice.h new file mode 100644 index 00000000..26125ab2 --- /dev/null +++ b/mtp/kiod_module/mtpdevice.h @@ -0,0 +1,78 @@ +/* + This file is part of the MTP KIOD module, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + 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, see . +*/ + +#ifndef MTPDEVICE_H +#define MTPDEVICE_H + +#include + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(LOG_KIOD_KMTPD) + +class MTPStorage; + +/** + * @brief This D-Bus interface is used to access a single MTP device. + */ +class MTPDevice : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString udi READ udi CONSTANT) + Q_PROPERTY(QString friendlyName READ friendlyName NOTIFY friendlyNameChanged) + +public: + explicit MTPDevice(const QString &dbusObjectPath, + LIBMTP_mtpdevice_t *device, + LIBMTP_raw_device_t *rawdevice, + const QString &udi, + qint32 timeout, + QObject *parent = nullptr); + ~MTPDevice() override; + + LIBMTP_mtpdevice_t *getDevice(); + QString dbusObjectName() const; + + // D-Bus properties + QString udi() const; + QString friendlyName() const; + +private: + const QString m_dbusObjectName; + QList m_storages; + qint32 m_timeout; + + LIBMTP_mtpdevice_t *m_mtpdevice; + LIBMTP_raw_device_t m_rawdevice; + + QString m_udi; + QString m_friendlyName; + +public slots: + // D-Bus methods + + int setFriendlyName(const QString &friendlyName); + QList listStorages() const; + +signals: + void friendlyNameChanged(const QString &friendlyName); +}; + +#endif // MTPDEVICE_H diff --git a/mtp/kiod_module/mtpstorage.cpp b/mtp/kiod_module/mtpstorage.cpp new file mode 100644 index 00000000..30a72ab5 --- /dev/null +++ b/mtp/kiod_module/mtpstorage.cpp @@ -0,0 +1,635 @@ +/* + This file is part of the MTP KIOD module, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + 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, see . +*/ + +#include "mtpstorage.h" + +#include +#include + +#include "mtpdevice.h" +#include "storageadaptor.h" + +/** + * @brief MTPDataPutFunc callback function, "puts" data from the device somewhere else + */ +static uint16_t onDataPut(void *, void *priv, uint32_t sendlen, unsigned char *data, uint32_t *putlen) +{ + MTPStorage *storage = static_cast(priv); + emit storage->dataReady(QByteArray(reinterpret_cast(data), int(sendlen))); + *putlen = sendlen; + + return LIBMTP_HANDLER_RETURN_OK; +} + +static int onDataProgress(const uint64_t sent, const uint64_t total, const void * const priv) +{ + MTPStorage *storage = const_cast(static_cast(priv)); + emit storage->copyProgress(sent, total); + return LIBMTP_HANDLER_RETURN_OK; +} + +static QString convertToPath(const QStringList &pathItems, const int elements) +{ + QString path; + + for (int i = 0; i < elements && elements <= pathItems.size(); i++) { + path.append(QLatin1Char('/')); + path.append(pathItems.at(i)); + } + + return path; +} + +/** + * @brief Converts a mime-type to a LIBMTP_filetype_t + */ +static LIBMTP_filetype_t getFiletype(const QString &filename) +{ + LIBMTP_filetype_t filetype; + + const QString ptype = filename.split(QLatin1Char('.')).last(); + + /* This need to be kept constantly updated as new file types arrive. */ + if (ptype == QLatin1String("wav")) { + filetype = LIBMTP_FILETYPE_WAV; + } else if (ptype == QLatin1String("mp3")) { + filetype = LIBMTP_FILETYPE_MP3; + } else if (ptype == QLatin1String("wma")) { + filetype = LIBMTP_FILETYPE_WMA; + } else if (ptype == QLatin1String("ogg")) { + filetype = LIBMTP_FILETYPE_OGG; + } else if (ptype == QLatin1String("mp4")) { + filetype = LIBMTP_FILETYPE_MP4; + } else if (ptype == QLatin1String("wmv")) { + filetype = LIBMTP_FILETYPE_WMV; + } else if (ptype == QLatin1String("avi")) { + filetype = LIBMTP_FILETYPE_AVI; + } else if (ptype == QLatin1String("mpeg") || + ptype == QLatin1String("mpg")) { + filetype = LIBMTP_FILETYPE_MPEG; + } else if (ptype == QLatin1String("asf")) { + filetype = LIBMTP_FILETYPE_ASF; + } else if (ptype == QLatin1String("qt") || + ptype == QLatin1String("mov")) { + filetype = LIBMTP_FILETYPE_QT; + } else if (ptype == QLatin1String("wma")) { + filetype = LIBMTP_FILETYPE_WMA; + } else if (ptype == QLatin1String("jpg") || + ptype == QLatin1String("jpeg")) { + filetype = LIBMTP_FILETYPE_JPEG; + } else if (ptype == QLatin1String("jfif")) { + filetype = LIBMTP_FILETYPE_JFIF; + } else if (ptype == QLatin1String("tif") || + ptype == QLatin1String("tiff")) { + filetype = LIBMTP_FILETYPE_TIFF; + } else if (ptype == QLatin1String("bmp")) { + filetype = LIBMTP_FILETYPE_BMP; + } else if (ptype == QLatin1String("gif")) { + filetype = LIBMTP_FILETYPE_GIF; + } else if (ptype == QLatin1String("pic") || + ptype == QLatin1String("pict")) { + filetype = LIBMTP_FILETYPE_PICT; + } else if (ptype == QLatin1String("png")) { + filetype = LIBMTP_FILETYPE_PNG; + } else if (ptype == QLatin1String("wmf")) { + filetype = LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT; + } else if (ptype == QLatin1String("ics")) { + filetype = LIBMTP_FILETYPE_VCALENDAR2; + } else if (ptype == QLatin1String("exe") || + ptype == QLatin1String("com") || + ptype == QLatin1String("bat") || + ptype == QLatin1String("dll") || + ptype == QLatin1String("sys")) { + filetype = LIBMTP_FILETYPE_WINEXEC; + } else if (ptype == QLatin1String("aac")) { + filetype = LIBMTP_FILETYPE_AAC; + } else if (ptype == QLatin1String("mp2")) { + filetype = LIBMTP_FILETYPE_MP2; + } else if (ptype == QLatin1String("flac")) { + filetype = LIBMTP_FILETYPE_FLAC; + } else if (ptype == QLatin1String("m4a")) { + filetype = LIBMTP_FILETYPE_M4A; + } else if (ptype == QLatin1String("doc")) { + filetype = LIBMTP_FILETYPE_DOC; + } else if (ptype == QLatin1String("xml")) { + filetype = LIBMTP_FILETYPE_XML; + } else if (ptype == QLatin1String("xls")) { + filetype = LIBMTP_FILETYPE_XLS; + } else if (ptype == QLatin1String("ppt")) { + filetype = LIBMTP_FILETYPE_PPT; + } else if (ptype == QLatin1String("mht")) { + filetype = LIBMTP_FILETYPE_MHT; + } else if (ptype == QLatin1String("jp2")) { + filetype = LIBMTP_FILETYPE_JP2; + } else if (ptype == QLatin1String("jpx")) { + filetype = LIBMTP_FILETYPE_JPX; + } else if (ptype == QLatin1String("bin")) { + filetype = LIBMTP_FILETYPE_FIRMWARE; + } else if (ptype == QLatin1String("vcf")) { + filetype = LIBMTP_FILETYPE_VCARD3; + } else { + /* Tagging as unknown file type */ + filetype = LIBMTP_FILETYPE_UNKNOWN; + } + + return filetype; +} + +/** + * @brief Converts a LIBMTP_filetype_t to a mime-type + */ +static QString getMimetype(LIBMTP_filetype_t filetype) +{ + switch (filetype) { + case LIBMTP_FILETYPE_FOLDER: + return QStringLiteral("inode/directory"); + + case LIBMTP_FILETYPE_WAV: + return QStringLiteral("audio/wav"); + case LIBMTP_FILETYPE_MP3: + return QStringLiteral("audio/x-mp3"); + case LIBMTP_FILETYPE_WMA: + return QStringLiteral("audio/x-ms-wma"); + case LIBMTP_FILETYPE_OGG: + return QStringLiteral("audio/x-vorbis+ogg"); + case LIBMTP_FILETYPE_AUDIBLE: + return QString(); + case LIBMTP_FILETYPE_MP4: + return QStringLiteral("audio/mp4"); + case LIBMTP_FILETYPE_UNDEF_AUDIO: + return QString(); + case LIBMTP_FILETYPE_WMV: + return QStringLiteral("video/x-ms-wmv"); + case LIBMTP_FILETYPE_AVI: + return QStringLiteral("video/x-msvideo"); + case LIBMTP_FILETYPE_MPEG: + return QStringLiteral("video/mpeg"); + case LIBMTP_FILETYPE_ASF: + return QStringLiteral("video/x-ms-asf"); + case LIBMTP_FILETYPE_QT: + return QStringLiteral("video/quicktime"); + case LIBMTP_FILETYPE_UNDEF_VIDEO: + return QString(); + case LIBMTP_FILETYPE_JPEG: + return QStringLiteral("image/jpeg"); + case LIBMTP_FILETYPE_JFIF: + return QString(); + case LIBMTP_FILETYPE_TIFF: + return QStringLiteral("image/tiff"); + case LIBMTP_FILETYPE_BMP: + return QStringLiteral("image/bmp"); + case LIBMTP_FILETYPE_GIF: + return QStringLiteral("image/gif"); + case LIBMTP_FILETYPE_PICT: + return QStringLiteral("image/x-pict"); + case LIBMTP_FILETYPE_PNG: + return QStringLiteral("image/png"); + case LIBMTP_FILETYPE_VCALENDAR1: + return QStringLiteral("text/x-vcalendar"); + case LIBMTP_FILETYPE_VCALENDAR2: + return QStringLiteral("text/x-vcalendar"); + case LIBMTP_FILETYPE_VCARD2: + return QStringLiteral("text/x-vcard"); + case LIBMTP_FILETYPE_VCARD3: + return QStringLiteral("text/x-vcard"); + case LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT: + return QStringLiteral("image/x-wmf"); + case LIBMTP_FILETYPE_WINEXEC: + return QStringLiteral("application/x-ms-dos-executable"); + case LIBMTP_FILETYPE_TEXT: + return QStringLiteral("text/plain"); + case LIBMTP_FILETYPE_HTML: + return QStringLiteral("text/html"); + case LIBMTP_FILETYPE_FIRMWARE: + return QString(); + case LIBMTP_FILETYPE_AAC: + return QStringLiteral("audio/aac"); + case LIBMTP_FILETYPE_MEDIACARD: + return QString(); + case LIBMTP_FILETYPE_FLAC: + return QStringLiteral("audio/flac"); + case LIBMTP_FILETYPE_MP2: + return QStringLiteral("video/mpeg"); + case LIBMTP_FILETYPE_M4A: + return QStringLiteral("audio/mp4"); + case LIBMTP_FILETYPE_DOC: + return QStringLiteral("application/msword"); + case LIBMTP_FILETYPE_XML: + return QStringLiteral("text/xml"); + case LIBMTP_FILETYPE_XLS: + return QStringLiteral("application/vnd.ms-excel"); + case LIBMTP_FILETYPE_PPT: + return QStringLiteral("application/vnd.ms-powerpoint"); + case LIBMTP_FILETYPE_MHT: + return QString(); + case LIBMTP_FILETYPE_JP2: + return QStringLiteral("image/jpeg2000"); + case LIBMTP_FILETYPE_JPX: + return QStringLiteral("application/x-jbuilder-project"); + case LIBMTP_FILETYPE_UNKNOWN: + default: + return QString(); + } +} + +/** + * @brief Creates @ref KMTPFile from LIBMTP_file_t + * + * @param file Must not be a nullptr! + */ +static KMTPFile createMTPFile(const LIBMTP_file_t *file) +{ + return KMTPFile(file->item_id, + file->parent_id, + file->storage_id, + file->filename, + file->filesize, + file->modificationdate, + getMimetype(file->filetype)); +} + +MTPStorage::MTPStorage(const QString &dbusObjectPath, const LIBMTP_devicestorage_t *mtpStorage, MTPDevice *parent) + : QObject(parent), + m_dbusObjectPath(dbusObjectPath) +{ + setStorageProperties(mtpStorage); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + new StorageAdaptor(this); + QDBusConnection::sessionBus().registerObject(m_dbusObjectPath, this); +} + +QString MTPStorage::dbusObjectPath() const +{ + return m_dbusObjectPath; +} + +QString MTPStorage::description() const +{ + return m_description; +} + +quint64 MTPStorage::maxCapacity() const +{ + return m_maxCapacity; +} + +quint64 MTPStorage::freeSpaceInBytes() +{ + updateStorageInfo(); + return m_freeSpaceInBytes; +} + +void MTPStorage::setStorageProperties(const LIBMTP_devicestorage_t *storage) +{ + m_id = storage->id; + m_maxCapacity = storage->MaxCapacity; + m_freeSpaceInBytes = storage->FreeSpaceInBytes; + m_description = QString::fromUtf8(storage->StorageDescription); +} + +void MTPStorage::updateStorageInfo() +{ + if (!LIBMTP_Get_Storage(getDevice(), LIBMTP_STORAGE_SORTBY_NOTSORTED)) { + for (const LIBMTP_devicestorage_t *storage = getDevice()->storage; storage != nullptr; storage = storage->next) { + if (m_id == storage->id) { + qCDebug(LOG_KIOD_KMTPD) << "storage info updated"; + setStorageProperties(storage); + break; + } + } + } +} + +LIBMTP_mtpdevice_t *MTPStorage::getDevice() const +{ + return static_cast(parent())->getDevice(); +} + +KMTPFile MTPStorage::getFileFromPath(const QString &path) +{ + const QStringList pathItems = path.split(QLatin1Char('/'), QString::SkipEmptyParts); + + // don't handle the root directory + if (!pathItems.isEmpty()) { + + // 1. check if the file is in the cache + const quint32 itemId = queryPath(path); + if (itemId) { + qCDebug(LOG_KIOD_KMTPD) << "Match found in cache, checking device"; + + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata(getDevice(), itemId); + if (file) { + qCDebug(LOG_KIOD_KMTPD) << "Found file in cache"; + const KMTPFile mtpFile = createMTPFile(file); + LIBMTP_destroy_file_t(file); + return mtpFile; + } + } + + // 2. query cache for parent + else if (pathItems.size() > 1) { + QString parentPath = convertToPath(pathItems, pathItems.size() - 1); + quint32 parentId = queryPath(parentPath); + + qCDebug(LOG_KIOD_KMTPD) << "Match for parent found in cache, checking device. Parent id = " << parentId; + + LIBMTP_file_t *parent = LIBMTP_Get_Filemetadata(getDevice(), parentId); + if (parent) { + qCDebug(LOG_KIOD_KMTPD) << "Found parent in cache"; + + const KMTPFileList list = getFilesAndFoldersCached(parentPath, parentId); + const auto it = std::find_if(list.constBegin(), list.constEnd(), [pathItems](const KMTPFile & file) { + return file.filename() == pathItems.last(); + }); + + LIBMTP_destroy_file_t(parent); + if (it != list.constEnd()) { + qCDebug(LOG_KIOD_KMTPD) << "Found file from cached parent"; + return *it; + } + } + } + } + + // 3. traverse further while depth not reached + QString currentPath; + quint32 currentParent = LIBMTP_FILES_AND_FOLDERS_ROOT; + + for (const QString &element : pathItems) { + const KMTPFileList list = getFilesAndFoldersCached(currentPath, currentParent); + auto it = std::find_if(list.constBegin(), list.constEnd(), [element] (const KMTPFile &file) { + return file.filename() == element; + }); + + if (it != list.constEnd()) { + currentParent = it->itemId(); + } else { + qCDebug(LOG_KIOD_KMTPD) << "File not found!"; + return KMTPFile(); + } + currentPath.append(QLatin1Char('/') + element); + } + + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata(getDevice(), currentParent); + if (file) { + qCDebug(LOG_KIOD_KMTPD) << "Found file using tree walk"; + + const KMTPFile mtpFile = createMTPFile(file); + LIBMTP_destroy_file_t(file); + return mtpFile; + } + return KMTPFile(); +} + +KMTPFileList MTPStorage::getFilesAndFoldersCached(const QString &path, quint32 parentId) +{ + KMTPFileList mtpFiles; + + LIBMTP_file_t *tmp = nullptr; + LIBMTP_file_t *file = LIBMTP_Get_Files_And_Folders(getDevice(), m_id, parentId); + while (file != nullptr) { + const KMTPFile mtpFile = createMTPFile(file); + addPath(path + QLatin1Char('/') + mtpFile.filename(), mtpFile.itemId()); + mtpFiles.append(mtpFile); + + tmp = file; + file = file->next; + LIBMTP_destroy_file_t(tmp); + } + return mtpFiles; +} + +quint32 MTPStorage::queryPath(const QString &path, int timeToLive) +{ + QPair< QDateTime, uint32_t > item = m_cache.value(path); + + if (item.second != 0) { + QDateTime dateTime = QDateTime::currentDateTimeUtc(); + + if (item.first > dateTime) { + item.first = dateTime.addSecs(timeToLive); + m_cache.insert(path, item); + return item.second; + } else { + m_cache.remove(path); + return 0; + } + } + + return 0; +} + +void MTPStorage::addPath(const QString &path, quint32 id, int timeToLive) +{ + QDateTime dateTime = QDateTime::currentDateTimeUtc(); + dateTime = dateTime.addSecs(timeToLive); + + QPair< QDateTime, uint32_t > item(dateTime, id); + + m_cache.insert(path, item); +} + +void MTPStorage::removePath(const QString &path) +{ + m_cache.remove(path); +} + +KMTPFileList MTPStorage::getFilesAndFolders(const QString &path, int &result) +{ + result = 0; + if (path.isEmpty() || path == QLatin1String("/")) { + // list root directory + return getFilesAndFoldersCached(path, LIBMTP_FILES_AND_FOLDERS_ROOT); + } + const KMTPFile file = getFileFromPath(path); + if (!file.isValid()) { + result = 1; // not existing + return KMTPFileList(); + } else if (!file.isFolder()) { + result = 2; // is file + return KMTPFileList(); + } + return getFilesAndFoldersCached(path, file.itemId()); +} + +KMTPFile MTPStorage::getFileMetadata(const QString &path) +{ + qCDebug(LOG_KIOD_KMTPD) << "getFileMetadata:" << path; + return getFileFromPath(path); +} + +int MTPStorage::getFileToHandler(const QString &path) +{ + qCDebug(LOG_KIOD_KMTPD) << "getFileToHandler:" << path; + + const KMTPFile source = getFileMetadata(path); + if (source.isValid()) { + const quint32 itemId = source.itemId(); + QTimer::singleShot(0, this, [this, itemId] { + const int result = LIBMTP_Get_File_To_Handler(getDevice(), itemId, onDataPut, this, onDataProgress, this); + if (result) { + LIBMTP_Dump_Errorstack(getDevice()); + LIBMTP_Clear_Errorstack(getDevice()); + } + emit copyFinished(result); + }); + return 0; + } + return 1; +} + +int MTPStorage::getFileToFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &sourcePath) +{ + qCDebug(LOG_KIOD_KMTPD) << "getFileToFileDescriptor:" << sourcePath; + + const KMTPFile source = getFileMetadata(sourcePath); + if (source.isValid()) { + const quint32 itemId = source.itemId(); + + // big files take some time to copy, and this may lead into D-Bus timeouts. + // therefore the actual copying is not done within the D-Bus method itself but right after we return to the event loop + QTimer::singleShot(0, this, [this, itemId, descriptor] { + const int result = LIBMTP_Get_File_To_File_Descriptor(getDevice(), itemId, descriptor.fileDescriptor(), onDataProgress, this); + if (result) { + LIBMTP_Dump_Errorstack(getDevice()); + LIBMTP_Clear_Errorstack(getDevice()); + } + emit copyFinished(result); + }); + return 0; + } + return 1; +} + +int MTPStorage::sendFileFromFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &destinationPath) +{ + qCDebug(LOG_KIOD_KMTPD) << "sendFileFromFileDescriptor:" << destinationPath; + + QStringList destItems = destinationPath.split(QLatin1Char('/'), QString::SkipEmptyParts); + if (destItems.isEmpty()) { + return 1; + } + + const QString filename = destItems.takeLast(); + uint32_t parentId = LIBMTP_FILES_AND_FOLDERS_ROOT; + + if (!destItems.isEmpty()) { + // not root folder, search for parent folder + const KMTPFile parent = getFileMetadata(convertToPath(destItems, destItems.size())); + parentId = parent.itemId(); + if (!parent.isFolder()) { + return 2; + } + } + + QTimer::singleShot(0, this, [this, parentId, descriptor, filename] { + int result = 1; + QT_STATBUF srcBuf; + if (QT_FSTAT(descriptor.fileDescriptor(), &srcBuf) != -1) { + const QDateTime lastModified = QDateTime::fromSecsSinceEpoch(srcBuf.st_mtim.tv_sec); + + LIBMTP_file_t *file = LIBMTP_new_file_t(); + file->parent_id = parentId; + file->filename = qstrdup(filename.toUtf8().data()); + file->filetype = getFiletype(filename); + file->filesize = quint64(srcBuf.st_size); + file->modificationdate = lastModified.toTime_t(); // no matter what to set here, current time is taken + file->storage_id = m_id; + + result = LIBMTP_Send_File_From_File_Descriptor(getDevice(), descriptor.fileDescriptor(), file, onDataProgress, this); + LIBMTP_destroy_file_t(file); + + if (result) { + LIBMTP_Dump_Errorstack(getDevice()); + LIBMTP_Clear_Errorstack(getDevice()); + } + } + emit copyFinished(result); + }); + + return 0; +} + +int MTPStorage::setFileName(const QString &path, const QString &newName) +{ + qCDebug(LOG_KIOD_KMTPD) << "setFileName:" << path << newName; + + const KMTPFile file = getFileFromPath(path); + if (file.isValid()) { + LIBMTP_file_t *source = LIBMTP_Get_Filemetadata(getDevice(), file.itemId()); + if (source) { + const int result = LIBMTP_Set_File_Name(getDevice(), source, newName.toUtf8().constData()); + if (!result) { + removePath(path); + LIBMTP_destroy_file_t(source); + } + return result; + } + } + return 1; +} + +quint32 MTPStorage::createFolder(const QString &path) +{ + qCDebug(LOG_KIOD_KMTPD) << "createFolder:" << path; + + quint32 folderId = 0; + const QStringList pathItems = path.split(QLatin1Char('/'), QString::SkipEmptyParts); + const quint32 destinationId = queryPath(path); + + if (!pathItems.isEmpty() && !destinationId) { + QByteArray dirName = pathItems.last().toUtf8(); + + if (pathItems.size() == 1) { + // create folder in device root + folderId = LIBMTP_Create_Folder(getDevice(), dirName.data(), LIBMTP_FILES_AND_FOLDERS_ROOT, m_id); + + } else { + const KMTPFile parentFolder = getFileMetadata(path.section(QLatin1Char('/'), 0, -2, QString::SectionIncludeLeadingSep)); + if (parentFolder.isFolder()) { + folderId = LIBMTP_Create_Folder(getDevice(), dirName.data(), parentFolder.itemId(), m_id); + } + } + + if (folderId) { + LIBMTP_Dump_Errorstack(getDevice()); + LIBMTP_Clear_Errorstack(getDevice()); + } else { + addPath(path, folderId); + } + } + return folderId; +} + +int MTPStorage::deleteObject(const QString &path) +{ + qCDebug(LOG_KIOD_KMTPD) << "deleteObject:" << path; + + const KMTPFile file = getFileMetadata(path); + const int ret = LIBMTP_Delete_Object(getDevice(), file.itemId()); + if (!ret) { + removePath(path); + } + return ret; +} + +#include "moc_mtpstorage.cpp" diff --git a/mtp/kiod_module/mtpstorage.h b/mtp/kiod_module/mtpstorage.h new file mode 100644 index 00000000..7b3fd05e --- /dev/null +++ b/mtp/kiod_module/mtpstorage.h @@ -0,0 +1,142 @@ +/* + This file is part of the MTP KIOD module, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + 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, see . +*/ + +#ifndef MTPSTORAGE_H +#define MTPSTORAGE_H + +#include + +#include +#include + +class MTPDevice; + +/** + * @brief This D-Bus interface is used to access a single MTP storage. + * + * This includes storage management like file-, folder- and object-access. + * + * As a performance optimization to reduce hardware interaction, + * a time based cache for file ids, mapping their path to their ID is used. + */ +class MTPStorage : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString description READ description) + Q_PROPERTY(quint64 maxCapacity READ maxCapacity) + Q_PROPERTY(quint64 freeSpaceInBytes READ freeSpaceInBytes) + +public: + explicit MTPStorage(const QString &dbusObjectPath, const LIBMTP_devicestorage_t *mtpStorage, MTPDevice *parent); + + QString dbusObjectPath() const; + + // D-Bus properties + QString description() const; + quint64 maxCapacity() const; + quint64 freeSpaceInBytes(); + +private: + Q_DISABLE_COPY(MTPStorage) + + void setStorageProperties(const LIBMTP_devicestorage_t *storage); + void updateStorageInfo(); + + LIBMTP_mtpdevice_t *getDevice() const; + + /** + * @brief Get the correct file/folder from the device. + * + * @param path + * @return + */ + KMTPFile getFileFromPath(const QString &path); + + /** + * @brief Get all children files/folders of @a parentId and cache them. + * + * @param path parent path, used for caching + * @param parentId + * @return + */ + KMTPFileList getFilesAndFoldersCached(const QString &path, quint32 parentId); + + /** + * @brief Returns the ID of the item at the given path, else 0. + * + * Automatically discards old items. + * + * @param path The Path to query the cache for + * @return The ID of the Item if it exists, else 0 + */ + quint32 queryPath(const QString &path, int timeToLive = 60); + + /** + * @brief Adds a Path to the Cache with the given id and ttl. + * + * @param path The path of the file/folder + * @param id The file ID on the storage + * @param timeToLive The time in seconds the entry should be valid + */ + void addPath(const QString &path, quint32 id, int timeToLive = 60); + + /** + * @brief Remove the given path from the cache, i.e. if it got deleted + * + * @param path The path that should be removed + */ + void removePath(const QString &path); + + const QString m_dbusObjectPath; + + // LIBMTP_devicestorage_t properties + quint32 m_id; /**< Unique ID for this storage */ + quint64 m_maxCapacity; /**< Maximum capability */ + quint64 m_freeSpaceInBytes; /**< Free space in bytes */ + QString m_description; /**< A brief description of this storage */ + + QHash > m_cache; + +public slots: + // D-Bus methods + + // file management + KMTPFileList getFilesAndFolders(const QString &path, int &result); + KMTPFile getFileMetadata(const QString &path); + + int getFileToHandler(const QString &path); + int getFileToFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &sourcePath); + + int sendFileFromFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &destinationPath); + int setFileName(const QString &path, const QString &newName); + + // folder management + quint32 createFolder(const QString &path); + + // object management + int deleteObject(const QString &path); + +signals: + // D-Bus signals + void dataReady(const QByteArray &data); + void copyProgress(qulonglong transferredBytes, qulonglong totalBytes); + void copyFinished(int result); +}; + +#endif // MTPSTORAGE_H diff --git a/mtp/shared/CMakeLists.txt b/mtp/shared/CMakeLists.txt new file mode 100644 index 00000000..381795ad --- /dev/null +++ b/mtp/shared/CMakeLists.txt @@ -0,0 +1,21 @@ +add_definitions(-DQT_NO_CAST_FROM_ASCII) + +set(kmtp_PART_SRCS + kmtpdinterface.cpp + kmtpdeviceinterface.cpp + kmtpstorageinterface.cpp + kmtpfile.cpp +) + +set_source_files_properties(org.kde.kmtp.storage.xml + PROPERTIES INCLUDE kmtpfile.h +) + +qt5_add_dbus_interfaces(kmtp_PART_SRCS + org.kde.kmtp.daemon.xml + org.kde.kmtp.device.xml + org.kde.kmtp.storage.xml +) + +add_library(kmtp STATIC ${kmtp_PART_SRCS}) +target_link_libraries(kmtp Qt5::Core Qt5::DBus) diff --git a/mtp/shared/kmtpdeviceinterface.cpp b/mtp/shared/kmtpdeviceinterface.cpp new file mode 100644 index 00000000..a1c6c6f1 --- /dev/null +++ b/mtp/shared/kmtpdeviceinterface.cpp @@ -0,0 +1,70 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "kmtpdeviceinterface.h" +#include "kmtpstorageinterface.h" + +KMTPDeviceInterface::KMTPDeviceInterface(const QString &dbusObjectPath, QObject *parent) + : QObject(parent) +{ + m_dbusInterface = new org::kde::kmtp::Device(QStringLiteral("org.kde.kiod5"), + dbusObjectPath, + QDBusConnection::sessionBus(), + this); + + const auto storageNames = m_dbusInterface->listStorages().value(); + m_storages.reserve(storageNames.count()); + for (const QDBusObjectPath &storageName : storageNames) { + m_storages.append(new KMTPStorageInterface(storageName.path(), this)); + } +} + +QString KMTPDeviceInterface::udi() const +{ + return m_dbusInterface->udi(); +} + +QString KMTPDeviceInterface::friendlyName() const +{ + return m_dbusInterface->friendlyName(); +} + +QVector KMTPDeviceInterface::storages() const +{ + return m_storages; +} + +KMTPStorageInterface *KMTPDeviceInterface::storageFromDescription(const QString &description) const +{ + auto storageIt = std::find_if(m_storages.constBegin(), m_storages.constEnd(), [description] (KMTPStorageInterface *storage) { + return storage->description() == description; + }); + + return storageIt == m_storages.constEnd() ? nullptr : *storageIt; +} + +int KMTPDeviceInterface::setFriendlyName(const QString &friendlyName) +{ + return m_dbusInterface->setFriendlyName(friendlyName); +} + +#include "moc_kmtpdeviceinterface.cpp" diff --git a/mtp/shared/kmtpdeviceinterface.h b/mtp/shared/kmtpdeviceinterface.h new file mode 100644 index 00000000..1e0c6010 --- /dev/null +++ b/mtp/shared/kmtpdeviceinterface.h @@ -0,0 +1,64 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KMTPDEVICEINTERFACE_H +#define KMTPDEVICEINTERFACE_H + +#include + +#include "deviceinterface.h" + +class KMTPStorageInterface; + +/** + * @brief The KMTPDeviceInterface class + * + * @note This interface should be a public API. + */ +class KMTPDeviceInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString udi READ udi CONSTANT) + Q_PROPERTY(QString friendlyName READ friendlyName NOTIFY friendlyNameChanged) + +public: + explicit KMTPDeviceInterface(const QString &dbusObjectPath, QObject *parent); + + QString udi() const; + QString friendlyName() const; + + QVector storages() const; + KMTPStorageInterface *storageFromDescription(const QString &description) const; + +private: + org::kde::kmtp::Device *m_dbusInterface; + QVector m_storages; + +public slots: + int setFriendlyName(const QString &friendlyName); + +signals: + void friendlyNameChanged(const QString &friendlyName); +}; + + +#endif // KMTPDEVICEINTERFACE_H diff --git a/mtp/shared/kmtpdinterface.cpp b/mtp/shared/kmtpdinterface.cpp new file mode 100644 index 00000000..1a2acd5a --- /dev/null +++ b/mtp/shared/kmtpdinterface.cpp @@ -0,0 +1,85 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "kmtpdinterface.h" +#include "kmtpdeviceinterface.h" + +KMTPDInterface::KMTPDInterface(QObject *parent) + : QObject(parent) +{ + // connect to the KDE MTP daemon over D-Bus + m_dbusInterface = new org::kde::kmtp::Daemon(QStringLiteral("org.kde.kiod5"), + QStringLiteral("/modules/kmtpd"), + QDBusConnection::sessionBus()); + + updateDevices(); +} + +bool KMTPDInterface::isValid() const +{ + return m_dbusInterface->isValid(); +} + +KMTPDeviceInterface *KMTPDInterface::deviceFromName(const QString &friendlyName) const +{ + auto deviceIt = std::find_if(m_devices.constBegin(), m_devices.constEnd(), [friendlyName] (const KMTPDeviceInterface *device) { + return device->friendlyName() == friendlyName; + }); + + return deviceIt == m_devices.constEnd() ? nullptr : *deviceIt; +} + +KMTPDeviceInterface *KMTPDInterface::deviceFromUdi(const QString &udi) const +{ + auto deviceIt = std::find_if(m_devices.constBegin(), m_devices.constEnd(), [udi] (const KMTPDeviceInterface *device) { + return device->udi() == udi; + }); + + return deviceIt == m_devices.constEnd() ? nullptr : *deviceIt; +} + +QVector KMTPDInterface::devices() const +{ + return m_devices; +} + +QString KMTPDInterface::version() const +{ + return m_dbusInterface->version(); +} + +void KMTPDInterface::updateDevices() +{ + qDeleteAll(m_devices); + m_devices.clear(); + const auto deviceNames = m_dbusInterface->listDevices().value(); + for (const QDBusObjectPath &deviceName : deviceNames) { + m_devices.append(new KMTPDeviceInterface(deviceName.path(), this)); + } +} + +QList KMTPDInterface::listDevices() +{ + return m_dbusInterface->listDevices(); +} + +#include "moc_kmtpdinterface.cpp" diff --git a/mtp/shared/kmtpdinterface.h b/mtp/shared/kmtpdinterface.h new file mode 100644 index 00000000..a314a87b --- /dev/null +++ b/mtp/shared/kmtpdinterface.h @@ -0,0 +1,70 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KMTPDINTERFACE_H +#define KMTPDINTERFACE_H + +#include +#include "daemoninterface.h" + +class KMTPDeviceInterface; + +/** + * @brief The KMTPDeviceInterface class + * + * @note This interface should be a public API. + */ +class KMTPDInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString version READ version CONSTANT) + +public: + explicit KMTPDInterface(QObject *parent = nullptr); + + bool isValid() const; + + KMTPDeviceInterface *deviceFromName(const QString &friendlyName) const; + KMTPDeviceInterface *deviceFromUdi(const QString &udi) const; + + QVector devices() const; + + // D-Bus properties + QString version() const; + +private: + void updateDevices(); + + org::kde::kmtp::Daemon *m_dbusInterface; + QVector m_devices; + +public slots: + // D-Bus methods + QList listDevices(); + +signals: + // D-Bus signals + void devicesChanged(); +}; + + +#endif // KMTPDINTERFACE_H diff --git a/mtp/shared/kmtpfile.cpp b/mtp/shared/kmtpfile.cpp new file mode 100644 index 00000000..efd7f424 --- /dev/null +++ b/mtp/shared/kmtpfile.cpp @@ -0,0 +1,147 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "kmtpfile.h" + +KMTPFile::KMTPFile() + : m_itemId(0), + m_parentId(0), + m_storageId(0), + m_filesize(0), + m_modificationdate(0) +{ +} + +KMTPFile::KMTPFile(quint32 itemId, + quint32 parentId, + quint32 storageId, + const char *filename, + quint64 filesize, + qint64 modificationdate, + const QString &filetype) + : m_itemId(itemId), + m_parentId(parentId), + m_storageId(storageId), + m_filename(QString::fromUtf8(filename)), + m_filesize(filesize), + m_modificationdate(modificationdate), + m_filetype(filetype) +{ + +} + +bool KMTPFile::isValid() const +{ + return m_itemId != 0; +} + +bool KMTPFile::isFolder() const +{ + return m_filetype == QLatin1String("inode/directory"); +} + +quint32 KMTPFile::itemId() const +{ + return m_itemId; +} + +quint32 KMTPFile::parentId() const +{ + return m_parentId; +} + +quint32 KMTPFile::storageId() const +{ + return m_storageId; +} + +QString KMTPFile::filename() const +{ + return m_filename; +} + +quint64 KMTPFile::filesize() const +{ + return m_filesize; +} + +qint64 KMTPFile::modificationdate() const +{ + return m_modificationdate; +} + +QString KMTPFile::filetype() const +{ + return m_filetype; +} + +QDBusArgument &operator<<(QDBusArgument &argument, const KMTPFile &mtpFile) +{ + argument.beginStructure(); + argument << mtpFile.m_itemId + << mtpFile.m_parentId + << mtpFile.m_storageId + << mtpFile.m_filename + << mtpFile.m_filesize + << mtpFile.m_modificationdate + << mtpFile.m_filetype; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, KMTPFile &mtpFile) +{ + argument.beginStructure(); + argument >> mtpFile.m_itemId + >> mtpFile.m_parentId + >> mtpFile.m_storageId + >> mtpFile.m_filename + >> mtpFile.m_filesize + >> mtpFile.m_modificationdate + >> mtpFile.m_filetype; + argument.endStructure(); + return argument; +} + +QDBusArgument &operator<<(QDBusArgument &argument, const KMTPFileList &list) +{ + argument.beginArray(qMetaTypeId()); + for (const KMTPFile &file : list) { + argument << file; + } + argument.endArray(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, KMTPFileList &list) +{ + argument.beginArray(); + list.clear(); + while (!argument.atEnd()) { + KMTPFile file; + argument >> file; + list.append(file); + } + + argument.endArray(); + return argument; +} diff --git a/mtp/shared/kmtpfile.h b/mtp/shared/kmtpfile.h new file mode 100644 index 00000000..b3670a88 --- /dev/null +++ b/mtp/shared/kmtpfile.h @@ -0,0 +1,90 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KMTPFILE_H +#define KMTPFILE_H + +#include + +/** + * @brief A wrapper class for LIBMTP_file_t which provides streaming operators for D-Bus marshalling + * + * LIBMTP_file_t definitions with some exceptions: + * item_id: unchanged + * parent_id: unchanged + * storage_id: unchanged + * filename: represented as string + * filesize: unchanged + * modificationsdate: represented as an int64 + * filetype: converted to a mime-string + * next: omitted as it is not necessary in a list + */ +class KMTPFile +{ + +public: + KMTPFile(); + KMTPFile(const KMTPFile &other) = default; + explicit KMTPFile(quint32 itemId, + quint32 parentId, + quint32 storageId, + const char *filename, + quint64 filesize, + qint64 modificationdate, + const QString &filetype); + + bool isValid() const; + bool isFolder() const; + + quint32 itemId() const; + quint32 parentId() const; + quint32 storageId() const; + QString filename() const; + quint64 filesize() const; + qint64 modificationdate() const; + QString filetype() const; + +private: + quint32 m_itemId; /**< Unique item ID */ + quint32 m_parentId; /**< ID of parent folder */ + quint32 m_storageId; /**< ID of storage holding this file */ + QString m_filename; /**< Filename of this file */ + quint64 m_filesize; /**< Size of file in bytes */ + qint64 m_modificationdate; /**< Date of last alteration of the file */ + QString m_filetype; /**< Filetype used for the current file */ + + friend QDBusArgument& operator<<(QDBusArgument &argument, const KMTPFile &mtpFile); + friend const QDBusArgument& operator>>(const QDBusArgument &argument, KMTPFile &mtpFile); +}; + +typedef QList KMTPFileList; + +Q_DECLARE_METATYPE(KMTPFile) +Q_DECLARE_METATYPE(KMTPFileList) + +QDBusArgument& operator<<(QDBusArgument &argument, const KMTPFile &mtpFile); +const QDBusArgument& operator>>(const QDBusArgument &argument, KMTPFile &mtpFile); + +QDBusArgument& operator<<(QDBusArgument &argument, const KMTPFileList &list); +const QDBusArgument& operator>>(const QDBusArgument &argument, KMTPFileList &list); + +#endif // KMTPFILE_H diff --git a/mtp/shared/kmtpstorageinterface.cpp b/mtp/shared/kmtpstorageinterface.cpp new file mode 100644 index 00000000..aa35f8de --- /dev/null +++ b/mtp/shared/kmtpstorageinterface.cpp @@ -0,0 +1,98 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "kmtpstorageinterface.h" +#include "kmtpdeviceinterface.h" + +KMTPStorageInterface::KMTPStorageInterface(const QString &dbusObjectPath, KMTPDeviceInterface *parent) + : QObject(parent) +{ + m_dbusInterface = new org::kde::kmtp::Storage(QStringLiteral("org.kde.kiod5"), + dbusObjectPath, + QDBusConnection::sessionBus(), + this); + m_dbusInterface->setTimeout(5 * 60 * 1000); // TODO: listing folders with a huge amount of files may take a while + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + connect(m_dbusInterface, &org::kde::kmtp::Storage::dataReady, this, &KMTPStorageInterface::dataReady); + connect(m_dbusInterface, &org::kde::kmtp::Storage::copyProgress, this, &KMTPStorageInterface::copyProgress); + connect(m_dbusInterface, &org::kde::kmtp::Storage::copyFinished, this, &KMTPStorageInterface::copyFinished); +} + +QString KMTPStorageInterface::description() const +{ + return m_dbusInterface->description(); +} + +quint64 KMTPStorageInterface::maxCapacity() const +{ + return m_dbusInterface->maxCapacity(); +} + +quint64 KMTPStorageInterface::freeSpaceInBytes() const +{ + return m_dbusInterface->freeSpaceInBytes(); +} + +KMTPFileList KMTPStorageInterface::getFilesAndFolders(const QString &path, int &result) const +{ + return m_dbusInterface->getFilesAndFolders(path, result); +} + +KMTPFile KMTPStorageInterface::getFileMetadata(const QString &path) const +{ + return m_dbusInterface->getFileMetadata(path); +} + +int KMTPStorageInterface::getFileToHandler(const QString &path) const +{ + return m_dbusInterface->getFileToHandler(path); +} + +int KMTPStorageInterface::getFileToFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &sourcePath) const +{ + return m_dbusInterface->getFileToFileDescriptor(descriptor, sourcePath).value(); +} + +int KMTPStorageInterface::sendFileFromFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &destinationPath) const +{ + return m_dbusInterface->sendFileFromFileDescriptor(descriptor, destinationPath); +} + +int KMTPStorageInterface::setFileName(const QString &path, const QString &newName) const +{ + return m_dbusInterface->setFileName(path, newName); +} + +quint32 KMTPStorageInterface::createFolder(const QString &path) const +{ + return m_dbusInterface->createFolder(path); +} + +int KMTPStorageInterface::deleteObject(const QString &path) const +{ + return m_dbusInterface->deleteObject(path); +} + +#include "moc_kmtpstorageinterface.cpp" diff --git a/mtp/shared/kmtpstorageinterface.h b/mtp/shared/kmtpstorageinterface.h new file mode 100644 index 00000000..74fb68ff --- /dev/null +++ b/mtp/shared/kmtpstorageinterface.h @@ -0,0 +1,77 @@ +/* + This file is part of the KMTP framework, part of the KDE project. + + Copyright (C) 2018 Andreas Krutzler + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KMTPSTORAGEINTERFACE_H +#define KMTPSTORAGEINTERFACE_H + +#include "storageinterface.h" +#include "kmtpfile.h" + +class KMTPDeviceInterface; + +/** + * @brief The KMTPStorageInterface class + * + * @note This interface should be a public API. + */ +class KMTPStorageInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString description READ description) + Q_PROPERTY(quint64 maxCapacity READ maxCapacity) + Q_PROPERTY(quint64 freeSpaceInBytes READ freeSpaceInBytes) + +public: + explicit KMTPStorageInterface(const QString &dbusObjectPath, KMTPDeviceInterface *parent); + + QString description() const; + quint64 maxCapacity() const; + quint64 freeSpaceInBytes() const; + +private: + org::kde::kmtp::Storage *m_dbusInterface; + +public slots: + // file management + KMTPFileList getFilesAndFolders(const QString &path, int &result) const; + KMTPFile getFileMetadata(const QString &path) const; + + int getFileToHandler(const QString &path) const; + int getFileToFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &sourcePath) const; + + int sendFileFromFileDescriptor(const QDBusUnixFileDescriptor &descriptor, const QString &destinationPath) const; + + int setFileName(const QString &path, const QString &newName) const; + + // folder management + quint32 createFolder(const QString &path) const; + + // object management + int deleteObject(const QString &path) const; + +signals: + void dataReady(const QByteArray &data); + void copyProgress(qulonglong transferredBytes, qulonglong totalBytes); + void copyFinished(int result); +}; + +#endif // KMTPSTORAGEINTERFACE_H diff --git a/mtp/shared/org.kde.kmtp.daemon.xml b/mtp/shared/org.kde.kmtp.daemon.xml new file mode 100644 index 00000000..c2dd6483 --- /dev/null +++ b/mtp/shared/org.kde.kmtp.daemon.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mtp/shared/org.kde.kmtp.device.xml b/mtp/shared/org.kde.kmtp.device.xml new file mode 100644 index 00000000..a84978a7 --- /dev/null +++ b/mtp/shared/org.kde.kmtp.device.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mtp/shared/org.kde.kmtp.storage.xml b/mtp/shared/org.kde.kmtp.storage.xml new file mode 100644 index 00000000..aba9eccc --- /dev/null +++ b/mtp/shared/org.kde.kmtp.storage.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +