Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 24287d0b authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add implementations for move and copy operations."

parents 5d03729b 708b3e0b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -103,6 +103,9 @@ public:

    virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property) = 0;

    virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
                                            MtpString& newPath) = 0;

    virtual void                    sessionStarted() = 0;

    virtual void                    sessionEnded() = 0;
+139 −61
Original line number Diff line number Diff line
@@ -67,8 +67,8 @@ static const MtpOperationCode kSupportedOperationCodes[] = {
    MTP_OPERATION_SET_DEVICE_PROP_VALUE,
    MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
//    MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
//    MTP_OPERATION_MOVE_OBJECT,
//    MTP_OPERATION_COPY_OBJECT,
    MTP_OPERATION_MOVE_OBJECT,
    MTP_OPERATION_COPY_OBJECT,
    MTP_OPERATION_GET_PARTIAL_OBJECT,
//    MTP_OPERATION_INITIATE_OPEN_CAPTURE,
    MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
@@ -437,6 +437,12 @@ bool MtpServer::handleRequest() {
        case MTP_OPERATION_DELETE_OBJECT:
            response = doDeleteObject();
            break;
        case MTP_OPERATION_COPY_OBJECT:
            response = doCopyObject();
            break;
        case MTP_OPERATION_MOVE_OBJECT:
            response = doMoveObject();
            break;
        case MTP_OPERATION_GET_OBJECT_PROP_DESC:
            response = doGetObjectPropDesc();
            break;
@@ -1020,6 +1026,137 @@ MtpResponseCode MtpServer::doSendObjectInfo() {
    return MTP_RESPONSE_OK;
}

MtpResponseCode MtpServer::doMoveObject() {
    if (!hasStorage())
        return MTP_RESPONSE_GENERAL_ERROR;
    if (mRequest.getParameterCount() < 3)
        return MTP_RESPONSE_INVALID_PARAMETER;
    MtpObjectHandle objectHandle = mRequest.getParameter(1);
    MtpStorageID storageID = mRequest.getParameter(2);
    MtpStorage* storage = getStorage(storageID);
    MtpObjectHandle parent = mRequest.getParameter(3);
    if (!storage)
        return MTP_RESPONSE_INVALID_STORAGE_ID;
    MtpString path;
    MtpResponseCode result;

    MtpString fromPath;
    int64_t fileLength;
    MtpObjectFormat format;
    MtpObjectInfo info(objectHandle);
    result = mDatabase->getObjectInfo(objectHandle, info);
    if (result != MTP_RESPONSE_OK)
        return result;
    result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format);
    if (result != MTP_RESPONSE_OK)
        return result;

    // special case the root
    if (parent == 0) {
        path = storage->getPath();
    } else {
        int64_t parentLength;
        MtpObjectFormat parentFormat;
        result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat);
        if (result != MTP_RESPONSE_OK)
            return result;
        if (parentFormat != MTP_FORMAT_ASSOCIATION)
            return MTP_RESPONSE_INVALID_PARENT_OBJECT;
    }

    if (path[path.size() - 1] != '/')
        path += "/";
    path += info.mName;

    result = mDatabase->moveObject(objectHandle, parent, path);
    if (result != MTP_RESPONSE_OK)
        return result;

    if (info.mStorageID == storageID) {
        ALOGV("Moving file from %s to %s", (const char*)fromPath, (const char*)path);
        if (rename(fromPath, path)) {
            ALOGE("rename() failed from %s to %s", (const char*)fromPath, (const char*)path);
            result = MTP_RESPONSE_GENERAL_ERROR;
        }
    } else {
        ALOGV("Moving across storages from %s to %s", (const char*)fromPath, (const char*)path);
        if (copyFile(fromPath, path)) {
            result = MTP_RESPONSE_GENERAL_ERROR;
        } else {
            deletePath(fromPath);
        }
    }

    // If the move failed, undo the database change
    if (result != MTP_RESPONSE_OK)
        if (mDatabase->moveObject(objectHandle, info.mParent, fromPath) != MTP_RESPONSE_OK)
            ALOGE("Couldn't undo failed move");

    return result;
}

