DataStoreInMemoryCache.kt

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.datastore.core

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.updateAndGet

/**
 * This is where a [DataStoreImpl] instance keeps its actual data.
 */
internal class DataStoreInMemoryCache<T> {
    @Suppress("UNCHECKED_CAST")
    private val cachedValue: MutableStateFlow<State<T>> = MutableStateFlow(
        UnInitialized as State<T>
    )

    val currentState: State<T>
        get() = cachedValue.value

    val flow: Flow<State<T>>
        get() = cachedValue

    /**
     * Tries to update the current value if an only if the new given state's version is equal to or
     * higher than the current state.
     */
    fun tryUpdate(
        newState: State<T>
    ): State<T> {
        val updated = cachedValue.updateAndGet { cached ->
            when (cached) {
                is ReadException<T>, UnInitialized -> {
                    // for ReadException and UnInitialized; we can always accept the new state.
                    // this is especially useful when multiple reads fail so each can
                    // send their own exception.
                    newState
                }
                is Data<T> -> {
                    // When overriding Data, only accept newer values.
                    // Note that, when we have Data, we'll only every try to read again
                    // if version changed, and it will arrive here as either new data or
                    // new error with its new version.
                    //
                    // The only other case that might happen is when a read happens in
                    // parallel to a write.
                    // In that case, read either has:
                    // old version, old data
                    // old version, new data
                    // new version, new data
                    // Since the write will send (new version, new data); it is OK to ignore
                    // what read sent if it has old version (or ignore what write sent
                    // if read already sent (new version, new data).
                    // The key constraint here is that, we will never receive
                    // (new version, old data) as version updates happen after data is written.
                    if (newState.version > cached.version) {
                        newState
                    } else {
                        cached
                    }
                }

                is Final<T> -> {
                    // no going back from final state
                    cached
                }
            }
        }
        return updated
    }
}