diff options
Diffstat (limited to 'media/mtp/MtpMediaScanner.cpp')
-rw-r--r-- | media/mtp/MtpMediaScanner.cpp | 377 |
1 files changed, 377 insertions, 0 deletions
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 |