/* ***** BEGIN LICENSE BLOCK ***** * Copyright (C) 2012, Peter Hatina * * 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 . * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include extern "C" { # include # include # include } #include "simple-mtpfs-fuse.h" #include "simple-mtpfs-libmtp.h" #include "simple-mtpfs-log.h" #include "simple-mtpfs-mtp-device.h" uint32_t MTPDevice::s_root_node = ~0; MTPDevice::MTPDevice(): m_device(nullptr), m_device_mutex(), m_root_dir(), m_move_enabled(false) { LIBMTP_Init(); } MTPDevice::~MTPDevice() { disconnect(); } bool MTPDevice::connect(int dev_no) { if (m_device) { logerr("Already connected.\n"); return true; } int raw_devices_cnt; LIBMTP_raw_device_t *raw_devices; LIBMTP_error_number_t err = LIBMTP_Detect_Raw_Devices( &raw_devices, &raw_devices_cnt); if (dev_no > raw_devices_cnt) { free(static_cast(raw_devices)); return false; } if (err != LIBMTP_ERROR_NONE) { switch(err) { case LIBMTP_ERROR_NO_DEVICE_ATTACHED: logerr("No raw devices found.\n"); break; case LIBMTP_ERROR_CONNECTING: logerr("There has been an error connecting. Exiting.\n"); break; case LIBMTP_ERROR_MEMORY_ALLOCATION: logerr("Encountered a Memory Allocation Error. Exiting.\n"); break; case LIBMTP_ERROR_GENERAL: logerr("General error occured. Exiting.\n"); break; case LIBMTP_ERROR_USB_LAYER: logerr("USB Layer error occured. Exiting.\n"); break; default: break; } return false; } m_device = LIBMTP_Open_Raw_Device_Uncached(&raw_devices[dev_no]); free(static_cast(raw_devices)); if (!m_device) return false; if (!enumStorages()) return false; logmsg("Connected.\n"); return true; } void MTPDevice::disconnect() { if (!m_device) return; LIBMTP_Release_Device(m_device); m_device = nullptr; logmsg("Disconnected.\n"); } bool MTPDevice::listDevices() { int raw_devices_cnt; LIBMTP_raw_device_t *raw_devices; LIBMTP_error_number_t err = LIBMTP_Detect_Raw_Devices( &raw_devices, &raw_devices_cnt); if (err != 0) { if (err == LIBMTP_ERROR_NO_DEVICE_ATTACHED) std::cerr << "No raw devices found.\n"; return false; } for (int i = 0; i < raw_devices_cnt; ++i) { std::cout << i + 1 << ": " << raw_devices[i].device_entry.vendor << raw_devices[i].device_entry.product << "\n"; } free(static_cast(raw_devices)); return true; } uint64_t MTPDevice::storageTotalSize() const { uint64_t total = 0; for (LIBMTP_devicestorage_t *s = m_device->storage; s; s = s->next) total += s->MaxCapacity; return total; } uint64_t MTPDevice::storageFreeSize() const { uint64_t free = 0; for (LIBMTP_devicestorage_t *s = m_device->storage; s; s = s->next) free += s->FreeSpaceInBytes; return free; } bool MTPDevice::enumStorages() { criticalEnter(); LIBMTP_Clear_Errorstack(m_device); if (LIBMTP_Get_Storage(m_device, LIBMTP_STORAGE_SORTBY_NOTSORTED) < 0) { logerr("Could not retrieve device storage. Exiting.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return false; } criticalLeave(); return true; } const TypeDir *MTPDevice::dirGetContent(const std::string &path) { if (path == "/") return &m_root_dir; std::string member; std::istringstream ss(path); const TypeDir *dir = &m_root_dir; while (std::getline(ss, member, '/') && dir) { if (member.empty()) continue; dir = dir->dir(member); } return dir; } const TypeDir *MTPDevice::dirFetchContent(std::string path) { if (!m_root_dir.isFetched()) { for (LIBMTP_devicestorage_t *s = m_device->storage; s; s = s->next) { m_root_dir.addDir(TypeDir(s_root_node, 0, s->id, std::string(s->StorageDescription))); m_root_dir.setFetched(); } } if (m_root_dir.dirCount() == 1) path = '/' + m_root_dir.dirs().begin()->name() + path; if (path == "/") return &m_root_dir; std::string member; std::istringstream ss(path); TypeDir *dir = &m_root_dir; while (std::getline(ss, member, '/')) { if (member.empty()) continue; const TypeDir *tmp = dir->dir(member); if (!tmp && !dir->isFetched()) { criticalEnter(); LIBMTP_file_t *content = LIBMTP_Get_Files_And_Folders( m_device, dir->storageid(), dir->id()); criticalLeave(); for (LIBMTP_file_t *f = content; f; f = f->next) { if (f->filetype == LIBMTP_FILETYPE_FOLDER) dir->addDir(TypeDir(f)); else dir->addFile(TypeFile(f)); } LIBMTP_Free_Files_And_Folders(&content); dir->setFetched(); tmp = dir->dir(member); } if (!tmp) return nullptr; dir = const_cast(tmp); } if (dir->isFetched()) return dir; criticalEnter(); dir->setFetched(); LIBMTP_file_t *content = LIBMTP_Get_Files_And_Folders( m_device, dir->storageid(), dir->id()); criticalLeave(); for (LIBMTP_file_t *f = content; f; f = f->next) { if (f->filetype == LIBMTP_FILETYPE_FOLDER) dir->addDir(TypeDir(f)); else dir->addFile(TypeFile(f)); } LIBMTP_Free_Files_And_Folders(&content); return dir; } int MTPDevice::dirCreateNew(const std::string &path) { const std::string tmp_basename(smtpfs_basename(path)); const std::string tmp_dirname(smtpfs_dirname(path)); const TypeDir *dir_parent = dirFetchContent(tmp_dirname); if (!dir_parent || dir_parent->id() == 0) { logerr("Can not remove directory '", path, "'.\n"); return -EINVAL; } char *c_name = strdup(tmp_basename.c_str()); criticalEnter(); uint32_t new_id = LIBMTP_Create_Folder(m_device, c_name, dir_parent->id(), dir_parent->storageid()); criticalLeave(); if (new_id == 0) { logerr("Could not create directory '", path, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); } else { const_cast(dir_parent)->addDir(TypeDir(new_id, dir_parent->id(), dir_parent->storageid(), tmp_basename)); logmsg("Directory '", path, "' created.\n"); } free(static_cast(c_name)); return new_id != 0 ? 0 : -EINVAL; } int MTPDevice::dirRemove(const std::string &path) { const std::string tmp_basename(smtpfs_basename(path)); const std::string tmp_dirname(smtpfs_dirname(path)); const TypeDir *dir_parent = dirFetchContent(tmp_dirname); const TypeDir *dir_to_remove = dir_parent ? dir_parent->dir(tmp_basename) : nullptr; if (!dir_parent || !dir_to_remove || dir_parent->id() == 0) { logerr("No such directory '", path, "' to remove.\n"); return -ENOENT; } criticalEnter(); int rval = LIBMTP_Delete_Object(m_device, dir_to_remove->id()); criticalLeave(); if (rval != 0){ logerr("Could not remove the directory '", path, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return -EINVAL; } const_cast(dir_parent)->removeDir(*dir_to_remove); logmsg("Folder '", path, "' removed.\n"); return 0; } int MTPDevice::dirRename(const std::string &oldpath, const std::string &newpath) { const std::string tmp_old_basename(smtpfs_basename(oldpath)); const std::string tmp_old_dirname(smtpfs_dirname(oldpath)); const std::string tmp_new_basename(smtpfs_basename(newpath)); const std::string tmp_new_dirname(smtpfs_dirname(newpath)); const TypeDir *dir_parent = dirFetchContent(tmp_old_dirname); const TypeDir *dir_to_rename = dir_parent ? dir_parent->dir(tmp_old_basename) : nullptr; if (!dir_parent || !dir_to_rename || dir_parent->id() == 0) { logerr("Can not rename '", tmp_old_basename, "' to '", tmp_new_basename, "'.\n"); return -EINVAL; } if (tmp_old_dirname != tmp_new_dirname) { logerr("Can not move '", oldpath, "' to '", newpath, "'.\n"); return -EINVAL; } LIBMTP_folder_t *folder = dir_to_rename->toLIBMTPFolder(); criticalEnter(); int ret = LIBMTP_Set_Folder_Name(m_device, folder, tmp_new_basename.c_str()); criticalLeave(); free(static_cast(folder->name)); free(static_cast(folder)); if (ret != 0) { logerr("Could not rename '", oldpath, "' to '", tmp_new_basename, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return -EINVAL; } const_cast(dir_to_rename)->setName(tmp_new_basename); logmsg("Directory '", oldpath, "' renamed to '", tmp_new_basename, "'.\n"); return 0; } int MTPDevice::rename(const std::string &oldpath, const std::string &newpath) { #ifndef SMTPFS_MOVE_BY_SET_OBJECT_PROPERTY const std::string tmp_old_basename(smtpfs_basename(oldpath)); const std::string tmp_old_dirname(smtpfs_dirname(oldpath)); const std::string tmp_new_dirname(smtpfs_dirname(newpath)); if (tmp_old_dirname != tmp_new_dirname) return -EINVAL; const TypeDir *dir_parent = dirFetchContent(tmp_old_dirname); if (!dir_parent || dir_parent->id() == 0) return -EINVAL; const TypeDir *dir_to_rename = dir_parent->dir(tmp_old_basename); if (dir_to_rename) return dirRename(oldpath, newpath); else return fileRename(oldpath, newpath); #else const std::string tmp_old_basename(smtpfs_basename(oldpath)); const std::string tmp_old_dirname(smtpfs_dirname(oldpath)); const std::string tmp_new_basename(smtpfs_basename(newpath)); const std::string tmp_new_dirname(smtpfs_dirname(newpath)); const TypeDir *dir_old_parent = dirFetchContent(tmp_old_dirname); const TypeDir *dir_new_parent = dirFetchContent(tmp_new_dirname); const TypeDir *dir_to_rename = dir_old_parent ? dir_old_parent->dir(tmp_old_basename) : nullptr; const TypeFile *file_to_rename = dir_old_parent ? dir_old_parent->file(tmp_old_basename) : nullptr; logdebug("dir_to_rename: ", dir_to_rename, "\n"); logdebug("file_to_rename: ", file_to_rename, "\n"); if (!dir_old_parent || !dir_new_parent || dir_old_parent->id() == 0) return -EINVAL; const TypeBasic *object_to_rename = dir_to_rename ? static_cast(dir_to_rename) : static_cast(file_to_rename); logdebug("object_to_rename: ", object_to_rename, "\n"); logdebug("object_to_rename->id(): ", object_to_rename->id(), "\n"); if (!object_to_rename) { logerr("No such file or directory to rename/move!\n"); return -ENOENT; } if (tmp_old_dirname != tmp_new_dirname) { criticalEnter(); int rval = LIBMTP_Set_Object_u32(m_device, object_to_rename->id(), LIBMTP_PROPERTY_ParentObject, dir_new_parent->id()); criticalLeave(); if (rval != 0) { logerr("Could not move '", oldpath, "' to '", newpath, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return -EINVAL; } const_cast(object_to_rename)->setParent(dir_new_parent->id()); } if (tmp_old_basename != tmp_new_basename) { criticalEnter(); int rval = LIBMTP_Set_Object_String(m_device, object_to_rename->id(), LIBMTP_PROPERTY_Name, tmp_new_basename.c_str()); criticalLeave(); if (rval != 0) { logerr("Could not rename '", oldpath, "' to '", newpath, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return -EINVAL; } } return 0; #endif } int MTPDevice::filePull(const std::string &src, const std::string &dst) { const std::string src_basename(smtpfs_basename(src)); const std::string src_dirname(smtpfs_dirname(src)); const TypeDir *dir_parent = dirFetchContent(src_dirname); const TypeFile *file_to_fetch = dir_parent ? dir_parent->file(src_basename) : nullptr; if (!dir_parent) { logerr("Can not fetch '", src, "'.\n"); return -EINVAL; } if (!file_to_fetch) { logerr("No such file '", src, "'.\n"); return -ENOENT; } if (file_to_fetch->size() == 0) { int fd = ::creat(dst.c_str(), S_IRUSR | S_IWUSR); ::close(fd); } else { logmsg("Started fetching '", src, "'.\n"); criticalEnter(); int rval = LIBMTP_Get_File_To_File(m_device, file_to_fetch->id(), dst.c_str(), nullptr, nullptr); criticalLeave(); if (rval != 0) { logerr("Could not fetch file '", src, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return -ENOENT; } } logmsg("File fetched '", src, "'.\n"); return 0; } int MTPDevice::filePush(const std::string &src, const std::string &dst) { const std::string dst_basename(smtpfs_basename(dst)); const std::string dst_dirname(smtpfs_dirname(dst)); const TypeDir *dir_parent = dirFetchContent(dst_dirname); const TypeFile *file_to_remove = dir_parent ? dir_parent->file(dst_basename) : nullptr; if (dir_parent && file_to_remove) { criticalEnter(); int rval = LIBMTP_Delete_Object(m_device, file_to_remove->id()); criticalLeave(); if (rval != 0) { logerr("Can not upload '", src, "' to '", dst, "'.\n"); return -EINVAL; } } struct stat64 file_stat; stat64(src.c_str(), &file_stat); TypeFile file_to_upload(0, dir_parent->id(), dir_parent->storageid(), dst_basename, static_cast(file_stat.st_size), 0); LIBMTP_file_t *f = file_to_upload.toLIBMTPFile(); if (file_stat.st_size) logmsg("Started uploading '", dst, "'.\n"); criticalEnter(); int rval = LIBMTP_Send_File_From_File(m_device, src.c_str(), f, nullptr, nullptr); criticalLeave(); if (rval != 0) { logerr("Could not upload file '", src, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); rval = -EINVAL; } else { file_to_upload.setId(f->item_id); file_to_upload.setParent(f->parent_id); file_to_upload.setStorage(f->storage_id); file_to_upload.setName(std::string(f->filename)); file_to_upload.setModificationDate(file_stat.st_mtime); if (file_to_remove) const_cast(dir_parent)->replaceFile(*file_to_remove, file_to_upload); else const_cast(dir_parent)->addFile(file_to_upload); } free(static_cast(f->filename)); free(static_cast(f)); logmsg("File '", dst, (file_stat.st_size ? " uploaded" : " created"), ".\n"); return rval; } int MTPDevice::fileRemove(const std::string &path) { const std::string tmp_basename(smtpfs_basename(path)); const std::string tmp_dirname(smtpfs_dirname(path)); const TypeDir *dir_parent = dirFetchContent(tmp_dirname); const TypeFile *file_to_remove = dir_parent ? dir_parent->file(tmp_basename) : nullptr; if (!dir_parent || !file_to_remove) { logerr("No such file '", path, "' to remove.\n"); return -ENOENT; } criticalEnter(); int rval = LIBMTP_Delete_Object(m_device, file_to_remove->id()); criticalLeave(); if (rval != 0) { logerr("Could not remove the directory '", path, "'.\n"); return -EINVAL; } const_cast(dir_parent)->removeFile(*file_to_remove); logmsg("File '", path, "' removed.\n"); return 0; } int MTPDevice::fileRename(const std::string &oldpath, const std::string &newpath) { const std::string tmp_old_basename(smtpfs_basename(oldpath)); const std::string tmp_old_dirname(smtpfs_dirname(oldpath)); const std::string tmp_new_basename(smtpfs_basename(newpath)); const std::string tmp_new_dirname(smtpfs_dirname(newpath)); const TypeDir *dir_parent = dirFetchContent(tmp_old_dirname); const TypeFile *file_to_rename = dir_parent ? dir_parent->file(tmp_old_basename) : nullptr; if (!dir_parent || !file_to_rename || tmp_old_dirname != tmp_new_dirname) { logerr("Can not rename '", oldpath, "' to '", tmp_new_basename, "'.\n"); return -EINVAL; } LIBMTP_file_t *file = file_to_rename->toLIBMTPFile(); criticalEnter(); int rval = LIBMTP_Set_File_Name(m_device, file, tmp_new_basename.c_str()); criticalLeave(); free(static_cast(file->filename)); free(static_cast(file)); if (rval > 0) { logerr("Could not rename '", oldpath, "' to '", newpath, "'.\n"); LIBMTP_Dump_Errorstack(m_device); LIBMTP_Clear_Errorstack(m_device); return -EINVAL; } const_cast(file_to_rename)->setName(tmp_new_basename); logmsg("File '", oldpath, "' renamed to '", tmp_new_basename, "'.\n"); return 0; }