add SessionVideoApi
This commit is contained in:
+81
@@ -0,0 +1,81 @@
|
||||
package ru.shadowsparky.vbox.backend.data.http
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
||||
import ru.shadowsparky.vbox.shared.domain.model.VideoDetails
|
||||
import ru.shadowsparky.vbox.shared.domain.model.VideoLinksResponse
|
||||
import ru.shadowsparky.vbox.shared.domain.model.VideosResponse
|
||||
import java.util.Collections
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class SessionVideoApi(private val wrapper: VideoApi) : VideoApi {
|
||||
private val cacheTtlMs = 3.days.inWholeMilliseconds
|
||||
private val maxCacheSize = 100
|
||||
|
||||
private val videosCache = createBoundedCache<String, VideosResponse>()
|
||||
private val detailsCache = createBoundedCache<Long, VideoDetails>()
|
||||
private val linksCache = createBoundedCache<VideoLinksKey, VideoLinksResponse>()
|
||||
|
||||
private val videosMutex = Mutex()
|
||||
private val detailsMutex = Mutex()
|
||||
private val linksMutex = Mutex()
|
||||
|
||||
override suspend fun fetchNewVideos(query: String?): VideosResponse {
|
||||
val cacheKey = query ?: ""
|
||||
videosCache.getValid(cacheKey)?.let { return it }
|
||||
return videosMutex.withLock {
|
||||
videosCache.getValid(cacheKey) ?: run {
|
||||
val remoteData = wrapper.fetchNewVideos(query)
|
||||
videosCache[cacheKey] = CacheEntry(remoteData)
|
||||
remoteData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchDetails(id: Long): VideoDetails {
|
||||
detailsCache.getValid(id)?.let { return it }
|
||||
return detailsMutex.withLock {
|
||||
detailsCache.getValid(id) ?: run {
|
||||
val remoteData = wrapper.fetchDetails(id)
|
||||
detailsCache[id] = CacheEntry(remoteData)
|
||||
remoteData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchVideoLinks(id: Long, seasonId: Long?): VideoLinksResponse {
|
||||
val cacheKey = VideoLinksKey(id, seasonId)
|
||||
linksCache.getValid(cacheKey)?.let { return it }
|
||||
return linksMutex.withLock {
|
||||
linksCache.getValid(cacheKey) ?: run {
|
||||
val remoteData = wrapper.fetchVideoLinks(id, seasonId)
|
||||
linksCache[cacheKey] = CacheEntry(remoteData)
|
||||
remoteData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <K, V> MutableMap<K, CacheEntry<V>>.getValid(key: K): V? {
|
||||
val entry = this[key] ?: return null
|
||||
val isExpired = System.currentTimeMillis() - entry.createdAt > cacheTtlMs
|
||||
return if (isExpired) {
|
||||
this.remove(key)
|
||||
null
|
||||
} else {
|
||||
entry.data
|
||||
}
|
||||
}
|
||||
|
||||
private fun <K, V> createBoundedCache(): MutableMap<K, CacheEntry<V>> {
|
||||
val map = object : LinkedHashMap<K, CacheEntry<V>>(maxCacheSize + 1, 0.75f, false) {
|
||||
override fun removeEldestEntry(eldest: Map.Entry<K, CacheEntry<V>>?): Boolean {
|
||||
return size > maxCacheSize
|
||||
}
|
||||
}
|
||||
return Collections.synchronizedMap(map)
|
||||
}
|
||||
|
||||
private data class CacheEntry<T>(val data: T, val createdAt: Long = System.currentTimeMillis())
|
||||
private data class VideoLinksKey(val id: Long, val seasonId: Long?)
|
||||
}
|
||||
@@ -3,11 +3,14 @@ package ru.shadowsparky.vbox.backend.di
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.annotation.Module
|
||||
import ru.shadowsparky.vbox.backend.data.http.ExternalBackendApi
|
||||
import ru.shadowsparky.vbox.backend.data.http.SessionVideoApi
|
||||
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
||||
|
||||
@Module
|
||||
class HttpModule {
|
||||
|
||||
@Factory
|
||||
fun provideVideoApi(impl: ExternalBackendApi): VideoApi = impl
|
||||
fun provideVideoApi(impl: ExternalBackendApi): VideoApi {
|
||||
return SessionVideoApi(impl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import ru.shadowsparky.http.domain.HttpException
|
||||
import ru.shadowsparky.vbox.backend.di.AuthEntryPoint
|
||||
import ru.shadowsparky.vbox.backend.di.RoutingEntryPoint
|
||||
import ru.shadowsparky.vbox.backend.presentation.routing.setupAuthMethods
|
||||
import ru.shadowsparky.vbox.backend.presentation.routing.setupBaseMethods
|
||||
import ru.shadowsparky.vbox.shared.domain.model.ServerExceptionInfo
|
||||
|
||||
val routingLogger: Logger = LoggerFactory.getLogger("routing")
|
||||
@@ -38,7 +37,6 @@ fun Application.configureRouting(
|
||||
}
|
||||
}
|
||||
routing {
|
||||
setupBaseMethods(routingEntryPoint.videoApi)
|
||||
setupAuthMethods(routingEntryPoint, authEntryPoint)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -28,22 +28,23 @@ fun Routing.setupAuthMethods(
|
||||
post(AuthTokenRepository.UPDATE_PATH) {
|
||||
call.respond(default.update(call.receive()))
|
||||
}
|
||||
post(AuthTokenRepository.REVOKE_PATH) {
|
||||
default.revoke(call.receive())
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
authenticate(AUTH_JWT_NAME) {
|
||||
get(HeathCheck.PATH) { call.respond(HttpStatusCode.OK) }
|
||||
post(AuthTokenRepository.CHANGE_PASS_PATH) {
|
||||
authEntryPoint.authTokenRepositoryFactory.create(call.obtainUserId())
|
||||
.changePassword(call.receive())
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
setupBaseMethods(routingEntryPoint.videoApi)
|
||||
setupSearch(searchFactory)
|
||||
setupRecentlyWatched(recentlyFactory)
|
||||
setupSavedMovie(savedMovieFactory)
|
||||
setupTagsRouting(userTagFactory, movieTagFactory)
|
||||
setupUpdates(updateFetcherFactory)
|
||||
setupChat(chatRepositoryFactory, processUserMessageUseCase, remoteEventHandler)
|
||||
get(HeathCheck.PATH) { call.respond(HttpStatusCode.OK) }
|
||||
post(AuthTokenRepository.CHANGE_PASS_PATH) {
|
||||
authEntryPoint.authTokenRepositoryFactory.create(call.obtainUserId())
|
||||
.changePassword(call.receive())
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
post(AuthTokenRepository.REVOKE_PATH) {
|
||||
default.revoke(call.receive())
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,13 +1,12 @@
|
||||
package ru.shadowsparky.vbox.backend.presentation.routing
|
||||
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.Routing
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.util.getOrFail
|
||||
import ru.shadowsparky.vbox.shared.domain.VideoApi
|
||||
|
||||
fun Routing.setupBaseMethods(api: VideoApi) {
|
||||
fun Route.setupBaseMethods(api: VideoApi) {
|
||||
get(VideoApi.VIDEOS_PATH) {
|
||||
val p = call.parameters
|
||||
val obj = api.fetchNewVideos(p[VideoApi.SEARCH_ARG])
|
||||
@@ -26,7 +25,4 @@ fun Routing.setupBaseMethods(api: VideoApi) {
|
||||
)
|
||||
)
|
||||
}
|
||||
get("/") {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user