diff options
author | Mike Lockwood <lockwood@android.com> | 2011-04-21 17:05:55 -0700 |
---|---|---|
committer | Mike Lockwood <lockwood@android.com> | 2011-05-04 10:41:20 -0400 |
commit | fdb50e6f8eaac31010b64fc79a60dacb5f776613 (patch) | |
tree | 1ceebb39efa006a324770eef30283e6c4a422899 /media | |
parent | fb6232635d339c83ca100e472b159f103dafb6e2 (diff) | |
download | frameworks_base-fdb50e6f8eaac31010b64fc79a60dacb5f776613.zip frameworks_base-fdb50e6f8eaac31010b64fc79a60dacb5f776613.tar.gz frameworks_base-fdb50e6f8eaac31010b64fc79a60dacb5f776613.tar.bz2 |
DO NOT MERGE MTP: Add extended operations to support in-place editing of files
MTP does not support partial writes of files (the entire file must be transferred at once).
This makes it impossible to implement a FUSE file system for MTP
with acceptable performance.
To fix this problem, this change adds extended MTP operations to allow
partial writes to files:
SendPartialObject - allows writing a subset of a file, or appending to the end of a file
TruncateObject - allows changing the size of a file
BeginEditObject - must be called before using SendPartialObject and TruncateObject
EndEditObject - commits changes to a file after it has been edited with SendPartialObject or TruncateObject
We also add GetPartialObject64, which is the same as GetPartialObject
but has a 64 bit offset rather than 32.
Change-Id: I000930b787b00a2da0b57de9790053b2d71b86fd
Signed-off-by: Mike Lockwood <lockwood@android.com>
Diffstat (limited to 'media')
-rw-r--r-- | media/jni/android_mtp_MtpDatabase.cpp | 42 | ||||
-rw-r--r-- | media/mtp/MtpDatabase.h | 3 | ||||
-rw-r--r-- | media/mtp/MtpDebug.cpp | 6 | ||||
-rw-r--r-- | media/mtp/MtpServer.cpp | 231 | ||||
-rw-r--r-- | media/mtp/MtpServer.h | 23 | ||||
-rw-r--r-- | media/mtp/mtp.h | 13 |
6 files changed, 280 insertions, 38 deletions
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index 17d39e3..e71ed4c 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -29,6 +29,7 @@ #include "MtpDatabase.h" #include "MtpDataPacket.h" +#include "MtpObjectInfo.h" #include "MtpProperty.h" #include "MtpStringBuffer.h" #include "MtpUtils.h" @@ -138,7 +139,7 @@ public: MtpDataPacket& packet); virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, - MtpDataPacket& packet); + MtpObjectInfo& info); virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, MtpString& outFilePath, @@ -746,7 +747,7 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, } MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, - MtpDataPacket& packet) { + MtpObjectInfo& info) { char date[20]; JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -756,46 +757,27 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, return MTP_RESPONSE_INVALID_OBJECT_HANDLE; jint* intValues = env->GetIntArrayElements(mIntBuffer, 0); - MtpStorageID storageID = intValues[0]; - MtpObjectFormat format = intValues[1]; - MtpObjectHandle parent = intValues[2]; + info.mStorageID = intValues[0]; + info.mFormat = intValues[1]; + info.mParent = intValues[2]; env->ReleaseIntArrayElements(mIntBuffer, intValues, 0); jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); uint64_t size = longValues[0]; - uint64_t modified = longValues[1]; + info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size); + info.mDateModified = longValues[1]; env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); -// int associationType = (format == MTP_FORMAT_ASSOCIATION ? +// info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ? // MTP_ASSOCIATION_TYPE_GENERIC_FOLDER : // MTP_ASSOCIATION_TYPE_UNDEFINED); - int associationType = MTP_ASSOCIATION_TYPE_UNDEFINED; - - packet.putUInt32(storageID); - packet.putUInt16(format); - packet.putUInt16(0); // protection status - packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size)); - packet.putUInt16(0); // thumb format - packet.putUInt32(0); // thumb compressed size - packet.putUInt32(0); // thumb pix width - packet.putUInt32(0); // thumb pix height - packet.putUInt32(0); // image pix width - packet.putUInt32(0); // image pix height - packet.putUInt32(0); // image bit depth - packet.putUInt32(parent); - packet.putUInt16(associationType); - packet.putUInt32(0); // association desc - packet.putUInt32(0); // sequence number + info.mAssociationType = MTP_ASSOCIATION_TYPE_UNDEFINED; jchar* str = env->GetCharArrayElements(mStringBuffer, 0); - packet.putString(str); // file name + MtpString temp(str); + info.mName = strdup((const char *)temp); env->ReleaseCharArrayElements(mStringBuffer, str, 0); - packet.putEmptyString(); - formatDateTime(modified, date, sizeof(date)); - packet.putString(date); // date modified - packet.putEmptyString(); // keywords - checkAndClearExceptionFromCallback(env, __FUNCTION__); return MTP_RESPONSE_OK; } diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h index 4d9a1ae..d7bde00 100644 --- a/media/mtp/MtpDatabase.h +++ b/media/mtp/MtpDatabase.h @@ -23,6 +23,7 @@ namespace android { class MtpDataPacket; class MtpProperty; +class MtpObjectInfo; class MtpDatabase { public: @@ -81,7 +82,7 @@ public: MtpDataPacket& packet) = 0; virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, - MtpDataPacket& packet) = 0; + MtpObjectInfo& info) = 0; virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, MtpString& outFilePath, diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp index 1668ecf..9f3037d 100644 --- a/media/mtp/MtpDebug.cpp +++ b/media/mtp/MtpDebug.cpp @@ -63,6 +63,12 @@ static const CodeEntry sOperationCodes[] = { { "MTP_OPERATION_GET_OBJECT_REFERENCES", 0x9810 }, { "MTP_OPERATION_SET_OBJECT_REFERENCES", 0x9811 }, { "MTP_OPERATION_SKIP", 0x9820 }, + // android extensions + { "MTP_OPERATION_GET_PARTIAL_OBJECT_64", 0x95C1 }, + { "MTP_OPERATION_SEND_PARTIAL_OBJECT", 0x95C2 }, + { "MTP_OPERATION_TRUNCATE_OBJECT", 0x95C3 }, + { "MTP_OPERATION_BEGIN_EDIT_OBJECT", 0x95C4 }, + { "MTP_OPERATION_END_EDIT_OBJECT", 0x95C5 }, { 0, 0 }, }; diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 37e02a3..ff4009c 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -30,6 +30,7 @@ #include "MtpDebug.h" #include "MtpDatabase.h" +#include "MtpObjectInfo.h" #include "MtpProperty.h" #include "MtpServer.h" #include "MtpStorage.h" @@ -79,6 +80,12 @@ static const MtpOperationCode kSupportedOperationCodes[] = { MTP_OPERATION_GET_OBJECT_REFERENCES, MTP_OPERATION_SET_OBJECT_REFERENCES, // MTP_OPERATION_SKIP, + // Android extension for direct file IO + MTP_OPERATION_GET_PARTIAL_OBJECT_64, + MTP_OPERATION_SEND_PARTIAL_OBJECT, + MTP_OPERATION_TRUNCATE_OBJECT, + MTP_OPERATION_BEGIN_EDIT_OBJECT, + MTP_OPERATION_END_EDIT_OBJECT, }; static const MtpEventCode kSupportedEventCodes[] = { @@ -218,6 +225,15 @@ void MtpServer::run() { } } + // commit any open edits + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + commitEdit(edit); + delete edit; + } + mObjectEditList.clear(); + if (mSessionOpen) mDatabase->sessionEnded(); } @@ -252,6 +268,44 @@ void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) { } } +void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path, + uint64_t size, MtpObjectFormat format, int fd) { + ObjectEdit* edit = new ObjectEdit; + edit->handle = handle; + edit->path = path; + edit->size = size; + edit->format = format; + edit->fd = fd; + mObjectEditList.add(edit); +} + +MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) { + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + if (edit->handle == handle) return edit; + } + return NULL; +} + +void MtpServer::removeEditObject(MtpObjectHandle handle) { + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + if (edit->handle == handle) { + delete edit; + mObjectEditList.removeAt(i); + return; + } + } + LOGE("ObjectEdit not found in removeEditObject"); +} + +void MtpServer::commitEdit(ObjectEdit* edit) { + mDatabase->endSendObject((const char *)edit->path, edit->handle, edit->format, true); +} + + bool MtpServer::handleRequest() { Mutex::Autolock autoLock(mMutex); @@ -322,7 +376,8 @@ bool MtpServer::handleRequest() { response = doGetObject(); break; case MTP_OPERATION_GET_PARTIAL_OBJECT: - response = doGetPartialObject(); + case MTP_OPERATION_GET_PARTIAL_OBJECT_64: + response = doGetPartialObject(operation); break; case MTP_OPERATION_SEND_OBJECT_INFO: response = doSendObjectInfo(); @@ -339,6 +394,18 @@ bool MtpServer::handleRequest() { case MTP_OPERATION_GET_DEVICE_PROP_DESC: response = doGetDevicePropDesc(); break; + case MTP_OPERATION_SEND_PARTIAL_OBJECT: + response = doSendPartialObject(); + break; + case MTP_OPERATION_TRUNCATE_OBJECT: + response = doTruncateObject(); + break; + case MTP_OPERATION_BEGIN_EDIT_OBJECT: + response = doBeginEditObject(); + break; + case MTP_OPERATION_END_EDIT_OBJECT: + response = doEndEditObject(); + break; default: LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation)); response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED; @@ -363,7 +430,7 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { mData.putUInt16(MTP_STANDARD_VERSION); mData.putUInt32(6); // MTP Vendor Extension ID mData.putUInt16(MTP_STANDARD_VERSION); - string.set("microsoft.com: 1.0;"); + string.set("microsoft.com: 1.0; android.com: 1.0;"); mData.putString(string); // MTP Extensions mData.putUInt16(0); //Functional Mode mData.putAUInt16(kSupportedOperationCodes, @@ -601,7 +668,40 @@ MtpResponseCode MtpServer::doGetObjectInfo() { if (!hasStorage()) return MTP_RESPONSE_INVALID_OBJECT_HANDLE; MtpObjectHandle handle = mRequest.getParameter(1); - return mDatabase->getObjectInfo(handle, mData); + MtpObjectInfo info(handle); + MtpResponseCode result = mDatabase->getObjectInfo(handle, info); + if (result == MTP_RESPONSE_OK) { + char date[20]; + + mData.putUInt32(info.mStorageID); + mData.putUInt16(info.mFormat); + mData.putUInt16(info.mProtectionStatus); + + // if object is being edited the database size may be out of date + uint32_t size = info.mCompressedSize; + ObjectEdit* edit = getEditObject(handle); + if (edit) + size = (edit->size > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->size); + mData.putUInt32(size); + + mData.putUInt16(info.mThumbFormat); + mData.putUInt32(info.mThumbCompressedSize); + mData.putUInt32(info.mThumbPixWidth); + mData.putUInt32(info.mThumbPixHeight); + mData.putUInt32(info.mImagePixWidth); + mData.putUInt32(info.mImagePixHeight); + mData.putUInt32(info.mImagePixDepth); + mData.putUInt32(info.mParent); + mData.putUInt16(info.mAssociationType); + mData.putUInt32(info.mAssociationDesc); + mData.putUInt32(info.mSequenceNumber); + mData.putString(info.mName); + mData.putEmptyString(); // date created + formatDateTime(info.mDateModified, date, sizeof(date)); + mData.putString(date); // date modified + mData.putEmptyString(); // keywords + } + return result; } MtpResponseCode MtpServer::doGetObject() { @@ -641,12 +741,22 @@ MtpResponseCode MtpServer::doGetObject() { return MTP_RESPONSE_OK; } -MtpResponseCode MtpServer::doGetPartialObject() { +MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) { if (!hasStorage()) return MTP_RESPONSE_INVALID_OBJECT_HANDLE; MtpObjectHandle handle = mRequest.getParameter(1); - uint32_t offset = mRequest.getParameter(2); - uint32_t length = mRequest.getParameter(3); + uint64_t offset; + uint32_t length; + offset = mRequest.getParameter(2); + if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) { + // android extension with 64 bit offset + uint64_t offset2 = mRequest.getParameter(3); + offset = offset | (offset2 << 32); + length = mRequest.getParameter(4); + } else { + // standard GetPartialObject + length = mRequest.getParameter(3); + } MtpString pathBuf; int64_t fileLength; MtpObjectFormat format; @@ -933,4 +1043,113 @@ MtpResponseCode MtpServer::doGetDevicePropDesc() { return MTP_RESPONSE_OK; } +MtpResponseCode MtpServer::doSendPartialObject() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + MtpObjectHandle handle = mRequest.getParameter(1); + uint64_t offset = mRequest.getParameter(2); + uint64_t offset2 = mRequest.getParameter(3); + offset = offset | (offset2 << 32); + uint32_t length = mRequest.getParameter(4); + + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + LOGE("object not open for edit in doSendPartialObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + // can't start writing past the end of the file + if (offset > edit->size) { + LOGD("writing past end of object, offset: %lld, edit->size: %lld", offset, edit->size); + return MTP_RESPONSE_GENERAL_ERROR; + } + + // 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(); + + const char* filePath = (const char *)edit->path; + LOGV("receiving partial %s %lld %ld\n", filePath, offset, length); + mtp_file_range mfr; + mfr.fd = edit->fd; + mfr.offset = offset; + mfr.length = length; + + // transfer the file + ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr); + LOGV("MTP_RECEIVE_FILE returned %d", ret); + if (ret < 0) { + mResponse.setParameter(1, 0); + if (errno == ECANCELED) + return MTP_RESPONSE_TRANSACTION_CANCELLED; + else + return MTP_RESPONSE_GENERAL_ERROR; + } + mResponse.setParameter(1, length); + uint64_t end = offset + length; + if (end > edit->size) { + edit->size = end; + } + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doTruncateObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + LOGE("object not open for edit in doTruncateObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + uint64_t offset = mRequest.getParameter(2); + uint64_t offset2 = mRequest.getParameter(3); + offset |= (offset2 << 32); + if (ftruncate(edit->fd, offset) != 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } else { + edit->size = offset; + return MTP_RESPONSE_OK; + } +} + +MtpResponseCode MtpServer::doBeginEditObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + if (getEditObject(handle)) { + LOGE("object already open for edit in doBeginEditObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + MtpString path; + int64_t fileLength; + MtpObjectFormat format; + int result = mDatabase->getObjectFilePath(handle, path, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + + int fd = open((const char *)path, O_RDWR | O_EXCL); + if (fd < 0) { + LOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno); + return MTP_RESPONSE_GENERAL_ERROR; + } + + addEditObject(handle, path, fileLength, format, fd); + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doEndEditObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + LOGE("object not open for edit in doEndEditObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + commitEdit(edit); + removeEditObject(handle); + return MTP_RESPONSE_OK; +} + } // namespace android diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h index fa729a8..35a38e7 100644 --- a/media/mtp/MtpServer.h +++ b/media/mtp/MtpServer.h @@ -65,6 +65,17 @@ private: Mutex mMutex; + // represents an MTP object that is being edited using the android extensions + // for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject) + struct ObjectEdit { + MtpObjectHandle handle; + MtpString path; + uint64_t size; + MtpObjectFormat format; + int fd; + }; + Vector<ObjectEdit*> mObjectEditList; + public: MtpServer(int fd, MtpDatabase* database, int fileGroup, int filePerm, int directoryPerm); @@ -86,6 +97,12 @@ private: void sendStoreRemoved(MtpStorageID id); void sendEvent(MtpEventCode code, uint32_t param1); + void addEditObject(MtpObjectHandle handle, MtpString& path, + uint64_t size, MtpObjectFormat format, int fd); + ObjectEdit* getEditObject(MtpObjectHandle handle); + void removeEditObject(MtpObjectHandle handle); + void commitEdit(ObjectEdit* edit); + bool handleRequest(); MtpResponseCode doGetDeviceInfo(); @@ -106,12 +123,16 @@ private: MtpResponseCode doGetObjectPropList(); MtpResponseCode doGetObjectInfo(); MtpResponseCode doGetObject(); - MtpResponseCode doGetPartialObject(); + MtpResponseCode doGetPartialObject(MtpOperationCode operation); MtpResponseCode doSendObjectInfo(); MtpResponseCode doSendObject(); MtpResponseCode doDeleteObject(); MtpResponseCode doGetObjectPropDesc(); MtpResponseCode doGetDevicePropDesc(); + MtpResponseCode doSendPartialObject(); + MtpResponseCode doTruncateObject(); + MtpResponseCode doBeginEditObject(); + MtpResponseCode doEndEditObject(); }; }; // namespace android diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h index 8bc2e22..d270df5 100644 --- a/media/mtp/mtp.h +++ b/media/mtp/mtp.h @@ -391,6 +391,19 @@ #define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811 #define MTP_OPERATION_SKIP 0x9820 +// Android extensions for direct file IO + +// Same as GetPartialObject, but with 64 bit offset +#define MTP_OPERATION_GET_PARTIAL_OBJECT_64 0x95C1 +// Same as GetPartialObject64, but copying host to device +#define MTP_OPERATION_SEND_PARTIAL_OBJECT 0x95C2 +// Truncates file to 64 bit length +#define MTP_OPERATION_TRUNCATE_OBJECT 0x95C3 +// Must be called before using SendPartialObject and TruncateObject +#define MTP_OPERATION_BEGIN_EDIT_OBJECT 0x95C4 +// Called to commit changes made by SendPartialObject and TruncateObject +#define MTP_OPERATION_END_EDIT_OBJECT 0x95C5 + // MTP Response Codes #define MTP_RESPONSE_UNDEFINED 0x2000 #define MTP_RESPONSE_OK 0x2001 |