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 
27 namespace android {
28 
MediaScanner()29 MediaScanner::MediaScanner()
30     : mLocale(NULL), mSkipList(NULL), mSkipIndex(NULL) {
31     loadSkipList();
32 }
33 
~MediaScanner()34 MediaScanner::~MediaScanner() {
35     setLocale(NULL);
36     free(mSkipList);
37     free(mSkipIndex);
38 }
39 
setLocale(const char * locale)40 void 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 
locale() const50 const char *MediaScanner::locale() const {
51     return mLocale;
52 }
53 
loadSkipList()54 void 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 
processDirectory(const char * path,MediaScannerClient & client)81 MediaScanResult 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 
shouldSkipDirectory(char * path)109 bool 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 
doProcessDirectory(char * path,int pathRemaining,MediaScannerClient & client,bool noMedia)131 MediaScanResult 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 
doProcessDirectoryEntry(char * path,int pathRemaining,MediaScannerClient & client,bool noMedia,struct dirent * entry,char * fileSpot)172 MediaScanResult 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 
clone()240 MediaAlbumArt *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 
init(MediaAlbumArt * instance,int32_t dataSize,const void * data)248 void MediaAlbumArt::init(MediaAlbumArt *instance, int32_t dataSize, const void *data) {
249     instance->mSize = dataSize;
250     memcpy(&instance->mData[0], data, dataSize);
251 }
252 
fromData(int32_t dataSize,const void * data)253 MediaAlbumArt *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