MTP: Make MtpDatabase class abstract so we can have multiple implementations

Rename existing test database to MtpSqliteDatabase
This is the first step in transitioning to using the media provider database

Change-Id: I5f36c854c6e76a79137c267b000a52ced803776c
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/media/mtp/MtpSqliteDatabase.cpp b/media/mtp/MtpSqliteDatabase.cpp
new file mode 100644
index 0000000..fa3bdfe
--- /dev/null
+++ b/media/mtp/MtpSqliteDatabase.cpp
@@ -0,0 +1,564 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MtpSqliteDatabase"
+
+#include "MtpDebug.h"
+#include "MtpSqliteDatabase.h"
+#include "MtpDataPacket.h"
+#include "MtpUtils.h"
+#include "SqliteDatabase.h"
+#include "SqliteStatement.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sqlite3.h>
+
+namespace android {
+
+#define FILE_ID_COLUMN                  1
+#define FILE_PATH_COLUMN                2
+#define FILE_FORMAT_COLUMN              3
+#define FILE_PARENT_COLUMN              4
+#define FILE_STORAGE_COLUMN             5
+#define FILE_SIZE_COLUMN                6
+#define FILE_MODIFIED_COLUMN            7
+
+#define AUDIO_ID_COLUMN                 1
+#define AUDIO_TITLE_COLUMN              2
+#define AUDIO_ARTIST_COLUMN             3
+#define AUDIO_ALBUM_COLUMN              4
+#define AUDIO_ALBUM_ARTIST_COLUMN       5
+#define AUDIO_GENRE_COLUMN              6
+#define AUDIO_COMPOSER_COLUMN           7
+#define AUDIO_TRACK_NUMBER_COLUMN       8
+#define AUDIO_YEAR_COLUMN               9
+#define AUDIO_DURATION_COLUMN           10
+#define AUDIO_USE_COUNT_COLUMN          11
+#define AUDIO_SAMPLE_RATE_COLUMN        12
+#define AUDIO_NUM_CHANNELS_COLUMN       13
+#define AUDIO_AUDIO_WAVE_CODEC_COLUMN   14
+#define AUDIO_AUDIO_BIT_RATE_COLUMN     15
+
+#define FILE_TABLE_CREATE    "CREATE TABLE IF NOT EXISTS files ("    \
+                        "_id INTEGER PRIMARY KEY,"              \
+                        "path TEXT,"                            \
+                        "format INTEGER,"                       \
+                        "parent INTEGER,"                       \
+                        "storage INTEGER,"                      \
+                        "size INTEGER,"                         \
+                        "date_modified INTEGER"                \
+                        ");"
+
+#define AUDIO_TABLE_CREATE    "CREATE TABLE IF NOT EXISTS audio ("    \
+                        "id INTEGER PRIMARY KEY,"               \
+                        "title TEXT,"                           \
+                        "artist TEXT,"                          \
+                        "album TEXT,"                           \
+                        "album_artist TEXT,"                    \
+                        "genre TEXT,"                           \
+                        "composer TEXT,"                        \
+                        "track_number INTEGER,"                 \
+                        "year INTEGER,"                         \
+                        "duration INTEGER,"                     \
+                        "use_count INTEGER,"                    \
+                        "sample_rate INTEGER,"                  \
+                        "num_channels INTEGER,"                 \
+                        "audio_wave_codec TEXT,"                \
+                        "audio_bit_rate INTEGER"                \
+                        ");"
+
+#define PATH_INDEX_CREATE "CREATE INDEX IF NOT EXISTS path_index on files(path);"
+
+#define FILE_ID_QUERY   "SELECT _id,format 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_modified FROM files WHERE _id = ?;"
+#define FILE_INSERT     "INSERT INTO files VALUES(?,?,?,?,?,?,?);"
+#define FILE_DELETE     "DELETE FROM files WHERE _id = ?;"
+
+#define AUDIO_INSERT    "INSERT INTO audio VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
+#define AUDIO_DELETE    "DELETE FROM audio WHERE id = ?;"
+
+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_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;
+}
+
+MtpSqliteDatabase::MtpSqliteDatabase()
+    :   mDatabase(NULL),
+        mFileIdQuery(NULL),
+        mFilePathQuery(NULL),
+        mObjectInfoQuery(NULL),
+        mFileInserter(NULL),
+        mFileDeleter(NULL),
+        mAudioInserter(NULL),
+        mAudioDeleter(NULL)
+{
+}
+
+MtpSqliteDatabase::~MtpSqliteDatabase() {
+    delete mDatabase;
+    delete mFileIdQuery;
+    delete mFilePathQuery;
+    delete mObjectInfoQuery;
+    delete mFileInserter;
+    delete mFileDeleter;
+    delete mAudioInserter;
+    delete mAudioDeleter;
+}
+
+bool MtpSqliteDatabase::open(const char* path, bool create) {
+    mDatabase = new SqliteDatabase;
+
+    if (!mDatabase->open(path, create))
+        goto fail;
+
+    // create tables and indices if necessary
+    if (!mDatabase->exec(FILE_TABLE_CREATE)) {
+        LOGE("could not create file table");
+        goto fail;
+    }
+    if (!mDatabase->exec(PATH_INDEX_CREATE)) {
+        LOGE("could not path index on file table");
+        goto fail;
+    }
+    if (!mDatabase->exec(AUDIO_TABLE_CREATE)) {
+        LOGE("could not create file table");
+        goto fail;
+    }
+
+    if (!mFileIdQuery) {
+        mFileIdQuery = new SqliteStatement(mDatabase);
+        if (!mFileIdQuery->prepare(FILE_ID_QUERY)) {
+            LOGE("could not compile FILE_ID_QUERY");
+            goto fail;
+        }
+    }
+    if (!mFilePathQuery) {
+        mFilePathQuery = new SqliteStatement(mDatabase);
+        if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) {
+            LOGE("could not compile FILE_PATH_QUERY");
+            goto fail;
+        }
+    }
+    if (!mObjectInfoQuery) {
+        mObjectInfoQuery = new SqliteStatement(mDatabase);
+        if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) {
+            LOGE("could not compile GET_OBJECT_INFO_QUERY");
+            goto fail;
+        }
+    }
+    if (!mFileInserter) {
+        mFileInserter = new SqliteStatement(mDatabase);
+        if (!mFileInserter->prepare(FILE_INSERT)) {
+            LOGE("could not compile FILE_INSERT\n");
+            goto fail;
+        }
+    }
+    if (!mFileDeleter) {
+        mFileDeleter = new SqliteStatement(mDatabase);
+        if (!mFileDeleter->prepare(FILE_DELETE)) {
+            LOGE("could not compile FILE_DELETE\n");
+            goto fail;
+        }
+    }
+    if (!mAudioInserter) {
+        mAudioInserter = new SqliteStatement(mDatabase);
+        if (!mAudioInserter->prepare(AUDIO_INSERT)) {
+            LOGE("could not compile AUDIO_INSERT\n");
+            goto fail;
+        }
+    }
+    if (!mAudioDeleter) {
+        mAudioDeleter = new SqliteStatement(mDatabase);
+        if (!mAudioDeleter->prepare(AUDIO_DELETE)) {
+            LOGE("could not compile AUDIO_DELETE\n");
+            goto fail;
+        }
+    }
+
+    return true;
+
+fail:
+    delete mDatabase;
+    delete mFileIdQuery;
+    delete mFilePathQuery;
+    delete mObjectInfoQuery;
+    delete mFileInserter;
+    delete mFileDeleter;
+    delete mAudioInserter;
+    delete mAudioDeleter;
+    mDatabase = NULL;
+    mFileIdQuery = NULL;
+    mFilePathQuery = NULL;
+    mObjectInfoQuery = NULL;
+    mFileInserter = NULL;
+    mFileDeleter = NULL;
+    mAudioInserter = NULL;
+    mAudioDeleter = NULL;
+    return false;
+}
+
+void MtpSqliteDatabase::close() {
+    if (mDatabase) {
+        mDatabase->close();
+        mDatabase = NULL;
+    }
+}
+
+MtpObjectHandle MtpSqliteDatabase::getObjectHandle(const char* path) {
+    mFileIdQuery->reset();
+    mFileIdQuery->bind(1, path);
+    if (mFileIdQuery->step()) {
+        int row = mFileIdQuery->getColumnInt(0);
+        if (row > 0) {
+            MtpObjectFormat format = mFileIdQuery->getColumnInt(1);
+            row |= getTableForFile(format);
+            return row;
+        }
+    }
+
+    return 0;
+}
+
+MtpObjectHandle MtpSqliteDatabase::addFile(const char* path,
+                                    MtpObjectFormat format,
+                                    MtpObjectHandle parent,
+                                    MtpStorageID storage,
+                                    uint64_t size,
+                                    time_t modified) {
+    mFileInserter->bind(FILE_PATH_COLUMN, path);
+    mFileInserter->bind(FILE_FORMAT_COLUMN, format);
+    mFileInserter->bind(FILE_PARENT_COLUMN, parent);
+    mFileInserter->bind(FILE_STORAGE_COLUMN, storage);
+    mFileInserter->bind(FILE_SIZE_COLUMN, size);
+    mFileInserter->bind(FILE_MODIFIED_COLUMN, modified);
+    mFileInserter->step();
+    mFileInserter->reset();
+    int result = mDatabase->lastInsertedRow();
+    return (result <= 0 ? kInvalidObjectHandle : result);
+}
+
+MtpObjectHandle MtpSqliteDatabase::addAudioFile(MtpObjectHandle handle) {
+    mAudioInserter->bind(AUDIO_ID_COLUMN, handle);
+    mAudioInserter->step();
+    mAudioInserter->reset();
+    int result = mDatabase->lastInsertedRow();
+    handle |= kObjectHandleTableAudio;
+    return (result > 0 ? handle : kInvalidObjectHandle);
+}
+
+MtpObjectHandle MtpSqliteDatabase::addAudioFile(MtpObjectHandle handle,
+                                    const char* title,
+                                    const char* artist,
+                                    const char* album,
+                                    const char* albumArtist,
+                                    const char* genre,
+                                    const char* composer,
+                                    const char* mimeType,
+                                    int track,
+                                    int year,
+                                    int duration) {
+    mAudioInserter->bind(AUDIO_ID_COLUMN, handle);
+    if (title) mAudioInserter->bind(AUDIO_TITLE_COLUMN, title);
+    if (artist) mAudioInserter->bind(AUDIO_ARTIST_COLUMN, artist);
+    if (album) mAudioInserter->bind(AUDIO_ALBUM_COLUMN, album);
+    if (albumArtist) mAudioInserter->bind(AUDIO_ALBUM_ARTIST_COLUMN, albumArtist);
+    if (genre) mAudioInserter->bind(AUDIO_GENRE_COLUMN, genre);
+    if (composer) mAudioInserter->bind(AUDIO_COMPOSER_COLUMN, composer);
+    if (track) mAudioInserter->bind(AUDIO_TRACK_NUMBER_COLUMN, track);
+    if (year) mAudioInserter->bind(AUDIO_YEAR_COLUMN, year);
+    if (duration) mAudioInserter->bind(AUDIO_DURATION_COLUMN, duration);
+    mAudioInserter->step();
+    mAudioInserter->reset();
+    int result = mDatabase->lastInsertedRow();
+    if (result <= 0)
+        return kInvalidObjectHandle;
+    result |= kObjectHandleTableAudio;
+    return result;
+}
+
+MtpObjectHandleList* MtpSqliteDatabase::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,format 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) {
+        if (parent != MTP_PARENT_ROOT)
+            parent &= kObjectHandleIndexMask;
+        snprintf(intBuffer, sizeof(intBuffer), "%d", parent);
+        if (whereStorage || whereFormat)
+            query += " AND";
+        query += " parent = ";
+        query += intBuffer;
+    }
+    query += ";";
+
+    SqliteStatement stmt(mDatabase);
+    LOGV("%s", (const char *)query);
+    stmt.prepare(query);
+
+    MtpObjectHandleList* list = new MtpObjectHandleList();
+    while (!stmt.isDone()) {
+        if (stmt.step()) {
+            int index = stmt.getColumnInt(0);
+            LOGV("stmt.getColumnInt returned %d", index);
+            if (index > 0) {
+                MtpObjectFormat format = stmt.getColumnInt(1);
+                index |= getTableForFile(format);
+                list->push(index);
+            }
+        }
+    }
+    LOGV("list size: %d", list->size());
+    return list;
+}
+
+
+MtpResponseCode MtpSqliteDatabase::getObjectProperty(MtpObjectHandle handle,
+                                    MtpObjectProperty property,
+                                    MtpDataPacket& packet) {
+    int         type;
+    const char* columnName;
+    char        intBuffer[20];
+
+    if (handle != MTP_PARENT_ROOT)
+        handle &= kObjectHandleIndexMask;
+
+    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(mDatabase);
+    LOGV("%s", (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:
+            LOGE("unsupported object type\n");
+            return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+    }
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpSqliteDatabase::getObjectInfo(MtpObjectHandle handle,
+                                        MtpDataPacket& packet) {
+    char    date[20];
+
+    if (handle != MTP_PARENT_ROOT)
+        handle &= kObjectHandleIndexMask;
+
+    mObjectInfoQuery->reset();
+    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 modified = mObjectInfoQuery->getColumnInt(5);
+    int associationType = (format == MTP_FORMAT_ASSOCIATION ?
+                            MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
+                            MTP_ASSOCIATION_TYPE_UNDEFINED);
+
+    LOGV("storageID: %d, format: %d, parent: %d", 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
+    packet.putEmptyString();
+    formatDateTime(modified, date, sizeof(date));
+    packet.putString(date);   // date modified
+    packet.putEmptyString();   // keywords
+
+    return MTP_RESPONSE_OK;
+}
+
+bool MtpSqliteDatabase::getObjectFilePath(MtpObjectHandle handle,
+                                    MtpString& filePath,
+                                    int64_t& fileLength) {
+    if (handle != MTP_PARENT_ROOT)
+        handle &= kObjectHandleIndexMask;
+    mFilePathQuery->reset();
+    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 MtpSqliteDatabase::deleteFile(MtpObjectHandle handle) {
+    uint32_t table = handle & kObjectHandleTableMask;
+    handle &= kObjectHandleIndexMask;
+    mFileDeleter->bind(1, handle);
+    mFileDeleter->step();
+    mFileDeleter->reset();
+    if (table == kObjectHandleTableAudio) {
+        mAudioDeleter->bind(1, handle);
+        mAudioDeleter->step();
+        mAudioDeleter->reset();
+    }
+
+    return true;
+}
+
+MtpObjectHandle* MtpSqliteDatabase::getFileList(int& outCount) {
+    MtpObjectHandle* result = NULL;
+    int count = 0;
+    SqliteStatement stmt(mDatabase);
+    stmt.prepare("SELECT count(*) FROM files;");
+
+    MtpObjectHandleList* list = new MtpObjectHandleList();
+    if (stmt.step())
+        count = stmt.getColumnInt(0);
+
+    if (count > 0) {
+        result = new MtpObjectHandle[count];
+        memset(result, 0, count * sizeof(*result));
+        SqliteStatement stmt2(mDatabase);
+        stmt2.prepare("SELECT _id,format FROM files;");
+
+        for (int i = 0; i < count; i++) {
+            if (!stmt2.step()) {
+                LOGW("getFileList ended early");
+                count = i;
+                break;
+            }
+            MtpObjectHandle handle = stmt2.getColumnInt(0);
+            MtpObjectFormat format = stmt2.getColumnInt(1);
+            handle |= getTableForFile(format);
+            result[i] = handle;
+        }
+    }
+    outCount = count;
+    return result;
+}
+
+void MtpSqliteDatabase::beginTransaction() {
+    mDatabase->beginTransaction();
+}
+
+void MtpSqliteDatabase::commitTransaction() {
+    mDatabase->commitTransaction();
+}
+
+void MtpSqliteDatabase::rollbackTransaction() {
+    mDatabase->rollbackTransaction();
+}
+
+}  // namespace android