1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "MediaScanner" 19#include <cutils/properties.h> 20#include <utils/Log.h> 21 22#include <media/mediascanner.h> 23 24#include <sys/stat.h> 25#include <dirent.h> 26 27namespace android { 28 29MediaScanner::MediaScanner() 30 : mLocale(NULL), mSkipList(NULL), mSkipIndex(NULL) { 31 loadSkipList(); 32} 33 34MediaScanner::~MediaScanner() { 35 setLocale(NULL); 36 free(mSkipList); 37 free(mSkipIndex); 38} 39 40void MediaScanner::setLocale(const char *locale) { 41 if (mLocale) { 42 free(mLocale); 43 mLocale = NULL; 44 } 45 if (locale) { 46 mLocale = strdup(locale); 47 } 48} 49 50const char *MediaScanner::locale() const { 51 return mLocale; 52} 53 54void MediaScanner::loadSkipList() { 55 mSkipList = (char *)malloc(PROPERTY_VALUE_MAX * sizeof(char)); 56 if (mSkipList) { 57 property_get("testing.mediascanner.skiplist", mSkipList, ""); 58 } 59 if (!mSkipList || (strlen(mSkipList) == 0)) { 60 free(mSkipList); 61 mSkipList = NULL; 62 return; 63 } 64 mSkipIndex = (int *)malloc(PROPERTY_VALUE_MAX * sizeof(int)); 65 if (mSkipIndex) { 66 // dup it because strtok will modify the string 67 char *skipList = strdup(mSkipList); 68 if (skipList) { 69 char * path = strtok(skipList, ","); 70 int i = 0; 71 while (path) { 72 mSkipIndex[i++] = strlen(path); 73 path = strtok(NULL, ","); 74 } 75 mSkipIndex[i] = -1; 76 free(skipList); 77 } 78 } 79} 80 81MediaScanResult MediaScanner::processDirectory( 82 const char *path, MediaScannerClient &client) { 83 int pathLength = strlen(path); 84 if (pathLength >= PATH_MAX) { 85 return MEDIA_SCAN_RESULT_SKIPPED; 86 } 87 char* pathBuffer = (char *)malloc(PATH_MAX + 1); 88 if (!pathBuffer) { 89 return MEDIA_SCAN_RESULT_ERROR; 90 } 91 92 int pathRemaining = PATH_MAX - pathLength; 93 strcpy(pathBuffer, path); 94 if (pathLength > 0 && pathBuffer[pathLength - 1] != '/') { 95 pathBuffer[pathLength] = '/'; 96 pathBuffer[pathLength + 1] = 0; 97 --pathRemaining; 98 } 99 100 client.setLocale(locale()); 101 102 MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false); 103 104 free(pathBuffer); 105 106 return result; 107} 108 109bool MediaScanner::shouldSkipDirectory(char *path) { 110 if (path && mSkipList && mSkipIndex) { 111 int len = strlen(path); 112 int idx = 0; 113 // track the start position of next path in the comma 114 // separated list obtained from getprop 115 int startPos = 0; 116 while (mSkipIndex[idx] != -1) { 117 // no point to match path name if strlen mismatch 118 if ((len == mSkipIndex[idx]) 119 // pick out the path segment from comma separated list 120 // to compare against current path parameter 121 && (strncmp(path, &mSkipList[startPos], len) == 0)) { 122 return true; 123 } 124 startPos += mSkipIndex[idx] + 1; // extra char for the delimiter 125 idx++; 126 } 127 } 128 return false; 129} 130 131MediaScanResult MediaScanner::doProcessDirectory( 132 char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) { 133 // place to copy file or directory name 134 char* fileSpot = path + strlen(path); 135 struct dirent* entry; 136 137 if (shouldSkipDirectory(path)) { 138 ALOGD("Skipping: %s", path); 139 return MEDIA_SCAN_RESULT_OK; 140 } 141 142 // Treat all files as non-media in directories that contain a ".nomedia" file 143 if (pathRemaining >= 8 /* strlen(".nomedia") */ ) { 144 strcpy(fileSpot, ".nomedia"); 145 if (access(path, F_OK) == 0) { 146 ALOGV("found .nomedia, setting noMedia flag"); 147 noMedia = true; 148 } 149 150 // restore path 151 fileSpot[0] = 0; 152 } 153 154 DIR* dir = opendir(path); 155 if (!dir) { 156 ALOGW("Error opening directory '%s', skipping: %s.", path, strerror(errno)); 157 return MEDIA_SCAN_RESULT_SKIPPED; 158 } 159 160 MediaScanResult result = MEDIA_SCAN_RESULT_OK; 161 while ((entry = readdir(dir))) { 162 if (doProcessDirectoryEntry(path, pathRemaining, client, noMedia, entry, fileSpot) 163 == MEDIA_SCAN_RESULT_ERROR) { 164 result = MEDIA_SCAN_RESULT_ERROR; 165 break; 166 } 167 } 168 closedir(dir); 169 return result; 170} 171 172MediaScanResult MediaScanner::doProcessDirectoryEntry( 173 char *path, int pathRemaining, MediaScannerClient &client, bool noMedia, 174 struct dirent* entry, char* fileSpot) { 175 struct stat statbuf; 176 const char* name = entry->d_name; 177 178 // ignore "." and ".." 179 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) { 180 return MEDIA_SCAN_RESULT_SKIPPED; 181 } 182 183 int nameLength = strlen(name); 184 if (nameLength + 1 > pathRemaining) { 185 // path too long! 186 return MEDIA_SCAN_RESULT_SKIPPED; 187 } 188 strcpy(fileSpot, name); 189 190 int type = entry->d_type; 191 if (type == DT_UNKNOWN) { 192 // If the type is unknown, stat() the file instead. 193 // This is sometimes necessary when accessing NFS mounted filesystems, but 194 // could be needed in other cases well. 195 if (stat(path, &statbuf) == 0) { 196 if (S_ISREG(statbuf.st_mode)) { 197 type = DT_REG; 198 } else if (S_ISDIR(statbuf.st_mode)) { 199 type = DT_DIR; 200 } 201 } else { 202 ALOGD("stat() failed for %s: %s", path, strerror(errno) ); 203 } 204 } 205 if (type == DT_DIR) { 206 bool childNoMedia = noMedia; 207 // set noMedia flag on directories with a name that starts with '.' 208 // for example, the Mac ".Trashes" directory 209 if (name[0] == '.') 210 childNoMedia = true; 211 212 // report the directory to the client 213 if (stat(path, &statbuf) == 0) { 214 status_t status = client.scanFile(path, statbuf.st_mtime, 0, 215 true /*isDirectory*/, childNoMedia); 216 if (status) { 217 return MEDIA_SCAN_RESULT_ERROR; 218 } 219 } 220 221 // and now process its contents 222 strcat(fileSpot, "/"); 223 MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1, 224 client, childNoMedia); 225 if (result == MEDIA_SCAN_RESULT_ERROR) { 226 return MEDIA_SCAN_RESULT_ERROR; 227 } 228 } else if (type == DT_REG) { 229 stat(path, &statbuf); 230 status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size, 231 false /*isDirectory*/, noMedia); 232 if (status) { 233 return MEDIA_SCAN_RESULT_ERROR; 234 } 235 } 236 237 return MEDIA_SCAN_RESULT_OK; 238} 239 240MediaAlbumArt *MediaAlbumArt::clone() { 241 size_t byte_size = this->size() + sizeof(MediaAlbumArt); 242 MediaAlbumArt *result = reinterpret_cast<MediaAlbumArt *>(malloc(byte_size)); 243 result->mSize = this->size(); 244 memcpy(&result->mData[0], &this->mData[0], this->size()); 245 return result; 246} 247 248void MediaAlbumArt::init(MediaAlbumArt *instance, int32_t dataSize, const void *data) { 249 instance->mSize = dataSize; 250 memcpy(&instance->mData[0], data, dataSize); 251} 252 253MediaAlbumArt *MediaAlbumArt::fromData(int32_t dataSize, const void* data) { 254 size_t byte_size = sizeof(MediaAlbumArt) + dataSize; 255 MediaAlbumArt *result = reinterpret_cast<MediaAlbumArt *>(malloc(byte_size)); 256 init(result, dataSize, data); 257 return result; 258} 259 260} // namespace android 261