You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
7.9 KiB
261 lines
7.9 KiB
/*
|
|
* Copyright (C) 2009 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.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "MediaScanner"
|
|
#include <cutils/properties.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include <media/mediascanner.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
|
|
namespace android {
|
|
|
|
MediaScanner::MediaScanner()
|
|
: mLocale(NULL), mSkipList(NULL), mSkipIndex(NULL) {
|
|
loadSkipList();
|
|
}
|
|
|
|
MediaScanner::~MediaScanner() {
|
|
setLocale(NULL);
|
|
free(mSkipList);
|
|
free(mSkipIndex);
|
|
}
|
|
|
|
void MediaScanner::setLocale(const char *locale) {
|
|
if (mLocale) {
|
|
free(mLocale);
|
|
mLocale = NULL;
|
|
}
|
|
if (locale) {
|
|
mLocale = strdup(locale);
|
|
}
|
|
}
|
|
|
|
const char *MediaScanner::locale() const {
|
|
return mLocale;
|
|
}
|
|
|
|
void MediaScanner::loadSkipList() {
|
|
mSkipList = (char *)malloc(PROPERTY_VALUE_MAX * sizeof(char));
|
|
if (mSkipList) {
|
|
property_get("testing.mediascanner.skiplist", mSkipList, "");
|
|
}
|
|
if (!mSkipList || (strlen(mSkipList) == 0)) {
|
|
free(mSkipList);
|
|
mSkipList = NULL;
|
|
return;
|
|
}
|
|
mSkipIndex = (int *)malloc(PROPERTY_VALUE_MAX * sizeof(int));
|
|
if (mSkipIndex) {
|
|
// dup it because strtok will modify the string
|
|
char *skipList = strdup(mSkipList);
|
|
if (skipList) {
|
|
char * path = strtok(skipList, ",");
|
|
int i = 0;
|
|
while (path) {
|
|
mSkipIndex[i++] = strlen(path);
|
|
path = strtok(NULL, ",");
|
|
}
|
|
mSkipIndex[i] = -1;
|
|
free(skipList);
|
|
}
|
|
}
|
|
}
|
|
|
|
MediaScanResult MediaScanner::processDirectory(
|
|
const char *path, MediaScannerClient &client) {
|
|
int pathLength = strlen(path);
|
|
if (pathLength >= PATH_MAX) {
|
|
return MEDIA_SCAN_RESULT_SKIPPED;
|
|
}
|
|
char* pathBuffer = (char *)malloc(PATH_MAX + 1);
|
|
if (!pathBuffer) {
|
|
return MEDIA_SCAN_RESULT_ERROR;
|
|
}
|
|
|
|
int pathRemaining = PATH_MAX - pathLength;
|
|
strcpy(pathBuffer, path);
|
|
if (pathLength > 0 && pathBuffer[pathLength - 1] != '/') {
|
|
pathBuffer[pathLength] = '/';
|
|
pathBuffer[pathLength + 1] = 0;
|
|
--pathRemaining;
|
|
}
|
|
|
|
client.setLocale(locale());
|
|
|
|
MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false);
|
|
|
|
free(pathBuffer);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool MediaScanner::shouldSkipDirectory(char *path) {
|
|
if (path && mSkipList && mSkipIndex) {
|
|
int len = strlen(path);
|
|
int idx = 0;
|
|
// track the start position of next path in the comma
|
|
// separated list obtained from getprop
|
|
int startPos = 0;
|
|
while (mSkipIndex[idx] != -1) {
|
|
// no point to match path name if strlen mismatch
|
|
if ((len == mSkipIndex[idx])
|
|
// pick out the path segment from comma separated list
|
|
// to compare against current path parameter
|
|
&& (strncmp(path, &mSkipList[startPos], len) == 0)) {
|
|
return true;
|
|
}
|
|
startPos += mSkipIndex[idx] + 1; // extra char for the delimiter
|
|
idx++;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MediaScanResult MediaScanner::doProcessDirectory(
|
|
char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) {
|
|
// place to copy file or directory name
|
|
char* fileSpot = path + strlen(path);
|
|
struct dirent* entry;
|
|
|
|
if (shouldSkipDirectory(path)) {
|
|
ALOGD("Skipping: %s", path);
|
|
return MEDIA_SCAN_RESULT_OK;
|
|
}
|
|
|
|
// Treat all files as non-media in directories that contain a ".nomedia" file
|
|
if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
|
|
strcpy(fileSpot, ".nomedia");
|
|
if (access(path, F_OK) == 0) {
|
|
ALOGV("found .nomedia, setting noMedia flag");
|
|
noMedia = true;
|
|
}
|
|
|
|
// restore path
|
|
fileSpot[0] = 0;
|
|
}
|
|
|
|
DIR* dir = opendir(path);
|
|
if (!dir) {
|
|
ALOGW("Error opening directory '%s', skipping: %s.", path, strerror(errno));
|
|
return MEDIA_SCAN_RESULT_SKIPPED;
|
|
}
|
|
|
|
MediaScanResult result = MEDIA_SCAN_RESULT_OK;
|
|
while ((entry = readdir(dir))) {
|
|
if (doProcessDirectoryEntry(path, pathRemaining, client, noMedia, entry, fileSpot)
|
|
== MEDIA_SCAN_RESULT_ERROR) {
|
|
result = MEDIA_SCAN_RESULT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
closedir(dir);
|
|
return result;
|
|
}
|
|
|
|
MediaScanResult MediaScanner::doProcessDirectoryEntry(
|
|
char *path, int pathRemaining, MediaScannerClient &client, bool noMedia,
|
|
struct dirent* entry, char* fileSpot) {
|
|
struct stat statbuf;
|
|
const char* name = entry->d_name;
|
|
|
|
// ignore "." and ".."
|
|
if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
|
|
return MEDIA_SCAN_RESULT_SKIPPED;
|
|
}
|
|
|
|
int nameLength = strlen(name);
|
|
if (nameLength + 1 > pathRemaining) {
|
|
// path too long!
|
|
return MEDIA_SCAN_RESULT_SKIPPED;
|
|
}
|
|
strcpy(fileSpot, name);
|
|
|
|
int type = entry->d_type;
|
|
if (type == DT_UNKNOWN) {
|
|
// If the type is unknown, stat() the file instead.
|
|
// This is sometimes necessary when accessing NFS mounted filesystems, but
|
|
// could be needed in other cases well.
|
|
if (stat(path, &statbuf) == 0) {
|
|
if (S_ISREG(statbuf.st_mode)) {
|
|
type = DT_REG;
|
|
} else if (S_ISDIR(statbuf.st_mode)) {
|
|
type = DT_DIR;
|
|
}
|
|
} else {
|
|
ALOGD("stat() failed for %s: %s", path, strerror(errno) );
|
|
}
|
|
}
|
|
if (type == DT_DIR) {
|
|
bool childNoMedia = noMedia;
|
|
// set noMedia flag on directories with a name that starts with '.'
|
|
// for example, the Mac ".Trashes" directory
|
|
if (name[0] == '.')
|
|
childNoMedia = true;
|
|
|
|
// report the directory to the client
|
|
if (stat(path, &statbuf) == 0) {
|
|
status_t status = client.scanFile(path, statbuf.st_mtime, 0,
|
|
true /*isDirectory*/, childNoMedia);
|
|
if (status) {
|
|
return MEDIA_SCAN_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
// and now process its contents
|
|
strcat(fileSpot, "/");
|
|
MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1,
|
|
client, childNoMedia);
|
|
if (result == MEDIA_SCAN_RESULT_ERROR) {
|
|
return MEDIA_SCAN_RESULT_ERROR;
|
|
}
|
|
} else if (type == DT_REG) {
|
|
stat(path, &statbuf);
|
|
status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size,
|
|
false /*isDirectory*/, noMedia);
|
|
if (status) {
|
|
return MEDIA_SCAN_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
return MEDIA_SCAN_RESULT_OK;
|
|
}
|
|
|
|
MediaAlbumArt *MediaAlbumArt::clone() {
|
|
size_t byte_size = this->size() + sizeof(MediaAlbumArt);
|
|
MediaAlbumArt *result = reinterpret_cast<MediaAlbumArt *>(malloc(byte_size));
|
|
result->mSize = this->size();
|
|
memcpy(&result->mData[0], &this->mData[0], this->size());
|
|
return result;
|
|
}
|
|
|
|
void MediaAlbumArt::init(MediaAlbumArt *instance, int32_t dataSize, const void *data) {
|
|
instance->mSize = dataSize;
|
|
memcpy(&instance->mData[0], data, dataSize);
|
|
}
|
|
|
|
MediaAlbumArt *MediaAlbumArt::fromData(int32_t dataSize, const void* data) {
|
|
size_t byte_size = sizeof(MediaAlbumArt) + dataSize;
|
|
MediaAlbumArt *result = reinterpret_cast<MediaAlbumArt *>(malloc(byte_size));
|
|
init(result, dataSize, data);
|
|
return result;
|
|
}
|
|
|
|
} // namespace android
|