Make CachedContent store content length in ContentMetadata
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=188742379
This commit is contained in:
parent
c38e7b1aeb
commit
00a7306fd8
@ -14,9 +14,7 @@ import java.io.IOException;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/** Tests {@link CachedContentIndex}. */
|
||||||
* Tests {@link CachedContentIndex}.
|
|
||||||
*/
|
|
||||||
public class CachedContentIndexTest extends InstrumentationTestCase {
|
public class CachedContentIndexTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
private final byte[] testIndexV1File = {
|
private final byte[] testIndexV1File = {
|
||||||
@ -53,14 +51,14 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
final String key3 = "key3";
|
final String key3 = "key3";
|
||||||
|
|
||||||
// Add two CachedContents with add methods
|
// Add two CachedContents with add methods
|
||||||
CachedContent cachedContent1 = new CachedContent(5, key1, 10);
|
CachedContent cachedContent1 = new CachedContent(5, key1);
|
||||||
index.addNew(cachedContent1);
|
index.addNew(cachedContent1);
|
||||||
CachedContent cachedContent2 = index.getOrAdd(key2);
|
CachedContent cachedContent2 = index.getOrAdd(key2);
|
||||||
assertThat(cachedContent1.id != cachedContent2.id).isTrue();
|
assertThat(cachedContent1.id != cachedContent2.id).isTrue();
|
||||||
|
|
||||||
// add a span
|
// add a span
|
||||||
File cacheSpanFile = SimpleCacheSpanTest
|
File cacheSpanFile =
|
||||||
.createCacheSpanFile(cacheDir, cachedContent1.id, 10, 20, 30);
|
SimpleCacheSpanTest.createCacheSpanFile(cacheDir, cachedContent1.id, 10, 20, 30);
|
||||||
SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(cacheSpanFile, index);
|
SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(cacheSpanFile, index);
|
||||||
assertThat(span).isNotNull();
|
assertThat(span).isNotNull();
|
||||||
cachedContent1.addSpan(span);
|
cachedContent1.addSpan(span);
|
||||||
@ -115,8 +113,12 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testStoreV1() throws Exception {
|
public void testStoreV1() throws Exception {
|
||||||
index.addNew(new CachedContent(2, "KLMNO", 2560));
|
CachedContent cachedContent1 = new CachedContent(2, "KLMNO");
|
||||||
index.addNew(new CachedContent(5, "ABCDE", 10));
|
cachedContent1.setLength(2560);
|
||||||
|
index.addNew(cachedContent1);
|
||||||
|
CachedContent cachedContent2 = new CachedContent(5, "ABCDE");
|
||||||
|
cachedContent2.setLength(10);
|
||||||
|
index.addNew(cachedContent2);
|
||||||
|
|
||||||
index.store();
|
index.store();
|
||||||
|
|
||||||
@ -165,8 +167,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
byte[] key = "Bar12345Bar12345".getBytes(C.UTF8_NAME); // 128 bit key
|
byte[] key = "Bar12345Bar12345".getBytes(C.UTF8_NAME); // 128 bit key
|
||||||
byte[] key2 = "Foo12345Foo12345".getBytes(C.UTF8_NAME); // 128 bit key
|
byte[] key2 = "Foo12345Foo12345".getBytes(C.UTF8_NAME); // 128 bit key
|
||||||
|
|
||||||
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
|
assertStoredAndLoadedEqual(
|
||||||
new CachedContentIndex(cacheDir, key));
|
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir, key));
|
||||||
|
|
||||||
// Rename the index file from the test above
|
// Rename the index file from the test above
|
||||||
File file1 = new File(cacheDir, CachedContentIndex.FILE_NAME);
|
File file1 = new File(cacheDir, CachedContentIndex.FILE_NAME);
|
||||||
@ -174,8 +176,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
assertThat(file1.renameTo(file2)).isTrue();
|
assertThat(file1.renameTo(file2)).isTrue();
|
||||||
|
|
||||||
// Write a new index file
|
// Write a new index file
|
||||||
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
|
assertStoredAndLoadedEqual(
|
||||||
new CachedContentIndex(cacheDir, key));
|
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir, key));
|
||||||
|
|
||||||
assertThat(file1.length()).isEqualTo(file2.length());
|
assertThat(file1.length()).isEqualTo(file2.length());
|
||||||
// Assert file content is different
|
// Assert file content is different
|
||||||
@ -187,8 +189,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
boolean threw = false;
|
boolean threw = false;
|
||||||
try {
|
try {
|
||||||
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
|
assertStoredAndLoadedEqual(
|
||||||
new CachedContentIndex(cacheDir, key2));
|
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir, key2));
|
||||||
} catch (AssertionError e) {
|
} catch (AssertionError e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
@ -197,8 +199,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
.isTrue();
|
.isTrue();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
|
assertStoredAndLoadedEqual(
|
||||||
new CachedContentIndex(cacheDir));
|
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir));
|
||||||
} catch (AssertionError e) {
|
} catch (AssertionError e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
@ -207,18 +209,18 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
.isTrue();
|
.isTrue();
|
||||||
|
|
||||||
// Non encrypted index file can be read even when encryption key provided.
|
// Non encrypted index file can be read even when encryption key provided.
|
||||||
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir),
|
assertStoredAndLoadedEqual(
|
||||||
new CachedContentIndex(cacheDir, key));
|
new CachedContentIndex(cacheDir), new CachedContentIndex(cacheDir, key));
|
||||||
|
|
||||||
// Test multiple store() calls
|
// Test multiple store() calls
|
||||||
CachedContentIndex index = new CachedContentIndex(cacheDir, key);
|
CachedContentIndex index = new CachedContentIndex(cacheDir, key);
|
||||||
index.addNew(new CachedContent(15, "key3", 110));
|
index.addNew(new CachedContent(15, "key3"));
|
||||||
index.store();
|
index.store();
|
||||||
assertStoredAndLoadedEqual(index, new CachedContentIndex(cacheDir, key));
|
assertStoredAndLoadedEqual(index, new CachedContentIndex(cacheDir, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRemoveEmptyNotLockedCachedContent() throws Exception {
|
public void testRemoveEmptyNotLockedCachedContent() throws Exception {
|
||||||
CachedContent cachedContent = new CachedContent(5, "key1", 10);
|
CachedContent cachedContent = new CachedContent(5, "key1");
|
||||||
index.addNew(cachedContent);
|
index.addNew(cachedContent);
|
||||||
|
|
||||||
index.maybeRemove(cachedContent.key);
|
index.maybeRemove(cachedContent.key);
|
||||||
@ -227,7 +229,7 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testCantRemoveNotEmptyCachedContent() throws Exception {
|
public void testCantRemoveNotEmptyCachedContent() throws Exception {
|
||||||
CachedContent cachedContent = new CachedContent(5, "key1", 10);
|
CachedContent cachedContent = new CachedContent(5, "key1");
|
||||||
index.addNew(cachedContent);
|
index.addNew(cachedContent);
|
||||||
File cacheSpanFile =
|
File cacheSpanFile =
|
||||||
SimpleCacheSpanTest.createCacheSpanFile(cacheDir, cachedContent.id, 10, 20, 30);
|
SimpleCacheSpanTest.createCacheSpanFile(cacheDir, cachedContent.id, 10, 20, 30);
|
||||||
@ -240,7 +242,7 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testCantRemoveLockedCachedContent() throws Exception {
|
public void testCantRemoveLockedCachedContent() throws Exception {
|
||||||
CachedContent cachedContent = new CachedContent(5, "key1", 10);
|
CachedContent cachedContent = new CachedContent(5, "key1");
|
||||||
cachedContent.setLocked(true);
|
cachedContent.setLocked(true);
|
||||||
index.addNew(cachedContent);
|
index.addNew(cachedContent);
|
||||||
|
|
||||||
@ -251,7 +253,7 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
private void assertStoredAndLoadedEqual(CachedContentIndex index, CachedContentIndex index2)
|
private void assertStoredAndLoadedEqual(CachedContentIndex index, CachedContentIndex index2)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
index.addNew(new CachedContent(5, "key1", 10));
|
index.addNew(new CachedContent(5, "key1"));
|
||||||
index.getOrAdd("key2");
|
index.getOrAdd("key2");
|
||||||
index.store();
|
index.store();
|
||||||
|
|
||||||
@ -264,5 +266,4 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
|
|||||||
assertThat(index2.get(key).getSpans()).isEqualTo(index.get(key).getSpans());
|
assertThat(index2.get(key).getSpans()).isEqualTo(index.get(key).getSpans());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,14 +28,16 @@ import java.util.TreeSet;
|
|||||||
*/
|
*/
|
||||||
/*package*/ final class CachedContent {
|
/*package*/ final class CachedContent {
|
||||||
|
|
||||||
|
private static final String EXOPLAYER_METADATA_NAME_PREFIX = "exo_";
|
||||||
|
private static final String METADATA_NAME_LENGTH = EXOPLAYER_METADATA_NAME_PREFIX + "len";
|
||||||
/** The cache file id that uniquely identifies the original stream. */
|
/** The cache file id that uniquely identifies the original stream. */
|
||||||
public final int id;
|
public final int id;
|
||||||
/** The cache key that uniquely identifies the original stream. */
|
/** The cache key that uniquely identifies the original stream. */
|
||||||
public final String key;
|
public final String key;
|
||||||
/** The cached spans of this content. */
|
/** The cached spans of this content. */
|
||||||
private final TreeSet<SimpleCacheSpan> cachedSpans;
|
private final TreeSet<SimpleCacheSpan> cachedSpans;
|
||||||
/** The length of the original stream, or {@link C#LENGTH_UNSET} if the length is unknown. */
|
/** Metadata values. */
|
||||||
private long length;
|
private DefaultContentMetadata metadata;
|
||||||
/** Whether the content is locked. */
|
/** Whether the content is locked. */
|
||||||
private boolean locked;
|
private boolean locked;
|
||||||
|
|
||||||
@ -45,8 +47,13 @@ import java.util.TreeSet;
|
|||||||
* @param input Input stream containing values needed to initialize CachedContent instance.
|
* @param input Input stream containing values needed to initialize CachedContent instance.
|
||||||
* @throws IOException If an error occurs during reading values.
|
* @throws IOException If an error occurs during reading values.
|
||||||
*/
|
*/
|
||||||
public CachedContent(DataInputStream input) throws IOException {
|
public static CachedContent readFromStream(DataInputStream input) throws IOException {
|
||||||
this(input.readInt(), input.readUTF(), input.readLong());
|
int id = input.readInt();
|
||||||
|
String key = input.readUTF();
|
||||||
|
long length = input.readLong();
|
||||||
|
CachedContent cachedContent = new CachedContent(id, key);
|
||||||
|
cachedContent.setLength(length);
|
||||||
|
return cachedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,12 +61,11 @@ import java.util.TreeSet;
|
|||||||
*
|
*
|
||||||
* @param id The cache file id.
|
* @param id The cache file id.
|
||||||
* @param key The cache stream key.
|
* @param key The cache stream key.
|
||||||
* @param length The length of the original stream.
|
|
||||||
*/
|
*/
|
||||||
public CachedContent(int id, String key, long length) {
|
public CachedContent(int id, String key) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.length = length;
|
this.metadata = new DefaultContentMetadata();
|
||||||
this.cachedSpans = new TreeSet<>();
|
this.cachedSpans = new TreeSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,17 +78,21 @@ import java.util.TreeSet;
|
|||||||
public void writeToStream(DataOutputStream output) throws IOException {
|
public void writeToStream(DataOutputStream output) throws IOException {
|
||||||
output.writeInt(id);
|
output.writeInt(id);
|
||||||
output.writeUTF(key);
|
output.writeUTF(key);
|
||||||
output.writeLong(length);
|
output.writeLong(getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the length of the content. */
|
/**
|
||||||
|
* Returns the length of the original stream, or {@link C#LENGTH_UNSET} if the length is unknown.
|
||||||
|
*/
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
return length;
|
return metadata.get(METADATA_NAME_LENGTH, C.LENGTH_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the length of the content. */
|
/** Sets the length of the content. */
|
||||||
public void setLength(long length) {
|
public void setLength(long length) {
|
||||||
this.length = length;
|
ContentMetadataMutations mutations =
|
||||||
|
new ContentMetadataMutations().set(METADATA_NAME_LENGTH, length);
|
||||||
|
metadata = new DefaultContentMetadata(metadata, mutations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the content is locked. */
|
/** Returns whether the content is locked. */
|
||||||
@ -194,6 +204,7 @@ import java.util.TreeSet;
|
|||||||
|
|
||||||
/** Calculates a hash code for the header of this {@code CachedContent}. */
|
/** Calculates a hash code for the header of this {@code CachedContent}. */
|
||||||
public int headerHashCode() {
|
public int headerHashCode() {
|
||||||
|
long length = getLength();
|
||||||
int result = id;
|
int result = id;
|
||||||
result = 31 * result + key.hashCode();
|
result = 31 * result + key.hashCode();
|
||||||
result = 31 * result + (int) (length ^ (length >>> 32));
|
result = 31 * result + (int) (length ^ (length >>> 32));
|
||||||
|
@ -45,9 +45,7 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
/**
|
/** Maintains the index of cached content. */
|
||||||
* This class maintains the index of cached content.
|
|
||||||
*/
|
|
||||||
/*package*/ class CachedContentIndex {
|
/*package*/ class CachedContentIndex {
|
||||||
|
|
||||||
public static final String FILE_NAME = "cached_content_index.exi";
|
public static final String FILE_NAME = "cached_content_index.exi";
|
||||||
@ -259,7 +257,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
int count = input.readInt();
|
int count = input.readInt();
|
||||||
int hashCode = 0;
|
int hashCode = 0;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
CachedContent cachedContent = new CachedContent(input);
|
CachedContent cachedContent = CachedContent.readFromStream(input);
|
||||||
add(cachedContent);
|
add(cachedContent);
|
||||||
hashCode += cachedContent.headerHashCode();
|
hashCode += cachedContent.headerHashCode();
|
||||||
}
|
}
|
||||||
@ -339,7 +337,8 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
|
|
||||||
private CachedContent addNew(String key, long length) {
|
private CachedContent addNew(String key, long length) {
|
||||||
int id = getNewId(idToKey);
|
int id = getNewId(idToKey);
|
||||||
CachedContent cachedContent = new CachedContent(id, key, length);
|
CachedContent cachedContent = new CachedContent(id, key);
|
||||||
|
cachedContent.setLength(length);
|
||||||
addNew(cachedContent);
|
addNew(cachedContent);
|
||||||
return cachedContent;
|
return cachedContent;
|
||||||
}
|
}
|
||||||
|
@ -15,63 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.upstream.cache;
|
package com.google.android.exoplayer2.upstream.cache;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
|
|
||||||
|
|
||||||
/** Interface for accessing cached content metadata which is stored as name, value pairs. */
|
/** Interface for accessing cached content metadata which is stored as name, value pairs. */
|
||||||
public interface ContentMetadata {
|
public interface ContentMetadata {
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for modifying values in a {@link ContentMetadata} object. The changes you make in an
|
|
||||||
* editor are not copied back to the original {@link ContentMetadata} until you call {@link
|
|
||||||
* #commit()}.
|
|
||||||
*/
|
|
||||||
interface Editor {
|
|
||||||
/**
|
|
||||||
* Sets a metadata value, to be committed once {@link #commit()} is called. Passing {@code null}
|
|
||||||
* as {@code value} isn't allowed. {@code value} byte array shouldn't be modified after passed
|
|
||||||
* to this method.
|
|
||||||
*
|
|
||||||
* @param name The name of the metadata value.
|
|
||||||
* @param value The value to be set.
|
|
||||||
* @return This Editor instance, for convenience.
|
|
||||||
*/
|
|
||||||
Editor set(String name, byte[] value);
|
|
||||||
/**
|
|
||||||
* Sets a metadata value, to be committed once {@link #commit()} is called. Passing {@code null}
|
|
||||||
* as value isn't allowed.
|
|
||||||
*
|
|
||||||
* @param name The name of the metadata value.
|
|
||||||
* @param value The value to be set.
|
|
||||||
* @return This Editor instance, for convenience.
|
|
||||||
*/
|
|
||||||
Editor set(String name, String value);
|
|
||||||
/**
|
|
||||||
* Sets a metadata value, to be committed once {@link #commit()} is called.
|
|
||||||
*
|
|
||||||
* @param name The name of the metadata value.
|
|
||||||
* @param value The value to be set.
|
|
||||||
* @return This Editor instance, for convenience.
|
|
||||||
*/
|
|
||||||
Editor set(String name, long value);
|
|
||||||
/**
|
|
||||||
* Sets a metadata value, to be committed once {@link #commit()} is called. Passing {@code null}
|
|
||||||
* as value isn't allowed.
|
|
||||||
*
|
|
||||||
* @param name The name of the metadata value.
|
|
||||||
* @return This Editor instance, for convenience.
|
|
||||||
*/
|
|
||||||
Editor remove(String name);
|
|
||||||
/**
|
|
||||||
* Commits changes. It can be called only once.
|
|
||||||
*
|
|
||||||
* @throws CacheException If the commit fails.
|
|
||||||
*/
|
|
||||||
void commit() throws CacheException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns an editor to change metadata values. */
|
|
||||||
Editor edit();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a metadata value.
|
* Returns a metadata value.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** Defines multiple mutations on metadata value which are applied atomically. */
|
||||||
|
public class ContentMetadataMutations {
|
||||||
|
|
||||||
|
private final Map<String, Object> editedValues;
|
||||||
|
private final List<String> removedValues;
|
||||||
|
|
||||||
|
/** Constructs a DefaultMetadataMutations. */
|
||||||
|
public ContentMetadataMutations() {
|
||||||
|
editedValues = new HashMap<>();
|
||||||
|
removedValues = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mutation to set a metadata value. Passing {@code null} as {@code value} isn't allowed.
|
||||||
|
*
|
||||||
|
* @param name The name of the metadata value.
|
||||||
|
* @param value The value to be set.
|
||||||
|
* @return This Editor instance, for convenience.
|
||||||
|
*/
|
||||||
|
public ContentMetadataMutations set(String name, String value) {
|
||||||
|
return checkAndSet(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mutation to set a metadata value.
|
||||||
|
*
|
||||||
|
* @param name The name of the metadata value.
|
||||||
|
* @param value The value to be set.
|
||||||
|
* @return This Editor instance, for convenience.
|
||||||
|
*/
|
||||||
|
public ContentMetadataMutations set(String name, long value) {
|
||||||
|
return checkAndSet(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mutation to set a metadata value. Passing {@code null} as {@code value} isn't allowed.
|
||||||
|
* {@code value} byte array shouldn't be modified after passed to this method.
|
||||||
|
*
|
||||||
|
* @param name The name of the metadata value.
|
||||||
|
* @param value The value to be set.
|
||||||
|
* @return This Editor instance, for convenience.
|
||||||
|
*/
|
||||||
|
public ContentMetadataMutations set(String name, byte[] value) {
|
||||||
|
return checkAndSet(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mutation to remove a metadata value.
|
||||||
|
*
|
||||||
|
* @param name The name of the metadata value.
|
||||||
|
* @return This Editor instance, for convenience.
|
||||||
|
*/
|
||||||
|
public ContentMetadataMutations remove(String name) {
|
||||||
|
removedValues.add(name);
|
||||||
|
editedValues.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of names of metadata values to be removed. The returned array shouldn't be
|
||||||
|
* modified.
|
||||||
|
*/
|
||||||
|
public List<String> getRemovedValues() {
|
||||||
|
return removedValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of metadata name, value pairs to be set. The returned map and the values in it
|
||||||
|
* shouldn't be modified.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getEditedValues() {
|
||||||
|
return editedValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContentMetadataMutations checkAndSet(String name, Object value) {
|
||||||
|
editedValues.put(name, Assertions.checkNotNull(value));
|
||||||
|
removedValues.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -15,59 +15,30 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.upstream.cache;
|
package com.google.android.exoplayer2.upstream.cache;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/** Default implementation of {@link ContentMetadata}. Values are stored as byte arrays. */
|
/** Default implementation of {@link ContentMetadata}. Values are stored as byte arrays. */
|
||||||
public final class DefaultContentMetadata implements ContentMetadata {
|
public final class DefaultContentMetadata implements ContentMetadata {
|
||||||
|
|
||||||
/** Listener for metadata change event. */
|
|
||||||
public interface ChangeListener {
|
|
||||||
/**
|
|
||||||
* Called when any metadata value is changed or removed.
|
|
||||||
*
|
|
||||||
* @param contentMetadata The reporting instance.
|
|
||||||
* @param metadataValues All metadata name, value pairs. It shouldn't be accessed out of this
|
|
||||||
* method.
|
|
||||||
*/
|
|
||||||
void onChange(DefaultContentMetadata contentMetadata, Map<String, byte[]> metadataValues)
|
|
||||||
throws CacheException;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, byte[]> metadata;
|
private final Map<String, byte[]> metadata;
|
||||||
@Nullable private ChangeListener changeListener;
|
|
||||||
|
|
||||||
|
/** Constructs an empty {@link DefaultContentMetadata}. */
|
||||||
public DefaultContentMetadata() {
|
public DefaultContentMetadata() {
|
||||||
this.metadata = new HashMap<>();
|
this.metadata = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@link DefaultContentMetadata} using name, value pairs data which was passed to
|
* Constructs a {@link DefaultContentMetadata} by copying metadata values from {@code other} and
|
||||||
* {@link ChangeListener#onChange(DefaultContentMetadata, Map)}.
|
* applying {@code mutations}.
|
||||||
*
|
|
||||||
* @param metadata Initial name, value pairs.
|
|
||||||
*/
|
*/
|
||||||
public DefaultContentMetadata(Map<String, byte[]> metadata) {
|
public DefaultContentMetadata(DefaultContentMetadata other, ContentMetadataMutations mutations) {
|
||||||
this.metadata = new HashMap<>(metadata);
|
this.metadata = new HashMap<>(other.metadata);
|
||||||
}
|
applyMutations(mutations);
|
||||||
|
|
||||||
/** Sets a {@link ChangeListener}. This method can be called once. */
|
|
||||||
public void setChangeListener(ChangeListener changeListener) {
|
|
||||||
Assertions.checkState(this.changeListener == null);
|
|
||||||
this.changeListener = Assertions.checkNotNull(changeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final Editor edit() {
|
|
||||||
return new EditorImpl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -112,60 +83,28 @@ public final class DefaultContentMetadata implements ContentMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void apply(ArrayList<String> removedValues, Map<String, byte[]> editedValues)
|
private void applyMutations(ContentMetadataMutations mutations) {
|
||||||
throws CacheException {
|
List<String> removedValues = mutations.getRemovedValues();
|
||||||
synchronized (metadata) {
|
for (int i = 0; i < removedValues.size(); i++) {
|
||||||
for (int i = 0; i < removedValues.size(); i++) {
|
metadata.remove(removedValues.get(i));
|
||||||
metadata.remove(removedValues.get(i));
|
}
|
||||||
}
|
Map<String, Object> editedValues = mutations.getEditedValues();
|
||||||
metadata.putAll(editedValues);
|
for (String name : editedValues.keySet()) {
|
||||||
if (changeListener != null) {
|
Object value = editedValues.get(name);
|
||||||
changeListener.onChange(this, Collections.unmodifiableMap(metadata));
|
metadata.put(name, getBytes(value));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EditorImpl implements Editor {
|
private static byte[] getBytes(Object value) {
|
||||||
|
if (value instanceof Long) {
|
||||||
private final Map<String, byte[]> editedValues;
|
return ByteBuffer.allocate(8).putLong((Long) value).array();
|
||||||
private final ArrayList<String> removedValues;
|
} else if (value instanceof String) {
|
||||||
|
return ((String) value).getBytes(Charset.forName(C.UTF8_NAME));
|
||||||
private EditorImpl() {
|
} else if (value instanceof byte[]) {
|
||||||
editedValues = new HashMap<>();
|
return (byte[]) value;
|
||||||
removedValues = new ArrayList<>();
|
} else {
|
||||||
}
|
throw new IllegalStateException();
|
||||||
|
|
||||||
@Override
|
|
||||||
public Editor set(String name, String value) {
|
|
||||||
set(name, value.getBytes());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Editor set(String name, long value) {
|
|
||||||
set(name, ByteBuffer.allocate(8).putLong(value).array());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Editor set(String name, byte[] value) {
|
|
||||||
editedValues.put(name, Assertions.checkNotNull(value));
|
|
||||||
removedValues.remove(name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Editor remove(String name) {
|
|
||||||
removedValues.add(name);
|
|
||||||
editedValues.remove(name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commit() throws CacheException {
|
|
||||||
apply(removedValues, editedValues);
|
|
||||||
removedValues.clear();
|
|
||||||
editedValues.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,6 @@ package com.google.android.exoplayer2.upstream.cache;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.ContentMetadata.Editor;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.DefaultContentMetadata.ChangeListener;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -36,11 +27,9 @@ import org.robolectric.RobolectricTestRunner;
|
|||||||
public class DefaultContentMetadataTest {
|
public class DefaultContentMetadataTest {
|
||||||
|
|
||||||
private DefaultContentMetadata contentMetadata;
|
private DefaultContentMetadata contentMetadata;
|
||||||
private FakeChangeListener changeListener;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
changeListener = new FakeChangeListener();
|
|
||||||
contentMetadata = createAbstractContentMetadata();
|
contentMetadata = createAbstractContentMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,124 +56,85 @@ public class DefaultContentMetadataTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEditReturnsAnEditor() throws Exception {
|
public void testEmptyMutationDoesNotFail() throws Exception {
|
||||||
assertThat(contentMetadata.edit()).isNotNull();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
}
|
new DefaultContentMetadata(new DefaultContentMetadata(), mutations);
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEditReturnsAnotherEditorEveryTime() throws Exception {
|
|
||||||
assertThat(contentMetadata.edit()).isNotEqualTo(contentMetadata.edit());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommitWithoutEditDoesNotFail() throws Exception {
|
|
||||||
Editor editor = contentMetadata.edit();
|
|
||||||
editor.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddNewMetadata() throws Exception {
|
public void testAddNewMetadata() throws Exception {
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.set("metadata name", "value");
|
mutations.set("metadata name", "value");
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
|
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddNewIntMetadata() throws Exception {
|
public void testAddNewIntMetadata() throws Exception {
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.set("metadata name", 5);
|
mutations.set("metadata name", 5);
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", 0)).isEqualTo(5);
|
assertThat(contentMetadata.get("metadata name", 0)).isEqualTo(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddNewByteArrayMetadata() throws Exception {
|
public void testAddNewByteArrayMetadata() throws Exception {
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
byte[] value = {1, 2, 3};
|
byte[] value = {1, 2, 3};
|
||||||
editor.set("metadata name", value);
|
mutations.set("metadata name", value);
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", new byte[] {})).isEqualTo(value);
|
assertThat(contentMetadata.get("metadata name", new byte[] {})).isEqualTo(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNewMetadataNotWrittenBeforeCommitted() throws Exception {
|
public void testNewMetadataNotWrittenBeforeCommitted() throws Exception {
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.set("metadata name", "value");
|
mutations.set("metadata name", "value");
|
||||||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
|
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEditMetadata() throws Exception {
|
public void testEditMetadata() throws Exception {
|
||||||
contentMetadata = createAbstractContentMetadata("metadata name", "value");
|
contentMetadata = createAbstractContentMetadata("metadata name", "value");
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.set("metadata name", "edited value");
|
mutations.set("metadata name", "edited value");
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("edited value");
|
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("edited value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveMetadata() throws Exception {
|
public void testRemoveMetadata() throws Exception {
|
||||||
contentMetadata = createAbstractContentMetadata("metadata name", "value");
|
contentMetadata = createAbstractContentMetadata("metadata name", "value");
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.remove("metadata name");
|
mutations.remove("metadata name");
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
|
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddAndRemoveMetadata() throws Exception {
|
public void testAddAndRemoveMetadata() throws Exception {
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.set("metadata name", "value");
|
mutations.set("metadata name", "value");
|
||||||
editor.remove("metadata name");
|
mutations.remove("metadata name");
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
|
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveAndAddMetadata() throws Exception {
|
public void testRemoveAndAddMetadata() throws Exception {
|
||||||
Editor editor = contentMetadata.edit();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
editor.remove("metadata name");
|
mutations.remove("metadata name");
|
||||||
editor.set("metadata name", "value");
|
mutations.set("metadata name", "value");
|
||||||
editor.commit();
|
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
|
||||||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
|
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnChangeIsCalledWhenMetadataEdited() throws Exception {
|
|
||||||
contentMetadata =
|
|
||||||
createAbstractContentMetadata(
|
|
||||||
"metadata name", "value", "metadata name2", "value2", "metadata name3", "value3");
|
|
||||||
Editor editor = contentMetadata.edit();
|
|
||||||
editor.set("metadata name", "edited value");
|
|
||||||
editor.remove("metadata name2");
|
|
||||||
editor.commit();
|
|
||||||
assertThat(changeListener.remainingValues).containsExactly("metadata name", "metadata name3");
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefaultContentMetadata createAbstractContentMetadata(String... pairs) {
|
private DefaultContentMetadata createAbstractContentMetadata(String... pairs) {
|
||||||
assertThat(pairs.length % 2).isEqualTo(0);
|
assertThat(pairs.length % 2).isEqualTo(0);
|
||||||
HashMap<String, byte[]> map = new HashMap<>();
|
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||||
for (int i = 0; i < pairs.length; i += 2) {
|
for (int i = 0; i < pairs.length; i += 2) {
|
||||||
map.put(pairs[i], getBytes(pairs[i + 1]));
|
mutations.set(pairs[i], pairs[i + 1]);
|
||||||
}
|
|
||||||
DefaultContentMetadata metadata = new DefaultContentMetadata(Collections.unmodifiableMap(map));
|
|
||||||
metadata.setChangeListener(changeListener);
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] getBytes(String value) {
|
|
||||||
return value.getBytes(Charset.forName(C.UTF8_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FakeChangeListener implements ChangeListener {
|
|
||||||
|
|
||||||
private ArrayList<String> remainingValues;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChange(DefaultContentMetadata contentMetadata, Map<String, byte[]> metadataValues)
|
|
||||||
throws CacheException {
|
|
||||||
remainingValues = new ArrayList<>(metadataValues.keySet());
|
|
||||||
}
|
}
|
||||||
|
return new DefaultContentMetadata(new DefaultContentMetadata(), mutations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user