| /* |
| * 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 "MtpDatabase" |
| |
| #include "MtpDebug.h" |
| #include "MtpDatabase.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; |
| } |
| |
| |
| |
| MtpDatabase::MtpDatabase() |
| : mFileIdQuery(NULL), |
| mFilePathQuery(NULL), |
| mObjectInfoQuery(NULL), |
| mFileInserter(NULL), |
| mFileDeleter(NULL), |
| mAudioInserter(NULL), |
| mAudioDeleter(NULL) |
| { |
| } |
| |
| MtpDatabase::~MtpDatabase() { |
| } |
| |
| bool MtpDatabase::open(const char* path, bool create) { |
| if (!SqliteDatabase::open(path, create)) |
| return false; |
| |
| // create tables and indices if necessary |
| if (!exec(FILE_TABLE_CREATE)) { |
| LOGE("could not create file table"); |
| return false; |
| } |
| if (!exec(PATH_INDEX_CREATE)) { |
| LOGE("could not path index on file table"); |
| return false; |
| } |
| if (!exec(AUDIO_TABLE_CREATE)) { |
| LOGE("could not create file table"); |
| return false; |
| } |
| |
| if (!mFileIdQuery) { |
| mFileIdQuery = new SqliteStatement(this); |
| if (!mFileIdQuery->prepare(FILE_ID_QUERY)) { |
| LOGE("could not compile FILE_ID_QUERY"); |
| exit(-1); |
| } |
| } |
| if (!mFilePathQuery) { |
| mFilePathQuery = new SqliteStatement(this); |
| if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) { |
| LOGE("could not compile FILE_PATH_QUERY"); |
| exit(-1); |
| } |
| } |
| if (!mObjectInfoQuery) { |
| mObjectInfoQuery = new SqliteStatement(this); |
| if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) { |
| LOGE("could not compile GET_OBJECT_INFO_QUERY"); |
| exit(-1); |
| } |
| } |
| if (!mFileInserter) { |
| mFileInserter = new SqliteStatement(this); |
| if (!mFileInserter->prepare(FILE_INSERT)) { |
| LOGE("could not compile FILE_INSERT\n"); |
| exit(-1); |
| } |
| } |
| if (!mFileDeleter) { |
| mFileDeleter = new SqliteStatement(this); |
| if (!mFileDeleter->prepare(FILE_DELETE)) { |
| LOGE("could not compile FILE_DELETE\n"); |
| exit(-1); |
| } |
| } |
| if (!mAudioInserter) { |
| mAudioInserter = new SqliteStatement(this); |
| if (!mAudioInserter->prepare(AUDIO_INSERT)) { |
| LOGE("could not compile AUDIO_INSERT\n"); |
| exit(-1); |
| } |
| } |
| if (!mAudioDeleter) { |
| mAudioDeleter = new SqliteStatement(this); |
| if (!mAudioDeleter->prepare(AUDIO_DELETE)) { |
| LOGE("could not compile AUDIO_DELETE\n"); |
| exit(-1); |
| } |
| } |
| |
| return true; |
| } |
| |
| uint32_t MtpDatabase::getTableForFile(MtpObjectFormat format) { |
| switch (format) { |
| case MTP_FORMAT_AIFF: |
| case MTP_FORMAT_WAV: |
| case MTP_FORMAT_MP3: |
| case MTP_FORMAT_FLAC: |
| case MTP_FORMAT_UNDEFINED_AUDIO: |
| case MTP_FORMAT_WMA: |
| case MTP_FORMAT_OGG: |
| case MTP_FORMAT_AAC: |
| case MTP_FORMAT_AUDIBLE: |
| return kObjectHandleTableAudio; |
| case MTP_FORMAT_AVI: |
| case MTP_FORMAT_MPEG: |
| case MTP_FORMAT_ASF: |
| case MTP_FORMAT_UNDEFINED_VIDEO: |
| case MTP_FORMAT_WMV: |
| case MTP_FORMAT_MP4_CONTAINER: |
| case MTP_FORMAT_MP2: |
| case MTP_FORMAT_3GP_CONTAINER: |
| return kObjectHandleTableVideo; |
| case MTP_FORMAT_DEFINED: |
| case MTP_FORMAT_EXIF_JPEG: |
| case MTP_FORMAT_TIFF_EP: |
| case MTP_FORMAT_FLASHPIX: |
| case MTP_FORMAT_BMP: |
| case MTP_FORMAT_CIFF: |
| case MTP_FORMAT_GIF: |
| case MTP_FORMAT_JFIF: |
| case MTP_FORMAT_CD: |
| case MTP_FORMAT_PICT: |
| case MTP_FORMAT_PNG: |
| case MTP_FORMAT_TIFF: |
| case MTP_FORMAT_TIFF_IT: |
| case MTP_FORMAT_JP2: |
| case MTP_FORMAT_JPX: |
| case MTP_FORMAT_WINDOWS_IMAGE_FORMAT: |
| return kObjectHandleTableImage; |
| case MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST: |
| case MTP_FORMAT_ABSTRACT_AV_PLAYLIST: |
| case MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST: |
| case MTP_FORMAT_WPL_PLAYLIST: |
| case MTP_FORMAT_M3U_PLAYLIST: |
| case MTP_FORMAT_MPL_PLAYLIST: |
| case MTP_FORMAT_ASX_PLAYLIST: |
| case MTP_FORMAT_PLS_PLAYLIST: |
| return kObjectHandleTablePlaylist; |
| default: |
| return kObjectHandleTableFile; |
| } |
| } |
| |
| MtpObjectHandle MtpDatabase::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 MtpDatabase::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 = lastInsertedRow(); |
| return (result <= 0 ? kInvalidObjectHandle : result); |
| } |
| |
| MtpObjectHandle MtpDatabase::addAudioFile(MtpObjectHandle handle) { |
| mAudioInserter->bind(AUDIO_ID_COLUMN, handle); |
| mAudioInserter->step(); |
| mAudioInserter->reset(); |
| int result = lastInsertedRow(); |
| handle |= kObjectHandleTableAudio; |
| return (result > 0 ? handle : kInvalidObjectHandle); |
| } |
| |
| MtpObjectHandle MtpDatabase::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 = lastInsertedRow(); |
| if (result <= 0) |
| return kInvalidObjectHandle; |
| result |= kObjectHandleTableAudio; |
| return result; |
| } |
| |
| 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,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(this); |
| 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 MtpDatabase::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(this); |
| 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 MtpDatabase::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 MtpDatabase::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 MtpDatabase::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* MtpDatabase::getFileList(int& outCount) { |
| MtpObjectHandle* result = NULL; |
| int count = 0; |
| SqliteStatement stmt(this); |
| 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(this); |
| 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; |
| } |
| |
| /* |
| for getObjectPropDesc |
| |
| packet.putUInt16(property); |
| packet.putUInt16(dataType); |
| packet.putUInt8(getSet); |
| // default value DTS |
| packet.putUInt32(groupCode); |
| packet.putUInt8(formFlag); |
| // form, variable |
| */ |
| |
| } // namespace android |