Feature/clear all cache (#117)
* public api to clear memory and disk caches
This commit is contained in:
parent
4afb8ceb0c
commit
f4fdd1e9d9
11 changed files with 275 additions and 32 deletions
|
@ -90,7 +90,7 @@ public class StoreRefreshWhenStaleTest {
|
|||
verify(fetcher, times(0)).fetch(barCode);
|
||||
verify(persister, times(1)).getRecordState(barCode);
|
||||
|
||||
store.clearMemory(barCode);
|
||||
store.clear(barCode);
|
||||
result = store.get(barCode).test().awaitTerminalEvent().getOnNextEvents().get(0);
|
||||
assertThat(result).isEqualTo(disk2);
|
||||
verify(fetcher, times(0)).fetch(barCode);
|
||||
|
|
10
store/src/main/java/com/nytimes/android/external/store/base/Clearable.java
vendored
Normal file
10
store/src/main/java/com/nytimes/android/external/store/base/Clearable.java
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
package com.nytimes.android.external.store.base;
|
||||
|
||||
|
||||
/**
|
||||
* Persisters should implement Clearable if they want store.clear(key) to also clear the persister
|
||||
* @param <T> Type of key/request param in store
|
||||
*/
|
||||
public interface Clearable<T> {
|
||||
void clear(T key);
|
||||
}
|
|
@ -43,7 +43,9 @@ public interface Store<T, V> {
|
|||
Observable<T> fetch(@Nonnull V key);
|
||||
|
||||
/**
|
||||
* @return an Observable that emits new items when they arrive.
|
||||
* @return an Observable that emits "fresh" new response from the store that hit the fetcher
|
||||
* WARNING: stream is an endless observable, be careful when combining
|
||||
* with operators that expect an OnComplete event
|
||||
*/
|
||||
@Nonnull
|
||||
Observable<T> stream();
|
||||
|
@ -63,12 +65,26 @@ public interface Store<T, V> {
|
|||
/**
|
||||
* Clear the memory cache of all entries
|
||||
*/
|
||||
@Deprecated
|
||||
void clearMemory();
|
||||
|
||||
/**
|
||||
* Purge a particular entry from memory cache.
|
||||
*/
|
||||
@Deprecated
|
||||
void clearMemory(@Nonnull V key);
|
||||
|
||||
/**
|
||||
* purges all entries from memory and disk cache
|
||||
* Persister will only be cleared if they implements Clearable
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Purge a particular entry from memory and disk cache.
|
||||
* Persister will only be cleared if they implements Clearable
|
||||
*/
|
||||
void clear(@Nonnull V key);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -109,6 +109,16 @@ public class ProxyStore<Parsed> implements Store<Parsed> {
|
|||
internalStore.clearMemory(barCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
internalStore.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(@Nonnull BarCode key) {
|
||||
internalStore.clear(key);
|
||||
}
|
||||
|
||||
protected Observable<Parsed> memory(@Nonnull BarCode id) {
|
||||
return internalStore.memory(id);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ package com.nytimes.android.external.store.base.impl;
|
|||
|
||||
import com.nytimes.android.external.cache.Cache;
|
||||
import com.nytimes.android.external.cache.CacheBuilder;
|
||||
import com.nytimes.android.external.store.base.Clearable;
|
||||
import com.nytimes.android.external.store.base.Fetcher;
|
||||
import com.nytimes.android.external.store.base.InternalStore;
|
||||
import com.nytimes.android.external.store.base.Parser;
|
||||
import com.nytimes.android.external.store.base.Persister;
|
||||
import com.nytimes.android.external.store.util.NoopPersister;
|
||||
import com.nytimes.android.external.store.util.OnErrorResumeWithEmpty;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
@ -309,22 +309,25 @@ final class RealInternalStore<Raw, Parsed, Key> implements InternalStore<Parsed,
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void clearMemory() {
|
||||
inFlightRequests.invalidateAll();
|
||||
clearDiskIfNoOp();
|
||||
|
||||
|
||||
for (Key cachedKey : memCache.asMap().keySet()) {
|
||||
memCache.invalidate(cachedKey);
|
||||
clearDiskIfNoOp(cachedKey);
|
||||
refreshSubject.onNext(cachedKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void clearDiskIfNoOp() {
|
||||
if (persister() instanceof NoopPersister) {
|
||||
persister = new NoopPersister<>();
|
||||
private boolean clearDiskIfNoOp(Key barCode) {
|
||||
if (persister() instanceof Clearable) {
|
||||
((Clearable<Key>) persister).clear(barCode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,11 +336,33 @@ final class RealInternalStore<Raw, Parsed, Key> implements InternalStore<Parsed,
|
|||
* @param barCode of data to clear
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void clearMemory(@Nonnull final Key barCode) {
|
||||
inFlightRequests.invalidate(barCode);
|
||||
clearDiskIfNoOp();
|
||||
memCache.invalidate(barCode);
|
||||
refreshSubject.onNext(barCode);
|
||||
if (persister instanceof Clearable) {
|
||||
((Clearable<Key>) persister).clear(barCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clear(){
|
||||
for (Key cachedKey : memCache.asMap().keySet()) {
|
||||
clear(cachedKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(@Nonnull Key key) {
|
||||
inFlightRequests.invalidate(key);
|
||||
memCache.invalidate(key);
|
||||
|
||||
if (persister instanceof Clearable) {
|
||||
((Clearable<Key>) persister).clear(key);
|
||||
}
|
||||
refreshSubject.onNext(key);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,17 @@ public class RealStore<Parsed, Key> implements Store<Parsed, Key> {
|
|||
internalStore.clearMemory(barCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
internalStore.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(@Nonnull Key key) {
|
||||
internalStore.clear();
|
||||
|
||||
}
|
||||
|
||||
protected Observable<Parsed> memory(@Nonnull Key id) {
|
||||
return internalStore.memory(id);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.nytimes.android.external.store.util;
|
||||
|
||||
|
||||
import com.nytimes.android.external.store.base.Clearable;
|
||||
import com.nytimes.android.external.store.base.Persister;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
@ -13,8 +14,8 @@ import rx.Observable;
|
|||
/**
|
||||
* Pass-through diskdao for stores that don't want to use persister
|
||||
*/
|
||||
public class NoopPersister<Raw, Key> implements Persister<Raw, Key> {
|
||||
private final ConcurrentMap<Key, Raw> networkResponses = new ConcurrentHashMap<>();
|
||||
public class NoopPersister<Raw, Key> implements Persister<Raw, Key>, Clearable<Key> {
|
||||
protected final ConcurrentMap<Key, Raw> networkResponses = new ConcurrentHashMap<>();
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
|
@ -29,4 +30,9 @@ public class NoopPersister<Raw, Key> implements Persister<Raw, Key> {
|
|||
networkResponses.put(barCode, raw);
|
||||
return Observable.just(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Key key) {
|
||||
networkResponses.remove(key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ClearStoreMemoryTest {
|
|||
|
||||
@Test
|
||||
public void testClearSingleBarCode() {
|
||||
// one request should produce one call
|
||||
//one request should produce one call
|
||||
BarCode barcode = new BarCode("type", "key");
|
||||
store.get(barcode).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls).isEqualTo(1);
|
||||
|
@ -58,14 +58,14 @@ public class ClearStoreMemoryTest {
|
|||
BarCode b1 = new BarCode("type1", "key1");
|
||||
BarCode b2 = new BarCode("type2", "key2");
|
||||
|
||||
// each request should produce one call
|
||||
//each request should produce one call
|
||||
store.get(b1).test().awaitTerminalEvent();
|
||||
store.get(b2).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls).isEqualTo(2);
|
||||
|
||||
store.clearMemory();
|
||||
|
||||
// after everything is cleared each request should produce another 2 calls
|
||||
//after everything is cleared each request should produce another 2 calls
|
||||
store.get(b1).test().awaitTerminalEvent();
|
||||
store.get(b2).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls).isEqualTo(4);
|
||||
|
|
110
store/src/test/java/com/nytimes/android/external/store/ClearStoreTest.java
vendored
Normal file
110
store/src/test/java/com/nytimes/android/external/store/ClearStoreTest.java
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
package com.nytimes.android.external.store;
|
||||
|
||||
import com.nytimes.android.external.store.base.Fetcher;
|
||||
import com.nytimes.android.external.store.base.Store;
|
||||
import com.nytimes.android.external.store.base.impl.BarCode;
|
||||
import com.nytimes.android.external.store.base.impl.StoreBuilder;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import rx.Observable;
|
||||
|
||||
import static com.nytimes.android.external.store.GetRefreshingTest.ClearingPersister;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ClearStoreTest {
|
||||
@Mock
|
||||
ClearingPersister persister;
|
||||
private AtomicInteger networkCalls;
|
||||
private Store<Integer> store;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
networkCalls = new AtomicInteger(0);
|
||||
store = StoreBuilder.<Integer>builder()
|
||||
.fetcher(new Fetcher<Integer, BarCode>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Observable<Integer> fetch(BarCode barCode) {
|
||||
return Observable.fromCallable(new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
return networkCalls.incrementAndGet();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.persister(persister)
|
||||
.open();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearSingleBarCode() {
|
||||
// one request should produce one call
|
||||
BarCode barcode = new BarCode("type", "key");
|
||||
|
||||
when(persister.read(barcode))
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk on get
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk after clearing
|
||||
.thenReturn(Observable.just(1)); //read from disk after making additional network call
|
||||
when(persister.write(barcode, 1)).thenReturn(Observable.just(true));
|
||||
when(persister.write(barcode, 2)).thenReturn(Observable.just(true));
|
||||
|
||||
|
||||
store.get(barcode).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls.intValue()).isEqualTo(1);
|
||||
|
||||
// after clearing the memory another call should be made
|
||||
store.clear(barcode);
|
||||
store.get(barcode).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls.intValue()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearAllBarCodes() {
|
||||
BarCode barcode1 = new BarCode("type1", "key1");
|
||||
BarCode barcode2 = new BarCode("type2", "key2");
|
||||
|
||||
when(persister.read(barcode1))
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)); //read from disk after making additional network call
|
||||
when(persister.write(barcode1, 1)).thenReturn(Observable.just(true));
|
||||
when(persister.write(barcode1, 2)).thenReturn(Observable.just(true));
|
||||
|
||||
when(persister.read(barcode2))
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)); //read from disk after making additional network call
|
||||
|
||||
when(persister.write(barcode2, 1)).thenReturn(Observable.just(true));
|
||||
when(persister.write(barcode2, 2)).thenReturn(Observable.just(true));
|
||||
|
||||
|
||||
// each request should produce one call
|
||||
store.get(barcode1).test().awaitTerminalEvent();
|
||||
store.get(barcode2).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls.intValue()).isEqualTo(2);
|
||||
|
||||
store.clear();
|
||||
|
||||
// after everything is cleared each request should produce another 2 calls
|
||||
store.get(barcode1).test().awaitTerminalEvent();
|
||||
store.get(barcode2).test().awaitTerminalEvent();
|
||||
assertThat(networkCalls.intValue()).isEqualTo(4);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,20 @@
|
|||
package com.nytimes.android.external.store;
|
||||
|
||||
import com.nytimes.android.external.store.base.Clearable;
|
||||
import com.nytimes.android.external.store.base.Fetcher;
|
||||
import com.nytimes.android.external.store.base.Store;
|
||||
import com.nytimes.android.external.store.base.Persister;
|
||||
import com.nytimes.android.external.store.base.beta.Store;
|
||||
import com.nytimes.android.external.store.base.impl.BarCode;
|
||||
import com.nytimes.android.external.store.base.impl.StoreBuilder;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@ -16,16 +22,19 @@ import rx.Observable;
|
|||
import rx.observers.AssertableSubscriber;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GetRefreshingTest {
|
||||
|
||||
private int networkCalls = 0;
|
||||
private Store<Integer> store;
|
||||
@Mock
|
||||
ClearingPersister persister;
|
||||
private AtomicInteger networkCalls;
|
||||
private Store<Integer, BarCode> store;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
networkCalls = 0;
|
||||
store = StoreBuilder.<Integer>builder()
|
||||
networkCalls = new AtomicInteger(0);
|
||||
store = StoreBuilder.<Integer>barcode()
|
||||
.fetcher(new Fetcher<Integer, BarCode>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
|
@ -33,28 +42,38 @@ public class GetRefreshingTest {
|
|||
return Observable.fromCallable(new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
return networkCalls++;
|
||||
return networkCalls.incrementAndGet();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.persister(persister)
|
||||
.open();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshOnClear() {
|
||||
BarCode barcode = new BarCode("type", "key");
|
||||
AssertableSubscriber<Integer> testStore = store.getRefreshing(barcode).test();
|
||||
assertThat(testStore.getValueCount()).isEqualTo(1);
|
||||
assertThat(networkCalls).isEqualTo(1);
|
||||
when(persister.read(barcode))
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)); //read from disk after making additional network call
|
||||
when(persister.write(barcode, 1)).thenReturn(Observable.just(true));
|
||||
when(persister.write(barcode, 2)).thenReturn(Observable.just(true));
|
||||
|
||||
|
||||
AssertableSubscriber<Integer> refreshingObservable = store.getRefreshing(barcode).test();
|
||||
assertThat(refreshingObservable.getValueCount()).isEqualTo(1);
|
||||
assertThat(networkCalls.intValue()).isEqualTo(1);
|
||||
//clearing the store should produce another network call
|
||||
store.clearMemory(barcode);
|
||||
assertThat(testStore.getValueCount()).isEqualTo(2);
|
||||
assertThat(networkCalls).isEqualTo(2);
|
||||
store.clear(barcode);
|
||||
assertThat(refreshingObservable.getValueCount()).isEqualTo(2);
|
||||
assertThat(networkCalls.intValue()).isEqualTo(2);
|
||||
|
||||
store.get(barcode).test().awaitTerminalEvent();
|
||||
assertThat(testStore.getValueCount()).isEqualTo(2);
|
||||
assertThat(networkCalls).isEqualTo(2);
|
||||
assertThat(refreshingObservable.getValueCount()).isEqualTo(2);
|
||||
assertThat(networkCalls.intValue()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -62,17 +81,53 @@ public class GetRefreshingTest {
|
|||
BarCode barcode1 = new BarCode("type", "key");
|
||||
BarCode barcode2 = new BarCode("type", "key2");
|
||||
|
||||
when(persister.read(barcode1))
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)); //read from disk after making additional network call
|
||||
when(persister.write(barcode1, 1)).thenReturn(Observable.just(true));
|
||||
when(persister.write(barcode1, 2)).thenReturn(Observable.just(true));
|
||||
|
||||
when(persister.read(barcode2))
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk
|
||||
.thenReturn(Observable.just(1)) //read from disk after fetching from network
|
||||
.thenReturn(Observable.<Integer>empty()) //read from disk after clearing disk cache
|
||||
.thenReturn(Observable.just(1)); //read from disk after making additional network call
|
||||
|
||||
when(persister.write(barcode2, 1)).thenReturn(Observable.just(true));
|
||||
when(persister.write(barcode2, 2)).thenReturn(Observable.just(true));
|
||||
|
||||
AssertableSubscriber<Integer> testObservable1 = store.getRefreshing(barcode1).test();
|
||||
AssertableSubscriber<Integer> testObservable2 = store.getRefreshing(barcode2).test();
|
||||
assertThat(testObservable1.getValueCount()).isEqualTo(1);
|
||||
assertThat(testObservable2.getValueCount()).isEqualTo(1);
|
||||
|
||||
assertThat(networkCalls).isEqualTo(2);
|
||||
assertThat(networkCalls.intValue()).isEqualTo(2);
|
||||
|
||||
store.clearMemory();
|
||||
assertThat(networkCalls).isEqualTo(4);
|
||||
store.clear();
|
||||
assertThat(networkCalls.intValue()).isEqualTo(4);
|
||||
|
||||
|
||||
}
|
||||
//everything will be mocked
|
||||
static class ClearingPersister implements Persister<Integer, BarCode>, Clearable<BarCode> {
|
||||
@Override
|
||||
public void clear(BarCode key) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Observable<Integer> read(BarCode barCode) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Observable<Boolean> write(BarCode barCode, Integer integer) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class StoreTest {
|
|||
public void testSubclass() {
|
||||
|
||||
ProxyStore<String> simpleStore = new SampleStore(fetcher, persister);
|
||||
simpleStore.clearMemory();
|
||||
simpleStore.clear();
|
||||
|
||||
when(fetcher.fetch(barCode))
|
||||
.thenReturn(Observable.just(NETWORK));
|
||||
|
|
Loading…
Reference in a new issue