mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-2789 App | Tech | Optimize unsubscribe for global subscriptions (#1534)
This commit is contained in:
parent
51ef35c45c
commit
63a658d515
11 changed files with 664 additions and 135 deletions
|
@ -2,6 +2,7 @@ package com.anytypeio.anytype.domain.config
|
|||
|
||||
import com.anytypeio.anytype.core_models.Config
|
||||
|
||||
@Deprecated("Refactoring needed")
|
||||
interface ConfigStorage {
|
||||
@Deprecated("Unsafe method. Use getOrNull() instead")
|
||||
@Throws(IllegalStateException::class)
|
||||
|
|
|
@ -5,9 +5,9 @@ import com.anytypeio.anytype.core_models.DVSort
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.SubscriptionEvent
|
||||
import com.anytypeio.anytype.domain.`object`.move
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.`object`.move
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
|
@ -39,6 +39,7 @@ class ObjectTypesSubscriptionContainer(
|
|||
beforeId = null,
|
||||
collection = null
|
||||
)
|
||||
store.clear()
|
||||
store.merge(
|
||||
types = initial.results.map { ObjectWrapper.Type(it.map) }
|
||||
)
|
||||
|
@ -130,6 +131,7 @@ class ObjectTypesSubscriptionContainer(
|
|||
|
||||
suspend fun unsubscribe() = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
store.clear()
|
||||
repo.cancelObjectSearchSubscription(
|
||||
listOf(SUBSCRIPTION_ID)
|
||||
)
|
||||
|
@ -157,7 +159,15 @@ class ObjectTypesSubscriptionContainer(
|
|||
val objects: List<Id> = emptyList(),
|
||||
val dependencies: List<Id> = emptyList(),
|
||||
val lastModified: Long = 0L
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = Index(
|
||||
objects = emptyList(),
|
||||
dependencies = emptyList(),
|
||||
lastModified = 0L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SUBSCRIPTION_ID = "object-type-store-subscription"
|
||||
|
|
|
@ -5,86 +5,55 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVFilterCondition
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscription
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ObjectTypesSubscriptionManager (
|
||||
private val scope: CoroutineScope = GlobalScope,
|
||||
private val container: ObjectTypesSubscriptionContainer,
|
||||
private val spaceManager: SpaceManager
|
||||
) {
|
||||
): GlobalSubscription {
|
||||
|
||||
private val pipeline = spaceManager.observe().flatMapLatest { config ->
|
||||
val params = buildParams(config)
|
||||
container.observe(params)
|
||||
val pipeline get() = spaceManager.state().flatMapLatest { state ->
|
||||
when(state) {
|
||||
is SpaceManager.State.Space.Active -> {
|
||||
val params = buildParams(state.config)
|
||||
container.observe(params)
|
||||
}
|
||||
is SpaceManager.State.Space.Idle -> {
|
||||
flow {
|
||||
emit(ObjectTypesSubscriptionContainer.Index.empty())
|
||||
}
|
||||
}
|
||||
is SpaceManager.State.NoSpace -> {
|
||||
flow {
|
||||
container.unsubscribe()
|
||||
emit(ObjectTypesSubscriptionContainer.Index.empty())
|
||||
}
|
||||
}
|
||||
is SpaceManager.State.Init -> {
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
fun onStart() {
|
||||
job?.cancel()
|
||||
job = scope.launch { pipeline.collect() }
|
||||
job = scope.launch {
|
||||
pipeline.collect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildParams(config: Config) =
|
||||
ObjectTypesSubscriptionContainer.Params(
|
||||
subscription = ObjectTypesSubscriptionContainer.SUBSCRIPTION_ID,
|
||||
filters = listOf(
|
||||
DVFilter(
|
||||
relation = Relations.LAYOUT,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = ObjectType.Layout.OBJECT_TYPE.code.toDouble()
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.SPACE_ID,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = config.space
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_DELETED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_ARCHIVED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.UNIQUE_KEY,
|
||||
condition = DVFilterCondition.NOT_EMPTY
|
||||
),
|
||||
),
|
||||
limit = 0,
|
||||
offset = 0L,
|
||||
sorts = emptyList(),
|
||||
sources = emptyList(),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.NAME,
|
||||
Relations.IS_HIDDEN,
|
||||
Relations.IS_DELETED,
|
||||
Relations.IS_ARCHIVED,
|
||||
Relations.SMARTBLOCKTYPES,
|
||||
Relations.LAYOUT,
|
||||
Relations.DESCRIPTION,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.SOURCE_OBJECT,
|
||||
Relations.IS_READ_ONLY,
|
||||
Relations.RECOMMENDED_LAYOUT,
|
||||
Relations.DEFAULT_TEMPLATE_ID,
|
||||
Relations.SPACE_ID,
|
||||
Relations.UNIQUE_KEY,
|
||||
Relations.RESTRICTIONS
|
||||
),
|
||||
ignoreWorkspace = true
|
||||
)
|
||||
|
||||
fun onStop() {
|
||||
scope.launch {
|
||||
container.unsubscribe()
|
||||
|
@ -92,4 +61,60 @@ class ObjectTypesSubscriptionManager (
|
|||
job = null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun buildParams(config: Config) =
|
||||
ObjectTypesSubscriptionContainer.Params(
|
||||
subscription = ObjectTypesSubscriptionContainer.SUBSCRIPTION_ID,
|
||||
filters = listOf(
|
||||
DVFilter(
|
||||
relation = Relations.LAYOUT,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = ObjectType.Layout.OBJECT_TYPE.code.toDouble()
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.SPACE_ID,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = config.space
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_DELETED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_ARCHIVED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.UNIQUE_KEY,
|
||||
condition = DVFilterCondition.NOT_EMPTY
|
||||
),
|
||||
),
|
||||
limit = 0,
|
||||
offset = 0L,
|
||||
sorts = emptyList(),
|
||||
sources = emptyList(),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.NAME,
|
||||
Relations.IS_HIDDEN,
|
||||
Relations.IS_DELETED,
|
||||
Relations.IS_ARCHIVED,
|
||||
Relations.SMARTBLOCKTYPES,
|
||||
Relations.LAYOUT,
|
||||
Relations.DESCRIPTION,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.SOURCE_OBJECT,
|
||||
Relations.IS_READ_ONLY,
|
||||
Relations.RECOMMENDED_LAYOUT,
|
||||
Relations.DEFAULT_TEMPLATE_ID,
|
||||
Relations.SPACE_ID,
|
||||
Relations.UNIQUE_KEY,
|
||||
Relations.RESTRICTIONS
|
||||
),
|
||||
ignoreWorkspace = true
|
||||
)
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ import com.anytypeio.anytype.core_models.DVSort
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.SubscriptionEvent
|
||||
import com.anytypeio.anytype.domain.`object`.move
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.`object`.move
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfRelations
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
|
@ -39,6 +39,7 @@ class RelationsSubscriptionContainer(
|
|||
beforeId = null,
|
||||
collection = null
|
||||
)
|
||||
store.clear()
|
||||
store.merge(
|
||||
relations = initial.results.map { ObjectWrapper.Relation(it.map) }
|
||||
)
|
||||
|
@ -132,6 +133,7 @@ class RelationsSubscriptionContainer(
|
|||
|
||||
suspend fun unsubscribe() = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
store.clear()
|
||||
repo.cancelObjectSearchSubscription(
|
||||
listOf(SUBSCRIPTION_ID)
|
||||
)
|
||||
|
@ -159,7 +161,15 @@ class RelationsSubscriptionContainer(
|
|||
val objects: List<Id> = emptyList(),
|
||||
val dependencies: List<Id> = emptyList(),
|
||||
val lastModified: Long = 0L
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = Index(
|
||||
objects = emptyList(),
|
||||
dependencies = emptyList(),
|
||||
lastModified = 0L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SUBSCRIPTION_ID = "relation-store-subscription"
|
||||
|
|
|
@ -6,13 +6,16 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.core_models.Marketplace
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscription
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
|
@ -20,10 +23,34 @@ class RelationsSubscriptionManager @Inject constructor(
|
|||
private val scope: CoroutineScope = GlobalScope,
|
||||
private val container: RelationsSubscriptionContainer,
|
||||
private val spaceManager: SpaceManager
|
||||
) {
|
||||
private val pipeline = spaceManager.observe().flatMapLatest { config ->
|
||||
val params = buildParams(listOf(config.space, config.techSpace, Marketplace.MARKETPLACE_SPACE_ID))
|
||||
container.observe(params)
|
||||
): GlobalSubscription {
|
||||
val pipeline get() = spaceManager.state().flatMapLatest { state ->
|
||||
when(state) {
|
||||
is SpaceManager.State.Space.Active -> {
|
||||
val params = buildParams(
|
||||
spaces = listOf(
|
||||
state.config.space,
|
||||
state.config.techSpace,
|
||||
Marketplace.MARKETPLACE_SPACE_ID
|
||||
)
|
||||
)
|
||||
container.observe(params)
|
||||
}
|
||||
is SpaceManager.State.Space.Idle -> {
|
||||
flow {
|
||||
emit(RelationsSubscriptionContainer.Index.empty())
|
||||
}
|
||||
}
|
||||
is SpaceManager.State.NoSpace -> {
|
||||
flow {
|
||||
container.unsubscribe()
|
||||
emit(RelationsSubscriptionContainer.Index.empty())
|
||||
}
|
||||
}
|
||||
is SpaceManager.State.Init -> {
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
@ -33,58 +60,6 @@ class RelationsSubscriptionManager @Inject constructor(
|
|||
job = scope.launch { pipeline.collect() }
|
||||
}
|
||||
|
||||
private fun buildParams(spaces: List<Id>) = RelationsSubscriptionContainer.Params(
|
||||
subscription = RelationsSubscriptionContainer.SUBSCRIPTION_ID,
|
||||
filters = listOf(
|
||||
DVFilter(
|
||||
relation = Relations.LAYOUT,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = ObjectType.Layout.RELATION.code.toDouble()
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_DELETED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_ARCHIVED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.SPACE_ID,
|
||||
condition = DVFilterCondition.IN,
|
||||
value = spaces
|
||||
)
|
||||
),
|
||||
limit = 0,
|
||||
offset = 0L,
|
||||
sorts = emptyList(),
|
||||
sources = emptyList(),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.SPACE_ID,
|
||||
Relations.TYPE,
|
||||
Relations.LAYOUT,
|
||||
Relations.NAME,
|
||||
Relations.RELATION_FORMAT,
|
||||
Relations.RELATION_KEY,
|
||||
Relations.SCOPE,
|
||||
Relations.IS_READ_ONLY,
|
||||
Relations.IS_HIDDEN,
|
||||
Relations.IS_DELETED,
|
||||
Relations.IS_ARCHIVED,
|
||||
Relations.IS_FAVORITE,
|
||||
Relations.RESTRICTIONS,
|
||||
Relations.MAX_COUNT,
|
||||
Relations.RELATION_READ_ONLY_VALUE,
|
||||
Relations.RELATION_DEFAULT_VALUE,
|
||||
Relations.RELATION_FORMAT_OBJECT_TYPES,
|
||||
Relations.SOURCE_OBJECT
|
||||
),
|
||||
ignoreWorkspace = true
|
||||
)
|
||||
|
||||
fun onStop() {
|
||||
scope.launch {
|
||||
container.unsubscribe()
|
||||
|
@ -92,4 +67,58 @@ class RelationsSubscriptionManager @Inject constructor(
|
|||
job = null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun buildParams(spaces: List<Id>) = RelationsSubscriptionContainer.Params(
|
||||
subscription = RelationsSubscriptionContainer.SUBSCRIPTION_ID,
|
||||
filters = listOf(
|
||||
DVFilter(
|
||||
relation = Relations.LAYOUT,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = ObjectType.Layout.RELATION.code.toDouble()
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_DELETED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_ARCHIVED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.SPACE_ID,
|
||||
condition = DVFilterCondition.IN,
|
||||
value = spaces
|
||||
)
|
||||
),
|
||||
limit = 0,
|
||||
offset = 0L,
|
||||
sorts = emptyList(),
|
||||
sources = emptyList(),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.SPACE_ID,
|
||||
Relations.TYPE,
|
||||
Relations.LAYOUT,
|
||||
Relations.NAME,
|
||||
Relations.RELATION_FORMAT,
|
||||
Relations.RELATION_KEY,
|
||||
Relations.SCOPE,
|
||||
Relations.IS_READ_ONLY,
|
||||
Relations.IS_HIDDEN,
|
||||
Relations.IS_DELETED,
|
||||
Relations.IS_ARCHIVED,
|
||||
Relations.IS_FAVORITE,
|
||||
Relations.RESTRICTIONS,
|
||||
Relations.MAX_COUNT,
|
||||
Relations.RELATION_READ_ONLY_VALUE,
|
||||
Relations.RELATION_DEFAULT_VALUE,
|
||||
Relations.RELATION_FORMAT_OBJECT_TYPES,
|
||||
Relations.SOURCE_OBJECT
|
||||
),
|
||||
ignoreWorkspace = true
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.anytypeio.anytype.domain.subscriptions
|
||||
|
||||
interface GlobalSubscription
|
|
@ -10,16 +10,23 @@ import com.anytypeio.anytype.domain.debugging.Logger
|
|||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Maybe convert to AppStateManager, with different states.
|
||||
*/
|
||||
interface SpaceManager {
|
||||
|
||||
suspend fun get(): Id
|
||||
suspend fun set(space: Id): Result<Config>
|
||||
|
||||
fun getConfig(): Config?
|
||||
fun getConfig(space: SpaceId) : Config?
|
||||
fun observe() : Flow<Config>
|
||||
fun state(): Flow<State>
|
||||
|
||||
fun clear()
|
||||
|
||||
class Impl @Inject constructor(
|
||||
|
@ -29,14 +36,15 @@ interface SpaceManager {
|
|||
private val logger: Logger
|
||||
) : SpaceManager {
|
||||
|
||||
private val currentSpace = MutableStateFlow(VAULT)
|
||||
private val currentSpace = MutableStateFlow(NO_SPACE)
|
||||
private val info = mutableMapOf<Id, Config>()
|
||||
|
||||
override suspend fun get(): Id {
|
||||
val curr = currentSpace.value
|
||||
return curr.ifEmpty {
|
||||
configStorage.getOrNull()?.space.orEmpty()
|
||||
if (curr.isEmpty()) {
|
||||
logger.logWarning("Accessing space manager in no space state")
|
||||
}
|
||||
return curr
|
||||
}
|
||||
|
||||
override fun getConfig(): Config? {
|
||||
|
@ -44,7 +52,7 @@ interface SpaceManager {
|
|||
return if (curr.isNotEmpty()) {
|
||||
info[curr]
|
||||
} else {
|
||||
configStorage.getOrNull()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,17 +75,23 @@ interface SpaceManager {
|
|||
override fun observe(): Flow<Config> {
|
||||
return currentSpace.mapNotNull { space ->
|
||||
if (space.isEmpty()) {
|
||||
configStorage.getOrNull()
|
||||
null
|
||||
} else {
|
||||
info[space]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun state(): Flow<State> {
|
||||
return currentSpace.map { space ->
|
||||
if (space == NO_SPACE) {
|
||||
State.NoSpace
|
||||
} else {
|
||||
val config = info[space]
|
||||
if (config != null)
|
||||
config
|
||||
else {
|
||||
val default = configStorage.getOrNull()
|
||||
if (default != null && default.space == space)
|
||||
default
|
||||
else
|
||||
null
|
||||
if (config != null) {
|
||||
State.Space.Active(config)
|
||||
} else {
|
||||
State.Space.Idle(SpaceId(space))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,11 +99,20 @@ interface SpaceManager {
|
|||
|
||||
override fun clear() {
|
||||
info.clear()
|
||||
currentSpace.value = VAULT
|
||||
currentSpace.value = NO_SPACE
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val VAULT = ""
|
||||
const val NO_SPACE = ""
|
||||
}
|
||||
}
|
||||
|
||||
sealed class State {
|
||||
data object Init: State()
|
||||
data object NoSpace: State()
|
||||
sealed class Space: State() {
|
||||
data class Idle(val space: SpaceId): Space()
|
||||
data class Active(val config: Config): Space()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
package com.anytypeio.anytype.domain.subscriptions
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.SearchResult
|
||||
import com.anytypeio.anytype.core_models.StubConfig
|
||||
import com.anytypeio.anytype.core_models.StubObjectType
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.common.DefaultCoroutineTestRule
|
||||
import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
|
||||
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
class ObjectTypesSubscriptionContainerTest {
|
||||
|
||||
@get:Rule
|
||||
val coroutineTestRule = DefaultCoroutineTestRule()
|
||||
|
||||
private val dispatchers = AppCoroutineDispatchers(
|
||||
main = coroutineTestRule.dispatcher,
|
||||
computation = coroutineTestRule.dispatcher,
|
||||
io = coroutineTestRule.dispatcher
|
||||
)
|
||||
|
||||
@Mock lateinit var spaceManager: SpaceManager
|
||||
|
||||
@Mock
|
||||
lateinit var repo: BlockRepository
|
||||
|
||||
@Mock
|
||||
lateinit var channel: SubscriptionEventChannel
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should populate store with latest results and clear it when unsubscribe`() = runTest {
|
||||
|
||||
// SETUP
|
||||
|
||||
val delay = 300
|
||||
|
||||
val subscription = ObjectTypesSubscriptionContainer.SUBSCRIPTION_ID
|
||||
|
||||
val store = DefaultStoreOfObjectTypes()
|
||||
|
||||
val defaultSpaceConfig = StubConfig()
|
||||
|
||||
val alternativeSpaceConfig = StubConfig()
|
||||
|
||||
val container = ObjectTypesSubscriptionContainer(
|
||||
repo = repo,
|
||||
channel = channel,
|
||||
store = store,
|
||||
dispatchers = dispatchers
|
||||
)
|
||||
|
||||
val manager = ObjectTypesSubscriptionManager(
|
||||
scope = TestScope(),
|
||||
spaceManager = spaceManager,
|
||||
container = container
|
||||
)
|
||||
|
||||
val defaultSpaceSearchParams = ObjectTypesSubscriptionManager.buildParams(
|
||||
defaultSpaceConfig
|
||||
)
|
||||
|
||||
val alternativeSpaceSearchParams = ObjectTypesSubscriptionManager.buildParams(
|
||||
alternativeSpaceConfig
|
||||
)
|
||||
|
||||
val defaultSpaceTypes = buildList {
|
||||
add(
|
||||
StubObjectType()
|
||||
)
|
||||
add(
|
||||
StubObjectType()
|
||||
)
|
||||
}
|
||||
|
||||
val alternativeSpaceTypes = buildList {
|
||||
add(
|
||||
StubObjectType()
|
||||
)
|
||||
add(
|
||||
StubObjectType()
|
||||
)
|
||||
}
|
||||
|
||||
// STUBBING
|
||||
|
||||
channel.stub {
|
||||
on {
|
||||
subscribe(
|
||||
listOf(subscription)
|
||||
)
|
||||
} doReturn emptyFlow()
|
||||
}
|
||||
|
||||
repo.stub {
|
||||
onBlocking {
|
||||
cancelObjectSearchSubscription(listOf(subscription))
|
||||
} doReturn Unit
|
||||
}
|
||||
|
||||
spaceManager.stub {
|
||||
on {
|
||||
state()
|
||||
} doReturn flow {
|
||||
emit(
|
||||
SpaceManager.State.Space.Active(
|
||||
defaultSpaceConfig
|
||||
)
|
||||
)
|
||||
delay(300)
|
||||
emit(
|
||||
SpaceManager.State.Space.Active(
|
||||
alternativeSpaceConfig
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
repo.stub {
|
||||
onBlocking {
|
||||
searchObjectsWithSubscription(
|
||||
subscription = defaultSpaceSearchParams.subscription,
|
||||
sorts = defaultSpaceSearchParams.sorts,
|
||||
filters = defaultSpaceSearchParams.filters,
|
||||
limit = defaultSpaceSearchParams.limit,
|
||||
offset = defaultSpaceSearchParams.offset,
|
||||
keys = defaultSpaceSearchParams.keys,
|
||||
ignoreWorkspace = defaultSpaceSearchParams.ignoreWorkspace,
|
||||
source = defaultSpaceSearchParams.sources,
|
||||
afterId = null,
|
||||
beforeId = null,
|
||||
collection = null,
|
||||
noDepSubscription = true
|
||||
)
|
||||
} doReturn SearchResult(
|
||||
results = defaultSpaceTypes.map { ObjectWrapper.Basic(it.map) },
|
||||
dependencies = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
repo.stub {
|
||||
onBlocking {
|
||||
searchObjectsWithSubscription(
|
||||
subscription = alternativeSpaceSearchParams.subscription,
|
||||
sorts = alternativeSpaceSearchParams.sorts,
|
||||
filters = alternativeSpaceSearchParams.filters,
|
||||
limit = alternativeSpaceSearchParams.limit,
|
||||
offset = alternativeSpaceSearchParams.offset,
|
||||
keys = alternativeSpaceSearchParams.keys,
|
||||
ignoreWorkspace = alternativeSpaceSearchParams.ignoreWorkspace,
|
||||
source = alternativeSpaceSearchParams.sources,
|
||||
afterId = null,
|
||||
beforeId = null,
|
||||
collection = null,
|
||||
noDepSubscription = true
|
||||
)
|
||||
} doReturn SearchResult(
|
||||
results = alternativeSpaceTypes.map { ObjectWrapper.Basic(it.map) },
|
||||
dependencies = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
manager.pipeline.test {
|
||||
val first = awaitItem()
|
||||
assertEquals(
|
||||
expected = defaultSpaceTypes,
|
||||
actual = store.getAll()
|
||||
)
|
||||
val second = awaitItem()
|
||||
awaitComplete()
|
||||
assertEquals(
|
||||
expected = alternativeSpaceTypes,
|
||||
actual = store.getAll()
|
||||
)
|
||||
|
||||
container.unsubscribe()
|
||||
|
||||
assertEquals(
|
||||
expected = emptyList(),
|
||||
actual = store.getAll()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
package com.anytypeio.anytype.domain.subscriptions
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.core_models.Marketplace
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.SearchResult
|
||||
import com.anytypeio.anytype.core_models.StubConfig
|
||||
import com.anytypeio.anytype.core_models.StubRelationObject
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.common.DefaultCoroutineTestRule
|
||||
import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations
|
||||
import com.anytypeio.anytype.domain.search.RelationsSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
|
||||
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
class RelationsSubscriptionContainerTest {
|
||||
|
||||
@get:Rule
|
||||
val coroutineTestRule = DefaultCoroutineTestRule()
|
||||
|
||||
private val dispatchers = AppCoroutineDispatchers(
|
||||
main = coroutineTestRule.dispatcher,
|
||||
computation = coroutineTestRule.dispatcher,
|
||||
io = coroutineTestRule.dispatcher
|
||||
)
|
||||
|
||||
@Mock lateinit var spaceManager: SpaceManager
|
||||
|
||||
@Mock
|
||||
lateinit var repo: BlockRepository
|
||||
|
||||
@Mock
|
||||
lateinit var channel: SubscriptionEventChannel
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should populate store with latest results and clear it when unsubscribe`() = runTest {
|
||||
|
||||
// SETUP
|
||||
|
||||
val delay = 300L
|
||||
|
||||
val subscription = RelationsSubscriptionContainer.SUBSCRIPTION_ID
|
||||
|
||||
val store = DefaultStoreOfRelations()
|
||||
|
||||
val defaultSpaceConfig = StubConfig()
|
||||
|
||||
val alternativeSpaceConfig = StubConfig()
|
||||
|
||||
val container = RelationsSubscriptionContainer(
|
||||
repo = repo,
|
||||
channel = channel,
|
||||
store = store,
|
||||
dispatchers = dispatchers
|
||||
)
|
||||
|
||||
val manager = RelationsSubscriptionManager (
|
||||
scope = TestScope(),
|
||||
spaceManager = spaceManager,
|
||||
container = container
|
||||
)
|
||||
|
||||
val defaultSpaceSearchParams = RelationsSubscriptionManager.buildParams(
|
||||
spaces = listOf(
|
||||
defaultSpaceConfig.space,
|
||||
defaultSpaceConfig.techSpace,
|
||||
Marketplace.MARKETPLACE_SPACE_ID,
|
||||
)
|
||||
)
|
||||
|
||||
val alternativeSpaceSearchParams = RelationsSubscriptionManager.buildParams(
|
||||
spaces = listOf(
|
||||
alternativeSpaceConfig.space,
|
||||
alternativeSpaceConfig.techSpace,
|
||||
Marketplace.MARKETPLACE_SPACE_ID,
|
||||
)
|
||||
)
|
||||
|
||||
val defaultSpaceTypes = buildList {
|
||||
add(
|
||||
StubRelationObject()
|
||||
)
|
||||
add(
|
||||
StubRelationObject()
|
||||
)
|
||||
}
|
||||
|
||||
val alternativeSpaceTypes = buildList {
|
||||
add(
|
||||
StubRelationObject()
|
||||
)
|
||||
add(
|
||||
StubRelationObject()
|
||||
)
|
||||
}
|
||||
|
||||
// STUBBING
|
||||
|
||||
channel.stub {
|
||||
on {
|
||||
subscribe(
|
||||
listOf(subscription)
|
||||
)
|
||||
} doReturn emptyFlow()
|
||||
}
|
||||
|
||||
repo.stub {
|
||||
onBlocking {
|
||||
cancelObjectSearchSubscription(listOf(subscription))
|
||||
} doReturn Unit
|
||||
}
|
||||
|
||||
spaceManager.stub {
|
||||
on {
|
||||
state()
|
||||
} doReturn flow {
|
||||
emit(
|
||||
SpaceManager.State.Space.Active(
|
||||
defaultSpaceConfig
|
||||
)
|
||||
)
|
||||
delay(delay)
|
||||
emit(
|
||||
SpaceManager.State.Space.Active(
|
||||
alternativeSpaceConfig
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
repo.stub {
|
||||
onBlocking {
|
||||
searchObjectsWithSubscription(
|
||||
subscription = defaultSpaceSearchParams.subscription,
|
||||
sorts = defaultSpaceSearchParams.sorts,
|
||||
filters = defaultSpaceSearchParams.filters,
|
||||
limit = defaultSpaceSearchParams.limit,
|
||||
offset = defaultSpaceSearchParams.offset,
|
||||
keys = defaultSpaceSearchParams.keys,
|
||||
ignoreWorkspace = defaultSpaceSearchParams.ignoreWorkspace,
|
||||
source = defaultSpaceSearchParams.sources,
|
||||
afterId = null,
|
||||
beforeId = null,
|
||||
collection = null,
|
||||
noDepSubscription = true
|
||||
)
|
||||
} doReturn SearchResult(
|
||||
results = defaultSpaceTypes.map { ObjectWrapper.Basic(it.map) },
|
||||
dependencies = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
repo.stub {
|
||||
onBlocking {
|
||||
searchObjectsWithSubscription(
|
||||
subscription = alternativeSpaceSearchParams.subscription,
|
||||
sorts = alternativeSpaceSearchParams.sorts,
|
||||
filters = alternativeSpaceSearchParams.filters,
|
||||
limit = alternativeSpaceSearchParams.limit,
|
||||
offset = alternativeSpaceSearchParams.offset,
|
||||
keys = alternativeSpaceSearchParams.keys,
|
||||
ignoreWorkspace = alternativeSpaceSearchParams.ignoreWorkspace,
|
||||
source = alternativeSpaceSearchParams.sources,
|
||||
afterId = null,
|
||||
beforeId = null,
|
||||
collection = null,
|
||||
noDepSubscription = true
|
||||
)
|
||||
} doReturn SearchResult(
|
||||
results = alternativeSpaceTypes.map { ObjectWrapper.Basic(it.map) },
|
||||
dependencies = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
manager.pipeline.test {
|
||||
val first = awaitItem()
|
||||
assertEquals(
|
||||
expected = defaultSpaceTypes,
|
||||
actual = store.getAll()
|
||||
)
|
||||
val second = awaitItem()
|
||||
awaitComplete()
|
||||
assertEquals(
|
||||
expected = alternativeSpaceTypes,
|
||||
actual = store.getAll()
|
||||
)
|
||||
|
||||
container.unsubscribe()
|
||||
|
||||
assertEquals(
|
||||
expected = emptyList(),
|
||||
actual = store.getAll()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1643,6 +1643,7 @@ class HomeScreenViewModel(
|
|||
|
||||
fun onVaultClicked() {
|
||||
viewModelScope.launch {
|
||||
spaceManager.clear()
|
||||
clearLastOpenedSpace.async(Unit).fold(
|
||||
onSuccess = {
|
||||
Timber.d("Cleared last opened space before opening vault")
|
||||
|
|
|
@ -213,7 +213,7 @@ fun StubCallout(
|
|||
|
||||
fun StubRelation(
|
||||
relationKey: String = MockDataFactory.randomString(),
|
||||
format: RelationFormat
|
||||
format: RelationFormat = Relation.Format.SHORT_TEXT
|
||||
): Relation = Relation(
|
||||
key = relationKey,
|
||||
name = MockDataFactory.randomString(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue