-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ktor
CacheStorage
based persistent cache (#61)
* ktor CacheStorage based persistent cache * merge fix * remove okio dependency from kamel-image * remove buildconfig --------- Co-authored-by: Luca Spinazzola <mspinluca@gmail.com>
- Loading branch information
Showing
25 changed files
with
1,414 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools"> | ||
|
||
<uses-permission android:name="android.permission.INTERNET" /> | ||
|
||
<application> | ||
|
||
<provider | ||
android:name="androidx.startup.InitializationProvider" | ||
android:authorities="${applicationId}.androidx-startup" | ||
android:exported="false" | ||
tools:node="merge"> | ||
|
||
<meta-data | ||
android:name="io.kamel.core.ApplicationContextInitializer" | ||
android:value="androidx.startup" /> | ||
</provider> | ||
|
||
</application> | ||
</manifest> |
15 changes: 15 additions & 0 deletions
15
kamel-core/src/androidMain/kotlin/io/kamel/core/ApplicationContextInitializer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.kamel.core | ||
|
||
import android.content.Context | ||
import androidx.startup.Initializer | ||
|
||
|
||
internal lateinit var applicationContext: Context | ||
|
||
internal class ApplicationContextInitializer : Initializer<Context> { | ||
override fun create(context: Context): Context = context.also { | ||
applicationContext = it | ||
} | ||
|
||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList() | ||
} |
15 changes: 15 additions & 0 deletions
15
kamel-core/src/androidMain/kotlin/io/kamel/core/cache/httpCache.android.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.kamel.core.cache | ||
|
||
import io.kamel.core.applicationContext | ||
import io.kamel.core.cache.disk.DiskCacheStorage | ||
import io.ktor.client.plugins.cache.storage.CacheStorage | ||
import okio.FileSystem | ||
import okio.Path.Companion.toOkioPath | ||
|
||
private val cacheDir = applicationContext.cacheDir.toOkioPath() | ||
|
||
internal actual fun httpCacheStorage(maxSize: Long): CacheStorage = DiskCacheStorage( | ||
fileSystem = FileSystem.SYSTEM, | ||
directory = cacheDir, | ||
maxSize = maxSize | ||
) |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
132 changes: 132 additions & 0 deletions
132
kamel-core/src/commonMain/kotlin/io/kamel/core/cache/disk/DiskCacheStorage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package io.kamel.core.cache.disk | ||
|
||
|
||
import io.kamel.core.utils.Kamel | ||
import io.ktor.client.plugins.cache.storage.* | ||
import io.ktor.http.* | ||
import io.ktor.util.* | ||
import io.ktor.util.date.* | ||
import io.ktor.utils.io.core.use | ||
import kotlinx.coroutines.* | ||
import okio.BufferedSink | ||
import okio.BufferedSource | ||
import okio.ByteString.Companion.encodeUtf8 | ||
import okio.FileSystem | ||
import okio.Path | ||
|
||
|
||
private fun Url.hash() = toString().encodeUtf8().md5().hex() | ||
|
||
private fun DiskLruCache.Editor.abortQuietly() { | ||
try { | ||
abort() | ||
} catch (_: Exception) { | ||
} | ||
} | ||
|
||
/** | ||
* storage that uses file system to store cache data. | ||
* @param fileSystem underlying filesystem. | ||
* @param directory directory to store cache data. | ||
* @param maxSize maximum cache size | ||
* @param dispatcher dispatcher to use for file operations. | ||
*/ | ||
internal class DiskCacheStorage( | ||
private val fileSystem: FileSystem, | ||
directory: Path, | ||
maxSize: Long, | ||
dispatcher: CoroutineDispatcher = Dispatchers.Kamel, | ||
) : CacheStorage { | ||
|
||
private val diskLruCache by lazy { DiskLruCache(fileSystem, directory, dispatcher, maxSize) } | ||
|
||
override suspend fun store(url: Url, data: CachedResponseData) { | ||
diskLruCache.edit(url.hash())?.let { editor -> | ||
try { | ||
fileSystem.write(editor.file()) { | ||
writeCache(this, data) | ||
} | ||
editor.commit() | ||
} catch (_: Exception) { | ||
editor.abortQuietly() | ||
} | ||
} | ||
} | ||
|
||
override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? { | ||
return diskLruCache.get(url.hash())?.use { | ||
try { | ||
fileSystem.read(it.file()) { | ||
readCache(this) | ||
} | ||
} catch (_: Exception) { | ||
null | ||
} | ||
} | ||
} | ||
|
||
override suspend fun findAll(url: Url): Set<CachedResponseData> { | ||
return find(url, emptyMap())?.let(::setOf) ?: emptySet() | ||
} | ||
|
||
private fun readCache(source: BufferedSource): CachedResponseData { | ||
val url = source.readUtf8Line()!! | ||
val status = HttpStatusCode(source.readInt(), source.readUtf8Line()!!) | ||
val version = HttpProtocolVersion.parse(source.readUtf8Line()!!) | ||
val headersCount = source.readInt() | ||
val headers = HeadersBuilder() | ||
for (j in 0 until headersCount) { | ||
val key = source.readUtf8Line()!! | ||
val value = source.readUtf8Line()!! | ||
headers.append(key, value) | ||
} | ||
val requestTime = GMTDate(source.readLong()) | ||
val responseTime = GMTDate(source.readLong()) | ||
val expirationTime = GMTDate(source.readLong()) | ||
val varyKeysCount = source.readInt() | ||
val varyKeys = buildMap { | ||
for (j in 0 until varyKeysCount) { | ||
val key = source.readUtf8Line()!! | ||
val value = source.readUtf8Line()!! | ||
put(key, value) | ||
} | ||
} | ||
val bodyCount = source.readInt() | ||
val body = ByteArray(bodyCount) | ||
source.readFully(body) | ||
return CachedResponseData( | ||
url = Url(url), | ||
statusCode = status, | ||
requestTime = requestTime, | ||
responseTime = responseTime, | ||
version = version, | ||
expires = expirationTime, | ||
headers = headers.build(), | ||
varyKeys = varyKeys, | ||
body = body | ||
) | ||
} | ||
|
||
private fun writeCache(channel: BufferedSink, cache: CachedResponseData) { | ||
channel.writeUtf8(cache.url.toString() + "\n") | ||
channel.writeInt(cache.statusCode.value) | ||
channel.writeUtf8(cache.statusCode.description + "\n") | ||
channel.writeUtf8(cache.version.toString() + "\n") | ||
val headers = cache.headers.flattenEntries() | ||
channel.writeInt(headers.size) | ||
for ((key, value) in headers) { | ||
channel.writeUtf8(key + "\n") | ||
channel.writeUtf8(value + "\n") | ||
} | ||
channel.writeLong(cache.requestTime.timestamp) | ||
channel.writeLong(cache.responseTime.timestamp) | ||
channel.writeLong(cache.expires.timestamp) | ||
channel.writeInt(cache.varyKeys.size) | ||
for ((key, value) in cache.varyKeys) { | ||
channel.writeUtf8(key + "\n") | ||
channel.writeUtf8(value + "\n") | ||
} | ||
channel.writeInt(cache.body.size) | ||
channel.write(cache.body) | ||
} | ||
} |
Oops, something went wrong.