Skip to content

Commit

Permalink
Add cacheOnly option to StoreReadRequest
Browse files Browse the repository at this point in the history
Signed-off-by: William Brawner <[email protected]>
  • Loading branch information
wbrawner authored and William Brawner committed Nov 14, 2023
1 parent 3559a78 commit cbb46cf
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ package org.mobilenativefoundation.store.store5
* @param skippedCaches List of cache types that should be skipped when retuning the response see [CacheType]
* @param refresh If set to true [Store] will always get fresh value from fetcher while also
* starting the stream from the local [com.dropbox.android.external.store4.impl.SourceOfTruth] and memory cache
*
* @param fetch If set to false, then fetcher will not be used
*/
data class StoreReadRequest<out Key> private constructor(
val key: Key,
private val skippedCaches: Int,
val refresh: Boolean = false,
val fallBackToSourceOfTruth: Boolean = false
val fallBackToSourceOfTruth: Boolean = false,
val fetch: Boolean = true
) {

internal fun shouldSkipCache(type: CacheType) = skippedCaches.and(type.flag) != 0
Expand Down Expand Up @@ -57,7 +58,8 @@ data class StoreReadRequest<out Key> private constructor(
)

/**
* Create a [StoreReadRequest] which will return data from memory/disk caches
* Create a [StoreReadRequest] which will return data from memory/disk caches if present,
* otherwise will hit your fetcher (filling your caches).
* @param refresh if true then return fetcher (new) data as well (updating your caches)
*/
fun <Key> cached(key: Key, refresh: Boolean) = StoreReadRequest(
Expand All @@ -66,6 +68,15 @@ data class StoreReadRequest<out Key> private constructor(
refresh = refresh
)

/**
* Create a [StoreReadRequest] which will return data from memory/disk caches
*/
fun <Key> cacheOnly(key: Key) = StoreReadRequest(
key = key,
skippedCaches = 0,
fetch = false
)

/**
* Create a [StoreReadRequest] which will return data from disk cache
* @param refresh if true then return fetcher (new) data as well (updating your caches)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,24 @@ internal class RealStore<Key : Any, Network : Any, Output : Any, Local : Any>(
}
}

cachedToEmit?.let { it: Output ->
if (cachedToEmit != null) {
// if we read a value from cache, dispatch it first
emit(StoreReadResponse.Data(value = it, origin = StoreReadResponseOrigin.Cache))
emit(
StoreReadResponse.Data(
value = cachedToEmit,
origin = StoreReadResponseOrigin.Cache
)
)
if (!request.fetch) {
// This request was only for cached items, so we stop here
return@flow
}
} else if (!request.fetch) {
// The cache is empty or invalid but we don't want to hit the fetcher
emit(StoreReadResponse.NoNewData(origin = StoreReadResponseOrigin.Cache))
return@flow
}

val stream: Flow<StoreReadResponse<Output>> = if (sourceOfTruth == null) {
// piggypack only if not specified fresh data AND we emitted a value from the cache
val piggybackOnly = !request.refresh && cachedToEmit != null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package org.mobilenativefoundation.store.store5

import kotlinx.atomicfu.atomic
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.mobilenativefoundation.store.store5.impl.extensions.get
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours

@FlowPreview
Expand Down Expand Up @@ -37,4 +40,63 @@ class StoreWithInMemoryCacheTests {
assertEquals("result", c)
assertEquals("result", d)
}

@Test
fun givenEmptyCacheThenCacheOnlyRequestReturnsNoNewData() = testScope.runTest {
val store = StoreBuilder
.from(Fetcher.of { _: Int -> throw RuntimeException("Fetcher shouldn't be hit") })
.cachePolicy(
MemoryPolicy
.builder<Any, Any>()
.build()
)
.build()
val response = store.stream(StoreReadRequest.cacheOnly(0)).first()
assertEquals(StoreReadResponse.NoNewData(StoreReadResponseOrigin.Cache), response)
}

@Test
fun givenPrimedCacheThenCacheOnlyRequestReturnsData() = testScope.runTest {
val fetcherHitCounter = atomic(0)
val store = StoreBuilder
.from(Fetcher.of { _: Int ->
fetcherHitCounter += 1
"result"
})
.cachePolicy(
MemoryPolicy
.builder<Any, Any>()
.build()
)
.build()
val a = store.get(0)
assertEquals("result", a)
assertEquals(1, fetcherHitCounter.value)
val response = store.stream(StoreReadRequest.cacheOnly(0)).first()
assertEquals("result", response.requireData())
assertEquals(1, fetcherHitCounter.value)
}

@Test
fun givenInvalidCacheThenCacheOnlyRequestReturnsNoNewData() = testScope.runTest {
val fetcherHitCounter = atomic(0)
val store = StoreBuilder
.from(Fetcher.of { _: Int ->
fetcherHitCounter += 1
"result"
})
.cachePolicy(
MemoryPolicy
.builder<Any, Any>()
.setExpireAfterWrite(Duration.ZERO)
.build()
)
.build()
val a = store.get(0)
assertEquals("result", a)
assertEquals(1, fetcherHitCounter.value)
val response = store.stream(StoreReadRequest.cacheOnly(0)).first()
assertEquals(StoreReadResponse.NoNewData(StoreReadResponseOrigin.Cache), response)
assertEquals(1, fetcherHitCounter.value)
}
}

0 comments on commit cbb46cf

Please sign in to comment.