mirror of
https://github.com/samsonjs/media.git
synced 2026-04-26 14:57:47 +00:00
Centralize serialization in CachedContentIndex
We need to support serialization to/from an SQLite table. The model of passing something around for each class to write into doesn't work well for SQL, and it would be messy to have two different structural designs for serialization. This change centralizes the logic in CachedContentIndex, where a centralized SQL based version can more easily sit alongside it. PiperOrigin-RevId: 230692291
This commit is contained in:
parent
49b9775d08
commit
f182c0c116
4 changed files with 137 additions and 150 deletions
|
|
@ -18,17 +18,11 @@ package com.google.android.exoplayer2.upstream.cache;
|
|||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/** Defines the cached content for a single stream. */
|
||||
/* package */ final class CachedContent {
|
||||
|
||||
private static final int VERSION_METADATA_INTRODUCED = 2;
|
||||
private static final int VERSION_MAX = Integer.MAX_VALUE;
|
||||
|
||||
/** The cache file id that uniquely identifies the original stream. */
|
||||
public final int id;
|
||||
/** The cache key that uniquely identifies the original stream. */
|
||||
|
|
@ -40,29 +34,6 @@ import java.util.TreeSet;
|
|||
/** Whether the content is locked. */
|
||||
private boolean locked;
|
||||
|
||||
/**
|
||||
* Reads an instance from a {@link DataInputStream}.
|
||||
*
|
||||
* @param version Version of the encoded data.
|
||||
* @param input Input stream containing values needed to initialize CachedContent instance.
|
||||
* @throws IOException If an error occurs during reading values.
|
||||
*/
|
||||
public static CachedContent readFromStream(int version, DataInputStream input)
|
||||
throws IOException {
|
||||
int id = input.readInt();
|
||||
String key = input.readUTF();
|
||||
CachedContent cachedContent = new CachedContent(id, key);
|
||||
if (version < VERSION_METADATA_INTRODUCED) {
|
||||
long length = input.readLong();
|
||||
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||
ContentMetadataMutations.setContentLength(mutations, length);
|
||||
cachedContent.applyMetadataMutations(mutations);
|
||||
} else {
|
||||
cachedContent.metadata = DefaultContentMetadata.readFromStream(input);
|
||||
}
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CachedContent.
|
||||
*
|
||||
|
|
@ -70,26 +41,18 @@ import java.util.TreeSet;
|
|||
* @param key The cache stream key.
|
||||
*/
|
||||
public CachedContent(int id, String key) {
|
||||
this(id, key, DefaultContentMetadata.EMPTY);
|
||||
}
|
||||
|
||||
public CachedContent(int id, String key, DefaultContentMetadata metadata) {
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.metadata = DefaultContentMetadata.EMPTY;
|
||||
this.metadata = metadata;
|
||||
this.cachedSpans = new TreeSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the instance to a {@link DataOutputStream}.
|
||||
*
|
||||
* @param output Output stream to store the values.
|
||||
* @throws IOException If an error occurs during writing values to output.
|
||||
*/
|
||||
public void writeToStream(DataOutputStream output) throws IOException {
|
||||
output.writeInt(id);
|
||||
output.writeUTF(key);
|
||||
metadata.writeToStream(output);
|
||||
}
|
||||
|
||||
/** Returns the metadata. */
|
||||
public ContentMetadata getMetadata() {
|
||||
public DefaultContentMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
@ -208,26 +171,11 @@ import java.util.TreeSet;
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a hash code for the header of this {@code CachedContent} which is compatible with
|
||||
* the index file with {@code version}.
|
||||
*/
|
||||
public int headerHashCode(int version) {
|
||||
int result = id;
|
||||
result = 31 * result + key.hashCode();
|
||||
if (version < VERSION_METADATA_INTRODUCED) {
|
||||
long length = ContentMetadata.getContentLength(metadata);
|
||||
result = 31 * result + (int) (length ^ (length >>> 32));
|
||||
} else {
|
||||
result = 31 * result + metadata.hashCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = headerHashCode(VERSION_MAX);
|
||||
result = 31 * result + cachedSpans.hashCode();
|
||||
int result = id;
|
||||
result = 31 * result + key.hashCode();
|
||||
result = 31 * result + metadata.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ import java.io.OutputStream;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import javax.crypto.Cipher;
|
||||
|
|
@ -51,6 +53,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||
public static final String FILE_NAME = "cached_content_index.exi";
|
||||
|
||||
private static final int VERSION = 2;
|
||||
private static final int VERSION_METADATA_INTRODUCED = 2;
|
||||
private static final int INCREMENTAL_METADATA_READ_LENGTH = 10 * 1024 * 1024;
|
||||
|
||||
private static final int FLAG_ENCRYPTED_INDEX = 1;
|
||||
|
||||
|
|
@ -245,6 +249,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||
return cachedContent != null ? cachedContent.getMetadata() : DefaultContentMetadata.EMPTY;
|
||||
}
|
||||
|
||||
private CachedContent addNew(String key) {
|
||||
int id = getNewId(idToKey);
|
||||
CachedContent cachedContent = new CachedContent(id, key);
|
||||
add(cachedContent);
|
||||
changed = true;
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
private void add(CachedContent cachedContent) {
|
||||
keyToContent.put(cachedContent.key, cachedContent);
|
||||
idToKey.put(cachedContent.id, cachedContent.key);
|
||||
}
|
||||
|
||||
private boolean readFile() {
|
||||
DataInputStream input = null;
|
||||
try {
|
||||
|
|
@ -276,9 +293,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||
int count = input.readInt();
|
||||
int hashCode = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
CachedContent cachedContent = CachedContent.readFromStream(version, input);
|
||||
CachedContent cachedContent = readCachedContent(version, input);
|
||||
add(cachedContent);
|
||||
hashCode += cachedContent.headerHashCode(version);
|
||||
hashCode += hashCachedContent(cachedContent, version);
|
||||
}
|
||||
int fileHashCode = input.readInt();
|
||||
boolean isEOF = input.read() == -1;
|
||||
|
|
@ -327,8 +344,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||
output.writeInt(keyToContent.size());
|
||||
int hashCode = 0;
|
||||
for (CachedContent cachedContent : keyToContent.values()) {
|
||||
cachedContent.writeToStream(output);
|
||||
hashCode += cachedContent.headerHashCode(VERSION);
|
||||
writeCachedContent(cachedContent, output);
|
||||
hashCode += hashCachedContent(cachedContent, VERSION);
|
||||
}
|
||||
output.writeInt(hashCode);
|
||||
atomicFile.endWrite(output);
|
||||
|
|
@ -342,17 +359,108 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||
}
|
||||
}
|
||||
|
||||
private CachedContent addNew(String key) {
|
||||
int id = getNewId(idToKey);
|
||||
CachedContent cachedContent = new CachedContent(id, key);
|
||||
add(cachedContent);
|
||||
changed = true;
|
||||
return cachedContent;
|
||||
/**
|
||||
* Calculates a hash code for a {@link CachedContent} which is compatible with a particular index
|
||||
* version.
|
||||
*/
|
||||
private int hashCachedContent(CachedContent cachedContent, int version) {
|
||||
int result = cachedContent.id;
|
||||
result = 31 * result + cachedContent.key.hashCode();
|
||||
if (version < VERSION_METADATA_INTRODUCED) {
|
||||
long length = ContentMetadata.getContentLength(cachedContent.getMetadata());
|
||||
result = 31 * result + (int) (length ^ (length >>> 32));
|
||||
} else {
|
||||
result = 31 * result + cachedContent.getMetadata().hashCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void add(CachedContent cachedContent) {
|
||||
keyToContent.put(cachedContent.key, cachedContent);
|
||||
idToKey.put(cachedContent.id, cachedContent.key);
|
||||
/**
|
||||
* Reads a {@link CachedContent} from a {@link DataInputStream}.
|
||||
*
|
||||
* @param version Version of the encoded data.
|
||||
* @param input Input stream containing values needed to initialize CachedContent instance.
|
||||
* @throws IOException If an error occurs during reading values.
|
||||
*/
|
||||
private static CachedContent readCachedContent(int version, DataInputStream input)
|
||||
throws IOException {
|
||||
int id = input.readInt();
|
||||
String key = input.readUTF();
|
||||
DefaultContentMetadata metadata;
|
||||
if (version < VERSION_METADATA_INTRODUCED) {
|
||||
long length = input.readLong();
|
||||
ContentMetadataMutations mutations = new ContentMetadataMutations();
|
||||
ContentMetadataMutations.setContentLength(mutations, length);
|
||||
metadata = DefaultContentMetadata.EMPTY.copyWithMutationsApplied(mutations);
|
||||
} else {
|
||||
metadata = readContentMetadata(input);
|
||||
}
|
||||
return new CachedContent(id, key, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link CachedContent} to a {@link DataOutputStream}.
|
||||
*
|
||||
* @param output Output stream to store the values.
|
||||
* @throws IOException If an error occurs during writing values to output.
|
||||
*/
|
||||
private static void writeCachedContent(CachedContent cachedContent, DataOutputStream output)
|
||||
throws IOException {
|
||||
output.writeInt(cachedContent.id);
|
||||
output.writeUTF(cachedContent.key);
|
||||
writeContentMetadata(cachedContent.getMetadata(), output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a {@link DefaultContentMetadata} from the given input stream.
|
||||
*
|
||||
* @param input Input stream to read from.
|
||||
* @return a {@link DefaultContentMetadata} instance.
|
||||
* @throws IOException If an error occurs during reading from input.
|
||||
*/
|
||||
private static DefaultContentMetadata readContentMetadata(DataInputStream input)
|
||||
throws IOException {
|
||||
int size = input.readInt();
|
||||
HashMap<String, byte[]> metadata = new HashMap<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
String name = input.readUTF();
|
||||
int valueSize = input.readInt();
|
||||
if (valueSize < 0) {
|
||||
throw new IOException("Invalid value size: " + valueSize);
|
||||
}
|
||||
// Grow the array incrementally to avoid OutOfMemoryError in the case that a corrupt (and very
|
||||
// large) valueSize was read. In such cases the implementation below is expected to throw
|
||||
// IOException from one of the readFully calls, due to the end of the input being reached.
|
||||
int bytesRead = 0;
|
||||
int nextBytesToRead = Math.min(valueSize, INCREMENTAL_METADATA_READ_LENGTH);
|
||||
byte[] value = Util.EMPTY_BYTE_ARRAY;
|
||||
while (bytesRead != valueSize) {
|
||||
value = Arrays.copyOf(value, bytesRead + nextBytesToRead);
|
||||
input.readFully(value, bytesRead, nextBytesToRead);
|
||||
bytesRead += nextBytesToRead;
|
||||
nextBytesToRead = Math.min(valueSize - bytesRead, INCREMENTAL_METADATA_READ_LENGTH);
|
||||
}
|
||||
metadata.put(name, value);
|
||||
}
|
||||
return new DefaultContentMetadata(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes itself to a {@link DataOutputStream}.
|
||||
*
|
||||
* @param output Output stream to store the values.
|
||||
* @throws IOException If an error occurs during writing values to output.
|
||||
*/
|
||||
private static void writeContentMetadata(DefaultContentMetadata metadata, DataOutputStream output)
|
||||
throws IOException {
|
||||
Set<Map.Entry<String, byte[]>> entrySet = metadata.entrySet();
|
||||
output.writeInt(entrySet.size());
|
||||
for (Map.Entry<String, byte[]> entry : entrySet) {
|
||||
output.writeUTF(entry.getKey());
|
||||
byte[] value = entry.getValue();
|
||||
output.writeInt(value.length);
|
||||
output.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ package com.google.android.exoplayer2.upstream.cache;
|
|||
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -28,6 +25,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/** Default implementation of {@link ContentMetadata}. Values are stored as byte arrays. */
|
||||
public final class DefaultContentMetadata implements ContentMetadata {
|
||||
|
|
@ -36,39 +34,16 @@ public final class DefaultContentMetadata implements ContentMetadata {
|
|||
public static final DefaultContentMetadata EMPTY =
|
||||
new DefaultContentMetadata(Collections.emptyMap());
|
||||
|
||||
private static final int MAX_VALUE_LENGTH = 10 * 1024 * 1024;
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* Deserializes a {@link DefaultContentMetadata} from the given input stream.
|
||||
*
|
||||
* @param input Input stream to read from.
|
||||
* @return a {@link DefaultContentMetadata} instance.
|
||||
* @throws IOException If an error occurs during reading from input.
|
||||
*/
|
||||
public static DefaultContentMetadata readFromStream(DataInputStream input) throws IOException {
|
||||
int size = input.readInt();
|
||||
HashMap<String, byte[]> metadata = new HashMap<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
String name = input.readUTF();
|
||||
int valueSize = input.readInt();
|
||||
if (valueSize < 0 || valueSize > MAX_VALUE_LENGTH) {
|
||||
throw new IOException("Invalid value size: " + valueSize);
|
||||
}
|
||||
byte[] value = new byte[valueSize];
|
||||
input.readFully(value);
|
||||
metadata.put(name, value);
|
||||
}
|
||||
return new DefaultContentMetadata(metadata);
|
||||
}
|
||||
|
||||
private final Map<String, byte[]> metadata;
|
||||
|
||||
public DefaultContentMetadata() {
|
||||
this(Collections.emptyMap());
|
||||
}
|
||||
|
||||
private DefaultContentMetadata(Map<String, byte[]> metadata) {
|
||||
/** @param metadata The metadata entries in their raw byte array form. */
|
||||
public DefaultContentMetadata(Map<String, byte[]> metadata) {
|
||||
this.metadata = Collections.unmodifiableMap(metadata);
|
||||
}
|
||||
|
||||
|
|
@ -84,20 +59,9 @@ public final class DefaultContentMetadata implements ContentMetadata {
|
|||
return new DefaultContentMetadata(mutatedMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes itself to a {@link DataOutputStream}.
|
||||
*
|
||||
* @param output Output stream to store the values.
|
||||
* @throws IOException If an error occurs during writing values to output.
|
||||
*/
|
||||
public void writeToStream(DataOutputStream output) throws IOException {
|
||||
output.writeInt(metadata.size());
|
||||
for (Entry<String, byte[]> entry : metadata.entrySet()) {
|
||||
output.writeUTF(entry.getKey());
|
||||
byte[] value = entry.getValue();
|
||||
output.writeInt(value.length);
|
||||
output.write(value);
|
||||
}
|
||||
/** Returns the set of metadata entries in their raw byte array form. */
|
||||
public Set<Entry<String, byte[]>> entrySet() {
|
||||
return metadata.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -190,18 +154,7 @@ public final class DefaultContentMetadata implements ContentMetadata {
|
|||
|
||||
private static void addValues(HashMap<String, byte[]> metadata, Map<String, Object> values) {
|
||||
for (String name : values.keySet()) {
|
||||
Object value = values.get(name);
|
||||
byte[] bytes = getBytes(value);
|
||||
if (bytes.length > MAX_VALUE_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"The size of "
|
||||
+ name
|
||||
+ " ("
|
||||
+ bytes.length
|
||||
+ ") is greater than maximum allowed: "
|
||||
+ MAX_VALUE_LENGTH);
|
||||
}
|
||||
metadata.put(name, bytes);
|
||||
metadata.put(name, getBytes(values.get(name)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ package com.google.android.exoplayer2.upstream.cache;
|
|||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
@ -133,24 +129,6 @@ public class DefaultContentMetadataTest {
|
|||
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeDeserialize() throws Exception {
|
||||
byte[] metadata3 = {1, 2, 3};
|
||||
contentMetadata =
|
||||
createContentMetadata(
|
||||
"metadata1 name", "value", "metadata2 name", 12345, "metadata3 name", metadata3);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
contentMetadata.writeToStream(new DataOutputStream(outputStream));
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
DefaultContentMetadata contentMetadata2 =
|
||||
DefaultContentMetadata.readFromStream(new DataInputStream(inputStream));
|
||||
|
||||
assertThat(contentMetadata2.get("metadata1 name", "default value")).isEqualTo("value");
|
||||
assertThat(contentMetadata2.get("metadata2 name", 0)).isEqualTo(12345);
|
||||
assertThat(contentMetadata2.get("metadata3 name", new byte[] {})).isEqualTo(metadata3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsStringValues() throws Exception {
|
||||
DefaultContentMetadata metadata1 = createContentMetadata("metadata1", "value");
|
||||
|
|
|
|||
Loading…
Reference in a new issue