simple-mtpfs/src/simple-mtpfs-mtp-device.cpp

528 lines
18 KiB
C++

/* ***** BEGIN LICENSE BLOCK *****
* Copyright (C) 2012, Peter Hatina <phatina@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
* ***** END LICENSE BLOCK ***** */
#include <config.h>
#include <algorithm>
#include <sstream>
#include <vector>
#include <cstring>
#include <cstdlib>
extern "C" {
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
}
#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)
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<void*>(raw_devices));
if (m_device == nullptr)
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");
}
void 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)
return;
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";
}
}
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<TypeDir*>(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<TypeDir*>(dir_parent)->addDir(TypeDir(new_id, dir_parent->id(),
dir_parent->storageid(), tmp_basename));
logmsg("Directory '", path, "' created.\n");
}
free(static_cast<void*>(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(tmp_basename);
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<TypeDir*>(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(tmp_old_basename);
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<void*>(folder->name));
free(static_cast<void*>(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<TypeDir*>(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(tmp_old_basename);
const TypeFile *file_to_rename = dir_old_parent->file(tmp_old_basename);
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<const TypeBasic*>(dir_to_rename) :
static_cast<const TypeBasic*>(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<TypeBasic*>(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);
if (!dir_parent) {
logerr("Can not fetch '", src, "'.\n");
return -EINVAL;
}
const TypeFile *file_to_fetch = dir_parent->file(src_basename);
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->file(dst_basename);
if (!dir_parent || dir_parent->file(dst_basename)) {
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<uint64_t>(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<TypeDir*>(dir_parent)->replaceFile(*file_to_remove, file_to_upload);
else
const_cast<TypeDir*>(dir_parent)->addFile(file_to_upload);
}
free(static_cast<void*>(f->filename));
free(static_cast<void*>(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->file(tmp_basename);
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<TypeDir*>(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->file(tmp_old_basename);
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<void*>(file->filename));
free(static_cast<void*>(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<TypeFile*>(file_to_rename)->setName(tmp_new_basename);
logmsg("File '", oldpath, "' renamed to '", tmp_new_basename, "'.\n");
return 0;
}