forked from google/ExoPlayer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ActionFile which stores and loads DownloadActions to/from a file.
This change also replaces individual DownloadAction versions with a single master version. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=171273880
- Loading branch information
1 parent
d5101d8
commit 10f8192
Showing
4 changed files
with
355 additions
and
2 deletions.
There are no files selected for viewing
95 changes: 95 additions & 0 deletions
95
library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* Copyright (C) 2017 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.offline; | ||
|
||
import com.google.android.exoplayer2.offline.DownloadAction.Deserializer; | ||
import com.google.android.exoplayer2.util.AtomicFile; | ||
import com.google.android.exoplayer2.util.ClosedSource; | ||
import com.google.android.exoplayer2.util.Util; | ||
import java.io.DataInputStream; | ||
import java.io.DataOutputStream; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
||
/** | ||
* Stores and loads {@link DownloadAction}s to/from a file. | ||
*/ | ||
@ClosedSource(reason = "Not ready yet") | ||
public final class ActionFile { | ||
|
||
private final AtomicFile atomicFile; | ||
|
||
/** | ||
* @param actionFile File to be used to store and load {@link DownloadAction}s. | ||
*/ | ||
public ActionFile(File actionFile) { | ||
atomicFile = new AtomicFile(actionFile); | ||
} | ||
|
||
/** | ||
* Loads {@link DownloadAction}s from file. | ||
* | ||
* @param deserializers {@link Deserializer}s to deserialize DownloadActions. | ||
* @return Loaded DownloadActions. | ||
* @throws IOException If there is an error during loading. | ||
*/ | ||
public DownloadAction[] load(Deserializer... deserializers) throws IOException { | ||
InputStream inputStream = null; | ||
try { | ||
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); | ||
} | ||
int actionCount = dataInputStream.readInt(); | ||
DownloadAction[] actions = new DownloadAction[actionCount]; | ||
for (int i = 0; i < actionCount; i++) { | ||
actions[i] = DownloadAction.deserializeFromStream(deserializers, dataInputStream, version); | ||
} | ||
return actions; | ||
} finally { | ||
Util.closeQuietly(inputStream); | ||
} | ||
} | ||
|
||
/** | ||
* Stores {@link DownloadAction}s to file. | ||
* | ||
* @param downloadActions DownloadActions to store to file. | ||
* @throws IOException If there is an error during storing. | ||
*/ | ||
public void store(DownloadAction... downloadActions) throws IOException { | ||
OutputStream outputStream = null; | ||
try { | ||
outputStream = atomicFile.startWrite(); | ||
DataOutputStream dataOutputStream = new DataOutputStream(outputStream); | ||
dataOutputStream.writeInt(DownloadAction.MASTER_VERSION); | ||
dataOutputStream.writeInt(downloadActions.length); | ||
for (DownloadAction action : downloadActions) { | ||
DownloadAction.serializeToStream(action, dataOutputStream); | ||
} | ||
atomicFile.endWrite(outputStream); | ||
// Avoid calling close twice. | ||
outputStream = null; | ||
} finally { | ||
Util.closeQuietly(outputStream); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
253 changes: 253 additions & 0 deletions
253
library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
/* | ||
* Copyright (C) 2017 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.offline; | ||
|
||
import static com.google.common.truth.Truth.assertThat; | ||
|
||
import com.google.android.exoplayer2.offline.DownloadAction.Deserializer; | ||
import com.google.android.exoplayer2.util.ClosedSource; | ||
import com.google.android.exoplayer2.util.Util; | ||
import java.io.DataInputStream; | ||
import java.io.DataOutputStream; | ||
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import org.junit.After; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.robolectric.RobolectricTestRunner; | ||
import org.robolectric.RuntimeEnvironment; | ||
import org.robolectric.annotation.Config; | ||
|
||
/** | ||
* Unit tests for {@link ProgressiveDownloadAction}. | ||
*/ | ||
@ClosedSource(reason = "Not ready yet") | ||
@RunWith(RobolectricTestRunner.class) | ||
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) | ||
public class ActionFileTest { | ||
|
||
private File tempFile; | ||
|
||
@Before | ||
public void setUp() throws Exception { | ||
tempFile = Util.createTempFile(RuntimeEnvironment.application, "ExoPlayerTest"); | ||
} | ||
|
||
@After | ||
public void tearDown() throws Exception { | ||
tempFile.delete(); | ||
} | ||
|
||
@Test | ||
public void testLoadNoDataThrowsIOException() throws Exception { | ||
try { | ||
loadActions(new Object[] {}); | ||
Assert.fail(); | ||
} catch (IOException e) { | ||
// Expected exception. | ||
} | ||
} | ||
|
||
@Test | ||
public void testLoadIncompleteHeaderThrowsIOException() throws Exception { | ||
try { | ||
loadActions(new Object[] {DownloadAction.MASTER_VERSION}); | ||
Assert.fail(); | ||
} catch (IOException e) { | ||
// Expected exception. | ||
} | ||
} | ||
|
||
@Test | ||
public void testLoadCompleteHeaderZeroAction() throws Exception { | ||
DownloadAction[] actions = | ||
loadActions(new Object[] {DownloadAction.MASTER_VERSION, /*action count*/0}); | ||
assertThat(actions).isNotNull(); | ||
assertThat(actions).hasLength(0); | ||
} | ||
|
||
@Test | ||
public void testLoadAction() throws Exception { | ||
DownloadAction[] actions = loadActions( | ||
new Object[] {DownloadAction.MASTER_VERSION, /*action count*/1, /*action 1*/"type2", 321}, | ||
new FakeDeserializer("type2")); | ||
assertThat(actions).isNotNull(); | ||
assertThat(actions).hasLength(1); | ||
assertAction(actions[0], "type2", DownloadAction.MASTER_VERSION, 321); | ||
} | ||
|
||
@Test | ||
public void testLoadActions() throws Exception { | ||
DownloadAction[] actions = loadActions( | ||
new Object[] {DownloadAction.MASTER_VERSION, /*action count*/2, /*action 1*/"type1", 123, | ||
/*action 2*/"type2", 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); | ||
} | ||
|
||
@Test | ||
public void testLoadNotSupportedVersion() throws Exception { | ||
try { | ||
loadActions(new Object[] {DownloadAction.MASTER_VERSION + 1, /*action count*/1, | ||
/*action 1*/"type2", 321}, new FakeDeserializer("type2")); | ||
Assert.fail(); | ||
} catch (IOException e) { | ||
// Expected exception. | ||
} | ||
} | ||
|
||
@Test | ||
public void testLoadNotSupportedType() throws Exception { | ||
try { | ||
loadActions(new Object[] {DownloadAction.MASTER_VERSION, /*action count*/1, | ||
/*action 1*/"type2", 321}, new FakeDeserializer("type1")); | ||
Assert.fail(); | ||
} catch (DownloadException e) { | ||
// Expected exception. | ||
} | ||
} | ||
|
||
@Test | ||
public void testStoreAndLoadNoActions() throws Exception { | ||
doTestSerializationRoundTrip(new DownloadAction[0]); | ||
} | ||
|
||
@Test | ||
public void testStoreAndLoadActions() throws Exception { | ||
doTestSerializationRoundTrip(new DownloadAction[] { | ||
new FakeDownloadAction("type1", DownloadAction.MASTER_VERSION, 123), | ||
new FakeDownloadAction("type2", DownloadAction.MASTER_VERSION, 321), | ||
}, new FakeDeserializer("type1"), new FakeDeserializer("type2")); | ||
} | ||
|
||
private void doTestSerializationRoundTrip(DownloadAction[] actions, | ||
Deserializer... deserializers) throws IOException { | ||
ActionFile actionFile = new ActionFile(tempFile); | ||
actionFile.store(actions); | ||
assertThat(actionFile.load(deserializers)).isEqualTo(actions); | ||
} | ||
|
||
private DownloadAction[] loadActions(Object[] values, Deserializer... deserializers) | ||
throws IOException { | ||
FileOutputStream fileOutputStream = new FileOutputStream(tempFile); | ||
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); | ||
try { | ||
for (Object value : values) { | ||
if (value instanceof Integer) { | ||
dataOutputStream.writeInt((Integer) value); // Action count | ||
} else if (value instanceof String) { | ||
dataOutputStream.writeUTF((String) value); // Action count | ||
} else { | ||
throw new IllegalArgumentException(); | ||
} | ||
} | ||
} finally { | ||
dataOutputStream.close(); | ||
} | ||
return new ActionFile(tempFile).load(deserializers); | ||
} | ||
|
||
private static void assertAction(DownloadAction action, String type, int version, int data) { | ||
assertThat(action).isInstanceOf(FakeDownloadAction.class); | ||
assertThat(action.getType()).isEqualTo(type); | ||
assertThat(((FakeDownloadAction) action).version).isEqualTo(version); | ||
assertThat(((FakeDownloadAction) action).data).isEqualTo(data); | ||
} | ||
|
||
private static class FakeDeserializer implements Deserializer { | ||
final String type; | ||
|
||
FakeDeserializer(String type) { | ||
this.type = type; | ||
} | ||
|
||
@Override | ||
public String getType() { | ||
return type; | ||
} | ||
|
||
@Override | ||
public DownloadAction readFromStream(int version, DataInputStream input) throws IOException { | ||
return new FakeDownloadAction(type, version, input.readInt()); | ||
} | ||
} | ||
|
||
private static class FakeDownloadAction extends DownloadAction { | ||
final String type; | ||
final int version; | ||
final int data; | ||
|
||
private FakeDownloadAction(String type, int version, int data) { | ||
this.type = type; | ||
this.version = version; | ||
this.data = data; | ||
} | ||
|
||
@Override | ||
protected String getType() { | ||
return type; | ||
} | ||
|
||
@Override | ||
protected void writeToStream(DataOutputStream output) throws IOException { | ||
output.writeInt(data); | ||
} | ||
|
||
@Override | ||
protected boolean isRemoveAction() { | ||
return false; | ||
} | ||
|
||
@Override | ||
protected boolean isSameMedia(DownloadAction other) { | ||
return false; | ||
} | ||
|
||
@Override | ||
protected Downloader createDownloader(DownloaderConstructorHelper downloaderConstructorHelper) { | ||
return null; | ||
} | ||
|
||
// auto generated code | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
FakeDownloadAction that = (FakeDownloadAction) o; | ||
return version == that.version && data == that.data && type.equals(that.type); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int result = type.hashCode(); | ||
result = 31 * result + version; | ||
result = 31 * result + data; | ||
return result; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters