diff options
author | Mike Lockwood <lockwood@android.com> | 2010-05-14 15:35:17 -0400 |
---|---|---|
committer | Mike Lockwood <lockwood@android.com> | 2010-05-18 16:12:38 -0400 |
commit | d0782678df0b23b06c8687372b58994ed252a61a (patch) | |
tree | c6e51021eb3b07f13a08da255608b206f7b2c377 | |
parent | f6e9a082a2d7d97bbe45fc7e38fb7ea2a62f39ac (diff) | |
download | frameworks_base-d0782678df0b23b06c8687372b58994ed252a61a.zip frameworks_base-d0782678df0b23b06c8687372b58994ed252a61a.tar.gz frameworks_base-d0782678df0b23b06c8687372b58994ed252a61a.tar.bz2 |
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>
-rw-r--r-- | media/mtp/Android.mk | 30 | ||||
-rw-r--r-- | media/mtp/MtpDatabase.cpp | 381 | ||||
-rw-r--r-- | media/mtp/MtpDatabase.h | 23 | ||||
-rw-r--r-- | media/mtp/MtpMediaScanner.cpp | 377 | ||||
-rw-r--r-- | media/mtp/MtpMediaScanner.h | 56 | ||||
-rw-r--r-- | media/mtp/MtpServer.cpp | 46 | ||||
-rw-r--r-- | media/mtp/MtpStorage.cpp | 62 | ||||
-rw-r--r-- | media/mtp/MtpStorage.h | 3 | ||||
-rw-r--r-- | media/mtp/mtp.h | 12 | ||||
-rw-r--r-- | media/mtp/scantest.cpp | 38 |
10 files changed, 858 insertions, 170 deletions
diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk index 4d86064..d82ace3 100644 --- a/media/mtp/Android.mk +++ b/media/mtp/Android.mk @@ -24,6 +24,7 @@ LOCAL_SRC_FILES:= \ MtpDatabase.cpp \ MtpDataPacket.cpp \ MtpDebug.cpp \ + MtpMediaScanner.cpp \ MtpPacket.cpp \ MtpRequestPacket.cpp \ MtpResponsePacket.cpp \ @@ -40,7 +41,7 @@ LOCAL_C_INCLUDES := external/sqlite/dist LOCAL_CFLAGS := -DMTP_DEVICE -LOCAL_SHARED_LIBRARIES := libutils libsqlite +LOCAL_SHARED_LIBRARIES := libutils libsqlite libstagefright include $(BUILD_EXECUTABLE) @@ -70,4 +71,31 @@ LOCAL_LDFLAGS := -g include $(BUILD_HOST_EXECUTABLE) +include $(CLEAR_VARS) + +LOCAL_MODULE := scantest +LOCAL_SRC_FILES:= \ + scantest.cpp \ + MtpMediaScanner.cpp \ + MtpDatabase.cpp \ + MtpDataPacket.cpp \ + MtpPacket.cpp \ + MtpStringBuffer.cpp \ + MtpUtils.cpp \ + SqliteDatabase.cpp \ + SqliteStatement.cpp \ + + +#LOCAL_STATIC_LIBRARIES := libusbhost +#LOCAL_LDLIBS := -lpthread + +LOCAL_C_INCLUDES := external/sqlite/dist +LOCAL_SHARED_LIBRARIES := libutils libsqlite libstagefright + + +LOCAL_CFLAGS := -g +LOCAL_LDFLAGS := -g + +include $(BUILD_EXECUTABLE) + endif
\ No newline at end of file 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 TABLE_CREATE "CREATE TABLE IF NOT EXISTS files (" \ +#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_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 @@ static const PropertyTableEntry kPropertyTable[] = { { 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 @@ static bool getPropertyInfo(MtpObjectProperty property, int& type, const char*& } + MtpDatabase::MtpDatabase() : mFileIdQuery(NULL), mObjectInfoQuery(NULL), mFileInserter(NULL), - mFileDeleter(NULL) + mFileDeleter(NULL), + mAudioInserter(NULL), + mAudioDeleter(NULL) { } @@ -98,66 +136,195 @@ 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"); + // 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; } - 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 { + if (!mFileIdQuery) { 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; + exit(-1); } } - - mFileIdQuery->bind(1, path); - if (mFileIdQuery->step()) { - int row = mFileIdQuery->getColumnInt(0); - if (row > 0) - return row; + 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"); - delete mFileInserter; - mFileInserter = NULL; - return kInvalidObjectHandle; + 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; } } - 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); + + 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 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 @@ MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID, 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 @@ MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID, 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 @@ MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID, 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 @@ MtpResponseCode MtpDatabase::getObjectProperty(MtpObjectHandle handle, 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 @@ 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; - } - } + 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 @@ MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, 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 @@ MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, 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 @@ MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, 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::getObjectFilePath(MtpObjectHandle handle, } 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 diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h index 2a48155..a6be6a6 100644 --- a/media/mtp/MtpDatabase.h +++ b/media/mtp/MtpDatabase.h @@ -33,20 +33,38 @@ private: SqliteStatement* mObjectInfoQuery; SqliteStatement* mFileInserter; SqliteStatement* mFileDeleter; + SqliteStatement* mAudioInserter; + SqliteStatement* mAudioDeleter; public: MtpDatabase(); virtual ~MtpDatabase(); + static uint32_t getTableForFile(MtpObjectFormat format); + bool open(const char* path, bool create); + MtpObjectHandle getObjectHandle(const char* path); MtpObjectHandle addFile(const char* path, MtpObjectFormat format, MtpObjectHandle parent, MtpStorageID storage, uint64_t size, - time_t created, time_t modified); + MtpObjectHandle addAudioFile(MtpObjectHandle id); + + MtpObjectHandle addAudioFile(MtpObjectHandle id, + 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); + MtpObjectHandleList* getObjectList(MtpStorageID storageID, MtpObjectFormat format, MtpObjectHandle parent); @@ -62,6 +80,9 @@ public: MtpString& filePath, int64_t& fileLength); bool deleteFile(MtpObjectHandle handle); + + // helper for media scanner + MtpObjectHandle* getFileList(int& outCount); }; }; // namespace android diff --git a/media/mtp/MtpMediaScanner.cpp b/media/mtp/MtpMediaScanner.cpp new file mode 100644 index 0000000..8b08f36 --- /dev/null +++ b/media/mtp/MtpMediaScanner.cpp @@ -0,0 +1,377 @@ +/* + * 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 "MtpMediaScanner.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> + +#include <media/mediascanner.h> +#include <media/stagefright/StagefrightMediaScanner.h> + +namespace android { + +class MtpMediaScannerClient : public MediaScannerClient +{ +public: + MtpMediaScannerClient() + { + reset(); + } + + virtual ~MtpMediaScannerClient() + { + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool scanFile(const char* path, long long lastModified, long long fileSize) + { + printf("scanFile %s\n", path); + return true; + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool handleStringTag(const char* name, const char* value) + { + int temp; + + if (!strcmp(name, "title")) { + mTitle = value; + mHasTitle = true; + } else if (!strcmp(name, "artist")) { + mArtist = value; + mHasArtist = true; + } else if (!strcmp(name, "album")) { + mAlbum = value; + mHasAlbum = true; + } else if (!strcmp(name, "albumartist")) { + mAlbumArtist = value; + mHasAlbumArtist = true; + } else if (!strcmp(name, "genre")) { + // FIXME - handle numeric values here + mGenre = value; + mHasGenre = true; + } else if (!strcmp(name, "composer")) { + mComposer = value; + mHasComposer = true; + } else if (!strcmp(name, "tracknumber")) { + if (sscanf(value, "%d", &temp) == 1) + mTrack = temp; + } else if (!strcmp(name, "discnumber")) { + // currently unused + } else if (!strcmp(name, "year") || !strcmp(name, "date")) { + if (sscanf(value, "%d", &temp) == 1) + mYear = temp; + } else if (!strcmp(name, "duration")) { + if (sscanf(value, "%d", &temp) == 1) + mDuration = temp; + } else { + printf("handleStringTag %s : %s\n", name, value); + } + return true; + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool setMimeType(const char* mimeType) + { + mMimeType = mimeType; + mHasMimeType = true; + return true; + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool addNoMediaFolder(const char* path) + { + printf("addNoMediaFolder %s\n", path); + return true; + } + + void reset() + { + mHasTitle = false; + mHasArtist = false; + mHasAlbum = false; + mHasAlbumArtist = false; + mHasGenre = false; + mHasComposer = false; + mHasMimeType = false; + mTrack = mYear = mDuration = 0; + } + + inline const char* getTitle() const { return mHasTitle ? (const char *)mTitle : NULL; } + inline const char* getArtist() const { return mHasArtist ? (const char *)mArtist : NULL; } + inline const char* getAlbum() const { return mHasAlbum ? (const char *)mAlbum : NULL; } + inline const char* getAlbumArtist() const { return mHasAlbumArtist ? (const char *)mAlbumArtist : NULL; } + inline const char* getGenre() const { return mHasGenre ? (const char *)mGenre : NULL; } + inline const char* getComposer() const { return mHasComposer ? (const char *)mComposer : NULL; } + inline const char* getMimeType() const { return mHasMimeType ? (const char *)mMimeType : NULL; } + inline int getTrack() const { return mTrack; } + inline int getYear() const { return mYear; } + inline int getDuration() const { return mDuration; } + +private: + MtpString mTitle; + MtpString mArtist; + MtpString mAlbum; + MtpString mAlbumArtist; + MtpString mGenre; + MtpString mComposer; + MtpString mMimeType; + + bool mHasTitle; + bool mHasArtist; + bool mHasAlbum; + bool mHasAlbumArtist; + bool mHasGenre; + bool mHasComposer; + bool mHasMimeType; + + int mTrack; + int mYear; + int mDuration; +}; + + +MtpMediaScanner::MtpMediaScanner(MtpStorageID id, const char* filePath, MtpDatabase* db) + : mStorageID(id), + mFilePath(filePath), + mDatabase(db), + mMediaScanner(NULL), + mMediaScannerClient(NULL), + mFileList(NULL), + mFileCount(0) +{ + mMediaScanner = new StagefrightMediaScanner; + mMediaScannerClient = new MtpMediaScannerClient; +} + +MtpMediaScanner::~MtpMediaScanner() { +} + +bool MtpMediaScanner::scanFiles() { + mDatabase->beginTransaction(); + mFileCount = 0; + mFileList = mDatabase->getFileList(mFileCount); + + int ret = scanDirectory(mFilePath, MTP_PARENT_ROOT); + + for (int i = 0; i < mFileCount; i++) { + MtpObjectHandle test = mFileList[i]; + if (! (test & kObjectHandleMarkBit)) { + printf("delete missing file %08X\n", test); + mDatabase->deleteFile(test); + } + } + + delete[] mFileList; + mFileCount = 0; + mDatabase->commitTransaction(); + return (ret == 0); +} + + +static const struct MediaFileTypeEntry +{ + const char* extension; + MtpObjectFormat format; + uint32_t table; +} sFileTypes[] = +{ + { "MP3", MTP_FORMAT_MP3, kObjectHandleTableAudio }, + { "M4A", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "WAV", MTP_FORMAT_WAV, kObjectHandleTableAudio }, + { "AMR", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "AWB", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "WMA", MTP_FORMAT_WMA, kObjectHandleTableAudio }, + { "OGG", MTP_FORMAT_OGG, kObjectHandleTableAudio }, + { "OGA", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "AAC", MTP_FORMAT_AAC, kObjectHandleTableAudio }, + { "MID", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "MIDI", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "XMF", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "RTTTL", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "SMF", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "IMY", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "RTX", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "OTA", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio }, + { "MPEG", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "MP4", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "M4V", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "3GP", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "3GPP", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "3G2", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "3GPP2", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "WMV", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "ASF", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo }, + { "JPG", MTP_FORMAT_EXIF_JPEG, kObjectHandleTableImage }, + { "JPEG", MTP_FORMAT_EXIF_JPEG, kObjectHandleTableImage }, + { "GIF", MTP_FORMAT_GIF, kObjectHandleTableImage }, + { "PNG", MTP_FORMAT_PNG, kObjectHandleTableImage }, + { "BMP", MTP_FORMAT_BMP, kObjectHandleTableImage }, + { "WBMP", MTP_FORMAT_BMP, kObjectHandleTableImage }, + { "M3U", MTP_FORMAT_M3U_PLAYLIST, kObjectHandleTablePlaylist }, + { "PLS", MTP_FORMAT_PLS_PLAYLIST, kObjectHandleTablePlaylist }, + { "WPL", MTP_FORMAT_WPL_PLAYLIST, kObjectHandleTablePlaylist }, +}; + +MtpObjectFormat MtpMediaScanner::getFileFormat(const char* path, uint32_t& table) +{ + const char* extension = strrchr(path, '.'); + if (!extension) + return MTP_FORMAT_UNDEFINED; + extension++; // skip the dot + + for (unsigned i = 0; i < sizeof(sFileTypes) / sizeof(sFileTypes[0]); i++) { + if (!strcasecmp(extension, sFileTypes[i].extension)) { + table = sFileTypes[i].table; + return sFileTypes[i].format; + } + } + table = kObjectHandleTableFile; + return MTP_FORMAT_UNDEFINED; +} + +int MtpMediaScanner::scanDirectory(const char* path, MtpObjectHandle parent) +{ + char buffer[PATH_MAX]; + struct dirent* entry; + + unsigned length = strlen(path); + if (length > sizeof(buffer) + 2) { + fprintf(stderr, "path too long: %s\n", path); + } + + DIR* dir = opendir(path); + if (!dir) { + fprintf(stderr, "opendir %s failed, errno: %d", path, errno); + return -1; + } + + strncpy(buffer, path, sizeof(buffer)); + char* fileStart = buffer + length; + // make sure we have a trailing slash + if (fileStart[-1] != '/') { + *(fileStart++) = '/'; + } + int fileNameLength = sizeof(buffer) + fileStart - buffer; + + while ((entry = readdir(dir))) { + const char* name = entry->d_name; + + // ignore "." and "..", as well as any files or directories staring with dot + if (name[0] == '.') { + continue; + } + if (strlen(name) + 1 > fileNameLength) { + fprintf(stderr, "path too long for %s\n", name); + continue; + } + strcpy(fileStart, name); + + struct stat statbuf; + memset(&statbuf, 0, sizeof(statbuf)); + stat(buffer, &statbuf); + + if (entry->d_type == DT_DIR) { + MtpObjectHandle handle = mDatabase->getObjectHandle(buffer); + if (handle) { + markFile(handle); + } else { + handle = mDatabase->addFile(buffer, MTP_FORMAT_ASSOCIATION, + parent, mStorageID, 0, statbuf.st_mtime); + } + scanDirectory(buffer, handle); + } else if (entry->d_type == DT_REG) { + scanFile(buffer, parent, statbuf); + } + } + + closedir(dir); + return 0; +} + +void MtpMediaScanner::scanFile(const char* path, MtpObjectHandle parent, struct stat& statbuf) { + uint32_t table; + MtpObjectFormat format = getFileFormat(path, table); + // don't scan unknown file types + if (format == MTP_FORMAT_UNDEFINED) + return; + MtpObjectHandle handle = mDatabase->getObjectHandle(path); + // fixme - rescan if mod date changed + if (handle) { + markFile(handle); + } else { + mDatabase->beginTransaction(); + handle = mDatabase->addFile(path, format, parent, mStorageID, + statbuf.st_size, statbuf.st_mtime); + if (handle <= 0) { + fprintf(stderr, "addFile failed in MtpMediaScanner::scanFile()\n"); + mDatabase->rollbackTransaction(); + return; + } + + if (table == kObjectHandleTableAudio) { + mMediaScannerClient->reset(); + mMediaScanner->processFile(path, NULL, *mMediaScannerClient); + handle = mDatabase->addAudioFile(handle, + mMediaScannerClient->getTitle(), + mMediaScannerClient->getArtist(), + mMediaScannerClient->getAlbum(), + mMediaScannerClient->getAlbumArtist(), + mMediaScannerClient->getGenre(), + mMediaScannerClient->getComposer(), + mMediaScannerClient->getMimeType(), + mMediaScannerClient->getTrack(), + mMediaScannerClient->getYear(), + mMediaScannerClient->getDuration()); + } + mDatabase->commitTransaction(); + } +} + +void MtpMediaScanner::markFile(MtpObjectHandle handle) { + if (mFileList) { + handle &= kObjectHandleIndexMask; + // binary search for the file in mFileList + int low = 0; + int high = mFileCount; + int index; + + while (low < high) { + index = (low + high) >> 1; + MtpObjectHandle test = (mFileList[index] & kObjectHandleIndexMask); + if (handle < test) + high = index; // item is less than index + else if (handle > test) + low = index + 1; // item is greater than index + else { + mFileList[index] |= kObjectHandleMarkBit; + return; + } + } + fprintf(stderr, "file %d not found in mFileList\n", handle); + } +} + +} // namespace android diff --git a/media/mtp/MtpMediaScanner.h b/media/mtp/MtpMediaScanner.h new file mode 100644 index 0000000..53d5063 --- /dev/null +++ b/media/mtp/MtpMediaScanner.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef _MTP_MEDIA_SCANNER_H +#define _MTP_MEDIA_SCANNER_H + +struct stat; + +namespace android { + +class MtpDatabase; +class SqliteStatement; +class MediaScanner; +class MtpMediaScannerClient; + +class MtpMediaScanner { +private: + MtpStorageID mStorageID; + const char* mFilePath; + MtpDatabase* mDatabase; + MediaScanner* mMediaScanner; + MtpMediaScannerClient* mMediaScannerClient; + + // for garbage collecting missing files + MtpObjectHandle* mFileList; + int mFileCount; + +public: + MtpMediaScanner(MtpStorageID id, const char* filePath, MtpDatabase* db); + virtual ~MtpMediaScanner(); + + bool scanFiles(); + +private: + MtpObjectFormat getFileFormat(const char* path, uint32_t& table); + int scanDirectory(const char* path, MtpObjectHandle parent); + void scanFile(const char* path, MtpObjectHandle parent, struct stat& statbuf); + void markFile(MtpObjectHandle handle); +}; + +}; // namespace android + +#endif // _MTP_MEDIA_SCANNER_H diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index d868926..6a90568 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -79,9 +79,35 @@ static const MtpObjectProperty kSupportedObjectProperties[] = { }; static const MtpObjectFormat kSupportedPlaybackFormats[] = { - // FIXME - fill this out later + // MTP_FORMAT_UNDEFINED, MTP_FORMAT_ASSOCIATION, + // MTP_FORMAT_TEXT, + // MTP_FORMAT_HTML, MTP_FORMAT_MP3, + //MTP_FORMAT_AVI, + MTP_FORMAT_MPEG, + // MTP_FORMAT_ASF, + MTP_FORMAT_EXIF_JPEG, + MTP_FORMAT_TIFF_EP, + // MTP_FORMAT_BMP, + MTP_FORMAT_GIF, + MTP_FORMAT_JFIF, + MTP_FORMAT_PNG, + MTP_FORMAT_TIFF, + MTP_FORMAT_WMA, + MTP_FORMAT_OGG, + MTP_FORMAT_AAC, + // MTP_FORMAT_FLAC, + // MTP_FORMAT_WMV, + MTP_FORMAT_MP4_CONTAINER, + MTP_FORMAT_MP2, + MTP_FORMAT_3GP_CONTAINER, + // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM, + // MTP_FORMAT_ABSTRACT_AV_PLAYLIST, + // MTP_FORMAT_WPL_PLAYLIST, + // MTP_FORMAT_M3U_PLAYLIST, + // MTP_FORMAT_MPL_PLAYLIST, + // MTP_FORMAT_PLS_PLAYLIST, }; MtpServer::MtpServer(int fd, const char* databasePath) @@ -420,9 +446,7 @@ MtpResponseCode MtpServer::doSendObjectInfo() { mData.getString(modified); // date modified // keywords follow - time_t createdTime, modifiedTime; - if (!parseDateTime(created, createdTime)) - createdTime = 0; + time_t modifiedTime; if (!parseDateTime(modified, modifiedTime)) modifiedTime = 0; printf("SendObjectInfo format: %04X size: %d name: %s, created: %s, modified: %s\n", @@ -432,11 +456,17 @@ format, mSendObjectFileSize, (const char*)name, (const char*)created, (const cha path += "/"; path += (const char *)name; - MtpObjectHandle handle = mDatabase->addFile((const char*)path, - format, parent, storageID, mSendObjectFileSize, - createdTime, modifiedTime); - if (handle == kInvalidObjectHandle) + mDatabase->beginTransaction(); + MtpObjectHandle handle = mDatabase->addFile((const char*)path, format, parent, storageID, + mSendObjectFileSize, modifiedTime); + if (handle == kInvalidObjectHandle) { + mDatabase->rollbackTransaction(); return MTP_RESPONSE_GENERAL_ERROR; + } + uint32_t table = MtpDatabase::getTableForFile(format); + if (table == kObjectHandleTableAudio) + handle = mDatabase->addAudioFile(handle); + mDatabase->commitTransaction(); if (format == MTP_FORMAT_ASSOCIATION) { mode_t mask = umask(0); diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp index d4de819..f176148 100644 --- a/media/mtp/MtpStorage.cpp +++ b/media/mtp/MtpStorage.cpp @@ -16,6 +16,7 @@ #include "MtpDatabase.h" #include "MtpStorage.h" +#include "MtpMediaScanner.h" #include <sys/types.h> #include <sys/stat.h> @@ -74,65 +75,8 @@ const char* MtpStorage::getDescription() const { } bool MtpStorage::scanFiles() { - mDatabase->beginTransaction(); - int ret = scanDirectory(mFilePath, MTP_PARENT_ROOT); - mDatabase->commitTransaction(); - return (ret == 0); -} - -int MtpStorage::scanDirectory(const char* path, MtpObjectHandle parent) -{ - char buffer[PATH_MAX]; - struct dirent* entry; - - int length = strlen(path); - if (length > sizeof(buffer) + 2) { - fprintf(stderr, "path too long: %s\n", path); - } - - DIR* dir = opendir(path); - if (!dir) { - fprintf(stderr, "opendir %s failed, errno: %d", path, errno); - return -1; - } - - strncpy(buffer, path, sizeof(buffer)); - char* fileStart = buffer + length; - // make sure we have a trailing slash - if (fileStart[-1] != '/') { - *(fileStart++) = '/'; - } - int fileNameLength = sizeof(buffer) + fileStart - buffer; - - while ((entry = readdir(dir))) { - const char* name = entry->d_name; - - // ignore "." and "..", as well as any files or directories staring with dot - if (name[0] == '.') { - continue; - } - if (strlen(name) + 1 > fileNameLength) { - fprintf(stderr, "path too long for %s\n", name); - continue; - } - strcpy(fileStart, name); - - struct stat statbuf; - memset(&statbuf, 0, sizeof(statbuf)); - stat(buffer, &statbuf); - - if (entry->d_type == DT_DIR) { - MtpObjectHandle handle = mDatabase->addFile(buffer, MTP_FORMAT_ASSOCIATION, - parent, mStorageID, 0, 0, statbuf.st_mtime); - scanDirectory(buffer, handle); - } else if (entry->d_type == DT_REG) { - mDatabase->addFile(buffer, MTP_FORMAT_UNDEFINED, parent, mStorageID, - statbuf.st_size, 0, statbuf.st_mtime); - } - } - - closedir(dir); - return 0; + MtpMediaScanner scanner(mStorageID, mFilePath, mDatabase); + return scanner.scanFiles(); } } // namespace android diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h index b1d4408..6097272 100644 --- a/media/mtp/MtpStorage.h +++ b/media/mtp/MtpStorage.h @@ -46,9 +46,6 @@ public: inline const char* getPath() const { return mFilePath; } bool scanFiles(); - -private: - int scanDirectory(const char* path, MtpObjectHandle parent); }; }; // namespace android diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h index 27abaa7..57a0281 100644 --- a/media/mtp/mtp.h +++ b/media/mtp/mtp.h @@ -32,7 +32,17 @@ typedef uint16_t MtpObjectProperty; // values 0x00000000 and 0xFFFFFFFF are reserved for special purposes. typedef uint32_t MtpObjectHandle; -#define kInvalidObjectHandle 0xFFFFFFFF +#define kInvalidObjectHandle 0xFFFFFFFF + +// MtpObjectHandle bits and masks +#define kObjectHandleMarkBit 0x80000000 // used for mark & sweep by MtpMediaScanner +#define kObjectHandleTableMask 0x70000000 // mask for object table +#define kObjectHandleTableFile 0x00000000 // object is only in the file table +#define kObjectHandleTableAudio 0x10000000 // object is in the audio table +#define kObjectHandleTableVideo 0x20000000 // object is in the video table +#define kObjectHandleTableImage 0x30000000 // object is in the images table +#define kObjectHandleTablePlaylist 0x40000000 // object is in the playlist table +#define kObjectHandleIndexMask 0x0FFFFFFF // mask for object index in file table #define MTP_STANDARD_VERSION 100 diff --git a/media/mtp/scantest.cpp b/media/mtp/scantest.cpp new file mode 100644 index 0000000..f910bb6 --- /dev/null +++ b/media/mtp/scantest.cpp @@ -0,0 +1,38 @@ +/* + * 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 <stdio.h> + +#include "MtpDatabase.h" +#include "MtpMediaScanner.h" + +using namespace android; + +int main(int argc, char* argv[]) { + if (argc != 2) { + fprintf(stderr, "usage: %s <storage path>\n", argv[0]); + return -1; + } + + MtpDatabase* database = new MtpDatabase(); + database->open("scantest.db", true); + + MtpMediaScanner scanner(1, argv[1], database); + scanner.scanFiles(); + database->close(); + + return 0; +} |