Checkpoint work on MTP and PTP investigation.
This change includes work in progress on a C++ library for both host and device
MTP and PTP support.
Currently the makefile builds two test programs:
mtptest - a command line test program that implements a small subset of device side MTP.
Requires a kernel driver that has not been checked in yet.
ptptest - a host tool to test USB host support for detecting and communicating with
digital cameras over PTP. Runs on Linux host.
Later this will be reformulated as a native library that will be used in the media process.
Change-Id: I81aab279975b600b59d99013ab97f9adf0b58da7
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/media/mtp/MtpDatabase.cpp b/media/mtp/MtpDatabase.cpp
new file mode 100644
index 0000000..bb44ab6
--- /dev/null
+++ b/media/mtp/MtpDatabase.cpp
@@ -0,0 +1,386 @@
+/*
+ * 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 "MtpDatabase.h"
+#include "MtpDataPacket.h"
+#include "SqliteDatabase.h"
+#include "SqliteStatement.h"
+
+#include <stdio.h>
+#include <sqlite3.h>
+
+#define ID_COLUMN 1
+#define PATH_COLUMN 2
+#define FORMAT_COLUMN 3
+#define PARENT_COLUMN 4
+#define STORAGE_COLUMN 5
+#define SIZE_COLUMN 6
+#define CREATED_COLUMN 7
+#define MODIFIED_COLUMN 8
+
+#define TABLE_CREATE "CREATE TABLE IF NOT EXISTS files (" \
+ "_id INTEGER PRIMARY KEY," \
+ "path TEXT," \
+ "format INTEGER," \
+ "parent INTEGER," \
+ "storage INTEGER," \
+ "size INTEGER," \
+ "date_created INTEGER," \
+ "date_modified INTEGER" \
+ ");"
+
+#define PATH_INDEX_CREATE "CREATE INDEX IF NOT EXISTS path_index on files(path);"
+
+#define FILE_ID_QUERY "SELECT _id FROM files WHERE path = ?;"
+#define FILE_PATH_QUERY "SELECT path,size FROM files WHERE _id = ?"
+
+#define GET_OBJECT_INFO_QUERY "SELECT storage,format,parent,path,size,date_created,date_modified FROM files WHERE _id = ?;"
+#define FILE_INSERT "INSERT INTO files VALUES(?,?,?,?,?,?,?,?);"
+#define FILE_DELETE "DELETE FROM files WHERE path = ?;"
+
+
+struct PropertyTableEntry {
+ MtpObjectProperty property;
+ int type;
+ const char* columnName;
+};
+
+static const PropertyTableEntry kPropertyTable[] = {
+ { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32, "parent" },
+ { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32, "storage" },
+ { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT32, "format" },
+ { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR, "path" },
+ { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64, "size" },
+ { MTP_PROPERTY_DATE_CREATED, MTP_TYPE_STR, "date_created" },
+ { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR, "date_modified" },
+};
+
+static bool getPropertyInfo(MtpObjectProperty property, int& type, const char*& columnName) {
+ int count = sizeof(kPropertyTable) / sizeof(kPropertyTable[0]);
+ const PropertyTableEntry* entry = kPropertyTable;
+ for (int i = 0; i < count; i++, entry++) {
+ if (entry->property == property) {
+ type = entry->type;
+ columnName = entry->columnName;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+MtpDatabase::MtpDatabase()
+ : mFileIdQuery(NULL),
+ mObjectInfoQuery(NULL),
+ mFileInserter(NULL),
+ mFileDeleter(NULL)
+{
+}
+
+MtpDatabase::~MtpDatabase() {
+}
+
+bool MtpDatabase::open(const char* path, bool create) {
+ if (!SqliteDatabase::open(path, create))
+ return false;
+
+ // create the table if necessary
+ if (!exec(TABLE_CREATE)) {
+ fprintf(stderr, "could not create table\n");
+ return false;
+ }
+ if (!exec(PATH_INDEX_CREATE)) {
+ fprintf(stderr, "could not path index\n");
+ return false;
+ }
+ return true;
+}
+
+MtpObjectHandle MtpDatabase::addFile(const char* path,
+ MtpObjectFormat format,
+ MtpObjectHandle parent,
+ MtpStorageID storage,
+ uint64_t size,
+ time_t created,
+ time_t modified) {
+
+ // first check to see if the file exists
+ if (mFileIdQuery)
+ mFileIdQuery->reset();
+ else {
+ mFileIdQuery = new SqliteStatement(this);
+ if (!mFileIdQuery->prepare(FILE_ID_QUERY)) {
+ fprintf(stderr, "could not compile FILE_ID_QUERY\n");
+ delete mFileIdQuery;
+ mFileIdQuery = NULL;
+ return kInvalidObjectHandle;
+ }
+ }
+
+ mFileIdQuery->bind(1, path);
+ if (mFileIdQuery->step()) {
+ int row = mFileIdQuery->getColumnInt(0);
+ if (row > 0)
+ return row;
+ }
+
+ if (!mFileInserter) {
+ mFileInserter = new SqliteStatement(this);
+ if (!mFileInserter->prepare(FILE_INSERT)) {
+ fprintf(stderr, "could not compile FILE_INSERT\n");
+ delete mFileInserter;
+ mFileInserter = NULL;
+ return kInvalidObjectHandle;
+ }
+ }
+ mFileInserter->bind(PATH_COLUMN, path);
+ mFileInserter->bind(FORMAT_COLUMN, format);
+ mFileInserter->bind(PARENT_COLUMN, parent);
+ mFileInserter->bind(STORAGE_COLUMN, storage);
+ mFileInserter->bind(SIZE_COLUMN, size);
+ mFileInserter->bind(CREATED_COLUMN, created);
+ mFileInserter->bind(MODIFIED_COLUMN, modified);
+ mFileInserter->step();
+ mFileInserter->reset();
+ int row = lastInsertedRow();
+ return (row > 0 ? row : kInvalidObjectHandle);
+}
+
+MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID,
+ MtpObjectFormat format,
+ MtpObjectHandle parent) {
+ bool whereStorage = (storageID != 0xFFFFFFFF);
+ bool whereFormat = (format != 0);
+ bool whereParent = (parent != 0);
+ char intBuffer[20];
+
+ MtpString query("SELECT _id FROM files");
+ if (whereStorage || whereFormat || whereParent)
+ query += " WHERE";
+ if (whereStorage) {
+ snprintf(intBuffer, sizeof(intBuffer), "%d", storageID);
+ query += " storage = ";
+ query += intBuffer;
+ }
+ if (whereFormat) {
+ snprintf(intBuffer, sizeof(intBuffer), "%d", format);
+ if (whereStorage)
+ query += " AND";
+ query += " format = ";
+ query += intBuffer;
+ }
+ if (whereParent) {
+ snprintf(intBuffer, sizeof(intBuffer), "%d", parent);
+ if (whereStorage || whereFormat)
+ query += " AND";
+ query += " parent = ";
+ query += intBuffer;
+ }
+ query += ";";
+
+ SqliteStatement stmt(this);
+ printf("%s\n", (const char *)query);
+ stmt.prepare(query);
+
+ MtpObjectHandleList* list = new MtpObjectHandleList();
+ while (!stmt.isDone()) {
+ if (stmt.step()) {
+ int index = stmt.getColumnInt(0);
+ printf("stmt.getColumnInt returned %d\n", index);
+ if (index > 0)
+ list->push(index);
+ }
+ }
+ printf("list size: %d\n", list->size());
+ return list;
+}
+
+MtpResponseCode MtpDatabase::getObjectProperty(MtpObjectHandle handle,
+ MtpObjectProperty property,
+ MtpDataPacket& packet) {
+ int type;
+ const char* columnName;
+ char intBuffer[20];
+
+ if (!getPropertyInfo(property, type, columnName))
+ return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
+ snprintf(intBuffer, sizeof(intBuffer), "%d", handle);
+
+ MtpString query("SELECT ");
+ query += columnName;
+ query += " FROM files WHERE _id = ";
+ query += intBuffer;
+ query += ";";
+
+ SqliteStatement stmt(this);
+ printf("%s\n", (const char *)query);
+ stmt.prepare(query);
+
+ if (!stmt.step())
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+
+ switch (type) {
+ case MTP_TYPE_INT8:
+ packet.putInt8(stmt.getColumnInt(0));
+ break;
+ case MTP_TYPE_UINT8:
+ packet.putUInt8(stmt.getColumnInt(0));
+ break;
+ case MTP_TYPE_INT16:
+ packet.putInt16(stmt.getColumnInt(0));
+ break;
+ case MTP_TYPE_UINT16:
+ packet.putUInt16(stmt.getColumnInt(0));
+ break;
+ case MTP_TYPE_INT32:
+ packet.putInt32(stmt.getColumnInt(0));
+ break;
+ case MTP_TYPE_UINT32:
+ packet.putUInt32(stmt.getColumnInt(0));
+ break;
+ case MTP_TYPE_INT64:
+ packet.putInt64(stmt.getColumnInt64(0));
+ break;
+ case MTP_TYPE_UINT64:
+ packet.putUInt64(stmt.getColumnInt64(0));
+ break;
+ case MTP_TYPE_STR:
+ packet.putString(stmt.getColumnString(0));
+ break;
+ default:
+ fprintf(stderr, "unsupported object type\n");
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+ return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle,
+ MtpDataPacket& packet) {
+ char date[20];
+
+ if (mObjectInfoQuery)
+ mObjectInfoQuery->reset();
+ else {
+ mObjectInfoQuery = new SqliteStatement(this);
+ if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) {
+ fprintf(stderr, "could not compile FILE_ID_QUERY\n");
+ delete mObjectInfoQuery;
+ mObjectInfoQuery = NULL;
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+ }
+
+ mObjectInfoQuery->bind(1, handle);
+ if (!mObjectInfoQuery->step())
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+
+ MtpStorageID storageID = mObjectInfoQuery->getColumnInt(0);
+ MtpObjectFormat format = mObjectInfoQuery->getColumnInt(1);
+ MtpObjectHandle parent = mObjectInfoQuery->getColumnInt(2);
+ // extract name from path. do we want a separate database entry for this?
+ const char* name = mObjectInfoQuery->getColumnString(3);
+ const char* lastSlash = strrchr(name, '/');
+ if (lastSlash)
+ name = lastSlash + 1;
+ int64_t size = mObjectInfoQuery->getColumnInt64(4);
+ time_t created = mObjectInfoQuery->getColumnInt(5);
+ time_t modified = mObjectInfoQuery->getColumnInt(6);
+ int associationType = (format == MTP_FORMAT_ASSOCIATION ?
+ MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
+ MTP_ASSOCIATION_TYPE_UNDEFINED);
+
+ printf("storageID: %d, format: %d, parent: %d\n", storageID, format, parent);
+
+ 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
+ packet.putString(name); // file name
+ formatDateTime(created, date, sizeof(date));
+ packet.putString(date); // date created
+ formatDateTime(modified, date, sizeof(date));
+ packet.putString(date); // date modified
+ packet.putEmptyString(); // keywords
+
+ return MTP_RESPONSE_OK;
+}
+
+bool MtpDatabase::getObjectFilePath(MtpObjectHandle handle,
+ MtpString& filePath,
+ int64_t& fileLength) {
+ if (mFilePathQuery)
+ mFilePathQuery->reset();
+ else {
+ mFilePathQuery = new SqliteStatement(this);
+ if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) {
+ fprintf(stderr, "could not compile FILE_ID_QUERY\n");
+ delete mFilePathQuery;
+ mFilePathQuery = NULL;
+ return kInvalidObjectHandle;
+ }
+ }
+
+ mFilePathQuery->bind(1, handle);
+ if (!mFilePathQuery->step())
+ return false;
+
+ const char* path = mFilePathQuery->getColumnString(0);
+ if (!path)
+ return false;
+ filePath = path;
+ fileLength = mFilePathQuery->getColumnInt64(1);
+ return true;
+}
+
+bool MtpDatabase::deleteFile(MtpObjectHandle handle) {
+ if (!mFileDeleter) {
+ mFileDeleter = new SqliteStatement(this);
+ if (!mFileDeleter->prepare(FILE_DELETE)) {
+ fprintf(stderr, "could not compile FILE_DELETE\n");
+ delete mFileDeleter;
+ mFileDeleter = NULL;
+ return false;
+ }
+ }
+printf("deleteFile %d\n", handle);
+ mFileDeleter->bind(1, handle);
+ mFileDeleter->step();
+ mFileDeleter->reset();
+ return true;
+}
+
+/*
+ for getObjectPropDesc
+
+ packet.putUInt16(property);
+ packet.putUInt16(dataType);
+ packet.putUInt8(getSet);
+ // default value DTS
+ packet.putUInt32(groupCode);
+ packet.putUInt8(formFlag);
+ // form, variable
+*/