blob: ac4c0cf5caca00ac17efa7ae745ce3fd4db05ece [file] [log] [blame]
Mike Lockwoodfceef462010-05-14 15:35:17 -04001/*
2 * Copyright (C) 2010 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
Mike Lockwoodb14e5882010-06-29 18:11:52 -040017#define LOG_TAG "MtpMediaScanner"
18
19#include "MtpDebug.h"
Mike Lockwoodfceef462010-05-14 15:35:17 -040020#include "MtpDatabase.h"
21#include "MtpMediaScanner.h"
Mike Lockwood335dd2b2010-05-19 10:33:39 -040022#include "mtp.h"
Mike Lockwoodfceef462010-05-14 15:35:17 -040023
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <sys/statfs.h>
27#include <unistd.h>
28#include <dirent.h>
29#include <errno.h>
30#include <string.h>
31#include <stdio.h>
32#include <limits.h>
33
34#include <media/mediascanner.h>
35#include <media/stagefright/StagefrightMediaScanner.h>
36
37namespace android {
38
39class MtpMediaScannerClient : public MediaScannerClient
40{
41public:
42 MtpMediaScannerClient()
43 {
44 reset();
45 }
46
47 virtual ~MtpMediaScannerClient()
48 {
49 }
50
51 // returns true if it succeeded, false if an exception occured in the Java code
52 virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
53 {
Mike Lockwoodb14e5882010-06-29 18:11:52 -040054 LOGV("scanFile %s", path);
Mike Lockwoodfceef462010-05-14 15:35:17 -040055 return true;
56 }
57
58 // returns true if it succeeded, false if an exception occured in the Java code
59 virtual bool handleStringTag(const char* name, const char* value)
60 {
61 int temp;
62
63 if (!strcmp(name, "title")) {
64 mTitle = value;
65 mHasTitle = true;
66 } else if (!strcmp(name, "artist")) {
67 mArtist = value;
68 mHasArtist = true;
69 } else if (!strcmp(name, "album")) {
70 mAlbum = value;
71 mHasAlbum = true;
72 } else if (!strcmp(name, "albumartist")) {
73 mAlbumArtist = value;
74 mHasAlbumArtist = true;
75 } else if (!strcmp(name, "genre")) {
76 // FIXME - handle numeric values here
77 mGenre = value;
78 mHasGenre = true;
79 } else if (!strcmp(name, "composer")) {
80 mComposer = value;
81 mHasComposer = true;
82 } else if (!strcmp(name, "tracknumber")) {
83 if (sscanf(value, "%d", &temp) == 1)
84 mTrack = temp;
85 } else if (!strcmp(name, "discnumber")) {
86 // currently unused
87 } else if (!strcmp(name, "year") || !strcmp(name, "date")) {
88 if (sscanf(value, "%d", &temp) == 1)
89 mYear = temp;
90 } else if (!strcmp(name, "duration")) {
91 if (sscanf(value, "%d", &temp) == 1)
92 mDuration = temp;
93 } else {
Mike Lockwoodb14e5882010-06-29 18:11:52 -040094 LOGV("handleStringTag %s : %s", name, value);
Mike Lockwoodfceef462010-05-14 15:35:17 -040095 }
96 return true;
97 }
98
99 // returns true if it succeeded, false if an exception occured in the Java code
100 virtual bool setMimeType(const char* mimeType)
101 {
102 mMimeType = mimeType;
103 mHasMimeType = true;
104 return true;
105 }
106
107 // returns true if it succeeded, false if an exception occured in the Java code
108 virtual bool addNoMediaFolder(const char* path)
109 {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400110 LOGV("addNoMediaFolder %s", path);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400111 return true;
112 }
113
114 void reset()
115 {
116 mHasTitle = false;
117 mHasArtist = false;
118 mHasAlbum = false;
119 mHasAlbumArtist = false;
120 mHasGenre = false;
121 mHasComposer = false;
122 mHasMimeType = false;
123 mTrack = mYear = mDuration = 0;
124 }
125
126 inline const char* getTitle() const { return mHasTitle ? (const char *)mTitle : NULL; }
127 inline const char* getArtist() const { return mHasArtist ? (const char *)mArtist : NULL; }
128 inline const char* getAlbum() const { return mHasAlbum ? (const char *)mAlbum : NULL; }
129 inline const char* getAlbumArtist() const { return mHasAlbumArtist ? (const char *)mAlbumArtist : NULL; }
130 inline const char* getGenre() const { return mHasGenre ? (const char *)mGenre : NULL; }
131 inline const char* getComposer() const { return mHasComposer ? (const char *)mComposer : NULL; }
132 inline const char* getMimeType() const { return mHasMimeType ? (const char *)mMimeType : NULL; }
133 inline int getTrack() const { return mTrack; }
134 inline int getYear() const { return mYear; }
135 inline int getDuration() const { return mDuration; }
136
137private:
138 MtpString mTitle;
139 MtpString mArtist;
140 MtpString mAlbum;
141 MtpString mAlbumArtist;
142 MtpString mGenre;
143 MtpString mComposer;
144 MtpString mMimeType;
145
146 bool mHasTitle;
147 bool mHasArtist;
148 bool mHasAlbum;
149 bool mHasAlbumArtist;
150 bool mHasGenre;
151 bool mHasComposer;
152 bool mHasMimeType;
153
154 int mTrack;
155 int mYear;
156 int mDuration;
157};
158
159
160MtpMediaScanner::MtpMediaScanner(MtpStorageID id, const char* filePath, MtpDatabase* db)
161 : mStorageID(id),
162 mFilePath(filePath),
163 mDatabase(db),
164 mMediaScanner(NULL),
165 mMediaScannerClient(NULL),
166 mFileList(NULL),
167 mFileCount(0)
168{
169 mMediaScanner = new StagefrightMediaScanner;
170 mMediaScannerClient = new MtpMediaScannerClient;
171}
172
173MtpMediaScanner::~MtpMediaScanner() {
174}
175
176bool MtpMediaScanner::scanFiles() {
177 mDatabase->beginTransaction();
178 mFileCount = 0;
179 mFileList = mDatabase->getFileList(mFileCount);
180
181 int ret = scanDirectory(mFilePath, MTP_PARENT_ROOT);
182
183 for (int i = 0; i < mFileCount; i++) {
184 MtpObjectHandle test = mFileList[i];
185 if (! (test & kObjectHandleMarkBit)) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400186 LOGV("delete missing file %08X", test);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400187 mDatabase->deleteFile(test);
188 }
189 }
190
191 delete[] mFileList;
192 mFileCount = 0;
193 mDatabase->commitTransaction();
194 return (ret == 0);
195}
196
197
198static const struct MediaFileTypeEntry
199{
200 const char* extension;
201 MtpObjectFormat format;
202 uint32_t table;
203} sFileTypes[] =
204{
205 { "MP3", MTP_FORMAT_MP3, kObjectHandleTableAudio },
206 { "M4A", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
207 { "WAV", MTP_FORMAT_WAV, kObjectHandleTableAudio },
208 { "AMR", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
209 { "AWB", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
210 { "WMA", MTP_FORMAT_WMA, kObjectHandleTableAudio },
211 { "OGG", MTP_FORMAT_OGG, kObjectHandleTableAudio },
212 { "OGA", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
213 { "AAC", MTP_FORMAT_AAC, kObjectHandleTableAudio },
214 { "MID", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
215 { "MIDI", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
216 { "XMF", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
217 { "RTTTL", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
218 { "SMF", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
219 { "IMY", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
220 { "RTX", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
221 { "OTA", MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio },
222 { "MPEG", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
223 { "MP4", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
224 { "M4V", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
225 { "3GP", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
226 { "3GPP", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
227 { "3G2", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
228 { "3GPP2", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
229 { "WMV", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
230 { "ASF", MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo },
231 { "JPG", MTP_FORMAT_EXIF_JPEG, kObjectHandleTableImage },
232 { "JPEG", MTP_FORMAT_EXIF_JPEG, kObjectHandleTableImage },
233 { "GIF", MTP_FORMAT_GIF, kObjectHandleTableImage },
234 { "PNG", MTP_FORMAT_PNG, kObjectHandleTableImage },
235 { "BMP", MTP_FORMAT_BMP, kObjectHandleTableImage },
236 { "WBMP", MTP_FORMAT_BMP, kObjectHandleTableImage },
237 { "M3U", MTP_FORMAT_M3U_PLAYLIST, kObjectHandleTablePlaylist },
238 { "PLS", MTP_FORMAT_PLS_PLAYLIST, kObjectHandleTablePlaylist },
239 { "WPL", MTP_FORMAT_WPL_PLAYLIST, kObjectHandleTablePlaylist },
240};
241
242MtpObjectFormat MtpMediaScanner::getFileFormat(const char* path, uint32_t& table)
243{
244 const char* extension = strrchr(path, '.');
245 if (!extension)
246 return MTP_FORMAT_UNDEFINED;
247 extension++; // skip the dot
248
249 for (unsigned i = 0; i < sizeof(sFileTypes) / sizeof(sFileTypes[0]); i++) {
250 if (!strcasecmp(extension, sFileTypes[i].extension)) {
251 table = sFileTypes[i].table;
252 return sFileTypes[i].format;
253 }
254 }
255 table = kObjectHandleTableFile;
256 return MTP_FORMAT_UNDEFINED;
257}
258
259int MtpMediaScanner::scanDirectory(const char* path, MtpObjectHandle parent)
260{
261 char buffer[PATH_MAX];
262 struct dirent* entry;
263
264 unsigned length = strlen(path);
265 if (length > sizeof(buffer) + 2) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400266 LOGE("path too long: %s", path);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400267 }
268
269 DIR* dir = opendir(path);
270 if (!dir) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400271 LOGE("opendir %s failed, errno: %d", path, errno);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400272 return -1;
273 }
274
275 strncpy(buffer, path, sizeof(buffer));
276 char* fileStart = buffer + length;
277 // make sure we have a trailing slash
278 if (fileStart[-1] != '/') {
279 *(fileStart++) = '/';
280 }
281 int fileNameLength = sizeof(buffer) + fileStart - buffer;
282
283 while ((entry = readdir(dir))) {
284 const char* name = entry->d_name;
285
286 // ignore "." and "..", as well as any files or directories staring with dot
287 if (name[0] == '.') {
288 continue;
289 }
290 if (strlen(name) + 1 > fileNameLength) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400291 LOGE("path too long for %s", name);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400292 continue;
293 }
294 strcpy(fileStart, name);
295
296 struct stat statbuf;
297 memset(&statbuf, 0, sizeof(statbuf));
298 stat(buffer, &statbuf);
299
Mike Lockwood6084a292010-06-14 23:00:30 -0700300 if (S_ISDIR(statbuf.st_mode)) {
Mike Lockwoodfceef462010-05-14 15:35:17 -0400301 MtpObjectHandle handle = mDatabase->getObjectHandle(buffer);
302 if (handle) {
303 markFile(handle);
304 } else {
305 handle = mDatabase->addFile(buffer, MTP_FORMAT_ASSOCIATION,
306 parent, mStorageID, 0, statbuf.st_mtime);
307 }
308 scanDirectory(buffer, handle);
Mike Lockwood6084a292010-06-14 23:00:30 -0700309 } else if (S_ISREG(statbuf.st_mode)) {
Mike Lockwoodfceef462010-05-14 15:35:17 -0400310 scanFile(buffer, parent, statbuf);
311 }
312 }
313
314 closedir(dir);
315 return 0;
316}
317
318void MtpMediaScanner::scanFile(const char* path, MtpObjectHandle parent, struct stat& statbuf) {
319 uint32_t table;
320 MtpObjectFormat format = getFileFormat(path, table);
321 // don't scan unknown file types
322 if (format == MTP_FORMAT_UNDEFINED)
323 return;
324 MtpObjectHandle handle = mDatabase->getObjectHandle(path);
325 // fixme - rescan if mod date changed
326 if (handle) {
327 markFile(handle);
328 } else {
329 mDatabase->beginTransaction();
330 handle = mDatabase->addFile(path, format, parent, mStorageID,
331 statbuf.st_size, statbuf.st_mtime);
332 if (handle <= 0) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400333 LOGE("addFile failed in MtpMediaScanner::scanFile()");
Mike Lockwoodfceef462010-05-14 15:35:17 -0400334 mDatabase->rollbackTransaction();
335 return;
336 }
337
338 if (table == kObjectHandleTableAudio) {
339 mMediaScannerClient->reset();
340 mMediaScanner->processFile(path, NULL, *mMediaScannerClient);
341 handle = mDatabase->addAudioFile(handle,
342 mMediaScannerClient->getTitle(),
343 mMediaScannerClient->getArtist(),
344 mMediaScannerClient->getAlbum(),
345 mMediaScannerClient->getAlbumArtist(),
346 mMediaScannerClient->getGenre(),
347 mMediaScannerClient->getComposer(),
348 mMediaScannerClient->getMimeType(),
349 mMediaScannerClient->getTrack(),
350 mMediaScannerClient->getYear(),
351 mMediaScannerClient->getDuration());
352 }
353 mDatabase->commitTransaction();
354 }
355}
356
357void MtpMediaScanner::markFile(MtpObjectHandle handle) {
358 if (mFileList) {
359 handle &= kObjectHandleIndexMask;
360 // binary search for the file in mFileList
361 int low = 0;
362 int high = mFileCount;
363 int index;
364
365 while (low < high) {
366 index = (low + high) >> 1;
367 MtpObjectHandle test = (mFileList[index] & kObjectHandleIndexMask);
368 if (handle < test)
369 high = index; // item is less than index
370 else if (handle > test)
371 low = index + 1; // item is greater than index
372 else {
373 mFileList[index] |= kObjectHandleMarkBit;
374 return;
375 }
376 }
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400377 LOGE("file %d not found in mFileList", handle);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400378 }
379}
380
381} // namespace android