/* * 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 "mtp.h" #include #include #include #include #include #include #include #include #include #include #include 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