Merge "Eliminate scanner file cache"

This commit is contained in:
Marco Nelissen
2012-02-22 09:42:54 -08:00
committed by Android (Google) Code Review

View File

@@ -62,6 +62,9 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import libcore.io.ErrnoException;
import libcore.io.Libcore;
/**
* Internal service helper that no-one should use directly.
*
@@ -348,20 +351,18 @@ public class MediaScanner
private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileCacheEntry {
private static class FileEntry {
long mRowId;
String mPath;
long mLastModified;
int mFormat;
boolean mSeenInFileSystem;
boolean mLastModifiedChanged;
FileCacheEntry(long rowId, String path, long lastModified, int format) {
FileEntry(long rowId, String path, long lastModified, int format) {
mRowId = rowId;
mPath = path;
mLastModified = lastModified;
mFormat = format;
mSeenInFileSystem = false;
mLastModifiedChanged = false;
}
@@ -373,11 +374,7 @@ public class MediaScanner
private MediaInserter mMediaInserter;
// hashes file path to FileCacheEntry.
// path should be lower case if mCaseInsensitivePaths is true
private LinkedHashMap<String, FileCacheEntry> mFileCache;
private ArrayList<FileCacheEntry> mPlayLists;
private ArrayList<FileEntry> mPlayLists;
private DrmManagerClient mDrmManagerClient = null;
@@ -432,7 +429,7 @@ public class MediaScanner
private int mWidth;
private int mHeight;
public FileCacheEntry beginFile(String path, String mimeType, long lastModified,
public FileEntry beginFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean noMedia) {
mMimeType = mimeType;
mFileType = 0;
@@ -465,11 +462,7 @@ public class MediaScanner
}
}
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
}
FileCacheEntry entry = mFileCache.get(key);
FileEntry entry = makeEntryFor(path);
// add some slack to avoid a rounding error
long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
boolean wasModified = delta > 1 || delta < -1;
@@ -477,13 +470,11 @@ public class MediaScanner
if (wasModified) {
entry.mLastModified = lastModified;
} else {
entry = new FileCacheEntry(0, path, lastModified,
entry = new FileEntry(0, path, lastModified,
(isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
mFileCache.put(key, entry);
}
entry.mLastModifiedChanged = true;
}
entry.mSeenInFileSystem = true;
if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
mPlayLists.add(entry);
@@ -525,7 +516,7 @@ public class MediaScanner
Uri result = null;
// long t1 = System.currentTimeMillis();
try {
FileCacheEntry entry = beginFile(path, mimeType, lastModified,
FileEntry entry = beginFile(path, mimeType, lastModified,
fileSize, isDirectory, noMedia);
// rescan for metadata if file was modified since last scan
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
@@ -778,7 +769,7 @@ public class MediaScanner
return map;
}
private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean music, boolean podcasts)
throws RemoteException {
// update database
@@ -1028,55 +1019,94 @@ public class MediaScanner
String where = null;
String[] selectionArgs = null;
if (mFileCache == null) {
mFileCache = new LinkedHashMap<String, FileCacheEntry>();
} else {
mFileCache.clear();
}
if (mPlayLists == null) {
mPlayLists = new ArrayList<FileCacheEntry>();
mPlayLists = new ArrayList<FileEntry>();
} else {
mPlayLists.clear();
}
if (filePath != null) {
// query for only one file
where = Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { filePath };
where = MediaStore.Files.FileColumns._ID + ">?" +
" AND " + Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { "", filePath };
} else {
where = MediaStore.Files.FileColumns._ID + ">?";
selectionArgs = new String[] { "" };
}
// Tell the provider to not delete the file.
// If the file is truly gone the delete is unnecessary, and we want to avoid
// accidentally deleting files that are really there (this may happen if the
// filesystem is mounted and unmounted while the scanner is running).
Uri.Builder builder = mFilesUri.buildUpon();
builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
// Build the list of files from the content provider
try {
if (prescanFiles) {
// First read existing files from the files table
// First read existing files from the files table.
// Because we'll be deleting entries for missing files as we go,
// we need to query the database in small batches, to avoid problems
// with CursorWindow positioning.
long lastId = Long.MIN_VALUE;
Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
mWasEmptyPriorToScan = true;
c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, null, null);
while (true) {
selectionArgs[0] = "" + lastId;
if (c != null) {
c.close();
c = null;
}
c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
if (c == null) {
break;
}
if (c != null) {
mWasEmptyPriorToScan = c.getCount() == 0;
int num = c.getCount();
if (num == 0) {
break;
}
mWasEmptyPriorToScan = false;
while (c.moveToNext()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
lastId = rowId;
// Only consider entries with absolute path names.
// This allows storing URIs in the database without the
// media scanner removing them.
if (path != null && path.startsWith("/")) {
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
boolean exists = false;
try {
exists = Libcore.os.access(path, libcore.io.OsConstants.F_OK);
} catch (ErrnoException e1) {
}
if (!exists && !MtpConstants.isAbstractObject(format)) {
// do not delete missing playlists, since they may have been
// modified by the user.
// The user can delete them in the media player instead.
// instead, clear the path and lastModified fields in the row
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
FileCacheEntry entry = new FileCacheEntry(rowId, path,
lastModified, format);
mFileCache.put(key, entry);
if (!MediaFile.isPlayListFileType(fileType)) {
deleter.delete(rowId);
if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
deleter.flush();
String parent = new File(path).getParent();
mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null);
}
}
}
}
}
c.close();
c = null;
}
}
}
@@ -1084,6 +1114,7 @@ public class MediaScanner
if (c != null) {
c.close();
}
deleter.flush();
}
// compute original size of images
@@ -1186,57 +1217,6 @@ public class MediaScanner
}
private void postscan(String[] directories) throws RemoteException {
Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
// Tell the provider to not delete the file.
// If the file is truly gone the delete is unnecessary, and we want to avoid
// accidentally deleting files that are really there (this may happen if the
// filesystem is mounted and unmounted while the scanner is running).
Uri.Builder builder = mFilesUri.buildUpon();
builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
while (iterator.hasNext()) {
FileCacheEntry entry = iterator.next();
String path = entry.mPath;
// remove database entries for files that no longer exist.
boolean fileMissing = false;
if (!entry.mSeenInFileSystem && !MtpConstants.isAbstractObject(entry.mFormat)) {
if (inScanDirectory(path, directories)) {
// we didn't see this file in the scan directory.
fileMissing = true;
} else {
// the file actually a directory or other abstract object
// or is outside of our scan directory,
// so we need to check for file existence here.
File testFile = new File(path);
if (!testFile.exists()) {
fileMissing = true;
}
}
}
if (fileMissing) {
// do not delete missing playlists, since they may have been modified by the user.
// the user can delete them in the media player instead.
// instead, clear the path and lastModified fields in the row
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
if (!MediaFile.isPlayListFileType(fileType)) {
deleter.delete(entry.mRowId);
iterator.remove();
if (entry.mPath.toLowerCase(Locale.US).endsWith("/.nomedia")) {
deleter.flush();
File f = new File(path);
mMediaProvider.call(MediaStore.UNHIDE_CALL, f.getParent(), null);
}
}
}
}
deleter.flush();
// handle playlists last, after we know what media files are on the storage.
if (mProcessPlaylists) {
@@ -1248,7 +1228,6 @@ public class MediaScanner
// allow GC to clean up
mPlayLists = null;
mFileCache = null;
mMediaProvider = null;
}
@@ -1422,11 +1401,7 @@ public class MediaScanner
// build file cache so we can look up tracks in the playlist
prescan(null, true);
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
}
FileCacheEntry entry = mFileCache.get(key);
FileEntry entry = makeEntryFor(path);
if (entry != null) {
processPlayList(entry);
}
@@ -1445,6 +1420,37 @@ public class MediaScanner
}
}
FileEntry makeEntryFor(String path) {
String key = path;
String where;
String[] selectionArgs;
if (mCaseInsensitivePaths) {
where = Files.FileColumns.DATA + " LIKE ?";
selectionArgs = new String[] { path };
} else {
where = Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { path };
}
Cursor c = null;
try {
c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, null, null);
if (c.moveToNext()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
return new FileEntry(rowId, path, lastModified, format);
}
} catch (RemoteException e) {
} finally {
if (c != null) {
c.close();
}
}
return null;
}
// returns the number of matching file/directory names, starting from the right
private int matchPaths(String path1, String path2) {
int result = 0;
@@ -1495,26 +1501,37 @@ public class MediaScanner
//FIXME - should we look for "../" within the path?
// best matching MediaFile for the play list entry
FileCacheEntry bestMatch = null;
FileEntry bestMatch = null;
// number of rightmost file/directory names for bestMatch
int bestMatchLength = 0;
Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
while (iterator.hasNext()) {
FileCacheEntry cacheEntry = iterator.next();
String path = cacheEntry.mPath;
Cursor c = null;
try {
c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
null, null, null, null);
} catch (RemoteException e1) {
}
if (path.equalsIgnoreCase(entry)) {
bestMatch = cacheEntry;
break; // don't bother continuing search
}
if (c != null) {
while (c.moveToNext()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
int matchLength = matchPaths(path, entry);
if (matchLength > bestMatchLength) {
bestMatch = cacheEntry;
bestMatchLength = matchLength;
if (path.equalsIgnoreCase(entry)) {
bestMatch = new FileEntry(rowId, path, lastModified, format);
break; // don't bother continuing search
}
int matchLength = matchPaths(path, entry);
if (matchLength > bestMatchLength) {
bestMatch = new FileEntry(rowId, path, lastModified, format);
bestMatchLength = matchLength;
}
}
c.close();
}
if (bestMatch == null) {
@@ -1524,7 +1541,7 @@ public class MediaScanner
try {
// check rowid is set. Rowid may be missing if it is inserted by bulkInsert().
if (bestMatch.mRowId == 0) {
Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
MediaStore.Files.FileColumns.DATA + "=?",
new String[] { bestMatch.mPath }, null, null);
if (c != null) {
@@ -1677,7 +1694,7 @@ public class MediaScanner
}
}
private void processPlayList(FileCacheEntry entry) throws RemoteException {
private void processPlayList(FileEntry entry) throws RemoteException {
String path = entry.mPath;
ContentValues values = new ContentValues();
int lastSlash = path.lastIndexOf('/');
@@ -1728,9 +1745,9 @@ public class MediaScanner
}
private void processPlayLists() throws RemoteException {
Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
Iterator<FileEntry> iterator = mPlayLists.iterator();
while (iterator.hasNext()) {
FileCacheEntry entry = iterator.next();
FileEntry entry = iterator.next();
// only process playlist files if they are new or have been modified since the last scan
if (entry.mLastModifiedChanged) {
processPlayList(entry);