From 49eb27da0acb81be3066d098fbcab3f2189c76d7 Mon Sep 17 00:00:00 2001 From: falhassen Date: Fri, 1 Jun 2018 08:57:01 -0700 Subject: [PATCH] Add a CacheKeyFactory to CacheDataSource. A CacheKeyFactory can be passed to the CacheDataSource constructor, allowing clients to dynamically generate a custom cache key for any given upstream uri. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=198878723 --- RELEASENOTES.md | 2 + .../upstream/cache/CacheDataSource.java | 47 ++- .../upstream/cache/CacheKeyFactory.java | 30 ++ .../exoplayer2/upstream/cache/CacheUtil.java | 9 + .../upstream/cache/CacheDataSourceTest.java | 299 ++++++++++++++++-- .../upstream/cache/CacheUtilTest.java | 16 + 6 files changed, 365 insertions(+), 38 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cd9939c05c..fed3a0b7fe 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when + creating a `CacheDataSource`. * Turned on Java 8 compiler support for the ExoPlayer library. Apps that depend on ExoPlayer via its source code rather than an AAR may need to add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 045fc25338..4cdca49596 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -119,6 +119,7 @@ public final class CacheDataSource implements DataSource { private final DataSource cacheReadDataSource; private final DataSource cacheWriteDataSource; private final DataSource upstreamDataSource; + private final CacheKeyFactory cacheKeyFactory; @Nullable private final EventListener eventListener; private final boolean blockOnCache; @@ -178,8 +179,13 @@ public final class CacheDataSource implements DataSource { */ public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags, long maxCacheFileSize) { - this(cache, upstream, new FileDataSource(), new CacheDataSink(cache, maxCacheFileSize), - flags, null); + this( + cache, + upstream, + new FileDataSource(), + new CacheDataSink(cache, maxCacheFileSize), + flags, + /* eventListener= */ null); } /** @@ -198,8 +204,43 @@ public final class CacheDataSource implements DataSource { */ public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, DataSink cacheWriteDataSink, @Flags int flags, @Nullable EventListener eventListener) { + this( + cache, + upstream, + cacheReadDataSource, + cacheWriteDataSink, + flags, + eventListener, + /* cacheKeyFactory= */ null); + } + + /** + * Constructs an instance with arbitrary {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. One use of this constructor is to allow data to be transformed + * before it is written to disk. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. + * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is + * accessed read-only. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. + * @param eventListener An optional {@link EventListener} to receive events. + * @param cacheKeyFactory An optional factory for cache keys. + */ + public CacheDataSource( + Cache cache, + DataSource upstream, + DataSource cacheReadDataSource, + DataSink cacheWriteDataSink, + @Flags int flags, + @Nullable EventListener eventListener, + @Nullable CacheKeyFactory cacheKeyFactory) { this.cache = cache; this.cacheReadDataSource = cacheReadDataSource; + this.cacheKeyFactory = + cacheKeyFactory != null ? cacheKeyFactory : CacheUtil.DEFAULT_CACHE_KEY_FACTORY; this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; this.ignoreCacheForUnsetLengthRequests = @@ -216,7 +257,7 @@ public final class CacheDataSource implements DataSource { @Override public long open(DataSpec dataSpec) throws IOException { try { - key = CacheUtil.getKey(dataSpec); + key = cacheKeyFactory.buildCacheKey(dataSpec); uri = dataSpec.uri; actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ uri); flags = dataSpec.flags; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java new file mode 100644 index 0000000000..1e173ca3ad --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheKeyFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.google.android.exoplayer2.upstream.cache; + +import android.net.Uri; +import com.google.android.exoplayer2.upstream.DataSpec; + +/** Factory for custom cache keys. */ +public interface CacheKeyFactory { + + /** + * Returns a custom cache key for the given {@link Uri}. + * + * @param dataSpec The upstream source from which data will be read and cached. + */ + String buildCacheKey(DataSpec dataSpec); +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java index a1f7aa3097..8337f27595 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java @@ -54,6 +54,15 @@ public final class CacheUtil { /** Default buffer size to be used while caching. */ public static final int DEFAULT_BUFFER_SIZE_BYTES = 128 * 1024; + /** Default {@link CacheKeyFactory} that calls through to {@link #getKey}. */ + public static final CacheKeyFactory DEFAULT_CACHE_KEY_FACTORY = + new CacheKeyFactory() { + @Override + public String buildCacheKey(DataSpec dataSpec) { + return getKey(dataSpec); + } + }; + /** * Generates a cache key out of the given {@link Uri}. * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 8dc702d3a3..6b48cffdd5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -46,16 +46,27 @@ public final class CacheDataSourceTest { private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; private static final int MAX_CACHE_FILE_SIZE = 3; + private static final String CACHE_KEY_PREFIX = "myCacheKeyFactoryPrefix"; private Uri testDataUri; - private String testDataKey; + private String fixedCacheKey; + private String expectedCacheKey; private File tempFolder; private SimpleCache cache; + private CacheKeyFactory cacheKeyFactory; @Before public void setUp() throws Exception { testDataUri = Uri.parse("test_data"); - testDataKey = CacheUtil.generateKey(testDataUri); + fixedCacheKey = CacheUtil.generateKey(testDataUri); + expectedCacheKey = fixedCacheKey; + cacheKeyFactory = + new CacheKeyFactory() { + @Override + public String buildCacheKey(DataSpec dataSpec) { + return CACHE_KEY_PREFIX + "." + CacheUtil.generateKey(dataSpec.uri); + } + }; tempFolder = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest"); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); } @@ -77,41 +88,203 @@ public final class CacheDataSourceTest { } } - @Test - public void testCacheAndRead() throws Exception { - assertCacheAndRead(false, false); - } - @Test public void testCacheAndReadUnboundedRequest() throws Exception { - assertCacheAndRead(true, false); + assertCacheAndRead(/* unboundedRequest= */ true, /* simulateUnknownLength= */ false); } @Test public void testCacheAndReadUnknownLength() throws Exception { - assertCacheAndRead(false, true); + assertCacheAndRead(/* unboundedRequest= */ false, /* simulateUnknownLength= */ true); } @Test public void testCacheAndReadUnboundedRequestUnknownLength() throws Exception { - assertCacheAndRead(true, true); + assertCacheAndRead(/* unboundedRequest= */ true, /* simulateUnknownLength= */ true); + } + + @Test + public void testCacheAndRead() throws Exception { + assertCacheAndRead(/* unboundedRequest= */ false, /* simulateUnknownLength= */ false); } @Test public void testUnsatisfiableRange() throws Exception { // Bounded request but the content length is unknown. This forces all data to be cached but not // the length - assertCacheAndRead(false, true); + assertCacheAndRead(/* unboundedRequest= */ false, /* simulateUnknownLength= */ true); // Now do an unbounded request. This will read all of the data from cache and then try to read // more from upstream which will cause to a 416 so CDS will store the length. - CacheDataSource cacheDataSource = createCacheDataSource(true, true); - assertReadDataContentLength(cacheDataSource, true, true); + CacheDataSource cacheDataSource = + createCacheDataSource(/* setReadException= */ true, /* simulateUnknownLength= */ true); + assertReadDataContentLength( + cacheDataSource, /* unboundedRequest= */ true, /* unknownLength= */ true); // If the user try to access off range then it should throw an IOException try { - cacheDataSource = createCacheDataSource(false, false); - cacheDataSource.open(new DataSpec(testDataUri, TEST_DATA.length, 5, testDataKey)); + cacheDataSource = + createCacheDataSource(/* setReadException= */ false, /* simulateUnknownLength= */ false); + cacheDataSource.open(new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + fail(); + } catch (IOException e) { + // success + } + } + + @Test + public void testCacheAndReadUnboundedRequestWithCacheKeyFactoryWithNullDataSpecCacheKey() + throws Exception { + fixedCacheKey = null; + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, /* key= */ null)); + + assertCacheAndRead( + /* unboundedRequest= */ true, /* simulateUnknownLength= */ false, cacheKeyFactory); + } + + @Test + public void testCacheAndReadUnknownLengthWithCacheKeyFactoryOverridingWithNullDataSpecCacheKey() + throws Exception { + fixedCacheKey = null; + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, /* key= */ null)); + + assertCacheAndRead( + /* unboundedRequest= */ false, /* simulateUnknownLength= */ true, cacheKeyFactory); + } + + @Test + public void + testCacheAndReadUnboundedRequestUnknownLengthWithCacheKeyFactoryWithNullDataSpecCacheKey() + throws Exception { + fixedCacheKey = null; + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, /* key= */ null)); + + assertCacheAndRead( + /* unboundedRequest= */ true, /* simulateUnknownLength= */ true, cacheKeyFactory); + } + + @Test + public void testCacheAndReadWithCacheKeyFactoryWithNullDataSpecCacheKey() throws Exception { + fixedCacheKey = null; + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, /* key= */ null)); + + assertCacheAndRead( + /* unboundedRequest= */ false, /* simulateUnknownLength= */ false, cacheKeyFactory); + } + + @Test + public void testUnsatisfiableRangeWithCacheKeyFactoryNullDataSpecCacheKey() throws Exception { + fixedCacheKey = null; + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, /* key= */ null)); + + // Bounded request but the content length is unknown. This forces all data to be cached but not + // the length + assertCacheAndRead( + /* unboundedRequest= */ false, /* simulateUnknownLength= */ true, cacheKeyFactory); + + // Now do an unbounded request. This will read all of the data from cache and then try to read + // more from upstream which will cause to a 416 so CDS will store the length. + CacheDataSource cacheDataSource = + createCacheDataSource( + /* setReadException= */ true, /* simulateUnknownLength= */ true, cacheKeyFactory); + assertReadDataContentLength( + cacheDataSource, /* unboundedRequest= */ true, /* unknownLength= */ true); + + // If the user try to access off range then it should throw an IOException + try { + cacheDataSource = + createCacheDataSource( + /* setReadException= */ false, /* simulateUnknownLength= */ false, cacheKeyFactory); + cacheDataSource.open(new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + fail(); + } catch (IOException e) { + // success + } + } + + @Test + public void testCacheAndReadUnboundedRequestWithCacheKeyFactoryOverridingDataSpecCacheKey() + throws Exception { + fixedCacheKey = CacheUtil.generateKey(testDataUri); + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + + assertCacheAndRead(true, false, cacheKeyFactory); + } + + @Test + public void testCacheAndReadUnknownLengthWithCacheKeyFactoryOverridingDataSpecCacheKey() + throws Exception { + fixedCacheKey = CacheUtil.generateKey(testDataUri); + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + + assertCacheAndRead(false, true, cacheKeyFactory); + } + + @Test + public void + testCacheAndReadUnboundedRequestUnknownLengthWithCacheKeyFactoryOverridingDataSpecCacheKey() + throws Exception { + fixedCacheKey = CacheUtil.generateKey(testDataUri); + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + + assertCacheAndRead( + /* unboundedRequest= */ true, /* simulateUnknownLength= */ true, cacheKeyFactory); + } + + @Test + public void testCacheAndReadWithCacheKeyFactoryOverridingDataSpecCacheKey() throws Exception { + fixedCacheKey = CacheUtil.generateKey(testDataUri); + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + + assertCacheAndRead( + /* unboundedRequest= */ false, /* simulateUnknownLength= */ false, cacheKeyFactory); + } + + @Test + public void testUnsatisfiableRangeWithCacheKeyFactoryOverridingDataSpecCacheKey() + throws Exception { + fixedCacheKey = CacheUtil.generateKey(testDataUri); + expectedCacheKey = + cacheKeyFactory.buildCacheKey( + new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); + + // Bounded request but the content length is unknown. This forces all data to be cached but not + // the length + assertCacheAndRead( + /* unboundedRequest= */ false, /* simulateUnknownLength= */ true, cacheKeyFactory); + + // Now do an unbounded request. This will read all of the data from cache and then try to read + // more from upstream which will cause to a 416 so CDS will store the length. + CacheDataSource cacheDataSource = + createCacheDataSource( + /* setReadException= */ true, /* simulateUnknownLength= */ true, cacheKeyFactory); + assertReadDataContentLength( + cacheDataSource, /* unboundedRequest= */ true, /* unknownLength= */ true); + + // If the user try to access off range then it should throw an IOException + try { + cacheDataSource = + createCacheDataSource( + /* setReadException= */ false, /* simulateUnknownLength= */ false, cacheKeyFactory); + cacheDataSource.open(new DataSpec(testDataUri, TEST_DATA.length, 5, fixedCacheKey)); fail(); } catch (IOException e) { // success @@ -123,7 +296,7 @@ public final class CacheDataSourceTest { // Read partial at EOS but don't cross it so length is unknown CacheDataSource cacheDataSource = createCacheDataSource(false, true); assertReadData(cacheDataSource, true, TEST_DATA.length - 2, 2); - assertThat(cache.getContentLength(testDataKey)).isEqualTo(C.LENGTH_UNSET); + assertThat(cache.getContentLength(expectedCacheKey)).isEqualTo(C.LENGTH_UNSET); // Now do an unbounded request for whole data. This will cause a bounded request from upstream. // End of data from upstream shouldn't be mixed up with EOS and cause length set wrong. @@ -133,7 +306,7 @@ public final class CacheDataSourceTest { // Now the length set correctly do an unbounded request with offset assertThat( cacheDataSource.open( - new DataSpec(testDataUri, TEST_DATA.length - 2, C.LENGTH_UNSET, testDataKey))) + new DataSpec(testDataUri, TEST_DATA.length - 2, C.LENGTH_UNSET, expectedCacheKey))) .isEqualTo(2); // An unbounded request with offset for not cached content @@ -155,12 +328,12 @@ public final class CacheDataSourceTest { CacheDataSource cacheDataSource = new CacheDataSource(cache, upstream, 0); int flags = DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH; - cacheDataSource.open(new DataSpec(testDataUri, 0, C.LENGTH_UNSET, testDataKey, flags)); + cacheDataSource.open(new DataSpec(testDataUri, 0, C.LENGTH_UNSET, expectedCacheKey, flags)); TestUtil.readToEnd(cacheDataSource); cacheDataSource.close(); assertThat(upstream.getAndClearOpenedDataSpecs()).hasLength(1); - assertThat(cache.getContentLength(testDataKey)).isEqualTo(TEST_DATA.length); + assertThat(cache.getContentLength(expectedCacheKey)).isEqualTo(TEST_DATA.length); } @Test @@ -171,7 +344,7 @@ public final class CacheDataSourceTest { new CacheDataSource( cache, upstream, CacheDataSource.FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS); - cacheDataSource.open(new DataSpec(testDataUri, 0, C.LENGTH_UNSET, testDataKey)); + cacheDataSource.open(new DataSpec(testDataUri, 0, C.LENGTH_UNSET, expectedCacheKey)); TestUtil.readToEnd(cacheDataSource); cacheDataSource.close(); @@ -206,7 +379,7 @@ public final class CacheDataSourceTest { new CacheDataSource(cache, upstream, new FileDataSource(), null, 0, null); // Open source and read some data from upstream as the data hasn't cached yet. - DataSpec dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, testDataKey); + DataSpec dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, fixedCacheKey); cacheDataSource.open(dataSpec); byte[] buffer = new byte[1024]; cacheDataSource.read(buffer, 0, buffer.length); @@ -245,7 +418,7 @@ public final class CacheDataSourceTest { .appendReadData(1); // Lock the content on the cache. - SimpleCacheSpan cacheSpan = cache.startReadWriteNonBlocking(testDataKey, 0); + SimpleCacheSpan cacheSpan = cache.startReadWriteNonBlocking(expectedCacheKey, 0); assertThat(cacheSpan).isNotNull(); assertThat(cacheSpan.isHoleSpan()).isTrue(); @@ -253,7 +426,7 @@ public final class CacheDataSourceTest { CacheDataSource cacheDataSource = new CacheDataSource(cache, upstream, 0); // Open source and read some data from upstream without writing to cache as the data is locked. - DataSpec dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, testDataKey); + DataSpec dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, fixedCacheKey); cacheDataSource.open(dataSpec); byte[] buffer = new byte[1024]; cacheDataSource.read(buffer, 0, buffer.length); @@ -286,7 +459,7 @@ public final class CacheDataSourceTest { upstream.getDataSet().newDefaultData().appendReadData(1024).endData(); // Cache the latter half of the data. - DataSpec dataSpec = new DataSpec(testDataUri, 512, C.LENGTH_UNSET, testDataKey); + DataSpec dataSpec = new DataSpec(testDataUri, 512, C.LENGTH_UNSET, fixedCacheKey); CacheUtil.cache(dataSpec, cache, upstream, /* counters= */ null, /* isCanceled= */ null); // Create cache read-only CacheDataSource. @@ -294,12 +467,12 @@ public final class CacheDataSourceTest { new CacheDataSource(cache, upstream, new FileDataSource(), null, 0, null); // Open source and read some data from upstream as the data hasn't cached yet. - dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, testDataKey); + dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, fixedCacheKey); cacheDataSource.open(dataSpec); TestUtil.readExactly(cacheDataSource, 100); // Delete cached data. - CacheUtil.remove(cache, testDataKey); + CacheUtil.remove(cache, expectedCacheKey); assertCacheEmpty(cache); // Read the rest of the data. @@ -317,21 +490,21 @@ public final class CacheDataSourceTest { // Cache the latter half of the data. int halfDataLength = 512; - DataSpec dataSpec = new DataSpec(testDataUri, halfDataLength, C.LENGTH_UNSET, testDataKey); + DataSpec dataSpec = new DataSpec(testDataUri, halfDataLength, C.LENGTH_UNSET, fixedCacheKey); CacheUtil.cache(dataSpec, cache, upstream, /* counters= */ null, /* isCanceled= */ null); // Create blocking CacheDataSource. CacheDataSource cacheDataSource = new CacheDataSource(cache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE); - dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, testDataKey); + dataSpec = new DataSpec(testDataUri, 0, C.LENGTH_UNSET, fixedCacheKey); cacheDataSource.open(dataSpec); // Read the first half from upstream as it hasn't cached yet. TestUtil.readExactly(cacheDataSource, halfDataLength); // Delete the cached latter half. - NavigableSet cachedSpans = cache.getCachedSpans(testDataKey); + NavigableSet cachedSpans = cache.getCachedSpans(expectedCacheKey); for (CacheSpan cachedSpan : cachedSpans) { if (cachedSpan.position >= halfDataLength) { try { @@ -355,8 +528,30 @@ public final class CacheDataSourceTest { // Just read from cache cacheDataSource = createCacheDataSource(true, simulateUnknownLength); - assertReadDataContentLength(cacheDataSource, unboundedRequest, - false /*length is already cached*/); + assertReadDataContentLength( + cacheDataSource, + unboundedRequest, + // Length is already cached. + /* unknownLength= */ false); + } + + private void assertCacheAndRead( + boolean unboundedRequest, boolean simulateUnknownLength, CacheKeyFactory cacheKeyFactory) + throws IOException { + // Read all data from upstream and write to cache + CacheDataSource cacheDataSource = + createCacheDataSource( + /* setReadException= */ false, simulateUnknownLength, cacheKeyFactory); + assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength); + + // Just read from cache + cacheDataSource = + createCacheDataSource(/* setReadException= */ true, simulateUnknownLength, cacheKeyFactory); + assertReadDataContentLength( + cacheDataSource, + unboundedRequest, + // Length is already cached. + /* unknownLength= */ false); } /** @@ -368,7 +563,7 @@ public final class CacheDataSourceTest { int length = unboundedRequest ? C.LENGTH_UNSET : TEST_DATA.length; assertReadData(cacheDataSource, unknownLength, 0, length); // If !unboundedRequest, CacheDataSource doesn't reach EOS so shouldn't cache content length - assertThat(cache.getContentLength(testDataKey)) + assertThat(cache.getContentLength(expectedCacheKey)) .isEqualTo(!unboundedRequest ? C.LENGTH_UNSET : TEST_DATA.length); } @@ -380,7 +575,11 @@ public final class CacheDataSourceTest { } DataSpec dataSpec = new DataSpec( - testDataUri, position, length, testDataKey, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); + testDataUri, + position, + length, + fixedCacheKey, + DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); assertThat(cacheDataSource.open(dataSpec)).isEqualTo(unknownLength ? length : testDataLength); cacheDataSource.close(); @@ -395,6 +594,16 @@ public final class CacheDataSourceTest { CacheDataSource.FLAG_BLOCK_ON_CACHE); } + private CacheDataSource createCacheDataSource( + boolean setReadException, boolean simulateUnknownLength, CacheKeyFactory cacheKeyFactory) { + return createCacheDataSource( + setReadException, + simulateUnknownLength, + CacheDataSource.FLAG_BLOCK_ON_CACHE, + new CacheDataSink(cache, MAX_CACHE_FILE_SIZE), + cacheKeyFactory); + } + private CacheDataSource createCacheDataSource(boolean setReadException, boolean simulateUnknownLength, @CacheDataSource.Flags int flags) { return createCacheDataSource(setReadException, simulateUnknownLength, flags, @@ -404,14 +613,34 @@ public final class CacheDataSourceTest { private CacheDataSource createCacheDataSource(boolean setReadException, boolean simulateUnknownLength, @CacheDataSource.Flags int flags, CacheDataSink cacheWriteDataSink) { + return createCacheDataSource( + setReadException, + simulateUnknownLength, + flags, + cacheWriteDataSink, + /* cacheKeyFactory= */ null); + } + + private CacheDataSource createCacheDataSource( + boolean setReadException, + boolean simulateUnknownLength, + @CacheDataSource.Flags int flags, + CacheDataSink cacheWriteDataSink, + CacheKeyFactory cacheKeyFactory) { FakeDataSource upstream = new FakeDataSource(); FakeData fakeData = upstream.getDataSet().newDefaultData() .setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA); if (setReadException) { fakeData.appendReadError(new IOException("Shouldn't read from upstream")); } - return new CacheDataSource(cache, upstream, new FileDataSource(), cacheWriteDataSink, - flags, null); + return new CacheDataSource( + cache, + upstream, + new FileDataSource(), + cacheWriteDataSink, + flags, + /* eventListener= */ null, + cacheKeyFactory); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java index 61c7f2b673..e3917b58d0 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java @@ -131,6 +131,22 @@ public final class CacheUtilTest { .isEqualTo(generateKey(testUri)); } + @Test + public void testDefaultCacheKeyFactory_buildCacheKey() throws Exception { + Uri testUri = Uri.parse("test"); + String key = "key"; + // If DataSpec.key is present, returns it + assertThat( + CacheUtil.DEFAULT_CACHE_KEY_FACTORY.buildCacheKey( + new DataSpec(testUri, 0, LENGTH_UNSET, key))) + .isEqualTo(key); + // If not generates a new one using DataSpec.uri + assertThat( + CacheUtil.DEFAULT_CACHE_KEY_FACTORY.buildCacheKey( + new DataSpec(testUri, 0, LENGTH_UNSET, null))) + .isEqualTo(generateKey(testUri)); + } + @Test public void testGetCachedNoData() throws Exception { CachingCounters counters = new CachingCounters();