Uses a new "MTP objects" table in the media provider to support basic enumeration of the external storage file system. Support for accessing audio, video and image metadata in the existing media provider tables will be added in a later commit. The C++ MtpDatabase class is now abstract, to support a proxy subclass that calls through JNI to the Java MtpDatabase class in the media provider. Change-Id: I90f0db5f3acc5d35ae78c27a8507edff16d14305 Signed-off-by: Mike Lockwood <lockwood@android.com>
636 lines
20 KiB
C++
636 lines
20 KiB
C++
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
|
|
#include <cutils/properties.h>
|
|
|
|
#include "MtpDebug.h"
|
|
#include "MtpProperty.h"
|
|
#include "MtpServer.h"
|
|
#include "MtpSqliteDatabase.h"
|
|
#include "MtpStorage.h"
|
|
#include "MtpStringBuffer.h"
|
|
|
|
#include "f_mtp.h"
|
|
|
|
namespace android {
|
|
|
|
static const MtpOperationCode kSupportedOperationCodes[] = {
|
|
MTP_OPERATION_GET_DEVICE_INFO,
|
|
MTP_OPERATION_OPEN_SESSION,
|
|
MTP_OPERATION_CLOSE_SESSION,
|
|
MTP_OPERATION_GET_STORAGE_IDS,
|
|
MTP_OPERATION_GET_STORAGE_INFO,
|
|
MTP_OPERATION_GET_NUM_OBJECTS,
|
|
MTP_OPERATION_GET_OBJECT_HANDLES,
|
|
MTP_OPERATION_GET_OBJECT_INFO,
|
|
MTP_OPERATION_GET_OBJECT,
|
|
// MTP_OPERATION_GET_THUMB,
|
|
MTP_OPERATION_DELETE_OBJECT,
|
|
MTP_OPERATION_SEND_OBJECT_INFO,
|
|
MTP_OPERATION_SEND_OBJECT,
|
|
// MTP_OPERATION_INITIATE_CAPTURE,
|
|
// MTP_OPERATION_FORMAT_STORE,
|
|
// MTP_OPERATION_RESET_DEVICE,
|
|
// MTP_OPERATION_SELF_TEST,
|
|
// MTP_OPERATION_SET_OBJECT_PROTECTION,
|
|
// MTP_OPERATION_POWER_DOWN,
|
|
MTP_OPERATION_GET_DEVICE_PROP_DESC,
|
|
MTP_OPERATION_GET_DEVICE_PROP_VALUE,
|
|
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_GET_PARTIAL_OBJECT,
|
|
// MTP_OPERATION_INITIATE_OPEN_CAPTURE,
|
|
MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
|
|
// MTP_OPERATION_GET_OBJECT_PROP_DESC,
|
|
MTP_OPERATION_GET_OBJECT_PROP_VALUE,
|
|
MTP_OPERATION_SET_OBJECT_PROP_VALUE,
|
|
// MTP_OPERATION_GET_OBJECT_REFERENCES,
|
|
// MTP_OPERATION_SET_OBJECT_REFERENCES,
|
|
// MTP_OPERATION_SKIP,
|
|
};
|
|
|
|
static const MtpObjectProperty kSupportedObjectProperties[] = {
|
|
MTP_PROPERTY_STORAGE_ID,
|
|
MTP_PROPERTY_OBJECT_FORMAT,
|
|
MTP_PROPERTY_OBJECT_SIZE,
|
|
MTP_PROPERTY_OBJECT_FILE_NAME,
|
|
MTP_PROPERTY_PARENT_OBJECT,
|
|
};
|
|
|
|
static const MtpObjectFormat kSupportedPlaybackFormats[] = {
|
|
// MTP_FORMAT_UNDEFINED,
|
|
MTP_FORMAT_ASSOCIATION,
|
|
// MTP_FORMAT_TEXT,
|
|
// MTP_FORMAT_HTML,
|
|
MTP_FORMAT_MP3,
|
|
//MTP_FORMAT_AVI,
|
|
MTP_FORMAT_MPEG,
|
|
// MTP_FORMAT_ASF,
|
|
MTP_FORMAT_EXIF_JPEG,
|
|
MTP_FORMAT_TIFF_EP,
|
|
// MTP_FORMAT_BMP,
|
|
MTP_FORMAT_GIF,
|
|
MTP_FORMAT_JFIF,
|
|
MTP_FORMAT_PNG,
|
|
MTP_FORMAT_TIFF,
|
|
MTP_FORMAT_WMA,
|
|
MTP_FORMAT_OGG,
|
|
MTP_FORMAT_AAC,
|
|
// MTP_FORMAT_FLAC,
|
|
// MTP_FORMAT_WMV,
|
|
MTP_FORMAT_MP4_CONTAINER,
|
|
MTP_FORMAT_MP2,
|
|
MTP_FORMAT_3GP_CONTAINER,
|
|
// MTP_FORMAT_ABSTRACT_AUDIO_ALBUM,
|
|
// MTP_FORMAT_ABSTRACT_AV_PLAYLIST,
|
|
// MTP_FORMAT_WPL_PLAYLIST,
|
|
// MTP_FORMAT_M3U_PLAYLIST,
|
|
// MTP_FORMAT_MPL_PLAYLIST,
|
|
// MTP_FORMAT_PLS_PLAYLIST,
|
|
};
|
|
|
|
MtpServer::MtpServer(int fd, MtpDatabase* database,
|
|
int fileGroup, int filePerm, int directoryPerm)
|
|
: mFD(fd),
|
|
mDatabase(database),
|
|
mFileGroup(fileGroup),
|
|
mFilePermission(filePerm),
|
|
mDirectoryPermission(directoryPerm),
|
|
mSessionID(0),
|
|
mSessionOpen(false),
|
|
mSendObjectHandle(kInvalidObjectHandle),
|
|
mSendObjectFileSize(0)
|
|
{
|
|
initObjectProperties();
|
|
}
|
|
|
|
MtpServer::~MtpServer() {
|
|
}
|
|
|
|
void MtpServer::addStorage(const char* filePath) {
|
|
int index = mStorages.size() + 1;
|
|
index |= index << 16; // set high and low part to our index
|
|
MtpStorage* storage = new MtpStorage(index, filePath, mDatabase);
|
|
addStorage(storage);
|
|
}
|
|
|
|
MtpStorage* MtpServer::getStorage(MtpStorageID id) {
|
|
for (int i = 0; i < mStorages.size(); i++) {
|
|
MtpStorage* storage = mStorages[i];
|
|
if (storage->getStorageID() == id)
|
|
return storage;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void MtpServer::scanStorage() {
|
|
for (int i = 0; i < mStorages.size(); i++) {
|
|
MtpStorage* storage = mStorages[i];
|
|
storage->scanFiles();
|
|
}
|
|
}
|
|
|
|
void MtpServer::run() {
|
|
int fd = mFD;
|
|
|
|
LOGV("MtpServer::run fd: %d\n", fd);
|
|
|
|
while (1) {
|
|
int ret = mRequest.read(fd);
|
|
if (ret < 0) {
|
|
LOGE("request read returned %d, errno: %d", ret, errno);
|
|
if (errno == ECANCELED) {
|
|
// return to top of loop and wait for next command
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
MtpOperationCode operation = mRequest.getOperationCode();
|
|
MtpTransactionID transaction = mRequest.getTransactionID();
|
|
|
|
LOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
|
|
mRequest.dump();
|
|
|
|
// FIXME need to generalize this
|
|
bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO);
|
|
if (dataIn) {
|
|
int ret = mData.read(fd);
|
|
if (ret < 0) {
|
|
LOGE("data read returned %d, errno: %d", ret, errno);
|
|
if (errno == ECANCELED) {
|
|
// return to top of loop and wait for next command
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
LOGV("received data:");
|
|
mData.dump();
|
|
} else {
|
|
mData.reset();
|
|
}
|
|
|
|
if (handleRequest()) {
|
|
if (!dataIn && mData.hasData()) {
|
|
mData.setOperationCode(operation);
|
|
mData.setTransactionID(transaction);
|
|
LOGV("sending data:");
|
|
mData.dump();
|
|
ret = mData.write(fd);
|
|
if (ret < 0) {
|
|
LOGE("request write returned %d, errno: %d", ret, errno);
|
|
if (errno == ECANCELED) {
|
|
// return to top of loop and wait for next command
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
mResponse.setTransactionID(transaction);
|
|
LOGV("sending response %04X", mResponse.getResponseCode());
|
|
ret = mResponse.write(fd);
|
|
if (ret < 0) {
|
|
LOGE("request write returned %d, errno: %d", ret, errno);
|
|
if (errno == ECANCELED) {
|
|
// return to top of loop and wait for next command
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
LOGV("skipping response\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
MtpProperty* MtpServer::getObjectProperty(MtpPropertyCode propCode) {
|
|
for (int i = 0; i < mObjectProperties.size(); i++) {
|
|
MtpProperty* property = mObjectProperties[i];
|
|
if (property->getPropertyCode() == propCode)
|
|
return property;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MtpProperty* MtpServer::getDeviceProperty(MtpPropertyCode propCode) {
|
|
for (int i = 0; i < mDeviceProperties.size(); i++) {
|
|
MtpProperty* property = mDeviceProperties[i];
|
|
if (property->getPropertyCode() == propCode)
|
|
return property;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void MtpServer::initObjectProperties() {
|
|
mObjectProperties.push(new MtpProperty(MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT16));
|
|
mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16));
|
|
mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64));
|
|
mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR));
|
|
mObjectProperties.push(new MtpProperty(MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32));
|
|
}
|
|
|
|
bool MtpServer::handleRequest() {
|
|
MtpOperationCode operation = mRequest.getOperationCode();
|
|
MtpResponseCode response;
|
|
|
|
mResponse.reset();
|
|
|
|
if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
|
|
// FIXME - need to delete mSendObjectHandle from the database
|
|
LOGE("expected SendObject after SendObjectInfo");
|
|
mSendObjectHandle = kInvalidObjectHandle;
|
|
}
|
|
|
|
switch (operation) {
|
|
case MTP_OPERATION_GET_DEVICE_INFO:
|
|
response = doGetDeviceInfo();
|
|
break;
|
|
case MTP_OPERATION_OPEN_SESSION:
|
|
response = doOpenSession();
|
|
break;
|
|
case MTP_OPERATION_CLOSE_SESSION:
|
|
response = doCloseSession();
|
|
break;
|
|
case MTP_OPERATION_GET_STORAGE_IDS:
|
|
response = doGetStorageIDs();
|
|
break;
|
|
case MTP_OPERATION_GET_STORAGE_INFO:
|
|
response = doGetStorageInfo();
|
|
break;
|
|
case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
|
|
response = doGetObjectPropsSupported();
|
|
break;
|
|
case MTP_OPERATION_GET_OBJECT_HANDLES:
|
|
response = doGetObjectHandles();
|
|
break;
|
|
case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
|
|
response = doGetObjectPropValue();
|
|
break;
|
|
case MTP_OPERATION_GET_OBJECT_INFO:
|
|
response = doGetObjectInfo();
|
|
break;
|
|
case MTP_OPERATION_GET_OBJECT:
|
|
response = doGetObject();
|
|
break;
|
|
case MTP_OPERATION_SEND_OBJECT_INFO:
|
|
response = doSendObjectInfo();
|
|
break;
|
|
case MTP_OPERATION_SEND_OBJECT:
|
|
response = doSendObject();
|
|
break;
|
|
case MTP_OPERATION_DELETE_OBJECT:
|
|
response = doDeleteObject();
|
|
break;
|
|
case MTP_OPERATION_GET_OBJECT_PROP_DESC:
|
|
response = doGetObjectPropDesc();
|
|
break;
|
|
default:
|
|
response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
|
|
return false;
|
|
mResponse.setResponseCode(response);
|
|
return true;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetDeviceInfo() {
|
|
MtpStringBuffer string;
|
|
char prop_value[PROPERTY_VALUE_MAX];
|
|
|
|
// fill in device info
|
|
mData.putUInt16(MTP_STANDARD_VERSION);
|
|
mData.putUInt32(6); // MTP Vendor Extension ID
|
|
mData.putUInt16(MTP_STANDARD_VERSION);
|
|
string.set("microsoft.com: 1.0;");
|
|
mData.putString(string); // MTP Extensions
|
|
mData.putUInt16(0); //Functional Mode
|
|
mData.putAUInt16(kSupportedOperationCodes,
|
|
sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
|
|
mData.putEmptyArray(); // Events Supported
|
|
mData.putEmptyArray(); // Device Properties Supported
|
|
mData.putEmptyArray(); // Capture Formats
|
|
mData.putAUInt16(kSupportedPlaybackFormats,
|
|
sizeof(kSupportedPlaybackFormats) / sizeof(uint16_t)); // Playback Formats
|
|
// FIXME
|
|
string.set("Google, Inc.");
|
|
mData.putString(string); // Manufacturer
|
|
|
|
property_get("ro.product.model", prop_value, "MTP Device");
|
|
string.set(prop_value);
|
|
mData.putString(string); // Model
|
|
string.set("1.0");
|
|
mData.putString(string); // Device Version
|
|
|
|
property_get("ro.serialno", prop_value, "????????");
|
|
string.set(prop_value);
|
|
mData.putString(string); // Serial Number
|
|
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doOpenSession() {
|
|
if (mSessionOpen) {
|
|
mResponse.setParameter(1, mSessionID);
|
|
return MTP_RESPONSE_SESSION_ALREADY_OPEN;
|
|
}
|
|
mSessionID = mRequest.getParameter(1);
|
|
mSessionOpen = true;
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doCloseSession() {
|
|
if (!mSessionOpen)
|
|
return MTP_RESPONSE_SESSION_NOT_OPEN;
|
|
mSessionID = 0;
|
|
mSessionOpen = false;
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetStorageIDs() {
|
|
if (!mSessionOpen)
|
|
return MTP_RESPONSE_SESSION_NOT_OPEN;
|
|
|
|
int count = mStorages.size();
|
|
mData.putUInt32(count);
|
|
for (int i = 0; i < count; i++)
|
|
mData.putUInt32(mStorages[i]->getStorageID());
|
|
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetStorageInfo() {
|
|
MtpStringBuffer string;
|
|
|
|
if (!mSessionOpen)
|
|
return MTP_RESPONSE_SESSION_NOT_OPEN;
|
|
MtpStorageID id = mRequest.getParameter(1);
|
|
MtpStorage* storage = getStorage(id);
|
|
if (!storage)
|
|
return MTP_RESPONSE_INVALID_STORAGE_ID;
|
|
|
|
mData.putUInt16(storage->getType());
|
|
mData.putUInt16(storage->getFileSystemType());
|
|
mData.putUInt16(storage->getAccessCapability());
|
|
mData.putUInt64(storage->getMaxCapacity());
|
|
mData.putUInt64(storage->getFreeSpace());
|
|
mData.putUInt32(1024*1024*1024); // Free Space in Objects
|
|
string.set(storage->getDescription());
|
|
mData.putString(string);
|
|
mData.putEmptyString(); // Volume Identifier
|
|
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetObjectPropsSupported() {
|
|
if (!mSessionOpen)
|
|
return MTP_RESPONSE_SESSION_NOT_OPEN;
|
|
MtpObjectFormat format = mRequest.getParameter(1);
|
|
mData.putAUInt16(kSupportedObjectProperties,
|
|
sizeof(kSupportedObjectProperties) / sizeof(uint16_t));
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetObjectHandles() {
|
|
if (!mSessionOpen)
|
|
return MTP_RESPONSE_SESSION_NOT_OPEN;
|
|
MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
|
|
MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
|
|
MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
|
|
// 0x00000000 for all objects?
|
|
if (parent == 0xFFFFFFFF)
|
|
parent = 0;
|
|
|
|
MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
|
|
mData.putAUInt32(handles);
|
|
delete handles;
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetObjectPropValue() {
|
|
MtpObjectHandle handle = mRequest.getParameter(1);
|
|
MtpObjectProperty property = mRequest.getParameter(2);
|
|
|
|
return mDatabase->getObjectProperty(handle, property, mData);
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetObjectInfo() {
|
|
MtpObjectHandle handle = mRequest.getParameter(1);
|
|
return mDatabase->getObjectInfo(handle, mData);
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetObject() {
|
|
MtpObjectHandle handle = mRequest.getParameter(1);
|
|
MtpString pathBuf;
|
|
int64_t fileLength;
|
|
if (!mDatabase->getObjectFilePath(handle, pathBuf, fileLength))
|
|
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
|
const char* filePath = (const char *)pathBuf;
|
|
|
|
mtp_file_range mfr;
|
|
mfr.fd = open(filePath, O_RDONLY);
|
|
if (mfr.fd < 0) {
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
}
|
|
mfr.offset = 0;
|
|
mfr.length = fileLength;
|
|
|
|
// send data header
|
|
mData.setOperationCode(mRequest.getOperationCode());
|
|
mData.setTransactionID(mRequest.getTransactionID());
|
|
mData.writeDataHeader(mFD, fileLength);
|
|
|
|
// then transfer the file
|
|
int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
|
|
close(mfr.fd);
|
|
if (ret < 0) {
|
|
if (errno == ECANCELED)
|
|
return MTP_RESPONSE_TRANSACTION_CANCELLED;
|
|
else
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
}
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doSendObjectInfo() {
|
|
MtpString path;
|
|
MtpStorageID storageID = mRequest.getParameter(1);
|
|
MtpStorage* storage = getStorage(storageID);
|
|
MtpObjectHandle parent = mRequest.getParameter(2);
|
|
if (!storage)
|
|
return MTP_RESPONSE_INVALID_STORAGE_ID;
|
|
|
|
// special case the root
|
|
if (parent == MTP_PARENT_ROOT) {
|
|
path = storage->getPath();
|
|
parent = 0;
|
|
} else {
|
|
int64_t dummy;
|
|
if (!mDatabase->getObjectFilePath(parent, path, dummy))
|
|
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
|
}
|
|
|
|
// read only the fields we need
|
|
mData.getUInt32(); // storage ID
|
|
MtpObjectFormat format = mData.getUInt16();
|
|
mData.getUInt16(); // protection status
|
|
mSendObjectFileSize = mData.getUInt32();
|
|
mData.getUInt16(); // thumb format
|
|
mData.getUInt32(); // thumb compressed size
|
|
mData.getUInt32(); // thumb pix width
|
|
mData.getUInt32(); // thumb pix height
|
|
mData.getUInt32(); // image pix width
|
|
mData.getUInt32(); // image pix height
|
|
mData.getUInt32(); // image bit depth
|
|
mData.getUInt32(); // parent
|
|
uint16_t associationType = mData.getUInt16();
|
|
uint32_t associationDesc = mData.getUInt32(); // association desc
|
|
mData.getUInt32(); // sequence number
|
|
MtpStringBuffer name, created, modified;
|
|
mData.getString(name); // file name
|
|
mData.getString(created); // date created
|
|
mData.getString(modified); // date modified
|
|
// keywords follow
|
|
|
|
time_t modifiedTime;
|
|
if (!parseDateTime(modified, modifiedTime))
|
|
modifiedTime = 0;
|
|
|
|
if (path[path.size() - 1] != '/')
|
|
path += "/";
|
|
path += (const char *)name;
|
|
|
|
mDatabase->beginTransaction();
|
|
MtpObjectHandle handle = mDatabase->addFile((const char*)path, format, parent, storageID,
|
|
mSendObjectFileSize, modifiedTime);
|
|
if (handle == kInvalidObjectHandle) {
|
|
mDatabase->rollbackTransaction();
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
}
|
|
mDatabase->commitTransaction();
|
|
|
|
if (format == MTP_FORMAT_ASSOCIATION) {
|
|
mode_t mask = umask(0);
|
|
int ret = mkdir((const char *)path, mDirectoryPermission);
|
|
umask(mask);
|
|
if (ret && ret != -EEXIST)
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
chown((const char *)path, getuid(), mFileGroup);
|
|
} else {
|
|
mSendObjectFilePath = path;
|
|
// save the handle for the SendObject call, which should follow
|
|
mSendObjectHandle = handle;
|
|
}
|
|
|
|
mResponse.setParameter(1, storageID);
|
|
mResponse.setParameter(2, (parent == 0 ? 0xFFFFFFFF: parent));
|
|
mResponse.setParameter(3, handle);
|
|
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doSendObject() {
|
|
if (mSendObjectHandle == kInvalidObjectHandle) {
|
|
LOGE("Expected SendObjectInfo before SendObject");
|
|
return MTP_RESPONSE_NO_VALID_OBJECT_INFO;
|
|
}
|
|
|
|
// read the header
|
|
int ret = mData.readDataHeader(mFD);
|
|
// FIXME - check for errors here.
|
|
|
|
// reset so we don't attempt to send this back
|
|
mData.reset();
|
|
|
|
mtp_file_range mfr;
|
|
mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC);
|
|
if (mfr.fd < 0) {
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
}
|
|
fchown(mfr.fd, getuid(), mFileGroup);
|
|
// set permissions
|
|
mode_t mask = umask(0);
|
|
fchmod(mfr.fd, mFilePermission);
|
|
umask(mask);
|
|
|
|
mfr.offset = 0;
|
|
mfr.length = mSendObjectFileSize;
|
|
|
|
// transfer the file
|
|
ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
|
|
close(mfr.fd);
|
|
|
|
// FIXME - we need to delete mSendObjectHandle from the database if this fails.
|
|
LOGV("MTP_RECEIVE_FILE returned %d", ret);
|
|
mSendObjectHandle = kInvalidObjectHandle;
|
|
|
|
if (ret < 0) {
|
|
unlink(mSendObjectFilePath);
|
|
if (errno == ECANCELED)
|
|
return MTP_RESPONSE_TRANSACTION_CANCELLED;
|
|
else
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
}
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doDeleteObject() {
|
|
MtpObjectHandle handle = mRequest.getParameter(1);
|
|
MtpObjectFormat format = mRequest.getParameter(1);
|
|
// FIXME - support deleting all objects if handle is 0xFFFFFFFF
|
|
// FIXME - implement deleting objects by format
|
|
// FIXME - handle non-empty directories
|
|
|
|
MtpString filePath;
|
|
int64_t fileLength;
|
|
if (!mDatabase->getObjectFilePath(handle, filePath, fileLength))
|
|
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
|
|
|
LOGV("deleting %s", (const char *)filePath);
|
|
// one of these should work
|
|
rmdir((const char *)filePath);
|
|
unlink((const char *)filePath);
|
|
|
|
mDatabase->deleteFile(handle);
|
|
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
MtpResponseCode MtpServer::doGetObjectPropDesc() {
|
|
MtpObjectProperty propCode = mRequest.getParameter(1);
|
|
MtpObjectFormat format = mRequest.getParameter(2);
|
|
MtpProperty* property = getObjectProperty(propCode);
|
|
if (!property)
|
|
return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
|
|
|
|
property->write(mData);
|
|
return MTP_RESPONSE_OK;
|
|
}
|
|
|
|
} // namespace android
|