/* * Main implementation for KIO-MTP * 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 "kio_mtp.h" #include "kio_mtp_helpers.h" #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// ///////////////////////////// Slave Implementation /////////////////////////// ////////////////////////////////////////////////////////////////////////////// int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: kio_mtp app-socket\n"); exit(-1); } KComponentData instance("kio_mtp"); KGlobal::locale(); QCoreApplication app(argc, argv); MTPSlave slave(argv[1]); slave.dispatchLoop(); kDebug(KIO_MTP) << "Slave EventLoop ended"; return 0; } MTPSlave::MTPSlave(const QByteArray& app) : SlaveBase("mtp", app) { LIBMTP_Init(); kDebug(KIO_MTP) << "Slave started"; deviceCache = new DeviceCache(60000, this); fileCache = new FileCache(this); kDebug(KIO_MTP) << "Caches created"; } MTPSlave::~MTPSlave() { kDebug(KIO_MTP) << "Slave destroyed"; } /** * @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); kDebug(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; kDebug(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) { kDebug(KIO_MTP) << "Match found in cache, checking device"; LIBMTP_file_t* file = LIBMTP_Get_Filemetadata(device, c_fileID); if (file) { kDebug(KIO_MTP) << "Found file in cache"; ret.first = file; ret.second = device; kDebug(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); kDebug(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) { kDebug(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; kDebug(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; kDebug(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 { kDebug(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 KUrl& url, bool redirect) { kDebug(KIO_MTP ) << url; if (url.path().startsWith(QLatin1String("udi=")) && redirect) { QString udi = url.path(KUrl::RemoveTrailingSlash).remove(0, 4); kDebug(KIO_MTP) << "udi" << udi; if (deviceCache->contains(udi, true)) { KUrl newUrl; newUrl.setScheme(QLatin1String("mtp")); newUrl.setPath(QLatin1Char('/') + deviceCache->get(udi, true)->getName()); redirection(newUrl); return 1; } else { return 2; } } else if (url.path().startsWith(QLatin1Char('/'))) { return 0; } return -1; } void MTPSlave::listDir(const KUrl& url) { kDebug(KIO_MTP) << url.path(); 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 devices if (pathItems.size() == 0) { kDebug(KIO_MTP) << "Root directory, listing devices"; totalSize(deviceCache->size()); foreach (CachedDevice* cachedDevice, deviceCache->getAll().values()) { LIBMTP_mtpdevice_t *device = cachedDevice->getDevice(); getEntry(entry, device); listEntry(entry, false); entry.clear(); } listEntry(entry, true); kDebug(KIO_MTP) << "[SUCCESS] :: Devices"; finished(); // 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); kDebug(KIO_MTP) << "Listing storages for device " << pathItems.at(0); totalSize(storages.size()); if (storages.size() > 0) { foreach (const QString &storageName, storages.keys()) { getEntry(entry, storages.value(storageName)); listEntry(entry, false); entry.clear(); } listEntry(entry, true); finished(); kDebug(KIO_MTP) << "[SUCCESS] :: Storages"; } else { warning(i18n("No Storages found. Maybe you need to unlock your device?")); error(ERR_NO_CONTENT, url.path()); kDebug(KIO_MTP) << "[ERROR]"; } // Storage, list files and folders of storage root } else { QMap files; if (pathItems.size() == 2) { kDebug(KIO_MTP) << "Getting storage root listing"; LIBMTP_devicestorage_t *storage = (LIBMTP_devicestorage_t*)pair.first; kDebug(KIO_MTP) << "We have a storage:" << (storage == NULL); files = getFiles(device, storage->id); } else { LIBMTP_file_t *parent = (LIBMTP_file_t*)pair.first; files = getFiles(device, parent->storage_id, parent->item_id); } for (QMap::iterator it = files.begin(); it != files.end(); ++it) { LIBMTP_file_t *file = it.value(); QString filePath = url.path(KUrl::AddTrailingSlash).append(it.key()); fileCache->addPath(filePath, file->item_id); getEntry(entry, file); listEntry(entry, false); entry.clear(); } listEntry(entry, true); finished(); kDebug(KIO_MTP) << "[SUCCESS] Files"; } } else { error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); kDebug(KIO_MTP) << "[ERROR]"; } } else { error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); kDebug(KIO_MTP) << "[ERROR]"; } } void MTPSlave::stat(const KUrl& url) { kDebug(KIO_MTP) << url.path(); 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()); UDSEntry entry; 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); } } statEntry(entry); finished(); } void MTPSlave::put(const KUrl& url, int, JobFlags flags) { int check = checkUrl(url); switch (check) { case 0: { break; } default: { error( ERR_MALFORMED_URL, url.path()); return; } } kDebug(KIO_MTP) << url.path(); QStringList destItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); // Can't copy to root or device, needs storage if (destItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, url.path()); return; } if (!(flags & KIO::Overwrite) && getPath(url.path()).first) { error(ERR_FILE_ALREADY_EXIST, url.path()); return; } destItems.takeLast(); QPair pair = getPath(url.directory()); if (!pair.first) { error(ERR_DOES_NOT_EXIST, url.path()); return; } 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, url.directory()); return; } KTemporaryFile temp; 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(url.fileName().toUtf8().data()); file->filetype = getFiletype(url.fileName()); 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, NULL, NULL); if (ret != 0) { resetDeviceStack(device); error(KIO::ERR_COULD_NOT_WRITE, url.fileName()); return; } finished(); } void MTPSlave::get(const KUrl& url) { int check = checkUrl( url ); switch (check) { case 0: { break; } default: { error(ERR_MALFORMED_URL, url.path()); return; } } kDebug(KIO_MTP) << url.path(); QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); // File if (pathItems.size() > 2) { QPair pair = getPath(url.path()); if (pair.first) { LIBMTP_file_t *file = (LIBMTP_file_t*)pair.first; totalSize(file->filesize); LIBMTP_mtpdevice_t *device = pair.second; int ret = LIBMTP_Get_File_To_Handler(device, file->item_id, &dataPut, this, &dataProgress, this); if (ret != 0) { resetDeviceStack(device); error(ERR_COULD_NOT_READ, url.path()); return; } data(QByteArray()); finished(); } else { error(ERR_DOES_NOT_EXIST, url.path()); } } else { error(ERR_UNSUPPORTED_ACTION, url.path()); } } void MTPSlave::copy(const KUrl& src, const KUrl& dest, int, JobFlags flags) { kDebug(KIO_MTP) << src.path() << dest.path(); // mtp:/// to mtp:/// if (src.protocol() == QLatin1String("mtp") && dest.protocol() == QLatin1String("mtp")) { kDebug(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; } // file:/// to mtp:/// if (src.protocol() == QLatin1String("file") && dest.protocol() == QLatin1String("mtp")) { 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 if (destItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, dest.path()); return; } kDebug(KIO_MTP) << "Copy file" << src.fileName() << "from filesystem to device" << src.directory(KUrl::AddTrailingSlash) << dest.directory(KUrl::AddTrailingSlash); if (!(flags & KIO::Overwrite) && getPath(dest.path()).first) { error(ERR_FILE_ALREADY_EXIST, dest.path()); return; } destItems.takeLast(); QPair pair = getPath(dest.directory()); if (!pair.first) { error(ERR_DOES_NOT_EXIST, dest.directory(KUrl::AddTrailingSlash)); return; } LIBMTP_mtpdevice_t *device = pair.second; uint32_t parent_id = 0xFFFFFFFF, storage_id = 0; if (destItems.size() == 2) { LIBMTP_devicestorage_t *storage = (LIBMTP_devicestorage_t*)pair.first; storage_id = storage->id; } else { LIBMTP_file_t *parent = (LIBMTP_file_t*)pair.first; storage_id = parent->storage_id; parent_id = parent->item_id; if (parent->filetype != LIBMTP_FILETYPE_FOLDER) { error(ERR_IS_FILE, dest.directory()); return; } } QFileInfo source(src.path()); LIBMTP_file_t *file = LIBMTP_new_file_t(); file->parent_id = parent_id; file->filename = strdup(dest.fileName().toUtf8().data()); file->filetype = getFiletype(dest.fileName()); file->filesize = source.size(); file->modificationdate = source.lastModified().toTime_t(); file->storage_id = storage_id; kDebug(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); if (ret != 0) { resetDeviceStack(device); error(KIO::ERR_COULD_NOT_WRITE, dest.fileName()); return; } kDebug(KIO_MTP) << "Sent file"; } // mtp:/// to file:/// if (src.protocol() == QLatin1String("mtp") && dest.protocol() == QLatin1String("file")) { int check = checkUrl(src); switch (check) { case 0: { break; } default: { error(ERR_MALFORMED_URL, src.path()); return; } } kDebug(KIO_MTP) << "Copy file" << src.fileName() << "from device to filesystem" << dest.directory(KUrl::AddTrailingSlash) << dest.directory(KUrl::AddTrailingSlash); 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); // 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()); 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, src.directory()); return; } kDebug(KIO_MTP) << "Getting file" << source->filename << dest.fileName() << 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) { resetDeviceStack(device); error(KIO::ERR_COULD_NOT_WRITE, dest.fileName()); return; } struct utimbuf *times = (utimbuf*)::malloc(sizeof(utimbuf)); times->actime = QDateTime::currentDateTime().toTime_t(); times->modtime = source->modificationdate; int result = utime(dest.path().toUtf8().data(), times); ::free(times); kDebug(KIO_MTP) << "Sent file"; } finished(); } void MTPSlave::mkdir(const KUrl& url, int) { int check = checkUrl(url); switch (check) { case 0: { break; } default: { error(ERR_MALFORMED_URL, url.path()); return; } } kDebug(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 ( url.directory() ); 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) { kDebug(KIO_MTP) << "Found parent" << file->item_id << file->filename; kDebug(KIO_MTP) << "Attempting to create folder" << dirName; ret = LIBMTP_Create_Folder(device, dirName, file->item_id, file->storage_id); } } if (ret != 0) { resetDeviceStack(device); error(ERR_COULD_NOT_MKDIR, url.path()); return; } else { fileCache->addPath(url.path(), ret); finished(); return; } } else { error(ERR_DIR_ALREADY_EXIST, url.path()); return; } error(ERR_COULD_NOT_MKDIR, url.path()); } void MTPSlave::del(const KUrl& url, bool) { int check = checkUrl(url); switch (check) { case 0: { break; } default: { error(ERR_MALFORMED_URL, url.path()); return; } } kDebug(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) { resetDeviceStack(pair.second); error(ERR_CANNOT_DELETE, url.path()); return; } fileCache->removePath(url.path()); finished(); } void MTPSlave::rename(const KUrl& src, const KUrl& 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; } } kDebug(KIO_MTP) << src.path(); QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); QPair pair = getPath(src.path()); if (pair.first) { // Rename Device if (srcItems.size() == 1) { LIBMTP_Set_Friendlyname(pair.second, dest.fileName().toUtf8().data()); // 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()); } return; } int ret = LIBMTP_Set_File_Name(pair.second, source, dest.fileName().toUtf8().data()); if (ret != 0) { resetDeviceStack(pair.second); error(ERR_CANNOT_RENAME, src.path()); return; } else { fileCache->addPath(dest.path(), source->item_id); fileCache->removePath(src.path()); } LIBMTP_destroy_file_t(source); } finished(); } else { error(ERR_DOES_NOT_EXIST, src.path()); } } #include "moc_kio_mtp.cpp"