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