Change-Id: Ifbd5bd5efa3cdb750ae1a2aae38181457554d34d Signed-off-by: Mike Lockwood <mike@spruce.(none)>
379 lines
13 KiB
C++
379 lines
13 KiB
C++
/*
|
|
* 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 <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
|