diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java index 0724755ac7..e37e09a090 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java @@ -29,6 +29,8 @@ import java.io.InputStream; */ public final class ActionFile { + /* package */ static final int VERSION = 0; + private final AtomicFile atomicFile; private final File actionFile; @@ -56,13 +58,13 @@ public final class ActionFile { inputStream = atomicFile.openRead(); DataInputStream dataInputStream = new DataInputStream(inputStream); int version = dataInputStream.readInt(); - if (version > DownloadAction.MASTER_VERSION) { - throw new IOException("Not supported action file version: " + version); + if (version > VERSION) { + throw new IOException("Unsupported action file version: " + version); } int actionCount = dataInputStream.readInt(); DownloadAction[] actions = new DownloadAction[actionCount]; for (int i = 0; i < actionCount; i++) { - actions[i] = DownloadAction.deserializeFromStream(deserializers, dataInputStream, version); + actions[i] = DownloadAction.deserializeFromStream(deserializers, dataInputStream); } return actions; } finally { @@ -80,7 +82,7 @@ public final class ActionFile { DataOutputStream output = null; try { output = new DataOutputStream(atomicFile.startWrite()); - output.writeInt(DownloadAction.MASTER_VERSION); + output.writeInt(VERSION); output.writeInt(downloadActions.length); for (DownloadAction action : downloadActions) { DownloadAction.serializeToStream(action, output); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java index 9c00534aeb..126a42cef5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java @@ -26,29 +26,23 @@ import java.io.OutputStream; /** Contains the necessary parameters for a download or remove action. */ public abstract class DownloadAction { - /** - * Master version for all {@link DownloadAction} serialization/deserialization implementations. On - * each change on any {@link DownloadAction} serialization format this version needs to be - * increased. - */ - public static final int MASTER_VERSION = 0; - /** Used to deserialize {@link DownloadAction}s. */ public abstract static class Deserializer { - public String type; + public final String type; + public final int version; - public Deserializer(String type) { + public Deserializer(String type, int version) { this.type = type; + this.version = version; } /** * Deserializes an action from the {@code input}. * - * @param version Version of the data. - * @param input DataInputStream to read data from. + * @param version The version of the serialized action. + * @param input The stream from which to read the action. * @see DownloadAction#writeToStream(DataOutputStream) - * @see DownloadAction#MASTER_VERSION */ public abstract DownloadAction readFromStream(int version, DataInputStream input) throws IOException; @@ -62,42 +56,23 @@ public abstract class DownloadAction { *

The caller is responsible for closing the given {@link InputStream}. * * @param deserializers {@link Deserializer}s for supported actions. - * @param input Input stream to read serialized data. + * @param input The stream from which to read the action. * @return The deserialized action. * @throws IOException If there is an IO error reading from {@code input}, or if the action type * isn't supported by any of the {@code deserializers}. */ public static DownloadAction deserializeFromStream( Deserializer[] deserializers, InputStream input) throws IOException { - return deserializeFromStream(deserializers, input, MASTER_VERSION); - } - - /** - * Deserializes one {@code action} which was serialized by {@link - * #serializeToStream(DownloadAction, OutputStream)} from the {@code input} using one of the - * {@link Deserializer}s which supports the type of the action. - * - *

The caller is responsible for closing the given {@link InputStream}. - * - * @param deserializers {@link Deserializer}s for supported actions. - * @param input Input stream to read serialized data. - * @param version Master version of the serialization. See {@link DownloadAction#MASTER_VERSION}. - * @return The deserialized action. - * @throws IOException If there is an IO error from {@code input}. - * @throws DownloadException If the action type isn't supported by any of the {@code - * deserializers}. - */ - public static DownloadAction deserializeFromStream( - Deserializer[] deserializers, InputStream input, int version) throws IOException { // Don't close the stream as it closes the underlying stream too. DataInputStream dataInputStream = new DataInputStream(input); String type = dataInputStream.readUTF(); + int version = dataInputStream.readInt(); for (Deserializer deserializer : deserializers) { - if (type.equals(deserializer.type)) { + if (type.equals(deserializer.type) && deserializer.version >= version) { return deserializer.readFromStream(version, dataInputStream); } } - throw new DownloadException("No Deserializer can be found to parse the data."); + throw new DownloadException("No deserializer found for:" + type + ", " + version); } /** Serializes {@code action} type and data into the {@code output}. */ @@ -106,12 +81,15 @@ public abstract class DownloadAction { // Don't close the stream as it closes the underlying stream too. DataOutputStream dataOutputStream = new DataOutputStream(output); dataOutputStream.writeUTF(action.type); + dataOutputStream.writeInt(action.version); action.writeToStream(dataOutputStream); dataOutputStream.flush(); } /** The type of the action. */ public final String type; + /** The action version. */ + public final int version; /** Whether this is a remove action. If false, this is a download action. */ public final boolean isRemoveAction; /** Custom data for this action. May be the empty string if no custom data was specified. */ @@ -119,11 +97,14 @@ public abstract class DownloadAction { /** * @param type The type of the action. + * @param version The action version. * @param isRemoveAction Whether this is a remove action. If false, this is a download action. * @param data Optional custom data for this action. If null, an empty string is used. */ - protected DownloadAction(String type, boolean isRemoveAction, @Nullable String data) { + protected DownloadAction( + String type, int version, boolean isRemoveAction, @Nullable String data) { this.type = type; + this.version = version; this.isRemoveAction = isRemoveAction; this.data = data != null ? data : ""; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadAction.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadAction.java index 99c21b47cf..4ccca775ef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadAction.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadAction.java @@ -28,9 +28,10 @@ import java.io.IOException; public final class ProgressiveDownloadAction extends DownloadAction { private static final String TYPE = "ProgressiveDownloadAction"; + private static final int VERSION = 0; public static final Deserializer DESERIALIZER = - new Deserializer(TYPE) { + new Deserializer(TYPE, VERSION) { @Override public ProgressiveDownloadAction readFromStream(int version, DataInputStream input) throws IOException { @@ -54,7 +55,7 @@ public final class ProgressiveDownloadAction extends DownloadAction { */ public ProgressiveDownloadAction( boolean isRemoveAction, @Nullable String data, Uri uri, @Nullable String customCacheKey) { - super(TYPE, isRemoveAction, data); + super(TYPE, VERSION, isRemoveAction, data); this.uri = Assertions.checkNotNull(uri); this.customCacheKey = customCacheKey; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloadAction.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloadAction.java index 453a744de6..c51393fc44 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloadAction.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloadAction.java @@ -37,8 +37,8 @@ public abstract class SegmentDownloadAction extends Downlo */ protected abstract static class SegmentDownloadActionDeserializer extends Deserializer { - public SegmentDownloadActionDeserializer(String type) { - super(type); + public SegmentDownloadActionDeserializer(String type, int version) { + super(type, version); } @Override @@ -71,15 +71,21 @@ public abstract class SegmentDownloadAction extends Downlo /** * @param type The type of the action. - * @param data Optional custom data for this action. If null, an empty string is used. + * @param version The action version. * @param isRemoveAction Whether the data will be removed. If {@code false} it will be downloaded. + * @param data Optional custom data for this action. If null, an empty string is used. * @param manifestUri The {@link Uri} of the manifest to be downloaded. * @param keys Keys of representations to be downloaded. If empty, all representations are * downloaded. If {@code removeAction} is true, {@code keys} must be an empty array. */ protected SegmentDownloadAction( - String type, boolean isRemoveAction, @Nullable String data, Uri manifestUri, K[] keys) { - super(type, isRemoveAction, data); + String type, + int version, + boolean isRemoveAction, + @Nullable String data, + Uri manifestUri, + K[] keys) { + super(type, version, isRemoveAction, data); this.manifestUri = manifestUri; if (isRemoveAction) { Assertions.checkArgument(keys.length == 0); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java index 24fcf487e9..3fd794f16b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java @@ -63,7 +63,7 @@ public class ActionFileTest { @Test public void testLoadIncompleteHeaderThrowsIOException() throws Exception { try { - loadActions(new Object[] {DownloadAction.MASTER_VERSION}); + loadActions(new Object[] {ActionFile.VERSION}); Assert.fail(); } catch (IOException e) { // Expected exception. @@ -72,8 +72,7 @@ public class ActionFileTest { @Test public void testLoadCompleteHeaderZeroAction() throws Exception { - DownloadAction[] actions = - loadActions(new Object[] {DownloadAction.MASTER_VERSION, /*action count*/0}); + DownloadAction[] actions = loadActions(new Object[] {ActionFile.VERSION, 0}); assertThat(actions).isNotNull(); assertThat(actions).hasLength(0); } @@ -83,12 +82,16 @@ public class ActionFileTest { DownloadAction[] actions = loadActions( new Object[] { - DownloadAction.MASTER_VERSION, /*action count*/ 1, /*action 1*/ "type2", "321" + ActionFile.VERSION, + 1, // Action count + "type2", // Action 1 + FakeDownloadAction.VERSION, + "321" }, new FakeDeserializer("type2")); assertThat(actions).isNotNull(); assertThat(actions).hasLength(1); - assertAction(actions[0], "type2", DownloadAction.MASTER_VERSION, "321"); + assertAction(actions[0], "type2", FakeDownloadAction.VERSION, "321"); } @Test @@ -96,26 +99,53 @@ public class ActionFileTest { DownloadAction[] actions = loadActions( new Object[] { - DownloadAction.MASTER_VERSION, /*action count*/ - 2, /*action 1*/ - "type1", + ActionFile.VERSION, + 2, // Action count + "type1", // Action 1 + FakeDownloadAction.VERSION, "123", - /*action 2*/ "type2", + "type2", // Action 2 + FakeDownloadAction.VERSION, "321" - }, // Action 2 + }, new FakeDeserializer("type1"), new FakeDeserializer("type2")); assertThat(actions).isNotNull(); assertThat(actions).hasLength(2); - assertAction(actions[0], "type1", DownloadAction.MASTER_VERSION, "123"); - assertAction(actions[1], "type2", DownloadAction.MASTER_VERSION, "321"); + assertAction(actions[0], "type1", FakeDownloadAction.VERSION, "123"); + assertAction(actions[1], "type2", FakeDownloadAction.VERSION, "321"); } @Test public void testLoadNotSupportedVersion() throws Exception { try { - loadActions(new Object[] {DownloadAction.MASTER_VERSION + 1, /*action count*/1, - /*action 1*/"type2", 321}, new FakeDeserializer("type2")); + loadActions( + new Object[] { + ActionFile.VERSION + 1, + 1, // Action count + "type2", // Action 1 + FakeDownloadAction.VERSION, + 321 + }, + new FakeDeserializer("type2")); + Assert.fail(); + } catch (IOException e) { + // Expected exception. + } + } + + @Test + public void testLoadNotSupportedActionVersion() throws Exception { + try { + loadActions( + new Object[] { + ActionFile.VERSION, + 1, // Action count + "type2", // Action 1 + FakeDownloadAction.VERSION + 1, + 321 + }, + new FakeDeserializer("type2")); Assert.fail(); } catch (IOException e) { // Expected exception. @@ -125,8 +155,15 @@ public class ActionFileTest { @Test public void testLoadNotSupportedType() throws Exception { try { - loadActions(new Object[] {DownloadAction.MASTER_VERSION, /*action count*/1, - /*action 1*/"type2", 321}, new FakeDeserializer("type1")); + loadActions( + new Object[] { + ActionFile.VERSION, + 1, // Action count + "type2", // Action 1 + FakeDownloadAction.VERSION, + 321 + }, + new FakeDeserializer("type1")); Assert.fail(); } catch (DownloadException e) { // Expected exception. @@ -142,8 +179,7 @@ public class ActionFileTest { public void testStoreAndLoadActions() throws Exception { doTestSerializationRoundTrip( new DownloadAction[] { - new FakeDownloadAction("type1", DownloadAction.MASTER_VERSION, "123"), - new FakeDownloadAction("type2", DownloadAction.MASTER_VERSION, "321"), + new FakeDownloadAction("type1", "123"), new FakeDownloadAction("type2", "321"), }, new FakeDeserializer("type1"), new FakeDeserializer("type2")); @@ -186,22 +222,21 @@ public class ActionFileTest { private static class FakeDeserializer extends Deserializer { FakeDeserializer(String type) { - super(type); + super(type, FakeDownloadAction.VERSION); } @Override public DownloadAction readFromStream(int version, DataInputStream input) throws IOException { - return new FakeDownloadAction(type, version, input.readUTF()); + return new FakeDownloadAction(type, input.readUTF()); } } private static class FakeDownloadAction extends DownloadAction { - public final int version; + public static final int VERSION = 0; - private FakeDownloadAction(String type, int version, String data) { - super(type, false, data); - this.version = version; + private FakeDownloadAction(String type, String data) { + super(type, VERSION, /* isRemoveAction= */ false, data); } @Override diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index 6f0af064a0..53cb7e3cdf 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -535,7 +535,7 @@ public class DownloadManagerTest { private final BlockingQueue states; private FakeDownloadAction(boolean isRemoveAction, @Nullable String mediaId) { - super("FakeDownloadAction", isRemoveAction, mediaId); + super("FakeDownloadAction", /* version= */ 0, isRemoveAction, mediaId); this.mediaId = mediaId; this.downloader = new FakeDownloader(isRemoveAction); this.states = new ArrayBlockingQueue<>(10); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloadActionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloadActionTest.java index f93c79c0f3..81e061b3df 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloadActionTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ProgressiveDownloadActionTest.java @@ -153,18 +153,19 @@ public class ProgressiveDownloadActionTest { assertThat(action2.isSameMedia(action1)).isFalse(); } - private static void doTestSerializationRoundTrip(ProgressiveDownloadAction action1) + private static void doTestSerializationRoundTrip(ProgressiveDownloadAction action) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream output = new DataOutputStream(out); - action1.writeToStream(output); + DownloadAction.serializeToStream(action, output); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); DataInputStream input = new DataInputStream(in); DownloadAction action2 = - ProgressiveDownloadAction.DESERIALIZER.readFromStream(DownloadAction.MASTER_VERSION, input); + DownloadAction.deserializeFromStream( + new DownloadAction.Deserializer[] {ProgressiveDownloadAction.DESERIALIZER}, input); - assertThat(action2).isEqualTo(action1); + assertThat(action2).isEqualTo(action); } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java index 0c5e5a01af..61e063cf91 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java @@ -29,9 +29,10 @@ import java.io.IOException; public final class DashDownloadAction extends SegmentDownloadAction { private static final String TYPE = "DashDownloadAction"; + private static final int VERSION = 0; public static final Deserializer DESERIALIZER = - new SegmentDownloadActionDeserializer(TYPE) { + new SegmentDownloadActionDeserializer(TYPE, VERSION) { @Override protected RepresentationKey readKey(DataInputStream input) throws IOException { @@ -51,11 +52,12 @@ public final class DashDownloadAction extends SegmentDownloadAction { private static final String TYPE = "HlsDownloadAction"; + private static final int VERSION = 0; public static final Deserializer DESERIALIZER = - new SegmentDownloadActionDeserializer(TYPE) { + new SegmentDownloadActionDeserializer(TYPE, VERSION) { @Override protected RenditionKey readKey(DataInputStream input) throws IOException { @@ -51,11 +52,12 @@ public final class HlsDownloadAction extends SegmentDownloadAction }; /** - * @see SegmentDownloadAction#SegmentDownloadAction(String, boolean, String, Uri, Comparable[]) + * @see SegmentDownloadAction#SegmentDownloadAction(String, int, boolean, String, Uri, + * Comparable[]) */ public HlsDownloadAction( boolean isRemoveAction, @Nullable String data, Uri manifestUri, RenditionKey... keys) { - super(TYPE, isRemoveAction, data, manifestUri, keys); + super(TYPE, VERSION, isRemoveAction, data, manifestUri, keys); } @Override diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java index 9dc3f69767..9c90f92938 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java @@ -29,9 +29,10 @@ import java.io.IOException; public final class SsDownloadAction extends SegmentDownloadAction { private static final String TYPE = "SsDownloadAction"; + private static final int VERSION = 0; public static final Deserializer DESERIALIZER = - new SegmentDownloadActionDeserializer(TYPE) { + new SegmentDownloadActionDeserializer(TYPE, VERSION) { @Override protected TrackKey readKey(DataInputStream input) throws IOException { @@ -51,11 +52,12 @@ public final class SsDownloadAction extends SegmentDownloadAction { }; /** - * @see SegmentDownloadAction#SegmentDownloadAction(String, boolean, String, Uri, Comparable[]) + * @see SegmentDownloadAction#SegmentDownloadAction(String, int, boolean, String, Uri, + * Comparable[]) */ public SsDownloadAction( boolean isRemoveAction, @Nullable String data, Uri manifestUri, TrackKey... keys) { - super(TYPE, isRemoveAction, data, manifestUri, keys); + super(TYPE, VERSION, isRemoveAction, data, manifestUri, keys); } @Override