Skip to content

Instantly share code, notes, and snippets.

@yongjhih
Last active August 17, 2023 15:16
Show Gist options
  • Save yongjhih/8283082022a27f5ff6fc25cd79a138a7 to your computer and use it in GitHub Desktop.
Save yongjhih/8283082022a27f5ff6fc25cd79a138a7 to your computer and use it in GitHub Desktop.
Memoized Cache Interceptor
/**
* Intercepts and caches memoized responses based on the provided LruCache.
*
* @property cache an instance of LruCache to store the responses.
*/
class MemoizedInterceptor(private val cache: LruCache<String, Response>) : Interceptor {
/**
* Callback that will be invoked when a cached response is found.
* @param request the request being executed.
* @param isFresh `true` if the cached response is still fresh, `false` otherwise.
* @param remainingAge the remaining time before the cached response expires, or `null` if not applicable.
*/
var onCached: (Request, Boolean, Long?) -> Unit = { _, _, _ -> }
/**
* Intercepts the request and checks if it has a memoized response. If available in the cache, the cached
* response is returned. Otherwise, a network call is made to fetch the data.
*
* @param chain the interceptor chain.
* @return the response either from cache or fetched from network.
*/
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val invocation = request.tag<Invocation>()
val memoized = invocation?.method()?.annotation<Memoized>()
val cacheControl = memoized?.value
?.toSet()
?.toHeaders()
?.let { CacheControl.parse(it).build(request.cacheControl) }
val maxAgeSeconds = cacheControl?.maxAgeSeconds
?.takeIf { it >= 0 }
?: Int.MAX_VALUE
val onlyIfCached = cacheControl?.onlyIfCached == true
val maxStaleSeconds = cacheControl?.maxStaleSeconds
?.takeIf { it >= 0 }
?: Int.MAX_VALUE
return if (memoized != null) {
val cachedResponse = cache.get(request.key)
if (onlyIfCached) {
cachedResponse?.ifFresh(maxStaleSeconds.seconds.inWholeMilliseconds, cache, onCached)
?: request.cacheErrorResponse(cachedResponse?.protocol ?: chain.connection()?.protocol() ?: Protocol.HTTP_1_1)
} else {
cachedResponse?.ifFresh(maxAgeSeconds.seconds.inWholeMilliseconds, cache, onCached)
?: chain.proceed(request)
}
} else chain.proceed(request)
}
}
fun ResponseBody.clone(size: Long = Long.MAX_VALUE, onSource: BufferedSource.() -> Unit = {}): ResponseBody = source().apply {
request(size)
onSource()
}.buffer.clone().asResponseBody(contentType(), contentLength())
fun Request.build(onBuild: Request.Builder.(Request) -> Unit) = newBuilder().also { it.onBuild(this) }.build()
fun Response.build(onBuild: Response.Builder.(Response) -> Unit) = newBuilder().also { it.onBuild(this) }.build()
fun HttpUrl.build(onBuild: HttpUrl.Builder.(HttpUrl) -> Unit) = newBuilder().also { it.onBuild(this) }.build()
inline fun <reified T> Request.tag() = tag(T::class.java)
inline fun <reified T : Annotation> Method.annotation(): T? = getAnnotation(T::class.java)
fun Iterable<String>.toHeaders(): Headers = Headers.Builder().apply {
mapNotNull { it.toPairBy() }.forEach {
val (key, value) = it
if (value != null) add(key, value)
}
}.build()
fun String.toPairBy(delimiter: String = ":", trim: Boolean = true): Pair<String, String?>? {
val index: Int = indexOf(delimiter)
if (index == -1 || index == 0) {
return null
}
if (index == length - 1) {
return substring(0, index) to null
}
return substring(0, index) to if (trim) substring(index + 1).trim() else substring(index + 1)
}
fun RequestBody.string(charset: Charset = Charsets.UTF_8) = Buffer().let {
writeTo(it)
//it.readUtf8()
it.readString(contentType()?.charset() ?: charset)
}
fun Response.isExpired(maxAge: Long, from: Long = System.currentTimeMillis()): Boolean =
remainingAge(maxAge, from) < 0
fun Response.isFresh(maxAge: Long, from: Long = System.currentTimeMillis()): Boolean =
!isExpired(maxAge, from)
fun Response.age(from: Long = System.currentTimeMillis()): Long =
from - receivedResponseAtMillis
fun Response.remainingAge(maxAge: Long, from: Long = System.currentTimeMillis()): Long =
maxAge - age(from)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment