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