add mtp responder to TWRP.
Big thanks to Dees_Troy for helping with the implementation.

Change-Id: I6c9c522b9c9de5dc139e2ecb0141008182ba07f0
diff --git a/mtp/MtpServer.cpp b/mtp/MtpServer.cpp
new file mode 100755
index 0000000..5dec580
--- /dev/null
+++ b/mtp/MtpServer.cpp
@@ -0,0 +1,1337 @@
+/*
+ * 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.
+ *
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ */
+
+#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 <sys/stat.h>
+#include <dirent.h>
+#include "../twcommon.h"
+#include <cutils/properties.h>
+
+#include "MtpTypes.h"
+#include "MtpDebug.h"
+#include "MtpDatabase.h"
+#include "MtpObjectInfo.h"
+#include "MtpProperty.h"
+#include "MtpServer.h"
+#include "MtpStorage.h"
+#include "MtpStringBuffer.h"
+
+#include <linux/usb/f_mtp.h>
+
+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_PROP_LIST,
+//	MTP_OPERATION_SET_OBJECT_PROP_LIST,
+//	MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
+//	MTP_OPERATION_SEND_OBJECT_PROP_LIST,
+	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[] = {
+	MTP_EVENT_OBJECT_ADDED,
+	MTP_EVENT_OBJECT_REMOVED,
+	MTP_EVENT_STORE_ADDED,
+	MTP_EVENT_STORE_REMOVED,
+	MTP_EVENT_OBJECT_PROP_CHANGED,
+};
+
+MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp,
+					int fileGroup, int filePerm, int directoryPerm)
+	:	mFD(fd),
+		mDatabase(database),
+		mPtp(ptp),
+		mFileGroup(fileGroup),
+		mFilePermission(filePerm),
+		mDirectoryPermission(directoryPerm),
+		mSessionID(0),
+		mSessionOpen(false),
+		mSendObjectHandle(kInvalidObjectHandle),
+		mSendObjectFormat(0),
+		mSendObjectFileSize(0)
+{
+}
+
+MtpServer::~MtpServer() {
+}
+
+void MtpServer::addStorage(MtpStorage* storage) {
+	MTPD("addStorage(): storage: %x\n", storage);
+	mDatabase->createDB(storage, storage->getStorageID());
+	android::Mutex::Autolock autoLock(mMutex);
+	mStorages.push(storage);
+	sendStoreAdded(storage->getStorageID());
+}
+
+void MtpServer::removeStorage(MtpStorage* storage) {
+	android::Mutex::Autolock autoLock(mMutex);
+
+	for (size_t i = 0; i < mStorages.size(); i++) {
+		if (mStorages[i] == storage) {
+			mStorages.removeAt(i);
+			sendStoreRemoved(storage->getStorageID());
+			break;
+		}
+	}
+}
+
+MtpStorage* MtpServer::getStorage(MtpStorageID id) {
+	MTPD("getStorage\n");
+	if (id == 0) {
+		MTPD("mStorages\n");
+		return mStorages[0];
+	}
+	for (size_t i = 0; i < mStorages.size(); i++) {
+		MtpStorage* storage = mStorages[i];
+		MTPD("id: %d\n", id);
+		MTPD("storage: %d\n", storage->getStorageID());
+		if (storage->getStorageID() == id) {
+			return storage;
+		}
+	}
+	return NULL;
+}
+
+bool MtpServer::hasStorage(MtpStorageID id) {
+	MTPD("in hasStorage\n");
+	if (id == 0 || id == 0xFFFFFFFF)
+		return mStorages.size() > 0;
+	return (getStorage(id) != NULL);
+}
+
+void MtpServer::run() {
+	int fd = mFD;
+
+	MTPI("MtpServer::run fd: %d\n", fd);
+
+	while (1) {
+		MTPD("About to read device...\n");
+		int ret = mRequest.read(fd);
+		if (ret < 0) {
+			MTPD("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();
+
+		MTPD("operation: %s", MtpDebug::getOperationCodeName(operation));
+		mRequest.dump();
+
+		// FIXME need to generalize this
+		bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
+					|| operation == MTP_OPERATION_SET_OBJECT_REFERENCES
+					|| operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
+					|| operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
+		if (dataIn) {
+			int ret = mData.read(fd);
+			if (ret < 0) {
+				MTPD("data read returned %d, errno: %d", ret, errno);
+				if (errno == ECANCELED) {
+					// return to top of loop and wait for next command
+					continue;
+				}
+				break;
+			}
+			MTPD("received data:");
+			mData.dump();
+		} else {
+			mData.reset();
+		}
+
+		if (handleRequest()) {
+			if (!dataIn && mData.hasData()) {
+				mData.setOperationCode(operation);
+				mData.setTransactionID(transaction);
+				MTPD("sending data:");
+				mData.dump();
+				ret = mData.write(fd);
+				if (ret < 0) {
+					MTPD("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);
+			MTPD("sending response %04X\n", mResponse.getResponseCode());
+			ret = mResponse.write(fd);
+			MTPD("ret: %d\n", ret);
+			mResponse.dump();
+			if (ret < 0) {
+				MTPD("request write returned %d, errno: %d", ret, errno);
+				if (errno == ECANCELED) {
+					// return to top of loop and wait for next command
+					continue;
+				}
+				break;
+			}
+		} else {
+			MTPD("skipping response\n");
+		}
+	}
+
+	// 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();
+	close(fd);
+	mFD = -1;
+}
+
+void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
+	MTPD("sendObjectAdded %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
+}
+
+void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
+	MTPD("sendObjectRemoved %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_REMOVED, handle);
+}
+
+void MtpServer::sendObjectUpdated(MtpObjectHandle handle) {
+	MTPD("sendObjectUpdated %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_PROP_CHANGED, handle);
+}
+
+void MtpServer::sendStoreAdded(MtpStorageID id) {
+	MTPD("sendStoreAdded %08X\n", id);
+	sendEvent(MTP_EVENT_STORE_ADDED, id);
+}
+
+void MtpServer::sendStoreRemoved(MtpStorageID id) {
+	MTPD("sendStoreRemoved %08X\n", id);
+	sendEvent(MTP_EVENT_STORE_REMOVED, id);
+}
+
+void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
+	MTPD("MtpServer::sendEvent sending event code: %x\n", code);
+	if (mSessionOpen) {
+		mEvent.setEventCode(code);
+		mEvent.setTransactionID(mRequest.getTransactionID());
+		mEvent.setParameter(1, param1);
+		int ret = mEvent.write(mFD);
+		MTPD("mEvent.write returned %d\n", ret);
+	}
+}
+
+void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
+		uint64_t size, MtpObjectFormat format, int fd) {
+	ObjectEdit*  edit = new ObjectEdit(handle, path, size, format, 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->mHandle == 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->mHandle == handle) {
+			delete edit;
+			mObjectEditList.removeAt(i);
+			return;
+		}
+	}
+	MTPE("ObjectEdit not found in removeEditObject");
+}
+
+void MtpServer::commitEdit(ObjectEdit* edit) {
+	mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true);
+}
+
+
+bool MtpServer::handleRequest() {
+	android::Mutex::Autolock autoLock(mMutex);
+
+	MtpOperationCode operation = mRequest.getOperationCode();
+	MtpResponseCode response;
+
+	mResponse.reset();
+
+	if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
+		// FIXME - need to delete mSendObjectHandle from the database
+		MTPE("expected SendObject after SendObjectInfo");
+		mSendObjectHandle = kInvalidObjectHandle;
+	}
+
+	switch (operation) {
+		case MTP_OPERATION_GET_DEVICE_INFO:
+				MTPD("doGetDeviceInfo()\n");
+				response = doGetDeviceInfo();
+				break;
+			case MTP_OPERATION_OPEN_SESSION:
+				MTPD("doOpenSesion()\n");
+				response = doOpenSession();
+				break;
+			case MTP_OPERATION_CLOSE_SESSION:
+				MTPD("doCloseSession()\n");
+				response = doCloseSession();
+				break;
+			case MTP_OPERATION_GET_STORAGE_IDS:
+				MTPD("doGetStorageIDs()\n");
+				response = doGetStorageIDs();
+				break;
+		 	 case MTP_OPERATION_GET_STORAGE_INFO:
+				MTPD("about to call doGetStorageInfo()\n");
+				response = doGetStorageInfo();
+				break;
+			case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
+				MTPD("about to call doGetObjectPropsSupported()\n");
+				response = doGetObjectPropsSupported();
+				break;
+			case MTP_OPERATION_GET_OBJECT_HANDLES:
+				MTPD("about to call doGetObjectHandles()\n");
+				response = doGetObjectHandles();
+				break;
+			case MTP_OPERATION_GET_NUM_OBJECTS:
+				MTPD("about to call doGetNumbObjects()\n");
+				response = doGetNumObjects();
+				break;
+			case MTP_OPERATION_GET_OBJECT_REFERENCES:
+				MTPD("about to call doGetObjectReferences()\n");
+				response = doGetObjectReferences();
+				break;
+			case MTP_OPERATION_SET_OBJECT_REFERENCES:
+				MTPD("about to call doSetObjectReferences()\n");
+				response = doSetObjectReferences();
+				break;
+			case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
+				MTPD("about to call doGetObjectPropValue()\n");
+				response = doGetObjectPropValue();
+				break;
+			case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
+				MTPD("about to call doSetObjectPropValue()\n");
+				response = doSetObjectPropValue();
+				break;
+			case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
+				MTPD("about to call doGetDevicPropValue()\n");
+				response = doGetDevicePropValue();
+				break;
+			case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
+				MTPD("about to call doSetDevicePropVaue()\n");
+				response = doSetDevicePropValue();
+				break;
+			case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
+				MTPD("about to call doResetDevicePropValue()\n");
+				response = doResetDevicePropValue();
+				break;
+			case MTP_OPERATION_GET_OBJECT_PROP_LIST:
+				MTPD("calling doGetObjectPropList()\n");
+				response = doGetObjectPropList();
+				break;
+			case MTP_OPERATION_GET_OBJECT_INFO:
+				MTPD("calling doGetObjectInfo()\n");
+				response = doGetObjectInfo();
+				break;
+			case MTP_OPERATION_GET_OBJECT:
+				MTPD("about to call doGetObject()\n");
+				response = doGetObject();
+				break;
+			case MTP_OPERATION_GET_THUMB:
+				response = doGetThumb();
+				break;
+			case MTP_OPERATION_GET_PARTIAL_OBJECT:
+			case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
+				response = doGetPartialObject(operation);
+				break;
+			case MTP_OPERATION_SEND_OBJECT_INFO:
+				MTPD("about to call doSendObjectInfo()\n");
+				response = doSendObjectInfo();
+				break;
+			case MTP_OPERATION_SEND_OBJECT:
+				MTPD("about to call doSendObject()\n");
+				response = doSendObject();
+				break;
+			case MTP_OPERATION_DELETE_OBJECT:
+				response = doDeleteObject();
+				break;
+			case MTP_OPERATION_GET_OBJECT_PROP_DESC:
+				MTPD("about to call doGetObjectPropDesc()\n");
+				response = doGetObjectPropDesc();
+				break;
+			case MTP_OPERATION_GET_DEVICE_PROP_DESC:
+				MTPD("about to call doGetDevicePropDesc()\n");
+				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:
+				MTPE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
+				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];
+
+	MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
+	MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
+	MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
+
+	// fill in device info
+	mData.putUInt16(MTP_STANDARD_VERSION);
+	if (mPtp) {
+		MTPD("doGetDeviceInfo putting 0\n");
+		mData.putUInt32(0);
+	} else {
+		// MTP Vendor Extension ID
+		MTPD("doGetDeviceInfo putting 6\n");
+		mData.putUInt32(6);
+	}
+	mData.putUInt16(MTP_STANDARD_VERSION);
+	if (mPtp) {
+		// no extensions
+		MTPD("doGetDeviceInfo no extensions\n");
+		string.set("");
+	} else {
+		// MTP extensions
+		MTPD("doGetDeviceInfo microsoft.com: 1.0; android.com: 1.0;\n");
+		string.set("microsoft.com: 1.0; android.com: 1.0;");
+	}
+	mData.putString(string); // MTP Extensions
+	mData.putUInt16(0); //Functional Mode
+	MTPD("doGetDeviceInfo opcodes, %i\n", sizeof(kSupportedOperationCodes) / sizeof(uint16_t));
+	MTPD("doGetDeviceInfo eventcodes, %i\n", sizeof(kSupportedEventCodes) / sizeof(uint16_t));
+	mData.putAUInt16(kSupportedOperationCodes,
+			sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
+	mData.putAUInt16(kSupportedEventCodes,
+			sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
+	mData.putAUInt16(deviceProperties); // Device Properties Supported
+	mData.putAUInt16(captureFormats); // Capture Formats
+	mData.putAUInt16(playbackFormats);  // Playback Formats
+
+	property_get("ro.product.manufacturer", prop_value, "unknown manufacturer");
+	MTPD("prop: %s\n", prop_value);
+	string.set(prop_value);
+	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, "????????");
+	MTPD("sn: %s\n", prop_value);
+	string.set(prop_value);
+	mData.putString(string);   // Serial Number
+
+	delete playbackFormats;
+	delete captureFormats;
+	delete deviceProperties;
+
+	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;
+
+	mDatabase->sessionStarted();
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doCloseSession() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	mSessionID = 0;
+	mSessionOpen = false;
+	mDatabase->sessionEnded();
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetStorageIDs() {
+	MTPD("doGetStorageIDs()\n");
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	int count = mStorages.size();
+	mData.putUInt32(count);
+	for (int i = 0; i < count; i++) {
+		MTPD("getting storageid %d\n", mStorages[i]->getStorageID());
+		mData.putUInt32(mStorages[i]->getStorageID());
+	}
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetStorageInfo() {
+	MtpStringBuffer   string;
+	MTPD("doGetStorageInfo()\n");
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	MtpStorageID id = mRequest.getParameter(1);
+	MtpStorage* storage = getStorage(id);
+	if (!storage) {
+		MTPE("invalid storage id\n");
+		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() {
+	MTPD("doGetObjectPropsSupported()\n");
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	MtpObjectFormat format = mRequest.getParameter(1);
+	mDatabase->lockMutex();
+	MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format);
+	mData.putAUInt16(properties);
+	delete properties;
+	mDatabase->unlockMutex();
+	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 (!hasStorage(storageID))
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+	MTPD("calling MtpDatabase->getObjectList()\n");
+	mDatabase->lockMutex();
+	MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
+	mData.putAUInt32(handles);
+	delete handles;
+	mDatabase->unlockMutex();
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetNumObjects() {
+	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 (!hasStorage(storageID))
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+	mDatabase->lockMutex();
+	int count = mDatabase->getNumObjects(storageID, format, parent);
+	mDatabase->unlockMutex();
+	if (count >= 0) {
+		mResponse.setParameter(1, count);
+		return MTP_RESPONSE_OK;
+	} else {
+		mResponse.setParameter(1, 0);
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	}
+}
+
+MtpResponseCode MtpServer::doGetObjectReferences() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+
+	// FIXME - check for invalid object handle
+	mDatabase->lockMutex();
+	MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
+	if (handles) {
+		mData.putAUInt32(handles);
+		delete handles;
+	} else {
+		MTPD("MtpServer::doGetObjectReferences putEmptyArray\n");
+		mData.putEmptyArray();
+	}
+	mDatabase->unlockMutex();
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSetObjectReferences() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpStorageID handle = mRequest.getParameter(1);
+
+	MtpObjectHandleList* references = mData.getAUInt32();
+	mDatabase->lockMutex();
+	MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
+	mDatabase->unlockMutex();
+	delete references;
+	return result;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropValue() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectProperty property = mRequest.getParameter(2);
+	MTPD("GetObjectPropValue %d %s\n", handle,
+			MtpDebug::getObjectPropCodeName(property));
+
+	mDatabase->lockMutex();
+	MtpResponseCode res = mDatabase->getObjectPropertyValue(handle, property, mData);
+	mDatabase->unlockMutex();
+	return res;
+}
+
+MtpResponseCode MtpServer::doSetObjectPropValue() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectProperty property = mRequest.getParameter(2);
+	MTPD("SetObjectPropValue %d %s\n", handle,
+			MtpDebug::getObjectPropCodeName(property));
+
+	mDatabase->lockMutex();
+	MtpResponseCode res = mDatabase->setObjectPropertyValue(handle, property, mData);
+	mDatabase->unlockMutex();
+	return res;
+}
+
+MtpResponseCode MtpServer::doGetDevicePropValue() {
+	MtpDeviceProperty property = mRequest.getParameter(1);
+	MTPD("GetDevicePropValue %s\n",
+			MtpDebug::getDevicePropCodeName(property));
+
+	mDatabase->lockMutex();
+	MtpResponseCode res = mDatabase->getDevicePropertyValue(property, mData);
+	mDatabase->unlockMutex();
+	return res;
+}
+
+MtpResponseCode MtpServer::doSetDevicePropValue() {
+	MtpDeviceProperty property = mRequest.getParameter(1);
+	MTPD("SetDevicePropValue %s\n",
+			MtpDebug::getDevicePropCodeName(property));
+
+	mDatabase->lockMutex();
+	MtpResponseCode res = mDatabase->setDevicePropertyValue(property, mData);
+	mDatabase->unlockMutex();
+	return res;
+}
+
+MtpResponseCode MtpServer::doResetDevicePropValue() {
+	MtpDeviceProperty property = mRequest.getParameter(1);
+	MTPD("ResetDevicePropValue %s\n",
+			MtpDebug::getDevicePropCodeName(property));
+
+	mDatabase->lockMutex();
+	MtpResponseCode res = mDatabase->resetDeviceProperty(property);
+	mDatabase->unlockMutex();
+	return res;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropList() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	// use uint32_t so we can support 0xFFFFFFFF
+	uint32_t format = mRequest.getParameter(2);
+	uint32_t property = mRequest.getParameter(3);
+	int groupCode = mRequest.getParameter(4);
+	int depth = mRequest.getParameter(5);
+	MTPD("GetObjectPropList %d format: %s property: %x group: %d depth: %d\n",
+			handle, MtpDebug::getFormatCodeName(format),
+			property, groupCode, depth);
+
+	mDatabase->lockMutex();
+	MtpResponseCode res = mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
+	mDatabase->unlockMutex();
+	return res;
+}
+
+MtpResponseCode MtpServer::doGetObjectInfo() {
+	MTPD("inside doGetObjectInfo()\n");
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectInfo info(handle);
+	MTPD("calling mtpdatabase getObjectInfo()\n");
+	mDatabase->lockMutex();
+	MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
+	mDatabase->unlockMutex();
+	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->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize);
+		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);
+		MTPD("info.mName: %s\n", info.mName);
+		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() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpString pathBuf;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	MTPD("MtpServer::doGetObject calling getObjectFilePath\n");
+	mDatabase->lockMutex();
+	int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
+	mDatabase->unlockMutex();
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	const char* filePath = (const char *)pathBuf;
+	MTPD("filePath: %s\n", filePath);
+	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;
+	MTPD("mfr.length: %lld\n", mfr.length);
+	mfr.command = mRequest.getOperationCode();
+	mfr.transaction_id = mRequest.getTransactionID();
+
+	// then transfer the file
+	int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
+	MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
+	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::doGetThumb() {
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	size_t thumbSize;
+	mDatabase->lockMutex();
+	void* thumb = mDatabase->getThumbnail(handle, thumbSize);
+	mDatabase->unlockMutex();
+	if (thumb) {
+		// send data
+		mData.setOperationCode(mRequest.getOperationCode());
+		mData.setTransactionID(mRequest.getTransactionID());
+		mData.writeData(mFD, thumb, thumbSize);
+		free(thumb);
+		return MTP_RESPONSE_OK;
+	} else {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+}
+
+MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	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;
+	MTPD("MtpServer::doGetPartialObject calling getObjectFilePath\n");
+	mDatabase->lockMutex();
+	int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
+	mDatabase->unlockMutex();
+	if (result != MTP_RESPONSE_OK) {
+		return result;
+	}
+	if (offset + length > (uint64_t)fileLength)
+		length = fileLength - offset;
+
+	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 = offset;
+	mfr.length = length;
+	mfr.command = mRequest.getOperationCode();
+	mfr.transaction_id = mRequest.getTransactionID();
+	mResponse.setParameter(1, length);
+
+	// transfer the file
+	int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
+	MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
+	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() {
+	MTPD("MtpServer::doSendObjectInfo starting\n");
+	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) {
+		MTPD("MtpServer::doSendObjectInfo special case root\n");
+		path = storage->getPath();
+		parent = 0;
+	} else {
+		int64_t length;
+		MtpObjectFormat format;
+		MTPD("MtpServer::doSendObjectInfo calling getObjectFilePath\n");
+		mDatabase->lockMutex();
+		int result = mDatabase->getObjectFilePath(parent, path, length, format);
+		mDatabase->unlockMutex();
+		if (result != MTP_RESPONSE_OK) {
+			return result;
+		}
+		if (format != MTP_FORMAT_ASSOCIATION)
+			return MTP_RESPONSE_INVALID_PARENT_OBJECT;
+	}
+
+	// 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
+
+	MTPD("name: %s format: %04X\n", (const char *)name, format);
+	time_t modifiedTime;
+	if (!parseDateTime(modified, modifiedTime)) {
+		modifiedTime = 0;
+	}
+	if (path[path.size() - 1] != '/') {
+		path += "/";
+	}
+	path += (const char *)name;
+
+	// check space first
+	if (mSendObjectFileSize > storage->getFreeSpace())
+		return MTP_RESPONSE_STORAGE_FULL;
+	uint64_t maxFileSize = storage->getMaxFileSize();
+	// check storage max file size
+	if (maxFileSize != 0) {
+		// if mSendObjectFileSize is 0xFFFFFFFF, then all we know is the file size
+		// is >= 0xFFFFFFFF
+		if (mSendObjectFileSize > maxFileSize || mSendObjectFileSize == 0xFFFFFFFF)
+			return MTP_RESPONSE_OBJECT_TOO_LARGE;
+	}
+
+	MTPD("MtpServer::doSendObjectInfo path: %s parent: %d storageID: %08X\n", (const char*)path, parent, storageID);
+	mDatabase->lockMutex();
+	MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
+			format, parent, storageID, mSendObjectFileSize, modifiedTime);
+	mDatabase->unlockMutex();
+	if (handle == kInvalidObjectHandle) {
+		MTPE("MtpServer::doSendObjectInfo returning MTP_RESPONSE_GENERAL_ERROR, handle == kInvalidObjectHandle\n");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+  if (format == MTP_FORMAT_ASSOCIATION) {
+		mode_t mask = umask(0);
+		MTPD("MtpServer::doSendObjectInfo mkdir '%s'\n", (const char *)path);
+		int ret = mkdir((const char *)path, mDirectoryPermission);
+		umask(mask);
+		if (ret && ret != -EEXIST) {
+			MTPE("MtpServer::doSendObjectInfo returning MTP_RESPONSE_GENERAL_ERROR, ret && ret != -EEXIST\n");
+			return MTP_RESPONSE_GENERAL_ERROR;
+		}
+		chown((const char *)path, getuid(), mFileGroup);
+
+		// SendObject does not get sent for directories, so call endSendObject here instead
+		mDatabase->lockMutex();
+		mDatabase->endSendObject(path, handle, MTP_FORMAT_ASSOCIATION, MTP_RESPONSE_OK);
+		mDatabase->unlockMutex();
+	} else {
+		mSendObjectFilePath = path;
+		// save the handle for the SendObject call, which should follow
+		mSendObjectHandle = handle;
+		mSendObjectFormat = format;
+	}
+
+	mResponse.setParameter(1, storageID);
+	mResponse.setParameter(2, parent);
+	mResponse.setParameter(3, handle);
+	MTPD("MtpServer::doSendObjectInfo returning MTP_RESPONSE_OK\n");
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSendObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_GENERAL_ERROR;
+	MtpResponseCode result = MTP_RESPONSE_OK;
+	mode_t mask;
+	int ret = 0, initialData;
+
+	if (mSendObjectHandle == kInvalidObjectHandle) {
+		MTPE("Expected SendObjectInfo before SendObject");
+		result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
+		goto done;
+	}
+
+	// read the header, and possibly some data
+	ret = mData.read(mFD);
+	if (ret < MTP_CONTAINER_HEADER_SIZE) {
+		MTPE("MTP_RESPONSE_GENERAL_ERROR\n");
+		result = MTP_RESPONSE_GENERAL_ERROR;
+		goto done;
+	}
+	initialData = ret - MTP_CONTAINER_HEADER_SIZE;
+
+	mtp_file_range  mfr;
+	mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC, 0640);
+	if (mfr.fd < 0) {
+		result = MTP_RESPONSE_GENERAL_ERROR;
+		MTPE("fd error\n");
+		goto done;
+	}
+	fchown(mfr.fd, getuid(), mFileGroup);
+	// set permissions
+	mask = umask(0);
+	fchmod(mfr.fd, mFilePermission);
+	umask(mask);
+
+	if (initialData > 0)
+		ret = write(mfr.fd, mData.getData(), initialData);
+
+	if (mSendObjectFileSize - initialData > 0) {
+		mfr.offset = initialData;
+		if (mSendObjectFileSize == 0xFFFFFFFF) {
+			// tell driver to read until it receives a short packet
+			mfr.length = 0xFFFFFFFF;
+		} else {
+			mfr.length = mSendObjectFileSize - initialData;
+		}
+
+		MTPD("receiving %s\n", (const char *)mSendObjectFilePath);
+		// transfer the file
+		ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+	}
+	close(mfr.fd);
+
+	if (ret < 0) {
+		unlink(mSendObjectFilePath);
+		if (errno == ECANCELED)
+			result = MTP_RESPONSE_TRANSACTION_CANCELLED;
+		else
+			result = MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+done:
+	// reset so we don't attempt to send the data back
+	MTPD("MTP_RECEIVE_FILE returned %d\n", ret);
+	mData.reset();
+	mDatabase->lockMutex();
+	mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
+			result == MTP_RESPONSE_OK);
+	mDatabase->unlockMutex();
+	mSendObjectHandle = kInvalidObjectHandle;
+	MTPD("result: %d\n", result);
+	mSendObjectFormat = 0;
+	return MTP_RESPONSE_OK;
+}
+
+static void deleteRecursive(const char* path) {
+	char pathbuf[PATH_MAX];
+	size_t pathLength = strlen(path);
+	if (pathLength >= sizeof(pathbuf) - 1) {
+		MTPE("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) {
+		MTPE("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) {
+			MTPE("path %s/%s too long\n", path, name);
+			continue;
+		}
+		strcpy(fileSpot, name);
+
+		int type = entry->d_type;
+		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 {
+		MTPE("deletePath stat failed for %s: %s", path, strerror(errno));
+	}
+}
+
+MtpResponseCode MtpServer::doDeleteObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectFormat format = mRequest.getParameter(2);
+	// FIXME - support deleting all objects if handle is 0xFFFFFFFF
+	// FIXME - implement deleting objects by format
+
+	MtpString filePath;
+	int64_t fileLength;
+	MTPD("MtpServer::doDeleteObject calling getObjectFilePath\n");
+	mDatabase->lockMutex();
+	int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
+	if (result == MTP_RESPONSE_OK) {
+		MTPD("deleting %s", (const char *)filePath);
+		result = mDatabase->deleteFile(handle);
+		// Don't delete the actual files unless the database deletion is allowed
+		if (result == MTP_RESPONSE_OK) {
+			deletePath((const char *)filePath);
+		}
+	}
+	mDatabase->unlockMutex();
+	return result;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropDesc() {
+	MtpObjectProperty propCode = mRequest.getParameter(1);
+	MtpObjectFormat format = mRequest.getParameter(2);
+	MTPD("MtpServer::doGetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
+										MtpDebug::getFormatCodeName(format));
+	mDatabase->lockMutex();
+	MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
+	mDatabase->unlockMutex();
+	if (!property) {
+		MTPE("MtpServer::doGetObjectPropDesc propery not supported\n");
+		return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+	}
+	property->write(mData);
+	delete property;
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetDevicePropDesc() {
+	MtpDeviceProperty propCode = mRequest.getParameter(1);
+	MTPD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
+	mDatabase->lockMutex();
+	MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
+	mDatabase->unlockMutex();
+	if (!property) {
+		MTPE("MtpServer::doGetDevicePropDesc property not supported\n");
+		return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+	}
+	property->write(mData);
+	delete property;
+	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) {
+		MTPE("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->mSize) {
+		MTPE("writing past end of object, offset: %lld, edit->mSize: %lld", offset, edit->mSize);
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	const char* filePath = (const char *)edit->mPath;
+	MTPD("receiving partial %s %lld %lld\n", filePath, offset, length);
+
+	// read the header, and possibly some data
+	int ret = mData.read(mFD);
+	if (ret < MTP_CONTAINER_HEADER_SIZE)
+		return MTP_RESPONSE_GENERAL_ERROR;
+	int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
+
+	if (initialData > 0) {
+		ret = write(edit->mFD, mData.getData(), initialData);
+		offset += initialData;
+		length -= initialData;
+	}
+
+	if (length > 0) {
+		mtp_file_range  mfr;
+		mfr.fd = edit->mFD;
+		mfr.offset = offset;
+		mfr.length = length;
+
+		// transfer the file
+		ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+		MTPD("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;
+	}
+
+	// reset so we don't attempt to send this back
+	mData.reset();
+	mResponse.setParameter(1, length);
+	uint64_t end = offset + length;
+	if (end > edit->mSize) {
+		edit->mSize = end;
+	}
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doTruncateObject() {
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	ObjectEdit* edit = getEditObject(handle);
+	if (!edit) {
+		MTPE("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->mFD, offset) != 0) {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	} else {
+		edit->mSize = offset;
+		return MTP_RESPONSE_OK;
+	}
+}
+
+MtpResponseCode MtpServer::doBeginEditObject() {
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	if (getEditObject(handle)) {
+		MTPE("object already open for edit in doBeginEditObject");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	MtpString path;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	MTPD("MtpServer::doBeginEditObject calling getObjectFilePath\n");
+	mDatabase->lockMutex();
+	int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
+	mDatabase->unlockMutex();
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	int fd = open((const char *)path, O_RDWR | O_EXCL);
+	if (fd < 0) {
+		MTPE("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) {
+		MTPE("object not open for edit in doEndEditObject");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	commitEdit(edit);
+	removeEditObject(handle);
+	return MTP_RESPONSE_OK;
+}