package dev.inmo.micro_utils.repos.cache.full import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.coroutines.SmartRWLocker import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.withReadAcquire import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.cache.* import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode import dev.inmo.micro_utils.repos.cache.util.actualizeAll import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers open class FullReadCRUDCacheRepo( protected open val parentRepo: ReadCRUDRepo, protected open val kvCache: KeyValueRepo, protected val locker: SmartRWLocker = SmartRWLocker(), protected open val idGetter: (ObjectType) -> IdType ) : ReadCRUDRepo, FullCacheRepo { protected suspend inline fun doOrTakeAndActualize( action: KeyValueRepo.() -> Optional, actionElse: ReadCRUDRepo.() -> T, actualize: KeyValueRepo.(T) -> Unit ): T { locker.withReadAcquire { kvCache.action().onPresented { return it } } return parentRepo.actionElse().also { kvCache.actualize(it) } } protected suspend inline fun doOrTakeAndActualizeWithWriteLock( action: KeyValueRepo.() -> Optional, actionElse: ReadCRUDRepo.() -> T, actualize: KeyValueRepo.(T) -> Unit ): T = doOrTakeAndActualize( action = action, actionElse = actionElse, actualize = { locker.withWriteLock { actualize(it) } } ) protected open suspend fun actualizeAll() { kvCache.actualizeAll(parentRepo, locker = locker) } override suspend fun getByPagination(pagination: Pagination): PaginationResult = doOrTakeAndActualize( { values(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, { getByPagination(pagination) }, { if (it.results.isNotEmpty()) actualizeAll() } ) override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult = doOrTakeAndActualize( { keys(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, { getIdsByPagination(pagination) }, { if (it.results.isNotEmpty()) actualizeAll() } ) override suspend fun count(): Long = doOrTakeAndActualize( { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, { count() }, { if (it != 0L) actualizeAll() } ) override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualizeWithWriteLock( { contains(id).takeIf { it }.optionalOrAbsentIfNull }, { contains(id) }, { if (it) parentRepo.getById(id) ?.let { kvCache.set(id, it) } } ) override suspend fun getAll(): Map = doOrTakeAndActualizeWithWriteLock( { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, { getAll() }, { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } } ) override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock( { get(id) ?.optional ?: Optional.absent() }, { getById(id) }, { it ?.let { kvCache.set(idGetter(it), it) } } ) override suspend fun invalidate() { actualizeAll() } } fun ReadCRUDRepo.cached( kvCache: KeyValueRepo, locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType ) = FullReadCRUDCacheRepo(this, kvCache, locker, idGetter) open class FullCRUDCacheRepo( override val parentRepo: CRUDRepo, kvCache: KeyValueRepo, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType ) : FullReadCRUDCacheRepo( parentRepo, kvCache, locker, idGetter ), WriteCRUDRepo by WriteCRUDCacheRepo( parentRepo, kvCache, scope, locker, idGetter ), CRUDRepo { init { if (!skipStartInvalidate) { scope.launchSafelyWithoutExceptions { invalidate() } } } override suspend fun invalidate() { actualizeAll() } } fun CRUDRepo.fullyCached( kvCache: KeyValueRepo = MapKeyValueRepo(), scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType ) = FullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, locker, idGetter) @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope, idGetter)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) fun CRUDRepo.cached( kvCache: KeyValueRepo, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType ) = fullyCached(kvCache, scope, skipStartInvalidate, locker, idGetter)