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