All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kernl.data.source.impl.MemoryCachedDataSource.kt Maven / Gradle / Ivy

Go to download

Kernl: A Kotlin Symbol Processing (KSP) library for automatic repository generation.

There is a newer version: 0.0.1-beta6
Show newest version
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()

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy