MTP: More prototyping work:
New media scanner test program
Media scanner now cleans up after files that no longer exist
Separate database table for audio files
Extract metadata from audio files with libstagefright
Change-Id: I2bd0fe877836c741658e72fcfeb89c11be0d9b41
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/media/mtp/MtpDatabase.cpp b/media/mtp/MtpDatabase.cpp
index 8f6c75d..ab22ddd 100644
--- a/media/mtp/MtpDatabase.cpp
+++ b/media/mtp/MtpDatabase.cpp
@@ -20,38 +20,74 @@
#include "SqliteStatement.h"
#include <stdio.h>
+#include <stdlib.h>
#include <sqlite3.h>
namespace android {
-#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 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 TABLE_CREATE "CREATE TABLE IF NOT EXISTS files (" \
+#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_created INTEGER," \
- "date_modified 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 FROM files WHERE 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_created,date_modified FROM files WHERE _id = ?;"
-#define FILE_INSERT "INSERT INTO files VALUES(?,?,?,?,?,?,?,?);"
-#define FILE_DELETE "DELETE FROM files WHERE path = ?;"
+#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;
@@ -65,7 +101,6 @@
{ 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" },
};
@@ -83,11 +118,14 @@
}
+
MtpDatabase::MtpDatabase()
: mFileIdQuery(NULL),
mObjectInfoQuery(NULL),
mFileInserter(NULL),
- mFileDeleter(NULL)
+ mFileDeleter(NULL),
+ mAudioInserter(NULL),
+ mAudioDeleter(NULL)
{
}
@@ -98,66 +136,195 @@
if (!SqliteDatabase::open(path, create))
return false;
- // create the table if necessary
- if (!exec(TABLE_CREATE)) {
- fprintf(stderr, "could not create table\n");
+ // create tables and indices if necessary
+ if (!exec(FILE_TABLE_CREATE)) {
+ fprintf(stderr, "could not create file table\n");
return false;
}
if (!exec(PATH_INDEX_CREATE)) {
- fprintf(stderr, "could not path index\n");
+ fprintf(stderr, "could not path index on file table\n");
return false;
}
+ if (!exec(AUDIO_TABLE_CREATE)) {
+ fprintf(stderr, "could not create file table\n");
+ return false;
+ }
+
+ if (!mFileIdQuery) {
+ mFileIdQuery = new SqliteStatement(this);
+ if (!mFileIdQuery->prepare(FILE_ID_QUERY)) {
+ fprintf(stderr, "could not compile FILE_ID_QUERY\n");
+ exit(-1);
+ }
+ }
+ if (!mFilePathQuery) {
+ mFilePathQuery = new SqliteStatement(this);
+ if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) {
+ fprintf(stderr, "could not compile FILE_PATH_QUERY\n");
+ exit(-1);
+ }
+ }
+ if (!mObjectInfoQuery) {
+ mObjectInfoQuery = new SqliteStatement(this);
+ if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) {
+ fprintf(stderr, "could not compile GET_OBJECT_INFO_QUERY\n");
+ exit(-1);
+ }
+ }
+ if (!mFileInserter) {
+ mFileInserter = new SqliteStatement(this);
+ if (!mFileInserter->prepare(FILE_INSERT)) {
+ fprintf(stderr, "could not compile FILE_INSERT\n");
+ exit(-1);
+ }
+ }
+ if (!mFileDeleter) {
+ mFileDeleter = new SqliteStatement(this);
+ if (!mFileDeleter->prepare(FILE_DELETE)) {
+ fprintf(stderr, "could not compile FILE_DELETE\n");
+ exit(-1);
+ }
+ }
+ if (!mAudioInserter) {
+ mAudioInserter = new SqliteStatement(this);
+ if (!mAudioInserter->prepare(AUDIO_INSERT)) {
+ fprintf(stderr, "could not compile AUDIO_INSERT\n");
+ exit(-1);
+ }
+ }
+ if (!mAudioDeleter) {
+ mAudioDeleter = new SqliteStatement(this);
+ if (!mAudioDeleter->prepare(AUDIO_DELETE)) {
+ fprintf(stderr, "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 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->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 row = lastInsertedRow();
- return (row > 0 ? row : kInvalidObjectHandle);
+ 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,
@@ -168,7 +335,7 @@
bool whereParent = (parent != 0);
char intBuffer[20];
- MtpString query("SELECT _id FROM files");
+ MtpString query("SELECT _id,format FROM files");
if (whereStorage || whereFormat || whereParent)
query += " WHERE";
if (whereStorage) {
@@ -184,6 +351,8 @@
query += intBuffer;
}
if (whereParent) {
+ if (parent != MTP_PARENT_ROOT)
+ parent &= kObjectHandleIndexMask;
snprintf(intBuffer, sizeof(intBuffer), "%d", parent);
if (whereStorage || whereFormat)
query += " AND";
@@ -201,14 +370,18 @@
if (stmt.step()) {
int index = stmt.getColumnInt(0);
printf("stmt.getColumnInt returned %d\n", index);
- if (index > 0)
+ if (index > 0) {
+ MtpObjectFormat format = stmt.getColumnInt(1);
+ index |= getTableForFile(format);
list->push(index);
+ }
}
}
printf("list size: %d\n", list->size());
return list;
}
+
MtpResponseCode MtpDatabase::getObjectProperty(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet) {
@@ -216,6 +389,9 @@
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);
@@ -272,18 +448,10 @@
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;
- }
- }
+ if (handle != MTP_PARENT_ROOT)
+ handle &= kObjectHandleIndexMask;
+ mObjectInfoQuery->reset();
mObjectInfoQuery->bind(1, handle);
if (!mObjectInfoQuery->step())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
@@ -297,8 +465,7 @@
if (lastSlash)
name = lastSlash + 1;
int64_t size = mObjectInfoQuery->getColumnInt64(4);
- time_t created = mObjectInfoQuery->getColumnInt(5);
- time_t modified = mObjectInfoQuery->getColumnInt(6);
+ time_t modified = mObjectInfoQuery->getColumnInt(5);
int associationType = (format == MTP_FORMAT_ASSOCIATION ?
MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
MTP_ASSOCIATION_TYPE_UNDEFINED);
@@ -321,8 +488,7 @@
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
+ packet.putEmptyString();
formatDateTime(modified, date, sizeof(date));
packet.putString(date); // date modified
packet.putEmptyString(); // keywords
@@ -333,18 +499,9 @@
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;
- }
- }
-
+ if (handle != MTP_PARENT_ROOT)
+ handle &= kObjectHandleIndexMask;
+ mFilePathQuery->reset();
mFilePathQuery->bind(1, handle);
if (!mFilePathQuery->step())
return false;
@@ -358,22 +515,52 @@
}
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);
+ 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()) {
+ printf("getFileList ended early\n");
+ 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