Commit 675b81e5 by olly Committed by Ian Baker

DataSource: Tighten contract to return -1 on premature end-of-input

PiperOrigin-RevId: 363001266
parent 6c9f9f9d
......@@ -33,7 +33,6 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
......@@ -478,10 +477,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
int read = castNonNull(responseByteStream).read(buffer, offset, readLength);
if (read == -1) {
if (bytesToRead != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new EOFException();
}
return C.RESULT_END_OF_INPUT;
}
......
......@@ -29,7 +29,6 @@ import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
......@@ -692,10 +691,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
int read = castNonNull(inputStream).read(buffer, offset, readLength);
if (read == -1) {
if (bytesToRead != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new EOFException();
}
return C.RESULT_END_OF_INPUT;
}
......
......@@ -108,7 +108,6 @@ public final class ProgressiveDownloader implements Downloader {
new CacheWriter(
dataSource,
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
progressListener);
priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager();
......
......@@ -472,7 +472,6 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
new CacheWriter(
dataSource,
segment.dataSpec,
/* allowShortContent= */ false,
temporaryBuffer,
progressNotifier);
}
......
......@@ -24,7 +24,6 @@ import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
......@@ -111,10 +110,6 @@ public final class AssetDataSource extends BaseDataSource {
}
if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new AssetDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
......
......@@ -24,7 +24,6 @@ import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
......@@ -147,10 +146,6 @@ public final class ContentDataSource extends BaseDataSource {
}
if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new ContentDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
......
......@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.PriorityTaskManager;
import com.google.android.exoplayer2.util.PriorityTaskManager.PriorityTooLowException;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
......@@ -51,7 +50,6 @@ public final class CacheWriter {
private final CacheDataSource dataSource;
private final Cache cache;
private final DataSpec dataSpec;
private final boolean allowShortContent;
private final String cacheKey;
private final byte[] temporaryBuffer;
@Nullable private final ProgressListener progressListener;
......@@ -65,10 +63,6 @@ public final class CacheWriter {
/**
* @param dataSource A {@link CacheDataSource} that writes to the target cache.
* @param dataSpec Defines the data to be written.
* @param allowShortContent Whether it's allowed for the content to end before the request as
* defined by the {@link DataSpec}. If {@code true} and the request exceeds the length of the
* content, then the content will be cached to the end. If {@code false} and the request
* exceeds the length of the content, {@link #cache} will throw an {@link IOException}.
* @param temporaryBuffer A temporary buffer to be used during caching, or {@code null} if the
* writer should instantiate its own internal temporary buffer.
* @param progressListener An optional progress listener.
......@@ -76,13 +70,11 @@ public final class CacheWriter {
public CacheWriter(
CacheDataSource dataSource,
DataSpec dataSpec,
boolean allowShortContent,
@Nullable byte[] temporaryBuffer,
@Nullable ProgressListener progressListener) {
this.dataSource = dataSource;
this.cache = dataSource.getCache();
this.dataSpec = dataSpec;
this.allowShortContent = allowShortContent;
this.temporaryBuffer =
temporaryBuffer == null ? new byte[DEFAULT_BUFFER_SIZE_BYTES] : temporaryBuffer;
this.progressListener = progressListener;
......@@ -143,14 +135,6 @@ public final class CacheWriter {
nextPosition += readBlockToCache(nextPosition, nextRequestLength);
}
}
// TODO: Remove allowShortContent parameter, this code block, and return the number of bytes
// cached. The caller can then check whether fewer bytes were cached than were requested.
if (!allowShortContent
&& dataSpec.length != C.LENGTH_UNSET
&& nextPosition != dataSpec.position + dataSpec.length) {
throw new EOFException();
}
}
/**
......@@ -176,9 +160,11 @@ public final class CacheWriter {
isDataSourceOpen = true;
} catch (IOException e) {
Util.closeQuietly(dataSource);
if (allowShortContent
&& isLastBlock
&& DataSourceException.isCausedByPositionOutOfRange(e)) {
// TODO: This exception handling may be required for interop with current HttpDataSource
// implementations that (incorrectly) throw a position-out-of-range when opened exactly one
// byte beyond the end of the resource. It should be removed when the HttpDataSource
// implementations are fixed.
if (isLastBlock && DataSourceException.isCausedByPositionOutOfRange(e)) {
// The length of the request exceeds the length of the content. If we allow shorter
// content and are reading the last block, fall through and try again with an unbounded
// request to read up to the end of the content.
......
......@@ -364,7 +364,6 @@ public final class CacheDataSourceTest {
new CacheWriter(
new CacheDataSource(cache, upstream2),
unboundedDataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
/* progressListener= */ null);
cacheWriter.cache();
......@@ -414,7 +413,6 @@ public final class CacheDataSourceTest {
new CacheWriter(
new CacheDataSource(cache, upstream2),
unboundedDataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
/* progressListener= */ null);
cacheWriter.cache();
......@@ -439,7 +437,6 @@ public final class CacheDataSourceTest {
new CacheWriter(
new CacheDataSource(cache, upstream),
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
/* progressListener= */ null);
cacheWriter.cache();
......@@ -476,7 +473,6 @@ public final class CacheDataSourceTest {
new CacheWriter(
new CacheDataSource(cache, upstream),
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
/* progressListener= */ null);
cacheWriter.cache();
......
......@@ -30,7 +30,6 @@ import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
......@@ -72,7 +71,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(Uri.parse("test_data")),
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -94,7 +92,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -106,7 +103,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(testUri),
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -129,7 +125,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -153,7 +148,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -165,7 +159,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(testUri),
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -187,7 +180,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
dataSpec,
/* allowShortContent= */ true,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......@@ -197,24 +189,6 @@ public final class CacheWriterTest {
}
@Test
public void cacheThrowEOFException() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, /* position= */ 0, /* length= */ 1000);
CacheWriter cacheWriter =
new CacheWriter(
new CacheDataSource(cache, dataSource),
dataSpec,
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
/* progressListener= */ null);
assertThrows(EOFException.class, cacheWriter::cache);
}
@Test
public void cache_afterFailureOnClose_succeeds() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource upstreamDataSource = new FakeDataSource(fakeDataSet);
......@@ -237,7 +211,6 @@ public final class CacheWriterTest {
new CacheWriter(
cacheDataSource,
new DataSpec(Uri.parse("test_data")),
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
......@@ -276,7 +249,6 @@ public final class CacheWriterTest {
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(Uri.parse("test_data")),
/* allowShortContent= */ false,
/* temporaryBuffer= */ null,
counters);
cacheWriter.cache();
......
......@@ -232,24 +232,27 @@ public abstract class DataSourceContractTest {
DataSpec dataSpec =
new DataSpec.Builder().setUri(resource.getUri()).setPosition(resourceLength).build();
try {
long length = dataSource.open(dataSpec);
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(0);
}
try {
byte[] data =
unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource);
assertThat(data).isEmpty();
long length = dataSource.open(dataSpec);
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(0);
}
try {
byte[] data =
unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource);
assertThat(data).isEmpty();
} catch (IOException e) {
// TODO: Remove this catch once the one below is removed.
throw new RuntimeException(e);
}
} catch (IOException e) {
// TODO: Remove this catch and require that implementations do not throw.
assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue();
}
} catch (IOException e) {
// TODO: Remove this catch and require that implementations do not throw.
assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue();
} finally {
dataSource.close();
}
......@@ -275,22 +278,25 @@ public abstract class DataSourceContractTest {
.setLength(1)
.build();
try {
long length = dataSource.open(dataSpec);
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
assertThat(length).isEqualTo(1);
try {
byte[] data =
unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource);
assertThat(data).isEmpty();
long length = dataSource.open(dataSpec);
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
assertThat(length).isEqualTo(1);
try {
byte[] data =
unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource);
assertThat(data).isEmpty();
} catch (IOException e) {
// TODO: Remove this catch once the one below is removed.
throw new RuntimeException(e);
}
} catch (IOException e) {
// TODO: Remove this catch and require that implementations do not throw.
assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue();
}
} catch (IOException e) {
// TODO: Remove this catch and require that implementations do not throw.
assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue();
} finally {
dataSource.close();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment