Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c28baf55 authored by Marco Nelissen's avatar Marco Nelissen Committed by Android (Google) Code Review
Browse files

Merge "Eliminate scanner file cache"

parents 8b265be1 58ef6890
Loading
Loading
Loading
Loading
+133 −116
Original line number Original line Diff line number Diff line
@@ -62,6 +62,9 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Locale;


import libcore.io.ErrnoException;
import libcore.io.Libcore;

/**
/**
 * Internal service helper that no-one should use directly.
 * 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 final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();


    private static class FileCacheEntry {
    private static class FileEntry {
        long mRowId;
        long mRowId;
        String mPath;
        String mPath;
        long mLastModified;
        long mLastModified;
        int mFormat;
        int mFormat;
        boolean mSeenInFileSystem;
        boolean mLastModifiedChanged;
        boolean mLastModifiedChanged;


        FileCacheEntry(long rowId, String path, long lastModified, int format) {
        FileEntry(long rowId, String path, long lastModified, int format) {
            mRowId = rowId;
            mRowId = rowId;
            mPath = path;
            mPath = path;
            mLastModified = lastModified;
            mLastModified = lastModified;
            mFormat = format;
            mFormat = format;
            mSeenInFileSystem = false;
            mLastModifiedChanged = false;
            mLastModifiedChanged = false;
        }
        }


@@ -373,11 +374,7 @@ public class MediaScanner


    private MediaInserter mMediaInserter;
    private MediaInserter mMediaInserter;


    // hashes file path to FileCacheEntry.
    private ArrayList<FileEntry> mPlayLists;
    // path should be lower case if mCaseInsensitivePaths is true
    private LinkedHashMap<String, FileCacheEntry> mFileCache;

    private ArrayList<FileCacheEntry> mPlayLists;


    private DrmManagerClient mDrmManagerClient = null;
    private DrmManagerClient mDrmManagerClient = null;


@@ -432,7 +429,7 @@ public class MediaScanner
        private int mWidth;
        private int mWidth;
        private int mHeight;
        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) {
                long fileSize, boolean isDirectory, boolean noMedia) {
            mMimeType = mimeType;
            mMimeType = mimeType;
            mFileType = 0;
            mFileType = 0;
@@ -465,11 +462,7 @@ public class MediaScanner
                }
                }
            }
            }


            String key = path;
            FileEntry entry = makeEntryFor(path);
            if (mCaseInsensitivePaths) {
                key = path.toLowerCase();
            }
            FileCacheEntry entry = mFileCache.get(key);
            // add some slack to avoid a rounding error
            // add some slack to avoid a rounding error
            long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
            long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
            boolean wasModified = delta > 1 || delta < -1;
            boolean wasModified = delta > 1 || delta < -1;
@@ -477,13 +470,11 @@ public class MediaScanner
                if (wasModified) {
                if (wasModified) {
                    entry.mLastModified = lastModified;
                    entry.mLastModified = lastModified;
                } else {
                } else {
                    entry = new FileCacheEntry(0, path, lastModified,
                    entry = new FileEntry(0, path, lastModified,
                            (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
                            (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
                    mFileCache.put(key, entry);
                }
                }
                entry.mLastModifiedChanged = true;
                entry.mLastModifiedChanged = true;
            }
            }
            entry.mSeenInFileSystem = true;


            if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
            if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
                mPlayLists.add(entry);
                mPlayLists.add(entry);
@@ -525,7 +516,7 @@ public class MediaScanner
            Uri result = null;
            Uri result = null;
//            long t1 = System.currentTimeMillis();
//            long t1 = System.currentTimeMillis();
            try {
            try {
                FileCacheEntry entry = beginFile(path, mimeType, lastModified,
                FileEntry entry = beginFile(path, mimeType, lastModified,
                        fileSize, isDirectory, noMedia);
                        fileSize, isDirectory, noMedia);
                // rescan for metadata if file was modified since last scan
                // rescan for metadata if file was modified since last scan
                if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
                if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
@@ -778,7 +769,7 @@ public class MediaScanner
            return map;
            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)
                boolean alarms, boolean music, boolean podcasts)
                throws RemoteException {
                throws RemoteException {
            // update database
            // update database
@@ -1028,55 +1019,94 @@ public class MediaScanner
        String where = null;
        String where = null;
        String[] selectionArgs = null;
        String[] selectionArgs = null;


        if (mFileCache == null) {
            mFileCache = new LinkedHashMap<String, FileCacheEntry>();
        } else {
            mFileCache.clear();
        }
        if (mPlayLists == null) {
        if (mPlayLists == null) {
            mPlayLists = new ArrayList<FileCacheEntry>();
            mPlayLists = new ArrayList<FileEntry>();
        } else {
        } else {
            mPlayLists.clear();
            mPlayLists.clear();
        }
        }


        if (filePath != null) {
        if (filePath != null) {
            // query for only one file
            // query for only one file
            where = Files.FileColumns.DATA + "=?";
            where = MediaStore.Files.FileColumns._ID + ">?" +
            selectionArgs = new String[] { filePath };
                " 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
        // Build the list of files from the content provider
        try {
        try {
            if (prescanFiles) {
            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,
                c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
                // we need to query the database in small batches, to avoid problems
                        where, selectionArgs, null, null);
                // with CursorWindow positioning.
                long lastId = Long.MIN_VALUE;
                Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
                mWasEmptyPriorToScan = true;


                while (true) {
                    selectionArgs[0] = "" + lastId;
                    if (c != null) {
                    if (c != null) {
                    mWasEmptyPriorToScan = c.getCount() == 0;
                        c.close();
                        c = null;
                    }
                    c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
                            where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
                    if (c == null) {
                        break;
                    }

                    int num = c.getCount();

                    if (num == 0) {
                        break;
                    }
                    mWasEmptyPriorToScan = false;
                    while (c.moveToNext()) {
                    while (c.moveToNext()) {
                        long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
                        long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
                        String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
                        String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
                        int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
                        int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
                        long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
                        long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
                        lastId = rowId;


                        // Only consider entries with absolute path names.
                        // Only consider entries with absolute path names.
                        // This allows storing URIs in the database without the
                        // This allows storing URIs in the database without the
                        // media scanner removing them.
                        // media scanner removing them.
                        if (path != null && path.startsWith("/")) {
                        if (path != null && path.startsWith("/")) {
                            String key = path;
                            boolean exists = false;
                            if (mCaseInsensitivePaths) {
                            try {
                                key = path.toLowerCase();
                                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,
                                if (!MediaFile.isPlayListFileType(fileType)) {
                                    lastModified, format);
                                    deleter.delete(rowId);
                            mFileCache.put(key, entry);
                                    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) {
            if (c != null) {
                c.close();
                c.close();
            }
            }
            deleter.flush();
        }
        }


        // compute original size of images
        // compute original size of images
@@ -1186,57 +1217,6 @@ public class MediaScanner
    }
    }


    private void postscan(String[] directories) throws RemoteException {
    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.
        // handle playlists last, after we know what media files are on the storage.
        if (mProcessPlaylists) {
        if (mProcessPlaylists) {
@@ -1248,7 +1228,6 @@ public class MediaScanner


        // allow GC to clean up
        // allow GC to clean up
        mPlayLists = null;
        mPlayLists = null;
        mFileCache = null;
        mMediaProvider = null;
        mMediaProvider = null;
    }
    }


@@ -1422,11 +1401,7 @@ public class MediaScanner
                // build file cache so we can look up tracks in the playlist
                // build file cache so we can look up tracks in the playlist
                prescan(null, true);
                prescan(null, true);


                String key = path;
                FileEntry entry = makeEntryFor(path);
                if (mCaseInsensitivePaths) {
                    key = path.toLowerCase();
                }
                FileCacheEntry entry = mFileCache.get(key);
                if (entry != null) {
                if (entry != null) {
                    processPlayList(entry);
                    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
    // returns the number of matching file/directory names, starting from the right
    private int matchPaths(String path1, String path2) {
    private int matchPaths(String path1, String path2) {
        int result = 0;
        int result = 0;
@@ -1495,27 +1501,38 @@ public class MediaScanner
        //FIXME - should we look for "../" within the path?
        //FIXME - should we look for "../" within the path?


        // best matching MediaFile for the play list entry
        // best matching MediaFile for the play list entry
        FileCacheEntry bestMatch = null;
        FileEntry bestMatch = null;


        // number of rightmost file/directory names for bestMatch
        // number of rightmost file/directory names for bestMatch
        int bestMatchLength = 0;
        int bestMatchLength = 0;


        Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
        Cursor c = null;
        while (iterator.hasNext()) {
        try {
            FileCacheEntry cacheEntry = iterator.next();
            c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
            String path = cacheEntry.mPath;
                    null, null, null, null);
        } catch (RemoteException e1) {
        }

        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);


                if (path.equalsIgnoreCase(entry)) {
                if (path.equalsIgnoreCase(entry)) {
                bestMatch = cacheEntry;
                    bestMatch = new FileEntry(rowId, path, lastModified, format);
                    break;    // don't bother continuing search
                    break;    // don't bother continuing search
                }
                }


                int matchLength = matchPaths(path, entry);
                int matchLength = matchPaths(path, entry);
                if (matchLength > bestMatchLength) {
                if (matchLength > bestMatchLength) {
                bestMatch = cacheEntry;
                    bestMatch = new FileEntry(rowId, path, lastModified, format);
                    bestMatchLength = matchLength;
                    bestMatchLength = matchLength;
                }
                }
            }
            }
            c.close();
        }


        if (bestMatch == null) {
        if (bestMatch == null) {
            return false;
            return false;
@@ -1524,7 +1541,7 @@ public class MediaScanner
        try {
        try {
            // check rowid is set. Rowid may be missing if it is inserted by bulkInsert().
            // check rowid is set. Rowid may be missing if it is inserted by bulkInsert().
            if (bestMatch.mRowId == 0) {
            if (bestMatch.mRowId == 0) {
                Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
                c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
                        MediaStore.Files.FileColumns.DATA + "=?",
                        MediaStore.Files.FileColumns.DATA + "=?",
                        new String[] { bestMatch.mPath }, null, null);
                        new String[] { bestMatch.mPath }, null, null);
                if (c != 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;
        String path = entry.mPath;
        ContentValues values = new ContentValues();
        ContentValues values = new ContentValues();
        int lastSlash = path.lastIndexOf('/');
        int lastSlash = path.lastIndexOf('/');
@@ -1728,9 +1745,9 @@ public class MediaScanner
    }
    }


    private void processPlayLists() throws RemoteException {
    private void processPlayLists() throws RemoteException {
        Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
        Iterator<FileEntry> iterator = mPlayLists.iterator();
        while (iterator.hasNext()) {
        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
            // only process playlist files if they are new or have been modified since the last scan
            if (entry.mLastModifiedChanged) {
            if (entry.mLastModifiedChanged) {
                processPlayList(entry);
                processPlayList(entry);