Merge "Add implementations for move and copy operations."

gugelfrei
Treehugger Robot 7 years ago committed by Gerrit Code Review
commit 24287d0bd5

@ -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;

@ -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;

@ -161,6 +161,8 @@ private:
MtpResponseCode doSendObjectInfo();
MtpResponseCode doSendObject();
MtpResponseCode doDeleteObject();
MtpResponseCode doMoveObject();
MtpResponseCode doCopyObject();
MtpResponseCode doGetObjectPropDesc();
MtpResponseCode doGetDevicePropDesc();
MtpResponseCode doSendPartialObject();

@ -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

@ -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

Loading…
Cancel
Save