From 2b15e74a34e61012447c6ea8db388c7dc00aabef Mon Sep 17 00:00:00 2001 From: Ricardo Lage Date: Tue, 28 Nov 2017 12:00:08 +0100 Subject: [PATCH 1/5] Add failing test --- .../external/fs3/FSAllOperationTest.java | 17 ++++++++++++++ .../fs3/filesystem/CrashOnReadFileSystem.java | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/CrashOnReadFileSystem.java diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java index d36217ae..642a49ea 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java @@ -1,6 +1,7 @@ package com.nytimes.android.external.fs3; +import com.nytimes.android.external.fs3.filesystem.CrashOnReadFileSystem; import com.nytimes.android.external.fs3.filesystem.FileSystem; import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; @@ -45,6 +46,22 @@ public void readAll() throws IOException { assertThat(observable.blockingLast().readUtf8()).isEqualTo(CHALLAH_CHALLAH); } + @Test + public void readAllWithCrash() throws IOException { + File tempDir = createTempDir(); + FileSystem fileSystem = new CrashOnReadFileSystem(tempDir); + + //write different data to File System for each barcode + fileSystem.write(FOLDER + "/key.txt", source(CHALLAH)); + fileSystem.write(FOLDER + "/crash_key.txt", source(CHALLAH)); + fileSystem.write(FOLDER + "/" + INNER_FOLDER + "/key2.txt", source(CHALLAH_CHALLAH)); + FSAllReader reader = new FSAllReader(fileSystem); + //read back all values for the FOLDER + Observable observable = reader.readAll(FOLDER); + assertThat(observable.blockingFirst().readUtf8()).isEqualTo(CHALLAH); + assertThat(observable.blockingLast().readUtf8()).isEqualTo(CHALLAH_CHALLAH); + } + @Test public void deleteAll() throws IOException { File tempDir = createTempDir(); diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/CrashOnReadFileSystem.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/CrashOnReadFileSystem.java new file mode 100644 index 00000000..54d51ecb --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/CrashOnReadFileSystem.java @@ -0,0 +1,23 @@ +package com.nytimes.android.external.fs3.filesystem; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import javax.annotation.Nonnull; +import okio.BufferedSource; + +public class CrashOnReadFileSystem extends FileSystemImpl { + + public CrashOnReadFileSystem(@Nonnull File root) throws IOException { + super(root); + } + + @Nonnull + @Override + public BufferedSource read(@Nonnull String path) throws FileNotFoundException { + if (path.contains("crash")) { + throw new FileNotFoundException(); + } + return super.read(path); + } +} From 3dce48112a66860efcffe10cce12aba4c3e60c5d Mon Sep 17 00:00:00 2001 From: Ricardo Lage Date: Tue, 28 Nov 2017 12:18:56 +0100 Subject: [PATCH 2/5] Fix crashing readAll() --- .../nytimes/android/external/fs3/FSAllReader.java | 14 ++++++-------- .../android/external/fs3/FSAllOperationTest.java | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java index 0312c64c..45e810de 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java @@ -9,13 +9,15 @@ import io.reactivex.Observable; import io.reactivex.exceptions.Exceptions; +import okio.Buffer; import okio.BufferedSource; +import okio.Okio; +import okio.Source; /** * FSReader is used when persisting from file system * PathResolver will be used in creating file system paths based on cache keys. * Make sure to have keys containing same data resolve to same "path" - * */ public class FSAllReader implements DiskAllRead { final FileSystem fileSystem; @@ -32,13 +34,9 @@ public Observable readAll(@Nonnull final String path) throws Fil try { bufferedSourceObservable = Observable .fromIterable(fileSystem.list(path)) - .map(s -> { - try { - return fileSystem.read(s); - } catch (FileNotFoundException e) { - throw Exceptions.propagate(e); - } - }); + .flatMap(s -> + Observable.defer(() -> Observable.just(fileSystem.read(s))) + .onErrorReturn(throwable -> Okio.buffer((Source) new Buffer()))); } catch (FileNotFoundException e) { throw Exceptions.propagate(e); } diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java index 642a49ea..924482ca 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java @@ -53,7 +53,7 @@ public void readAllWithCrash() throws IOException { //write different data to File System for each barcode fileSystem.write(FOLDER + "/key.txt", source(CHALLAH)); - fileSystem.write(FOLDER + "/crash_key.txt", source(CHALLAH)); + fileSystem.write(FOLDER + "/key_crash.txt", source(CHALLAH)); fileSystem.write(FOLDER + "/" + INNER_FOLDER + "/key2.txt", source(CHALLAH_CHALLAH)); FSAllReader reader = new FSAllReader(fileSystem); //read back all values for the FOLDER From 14d325800b76f7e8e1764a418c5634e0c76a7b8e Mon Sep 17 00:00:00 2001 From: Ricardo Lage Date: Tue, 28 Nov 2017 16:05:15 +0100 Subject: [PATCH 3/5] Pass the throwable down the chain wrapped in a ReadResultBufferedSource --- .../android/external/fs3/FSAllReader.java | 4 +- .../fs3/ReadResultBufferedSource.java | 258 ++++++++++++++++++ .../fs3/ReadResultBufferedSourceFactory.java | 14 + 3 files changed, 273 insertions(+), 3 deletions(-) create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java index 45e810de..75e2f3fe 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java @@ -9,10 +9,8 @@ import io.reactivex.Observable; import io.reactivex.exceptions.Exceptions; -import okio.Buffer; import okio.BufferedSource; import okio.Okio; -import okio.Source; /** * FSReader is used when persisting from file system @@ -36,7 +34,7 @@ public Observable readAll(@Nonnull final String path) throws Fil .fromIterable(fileSystem.list(path)) .flatMap(s -> Observable.defer(() -> Observable.just(fileSystem.read(s))) - .onErrorReturn(throwable -> Okio.buffer((Source) new Buffer()))); + .onErrorReturn(throwable -> Okio.buffer((ReadResultBufferedSourceFactory.createFailureResult(throwable))))); } catch (FileNotFoundException e) { throw Exceptions.propagate(e); } diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java new file mode 100644 index 00000000..3d528521 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java @@ -0,0 +1,258 @@ +package com.nytimes.android.external.fs3; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import javax.annotation.Nullable; +import okio.Buffer; +import okio.BufferedSource; +import okio.ByteString; +import okio.Options; +import okio.Sink; +import okio.Timeout; + +public final class ReadResultBufferedSource implements BufferedSource { + + private final Buffer buffer; + private final Throwable throwable; + + ReadResultBufferedSource(Throwable throwable) { + this.buffer = new Buffer(); + this.throwable = throwable; + } + + public boolean isSuccess() { + return throwable == null; + } + + @Nullable + public Throwable getThrowable() { + return throwable; + } + + @Override + public Buffer buffer() { + return buffer; + } + + @Override + public boolean exhausted() throws IOException { + return buffer.exhausted(); + } + + @Override + public void require(long byteCount) throws IOException { + buffer.require(byteCount); + } + + @Override + public boolean request(long byteCount) throws IOException { + return buffer.request(byteCount); + } + + @Override + public byte readByte() throws IOException { + return buffer.readByte(); + } + + @Override + public short readShort() throws IOException { + return buffer.readShort(); + } + + @Override + public short readShortLe() throws IOException { + return buffer.readShortLe(); + } + + @Override + public int readInt() throws IOException { + return buffer.readInt(); + } + + @Override + public int readIntLe() throws IOException { + return buffer.readIntLe(); + } + + @Override + public long readLong() throws IOException { + return buffer.readLong(); + } + + @Override + public long readLongLe() throws IOException { + return buffer.readLongLe(); + } + + @Override + public long readDecimalLong() throws IOException { + return buffer.readDecimalLong(); + } + + @Override + public long readHexadecimalUnsignedLong() throws IOException { + return buffer.readHexadecimalUnsignedLong(); + } + + @Override + public void skip(long byteCount) throws IOException { + buffer.skip(byteCount); + } + + @Override + public ByteString readByteString() throws IOException { + return buffer.readByteString(); + } + + @Override + public ByteString readByteString(long byteCount) throws IOException { + return buffer.readByteString(byteCount); + } + + @Override + public int select(Options options) throws IOException { + return buffer.select(options); + } + + @Override + public byte[] readByteArray() throws IOException { + return buffer.readByteArray(); + } + + @Override + public byte[] readByteArray(long byteCount) throws IOException { + return buffer.readByteArray(byteCount); + } + + @Override + public int read(byte[] sink) throws IOException { + return buffer.read(sink); + } + + @Override + public void readFully(byte[] sink) throws IOException { + buffer.readFully(sink); + } + + @Override + public int read(byte[] sink, int offset, int byteCount) throws IOException { + return buffer.read(sink, offset, byteCount); + } + + @Override + public void readFully(Buffer sink, long byteCount) throws IOException { + buffer.readFully(sink, byteCount); + } + + @Override + public long readAll(Sink sink) throws IOException { + return buffer.readAll(sink); + } + + @Override + public String readUtf8() throws IOException { + return buffer.readUtf8(); + } + + @Override + public String readUtf8(long byteCount) throws IOException { + return buffer.readUtf8(byteCount); + } + + @Nullable + @Override + public String readUtf8Line() throws IOException { + return buffer.readUtf8Line(); + } + + @Override + public String readUtf8LineStrict() throws IOException { + return buffer.readUtf8LineStrict(); + } + + @Override + public String readUtf8LineStrict(long limit) throws IOException { + return buffer.readUtf8LineStrict(limit); + } + + @Override + public int readUtf8CodePoint() throws IOException { + return buffer.readUtf8CodePoint(); + } + + @Override + public String readString(Charset charset) throws IOException { + return buffer.readString(charset); + } + + @Override + public String readString(long byteCount, Charset charset) throws IOException { + return buffer.readString(byteCount, charset); + } + + @Override + public long indexOf(byte b) throws IOException { + return buffer.indexOf(b); + } + + @Override + public long indexOf(byte b, long fromIndex) throws IOException { + return buffer.indexOf(b, fromIndex); + } + + @Override + public long indexOf(byte b, long fromIndex, long toIndex) throws IOException { + return buffer.indexOf(b, fromIndex, toIndex); + } + + @Override + public long indexOf(ByteString bytes) throws IOException { + return buffer.indexOf(bytes); + } + + @Override + public long indexOf(ByteString bytes, long fromIndex) throws IOException { + return buffer.indexOf(bytes, fromIndex); + } + + @Override + public long indexOfElement(ByteString targetBytes) throws IOException { + return buffer.indexOfElement(targetBytes); + } + + @Override + public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException { + return buffer.indexOfElement(targetBytes, fromIndex); + } + + @Override + public boolean rangeEquals(long offset, ByteString bytes) throws IOException { + return buffer.rangeEquals(offset, bytes); + } + + @Override + public boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCount) throws IOException { + return buffer.rangeEquals(offset, bytes, bytesOffset, byteCount); + } + + @Override + public InputStream inputStream() { + return buffer.inputStream(); + } + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + return buffer.read(sink, byteCount); + } + + @Override + public Timeout timeout() { + return buffer.timeout(); + } + + @Override + public void close() throws IOException { + buffer.close(); + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java new file mode 100644 index 00000000..fc3b8b7f --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java @@ -0,0 +1,14 @@ +package com.nytimes.android.external.fs3; + +import javax.annotation.Nonnull; + +public final class ReadResultBufferedSourceFactory { + + @Nonnull + public static ReadResultBufferedSource createFailureResult(@Nonnull Throwable throwable) { + if (throwable == null) { + throw new IllegalArgumentException("throwable cannot be null."); + } + return new ReadResultBufferedSource(throwable); + } +} From c39d84d157540aa56070559afdb09ca61724a8e9 Mon Sep 17 00:00:00 2001 From: Ricardo Lage Date: Tue, 28 Nov 2017 16:51:34 +0100 Subject: [PATCH 4/5] Fix checks --- .../java/com/nytimes/android/external/fs3/FSAllReader.java | 3 ++- .../android/external/fs3/ReadResultBufferedSourceFactory.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java index 75e2f3fe..ac1cf75e 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java @@ -34,7 +34,8 @@ public Observable readAll(@Nonnull final String path) throws Fil .fromIterable(fileSystem.list(path)) .flatMap(s -> Observable.defer(() -> Observable.just(fileSystem.read(s))) - .onErrorReturn(throwable -> Okio.buffer((ReadResultBufferedSourceFactory.createFailureResult(throwable))))); + .onErrorReturn(throwable -> Okio.buffer( + ReadResultBufferedSourceFactory.createFailureResult(throwable)))); } catch (FileNotFoundException e) { throw Exceptions.propagate(e); } diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java index fc3b8b7f..e2b61dc7 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java @@ -4,6 +4,9 @@ public final class ReadResultBufferedSourceFactory { + private ReadResultBufferedSourceFactory() { + } + @Nonnull public static ReadResultBufferedSource createFailureResult(@Nonnull Throwable throwable) { if (throwable == null) { From fb591af461d90f92006786152e01171db7277a9c Mon Sep 17 00:00:00 2001 From: Ricardo Lage Date: Mon, 11 Dec 2017 17:34:19 +0100 Subject: [PATCH 5/5] Add safeReadAll() and return the result wrapped in a ReadResult object --- .../android/external/fs3/FSAllReader.java | 33 ++- .../fs3/ReadResultBufferedSource.java | 258 ------------------ .../fs3/ReadResultBufferedSourceFactory.java | 17 -- .../external/fs3/SourceAllPersister.java | 7 + .../external/fs3/FSAllOperationTest.java | 21 ++ .../external/store3/base/AllPersister.java | 17 +- .../external/store3/base/DiskAllRead.java | 8 + .../external/store3/base/ReadResult.java | 27 ++ .../store3/base/ReadResultFactory.java | 25 ++ 9 files changed, 130 insertions(+), 283 deletions(-) delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/ReadResult.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/ReadResultFactory.java diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java index ac1cf75e..c1621458 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSAllReader.java @@ -3,6 +3,8 @@ import com.nytimes.android.external.fs3.filesystem.FileSystem; import com.nytimes.android.external.store3.base.DiskAllRead; +import com.nytimes.android.external.store3.base.ReadResult; +import com.nytimes.android.external.store3.base.ReadResultFactory; import java.io.FileNotFoundException; import javax.annotation.Nonnull; @@ -10,7 +12,6 @@ import io.reactivex.Observable; import io.reactivex.exceptions.Exceptions; import okio.BufferedSource; -import okio.Okio; /** * FSReader is used when persisting from file system @@ -24,6 +25,25 @@ public FSAllReader(FileSystem fileSystem) { this.fileSystem = fileSystem; } + @Nonnull + @Override + public Observable> safeReadAll(@Nonnull final String path) { + return Observable.defer(() -> { + Observable> bufferedSourceObservable = null; + try { + bufferedSourceObservable = Observable + .fromIterable(fileSystem.list(path)) + .flatMap(s -> + Observable.defer(() -> Observable.just(fileSystem.read(s))) + .map(ReadResultFactory::createSuccessResult) + .onErrorReturn(ReadResultFactory::createFailureResult)); + } catch (FileNotFoundException e) { + throw Exceptions.propagate(e); + } + return bufferedSourceObservable; + }); + } + @Nonnull @Override public Observable readAll(@Nonnull final String path) throws FileNotFoundException { @@ -32,10 +52,13 @@ public Observable readAll(@Nonnull final String path) throws Fil try { bufferedSourceObservable = Observable .fromIterable(fileSystem.list(path)) - .flatMap(s -> - Observable.defer(() -> Observable.just(fileSystem.read(s))) - .onErrorReturn(throwable -> Okio.buffer( - ReadResultBufferedSourceFactory.createFailureResult(throwable)))); + .map(s -> { + try { + return fileSystem.read(s); + } catch (FileNotFoundException e) { + throw Exceptions.propagate(e); + } + }); } catch (FileNotFoundException e) { throw Exceptions.propagate(e); } diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java deleted file mode 100644 index 3d528521..00000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSource.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.nytimes.android.external.fs3; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import javax.annotation.Nullable; -import okio.Buffer; -import okio.BufferedSource; -import okio.ByteString; -import okio.Options; -import okio.Sink; -import okio.Timeout; - -public final class ReadResultBufferedSource implements BufferedSource { - - private final Buffer buffer; - private final Throwable throwable; - - ReadResultBufferedSource(Throwable throwable) { - this.buffer = new Buffer(); - this.throwable = throwable; - } - - public boolean isSuccess() { - return throwable == null; - } - - @Nullable - public Throwable getThrowable() { - return throwable; - } - - @Override - public Buffer buffer() { - return buffer; - } - - @Override - public boolean exhausted() throws IOException { - return buffer.exhausted(); - } - - @Override - public void require(long byteCount) throws IOException { - buffer.require(byteCount); - } - - @Override - public boolean request(long byteCount) throws IOException { - return buffer.request(byteCount); - } - - @Override - public byte readByte() throws IOException { - return buffer.readByte(); - } - - @Override - public short readShort() throws IOException { - return buffer.readShort(); - } - - @Override - public short readShortLe() throws IOException { - return buffer.readShortLe(); - } - - @Override - public int readInt() throws IOException { - return buffer.readInt(); - } - - @Override - public int readIntLe() throws IOException { - return buffer.readIntLe(); - } - - @Override - public long readLong() throws IOException { - return buffer.readLong(); - } - - @Override - public long readLongLe() throws IOException { - return buffer.readLongLe(); - } - - @Override - public long readDecimalLong() throws IOException { - return buffer.readDecimalLong(); - } - - @Override - public long readHexadecimalUnsignedLong() throws IOException { - return buffer.readHexadecimalUnsignedLong(); - } - - @Override - public void skip(long byteCount) throws IOException { - buffer.skip(byteCount); - } - - @Override - public ByteString readByteString() throws IOException { - return buffer.readByteString(); - } - - @Override - public ByteString readByteString(long byteCount) throws IOException { - return buffer.readByteString(byteCount); - } - - @Override - public int select(Options options) throws IOException { - return buffer.select(options); - } - - @Override - public byte[] readByteArray() throws IOException { - return buffer.readByteArray(); - } - - @Override - public byte[] readByteArray(long byteCount) throws IOException { - return buffer.readByteArray(byteCount); - } - - @Override - public int read(byte[] sink) throws IOException { - return buffer.read(sink); - } - - @Override - public void readFully(byte[] sink) throws IOException { - buffer.readFully(sink); - } - - @Override - public int read(byte[] sink, int offset, int byteCount) throws IOException { - return buffer.read(sink, offset, byteCount); - } - - @Override - public void readFully(Buffer sink, long byteCount) throws IOException { - buffer.readFully(sink, byteCount); - } - - @Override - public long readAll(Sink sink) throws IOException { - return buffer.readAll(sink); - } - - @Override - public String readUtf8() throws IOException { - return buffer.readUtf8(); - } - - @Override - public String readUtf8(long byteCount) throws IOException { - return buffer.readUtf8(byteCount); - } - - @Nullable - @Override - public String readUtf8Line() throws IOException { - return buffer.readUtf8Line(); - } - - @Override - public String readUtf8LineStrict() throws IOException { - return buffer.readUtf8LineStrict(); - } - - @Override - public String readUtf8LineStrict(long limit) throws IOException { - return buffer.readUtf8LineStrict(limit); - } - - @Override - public int readUtf8CodePoint() throws IOException { - return buffer.readUtf8CodePoint(); - } - - @Override - public String readString(Charset charset) throws IOException { - return buffer.readString(charset); - } - - @Override - public String readString(long byteCount, Charset charset) throws IOException { - return buffer.readString(byteCount, charset); - } - - @Override - public long indexOf(byte b) throws IOException { - return buffer.indexOf(b); - } - - @Override - public long indexOf(byte b, long fromIndex) throws IOException { - return buffer.indexOf(b, fromIndex); - } - - @Override - public long indexOf(byte b, long fromIndex, long toIndex) throws IOException { - return buffer.indexOf(b, fromIndex, toIndex); - } - - @Override - public long indexOf(ByteString bytes) throws IOException { - return buffer.indexOf(bytes); - } - - @Override - public long indexOf(ByteString bytes, long fromIndex) throws IOException { - return buffer.indexOf(bytes, fromIndex); - } - - @Override - public long indexOfElement(ByteString targetBytes) throws IOException { - return buffer.indexOfElement(targetBytes); - } - - @Override - public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException { - return buffer.indexOfElement(targetBytes, fromIndex); - } - - @Override - public boolean rangeEquals(long offset, ByteString bytes) throws IOException { - return buffer.rangeEquals(offset, bytes); - } - - @Override - public boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCount) throws IOException { - return buffer.rangeEquals(offset, bytes, bytesOffset, byteCount); - } - - @Override - public InputStream inputStream() { - return buffer.inputStream(); - } - - @Override - public long read(Buffer sink, long byteCount) throws IOException { - return buffer.read(sink, byteCount); - } - - @Override - public Timeout timeout() { - return buffer.timeout(); - } - - @Override - public void close() throws IOException { - buffer.close(); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java deleted file mode 100644 index e2b61dc7..00000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/ReadResultBufferedSourceFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.nytimes.android.external.fs3; - -import javax.annotation.Nonnull; - -public final class ReadResultBufferedSourceFactory { - - private ReadResultBufferedSourceFactory() { - } - - @Nonnull - public static ReadResultBufferedSource createFailureResult(@Nonnull Throwable throwable) { - if (throwable == null) { - throw new IllegalArgumentException("throwable cannot be null."); - } - return new ReadResultBufferedSource(throwable); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java index 2c55fc83..c0ba8fa4 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java @@ -3,6 +3,7 @@ import com.nytimes.android.external.fs3.filesystem.FileSystem; import com.nytimes.android.external.store3.base.AllPersister; +import com.nytimes.android.external.store3.base.ReadResult; import com.nytimes.android.external.store3.base.impl.BarCode; import java.io.FileNotFoundException; @@ -35,6 +36,12 @@ public SourceAllPersister(FileSystem fileSystem) { sourceFileWriter = new FSWriter<>(fileSystem, new BarCodeReadAllPathResolver()); } + @Nonnull + @Override + public Observable> safeReadAll(@Nonnull String path) { + return sourceFileAllReader.safeReadAll(path); + } + @Nonnull @Override public Observable readAll(@Nonnull final String path) throws FileNotFoundException { diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java index 924482ca..501608a8 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java @@ -5,6 +5,7 @@ import com.nytimes.android.external.fs3.filesystem.FileSystem; import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; +import com.nytimes.android.external.store3.base.ReadResult; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -47,6 +48,26 @@ public void readAll() throws IOException { } @Test + public void safeReadAllWithCrash() throws IOException { + File tempDir = createTempDir(); + FileSystem fileSystem = new CrashOnReadFileSystem(tempDir); + + //write different data to File System for each barcode + fileSystem.write(FOLDER + "/key.txt", source(CHALLAH)); + fileSystem.write(FOLDER + "/key_crash.txt", source(CHALLAH)); + fileSystem.write(FOLDER + "/" + INNER_FOLDER + "/key2.txt", source(CHALLAH_CHALLAH)); + FSAllReader reader = new FSAllReader(fileSystem); + //read back all values for the FOLDER + Observable> observable = reader.safeReadAll(FOLDER); + observable.test() + .assertValueAt(0, bufferedSourceReadResult -> + bufferedSourceReadResult.getResult().readUtf8().equals(CHALLAH)) + .assertValueAt(1, bufferedSourceReadResult -> !bufferedSourceReadResult.isSuccess()) + .assertValueAt(2, bufferedSourceReadResult -> + bufferedSourceReadResult.getResult().readUtf8().equals(CHALLAH_CHALLAH)); + } + + @Test(expected = RuntimeException.class) public void readAllWithCrash() throws IOException { File tempDir = createTempDir(); FileSystem fileSystem = new CrashOnReadFileSystem(tempDir); diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java b/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java index fe1394fe..210cabde 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java @@ -10,15 +10,26 @@ import io.reactivex.Single; -public interface AllPersister extends Persister, DiskAllRead, DiskAllErase { +public interface AllPersister extends Persister, DiskAllRead, DiskAllErase { + /** * @param path to use to get data from persister * If data is not available implementer needs to - * throw an exception + * check for failures in the emitted {@link ReadResult} */ + @Nonnull @Override + Observable> safeReadAll(@Nonnull final String path); + + /** + * @deprecated Use {@link #safeReadAll(String)} instead + * @param path to use to get data from persister + * If data is not available implementer needs to + * throw an exception + */ @Nonnull - Observable readAll(@Nonnull final String path) throws FileNotFoundException; + @Override + Observable readAll(@Nonnull String path) throws FileNotFoundException; /** * @param path to delete all the data in the the path. diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/DiskAllRead.java b/store/src/main/java/com/nytimes/android/external/store3/base/DiskAllRead.java index 4186d6a8..7b3a9090 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/DiskAllRead.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/DiskAllRead.java @@ -8,7 +8,15 @@ import io.reactivex.Observable; public interface DiskAllRead { + + /** + * Use {@link #safeReadAll(String)} instead + */ + @Deprecated @Nonnull Observable readAll(@Nonnull String path) throws FileNotFoundException; + + @Nonnull + Observable> safeReadAll(@Nonnull final String path); } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/ReadResult.java b/store/src/main/java/com/nytimes/android/external/store3/base/ReadResult.java new file mode 100644 index 00000000..70bbbc74 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/ReadResult.java @@ -0,0 +1,27 @@ +package com.nytimes.android.external.store3.base; + +import javax.annotation.Nullable; + +public final class ReadResult { + private final Raw raw; + private final Throwable throwable; + + ReadResult(Raw raw, Throwable throwable) { + this.raw = raw; + this.throwable = throwable; + } + + @Nullable + public Raw getResult() { + return raw; + } + + @Nullable + public Throwable getThrowable() { + return throwable; + } + + public boolean isSuccess() { + return raw != null; + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/ReadResultFactory.java b/store/src/main/java/com/nytimes/android/external/store3/base/ReadResultFactory.java new file mode 100644 index 00000000..9d3d2d92 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/ReadResultFactory.java @@ -0,0 +1,25 @@ +package com.nytimes.android.external.store3.base; + +import javax.annotation.Nonnull; + +public final class ReadResultFactory { + + private ReadResultFactory() { + } + + @Nonnull + public static ReadResult createFailureResult(@Nonnull Throwable throwable) { + if (throwable == null) { + throw new IllegalArgumentException("throwable cannot be null."); + } + return new ReadResult<>(null, throwable); + } + + @Nonnull + public static ReadResult createSuccessResult(@Nonnull Raw result) { + if (result == null) { + throw new IllegalArgumentException("result cannot be null."); + } + return new ReadResult<>(result, null); + } +}