Feature/filepersister (#115)
* generic file persister with path resolver
This commit is contained in:
parent
f4fdd1e9d9
commit
d01f2abe45
13 changed files with 245 additions and 58 deletions
10
filesystem/src/main/java/com/nytimes/android/external/fs/BarCodePathResolver.java
vendored
Normal file
10
filesystem/src/main/java/com/nytimes/android/external/fs/BarCodePathResolver.java
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package com.nytimes.android.external.fs;
|
||||||
|
|
||||||
|
import com.nytimes.android.external.store.base.impl.BarCode;
|
||||||
|
|
||||||
|
class BarCodePathResolver implements PathResolver<BarCode> {
|
||||||
|
@Override
|
||||||
|
public String resolve(BarCode key) {
|
||||||
|
return key.toString();
|
||||||
|
}
|
||||||
|
}
|
41
filesystem/src/main/java/com/nytimes/android/external/fs/FSReader.java
vendored
Normal file
41
filesystem/src/main/java/com/nytimes/android/external/fs/FSReader.java
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package com.nytimes.android.external.fs;
|
||||||
|
|
||||||
|
import com.nytimes.android.external.fs.filesystem.FileSystem;
|
||||||
|
import com.nytimes.android.external.store.base.DiskRead;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import okio.BufferedSource;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
* @param <T> key type
|
||||||
|
*/
|
||||||
|
public class FSReader<T> implements DiskRead<BufferedSource, T> {
|
||||||
|
final FileSystem fileSystem;
|
||||||
|
final PathResolver<T> pathResolver;
|
||||||
|
|
||||||
|
public FSReader(FileSystem fileSystem, PathResolver<T> pathResolver) {
|
||||||
|
this.fileSystem = fileSystem;
|
||||||
|
this.pathResolver = pathResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Observable<BufferedSource> read(final T id) {
|
||||||
|
return fileSystem.exists(pathResolver.resolve(id)) ?
|
||||||
|
Observable.fromCallable(new Callable<BufferedSource>() {
|
||||||
|
@Override
|
||||||
|
public BufferedSource call() throws FileNotFoundException {
|
||||||
|
return fileSystem.read(pathResolver.resolve(id));
|
||||||
|
}
|
||||||
|
}) :
|
||||||
|
Observable.<BufferedSource>empty();
|
||||||
|
}
|
||||||
|
}
|
40
filesystem/src/main/java/com/nytimes/android/external/fs/FSWriter.java
vendored
Normal file
40
filesystem/src/main/java/com/nytimes/android/external/fs/FSWriter.java
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package com.nytimes.android.external.fs;
|
||||||
|
|
||||||
|
import com.nytimes.android.external.fs.filesystem.FileSystem;
|
||||||
|
import com.nytimes.android.external.store.base.DiskWrite;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import okio.BufferedSource;
|
||||||
|
import rx.Observable;
|
||||||
|
/**
|
||||||
|
* FSReader is used when persisting to 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"
|
||||||
|
* @param <T> key type
|
||||||
|
*/
|
||||||
|
public class FSWriter<T> implements DiskWrite<BufferedSource, T> {
|
||||||
|
final FileSystem fileSystem;
|
||||||
|
final PathResolver<T> pathResolver;
|
||||||
|
|
||||||
|
public FSWriter(FileSystem fileSystem, PathResolver<T> pathResolver) {
|
||||||
|
this.fileSystem = fileSystem;
|
||||||
|
this.pathResolver = pathResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Observable<Boolean> write(final T barCode, final BufferedSource data) {
|
||||||
|
return Observable.fromCallable(new Callable<Boolean>() {
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
|
||||||
|
public Boolean call() throws Exception {
|
||||||
|
fileSystem.write(pathResolver.resolve(barCode), data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
48
filesystem/src/main/java/com/nytimes/android/external/fs/FileSystemPersister.java
vendored
Normal file
48
filesystem/src/main/java/com/nytimes/android/external/fs/FileSystemPersister.java
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package com.nytimes.android.external.fs;
|
||||||
|
|
||||||
|
import com.nytimes.android.external.fs.filesystem.FileSystem;
|
||||||
|
import com.nytimes.android.external.store.base.Persister;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import okio.BufferedSource;
|
||||||
|
import rx.Observable;
|
||||||
|
/**
|
||||||
|
* FileSystemPersister is used when persisting to/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"
|
||||||
|
* @param <T> key type
|
||||||
|
*/
|
||||||
|
public final class FileSystemPersister<T> implements Persister<BufferedSource, T> {
|
||||||
|
final FileSystem fileSystem;
|
||||||
|
private final FSReader<T> fileReader;
|
||||||
|
private final FSWriter<T> fileWriter;
|
||||||
|
|
||||||
|
private FileSystemPersister(FileSystem fileSystem, PathResolver<T> pathResolver) {
|
||||||
|
|
||||||
|
fileReader = new FSReader<>(fileSystem, pathResolver);
|
||||||
|
fileWriter = new FSWriter<>(fileSystem, pathResolver);
|
||||||
|
this.fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static <T> Persister<BufferedSource, T> create(FileSystem fileSystem,
|
||||||
|
PathResolver<T> pathResolver) {
|
||||||
|
if (fileSystem == null) {
|
||||||
|
throw new IllegalArgumentException("root file cannot be null.");
|
||||||
|
}
|
||||||
|
return new FileSystemPersister<>(fileSystem, pathResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Observable<BufferedSource> read(final T id) {
|
||||||
|
return fileReader.read(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Observable<Boolean> write(final T barCode, final BufferedSource data) {
|
||||||
|
return fileWriter.write(barCode, data);
|
||||||
|
}
|
||||||
|
}
|
15
filesystem/src/main/java/com/nytimes/android/external/fs/PathResolver.java
vendored
Normal file
15
filesystem/src/main/java/com/nytimes/android/external/fs/PathResolver.java
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package com.nytimes.android.external.fs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by 206847 on 2/8/17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
* @param <T> Store key/request param type
|
||||||
|
*/
|
||||||
|
public interface PathResolver<T> {
|
||||||
|
|
||||||
|
String resolve(T key);
|
||||||
|
}
|
|
@ -5,42 +5,20 @@ import com.nytimes.android.external.store.base.DiskRead;
|
||||||
import com.nytimes.android.external.store.base.RecordState;
|
import com.nytimes.android.external.store.base.RecordState;
|
||||||
import com.nytimes.android.external.store.base.impl.BarCode;
|
import com.nytimes.android.external.store.base.impl.BarCode;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import okio.BufferedSource;
|
import okio.BufferedSource;
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
import static com.nytimes.android.external.fs.SourcePersister.pathForBarcode;
|
import static com.nytimes.android.external.fs.SourcePersister.pathForBarcode;
|
||||||
|
|
||||||
public class SourceFileReader implements DiskRead<BufferedSource, BarCode> {
|
public class SourceFileReader extends FSReader<BarCode> implements DiskRead<BufferedSource, BarCode> {
|
||||||
|
|
||||||
final FileSystem fileSystem;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SourceFileReader(FileSystem fileSystem) {
|
public SourceFileReader(FileSystem fileSystem) {
|
||||||
this.fileSystem = fileSystem;
|
super(fileSystem, new BarCodePathResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Observable<BufferedSource> read(@Nonnull final BarCode barCode) {
|
|
||||||
return Observable.fromCallable(new Callable<BufferedSource>() {
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public BufferedSource call() throws FileNotFoundException {
|
|
||||||
return fileSystem.read(pathForBarcode(barCode));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean exists(@Nonnull BarCode barCode) {
|
|
||||||
return fileSystem.exists(pathForBarcode(barCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public RecordState getRecordState(@Nonnull BarCode barCode,
|
public RecordState getRecordState(@Nonnull BarCode barCode,
|
||||||
|
|
|
@ -4,38 +4,12 @@ import com.nytimes.android.external.fs.filesystem.FileSystem;
|
||||||
import com.nytimes.android.external.store.base.DiskWrite;
|
import com.nytimes.android.external.store.base.DiskWrite;
|
||||||
import com.nytimes.android.external.store.base.impl.BarCode;
|
import com.nytimes.android.external.store.base.impl.BarCode;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import okio.BufferedSource;
|
import okio.BufferedSource;
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
import static com.nytimes.android.external.fs.SourcePersister.pathForBarcode;
|
public class SourceFileWriter extends FSWriter<BarCode> implements DiskWrite<BufferedSource, BarCode> {
|
||||||
import static okio.Okio.buffer;
|
|
||||||
|
|
||||||
public class SourceFileWriter implements DiskWrite<BufferedSource, BarCode> {
|
|
||||||
|
|
||||||
final FileSystem fileSystem;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SourceFileWriter(FileSystem fileSystem) {
|
public SourceFileWriter(FileSystem fileSystem) {
|
||||||
this.fileSystem = fileSystem;
|
super(fileSystem, new BarCodePathResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Observable<Boolean> write(@Nonnull final BarCode barCode, @Nonnull final BufferedSource data) {
|
|
||||||
return Observable.fromCallable(new Callable<Boolean>() {
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
|
|
||||||
public Boolean call() throws Exception {
|
|
||||||
fileSystem.write(pathForBarcode(barCode), buffer(data));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class SourcePersister implements Persister<BufferedSource, BarCode>{
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public Observable<BufferedSource> read(@Nonnull final BarCode barCode) {
|
public Observable<BufferedSource> read(@Nonnull final BarCode barCode) {
|
||||||
return sourceFileReader.exists(barCode) ? sourceFileReader.read(barCode) : Observable.<BufferedSource>empty();
|
return sourceFileReader.read(barCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
|
|
@ -39,10 +39,11 @@ public final class SourcePersisterFactory {
|
||||||
* persistence {@link com.nytimes.android.external.fs.filesystem.FileSystem}.
|
* persistence {@link com.nytimes.android.external.fs.filesystem.FileSystem}.
|
||||||
**/
|
**/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public static Persister<BufferedSource, BarCode> create(@Nonnull FileSystem fileSystem) {
|
public static Persister<BufferedSource, BarCode> create(@Nonnull FileSystem fileSystem) {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
throw new IllegalArgumentException("fileSystem cannot be null.");
|
throw new IllegalArgumentException("fileSystem cannot be null.");
|
||||||
}
|
}
|
||||||
return new SourcePersister(fileSystem);
|
return new SourcePersister(fileSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
79
filesystem/src/test/java/com/nytimes/android/external/fs/FilePersisterTest.java
vendored
Normal file
79
filesystem/src/test/java/com/nytimes/android/external/fs/FilePersisterTest.java
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package com.nytimes.android.external.fs;
|
||||||
|
|
||||||
|
import com.nytimes.android.external.fs.filesystem.FileSystem;
|
||||||
|
import com.nytimes.android.external.store.base.Persister;
|
||||||
|
import com.nytimes.android.external.store.base.impl.BarCode;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import okio.BufferedSource;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class FilePersisterTest {
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
FileSystem fileSystem;
|
||||||
|
@Mock
|
||||||
|
BufferedSource bufferedSource;
|
||||||
|
|
||||||
|
private final BarCode simple = new BarCode("type", "key");
|
||||||
|
private final String resolvedPath = new BarCodePathResolver().resolve(simple);
|
||||||
|
private Persister<BufferedSource, BarCode> fileSystemPersister;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
fileSystemPersister = FileSystemPersister.create(fileSystem, new BarCodePathResolver());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readExists() throws FileNotFoundException {
|
||||||
|
when(fileSystem.exists(resolvedPath))
|
||||||
|
.thenReturn(true);
|
||||||
|
when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource);
|
||||||
|
|
||||||
|
BufferedSource returnedValue = fileSystemPersister.read(simple).toBlocking().single();
|
||||||
|
assertThat(returnedValue).isEqualTo(bufferedSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readDoesNotExist() throws FileNotFoundException {
|
||||||
|
expectedException.expect(NoSuchElementException.class);
|
||||||
|
when(fileSystem.exists(resolvedPath))
|
||||||
|
.thenReturn(false);
|
||||||
|
|
||||||
|
fileSystemPersister.read(simple).toBlocking().single();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeThenRead() throws IOException {
|
||||||
|
when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource);
|
||||||
|
when(fileSystem.exists(resolvedPath)).thenReturn(true);
|
||||||
|
fileSystemPersister.write(simple, bufferedSource).toBlocking().single();
|
||||||
|
BufferedSource source = fileSystemPersister.read(simple).toBlocking().first();
|
||||||
|
InOrder inOrder = inOrder(fileSystem);
|
||||||
|
inOrder.verify(fileSystem).write(resolvedPath, bufferedSource);
|
||||||
|
inOrder.verify(fileSystem).exists(resolvedPath);
|
||||||
|
inOrder.verify(fileSystem).read(resolvedPath);
|
||||||
|
|
||||||
|
assertThat(source).isEqualTo(bufferedSource);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,9 +41,9 @@ public class RecordPersisterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readExists() throws FileNotFoundException {
|
public void readExists() throws FileNotFoundException {
|
||||||
when(fileSystem.exists(SourcePersister.pathForBarcode(simple)))
|
when(fileSystem.exists(simple.toString()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
when(fileSystem.read(SourcePersister.pathForBarcode(simple))).thenReturn(bufferedSource);
|
when(fileSystem.read(simple.toString())).thenReturn(bufferedSource);
|
||||||
|
|
||||||
BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single();
|
BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single();
|
||||||
assertThat(returnedValue).isEqualTo(bufferedSource);
|
assertThat(returnedValue).isEqualTo(bufferedSource);
|
||||||
|
|
|
@ -40,9 +40,9 @@ public class SourcePersisterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readExists() throws FileNotFoundException {
|
public void readExists() throws FileNotFoundException {
|
||||||
when(fileSystem.exists(SourcePersister.pathForBarcode(simple)))
|
when(fileSystem.exists(simple.toString()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
when(fileSystem.read(SourcePersister.pathForBarcode(simple))).thenReturn(bufferedSource);
|
when(fileSystem.read(simple.toString())).thenReturn(bufferedSource);
|
||||||
|
|
||||||
BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single();
|
BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single();
|
||||||
assertThat(returnedValue).isEqualTo(bufferedSource);
|
assertThat(returnedValue).isEqualTo(bufferedSource);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import rx.Observable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for fetching data from persister
|
* Interface for fetching data from persister
|
||||||
|
* when implementing also think about implementing PathResolver to ease in creating primary keys
|
||||||
*
|
*
|
||||||
* @param <Raw> data type before parsing
|
* @param <Raw> data type before parsing
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue