DiskBasedCache.java revision e5a344749f100325cd8ef0d78cd7db5221b5e628
1d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/* 2d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Copyright (C) 2011 The Android Open Source Project 3d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 4d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Licensed under the Apache License, Version 2.0 (the "License"); 5d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * you may not use this file except in compliance with the License. 6d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * You may obtain a copy of the License at 7d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 8d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * http://www.apache.org/licenses/LICENSE-2.0 9d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 10d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Unless required by applicable law or agreed to in writing, software 11d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * distributed under the License is distributed on an "AS IS" BASIS, 12d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * See the License for the specific language governing permissions and 14d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * limitations under the License. 15d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 16d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 17d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupackage com.android.volley.toolbox; 18d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 19d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport android.os.SystemClock; 20d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 21d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.Cache; 22d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.VolleyLog; 23d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 24d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.File; 25d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.FileInputStream; 26d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.FileOutputStream; 27d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.FilterInputStream; 28d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.IOException; 29d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.InputStream; 30d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.ObjectInputStream; 31d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.ObjectOutputStream; 32d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.OutputStream; 33e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queruimport java.util.Collections; 34e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queruimport java.util.HashMap; 35d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.Iterator; 36d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.LinkedHashMap; 37d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.Map; 38d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 39d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/** 40d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Cache implementation that caches files directly onto the hard disk in the specified 41d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * directory. The default disk usage size is 5MB, but is configurable. 42d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 43d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupublic class DiskBasedCache implements Cache { 44d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 45d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Map of the Key, CacheHeader pairs */ 46d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private final Map<String, CacheHeader> mEntries = 47d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru new LinkedHashMap<String, CacheHeader>(16, .75f, true); 48d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 49d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Total amount of space currently used by the cache in bytes. */ 50d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private long mTotalSize = 0; 51d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 52d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The root directory to use for the cache. */ 53d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private final File mRootDirectory; 54d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 55d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The maximum size of the cache in bytes. */ 56d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private final int mMaxCacheSizeInBytes; 57d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 58d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Default maximum disk usage in bytes. */ 59d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; 60d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 61d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** High water mark percentage for the cache */ 62d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static final float HYSTERESIS_FACTOR = 0.9f; 63d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 64d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Current cache version */ 65e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru private static final int CACHE_VERSION = 2; 66d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 67d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 68d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Constructs an instance of the DiskBasedCache at the specified directory. 69d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param rootDirectory The root directory of the cache. 70d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param maxCacheSizeInBytes The maximum size of the cache in bytes. 71d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 72d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { 73d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mRootDirectory = rootDirectory; 74d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mMaxCacheSizeInBytes = maxCacheSizeInBytes; 75d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 76d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 77d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 78d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Constructs an instance of the DiskBasedCache at the specified directory using 79d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * the default maximum cache size of 5MB. 80d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param rootDirectory The root directory of the cache. 81d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 82d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public DiskBasedCache(File rootDirectory) { 83d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); 84d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 85d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 86d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 87d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Clears the cache. Deletes all cached files from disk. 88d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 89d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 90d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void clear() { 91d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File[] files = mRootDirectory.listFiles(); 92d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (files != null) { 93d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (File file : files) { 94d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru file.delete(); 95d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 96d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 97d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.clear(); 98d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize = 0; 99d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Cache cleared."); 100d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 101d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 102d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 103d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Returns the cache entry with the specified key if it exists, null otherwise. 104d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 105d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 106d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized Entry get(String key) { 107d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = mEntries.get(key); 108d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // if the entry does not exist, return. 109d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry == null) { 110d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 111d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 112d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 113d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File file = getFileForKey(key); 114d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CountingInputStream cis = null; 115d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 116d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru cis = new CountingInputStream(new FileInputStream(file)); 117d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader.readHeader(cis); // eat header 118d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); 119d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry.toCacheEntry(data); 120d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 121d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); 122d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru remove(key); 123d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 124d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } finally { 125d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (cis != null) { 126d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 127d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru cis.close(); 128d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException ioe) { 129d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 130d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 131d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 132d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 133d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 134d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 135d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 136d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Initializes the DiskBasedCache by scanning for all files currently in the 137445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick * specified root directory. Creates the root directory if necessary. 138d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 139d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 140d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void initialize() { 141445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick if (!mRootDirectory.exists()) { 142445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick if (!mRootDirectory.mkdirs()) { 143445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); 144445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick } 145445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick return; 146445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick } 147445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick 148d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File[] files = mRootDirectory.listFiles(); 149d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (files == null) { 150d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 151d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 152d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (File file : files) { 153d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru FileInputStream fis = null; 154d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 155d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fis = new FileInputStream(file); 156d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = CacheHeader.readHeader(fis); 157d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.size = file.length(); 158d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru putEntry(entry.key, entry); 159d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 160d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (file != null) { 161d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru file.delete(); 162d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 163d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } finally { 164d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 165d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (fis != null) { 166d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fis.close(); 167d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 168d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException ignored) { } 169d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 170d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 171d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 172d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 173d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 174d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Invalidates an entry in the cache. 175d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key Cache key 176d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param fullExpire True to fully expire the entry, false to soft expire 177d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 178d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 179d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void invalidate(String key, boolean fullExpire) { 180d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Entry entry = get(key); 181d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry != null) { 182d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.softTtl = 0; 183d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (fullExpire) { 184d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.ttl = 0; 185d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 186d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru put(key, entry); 187d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 188d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 189d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 190d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 191d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 192d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Puts the entry with the specified key into the cache. 193d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 194d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 195d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void put(String key, Entry entry) { 196d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru pruneIfNeeded(entry.data.length); 197d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File file = getFileForKey(key); 198d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 199d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru FileOutputStream fos = new FileOutputStream(file); 200d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader e = new CacheHeader(key, entry); 201d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.writeHeader(fos); 202d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fos.write(entry.data); 203d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fos.close(); 204d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru putEntry(key, e); 205d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 206d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 207d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 208d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = file.delete(); 209d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!deleted) { 210d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); 211d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 212d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 213d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 214d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 215d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Removes the specified key from the cache if it exists. 216d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 217d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 218d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void remove(String key) { 219d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = getFileForKey(key).delete(); 220d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru removeEntry(key); 221d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!deleted) { 222d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 223d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru key, getFilenameForKey(key)); 224d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 225d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 226d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 227d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 228d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Creates a pseudo-unique filename for the specified cache key. 229d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key to generate a file name for. 230d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @return A pseudo-unique filename. 231d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 232d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private String getFilenameForKey(String key) { 233d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int firstHalfLength = key.length() / 2; 234d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); 235d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); 236d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return localFilename; 237d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 238d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 239d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 240d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Returns a file object for the given cache key. 241d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 242d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public File getFileForKey(String key) { 243d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return new File(mRootDirectory, getFilenameForKey(key)); 244d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 245d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 246d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 247d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Prunes the cache to fit the amount of bytes specified. 248d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param neededSpace The amount of bytes we are trying to fit into the cache. 249d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 250d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void pruneIfNeeded(int neededSpace) { 251d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { 252d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 253d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 254d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (VolleyLog.DEBUG) { 255d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.v("Pruning old cache entries."); 256d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 257d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 258d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long before = mTotalSize; 259d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int prunedFiles = 0; 260d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long startTime = SystemClock.elapsedRealtime(); 261d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 262d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); 263d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru while (iterator.hasNext()) { 264d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Map.Entry<String, CacheHeader> entry = iterator.next(); 265d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader e = entry.getValue(); 266d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = getFileForKey(e.key).delete(); 267d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (deleted) { 268d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize -= e.size; 269d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else { 270d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 271d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.key, getFilenameForKey(e.key)); 272d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 273d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru iterator.remove(); 274d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru prunedFiles++; 275d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 276d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { 277d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru break; 278d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 279d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 280d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 281d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (VolleyLog.DEBUG) { 282d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.v("pruned %d files, %d bytes, %d ms", 283d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); 284d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 285d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 286d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 287d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 288d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Puts the entry with the specified key into the cache. 289d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key to identify the entry by. 290d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param entry The entry to cache. 291d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 292d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void putEntry(String key, CacheHeader entry) { 293d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!mEntries.containsKey(key)) { 294d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize += entry.size; 295d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else { 296d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader oldEntry = mEntries.get(key); 297d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize += (entry.size - oldEntry.size); 298d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 299d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.put(key, entry); 300d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 301d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 302d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 303d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Removes the entry identified by 'key' from the cache. 304d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 305d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void removeEntry(String key) { 306d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = mEntries.get(key); 307d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry != null) { 308d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize -= entry.size; 309d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.remove(key); 310d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 311d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 312d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 313d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 314d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Reads the contents of an InputStream into a byte[]. 315d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * */ 316d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static byte[] streamToBytes(InputStream in, int length) throws IOException { 317d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru byte[] bytes = new byte[length]; 318d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int count; 319d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int pos = 0; 320d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { 321d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru pos += count; 322d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 323d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pos != length) { 324d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); 325d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 326d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return bytes; 327d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 328d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 329d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 330d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Handles holding onto the cache headers for an entry. 331d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 332e5a344749f100325cd8ef0d78cd7db5221b5e628Ficus Kirkpatrick // Visible for testing. 333e5a344749f100325cd8ef0d78cd7db5221b5e628Ficus Kirkpatrick static class CacheHeader { 334d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The size of the data identified by this CacheHeader. (This is not 335d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * serialized to disk. */ 336d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long size; 337d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 338d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The key that identifies the cache entry. */ 339d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public String key; 340d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 341d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** ETag for cache coherence. */ 342d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public String etag; 343d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 344d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Date of this response as reported by the server. */ 345d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long serverDate; 346d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 347d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** TTL for this record. */ 348d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long ttl; 349d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 350d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Soft TTL for this record. */ 351d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long softTtl; 352d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 353e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru /** Headers from the response resulting in this cache entry. */ 354e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru public Map<String, String> responseHeaders; 355e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 356d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private CacheHeader() { } 357d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 358d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 359d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Instantiates a new CacheHeader object 360d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key that identifies the cache entry 361d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param entry The cache entry. 362d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 363d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public CacheHeader(String key, Entry entry) { 364d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.key = key; 365d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.size = entry.data.length; 366d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.etag = entry.etag; 367d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.serverDate = entry.serverDate; 368d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.ttl = entry.ttl; 369d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.softTtl = entry.softTtl; 370e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru this.responseHeaders = entry.responseHeaders; 371d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 372d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 373d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 374d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Reads the header off of an InputStream and returns a CacheHeader object. 375d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param is The InputStream to read from. 376d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @throws IOException 377d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 378d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static CacheHeader readHeader(InputStream is) throws IOException { 379d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = new CacheHeader(); 380d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru ObjectInputStream ois = new ObjectInputStream(is); 381d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int version = ois.readByte(); 382d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (version != CACHE_VERSION) { 383d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // don't bother deleting, it'll get pruned eventually 384d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru throw new IOException(); 385d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 386d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.key = ois.readUTF(); 387d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.etag = ois.readUTF(); 388d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry.etag.equals("")) { 389d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.etag = null; 390d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 391d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.serverDate = ois.readLong(); 392d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.ttl = ois.readLong(); 393d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.softTtl = ois.readLong(); 394e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru entry.responseHeaders = readStringStringMap(ois); 395d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry; 396d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 397d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 398d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 399d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Creates a cache entry for the specified data. 400d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 401d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public Entry toCacheEntry(byte[] data) { 402d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Entry e = new Entry(); 403d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.data = data; 404d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.etag = etag; 405d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.serverDate = serverDate; 406d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.ttl = ttl; 407d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.softTtl = softTtl; 408e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru e.responseHeaders = responseHeaders; 409d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return e; 410d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 411d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 412d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 413d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Writes the contents of this CacheHeader to the specified OutputStream. 414d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 415d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public boolean writeHeader(OutputStream os) { 416d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 417d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru ObjectOutputStream oos = new ObjectOutputStream(os); 418d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.writeByte(CACHE_VERSION); 419d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.writeUTF(key); 420d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.writeUTF(etag == null ? "" : etag); 421d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.writeLong(serverDate); 422d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.writeLong(ttl); 423d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.writeLong(softTtl); 424e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru writeStringStringMap(responseHeaders, oos); 425d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru oos.flush(); 426d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return true; 427d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 428d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("%s", e.toString()); 429d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return false; 430d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 431d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 432e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 433e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru /** 434e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru * Writes all entries of {@code map} into {@code oos}. 435e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru */ 436e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru private static void writeStringStringMap(Map<String, String> map, ObjectOutputStream oos) 437e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru throws IOException { 438e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru if (map != null) { 439e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru oos.writeInt(map.size()); 440e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru for (Map.Entry<String, String> entry : map.entrySet()) { 441e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru oos.writeUTF(entry.getKey()); 442e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru oos.writeUTF(entry.getValue()); 443e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru } 444e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru } else { 445e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru oos.writeInt(0); 446e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru } 447e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru } 448e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 449e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru /** 450e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru * @return a string to string map which contains the entries read from {@code ois} 451e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru * previously written by {@link #writeStringStringMap} 452e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru */ 453e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru private static Map<String, String> readStringStringMap(ObjectInputStream ois) 454e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru throws IOException { 455e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru int size = ois.readInt(); 456e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru Map<String, String> result = (size == 0) 457e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru ? Collections.<String, String>emptyMap() 458e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru : new HashMap<String, String>(size); 459e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru for (int i = 0; i < size; i++) { 460ab6ea086d1bfe6387c35270d48be1a5c0567d7d3David Braun String key = ois.readUTF().intern(); 461ab6ea086d1bfe6387c35270d48be1a5c0567d7d3David Braun String value = ois.readUTF().intern(); 462e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru result.put(key, value); 463e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru } 464e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru return result; 465e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru } 466d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 467d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 468d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static class CountingInputStream extends FilterInputStream { 469d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private int bytesRead = 0; 470d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 471d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private CountingInputStream(InputStream in) { 472d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru super(in); 473d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 474d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 475d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 476d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public int read() throws IOException { 477d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int result = super.read(); 478d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (result != -1) { 479d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru bytesRead++; 480d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 481d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return result; 482d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 483d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 484d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 485d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public int read(byte[] buffer, int offset, int count) throws IOException { 486d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int result = super.read(buffer, offset, count); 487d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (result != -1) { 488d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru bytesRead += result; 489d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 490d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return result; 491d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 492d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 493d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru} 494