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()); 12386c71b8458444dfcbf2c3919961b86bc92f65657Shen Lin remove(key); 12486c71b8458444dfcbf2c3919961b86bc92f65657Shen Lin return null; 12586c71b8458444dfcbf2c3919961b86bc92f65657Shen Lin } catch (NegativeArraySizeException e) { 12686c71b8458444dfcbf2c3919961b86bc92f65657Shen Lin VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); 127d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru remove(key); 128d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 129d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } finally { 130d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (cis != null) { 131d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 132d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru cis.close(); 133d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException ioe) { 134d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 135d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 136d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 137d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 138d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 139d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 140d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 141d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Initializes the DiskBasedCache by scanning for all files currently in the 142445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick * specified root directory. Creates the root directory if necessary. 143d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 144d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 145d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void initialize() { 146445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick if (!mRootDirectory.exists()) { 147445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick if (!mRootDirectory.mkdirs()) { 148445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); 149445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick } 150445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick return; 151445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick } 152445ed3471fb55abae917324375b30c8a677e5d0bFicus Kirkpatrick 153d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File[] files = mRootDirectory.listFiles(); 154d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (files == null) { 155d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 156d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 157d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (File file : files) { 158aab3ca2670c2a9ad888085ce8f1b34c6c9fc50a3Fabian Frank BufferedInputStream fis = null; 159d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 160aab3ca2670c2a9ad888085ce8f1b34c6c9fc50a3Fabian Frank fis = new BufferedInputStream(new FileInputStream(file)); 161d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = CacheHeader.readHeader(fis); 162d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.size = file.length(); 163d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru putEntry(entry.key, entry); 164d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 165d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (file != null) { 166d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru file.delete(); 167d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 168d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } finally { 169d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 170d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (fis != null) { 171d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fis.close(); 172d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 173d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException ignored) { } 174d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 175d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 176d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 177d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 178d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 179d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Invalidates an entry in the cache. 180d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key Cache key 181d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param fullExpire True to fully expire the entry, false to soft expire 182d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 183d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 184d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void invalidate(String key, boolean fullExpire) { 185d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Entry entry = get(key); 186d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry != null) { 187d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.softTtl = 0; 188d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (fullExpire) { 189d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.ttl = 0; 190d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 191d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru put(key, entry); 192d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 193d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 194d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 195d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 196d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 197d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Puts the entry with the specified key into the cache. 198d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 199d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 200d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void put(String key, Entry entry) { 201d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru pruneIfNeeded(entry.data.length); 202d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru File file = getFileForKey(key); 203d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 20419c4ec0b8f26bc140273d6a8c3ee04a69cede504Ficus Kirkpatrick BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); 205d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader e = new CacheHeader(key, entry); 2065bd5325bd61414480091b262204f1a45d57c1884Aurash Mahbod boolean success = e.writeHeader(fos); 2075bd5325bd61414480091b262204f1a45d57c1884Aurash Mahbod if (!success) { 2085bd5325bd61414480091b262204f1a45d57c1884Aurash Mahbod fos.close(); 2095bd5325bd61414480091b262204f1a45d57c1884Aurash Mahbod VolleyLog.d("Failed to write header for %s", file.getAbsolutePath()); 2105bd5325bd61414480091b262204f1a45d57c1884Aurash Mahbod throw new IOException(); 2115bd5325bd61414480091b262204f1a45d57c1884Aurash Mahbod } 212d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fos.write(entry.data); 213d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru fos.close(); 214d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru putEntry(key, e); 215d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 216d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 217d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 218d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = file.delete(); 219d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!deleted) { 220d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); 221d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 222d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 223d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 224d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 225d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Removes the specified key from the cache if it exists. 226d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 227d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 228d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public synchronized void remove(String key) { 229d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = getFileForKey(key).delete(); 230d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru removeEntry(key); 231d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!deleted) { 232d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 233d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru key, getFilenameForKey(key)); 234d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 235d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 236d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 237d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 238d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Creates a pseudo-unique filename for the specified cache key. 239d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key to generate a file name for. 240d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @return A pseudo-unique filename. 241d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 242d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private String getFilenameForKey(String key) { 243d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int firstHalfLength = key.length() / 2; 244d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); 245d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); 246d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return localFilename; 247d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 248d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 249d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 250d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Returns a file object for the given cache key. 251d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 252d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public File getFileForKey(String key) { 253d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return new File(mRootDirectory, getFilenameForKey(key)); 254d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 255d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 256d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 257d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Prunes the cache to fit the amount of bytes specified. 258d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param neededSpace The amount of bytes we are trying to fit into the cache. 259d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 260d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void pruneIfNeeded(int neededSpace) { 261d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { 262d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return; 263d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 264d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (VolleyLog.DEBUG) { 265d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.v("Pruning old cache entries."); 266d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 267d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 268d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long before = mTotalSize; 269d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int prunedFiles = 0; 270d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long startTime = SystemClock.elapsedRealtime(); 271d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 272d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); 273d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru while (iterator.hasNext()) { 274d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Map.Entry<String, CacheHeader> entry = iterator.next(); 275d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader e = entry.getValue(); 276d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean deleted = getFileForKey(e.key).delete(); 277d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (deleted) { 278d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize -= e.size; 279d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else { 280d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 281d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.key, getFilenameForKey(e.key)); 282d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 283d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru iterator.remove(); 284d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru prunedFiles++; 285d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 286d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { 287d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru break; 288d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 289d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 290d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 291d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (VolleyLog.DEBUG) { 292d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.v("pruned %d files, %d bytes, %d ms", 293d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); 294d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 295d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 296d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 297d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 298d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Puts the entry with the specified key into the cache. 299d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key to identify the entry by. 300d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param entry The entry to cache. 301d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 302d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void putEntry(String key, CacheHeader entry) { 303d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (!mEntries.containsKey(key)) { 304d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize += entry.size; 305d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else { 306d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader oldEntry = mEntries.get(key); 307d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize += (entry.size - oldEntry.size); 308d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 309d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.put(key, entry); 310d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 311d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 312d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 313d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Removes the entry identified by 'key' from the cache. 314d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 315d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private void removeEntry(String key) { 316d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = mEntries.get(key); 317d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry != null) { 318d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mTotalSize -= entry.size; 319d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru mEntries.remove(key); 320d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 321d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 322d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 323d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 324d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Reads the contents of an InputStream into a byte[]. 325d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * */ 326d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static byte[] streamToBytes(InputStream in, int length) throws IOException { 327d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru byte[] bytes = new byte[length]; 328d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int count; 329d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int pos = 0; 330d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { 331d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru pos += count; 332d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 333d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pos != length) { 334d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); 335d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 336d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return bytes; 337d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 338d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 339d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 340d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Handles holding onto the cache headers for an entry. 341d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 342e5a344749f100325cd8ef0d78cd7db5221b5e628Ficus Kirkpatrick // Visible for testing. 343e5a344749f100325cd8ef0d78cd7db5221b5e628Ficus Kirkpatrick static class CacheHeader { 344d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The size of the data identified by this CacheHeader. (This is not 345d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * serialized to disk. */ 346d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long size; 347d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 348d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** The key that identifies the cache entry. */ 349d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public String key; 350d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 351d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** ETag for cache coherence. */ 352d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public String etag; 353d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 354d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Date of this response as reported by the server. */ 355d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long serverDate; 356d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 3579324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann /** The last modified date for the requested object. */ 3589324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann public long lastModified; 3599324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann 360d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** TTL for this record. */ 361d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long ttl; 362d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 363d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** Soft TTL for this record. */ 364d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public long softTtl; 365d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 366e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru /** Headers from the response resulting in this cache entry. */ 367e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru public Map<String, String> responseHeaders; 368e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 369d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private CacheHeader() { } 370d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 371d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 372d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Instantiates a new CacheHeader object 373d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param key The key that identifies the cache entry 374d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param entry The cache entry. 375d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 376d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public CacheHeader(String key, Entry entry) { 377d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.key = key; 378d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.size = entry.data.length; 379d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.etag = entry.etag; 380d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.serverDate = entry.serverDate; 3819324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann this.lastModified = entry.lastModified; 382d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.ttl = entry.ttl; 383d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru this.softTtl = entry.softTtl; 384e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru this.responseHeaders = entry.responseHeaders; 385d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 386d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 387d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 388d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Reads the header off of an InputStream and returns a CacheHeader object. 389d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param is The InputStream to read from. 390d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @throws IOException 391d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 392d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static CacheHeader readHeader(InputStream is) throws IOException { 393d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru CacheHeader entry = new CacheHeader(); 394b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int magic = readInt(is); 395b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick if (magic != CACHE_MAGIC) { 396d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // don't bother deleting, it'll get pruned eventually 397d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru throw new IOException(); 398d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 399b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.key = readString(is); 400b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.etag = readString(is); 401d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (entry.etag.equals("")) { 402d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.etag = null; 403d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 404b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.serverDate = readLong(is); 405f42d384cece1686c75c282e5cb72fa9b340af5feRalph Bergmann entry.lastModified = readLong(is); 406b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.ttl = readLong(is); 407b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.softTtl = readLong(is); 408b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick entry.responseHeaders = readStringStringMap(is); 4099324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann 410d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry; 411d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 412d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 413d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 414d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Creates a cache entry for the specified data. 415d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 416d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public Entry toCacheEntry(byte[] data) { 417d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Entry e = new Entry(); 418d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.data = data; 419d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.etag = etag; 420d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.serverDate = serverDate; 4219324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann e.lastModified = lastModified; 422d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.ttl = ttl; 423d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru e.softTtl = softTtl; 424e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru e.responseHeaders = responseHeaders; 425d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return e; 426d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 427d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 428b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 429d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 430d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Writes the contents of this CacheHeader to the specified OutputStream. 431d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 432d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public boolean writeHeader(OutputStream os) { 433d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 434b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeInt(os, CACHE_MAGIC); 435b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, key); 436b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, etag == null ? "" : etag); 437b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, serverDate); 438f42d384cece1686c75c282e5cb72fa9b340af5feRalph Bergmann writeLong(os, lastModified); 439b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, ttl); 440b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, softTtl); 441b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeStringStringMap(responseHeaders, os); 442b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.flush(); 443d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return true; 444d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (IOException e) { 445d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru VolleyLog.d("%s", e.toString()); 446d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return false; 447d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 448d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 449e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru 450d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 451d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 452d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private static class CountingInputStream extends FilterInputStream { 453d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private int bytesRead = 0; 454d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 455d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru private CountingInputStream(InputStream in) { 456d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru super(in); 457d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 458d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 459d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 460d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public int read() throws IOException { 461d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int result = super.read(); 462d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (result != -1) { 463d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru bytesRead++; 464d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 465d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return result; 466d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 467d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 468d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru @Override 469d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public int read(byte[] buffer, int offset, int count) throws IOException { 470d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru int result = super.read(buffer, offset, count); 471d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (result != -1) { 472d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru bytesRead += result; 473d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 474d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return result; 475d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 476d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 477b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 478b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick /* 479b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * Homebrewed simple serialization system used for reading and writing cache 480b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * headers on disk. Once upon a time, this used the standard Java 481b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * Object{Input,Output}Stream, but the default implementation relies heavily 482b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * on reflection (even for standard types) and generates a ton of garbage. 483b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick */ 484b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 485b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick /** 486b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * Simple wrapper around {@link InputStream#read()} that throws EOFException 487b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick * instead of returning -1. 488b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick */ 489b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick private static int read(InputStream is) throws IOException { 490b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int b = is.read(); 491b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick if (b == -1) { 492b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick throw new EOFException(); 493b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 494b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return b; 495b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 496b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 497b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeInt(OutputStream os, int n) throws IOException { 498b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 0) & 0xff); 499b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 8) & 0xff); 500b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 16) & 0xff); 501b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((n >> 24) & 0xff); 502b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 503b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 504b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static int readInt(InputStream is) throws IOException { 505b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int n = 0; 506b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 0); 507b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 8); 508b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 16); 509b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= (read(is) << 24); 510b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return n; 511b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 512b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 513b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeLong(OutputStream os, long n) throws IOException { 514b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 0)); 515b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 8)); 516b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 16)); 517b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 24)); 518b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 32)); 519b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 40)); 520b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 48)); 521b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write((byte)(n >>> 56)); 522b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 523b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 524b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static long readLong(InputStream is) throws IOException { 525b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick long n = 0; 526b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 0); 527b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 8); 528b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 16); 529b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 24); 530b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 32); 531b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 40); 532b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 48); 533b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick n |= ((read(is) & 0xFFL) << 56); 534b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return n; 535b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 536b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 537b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeString(OutputStream os, String s) throws IOException { 538b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick byte[] b = s.getBytes("UTF-8"); 539b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeLong(os, b.length); 540b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick os.write(b, 0, b.length); 541b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 542b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 543b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static String readString(InputStream is) throws IOException { 544b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int n = (int) readLong(is); 545b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick byte[] b = streamToBytes(is, n); 546b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return new String(b, "UTF-8"); 547b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 548b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 549b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException { 550b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick if (map != null) { 551b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeInt(os, map.size()); 552b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick for (Map.Entry<String, String> entry : map.entrySet()) { 553b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, entry.getKey()); 554b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeString(os, entry.getValue()); 555b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 556b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } else { 557b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick writeInt(os, 0); 558b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 559b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 560b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 561b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick static Map<String, String> readStringStringMap(InputStream is) throws IOException { 562b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick int size = readInt(is); 563b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick Map<String, String> result = (size == 0) 564b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick ? Collections.<String, String>emptyMap() 565b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick : new HashMap<String, String>(size); 566b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick for (int i = 0; i < size; i++) { 567b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick String key = readString(is).intern(); 568b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick String value = readString(is).intern(); 569b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick result.put(key, value); 570b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 571b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick return result; 572b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick } 573b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 574b33d0d6651b0b31e965839211d410136db2dcb5bFicus Kirkpatrick 575d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru} 576