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