MtpResponseCode MtpServer::doCopyObject() {
    if (!hasStorage())
        return MTP_RESPONSE_GENERAL_ERROR;
    MtpResponseCode result = MTP_RESPONSE_OK;
    if (mRequest.getParameterCount() < 3)
        return MTP_RESPONSE_INVALID_PARAMETER;
    MtpObjectHandle objectHandle = mRequest.getParameter(1);
    MtpStorageID storageID = mRequest.getParameter(2);
    MtpStorage* storage = getStorage(storageID);
    MtpObjectHandle parent = mRequest.getParameter(3);
    if (!storage)
        return MTP_RESPONSE_INVALID_STORAGE_ID;
    MtpString path;

    MtpString fromPath;
    int64_t fileLength;
    MtpObjectFormat format;
    MtpObjectInfo info(objectHandle);
    result = mDatabase->getObjectInfo(objectHandle, info);
    if (result != MTP_RESPONSE_OK)
        return result;
    result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format);
    if (result != MTP_RESPONSE_OK)
        return result;

    // special case the root
    if (parent == 0) {
        path = storage->getPath();
    } else {
        int64_t parentLength;
        MtpObjectFormat parentFormat;
        result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat);
        if (result != MTP_RESPONSE_OK)
            return result;
        if (parentFormat != MTP_FORMAT_ASSOCIATION)
            return MTP_RESPONSE_INVALID_PARENT_OBJECT;
    }

    // check space first
    if ((uint64_t) fileLength > storage->getFreeSpace())
        return MTP_RESPONSE_STORAGE_FULL;

    if (path[path.size() - 1] != '/')
        path += "/";
    path += info.mName;

    MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
            format, parent, storageID, fileLength, info.mDateModified);
    if (handle == kInvalidObjectHandle) {
        return MTP_RESPONSE_GENERAL_ERROR;
    }

    ALOGV("Copying file from %s to %s", (const char*)fromPath, (const char*)path);
    if (copyFile(fromPath, path)) {
        result = MTP_RESPONSE_GENERAL_ERROR;
    }

    mDatabase->endSendObject(path, handle, format, result);
    mResponse.setParameter(1, handle);
    return result;
}

MtpResponseCode MtpServer::doSendObject() {
    if (!hasStorage())
        return MTP_RESPONSE_GENERAL_ERROR;
@@ -1124,65 +1261,6 @@ done:
    return result;
}

static void deleteRecursive(const char* path) {
    char pathbuf[PATH_MAX];
    size_t pathLength = strlen(path);
    if (pathLength >= sizeof(pathbuf) - 1) {
        ALOGE("path too long: %s\n", path);
    }
    strcpy(pathbuf, path);
    if (pathbuf[pathLength - 1] != '/') {
        pathbuf[pathLength++] = '/';
    }
    char* fileSpot = pathbuf + pathLength;
    int pathRemaining = sizeof(pathbuf) - pathLength - 1;

    DIR* dir = opendir(path);
    if (!dir) {
        ALOGE("opendir %s failed: %s", path, strerror(errno));
        return;
    }

    struct dirent* entry;
    while ((entry = readdir(dir))) {
        const char* name = entry->d_name;

        // ignore "." and ".."
        if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
            continue;
        }

        int nameLength = strlen(name);
        if (nameLength > pathRemaining) {
            ALOGE("path %s/%s too long\n", path, name);
            continue;
        }
        strcpy(fileSpot, name);

        if (entry->d_type == DT_DIR) {
            deleteRecursive(pathbuf);
            rmdir(pathbuf);
        } else {
            unlink(pathbuf);
        }
    }
    closedir(dir);
}

static void deletePath(const char* path) {
    struct stat statbuf;
    if (stat(path, &statbuf) == 0) {
        if (S_ISDIR(statbuf.st_mode)) {
            deleteRecursive(path);
            rmdir(path);
        } else {
            unlink(path);
        }
    } else {
        ALOGE("deletePath stat failed for %s: %s", path, strerror(errno));
    }
}

