Updated on 2026-08-14
This commit is contained in:
parent
a57d077863
commit
11f4759544
13 changed files with 273 additions and 0 deletions
|
|
@ -43,6 +43,7 @@ dependencies {
|
||||||
implementation(project(":data:source:preferences"))
|
implementation(project(":data:source:preferences"))
|
||||||
implementation(projects.data.card)
|
implementation(projects.data.card)
|
||||||
implementation(projects.data.tokens)
|
implementation(projects.data.tokens)
|
||||||
|
implementation(projects.data.common)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(project(":features:onboarding"))
|
implementation(project(":features:onboarding"))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.tangem.datasource.di
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.cache.CacheKeysStore
|
||||||
|
import com.tangem.datasource.local.cache.RuntimeCacheKeysStore
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object CacheKeysStoreModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCacheKeysStore(): CacheKeysStore {
|
||||||
|
return RuntimeCacheKeysStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
14
core/datasource/src/main/java/com/tangem/datasource/local/cache/CacheKeysStore.kt
vendored
Normal file
14
core/datasource/src/main/java/com/tangem/datasource/local/cache/CacheKeysStore.kt
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.datasource.local.cache
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.cache.model.CacheKey
|
||||||
|
|
||||||
|
interface CacheKeysStore {
|
||||||
|
|
||||||
|
fun get(id: String): CacheKey?
|
||||||
|
|
||||||
|
fun addOrReplace(key: CacheKey)
|
||||||
|
|
||||||
|
fun remove(id: String)
|
||||||
|
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
25
core/datasource/src/main/java/com/tangem/datasource/local/cache/RuntimeCacheKeysStore.kt
vendored
Normal file
25
core/datasource/src/main/java/com/tangem/datasource/local/cache/RuntimeCacheKeysStore.kt
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.tangem.datasource.local.cache
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.cache.model.CacheKey
|
||||||
|
import com.tangem.datasource.local.store.RuntimeStore
|
||||||
|
|
||||||
|
internal class RuntimeCacheKeysStore : CacheKeysStore {
|
||||||
|
|
||||||
|
private val store = RuntimeStore(keyProvider = CacheKey::id)
|
||||||
|
|
||||||
|
override fun get(id: String): CacheKey? {
|
||||||
|
return store.getSync { it.id == id }.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOrReplace(key: CacheKey) {
|
||||||
|
store.addOrReplace(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(id: String) {
|
||||||
|
store.remove { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
store.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
10
core/datasource/src/main/java/com/tangem/datasource/local/cache/model/CacheKey.kt
vendored
Normal file
10
core/datasource/src/main/java/com/tangem/datasource/local/cache/model/CacheKey.kt
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.datasource.local.cache.model
|
||||||
|
|
||||||
|
import org.joda.time.Duration
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
|
data class CacheKey(
|
||||||
|
val id: String,
|
||||||
|
val updatedAt: LocalDateTime,
|
||||||
|
val expiresIn: Duration,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.tangem.datasource.local.store
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
internal class RuntimeStore<Key, Data>(private val keyProvider: (Data) -> Key) {
|
||||||
|
|
||||||
|
private val store = MutableStateFlow<HashMap<Key, Data>>(hashMapOf())
|
||||||
|
|
||||||
|
fun get(selector: (Data) -> Boolean = { true }): Flow<List<Data>> {
|
||||||
|
return store.map { getInternal(it, selector) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSync(selector: (Data) -> Boolean = { true }): List<Data> {
|
||||||
|
return getInternal(store.value, selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Uncomment if needed
|
||||||
|
// fun addOrReplace(items: Collection<Data>) {
|
||||||
|
// if (items.isEmpty()) return
|
||||||
|
//
|
||||||
|
// val storeValue = store.value
|
||||||
|
//
|
||||||
|
// items.forEach { item ->
|
||||||
|
// storeValue[keyProvider(item)] = item
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// store.value = storeValue
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun addOrReplace(item: Data) {
|
||||||
|
val storeValue = store.value
|
||||||
|
storeValue[keyProvider(item)] = item
|
||||||
|
|
||||||
|
store.value = storeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(selector: (Data) -> Boolean) {
|
||||||
|
val storeValue = store.value
|
||||||
|
|
||||||
|
storeValue.forEach { (key, item) ->
|
||||||
|
if (selector(item)) {
|
||||||
|
storeValue.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.value = storeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
store.update { hashMapOf() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInternal(store: HashMap<Key, Data>, selector: (Data) -> Boolean): List<Data> {
|
||||||
|
return store.values.filter { selector(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
1
data/common/.gitignore
vendored
Normal file
1
data/common/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
16
data/common/build.gradle.kts
Normal file
16
data/common/build.gradle.kts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
|
alias(deps.plugins.kotlin.kapt)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(deps.jodatime)
|
||||||
|
|
||||||
|
implementation(deps.hilt.android)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
}
|
||||||
1
data/common/src/main/AndroidManifest.xml
Normal file
1
data/common/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<manifest package="com.tangem.data.common" />
|
||||||
52
data/common/src/main/kotlin/com/tangem/data/common/cache/CacheRegistry.kt
vendored
Normal file
52
data/common/src/main/kotlin/com/tangem/data/common/cache/CacheRegistry.kt
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.tangem.data.common.cache
|
||||||
|
|
||||||
|
import org.joda.time.Duration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a registry for managing cache.
|
||||||
|
*/
|
||||||
|
interface CacheRegistry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the cache key is expired.
|
||||||
|
*
|
||||||
|
* @param key cache key.
|
||||||
|
* @return `true` if the cache key is expired, `false` otherwise.
|
||||||
|
*/
|
||||||
|
suspend fun isExpired(key: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the cache key in registry.
|
||||||
|
*
|
||||||
|
* If the key doesn't exist, or it's already invalidated, this method doesn't have any effect.
|
||||||
|
*
|
||||||
|
* @param key cache key.
|
||||||
|
*/
|
||||||
|
suspend fun invalidate(key: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates all cache keys in the registry.
|
||||||
|
*
|
||||||
|
* After the call, the registry doesn't contain any valid keys.
|
||||||
|
*/
|
||||||
|
suspend fun invalidateAll()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a callback to be invoked when the cache key expires.
|
||||||
|
*
|
||||||
|
* @param key cache key.
|
||||||
|
* @param skipCache if `true`, the callback will be invoked regardless of whether the key has expired or not.
|
||||||
|
* @param expireIn the duration after which the cache key is considered expired.
|
||||||
|
* @param block the block of code to be executed when the cache key expires.
|
||||||
|
*/
|
||||||
|
suspend fun invokeOnExpire(
|
||||||
|
key: String,
|
||||||
|
skipCache: Boolean,
|
||||||
|
expireIn: Duration = Duration.standardMinutes(DEFAULT_CACHE_KEY_EXPIRE_IN_MINUTES),
|
||||||
|
block: suspend () -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val DEFAULT_CACHE_KEY_EXPIRE_IN_MINUTES = 5L
|
||||||
|
}
|
||||||
|
}
|
||||||
52
data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt
vendored
Normal file
52
data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.tangem.data.common.cache
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.cache.CacheKeysStore
|
||||||
|
import com.tangem.datasource.local.cache.model.CacheKey
|
||||||
|
import org.joda.time.Duration
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
|
internal class DefaultCacheRegistry(
|
||||||
|
private val cacheKeysStore: CacheKeysStore,
|
||||||
|
) : CacheRegistry {
|
||||||
|
|
||||||
|
override suspend fun isExpired(key: String): Boolean {
|
||||||
|
val cacheKey = cacheKeysStore.get(key) ?: return true
|
||||||
|
|
||||||
|
return cacheKey.updatedAt
|
||||||
|
.plus(cacheKey.expiresIn)
|
||||||
|
.isBefore(LocalDateTime.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate(key: String) {
|
||||||
|
cacheKeysStore.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidateAll() {
|
||||||
|
cacheKeysStore.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invokeOnExpire(
|
||||||
|
key: String,
|
||||||
|
skipCache: Boolean,
|
||||||
|
expireIn: Duration,
|
||||||
|
block: suspend () -> Unit,
|
||||||
|
) {
|
||||||
|
val isExpired = isExpired(key) || skipCache
|
||||||
|
if (!isExpired) return
|
||||||
|
|
||||||
|
cacheKeysStore.addOrReplace(
|
||||||
|
key = CacheKey(
|
||||||
|
id = key,
|
||||||
|
updatedAt = LocalDateTime.now(),
|
||||||
|
expiresIn = expireIn,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
invalidate(key)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
data/common/src/main/kotlin/com/tangem/data/common/cache/di/CacheRegistryModule.kt
vendored
Normal file
21
data/common/src/main/kotlin/com/tangem/data/common/cache/di/CacheRegistryModule.kt
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.data.common.cache.di
|
||||||
|
|
||||||
|
import com.tangem.data.common.cache.CacheRegistry
|
||||||
|
import com.tangem.data.common.cache.DefaultCacheRegistry
|
||||||
|
import com.tangem.datasource.local.cache.CacheKeysStore
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object CacheRegistryModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCacheRegistry(cacheKeysStore: CacheKeysStore): CacheRegistry {
|
||||||
|
return DefaultCacheRegistry(cacheKeysStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,6 +85,7 @@ include(":domain:tokens")
|
||||||
// endregion Domain modules
|
// endregion Domain modules
|
||||||
|
|
||||||
// region Data modules
|
// region Data modules
|
||||||
|
include(":data:common")
|
||||||
include(":data:card")
|
include(":data:card")
|
||||||
include(":data:tokens")
|
include(":data:tokens")
|
||||||
include(":data:source:preferences")
|
include(":data:source:preferences")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue