1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/* 2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2010 The Android Open Source Project 3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License"); 5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License. 6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at 7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * http://www.apache.org/licenses/LICENSE-2.0 9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software 11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS, 12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and 14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License. 15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.data; 18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 192b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport android.content.ContentValues; 202b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport android.content.Context; 212b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport android.database.Cursor; 222b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport android.database.sqlite.SQLiteDatabase; 232b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport android.database.sqlite.SQLiteOpenHelper; 242b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin 25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.app.GalleryApp; 26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.common.LruCache; 27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.common.Utils; 28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.data.DownloadEntry.Columns; 29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.Future; 30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.FutureListener; 31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.ThreadPool; 32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.ThreadPool.CancelListener; 33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.ThreadPool.Job; 34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.ThreadPool.JobContext; 35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.io.File; 37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.net.URL; 38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.HashMap; 39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.HashSet; 40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class DownloadCache { 42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TAG = "DownloadCache"; 43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MAX_DELETE_COUNT = 16; 44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int LRU_CAPACITY = 4; 45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TABLE_NAME = DownloadEntry.SCHEMA.getTableName(); 47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String QUERY_PROJECTION[] = {Columns.ID, Columns.DATA}; 49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String WHERE_HASH_AND_URL = String.format( 50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin "%s = ? AND %s = ?", Columns.HASH_CODE, Columns.CONTENT_URL); 51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int QUERY_INDEX_ID = 0; 52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int QUERY_INDEX_DATA = 1; 53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String FREESPACE_PROJECTION[] = { 55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Columns.ID, Columns.DATA, Columns.CONTENT_URL, Columns.CONTENT_SIZE}; 56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String FREESPACE_ORDER_BY = 57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String.format("%s ASC", Columns.LAST_ACCESS); 58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int FREESPACE_IDNEX_ID = 0; 59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int FREESPACE_IDNEX_DATA = 1; 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int FREESPACE_INDEX_CONTENT_URL = 2; 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int FREESPACE_INDEX_CONTENT_SIZE = 3; 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String ID_WHERE = Columns.ID + " = ?"; 64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String SUM_PROJECTION[] = 66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin {String.format("sum(%s)", Columns.CONTENT_SIZE)}; 67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int SUM_INDEX_SUM = 0; 68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final LruCache<String, Entry> mEntryMap = 70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin new LruCache<String, Entry>(LRU_CAPACITY); 71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final HashMap<String, DownloadTask> mTaskMap = 72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin new HashMap<String, DownloadTask>(); 73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final File mRoot; 74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final GalleryApp mApplication; 75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final SQLiteDatabase mDatabase; 76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final long mCapacity; 77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private long mTotalBytes = 0; 79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private boolean mInitialized = false; 80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public DownloadCache(GalleryApp application, File root, long capacity) { 82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mRoot = Utils.checkNotNull(root); 83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mApplication = Utils.checkNotNull(application); 84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCapacity = capacity; 85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDatabase = new DatabaseHelper(application.getAndroidContext()) 86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .getWritableDatabase(); 87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Entry findEntryInDatabase(String stringUrl) { 90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long hash = Utils.crc64Long(stringUrl); 91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String whereArgs[] = {String.valueOf(hash), stringUrl}; 92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mDatabase.query(TABLE_NAME, QUERY_PROJECTION, 93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin WHERE_HASH_AND_URL, whereArgs, null, null, null); 94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor.moveToNext()) { 96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin File file = new File(cursor.getString(QUERY_INDEX_DATA)); 97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long id = cursor.getInt(QUERY_INDEX_ID); 98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Entry entry = null; 99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mEntryMap) { 100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin entry = mEntryMap.get(stringUrl); 101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (entry == null) { 102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin entry = new Entry(id, file); 103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mEntryMap.put(stringUrl, entry); 104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return entry; 107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return null; 112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Entry download(JobContext jc, URL url) { 115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!mInitialized) initialize(); 116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String stringUrl = url.toString(); 118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // First find in the entry-pool 120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mEntryMap) { 121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Entry entry = mEntryMap.get(stringUrl); 122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (entry != null) { 123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin updateLastAccess(entry.mId); 124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return entry; 125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Then, find it in database 129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin TaskProxy proxy = new TaskProxy(); 130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mTaskMap) { 131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Entry entry = findEntryInDatabase(stringUrl); 132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (entry != null) { 133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin updateLastAccess(entry.mId); 134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return entry; 135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Finally, we need to download the file .... 138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // First check if we are downloading it now ... 139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin DownloadTask task = mTaskMap.get(stringUrl); 140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (task == null) { // if not, start the download task now 141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin task = new DownloadTask(stringUrl); 142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTaskMap.put(stringUrl, task); 143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin task.mFuture = mApplication.getThreadPool().submit(task, task); 144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin task.addProxy(proxy); 146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return proxy.get(jc); 149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private void updateLastAccess(long id) { 152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ContentValues values = new ContentValues(); 153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); 154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDatabase.update(TABLE_NAME, values, 155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ID_WHERE, new String[] {String.valueOf(id)}); 156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private synchronized void freeSomeSpaceIfNeed(int maxDeleteFileCount) { 159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mTotalBytes <= mCapacity) return; 160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mDatabase.query(TABLE_NAME, 161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin FREESPACE_PROJECTION, null, null, null, null, FREESPACE_ORDER_BY); 162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (maxDeleteFileCount > 0 164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin && mTotalBytes > mCapacity && cursor.moveToNext()) { 165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long id = cursor.getLong(FREESPACE_IDNEX_ID); 166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String url = cursor.getString(FREESPACE_INDEX_CONTENT_URL); 167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long size = cursor.getLong(FREESPACE_INDEX_CONTENT_SIZE); 168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String path = cursor.getString(FREESPACE_IDNEX_DATA); 169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin boolean containsKey; 170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mEntryMap) { 171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin containsKey = mEntryMap.containsKey(url); 172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!containsKey) { 174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin --maxDeleteFileCount; 175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTotalBytes -= size; 176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin new File(path).delete(); 177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDatabase.delete(TABLE_NAME, 178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ID_WHERE, new String[]{String.valueOf(id)}); 179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // skip delete, since it is being used 181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private synchronized long insertEntry(String url, File file) { 189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long size = file.length(); 190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTotalBytes += size; 191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ContentValues values = new ContentValues(); 193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String hashCode = String.valueOf(Utils.crc64Long(url)); 194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values.put(Columns.DATA, file.getAbsolutePath()); 195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values.put(Columns.HASH_CODE, hashCode); 196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values.put(Columns.CONTENT_URL, url); 197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values.put(Columns.CONTENT_SIZE, size); 198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values.put(Columns.LAST_UPDATED, System.currentTimeMillis()); 199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mDatabase.insert(TABLE_NAME, "", values); 200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private synchronized void initialize() { 203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mInitialized) return; 204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mInitialized = true; 205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!mRoot.isDirectory()) mRoot.mkdirs(); 206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!mRoot.isDirectory()) { 207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin throw new RuntimeException("cannot create " + mRoot.getAbsolutePath()); 208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 209f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mDatabase.query( 211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin TABLE_NAME, SUM_PROJECTION, null, null, null, null, null); 212f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTotalBytes = 0; 213f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 214f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor.moveToNext()) { 215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTotalBytes = cursor.getLong(SUM_INDEX_SUM); 216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mTotalBytes > mCapacity) freeSomeSpaceIfNeed(MAX_DELETE_COUNT); 221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 223f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final class DatabaseHelper extends SQLiteOpenHelper { 224f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static final String DATABASE_NAME = "download.db"; 225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static final int DATABASE_VERSION = 2; 226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 227f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public DatabaseHelper(Context context) { 228f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin super(context, DATABASE_NAME, null, DATABASE_VERSION); 229f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 230f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 231f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 232f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void onCreate(SQLiteDatabase db) { 233f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin DownloadEntry.SCHEMA.createTables(db); 234f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Delete old files 235f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (File file : mRoot.listFiles()) { 236f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!file.delete()) { 237f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Log.w(TAG, "fail to remove: " + file.getAbsolutePath()); 238f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 242f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 243f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 244f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin //reset everything 245f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin DownloadEntry.SCHEMA.dropTables(db); 246f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin onCreate(db); 247f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 248f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 249f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 250f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public class Entry { 251f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public File cacheFile; 252f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin protected long mId; 253f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 254f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Entry(long id, File cacheFile) { 255f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mId = id; 256f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin this.cacheFile = Utils.checkNotNull(cacheFile); 257f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 258f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 259f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 260f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private class DownloadTask implements Job<File>, FutureListener<File> { 261f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private HashSet<TaskProxy> mProxySet = new HashSet<TaskProxy>(); 262f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Future<File> mFuture; 263f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final String mUrl; 264f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 265f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public DownloadTask(String url) { 266f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mUrl = Utils.checkNotNull(url); 267f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 268f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 269f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void removeProxy(TaskProxy proxy) { 270f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mTaskMap) { 271f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(mProxySet.remove(proxy)); 272f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mProxySet.isEmpty()) { 273f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mFuture.cancel(); 274f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTaskMap.remove(mUrl); 275f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 276f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 277f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 278f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 279f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // should be used in synchronized block of mDatabase 280f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void addProxy(TaskProxy proxy) { 281f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin proxy.mTask = this; 282f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mProxySet.add(proxy); 283f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 284f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 2857817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void onFutureDone(Future<File> future) { 287f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin File file = future.get(); 288f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long id = 0; 289f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (file != null) { // insert to database 290f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin id = insertEntry(mUrl, file); 291f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 292f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 293f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (future.isCancelled()) { 294f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(mProxySet.isEmpty()); 295f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return; 296f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 297f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 298f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mTaskMap) { 299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Entry entry = null; 300f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (mEntryMap) { 301f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (file != null) { 302f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin entry = new Entry(id, file); 303f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(mEntryMap.put(mUrl, entry) == null); 304f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 305f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 306f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (TaskProxy proxy : mProxySet) { 307f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin proxy.setResult(entry); 308f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 309f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTaskMap.remove(mUrl); 310f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin freeSomeSpaceIfNeed(MAX_DELETE_COUNT); 311f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 312f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 313f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 3147817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 315f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public File run(JobContext jc) { 316f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // TODO: utilize etag 317f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin jc.setMode(ThreadPool.MODE_NETWORK); 318f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin File tempFile = null; 319f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 320f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin URL url = new URL(mUrl); 321f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin tempFile = File.createTempFile("cache", ".tmp", mRoot); 322f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // download from url to tempFile 323f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin jc.setMode(ThreadPool.MODE_NETWORK); 324f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin boolean downloaded = DownloadUtils.requestDownload(jc, url, tempFile); 325f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin jc.setMode(ThreadPool.MODE_NONE); 326f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (downloaded) return tempFile; 327f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } catch (Exception e) { 328f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Log.e(TAG, String.format("fail to download %s", mUrl), e); 329f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 330f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin jc.setMode(ThreadPool.MODE_NONE); 331f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 332f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (tempFile != null) tempFile.delete(); 333f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return null; 334f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 335f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 336f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 337f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static class TaskProxy { 338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private DownloadTask mTask; 339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private boolean mIsCancelled = false; 340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Entry mEntry; 341f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 342f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized void setResult(Entry entry) { 343f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mIsCancelled) return; 344f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mEntry = entry; 345f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin notifyAll(); 346f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 347f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 348f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public synchronized Entry get(JobContext jc) { 349f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin jc.setCancelListener(new CancelListener() { 3507817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 351f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void onCancel() { 352f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mTask.removeProxy(TaskProxy.this); 353f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (TaskProxy.this) { 354f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mIsCancelled = true; 355f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin TaskProxy.this.notifyAll(); 356f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 358f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin }); 359f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (!mIsCancelled && mEntry == null) { 360f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 361f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin wait(); 362f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } catch (InterruptedException e) { 363f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Log.w(TAG, "ignore interrupt", e); 364f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 365f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 366f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin jc.setCancelListener(null); 367f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mEntry; 368f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 369f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 370f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 371