Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2013 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 package com.android.photos.data;
     17 
     18 import android.content.ContentValues;
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.database.sqlite.SQLiteDatabase;
     22 import android.database.sqlite.SQLiteOpenHelper;
     23 import android.net.Uri;
     24 import android.provider.BaseColumns;
     25 
     26 import com.android.photos.data.MediaRetriever.MediaSize;
     27 
     28 import java.io.File;
     29 
     30 class MediaCacheDatabase extends SQLiteOpenHelper {
     31     public static final int DB_VERSION = 1;
     32     public static final String DB_NAME = "mediacache.db";
     33 
     34     /** Internal database table used for the media cache */
     35     public static final String TABLE = "media_cache";
     36 
     37     private static interface Columns extends BaseColumns {
     38         /** The Content URI of the original image. */
     39         public static final String URI = "uri";
     40         /** MediaSize.getValue() values. */
     41         public static final String MEDIA_SIZE = "media_size";
     42         /** The last time this image was queried. */
     43         public static final String LAST_ACCESS = "last_access";
     44         /** The image size in bytes. */
     45         public static final String SIZE_IN_BYTES = "size";
     46     }
     47 
     48     static interface Action {
     49         void execute(Uri uri, long id, MediaSize size, Object parameter);
     50     }
     51 
     52     private static final String[] PROJECTION_ID = {
     53         Columns._ID,
     54     };
     55 
     56     private static final String[] PROJECTION_CACHED = {
     57         Columns._ID, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES,
     58     };
     59 
     60     private static final String[] PROJECTION_CACHE_SIZE = {
     61         "SUM(" + Columns.SIZE_IN_BYTES + ")"
     62     };
     63 
     64     private static final String[] PROJECTION_DELETE_OLD = {
     65         Columns._ID, Columns.URI, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, Columns.LAST_ACCESS,
     66     };
     67 
     68     public static final String CREATE_TABLE = "CREATE TABLE " + TABLE + "("
     69             + Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
     70             + Columns.URI + " TEXT NOT NULL,"
     71             + Columns.MEDIA_SIZE + " INTEGER NOT NULL,"
     72             + Columns.LAST_ACCESS + " INTEGER NOT NULL,"
     73             + Columns.SIZE_IN_BYTES + " INTEGER NOT NULL,"
     74             + "UNIQUE(" + Columns.URI + ", " + Columns.MEDIA_SIZE + "))";
     75 
     76     public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE;
     77 
     78     public static final String WHERE_THUMBNAIL = Columns.MEDIA_SIZE + " = "
     79             + MediaSize.Thumbnail.getValue();
     80 
     81     public static final String WHERE_NOT_THUMBNAIL = Columns.MEDIA_SIZE + " <> "
     82             + MediaSize.Thumbnail.getValue();
     83 
     84     public static final String WHERE_CLEAR_CACHE = Columns.LAST_ACCESS + " <= ?";
     85 
     86     public static final String WHERE_CLEAR_CACHE_LARGE = WHERE_CLEAR_CACHE + " AND "
     87             + WHERE_NOT_THUMBNAIL;
     88 
     89     static class QueryCacheResults {
     90         public QueryCacheResults(long id, int sizeVal) {
     91             this.id = id;
     92             this.size = MediaSize.fromInteger(sizeVal);
     93         }
     94         public long id;
     95         public MediaSize size;
     96     }
     97 
     98     public MediaCacheDatabase(Context context) {
     99         super(context, DB_NAME, null, DB_VERSION);
    100     }
    101 
    102     @Override
    103     public void onCreate(SQLiteDatabase db) {
    104         db.execSQL(CREATE_TABLE);
    105     }
    106 
    107     @Override
    108     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    109         db.execSQL(DROP_TABLE);
    110         onCreate(db);
    111         MediaCache.getInstance().clearCacheDir();
    112     }
    113 
    114     public Long getCached(Uri uri, MediaSize size) {
    115         String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?";
    116         SQLiteDatabase db = getWritableDatabase();
    117         String[] whereArgs = {
    118                 uri.toString(), String.valueOf(size.getValue()),
    119         };
    120         Cursor cursor = db.query(TABLE, PROJECTION_ID, where, whereArgs, null, null, null);
    121         Long id = null;
    122         if (cursor.moveToNext()) {
    123             id = cursor.getLong(0);
    124         }
    125         cursor.close();
    126         if (id != null) {
    127             String[] updateArgs = {
    128                 id.toString()
    129             };
    130             ContentValues values = new ContentValues();
    131             values.put(Columns.LAST_ACCESS, System.currentTimeMillis());
    132             db.beginTransaction();
    133             try {
    134                 db.update(TABLE, values, Columns._ID + " = ?", updateArgs);
    135                 db.setTransactionSuccessful();
    136             } finally {
    137                 db.endTransaction();
    138             }
    139         }
    140         return id;
    141     }
    142 
    143     public MediaSize executeOnBestCached(Uri uri, MediaSize size, Action action) {
    144         String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " < ?";
    145         String orderBy = Columns.MEDIA_SIZE + " DESC";
    146         SQLiteDatabase db = getReadableDatabase();
    147         String[] whereArgs = {
    148                 uri.toString(), String.valueOf(size.getValue()),
    149         };
    150         Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, orderBy);
    151         MediaSize bestSize = null;
    152         if (cursor.moveToNext()) {
    153             long id = cursor.getLong(0);
    154             bestSize = MediaSize.fromInteger(cursor.getInt(1));
    155             long fileSize = cursor.getLong(2);
    156             action.execute(uri, id, bestSize, fileSize);
    157         }
    158         cursor.close();
    159         return bestSize;
    160     }
    161 
    162     public long insert(Uri uri, MediaSize size, Action action, File tempFile) {
    163         SQLiteDatabase db = getWritableDatabase();
    164         db.beginTransaction();
    165         try {
    166             ContentValues values = new ContentValues();
    167             values.put(Columns.LAST_ACCESS, System.currentTimeMillis());
    168             values.put(Columns.MEDIA_SIZE, size.getValue());
    169             values.put(Columns.URI, uri.toString());
    170             values.put(Columns.SIZE_IN_BYTES, tempFile.length());
    171             long id = db.insert(TABLE, null, values);
    172             if (id != -1) {
    173                 action.execute(uri, id, size, tempFile);
    174                 db.setTransactionSuccessful();
    175             }
    176             return id;
    177         } finally {
    178             db.endTransaction();
    179         }
    180     }
    181 
    182     public void updateLength(long id, long fileSize) {
    183         ContentValues values = new ContentValues();
    184         values.put(Columns.SIZE_IN_BYTES, fileSize);
    185         String[] whereArgs = {
    186             String.valueOf(id)
    187         };
    188         SQLiteDatabase db = getWritableDatabase();
    189         db.beginTransaction();
    190         try {
    191             db.update(TABLE, values, Columns._ID + " = ?", whereArgs);
    192             db.setTransactionSuccessful();
    193         } finally {
    194             db.endTransaction();
    195         }
    196     }
    197 
    198     public void delete(Uri uri, MediaSize size, Action action) {
    199         String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?";
    200         String[] whereArgs = {
    201                 uri.toString(), String.valueOf(size.getValue()),
    202         };
    203         deleteRows(uri, where, whereArgs, action);
    204     }
    205 
    206     public void delete(Uri uri, Action action) {
    207         String where = Columns.URI + " = ?";
    208         String[] whereArgs = {
    209             uri.toString()
    210         };
    211         deleteRows(uri, where, whereArgs, action);
    212     }
    213 
    214     private void deleteRows(Uri uri, String where, String[] whereArgs, Action action) {
    215         SQLiteDatabase db = getWritableDatabase();
    216         // Make this an atomic operation
    217         db.beginTransaction();
    218         Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, null);
    219         while (cursor.moveToNext()) {
    220             long id = cursor.getLong(0);
    221             MediaSize size = MediaSize.fromInteger(cursor.getInt(1));
    222             long length = cursor.getLong(2);
    223             action.execute(uri, id, size, length);
    224         }
    225         cursor.close();
    226         try {
    227             db.delete(TABLE, where, whereArgs);
    228             db.setTransactionSuccessful();
    229         } finally {
    230             db.endTransaction();
    231         }
    232     }
    233 
    234     public void deleteOldCached(boolean includeThumbnails, long deleteSize, Action action) {
    235         String where = includeThumbnails ? null : WHERE_NOT_THUMBNAIL;
    236         long lastAccess = 0;
    237         SQLiteDatabase db = getWritableDatabase();
    238         db.beginTransaction();
    239         try {
    240             Cursor cursor = db.query(TABLE, PROJECTION_DELETE_OLD, where, null, null, null,
    241                     Columns.LAST_ACCESS);
    242             while (cursor.moveToNext()) {
    243                 long id = cursor.getLong(0);
    244                 String uri = cursor.getString(1);
    245                 MediaSize size = MediaSize.fromInteger(cursor.getInt(2));
    246                 long length = cursor.getLong(3);
    247                 long imageLastAccess = cursor.getLong(4);
    248 
    249                 if (imageLastAccess != lastAccess && deleteSize < 0) {
    250                     break; // We've deleted enough.
    251                 }
    252                 lastAccess = imageLastAccess;
    253                 action.execute(Uri.parse(uri), id, size, length);
    254                 deleteSize -= length;
    255             }
    256             cursor.close();
    257             String[] whereArgs = {
    258                 String.valueOf(lastAccess),
    259             };
    260             String whereDelete = includeThumbnails ? WHERE_CLEAR_CACHE : WHERE_CLEAR_CACHE_LARGE;
    261             db.delete(TABLE, whereDelete, whereArgs);
    262             db.setTransactionSuccessful();
    263         } finally {
    264             db.endTransaction();
    265         }
    266     }
    267 
    268     public long getCacheSize() {
    269         return getCacheSize(null);
    270     }
    271 
    272     public long getThumbnailCacheSize() {
    273         return getCacheSize(WHERE_THUMBNAIL);
    274     }
    275 
    276     private long getCacheSize(String where) {
    277         SQLiteDatabase db = getReadableDatabase();
    278         Cursor cursor = db.query(TABLE, PROJECTION_CACHE_SIZE, where, null, null, null, null);
    279         long size = -1;
    280         if (cursor.moveToNext()) {
    281             size = cursor.getLong(0);
    282         }
    283         cursor.close();
    284         return size;
    285     }
    286 }
    287