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

Commit 848fa509 authored by Jerry Zhang's avatar Jerry Zhang Committed by android-build-merger
Browse files

Merge "Add implementations for move and copy operations."

am: 24287d0b

Change-Id: I9a63975ec6f1dd0df0e93255e8a8be4ec1db170a
parents dad308b4 24287d0b
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