aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
diff options
context:
space:
mode:
authorAnonymous <no-reply@google.com>2021-07-09 17:40:10 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-07-09 17:40:10 +0000
commitfd5d34f4fc6ffdf0b9c31381ffee78cf73f007d3 (patch)
treecf5d56ccdb862fd9676c855f919408ef940bc019 /src/main/java/com/android/volley/toolbox/DiskBasedCache.java
parent904ab87ee280c7545a908a8ea10a833385a7cf3f (diff)
parentba6983e134f8f64ced7921f506862a7fcee59d19 (diff)
downloadvolley-android13-qpr2-s5-release.tar.gz
Import of Volley from GitHub to AOSP. am: 212e7004ac am: f77c53c3a3 am: f33c2e45dc am: ba6983e134t_frc_odp_330442040t_frc_odp_330442000t_frc_con_330443020t_frc_cbr_330443000t_frc_ase_330444010t_frc_art_330443060t_frc_adb_330444000frc_340821000frc_340819280frc_340819220frc_340819190frc_340819030frc_340819020frc_340819010frc_340818170frc_340818110android-vts-14.0_r4android-vts-14.0_r3android-vts-14.0_r2android-vts-14.0_r1android-u-beta-1-gplandroid-security-14.0.0_r9android-security-14.0.0_r8android-security-14.0.0_r7android-security-14.0.0_r6android-security-14.0.0_r5android-security-14.0.0_r4android-security-14.0.0_r3android-security-14.0.0_r2android-security-14.0.0_r10android-security-14.0.0_r1android-platform-14.0.0_r9android-platform-14.0.0_r8android-platform-14.0.0_r7android-platform-14.0.0_r6android-platform-14.0.0_r5android-platform-14.0.0_r4android-platform-14.0.0_r3android-platform-14.0.0_r2android-platform-14.0.0_r1android-cts-14.0_r4android-cts-14.0_r3android-cts-14.0_r2android-cts-14.0_r1android-14.0.0_r9android-14.0.0_r8android-14.0.0_r7android-14.0.0_r6android-14.0.0_r54android-14.0.0_r53android-14.0.0_r52android-14.0.0_r51android-14.0.0_r50android-14.0.0_r5android-14.0.0_r45android-14.0.0_r44android-14.0.0_r43android-14.0.0_r42android-14.0.0_r41android-14.0.0_r40android-14.0.0_r4android-14.0.0_r39android-14.0.0_r38android-14.0.0_r37android-14.0.0_r36android-14.0.0_r35android-14.0.0_r34android-14.0.0_r33android-14.0.0_r32android-14.0.0_r31android-14.0.0_r30android-14.0.0_r3android-14.0.0_r29android-14.0.0_r28android-14.0.0_r27android-14.0.0_r26android-14.0.0_r25android-14.0.0_r24android-14.0.0_r23android-14.0.0_r22android-14.0.0_r21android-14.0.0_r20android-14.0.0_r2android-14.0.0_r19android-14.0.0_r18android-14.0.0_r17android-14.0.0_r16android-14.0.0_r15android-14.0.0_r14android-14.0.0_r13android-14.0.0_r12android-14.0.0_r11android-14.0.0_r10android-14.0.0_r1android-13.0.0_r83android-13.0.0_r82android-13.0.0_r81android-13.0.0_r80android-13.0.0_r79android-13.0.0_r78android-13.0.0_r77android-13.0.0_r76android-13.0.0_r75android-13.0.0_r74android-13.0.0_r73android-13.0.0_r72android-13.0.0_r71android-13.0.0_r70android-13.0.0_r69android-13.0.0_r68android-13.0.0_r67android-13.0.0_r66android-13.0.0_r65android-13.0.0_r64android-13.0.0_r63android-13.0.0_r62android-13.0.0_r61android-13.0.0_r60android-13.0.0_r59android-13.0.0_r58android-13.0.0_r57android-13.0.0_r56android-13.0.0_r55android-13.0.0_r54android-13.0.0_r53android-13.0.0_r52android-13.0.0_r51android-13.0.0_r50android-13.0.0_r49android-13.0.0_r48android-13.0.0_r47android-13.0.0_r46android-13.0.0_r45android-13.0.0_r44android-13.0.0_r43android-13.0.0_r42android-13.0.0_r41android-13.0.0_r40android-13.0.0_r39android-13.0.0_r38android-13.0.0_r37android-13.0.0_r36android-13.0.0_r35android-13.0.0_r34android-13.0.0_r33android-13.0.0_r32android-13.0.0_r30android-13.0.0_r29android-13.0.0_r28android-13.0.0_r27android-13.0.0_r24android-13.0.0_r23android-13.0.0_r22android-13.0.0_r21android-13.0.0_r20android-13.0.0_r19android-13.0.0_r18android-13.0.0_r17android-13.0.0_r16aml_uwb_341710010aml_uwb_341513070aml_uwb_341511050aml_uwb_341310300aml_uwb_341310030aml_uwb_341111010aml_uwb_341011000aml_uwb_331910010aml_uwb_331820070aml_uwb_331613010aml_uwb_331611010aml_uwb_331410010aml_uwb_331310030aml_uwb_331115000aml_uwb_331015040aml_uwb_330810010aml_tz5_341510070aml_tz5_341510050aml_tz5_341510010aml_tz4_332714070aml_tz4_332714050aml_tz4_332714010aml_tz4_331910000aml_tz4_331314030aml_tz4_331314020aml_tz4_331314010aml_tz4_331012050aml_tz4_331012040aml_tz4_331012000aml_rkp_341810000aml_rkp_341510000aml_rkp_341311000aml_rkp_341114000aml_rkp_341015010aml_rkp_341012000aml_odp_341812000aml_odp_341717000aml_odp_341610000aml_hef_341811030aml_hef_341717050aml_hef_341613000aml_hef_341512030aml_hef_341415040aml_hef_341311010aml_hef_341114030aml_go_wif_330911000aml_go_uwb_330912000aml_go_tz4_330912000aml_go_tet_330914010aml_go_swc_330913000aml_go_sta_330911000aml_go_sdk_330810000aml_go_sch_330911000aml_go_res_330912000aml_go_per_330912000aml_go_odp_330913000aml_go_odp_330912000aml_go_neu_330912000aml_go_net_330913000aml_go_mpr_330912000aml_go_ase_330913000aml_go_ads_330915100aml_go_ads_330915000aml_go_ads_330913000aml_go_adb_330913000aml_cfg_341510000aml_ase_341810000aml_ase_341510000aml_ase_341410000aml_ase_341310010aml_ase_341113000aml_ase_340913000aml_ase_331311020aml_ase_331112000aml_ase_331011020aml_ads_341826300aml_ads_341826060aml_ads_341720000aml_ads_341615050aml_ads_341517040aml_ads_341413000aml_ads_341316030aml_ads_341131050aml_ads_341027030aml_ads_340915050aml_ads_331920180aml_ads_331814200aml_ads_331710270aml_ads_331611190aml_ads_331511020aml_ads_331418080aml_ads_331131000main-16k-with-phonesmain-16kandroid14-tests-releaseandroid14-security-releaseandroid14-s2-releaseandroid14-s1-releaseandroid14-releaseandroid14-qpr3-s2-releaseandroid14-qpr3-releaseandroid14-qpr2-s5-releaseandroid14-qpr2-s4-releaseandroid14-qpr2-s3-releaseandroid14-qpr2-s2-releaseandroid14-qpr2-s1-releaseandroid14-qpr2-releaseandroid14-qpr1-s2-releaseandroid14-qpr1-releaseandroid14-platform-releaseandroid14-mainline-uwb-releaseandroid14-mainline-healthfitness-releaseandroid14-mainline-appsearch-releaseandroid14-mainline-adservices-releaseandroid14-gsiandroid14-devandroid14-d2-s5-releaseandroid14-d2-s4-releaseandroid14-d2-s3-releaseandroid14-d2-s2-releaseandroid14-d2-s1-releaseandroid14-d2-releaseandroid14-d1-s7-releaseandroid14-d1-s6-releaseandroid14-d1-s5-releaseandroid14-d1-s4-releaseandroid14-d1-s3-releaseandroid14-d1-s2-releaseandroid14-d1-s1-releaseandroid14-d1-releaseandroid13-qpr3-s9-releaseandroid13-qpr3-s8-releaseandroid13-qpr3-s7-releaseandroid13-qpr3-s6-releaseandroid13-qpr3-s5-releaseandroid13-qpr3-s4-releaseandroid13-qpr3-s3-releaseandroid13-qpr3-s2-releaseandroid13-qpr3-s14-releaseandroid13-qpr3-s13-releaseandroid13-qpr3-s12-releaseandroid13-qpr3-s11-releaseandroid13-qpr3-s10-releaseandroid13-qpr3-s1-releaseandroid13-qpr3-releaseandroid13-qpr3-c-s8-releaseandroid13-qpr3-c-s7-releaseandroid13-qpr3-c-s6-releaseandroid13-qpr3-c-s5-releaseandroid13-qpr3-c-s4-releaseandroid13-qpr3-c-s3-releaseandroid13-qpr3-c-s2-releaseandroid13-qpr3-c-s12-releaseandroid13-qpr3-c-s11-releaseandroid13-qpr3-c-s10-releaseandroid13-qpr3-c-s1-releaseandroid13-qpr2-s9-releaseandroid13-qpr2-s8-releaseandroid13-qpr2-s7-releaseandroid13-qpr2-s6-releaseandroid13-qpr2-s5-releaseandroid13-qpr2-s3-releaseandroid13-qpr2-s2-releaseandroid13-qpr2-s12-releaseandroid13-qpr2-s11-releaseandroid13-qpr2-s10-releaseandroid13-qpr2-s1-releaseandroid13-qpr2-releaseandroid13-qpr2-b-s1-releaseandroid13-qpr1-s8-releaseandroid13-qpr1-s7-releaseandroid13-qpr1-s6-releaseandroid13-qpr1-s5-releaseandroid13-qpr1-s4-releaseandroid13-qpr1-s3-releaseandroid13-qpr1-s2-releaseandroid13-qpr1-s1-releaseandroid13-qpr1-releaseandroid13-mainline-uwb-releaseandroid13-mainline-tzdata4-releaseandroid13-mainline-go-wifi-releaseandroid13-mainline-go-uwb-releaseandroid13-mainline-go-tzdata4-releaseandroid13-mainline-go-tethering-releaseandroid13-mainline-go-sdkext-releaseandroid13-mainline-go-scheduling-releaseandroid13-mainline-go-resolv-releaseandroid13-mainline-go-permission-releaseandroid13-mainline-go-os-statsd-releaseandroid13-mainline-go-odp-releaseandroid13-mainline-go-neuralnetworks-releaseandroid13-mainline-go-networking-releaseandroid13-mainline-go-mediaprovider-releaseandroid13-mainline-go-media-swcodec-releaseandroid13-mainline-go-appsearch-releaseandroid13-mainline-go-adservices-releaseandroid13-mainline-go-adbd-releaseandroid13-mainline-appsearch-releaseandroid13-mainline-adservices-releaseandroid13-frc-odp-releaseandroid13-frc-conscrypt-releaseandroid13-frc-cellbroadcast-releaseandroid13-frc-art-releaseandroid13-frc-adbd-releaseandroid13-devandroid13-d4-s2-releaseandroid13-d4-s1-releaseandroid13-d4-releaseandroid13-d3-s1-releaseandroid13-d2-releaseaml_tz5_341510010aml_tz4_332714010aml_rkp_341810000aml_odp_341812000aml_hef_341811030aml_ase_341810000aml_ads_341826300aml_ads_341826060
Original change: https://android-review.googlesource.com/c/platform/external/volley/+/1758419 Change-Id: I7191d1e2ebb6c416909593386dbbd3481241bf8c
Diffstat (limited to 'src/main/java/com/android/volley/toolbox/DiskBasedCache.java')
-rw-r--r--src/main/java/com/android/volley/toolbox/DiskBasedCache.java677
1 files changed, 0 insertions, 677 deletions
diff --git a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
deleted file mode 100644
index d4310e0..0000000
--- a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
+++ /dev/null
@@ -1,677 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.volley.toolbox;
-
-import android.os.SystemClock;
-import android.text.TextUtils;
-import androidx.annotation.VisibleForTesting;
-import com.android.volley.Cache;
-import com.android.volley.Header;
-import com.android.volley.VolleyLog;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Cache implementation that caches files directly onto the hard disk in the specified directory.
- * The default disk usage size is 5MB, but is configurable.
- *
- * <p>This cache supports the {@link Entry#allResponseHeaders} headers field.
- */
-public class DiskBasedCache implements Cache {
-
- /** Map of the Key, CacheHeader pairs */
- private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, .75f, true);
-
- /** Total amount of space currently used by the cache in bytes. */
- private long mTotalSize = 0;
-
- /** The supplier for the root directory to use for the cache. */
- private final FileSupplier mRootDirectorySupplier;
-
- /** The maximum size of the cache in bytes. */
- private final int mMaxCacheSizeInBytes;
-
- /** Default maximum disk usage in bytes. */
- private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
-
- /** High water mark percentage for the cache */
- @VisibleForTesting static final float HYSTERESIS_FACTOR = 0.9f;
-
- /** Magic number for current version of cache file format. */
- private static final int CACHE_MAGIC = 0x20150306;
-
- /**
- * Constructs an instance of the DiskBasedCache at the specified directory.
- *
- * @param rootDirectory The root directory of the cache.
- * @param maxCacheSizeInBytes The maximum size of the cache in bytes. Note that the cache may
- * briefly exceed this size on disk when writing a new entry that pushes it over the limit
- * until the ensuing pruning completes.
- */
- public DiskBasedCache(final File rootDirectory, int maxCacheSizeInBytes) {
- mRootDirectorySupplier =
- new FileSupplier() {
- @Override
- public File get() {
- return rootDirectory;
- }
- };
- mMaxCacheSizeInBytes = maxCacheSizeInBytes;
- }
-
- /**
- * Constructs an instance of the DiskBasedCache at the specified directory.
- *
- * @param rootDirectorySupplier The supplier for the root directory of the cache.
- * @param maxCacheSizeInBytes The maximum size of the cache in bytes. Note that the cache may
- * briefly exceed this size on disk when writing a new entry that pushes it over the limit
- * until the ensuing pruning completes.
- */
- public DiskBasedCache(FileSupplier rootDirectorySupplier, int maxCacheSizeInBytes) {
- mRootDirectorySupplier = rootDirectorySupplier;
- mMaxCacheSizeInBytes = maxCacheSizeInBytes;
- }
-
- /**
- * Constructs an instance of the DiskBasedCache at the specified directory using the default
- * maximum cache size of 5MB.
- *
- * @param rootDirectory The root directory of the cache.
- */
- public DiskBasedCache(File rootDirectory) {
- this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
- }
-
- /**
- * Constructs an instance of the DiskBasedCache at the specified directory using the default
- * maximum cache size of 5MB.
- *
- * @param rootDirectorySupplier The supplier for the root directory of the cache.
- */
- public DiskBasedCache(FileSupplier rootDirectorySupplier) {
- this(rootDirectorySupplier, DEFAULT_DISK_USAGE_BYTES);
- }
-
- /** Clears the cache. Deletes all cached files from disk. */
- @Override
- public synchronized void clear() {
- File[] files = mRootDirectorySupplier.get().listFiles();
- if (files != null) {
- for (File file : files) {
- file.delete();
- }
- }
- mEntries.clear();
- mTotalSize = 0;
- VolleyLog.d("Cache cleared.");
- }
-
- /** Returns the cache entry with the specified key if it exists, null otherwise. */
- @Override
- public synchronized Entry get(String key) {
- CacheHeader entry = mEntries.get(key);
- // if the entry does not exist, return.
- if (entry == null) {
- return null;
- }
- File file = getFileForKey(key);
- try {
- CountingInputStream cis =
- new CountingInputStream(
- new BufferedInputStream(createInputStream(file)), file.length());
- try {
- CacheHeader entryOnDisk = CacheHeader.readHeader(cis);
- if (!TextUtils.equals(key, entryOnDisk.key)) {
- // File was shared by two keys and now holds data for a different entry!
- VolleyLog.d(
- "%s: key=%s, found=%s", file.getAbsolutePath(), key, entryOnDisk.key);
- // Remove key whose contents on disk have been replaced.
- removeEntry(key);
- return null;
- }
- byte[] data = streamToBytes(cis, cis.bytesRemaining());
- return entry.toCacheEntry(data);
- } finally {
- // Any IOException thrown here is handled by the below catch block by design.
- //noinspection ThrowFromFinallyBlock
- cis.close();
- }
- } catch (IOException e) {
- VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
- remove(key);
- return null;
- }
- }
-
- /**
- * Initializes the DiskBasedCache by scanning for all files currently in the specified root
- * directory. Creates the root directory if necessary.
- */
- @Override
- public synchronized void initialize() {
- File rootDirectory = mRootDirectorySupplier.get();
- if (!rootDirectory.exists()) {
- if (!rootDirectory.mkdirs()) {
- VolleyLog.e("Unable to create cache dir %s", rootDirectory.getAbsolutePath());
- }
- return;
- }
- File[] files = rootDirectory.listFiles();
- if (files == null) {
- return;
- }
- for (File file : files) {
- try {
- long entrySize = file.length();
- CountingInputStream cis =
- new CountingInputStream(
- new BufferedInputStream(createInputStream(file)), entrySize);
- try {
- CacheHeader entry = CacheHeader.readHeader(cis);
- entry.size = entrySize;
- putEntry(entry.key, entry);
- } finally {
- // Any IOException thrown here is handled by the below catch block by design.
- //noinspection ThrowFromFinallyBlock
- cis.close();
- }
- } catch (IOException e) {
- //noinspection ResultOfMethodCallIgnored
- file.delete();
- }
- }
- }
-
- /**
- * Invalidates an entry in the cache.
- *
- * @param key Cache key
- * @param fullExpire True to fully expire the entry, false to soft expire
- */
- @Override
- public synchronized void invalidate(String key, boolean fullExpire) {
- Entry entry = get(key);
- if (entry != null) {
- entry.softTtl = 0;
- if (fullExpire) {
- entry.ttl = 0;
- }
- put(key, entry);
- }
- }
-
- /** Puts the entry with the specified key into the cache. */
- @Override
- public synchronized void put(String key, Entry entry) {
- // If adding this entry would trigger a prune, but pruning would cause the new entry to be
- // deleted, then skip writing the entry in the first place, as this is just churn.
- // Note that we don't include the cache header overhead in this calculation for simplicity,
- // so putting entries which are just below the threshold may still cause this churn.
- if (mTotalSize + entry.data.length > mMaxCacheSizeInBytes
- && entry.data.length > mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
- return;
- }
- File file = getFileForKey(key);
- try {
- BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
- CacheHeader e = new CacheHeader(key, entry);
- boolean success = e.writeHeader(fos);
- if (!success) {
- fos.close();
- VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
- throw new IOException();
- }
- fos.write(entry.data);
- fos.close();
- e.size = file.length();
- putEntry(key, e);
- pruneIfNeeded();
- } catch (IOException e) {
- boolean deleted = file.delete();
- if (!deleted) {
- VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
- }
- initializeIfRootDirectoryDeleted();
- }
- }
-
- /** Removes the specified key from the cache if it exists. */
- @Override
- public synchronized void remove(String key) {
- boolean deleted = getFileForKey(key).delete();
- removeEntry(key);
- if (!deleted) {
- VolleyLog.d(
- "Could not delete cache entry for key=%s, filename=%s",
- key, getFilenameForKey(key));
- }
- }
-
- /**
- * Creates a pseudo-unique filename for the specified cache key.
- *
- * @param key The key to generate a file name for.
- * @return A pseudo-unique filename.
- */
- private String getFilenameForKey(String key) {
- int firstHalfLength = key.length() / 2;
- String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
- localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
- return localFilename;
- }
-
- /** Returns a file object for the given cache key. */
- public File getFileForKey(String key) {
- return new File(mRootDirectorySupplier.get(), getFilenameForKey(key));
- }
-
- /** Re-initialize the cache if the directory was deleted. */
- private void initializeIfRootDirectoryDeleted() {
- if (!mRootDirectorySupplier.get().exists()) {
- VolleyLog.d("Re-initializing cache after external clearing.");
- mEntries.clear();
- mTotalSize = 0;
- initialize();
- }
- }
-
- /** Represents a supplier for {@link File}s. */
- public interface FileSupplier {
- File get();
- }
-
- /** Prunes the cache to fit the maximum size. */
- private void pruneIfNeeded() {
- if (mTotalSize < mMaxCacheSizeInBytes) {
- return;
- }
- if (VolleyLog.DEBUG) {
- VolleyLog.v("Pruning old cache entries.");
- }
-
- long before = mTotalSize;
- int prunedFiles = 0;
- long startTime = SystemClock.elapsedRealtime();
-
- Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<String, CacheHeader> entry = iterator.next();
- CacheHeader e = entry.getValue();
- boolean deleted = getFileForKey(e.key).delete();
- if (deleted) {
- mTotalSize -= e.size;
- } else {
- VolleyLog.d(
- "Could not delete cache entry for key=%s, filename=%s",
- e.key, getFilenameForKey(e.key));
- }
- iterator.remove();
- prunedFiles++;
-
- if (mTotalSize < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
- break;
- }
- }
-
- if (VolleyLog.DEBUG) {
- VolleyLog.v(
- "pruned %d files, %d bytes, %d ms",
- prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
- }
- }
-
- /**
- * Puts the entry with the specified key into the cache.
- *
- * @param key The key to identify the entry by.
- * @param entry The entry to cache.
- */
- private void putEntry(String key, CacheHeader entry) {
- if (!mEntries.containsKey(key)) {
- mTotalSize += entry.size;
- } else {
- CacheHeader oldEntry = mEntries.get(key);
- mTotalSize += (entry.size - oldEntry.size);
- }
- mEntries.put(key, entry);
- }
-
- /** Removes the entry identified by 'key' from the cache. */
- private void removeEntry(String key) {
- CacheHeader removed = mEntries.remove(key);
- if (removed != null) {
- mTotalSize -= removed.size;
- }
- }
-
- /**
- * Reads length bytes from CountingInputStream into byte array.
- *
- * @param cis input stream
- * @param length number of bytes to read
- * @throws IOException if fails to read all bytes
- */
- @VisibleForTesting
- static byte[] streamToBytes(CountingInputStream cis, long length) throws IOException {
- long maxLength = cis.bytesRemaining();
- // Length cannot be negative or greater than bytes remaining, and must not overflow int.
- if (length < 0 || length > maxLength || (int) length != length) {
- throw new IOException("streamToBytes length=" + length + ", maxLength=" + maxLength);
- }
- byte[] bytes = new byte[(int) length];
- new DataInputStream(cis).readFully(bytes);
- return bytes;
- }
-
- @VisibleForTesting
- InputStream createInputStream(File file) throws FileNotFoundException {
- return new FileInputStream(file);
- }
-
- @VisibleForTesting
- OutputStream createOutputStream(File file) throws FileNotFoundException {
- return new FileOutputStream(file);
- }
-
- /** Handles holding onto the cache headers for an entry. */
- @VisibleForTesting
- static class CacheHeader {
- /**
- * The size of the data identified by this CacheHeader on disk (both header and data).
- *
- * <p>Must be set by the caller after it has been calculated.
- *
- * <p>This is not serialized to disk.
- */
- long size;
-
- /** The key that identifies the cache entry. */
- final String key;
-
- /** ETag for cache coherence. */
- final String etag;
-
- /** Date of this response as reported by the server. */
- final long serverDate;
-
- /** The last modified date for the requested object. */
- final long lastModified;
-
- /** TTL for this record. */
- final long ttl;
-
- /** Soft TTL for this record. */
- final long softTtl;
-
- /** Headers from the response resulting in this cache entry. */
- final List<Header> allResponseHeaders;
-
- private CacheHeader(
- String key,
- String etag,
- long serverDate,
- long lastModified,
- long ttl,
- long softTtl,
- List<Header> allResponseHeaders) {
- this.key = key;
- this.etag = "".equals(etag) ? null : etag;
- this.serverDate = serverDate;
- this.lastModified = lastModified;
- this.ttl = ttl;
- this.softTtl = softTtl;
- this.allResponseHeaders = allResponseHeaders;
- }
-
- /**
- * Instantiates a new CacheHeader object.
- *
- * @param key The key that identifies the cache entry
- * @param entry The cache entry.
- */
- CacheHeader(String key, Entry entry) {
- this(
- key,
- entry.etag,
- entry.serverDate,
- entry.lastModified,
- entry.ttl,
- entry.softTtl,
- getAllResponseHeaders(entry));
- }
-
- private static List<Header> getAllResponseHeaders(Entry entry) {
- // If the entry contains all the response headers, use that field directly.
- if (entry.allResponseHeaders != null) {
- return entry.allResponseHeaders;
- }
-
- // Legacy fallback - copy headers from the map.
- return HttpHeaderParser.toAllHeaderList(entry.responseHeaders);
- }
-
- /**
- * Reads the header from a CountingInputStream and returns a CacheHeader object.
- *
- * @param is The InputStream to read from.
- * @throws IOException if fails to read header
- */
- static CacheHeader readHeader(CountingInputStream is) throws IOException {
- int magic = readInt(is);
- if (magic != CACHE_MAGIC) {
- // don't bother deleting, it'll get pruned eventually
- throw new IOException();
- }
- String key = readString(is);
- String etag = readString(is);
- long serverDate = readLong(is);
- long lastModified = readLong(is);
- long ttl = readLong(is);
- long softTtl = readLong(is);
- List<Header> allResponseHeaders = readHeaderList(is);
- return new CacheHeader(
- key, etag, serverDate, lastModified, ttl, softTtl, allResponseHeaders);
- }
-
- /** Creates a cache entry for the specified data. */
- Entry toCacheEntry(byte[] data) {
- Entry e = new Entry();
- e.data = data;
- e.etag = etag;
- e.serverDate = serverDate;
- e.lastModified = lastModified;
- e.ttl = ttl;
- e.softTtl = softTtl;
- e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders);
- e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders);
- return e;
- }
-
- /** Writes the contents of this CacheHeader to the specified OutputStream. */
- boolean writeHeader(OutputStream os) {
- try {
- writeInt(os, CACHE_MAGIC);
- writeString(os, key);
- writeString(os, etag == null ? "" : etag);
- writeLong(os, serverDate);
- writeLong(os, lastModified);
- writeLong(os, ttl);
- writeLong(os, softTtl);
- writeHeaderList(allResponseHeaders, os);
- os.flush();
- return true;
- } catch (IOException e) {
- VolleyLog.d("%s", e.toString());
- return false;
- }
- }
- }
-
- @VisibleForTesting
- static class CountingInputStream extends FilterInputStream {
- private final long length;
- private long bytesRead;
-
- CountingInputStream(InputStream in, long length) {
- super(in);
- this.length = length;
- }
-
- @Override
- public int read() throws IOException {
- int result = super.read();
- if (result != -1) {
- bytesRead++;
- }
- return result;
- }
-
- @Override
- public int read(byte[] buffer, int offset, int count) throws IOException {
- int result = super.read(buffer, offset, count);
- if (result != -1) {
- bytesRead += result;
- }
- return result;
- }
-
- @VisibleForTesting
- long bytesRead() {
- return bytesRead;
- }
-
- long bytesRemaining() {
- return length - bytesRead;
- }
- }
-
- /*
- * Homebrewed simple serialization system used for reading and writing cache
- * headers on disk. Once upon a time, this used the standard Java
- * Object{Input,Output}Stream, but the default implementation relies heavily
- * on reflection (even for standard types) and generates a ton of garbage.
- *
- * TODO: Replace by standard DataInput and DataOutput in next cache version.
- */
-
- /**
- * Simple wrapper around {@link InputStream#read()} that throws EOFException instead of
- * returning -1.
- */
- private static int read(InputStream is) throws IOException {
- int b = is.read();
- if (b == -1) {
- throw new EOFException();
- }
- return b;
- }
-
- static void writeInt(OutputStream os, int n) throws IOException {
- os.write((n >> 0) & 0xff);
- os.write((n >> 8) & 0xff);
- os.write((n >> 16) & 0xff);
- os.write((n >> 24) & 0xff);
- }
-
- static int readInt(InputStream is) throws IOException {
- int n = 0;
- n |= (read(is) << 0);
- n |= (read(is) << 8);
- n |= (read(is) << 16);
- n |= (read(is) << 24);
- return n;
- }
-
- static void writeLong(OutputStream os, long n) throws IOException {
- os.write((byte) (n >>> 0));
- os.write((byte) (n >>> 8));
- os.write((byte) (n >>> 16));
- os.write((byte) (n >>> 24));
- os.write((byte) (n >>> 32));
- os.write((byte) (n >>> 40));
- os.write((byte) (n >>> 48));
- os.write((byte) (n >>> 56));
- }
-
- static long readLong(InputStream is) throws IOException {
- long n = 0;
- n |= ((read(is) & 0xFFL) << 0);
- n |= ((read(is) & 0xFFL) << 8);
- n |= ((read(is) & 0xFFL) << 16);
- n |= ((read(is) & 0xFFL) << 24);
- n |= ((read(is) & 0xFFL) << 32);
- n |= ((read(is) & 0xFFL) << 40);
- n |= ((read(is) & 0xFFL) << 48);
- n |= ((read(is) & 0xFFL) << 56);
- return n;
- }
-
- static void writeString(OutputStream os, String s) throws IOException {
- byte[] b = s.getBytes("UTF-8");
- writeLong(os, b.length);
- os.write(b, 0, b.length);
- }
-
- static String readString(CountingInputStream cis) throws IOException {
- long n = readLong(cis);
- byte[] b = streamToBytes(cis, n);
- return new String(b, "UTF-8");
- }
-
- static void writeHeaderList(List<Header> headers, OutputStream os) throws IOException {
- if (headers != null) {
- writeInt(os, headers.size());
- for (Header header : headers) {
- writeString(os, header.getName());
- writeString(os, header.getValue());
- }
- } else {
- writeInt(os, 0);
- }
- }
-
- static List<Header> readHeaderList(CountingInputStream cis) throws IOException {
- int size = readInt(cis);
- if (size < 0) {
- throw new IOException("readHeaderList size=" + size);
- }
- List<Header> result =
- (size == 0) ? Collections.<Header>emptyList() : new ArrayList<Header>();
- for (int i = 0; i < size; i++) {
- String name = readString(cis).intern();
- String value = readString(cis).intern();
- result.add(new Header(name, value));
- }
- return result;
- }
-}