kernl.data.source.impl.MemoryCachedDataSource.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Kernl.Runtime Show documentation
Show all versions of Kernl.Runtime Show documentation
Kernl: A Kotlin Symbol Processing (KSP) library for automatic repository generation.
package io.github.mattshoe.shoebox.kernl.data.source.impl
import io.github.mattshoe.shoebox.kernl.data.DataResult
import io.github.mattshoe.shoebox.kernl.data.source.DataSource
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
/**
* This implementation of [DataSource] only caches data in-memory rather than on-disk.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal open class MemoryCachedDataSource(
private val dispatcher: CoroutineDispatcher
): DataSource {
private val dataMutex = Mutex()
protected open val _data = MutableSharedFlow>(replay = 1)
private lateinit var dataRetrievalAction: suspend () -> T
final override var value: DataResult? = null
private set
override val data: Flow>
get() = _data
override suspend fun initialize(forceFetch: Boolean, dataRetrieval: suspend () -> T) = withContext(dispatcher) {
fetchData(forceFetch, dataRetrieval)
}
override suspend fun refresh() = withContext(dispatcher) {
check(this@MemoryCachedDataSource::dataRetrievalAction.isInitialized) {
"Refresh was invoked before the data source was initialized."
}
fetchData(forceFetch = true, null)
}
override suspend fun invalidate() {
_data.resetReplayCache()
with (DataResult.Invalidated()) {
_data.emit(this)
value = this
}
}
private suspend fun fetchData(forceFetch: Boolean, dataRetrieval: (suspend () -> T)?) {
if (canFetchData(forceFetch)) {
try {
// if non-null then we need to save the retrieval operation for refreshes
dataRetrieval?.let {
[email protected] = it
}
val dataResult: DataResult = DataResult.Success(
dataRetrievalAction.invoke()
)
_data.emit(dataResult)
value = dataResult
} catch (e: CancellationException) {
println("Cancelled!!! $e")
throw e
} catch (e: Throwable) {
println("DS error: $e")
val dataResult = DataResult.Error(e)
_data.emit(dataResult)
value = dataResult
} finally {
dataMutex.unlock()
}
}
}
private fun canFetchData(forceFetch: Boolean): Boolean {
val hasDataBeenFetchedAlready = this::dataRetrievalAction.isInitialized
return (forceFetch || !hasDataBeenFetchedAlready) && noOtherServiceCallsAreInFlight()
}
private fun noOtherServiceCallsAreInFlight() = dataMutex.tryLock()
}