13713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick/*
23713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Copyright (C) 2011 The Android Open Source Project
33713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick *
43713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Licensed under the Apache License, Version 2.0 (the "License");
53713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * you may not use this file except in compliance with the License.
63713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * You may obtain a copy of the License at
73713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick *
83713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick *      http://www.apache.org/licenses/LICENSE-2.0
93713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick *
103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Unless required by applicable law or agreed to in writing, software
113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * distributed under the License is distributed on an "AS IS" BASIS,
123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * See the License for the specific language governing permissions and
143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * limitations under the License.
153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */
163713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickpackage com.android.volley.toolbox;
183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
199c19fc62ddd9d1b371cb3ead4e10bb5ff1100a6cJeff Sharkeyimport android.os.SystemClock;
209c19fc62ddd9d1b371cb3ead4e10bb5ff1100a6cJeff Sharkey
213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.Cache;
223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.VolleyLog;
233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.File;
253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.FileInputStream;
263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.FileOutputStream;
27c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Bartaimport java.io.FilterInputStream;
283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.IOException;
293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.InputStream;
303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.ObjectInputStream;
313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.ObjectOutputStream;
323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.io.OutputStream;
333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.util.Iterator;
343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.util.LinkedHashMap;
353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.util.Map;
363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
373713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick/**
383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Cache implementation that caches files directly onto the hard disk in the specified
393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * directory. The default disk usage size is 5MB, but is configurable.
403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */
413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickpublic class DiskBasedCache implements Cache {
423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** Map of the Key, CacheHeader pairs */
443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private final Map<String, CacheHeader> mEntries =
453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            new LinkedHashMap<String, CacheHeader>(16, .75f, true);
463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
473713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** Total amount of space currently used by the cache in bytes. */
483713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private long mTotalSize = 0;
493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** The root directory to use for the cache. */
513713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private final File mRootDirectory;
523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** The maximum size of the cache in bytes. */
543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private final int mMaxCacheSizeInBytes;
553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** Default maximum disk usage in bytes. */
573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
583713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
593713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** High water mark percentage for the cache */
603713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private static final float HYSTERESIS_FACTOR = 0.9f;
613713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
623713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /** Current cache version */
633713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private static final int CACHE_VERSION = 1;
643713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
653713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
663713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Constructs an instance of the DiskBasedCache at the specified directory.
673713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param rootDirectory The root directory of the cache.
683713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
693713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
703713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
713713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        mRootDirectory = rootDirectory;
723713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
733713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
743713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
753713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
763713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Constructs an instance of the DiskBasedCache at the specified directory using
773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * the default maximum cache size of 5MB.
783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param rootDirectory The root directory of the cache.
793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public DiskBasedCache(File rootDirectory) {
813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
843713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
853713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Clears the cache. Deletes all cached files from disk.
863713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
873713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    @Override
883713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public synchronized void clear() {
893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        File[] files = mRootDirectory.listFiles();
903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (files != null) {
913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            for (File file : files) {
923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                file.delete();
933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        mEntries.clear();
963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        mTotalSize = 0;
973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        VolleyLog.d("Cache cleared.");
983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
1003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
1013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Returns the cache entry with the specified key if it exists, null otherwise.
1023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
1033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    @Override
1043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public synchronized Entry get(String key) {
1053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        CacheHeader entry = mEntries.get(key);
1063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        // if the entry does not exist, return.
1073713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (entry == null) {
1083713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return null;
1093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
1103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
1113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        File file = getFileForKey(key);
112c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        CountingInputStream cis = null;
1133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        try {
114c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            cis = new CountingInputStream(new FileInputStream(file));
115c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            CacheHeader.readHeader(cis); // eat header
116c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
1173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return entry.toCacheEntry(data);
1183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        } catch (IOException e) {
1193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
1203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            remove(key);
1213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return null;
1223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        } finally {
123c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            if (cis != null) {
1243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                try {
125c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta                    cis.close();
1263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                } catch (IOException ioe) {
1273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                    return null;
1283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                }
1293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
1303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
1313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
1323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
1333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
1343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Initializes the DiskBasedCache by scanning for all files currently in the
1353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * specified root directory.
1363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
1373713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    @Override
1383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public synchronized void initialize() {
1393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        File[] files = mRootDirectory.listFiles();
1403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (files == null) {
1413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return;
1423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
1433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        for (File file : files) {
1443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            FileInputStream fis = null;
1453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            try {
1463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                fis = new FileInputStream(file);
1473713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                CacheHeader entry = CacheHeader.readHeader(fis);
1483713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                entry.size = file.length();
1493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                putEntry(entry.key, entry);
1503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            } catch (IOException e) {
1513713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                if (file != null) {
1523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                   file.delete();
1533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                }
1543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            } finally {
1553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                try {
1563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                    if (fis != null) {
1573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                        fis.close();
1583713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                    }
1593713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                } catch (IOException ignored) { }
1603713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
1613713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
1623713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
1633713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
1643713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
1653713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Invalidates an entry in the cache.
1663713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param key Cache key
1673713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param fullExpire True to fully expire the entry, false to soft expire
1683713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
1693713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    @Override
1703713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public synchronized void invalidate(String key, boolean fullExpire) {
1713713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        Entry entry = get(key);
1723713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (entry != null) {
1733713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            entry.softTtl = 0;
1743713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            if (fullExpire) {
1753713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                entry.ttl = 0;
1763713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
1773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            put(key, entry);
1783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
1793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
1803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
1813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
1823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
1833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Puts the entry with the specified key into the cache.
1843713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
1853713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    @Override
1863713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public synchronized void put(String key, Entry entry) {
1873713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        pruneIfNeeded(entry.data.length);
1883713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        File file = getFileForKey(key);
1893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        try {
1903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            FileOutputStream fos = new FileOutputStream(file);
1913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            CacheHeader e = new CacheHeader(key, entry);
1923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            e.writeHeader(fos);
1933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            fos.write(entry.data);
1943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            fos.close();
1953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            putEntry(key, e);
1963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return;
1973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        } catch (IOException e) {
1983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
1993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        boolean deleted = file.delete();
2003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (!deleted) {
2013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
2023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
2043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
2063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Removes the specified key from the cache if it exists.
2073713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
2083713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    @Override
2093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    public synchronized void remove(String key) {
2103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        boolean deleted = getFileForKey(key).delete();
2113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        removeEntry(key);
2123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (!deleted) {
2133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
2143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                    key, getFilenameForKey(key));
2153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2163713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
2173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
2193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Creates a pseudo-unique filename for the specified cache key.
2203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param key The key to generate a file name for.
2213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @return A pseudo-unique filename.
2223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
2233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private String getFilenameForKey(String key) {
2243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        int firstHalfLength = key.length() / 2;
2253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
2263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
2273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        return localFilename;
2283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
2293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
2313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Returns a file object for the given cache key.
2323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
233cbbd3af2a6fd57ebbe9da9b1c2f42a3c98171bb1Aurash Mahbod    public File getFileForKey(String key) {
2343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        return new File(mRootDirectory, getFilenameForKey(key));
2353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
2363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2373713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
2383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Prunes the cache to fit the amount of bytes specified.
2393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param neededSpace The amount of bytes we are trying to fit into the cache.
2403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
2413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private void pruneIfNeeded(int neededSpace) {
2423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
2433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return;
2443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (VolleyLog.DEBUG) {
2463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            VolleyLog.v("Pruning old cache entries.");
2473713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2483713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        long before = mTotalSize;
2503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        int prunedFiles = 0;
2519c19fc62ddd9d1b371cb3ead4e10bb5ff1100a6cJeff Sharkey        long startTime = SystemClock.elapsedRealtime();
2523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
2543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        while (iterator.hasNext()) {
2553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            Map.Entry<String, CacheHeader> entry = iterator.next();
2563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            CacheHeader e = entry.getValue();
2573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            boolean deleted = getFileForKey(e.key).delete();
2583713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            if (deleted) {
2593713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                mTotalSize -= e.size;
2603713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            } else {
2613713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick               VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
2623713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                       e.key, getFilenameForKey(e.key));
2633713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
2643713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            iterator.remove();
2653713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            prunedFiles++;
2663713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2673713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
2683713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                break;
2693713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
2703713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2713713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2723713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (VolleyLog.DEBUG) {
2733713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            VolleyLog.v("pruned %d files, %d bytes, %d ms",
2749c19fc62ddd9d1b371cb3ead4e10bb5ff1100a6cJeff Sharkey                    prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
2753713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2763713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
2773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
2793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Puts the entry with the specified key into the cache.
2803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param key The key to identify the entry by.
2813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * @param entry The entry to cache.
2823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
2833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private void putEntry(String key, CacheHeader entry) {
2843713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (!mEntries.containsKey(key)) {
2853713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            mTotalSize += entry.size;
2863713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        } else {
2873713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            CacheHeader oldEntry = mEntries.get(key);
2883713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            mTotalSize += (entry.size - oldEntry.size);
2893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
2903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        mEntries.put(key, entry);
2913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
2923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
2933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
2943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Removes the entry identified by 'key' from the cache.
2953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
2963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private void removeEntry(String key) {
2973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        CacheHeader entry = mEntries.get(key);
2983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        if (entry != null) {
2993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            mTotalSize -= entry.size;
3003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            mEntries.remove(key);
3013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
3023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
3033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
3053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Reads the contents of an InputStream into a byte[].
3063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * */
307c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta    private static byte[] streamToBytes(InputStream in, int length) throws IOException {
308c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        byte[] bytes = new byte[length];
3093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        int count;
310c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        int pos = 0;
311c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
312c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            pos += count;
3133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
314c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        if (pos != length) {
315c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
316c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        }
317c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        return bytes;
3183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
3193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    /**
3213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     * Handles holding onto the cache headers for an entry.
3223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick     */
3233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    private static class CacheHeader {
3243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /** The size of the data identified by this CacheHeader. (This is not
3253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * serialized to disk. */
3263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public long size;
3273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /** The key that identifies the cache entry. */
3293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public String key;
3303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /** ETag for cache coherence. */
3323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public String etag;
3333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /** Date of this response as reported by the server. */
3353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public long serverDate;
3363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3373713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /** TTL for this record. */
3383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public long ttl;
3393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /** Soft TTL for this record. */
3413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public long softTtl;
3423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        private CacheHeader() { }
3443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /**
3463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * Instantiates a new CacheHeader object
3473713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * @param key The key that identifies the cache entry
3483713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * @param entry The cache entry.
3493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         */
3503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public CacheHeader(String key, Entry entry) {
3513713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            this.key = key;
3523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            this.size = entry.data.length;
3533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            this.etag = entry.etag;
3543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            this.serverDate = entry.serverDate;
3553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            this.ttl = entry.ttl;
3563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            this.softTtl = entry.softTtl;
3573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
3583713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3593713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /**
3603713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * Reads the header off of an InputStream and returns a CacheHeader object.
3613713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * @param is The InputStream to read from.
3623713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * @throws IOException
3633713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         */
3643713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public static CacheHeader readHeader(InputStream is) throws IOException {
3653713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            CacheHeader entry = new CacheHeader();
3663713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            ObjectInputStream ois = new ObjectInputStream(is);
3673713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            int version = ois.readByte();
3683713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            if (version != CACHE_VERSION) {
3693713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                // don't bother deleting, it'll get pruned eventually
3703713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                throw new IOException();
3713713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
3723713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            entry.key = ois.readUTF();
3733713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            entry.etag = ois.readUTF();
3743713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            if (entry.etag.equals("")) {
3753713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                entry.etag = null;
3763713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
3773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            entry.serverDate = ois.readLong();
3783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            entry.ttl = ois.readLong();
3793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            entry.softTtl = ois.readLong();
3803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return entry;
3813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
3823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /**
3843713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * Creates a cache entry for the specified data.
3853713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         */
3863713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public Entry toCacheEntry(byte[] data) {
3873713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            Entry e = new Entry();
3883713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            e.data = data;
3893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            e.etag = etag;
3903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            e.serverDate = serverDate;
3913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            e.ttl = ttl;
3923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            e.softTtl = softTtl;
3933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            return e;
3943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
3953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick
3963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        /**
3973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         * Writes the contents of this CacheHeader to the specified OutputStream.
3983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick         */
3993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        public boolean writeHeader(OutputStream os) {
4003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            try {
4013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                ObjectOutputStream oos = new ObjectOutputStream(os);
4023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.writeByte(CACHE_VERSION);
4033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.writeUTF(key);
4043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.writeUTF(etag == null ? "" : etag);
4053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.writeLong(serverDate);
4063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.writeLong(ttl);
4073713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.writeLong(softTtl);
4083713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                oos.flush();
4093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                return true;
4103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            } catch (IOException e) {
4113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                VolleyLog.d("%s", e.toString());
4123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick                return false;
4133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick            }
4143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick        }
4153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick    }
416c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta
417c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta    private static class CountingInputStream extends FilterInputStream {
418c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        private int bytesRead = 0;
419c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta
420c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        private CountingInputStream(InputStream in) {
421c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            super(in);
422c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        }
423c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta
424c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        @Override
425c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        public int read() throws IOException {
426c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            int result = super.read();
427c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            if (result != -1) {
428c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta                bytesRead++;
429c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            }
430c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            return result;
431c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        }
432c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta
433c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        @Override
434c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        public int read(byte[] buffer, int offset, int count) throws IOException {
435c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            int result = super.read(buffer, offset, count);
436c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            if (result != -1) {
437c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta                bytesRead += result;
438c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            }
439c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta            return result;
440c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta        }
441c4cbfcb8d044cea99e2471ce5c401cd959b6cdfeScott Barta    }
4423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick}
443