/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.fasterphotos.data; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.provider.BaseColumns; import com.android.fasterphotos.data.MediaRetriever.MediaSize; import java.io.File; class MediaCacheDatabase extends SQLiteOpenHelper { public static final int DB_VERSION = 1; public static final String DB_NAME = "mediacache.db"; /** Internal database table used for the media cache */ public static final String TABLE = "media_cache"; private static interface Columns extends BaseColumns { /** The Content URI of the original image. */ public static final String URI = "uri"; /** MediaSize.getValue() values. */ public static final String MEDIA_SIZE = "media_size"; /** The last time this image was queried. */ public static final String LAST_ACCESS = "last_access"; /** The image size in bytes. */ public static final String SIZE_IN_BYTES = "size"; } static interface Action { void execute(Uri uri, long id, MediaSize size, Object parameter); } private static final String[] PROJECTION_ID = { Columns._ID, }; private static final String[] PROJECTION_CACHED = { Columns._ID, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, }; private static final String[] PROJECTION_CACHE_SIZE = { "SUM(" + Columns.SIZE_IN_BYTES + ")" }; private static final String[] PROJECTION_DELETE_OLD = { Columns._ID, Columns.URI, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, Columns.LAST_ACCESS, }; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE + "(" + Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + Columns.URI + " TEXT NOT NULL," + Columns.MEDIA_SIZE + " INTEGER NOT NULL," + Columns.LAST_ACCESS + " INTEGER NOT NULL," + Columns.SIZE_IN_BYTES + " INTEGER NOT NULL," + "UNIQUE(" + Columns.URI + ", " + Columns.MEDIA_SIZE + "))"; public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE; public static final String WHERE_THUMBNAIL = Columns.MEDIA_SIZE + " = " + MediaSize.Thumbnail.getValue(); public static final String WHERE_NOT_THUMBNAIL = Columns.MEDIA_SIZE + " <> " + MediaSize.Thumbnail.getValue(); public static final String WHERE_CLEAR_CACHE = Columns.LAST_ACCESS + " <= ?"; public static final String WHERE_CLEAR_CACHE_LARGE = WHERE_CLEAR_CACHE + " AND " + WHERE_NOT_THUMBNAIL; static class QueryCacheResults { public QueryCacheResults(long id, int sizeVal) { this.id = id; this.size = MediaSize.fromInteger(sizeVal); } public long id; public MediaSize size; } public MediaCacheDatabase(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP_TABLE); onCreate(db); MediaCache.getInstance().clearCacheDir(); } public Long getCached(Uri uri, MediaSize size) { String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?"; SQLiteDatabase db = getWritableDatabase(); String[] whereArgs = { uri.toString(), String.valueOf(size.getValue()), }; Cursor cursor = db.query(TABLE, PROJECTION_ID, where, whereArgs, null, null, null); Long id = null; if (cursor.moveToNext()) { id = cursor.getLong(0); } cursor.close(); if (id != null) { String[] updateArgs = { id.toString() }; ContentValues values = new ContentValues(); values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); db.beginTransaction(); try { db.update(TABLE, values, Columns._ID + " = ?", updateArgs); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } return id; } public MediaSize executeOnBestCached(Uri uri, MediaSize size, Action action) { String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " < ?"; String orderBy = Columns.MEDIA_SIZE + " DESC"; SQLiteDatabase db = getReadableDatabase(); String[] whereArgs = { uri.toString(), String.valueOf(size.getValue()), }; Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, orderBy); MediaSize bestSize = null; if (cursor.moveToNext()) { long id = cursor.getLong(0); bestSize = MediaSize.fromInteger(cursor.getInt(1)); long fileSize = cursor.getLong(2); action.execute(uri, id, bestSize, fileSize); } cursor.close(); return bestSize; } public long insert(Uri uri, MediaSize size, Action action, File tempFile) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { ContentValues values = new ContentValues(); values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); values.put(Columns.MEDIA_SIZE, size.getValue()); values.put(Columns.URI, uri.toString()); values.put(Columns.SIZE_IN_BYTES, tempFile.length()); long id = db.insert(TABLE, null, values); if (id != -1) { action.execute(uri, id, size, tempFile); db.setTransactionSuccessful(); } return id; } finally { db.endTransaction(); } } public void updateLength(long id, long fileSize) { ContentValues values = new ContentValues(); values.put(Columns.SIZE_IN_BYTES, fileSize); String[] whereArgs = { String.valueOf(id) }; SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { db.update(TABLE, values, Columns._ID + " = ?", whereArgs); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } public void delete(Uri uri, MediaSize size, Action action) { String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?"; String[] whereArgs = { uri.toString(), String.valueOf(size.getValue()), }; deleteRows(uri, where, whereArgs, action); } public void delete(Uri uri, Action action) { String where = Columns.URI + " = ?"; String[] whereArgs = { uri.toString() }; deleteRows(uri, where, whereArgs, action); } private void deleteRows(Uri uri, String where, String[] whereArgs, Action action) { SQLiteDatabase db = getWritableDatabase(); // Make this an atomic operation db.beginTransaction(); Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, null); while (cursor.moveToNext()) { long id = cursor.getLong(0); MediaSize size = MediaSize.fromInteger(cursor.getInt(1)); long length = cursor.getLong(2); action.execute(uri, id, size, length); } cursor.close(); try { db.delete(TABLE, where, whereArgs); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } public void deleteOldCached(boolean includeThumbnails, long deleteSize, Action action) { String where = includeThumbnails ? null : WHERE_NOT_THUMBNAIL; long lastAccess = 0; SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { Cursor cursor = db.query(TABLE, PROJECTION_DELETE_OLD, where, null, null, null, Columns.LAST_ACCESS); while (cursor.moveToNext()) { long id = cursor.getLong(0); String uri = cursor.getString(1); MediaSize size = MediaSize.fromInteger(cursor.getInt(2)); long length = cursor.getLong(3); long imageLastAccess = cursor.getLong(4); if (imageLastAccess != lastAccess && deleteSize < 0) { break; // We've deleted enough. } lastAccess = imageLastAccess; action.execute(Uri.parse(uri), id, size, length); deleteSize -= length; } cursor.close(); String[] whereArgs = { String.valueOf(lastAccess), }; String whereDelete = includeThumbnails ? WHERE_CLEAR_CACHE : WHERE_CLEAR_CACHE_LARGE; db.delete(TABLE, whereDelete, whereArgs); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } public long getCacheSize() { return getCacheSize(null); } public long getThumbnailCacheSize() { return getCacheSize(WHERE_THUMBNAIL); } private long getCacheSize(String where) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(TABLE, PROJECTION_CACHE_SIZE, where, null, null, null, null); long size = -1; if (cursor.moveToNext()) { size = cursor.getLong(0); } cursor.close(); return size; } }