MtpResponseCode MtpServer::doDeleteObject() {
    if (!hasStorage())
        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+2 −0
Original line number Diff line number Diff line
@@ -161,6 +161,8 @@ private:
    MtpResponseCode     doSendObjectInfo();
    MtpResponseCode     doSendObject();
    MtpResponseCode     doDeleteObject();
    MtpResponseCode     doMoveObject();
    MtpResponseCode     doCopyObject();
    MtpResponseCode     doGetObjectPropDesc();
    MtpResponseCode     doGetDevicePropDesc();
    MtpResponseCode     doSendPartialObject();
+107 −0
Original line number Diff line number Diff line
@@ -16,13 +16,23 @@

#define LOG_TAG "MtpUtils"

#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "MtpUtils.h"

namespace android {

constexpr unsigned long FILE_COPY_SIZE = 262144;

/*
DateTime strings follow a compatible subset of the definition found in ISO 8601, and
take the form of a Unicode string formatted as: "YYYYMMDDThhmmss.s". In this
@@ -78,4 +88,101 @@ void formatDateTime(time_t seconds, char* buffer, int bufferLength) {
        tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
}

int copyFile(const char *fromPath, const char *toPath) {
    auto start = std::chrono::steady_clock::now();

    android::base::unique_fd fromFd(open(fromPath, O_RDONLY));
    if (fromFd == -1) {
        PLOG(ERROR) << "Failed to open copy from " << fromPath;
        return -1;
    }
    android::base::unique_fd toFd(open(toPath, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR));
    if (toFd == -1) {
        PLOG(ERROR) << "Failed to open copy to " << toPath;
        return -1;
    }
    off_t offset = 0;

    struct stat sstat = {};
    if (stat(fromPath, &sstat) == -1)
        return -1;

    off_t length = sstat.st_size;
    int ret = 0;

    while (offset < length) {
        ssize_t transfer_length = std::min(length - offset, (off_t) FILE_COPY_SIZE);
        ret = sendfile(toFd, fromFd, &offset, transfer_length);
        if (ret != transfer_length) {
            ret = -1;
            PLOG(ERROR) << "Copying failed!";
            break;
        }
    }
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;
    LOG(INFO) << "Copied a file with MTP. Time: " << diff.count() << " s, Size: " << length <<
        ", Rate: " << ((double) length) / diff.count() << " bytes/s";
    return ret == -1 ? -1 : 0;
}

void deleteRecursive(const char* path) {
    char pathbuf[PATH_MAX];
    size_t pathLength = strlen(path);
    if (pathLength >= sizeof(pathbuf) - 1) {
        LOG(ERROR) << "path too long: " << path;
    }
    strcpy(pathbuf, path);
    if (pathbuf[pathLength - 1] != '/') {
        pathbuf[pathLength++] = '/';
    }
    char* fileSpot = pathbuf + pathLength;
    int pathRemaining = sizeof(pathbuf) - pathLength - 1;

    DIR* dir = opendir(path);
    if (!dir) {
        PLOG(ERROR) << "opendir " << path << " failed";
        return;
    }

    struct dirent* entry;
    while ((entry = readdir(dir))) {
        const char* name = entry->d_name;

        // ignore "." and ".."
        if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
            continue;
        }

        int nameLength = strlen(name);
        if (nameLength > pathRemaining) {
            LOG(ERROR) << "path " << path << "/" << name << " too long";
            continue;
        }
        strcpy(fileSpot, name);

        if (entry->d_type == DT_DIR) {
            deleteRecursive(pathbuf);
            rmdir(pathbuf);
        } else {
            unlink(pathbuf);
        }
    }
    closedir(dir);
}

void deletePath(const char* path) {
    struct stat statbuf;
    if (stat(path, &statbuf) == 0) {
        if (S_ISDIR(statbuf.st_mode)) {
            deleteRecursive(path);
            rmdir(path);
        } else {
            unlink(path);
        }
    } else {
        PLOG(ERROR) << "deletePath stat failed for " << path;;
    }
}

}  // namespace android
+4 −0
Original line number Diff line number Diff line
@@ -24,6 +24,10 @@ namespace android {
bool parseDateTime(const char* dateTime, time_t& outSeconds);
void formatDateTime(time_t seconds, char* buffer, int bufferLength);

int copyFile(const char *fromPath, const char *toPath);
void deleteRecursive(const char* path);
void deletePath(const char* path);

}; // namespace android

#endif // _MTP_UTILS_H