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 24b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrickimport java.io.EOFException; 25d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.File; 26d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.FileInputStream; 27d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.FileOutputStream; 28d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.FilterInputStream; 29d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.IOException; 30d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.InputStream; 31d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.io.OutputStream; 32e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queruimport java.util.Collections; 33e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queruimport java.util.HashMap; 34d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.Iterator; 35d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.LinkedHashMap; 36d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.Map; 37d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 38d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/** 39d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Cache implementation that caches files directly onto the hard disk in the specified 40d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * directory. The default disk usage size is 5MB, but is configurable. 41d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 42d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupublic class DiskBasedCache implements Cache { 43d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 44d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Map of the Key, CacheHeader pairs */ 45d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private final Map<String, CacheHeader> mEntries = 46d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru new LinkedHashMap<String, CacheHeader>(16, .75f, true); 47d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 48d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Total amount of space currently used by the cache in bytes. */ 49d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private long mTotalSize = 0; 50d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 51d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The root directory to use for the cache. */ 52d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private final File mRootDirectory; 53d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 54d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The maximum size of the cache in bytes. */ 55d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private final int mMaxCacheSizeInBytes; 56d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 57d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Default maximum disk usage in bytes. */ 58d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; 59d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 60d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** High water mark percentage for the cache */ 61d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static final float HYSTERESIS_FACTOR = 0.9f; 62d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 63b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick /** Magic number for current version of cache file format. */ 64b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick private static final int CACHE_MAGIC = 0x20120504; 65d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 66d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 67d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Constructs an instance of the DiskBasedCache at the specified directory. 68d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param rootDirectory The root directory of the cache. 69d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param maxCacheSizeInBytes The maximum size of the cache in bytes. 70d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 71d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { 72d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mRootDirectory = rootDirectory; 73d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mMaxCacheSizeInBytes = maxCacheSizeInBytes; 74d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 75d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 76d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 77d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Constructs an instance of the DiskBasedCache at the specified directory using 78d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * the default maximum cache size of 5MB. 79d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param rootDirectory The root directory of the cache. 80d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 81d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public DiskBasedCache(File rootDirectory) { 82d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); 83d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 84d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 85d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 86d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Clears the cache. Deletes all cached files from disk. 87d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 88d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 89d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void clear() { 90d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File[] files = mRootDirectory.listFiles(); 91d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (files != null) { 92d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (File file : files) { 93d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru file.delete(); 94d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 95d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 96d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.clear(); 97d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize = 0; 98d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Cache cleared."); 99d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 100d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 101d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 102d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Returns the cache entry with the specified key if it exists, null otherwise. 103d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 104d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 105d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized Entry get(String key) { 106d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = mEntries.get(key); 107d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // if the entry does not exist, return. 108d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry == null) { 109d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 110d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 111d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 112d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File file = getFileForKey(key); 113d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CountingInputStream cis = null; 114d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 115d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru cis = new CountingInputStream(new FileInputStream(file)); 116d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader.readHeader(cis); // eat header 117d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); 118d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry.toCacheEntry(data); 119d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 120d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); 121d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru remove(key); 122d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 123d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } finally { 124d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (cis != null) { 125d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 126d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru cis.close(); 127d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException ioe) { 128d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 129d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 130d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 131d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 132d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 133d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 134d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 135d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Initializes the DiskBasedCache by scanning for all files currently in the 136445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick * specified root directory. Creates the root directory if necessary. 137d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 138d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 139d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void initialize() { 140445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick if (!mRootDirectory.exists()) { 141445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick if (!mRootDirectory.mkdirs()) { 142445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); 143445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick } 144445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick return; 145445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick } 146445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick 147d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File[] files = mRootDirectory.listFiles(); 148d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (files == null) { 149d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 150d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 151d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (File file : files) { 152d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru FileInputStream fis = null; 153d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 154d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fis = new FileInputStream(file); 155d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = CacheHeader.readHeader(fis); 156d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.size = file.length(); 157d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru putEntry(entry.key, entry); 158d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 159d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (file != null) { 160d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru file.delete(); 161d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 162d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } finally { 163d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 164d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (fis != null) { 165d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fis.close(); 166d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 167d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException ignored) { } 168d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 169d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 170d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 171d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 172d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 173d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Invalidates an entry in the cache. 174d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key Cache key 175d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param fullExpire True to fully expire the entry, false to soft expire 176d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 177d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 178d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void invalidate(String key, boolean fullExpire) { 179d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Entry entry = get(key); 180d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry != null) { 181d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.softTtl = 0; 182d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (fullExpire) { 183d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.ttl = 0; 184d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 185d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru put(key, entry); 186d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 187d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 188d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 189d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 190d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 191d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Puts the entry with the specified key into the cache. 192d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 193d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 194d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void put(String key, Entry entry) { 195d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru pruneIfNeeded(entry.data.length); 196d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File file = getFileForKey(key); 197d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 198d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru FileOutputStream fos = new FileOutputStream(file); 199d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader e = new CacheHeader(key, entry); 200d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.writeHeader(fos); 201d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fos.write(entry.data); 202d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fos.close(); 203d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru putEntry(key, e); 204d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 205d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 206d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 207d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = file.delete(); 208d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!deleted) { 209d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); 210d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 211d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 212d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 213d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 214d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Removes the specified key from the cache if it exists. 215d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 216d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 217d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void remove(String key) { 218d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = getFileForKey(key).delete(); 219d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru removeEntry(key); 220d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!deleted) { 221d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 222d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru key, getFilenameForKey(key)); 223d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 224d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 225d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 226d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 227d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Creates a pseudo-unique filename for the specified cache key. 228d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key to generate a file name for. 229d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @return A pseudo-unique filename. 230d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 231d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private String getFilenameForKey(String key) { 232d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int firstHalfLength = key.length() / 2; 233d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); 234d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); 235d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return localFilename; 236d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 237d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 238d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 239d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Returns a file object for the given cache key. 240d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 241d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public File getFileForKey(String key) { 242d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return new File(mRootDirectory, getFilenameForKey(key)); 243d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 244d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 245d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 246d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Prunes the cache to fit the amount of bytes specified. 247d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param neededSpace The amount of bytes we are trying to fit into the cache. 248d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 249d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void pruneIfNeeded(int neededSpace) { 250d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { 251d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 252d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 253d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (VolleyLog.DEBUG) { 254d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.v("Pruning old cache entries."); 255d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 256d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 257d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long before = mTotalSize; 258d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int prunedFiles = 0; 259d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long startTime = SystemClock.elapsedRealtime(); 260d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 261d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); 262d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru while (iterator.hasNext()) { 263d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Map.Entry<String, CacheHeader> entry = iterator.next(); 264d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader e = entry.getValue(); 265d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = getFileForKey(e.key).delete(); 266d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (deleted) { 267d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize -= e.size; 268d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else { 269d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 270d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.key, getFilenameForKey(e.key)); 271d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 272d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru iterator.remove(); 273d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru prunedFiles++; 274d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 275d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { 276d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru break; 277d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 278d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 279d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 280d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (VolleyLog.DEBUG) { 281d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.v("pruned %d files, %d bytes, %d ms", 282d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); 283d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 284d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 285d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 286d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 287d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Puts the entry with the specified key into the cache. 288d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key to identify the entry by. 289d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param entry The entry to cache. 290d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 291d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void putEntry(String key, CacheHeader entry) { 292d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!mEntries.containsKey(key)) { 293d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize += entry.size; 294d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else { 295d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader oldEntry = mEntries.get(key); 296d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize += (entry.size - oldEntry.size); 297d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 298d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.put(key, entry); 299d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 300d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 301d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 302d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Removes the entry identified by 'key' from the cache. 303d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 304d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void removeEntry(String key) { 305d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = mEntries.get(key); 306d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry != null) { 307d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize -= entry.size; 308d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.remove(key); 309d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 310d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 311d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 312d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 313d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Reads the contents of an InputStream into a byte[]. 314d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * */ 315d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static byte[] streamToBytes(InputStream in, int length) throws IOException { 316d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru byte[] bytes = new byte[length]; 317d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int count; 318d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int pos = 0; 319d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { 320d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru pos += count; 321d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 322d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pos != length) { 323d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); 324d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 325d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return bytes; 326d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 327d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 328d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 329d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Handles holding onto the cache headers for an entry. 330d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 331e5a344749f100325cd8ef0d78cd7db5221b5e628Ficus Kirkpatrick // Visible for testing. 332e5a344749f100325cd8ef0d78cd7db5221b5e628Ficus Kirkpatrick static class CacheHeader { 333d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The size of the data identified by this CacheHeader. (This is not 334d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * serialized to disk. */ 335d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long size; 336d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 337d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The key that identifies the cache entry. */ 338d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public String key; 339d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 340d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** ETag for cache coherence. */ 341d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public String etag; 342d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 343d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Date of this response as reported by the server. */ 344d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long serverDate; 345d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 346d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** TTL for this record. */ 347d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long ttl; 348d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 349d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Soft TTL for this record. */ 350d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long softTtl; 351d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 352e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru /** Headers from the response resulting in this cache entry. */ 353e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru public Map<String, String> responseHeaders; 354e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 355d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private CacheHeader() { } 356d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 357d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 358d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Instantiates a new CacheHeader object 359d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key that identifies the cache entry 360d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param entry The cache entry. 361d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 362d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public CacheHeader(String key, Entry entry) { 363d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.key = key; 364d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.size = entry.data.length; 365d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.etag = entry.etag; 366d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.serverDate = entry.serverDate; 367d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.ttl = entry.ttl; 368d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.softTtl = entry.softTtl; 369e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru this.responseHeaders = entry.responseHeaders; 370d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 371d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 372d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 373d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Reads the header off of an InputStream and returns a CacheHeader object. 374d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param is The InputStream to read from. 375d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @throws IOException 376d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 377d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static CacheHeader readHeader(InputStream is) throws IOException { 378d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = new CacheHeader(); 379b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int magic = readInt(is); 380b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick if (magic != CACHE_MAGIC) { 381d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // don't bother deleting, it'll get pruned eventually 382d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru throw new IOException(); 383d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 384b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.key = readString(is); 385b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.etag = readString(is); 386d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry.etag.equals("")) { 387d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.etag = null; 388d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 389b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.serverDate = readLong(is); 390b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.ttl = readLong(is); 391b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.softTtl = readLong(is); 392b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.responseHeaders = readStringStringMap(is); 393d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry; 394d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 395d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 396d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 397d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Creates a cache entry for the specified data. 398d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 399d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public Entry toCacheEntry(byte[] data) { 400d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Entry e = new Entry(); 401d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.data = data; 402d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.etag = etag; 403d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.serverDate = serverDate; 404d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.ttl = ttl; 405d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.softTtl = softTtl; 406e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru e.responseHeaders = responseHeaders; 407d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return e; 408d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 409d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 410b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 411d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 412d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Writes the contents of this CacheHeader to the specified OutputStream. 413d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 414d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public boolean writeHeader(OutputStream os) { 415d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 416b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeInt(os, CACHE_MAGIC); 417b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, key); 418b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, etag == null ? "" : etag); 419b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, serverDate); 420b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, ttl); 421b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, softTtl); 422b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeStringStringMap(responseHeaders, os); 423b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.flush(); 424d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return true; 425d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 426d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("%s", e.toString()); 427d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return false; 428d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 429d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 430e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 431d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 432d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 433d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static class CountingInputStream extends FilterInputStream { 434d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private int bytesRead = 0; 435d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 436d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private CountingInputStream(InputStream in) { 437d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru super(in); 438d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 439d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 440d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 441d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public int read() throws IOException { 442d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int result = super.read(); 443d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (result != -1) { 444d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru bytesRead++; 445d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 446d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return result; 447d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 448d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 449d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 450d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public int read(byte[] buffer, int offset, int count) throws IOException { 451d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int result = super.read(buffer, offset, count); 452d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (result != -1) { 453d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru bytesRead += result; 454d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 455d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return result; 456d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 457d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 458b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 459b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick /* 460b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * Homebrewed simple serialization system used for reading and writing cache 461b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * headers on disk. Once upon a time, this used the standard Java 462b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * Object{Input,Output}Stream, but the default implementation relies heavily 463b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * on reflection (even for standard types) and generates a ton of garbage. 464b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick */ 465b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 466b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick /** 467b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * Simple wrapper around {@link InputStream#read()} that throws EOFException 468b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * instead of returning -1. 469b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick */ 470b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick private static int read(InputStream is) throws IOException { 471b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int b = is.read(); 472b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick if (b == -1) { 473b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick throw new EOFException(); 474b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 475b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return b; 476b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 477b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 478b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeInt(OutputStream os, int n) throws IOException { 479b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 0) & 0xff); 480b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 8) & 0xff); 481b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 16) & 0xff); 482b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 24) & 0xff); 483b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 484b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 485b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static int readInt(InputStream is) throws IOException { 486b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int n = 0; 487b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 0); 488b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 8); 489b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 16); 490b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 24); 491b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return n; 492b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 493b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 494b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeLong(OutputStream os, long n) throws IOException { 495b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 0)); 496b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 8)); 497b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 16)); 498b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 24)); 499b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 32)); 500b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 40)); 501b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 48)); 502b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 56)); 503b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 504b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 505b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static long readLong(InputStream is) throws IOException { 506b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick long n = 0; 507b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 0); 508b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 8); 509b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 16); 510b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 24); 511b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 32); 512b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 40); 513b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 48); 514b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 56); 515b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return n; 516b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 517b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 518b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeString(OutputStream os, String s) throws IOException { 519b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick byte[] b = s.getBytes("UTF-8"); 520b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, b.length); 521b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write(b, 0, b.length); 522b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 523b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 524b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static String readString(InputStream is) throws IOException { 525b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int n = (int) readLong(is); 526b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick byte[] b = streamToBytes(is, n); 527b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return new String(b, "UTF-8"); 528b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 529b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 530b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException { 531b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick if (map != null) { 532b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeInt(os, map.size()); 533b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick for (Map.Entry<String, String> entry : map.entrySet()) { 534b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, entry.getKey()); 535b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, entry.getValue()); 536b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 537b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } else { 538b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeInt(os, 0); 539b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 540b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 541b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 542b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static Map<String, String> readStringStringMap(InputStream is) throws IOException { 543b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int size = readInt(is); 544b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick Map<String, String> result = (size == 0) 545b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick ? Collections.<String, String>emptyMap() 546b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick : new HashMap<String, String>(size); 547b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick for (int i = 0; i < size; i++) { 548b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick String key = readString(is).intern(); 549b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick String value = readString(is).intern(); 550b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick result.put(key, value); 551b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 552b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return result; 553b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 554b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 555b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 556d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru} 557