Feature/filepersister (#115)

* generic file persister with path resolver
This commit is contained in:
Mike Nakhimovich 2017-02-08 15:46:35 -05:00 committed by GitHub
parent f4fdd1e9d9
commit d01f2abe45
13 changed files with 245 additions and 58 deletions

View 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();
}
}

View 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();
}
}

View 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;
}
});
}
}

View 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);
}
}

View 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);
}

View file

@ -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.impl.BarCode;
import java.io.FileNotFoundException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import okio.BufferedSource;
import rx.Observable;
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) {
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
public RecordState getRecordState(@Nonnull BarCode barCode,

View file

@ -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.impl.BarCode;
import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import okio.BufferedSource;
import rx.Observable;
import static com.nytimes.android.external.fs.SourcePersister.pathForBarcode;
import static okio.Okio.buffer;
public class SourceFileWriter extends FSWriter<BarCode> implements DiskWrite<BufferedSource, BarCode> {
public class SourceFileWriter implements DiskWrite<BufferedSource, BarCode> {
final FileSystem fileSystem;
@Inject
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;
}
});
}
}

View file

@ -41,7 +41,7 @@ public class SourcePersister implements Persister<BufferedSource, BarCode>{
@Nonnull
@Override
public Observable<BufferedSource> read(@Nonnull final BarCode barCode) {
return sourceFileReader.exists(barCode) ? sourceFileReader.read(barCode) : Observable.<BufferedSource>empty();
return sourceFileReader.read(barCode);
}
@Nonnull

View file

@ -39,10 +39,11 @@ public final class SourcePersisterFactory {
* persistence {@link com.nytimes.android.external.fs.filesystem.FileSystem}.
**/
@Nonnull
public static Persister<BufferedSource, BarCode> create(@Nonnull FileSystem fileSystem) {
public static Persister<BufferedSource, BarCode> create(@Nonnull FileSystem fileSystem) {
if (fileSystem == null) {
throw new IllegalArgumentException("fileSystem cannot be null.");
}
return new SourcePersister(fileSystem);
}
}

View 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);
}
}

View file

@ -41,9 +41,9 @@ public class RecordPersisterTest {
@Test
public void readExists() throws FileNotFoundException {
when(fileSystem.exists(SourcePersister.pathForBarcode(simple)))
when(fileSystem.exists(simple.toString()))
.thenReturn(true);
when(fileSystem.read(SourcePersister.pathForBarcode(simple))).thenReturn(bufferedSource);
when(fileSystem.read(simple.toString())).thenReturn(bufferedSource);
BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single();
assertThat(returnedValue).isEqualTo(bufferedSource);

View file

@ -40,9 +40,9 @@ public class SourcePersisterTest {
@Test
public void readExists() throws FileNotFoundException {
when(fileSystem.exists(SourcePersister.pathForBarcode(simple)))
when(fileSystem.exists(simple.toString()))
.thenReturn(true);
when(fileSystem.read(SourcePersister.pathForBarcode(simple))).thenReturn(bufferedSource);
when(fileSystem.read(simple.toString())).thenReturn(bufferedSource);
BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single();
assertThat(returnedValue).isEqualTo(bufferedSource);

View file

@ -6,6 +6,7 @@ import rx.Observable;
/**
* 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
*/