diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/resource/CacheManager.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/CacheManager.kt new file mode 100644 index 00000000..44dfd2a6 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/CacheManager.kt @@ -0,0 +1,6 @@ +package dev.usbharu.hideout.core.service.resource + +interface CacheManager { + suspend fun putCache(key: String, block: suspend () -> ResolveResponse) + suspend fun getOrWait(key: String): ResolveResponse +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/resource/InMemoryCacheManager.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/InMemoryCacheManager.kt new file mode 100644 index 00000000..b48fadd7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/InMemoryCacheManager.kt @@ -0,0 +1,49 @@ +package dev.usbharu.hideout.core.service.resource + +import dev.usbharu.hideout.util.LruCache +import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.springframework.stereotype.Service +import java.time.Instant + +@Service +class InMemoryCacheManager : CacheManager { + private val cacheKey = LruCache(15) + private val valueStore = mutableMapOf() + private val keyMutex = Mutex() + + override suspend fun putCache(key: String, block: suspend () -> ResolveResponse) { + val needRunBlock: Boolean + keyMutex.withLock { + cacheKey.filter { Instant.ofEpochMilli(it.value).plusSeconds(300) <= Instant.now() } + + val cached = cacheKey.get(key) + if (cached == null) { + needRunBlock = true + cacheKey[key] = Instant.now().toEpochMilli() + + valueStore.remove(key) + } else { + needRunBlock = false + } + } + if (needRunBlock) { + val processed = block() + + if (cacheKey.containsKey(key)) { + valueStore[key] = processed + } + } + } + + override suspend fun getOrWait(key: String): ResolveResponse { + while (valueStore.contains(key).not()) { + if (cacheKey.containsKey(key).not()) { + throw IllegalStateException("Invalid cache key.") + } + delay(1) + } + return valueStore.getValue(key) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResolveResponse.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResolveResponse.kt new file mode 100644 index 00000000..8261d8dc --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResolveResponse.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.core.service.resource + +import io.ktor.client.statement.* +import io.ktor.util.* +import io.ktor.utils.io.jvm.javaio.* +import java.io.InputStream + +class KtorResolveResponse(val ktorHttpResponse: HttpResponse) : ResolveResponse { + + override suspend fun body(): InputStream = ktorHttpResponse.bodyAsChannel().toInputStream() + override suspend fun header(): Map> = ktorHttpResponse.headers.toMap() + override suspend fun status(): Int = ktorHttpResponse.status.value + override suspend fun statusMessage(): String = ktorHttpResponse.status.description +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveService.kt new file mode 100644 index 00000000..4cd99bc8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveService.kt @@ -0,0 +1,24 @@ +package dev.usbharu.hideout.core.service.resource + +import io.ktor.client.* +import io.ktor.client.request.* +import org.springframework.stereotype.Service + +@Service +open class KtorResourceResolveService(private val httpClient: HttpClient, private val cacheManager: CacheManager) : + ResourceResolveService { + override suspend fun resolve(url: String): ResolveResponse { + cacheManager.putCache(getCacheKey(url)) { + runResolve(url) + } + return cacheManager.getOrWait(getCacheKey(url)) + } + + protected suspend fun runResolve(url: String): ResolveResponse { + val httpResponse = httpClient.get(url) + + return KtorResolveResponse(httpResponse) + } + + protected suspend fun getCacheKey(url: String) = url +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResolveResponse.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResolveResponse.kt new file mode 100644 index 00000000..bcfb0bcb --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResolveResponse.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.core.service.resource + +import java.io.InputStream + +interface ResolveResponse { + suspend fun body(): InputStream + suspend fun header(): Map> + suspend fun status(): Int + suspend fun statusMessage(): String +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResourceResolveService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResourceResolveService.kt new file mode 100644 index 00000000..b2229b30 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResourceResolveService.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.core.service.resource + +interface ResourceResolveService { + suspend fun resolve(url: String): ResolveResponse +}