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