FileSystemRecordPersister (#137)

* FileSystemRecordPersister

* checkstyle
This commit is contained in:
Mike Nakhimovich 2017-02-23 12:47:51 -05:00 committed by GitHub
parent f353a5ff8e
commit 9395d8a36c
6 changed files with 182 additions and 4 deletions

View file

@ -0,0 +1,71 @@
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.RecordProvider;
import com.nytimes.android.external.store.base.RecordState;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import okio.BufferedSource;
import rx.Observable;
/**
* FileSystemRecordPersister is used when persisting to/from file system while being stale aware
* 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 <Key> key type
*/
public final class FileSystemRecordPersister<Key> implements Persister<BufferedSource, Key>, RecordProvider<Key> {
private final FSReader<Key> fileReader;
private final FSWriter<Key> fileWriter;
private final FileSystem fileSystem;
private final PathResolver<Key> pathResolver;
private final long expirationDuration;
@Nonnull
private final TimeUnit expirationUnit;
private FileSystemRecordPersister(FileSystem fileSystem, PathResolver<Key> pathResolver,
long expirationDuration,
@Nonnull TimeUnit expirationUnit) {
this.fileSystem = fileSystem;
this.pathResolver = pathResolver;
this.expirationDuration = expirationDuration;
this.expirationUnit = expirationUnit;
fileReader = new FSReader<>(fileSystem, pathResolver);
fileWriter = new FSWriter<>(fileSystem, pathResolver);
}
@Nonnull
public static <T> FileSystemRecordPersister<T> create(FileSystem fileSystem,
PathResolver<T> pathResolver,
long expirationDuration,
@Nonnull TimeUnit expirationUnit) {
if (fileSystem == null) {
throw new IllegalArgumentException("root file cannot be null.");
}
return new FileSystemRecordPersister<>(fileSystem, pathResolver,
expirationDuration, expirationUnit);
}
@Nonnull
@Override
public RecordState getRecordState(@Nonnull Key key) {
return fileSystem.getRecordState(expirationUnit, expirationDuration, pathResolver.resolve(key));
}
@Nonnull
@Override
public Observable<BufferedSource> read(@Nonnull Key key) {
return fileReader.read(key);
}
@Nonnull
@Override
public Observable<Boolean> write(@Nonnull Key key, @Nonnull BufferedSource bufferedSource) {
return fileWriter.write(key, bufferedSource);
}
}

View file

@ -0,0 +1,107 @@
package com.nytimes.android.external.fs;
import com.nytimes.android.external.fs.filesystem.FileSystem;
import com.nytimes.android.external.store.base.RecordState;
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 java.util.concurrent.TimeUnit;
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 FileSystemRecordPersisterTest {
@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 FileSystemRecordPersister<BarCode> fileSystemPersister;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
fileSystemPersister = FileSystemRecordPersister.create(fileSystem,
new BarCodePathResolver(),
1, TimeUnit.DAYS);
}
@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);
}
@Test
public void freshTest() {
when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath))
.thenReturn(RecordState.FRESH);
assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.FRESH);
}
@Test
public void staleTest() {
when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath))
.thenReturn(RecordState.STALE);
assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.STALE);
}
@Test
public void missingTest() {
when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath))
.thenReturn(RecordState.MISSING);
assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.MISSING);
}
}

View file

@ -3,8 +3,8 @@ package com.nytimes.android.external.fs;
import com.google.gson.Gson;
import com.nytimes.android.external.store.base.Fetcher;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.BarCode;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.StoreBuilder;
import com.nytimes.android.external.store.middleware.GsonSourceParser;

View file

@ -2,8 +2,8 @@ package com.nytimes.android.external.fs;
import com.google.gson.Gson;
import com.nytimes.android.external.store.base.Fetcher;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.BarCode;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.StoreBuilder;
import com.nytimes.android.external.store.middleware.GsonSourceParser;

View file

@ -2,8 +2,8 @@ package com.nytimes.android.external.fs;
import com.nytimes.android.external.store.base.Fetcher;
import com.nytimes.android.external.store.base.RecordState;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.BarCode;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.StoreBuilder;
import org.junit.Before;

View file

@ -2,8 +2,8 @@ package com.nytimes.android.external.fs;
import com.nytimes.android.external.store.base.Fetcher;
import com.nytimes.android.external.store.base.RecordState;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.BarCode;
import com.nytimes.android.external.store.base.impl.Store;
import com.nytimes.android.external.store.base.impl.StoreBuilder;
import org.junit.Before;