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