diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a297e0b..9ff4610ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## EventManager Version [1.19.1] + +Nov 18, 2021 + +### Dependencies + +- Contact 1.19.1. +- EventManager 1.19.1. +- Mail Settings 1.19.1. +- User 1.19.1. +- User Settings 1.19.1. + +### New Features + +- Added EventManagerConfig parameter to all EventListener functions. + +### Breaking Changes + +- EventListener functions signature changed (userId removed, added config). + ## Plan Presentation [1.18.1] 17 Nov, 2021 diff --git a/contact/build.gradle.kts b/contact/build.gradle.kts index 847eeb5dd..c1347c07a 100644 --- a/contact/build.gradle.kts +++ b/contact/build.gradle.kts @@ -23,7 +23,7 @@ plugins { kotlin("android") } -libVersion = Version(1, 18, 0) +libVersion = Version(1, 19, 1) android() diff --git a/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEmailsEventListener.kt b/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEmailsEventListener.kt index 133e1ee97..0b433edba 100644 --- a/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEmailsEventListener.kt +++ b/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEmailsEventListener.kt @@ -26,8 +26,8 @@ import me.proton.core.contact.domain.entity.ContactEmailId import me.proton.core.contact.domain.entity.ContactId import me.proton.core.contact.domain.repository.ContactLocalDataSource import me.proton.core.contact.domain.repository.ContactRepository -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -62,7 +62,10 @@ class ContactEmailEventListener @Inject constructor( override val type = Type.Core override val order = 2 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.contactEmails?.map { Event(requireNotNull(Action.map[it.action]), it.id, it.contactEmail) } @@ -72,31 +75,31 @@ class ContactEmailEventListener @Inject constructor( return db.inTransaction(block) } - override suspend fun onPrepare(userId: UserId, entities: List) { + override suspend fun onPrepare(config: EventManagerConfig, entities: List) { // Don't fetch Contacts that will be created in this set of modifications. - val contactActions = contactEventListener.getActionMap(userId) + val contactActions = contactEventListener.getActionMap(config) val createContactIds = contactActions[Action.Create].orEmpty().map { it.key }.toHashSet() // Make sure we'll fetch other Contacts. entities.filterNot { createContactIds.contains(it.contactId) }.forEach { - contactRepository.getContactWithCards(userId, ContactId(it.contactId), refresh = false) + contactRepository.getContactWithCards(config.userId, ContactId(it.contactId), refresh = false) } } - override suspend fun onCreate(userId: UserId, entities: List) { - entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(userId)) } + override suspend fun onCreate(config: EventManagerConfig, entities: List) { + entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(config.userId)) } } - override suspend fun onUpdate(userId: UserId, entities: List) { - entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(userId)) } + override suspend fun onUpdate(config: EventManagerConfig, entities: List) { + entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(config.userId)) } } - override suspend fun onDelete(userId: UserId, keys: List) { + override suspend fun onDelete(config: EventManagerConfig, keys: List) { contactLocalDataSource.deleteContactEmails(*keys.map { ContactEmailId(it) }.toTypedArray()) } - override suspend fun onResetAll(userId: UserId) { + override suspend fun onResetAll(config: EventManagerConfig) { // Already handled in ContactEventListener: - // contactLocalDataSource.deleteAllContactEmails(userId) + // contactLocalDataSource.deleteAllContactEmails(config.userId) // contactRepository.getAllContactEmails(userId, refresh = true) } } diff --git a/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEventListener.kt b/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEventListener.kt index 463213a8e..789913c44 100644 --- a/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEventListener.kt +++ b/contact/data/src/main/kotlin/me/proton/core/contact/data/ContactEventListener.kt @@ -25,8 +25,8 @@ import me.proton.core.contact.data.local.db.ContactDatabase import me.proton.core.contact.domain.entity.ContactId import me.proton.core.contact.domain.repository.ContactLocalDataSource import me.proton.core.contact.domain.repository.ContactRepository -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -60,7 +60,10 @@ class ContactEventListener @Inject constructor( override val type = Type.Core override val order = 1 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.contacts?.map { Event(requireNotNull(Action.map[it.action]), it.id, it.contact) } @@ -70,20 +73,20 @@ class ContactEventListener @Inject constructor( return db.inTransaction(block) } - override suspend fun onCreate(userId: UserId, entities: List) { - entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(userId)) } + override suspend fun onCreate(config: EventManagerConfig, entities: List) { + entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(config.userId)) } } - override suspend fun onUpdate(userId: UserId, entities: List) { - entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(userId)) } + override suspend fun onUpdate(config: EventManagerConfig, entities: List) { + entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(config.userId)) } } - override suspend fun onDelete(userId: UserId, keys: List) { + override suspend fun onDelete(config: EventManagerConfig, keys: List) { contactLocalDataSource.deleteContacts(*keys.map { ContactId(it) }.toTypedArray()) } - override suspend fun onResetAll(userId: UserId) { - contactLocalDataSource.deleteAllContacts(userId) - contactRepository.getAllContacts(userId, refresh = true) + override suspend fun onResetAll(config: EventManagerConfig) { + contactLocalDataSource.deleteAllContacts(config.userId) + contactRepository.getAllContacts(config.userId, refresh = true) } } diff --git a/event-manager/build.gradle.kts b/event-manager/build.gradle.kts index 42204d769..4a4a56215 100644 --- a/event-manager/build.gradle.kts +++ b/event-manager/build.gradle.kts @@ -24,7 +24,7 @@ plugins { kotlin("android") } -libVersion = Version(1, 18, 1) +libVersion = Version(1, 19, 1) android() diff --git a/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventDeserializer.kt b/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventDeserializer.kt index bac12f5a9..918337adf 100644 --- a/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventDeserializer.kt +++ b/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventDeserializer.kt @@ -18,7 +18,6 @@ package me.proton.core.eventmanager.data -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.data.api.response.GetCalendarEventsResponse import me.proton.core.eventmanager.data.api.response.GetCalendarLatestEventIdResponse import me.proton.core.eventmanager.data.api.response.GetCoreEventsResponse @@ -37,7 +36,7 @@ interface EventDeserializer { val config: EventManagerConfig val endpoint: String fun deserializeLatestEventId(response: EventIdResponse): EventId - fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata + fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata } internal data class CoreEventDeserializer( @@ -49,10 +48,10 @@ internal data class CoreEventDeserializer( override fun deserializeLatestEventId(response: EventIdResponse): EventId = EventId(response.body.deserialize().eventId) - override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata = + override fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata = response.body.deserialize().let { EventMetadata( - userId = userId, + userId = config.userId, eventId = eventId, config = config, nextEventId = EventId(it.eventId), @@ -73,10 +72,10 @@ internal data class CalendarEventDeserializer( override fun deserializeLatestEventId(response: EventIdResponse): EventId = EventId(response.body.deserialize().eventId) - override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata = + override fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata = response.body.deserialize().let { EventMetadata( - userId = userId, + userId = config.userId, eventId = eventId, config = config, nextEventId = EventId(it.eventId), @@ -97,10 +96,10 @@ internal class DriveEventDeserializer( override fun deserializeLatestEventId(response: EventIdResponse): EventId = EventId(response.body.deserialize().eventId) - override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata = + override fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata = response.body.deserialize().let { EventMetadata( - userId = userId, + userId = config.userId, eventId = eventId, config = config, nextEventId = EventId(it.eventId), diff --git a/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventManagerImpl.kt b/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventManagerImpl.kt index 48bf89139..8c44821eb 100644 --- a/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventManagerImpl.kt +++ b/event-manager/data/src/main/kotlin/me/proton/core/eventmanager/data/EventManagerImpl.kt @@ -78,7 +78,7 @@ class EventManagerImpl @AssistedInject constructor( response: EventsResponse ): Map, List>> { return eventListenersByOrder.values.flatten().associateWith { eventListener -> - eventListener.deserializeEvents(response).orEmpty() + eventListener.deserializeEvents(config, response).orEmpty() } } @@ -160,7 +160,7 @@ class EventManagerImpl @AssistedInject constructor( ) { // Fully sequential and ordered. eventListenersByOrder.values.flatten().forEach { - it.notifyResetAll(metadata.userId) + it.notifyResetAll(config) } }.onFailure { CoreLogger.e(LogTag.NOTIFY_ERROR, it) @@ -181,11 +181,11 @@ class EventManagerImpl @AssistedInject constructor( // Set actions for all listeners. val eventsByListener = deserializeEventsByListener(requireNotNull(metadata.response)) eventsByListener.forEach { (eventListener, list) -> - eventListener.setActionMap(metadata.userId, list as List) + eventListener.setActionMap(config, list as List) } // Notify prepare for all listeners. eventListenersByOrder.values.flatten().forEach { eventListener -> - eventListener.notifyPrepare(metadata.userId) + eventListener.notifyPrepare(config) } }.onFailure { CoreLogger.e(LogTag.NOTIFY_ERROR, it) @@ -205,7 +205,7 @@ class EventManagerImpl @AssistedInject constructor( ) { // Fully sequential and ordered. eventListenersByOrder.values.flatten().forEach { eventListener -> - eventListener.notifyEvents(metadata.userId) + eventListener.notifyEvents(config) } }.onFailure { CoreLogger.e(LogTag.NOTIFY_ERROR, it) @@ -225,7 +225,7 @@ class EventManagerImpl @AssistedInject constructor( ) { // Fully sequential and ordered. eventListenersByOrder.values.flatten().forEach { eventListener -> - eventListener.notifyComplete(metadata.userId) + eventListener.notifyComplete(config) } }.onFailure { CoreLogger.e(LogTag.NOTIFY_ERROR, it) @@ -336,7 +336,7 @@ class EventManagerImpl @AssistedInject constructor( eventMetadataRepository.getEvents(config.userId, eventId, deserializer.endpoint) override suspend fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata = - deserializer.deserializeEventMetadata(config.userId, eventId, response) + deserializer.deserializeEventMetadata(eventId, response) companion object { // Constraint: retriesBeforeNotifyResetAll < retriesBeforeReset. diff --git a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/EventManagerImplTest.kt b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/EventManagerImplTest.kt index a4991686e..329bfdabb 100644 --- a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/EventManagerImplTest.kt +++ b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/EventManagerImplTest.kt @@ -33,6 +33,7 @@ import me.proton.core.account.domain.entity.AccountDetails import me.proton.core.account.domain.entity.AccountState import me.proton.core.accountmanager.domain.AccountManager import me.proton.core.domain.entity.UserId +import me.proton.core.eventmanager.data.listener.CalendarEventListener import me.proton.core.eventmanager.data.listener.ContactEventListener import me.proton.core.eventmanager.data.listener.UserEventListener import me.proton.core.eventmanager.domain.EventListener @@ -44,11 +45,15 @@ import me.proton.core.eventmanager.domain.entity.EventIdResponse import me.proton.core.eventmanager.domain.entity.EventMetadata import me.proton.core.eventmanager.domain.entity.EventsResponse import me.proton.core.eventmanager.domain.entity.State +import me.proton.core.eventmanager.domain.extension.asCalendar +import me.proton.core.eventmanager.domain.extension.asDrive import me.proton.core.eventmanager.domain.repository.EventMetadataRepository import me.proton.core.eventmanager.domain.work.EventWorkerManager import me.proton.core.presentation.app.AppLifecycleProvider import org.junit.Before import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class EventManagerImplTest { @@ -64,6 +69,7 @@ class EventManagerImplTest { private lateinit var userEventListener: UserEventListener private lateinit var contactEventListener: ContactEventListener + private lateinit var calendarEventListener: CalendarEventListener private lateinit var listeners: Set> private val user1 = Account( @@ -89,17 +95,22 @@ class EventManagerImplTest { private val user1Config = EventManagerConfig.Core(user1.userId) private val user2Config = EventManagerConfig.Core(user2.userId) + private val calendarId = "calendarId" + private val calendarConfig = EventManagerConfig.Calendar(user1.userId, calendarId) + private val eventId = "eventId" private val appState = MutableStateFlow(AppLifecycleProvider.State.Foreground) private lateinit var user1Manager: EventManager private lateinit var user2Manager: EventManager + private lateinit var calendarManager: EventManager @Before fun before() { userEventListener = spyk(UserEventListener()) contactEventListener = spyk(ContactEventListener()) - listeners = setOf>(userEventListener, contactEventListener) + calendarEventListener = spyk(CalendarEventListener()) + listeners = setOf(userEventListener, contactEventListener, calendarEventListener) appLifecycleProvider = mockk { every { state } returns appState @@ -129,6 +140,7 @@ class EventManagerImplTest { eventManagerProvider = EventManagerProviderImpl(eventManagerFactor, listeners) user1Manager = eventManagerProvider.get(user1Config) user2Manager = eventManagerProvider.get(user2Config) + calendarManager = eventManagerProvider.get(calendarConfig) coEvery { eventMetadataRepository.getLatestEventId(any(), any()) } returns EventIdResponse("{ \"EventID\": \"$eventId\" }") @@ -145,6 +157,9 @@ class EventManagerImplTest { coEvery { eventMetadataRepository.get(user2Config) } returns listOf(EventMetadata(user2.userId, EventId(eventId), user2Config, createdAt = 1)) + + coEvery { eventMetadataRepository.get(calendarConfig) } returns + listOf(EventMetadata(user1.userId, EventId(eventId), calendarConfig, createdAt = 1)) } @Test @@ -156,34 +171,34 @@ class EventManagerImplTest { coVerify(exactly = 2) { userEventListener.inTransaction(any()) } coVerify(exactly = 2) { contactEventListener.inTransaction(any()) } - coVerify(exactly = 1) { userEventListener.onPrepare(user1.userId, any()) } - coVerify(exactly = 1) { userEventListener.onUpdate(user1.userId, any()) } - coVerify(exactly = 0) { userEventListener.onDelete(user1.userId, any()) } - coVerify(exactly = 0) { userEventListener.onCreate(user1.userId, any()) } - coVerify(exactly = 0) { userEventListener.onPartial(user1.userId, any()) } + coVerify(exactly = 1) { userEventListener.onPrepare(user1Config, any()) } + coVerify(exactly = 1) { userEventListener.onUpdate(user1Config, any()) } + coVerify(exactly = 0) { userEventListener.onDelete(user1Config, any()) } + coVerify(exactly = 0) { userEventListener.onCreate(user1Config, any()) } + coVerify(exactly = 0) { userEventListener.onPartial(user1Config, any()) } - coVerify(exactly = 1) { contactEventListener.onPrepare(user1.userId, any()) } - coVerify(exactly = 0) { contactEventListener.onUpdate(user1.userId, any()) } - coVerify(exactly = 0) { contactEventListener.onDelete(user1.userId, any()) } - coVerify(exactly = 1) { contactEventListener.onCreate(user1.userId, any()) } - coVerify(exactly = 0) { contactEventListener.onPartial(user1.userId, any()) } + coVerify(exactly = 1) { contactEventListener.onPrepare(user1Config, any()) } + coVerify(exactly = 0) { contactEventListener.onUpdate(user1Config, any()) } + coVerify(exactly = 0) { contactEventListener.onDelete(user1Config, any()) } + coVerify(exactly = 1) { contactEventListener.onCreate(user1Config, any()) } + coVerify(exactly = 0) { contactEventListener.onPartial(user1Config, any()) } coVerify(atLeast = 1) { eventMetadataRepository.updateState(user1Config, any(), State.NotifyPrepare) } coVerify(atLeast = 1) { eventMetadataRepository.updateState(user1Config, any(), State.NotifyEvents) } coVerify(atLeast = 1) { eventMetadataRepository.updateState(user1Config, any(), State.NotifyComplete) } coVerify(exactly = 1) { eventMetadataRepository.updateState(user1Config, any(), State.Completed) } - coVerify(exactly = 1) { userEventListener.onPrepare(user2.userId, any()) } - coVerify(exactly = 1) { userEventListener.onUpdate(user2.userId, any()) } - coVerify(exactly = 0) { userEventListener.onDelete(user2.userId, any()) } - coVerify(exactly = 0) { userEventListener.onCreate(user2.userId, any()) } - coVerify(exactly = 0) { userEventListener.onPartial(user2.userId, any()) } + coVerify(exactly = 1) { userEventListener.onPrepare(user2Config, any()) } + coVerify(exactly = 1) { userEventListener.onUpdate(user2Config, any()) } + coVerify(exactly = 0) { userEventListener.onDelete(user2Config, any()) } + coVerify(exactly = 0) { userEventListener.onCreate(user2Config, any()) } + coVerify(exactly = 0) { userEventListener.onPartial(user2Config, any()) } - coVerify(exactly = 1) { contactEventListener.onPrepare(user2.userId, any()) } - coVerify(exactly = 0) { contactEventListener.onUpdate(user2.userId, any()) } - coVerify(exactly = 0) { contactEventListener.onDelete(user2.userId, any()) } - coVerify(exactly = 1) { contactEventListener.onCreate(user2.userId, any()) } - coVerify(exactly = 0) { contactEventListener.onPartial(user2.userId, any()) } + coVerify(exactly = 1) { contactEventListener.onPrepare(user2Config, any()) } + coVerify(exactly = 0) { contactEventListener.onUpdate(user2Config, any()) } + coVerify(exactly = 0) { contactEventListener.onDelete(user2Config, any()) } + coVerify(exactly = 1) { contactEventListener.onCreate(user2Config, any()) } + coVerify(exactly = 0) { contactEventListener.onPartial(user2Config, any()) } coVerify(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyPrepare) } coVerify(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyEvents) } @@ -194,25 +209,25 @@ class EventManagerImplTest { @Test fun callOnPrepareThrowException() = runBlocking { // GIVEN - coEvery { userEventListener.onPrepare(user1.userId, any()) } throws Exception("IOException") + coEvery { userEventListener.onPrepare(user1Config, any()) } throws Exception("IOException") // WHEN user1Manager.process() // THEN coVerify(exactly = 2) { eventMetadataRepository.updateState(any(), any(), State.NotifyPrepare) } - coVerify(exactly = 0) { userEventListener.onUpdate(user1.userId, any()) } + coVerify(exactly = 0) { userEventListener.onUpdate(user1Config, any()) } coVerify(exactly = 0) { userEventListener.inTransaction(any()) } } @Test fun callOnUpdateThrowException() = runBlocking { // GIVEN - coEvery { userEventListener.onUpdate(user1.userId, any()) } throws Exception("SqlForeignKeyException") + coEvery { userEventListener.onUpdate(user1Config, any()) } throws Exception("SqlForeignKeyException") // WHEN user1Manager.process() // THEN coVerify(exactly = 2) { eventMetadataRepository.updateState(any(), any(), State.NotifyPrepare) } coVerify(exactly = 2) { eventMetadataRepository.updateState(any(), any(), State.NotifyEvents) } - coVerify(exactly = 1) { userEventListener.onUpdate(user1.userId, any()) } + coVerify(exactly = 1) { userEventListener.onUpdate(user1Config, any()) } coVerify(exactly = 1) { userEventListener.inTransaction(any()) } } @@ -225,11 +240,11 @@ class EventManagerImplTest { // WHEN user1Manager.process() // THEN - coVerify(exactly = 1) { userEventListener.onPrepare(user1.userId, any()) } - coVerify(exactly = 1) { userEventListener.onUpdate(user1.userId, any()) } - coVerify(exactly = 0) { userEventListener.onDelete(user1.userId, any()) } - coVerify(exactly = 0) { userEventListener.onCreate(user1.userId, any()) } - coVerify(exactly = 0) { userEventListener.onPartial(user1.userId, any()) } + coVerify(exactly = 1) { userEventListener.onPrepare(user1Config, any()) } + coVerify(exactly = 1) { userEventListener.onUpdate(user1Config, any()) } + coVerify(exactly = 0) { userEventListener.onDelete(user1Config, any()) } + coVerify(exactly = 0) { userEventListener.onCreate(user1Config, any()) } + coVerify(exactly = 0) { userEventListener.onPartial(user1Config, any()) } } @Test @@ -245,4 +260,23 @@ class EventManagerImplTest { coVerify(exactly = 0) { userEventListener.onCreate(any(), any()) } coVerify(exactly = 0) { userEventListener.onPartial(any(), any()) } } + + @Test + fun tryCastEventManagerConfig() = runBlocking { + // GIVEN + coEvery { eventMetadataRepository.getEvents(any(), any(), any()) } returns + EventsResponse(TestEvents.calendarFullEventsResponse) + // WHEN + calendarManager.process() + // THEN + coVerify(exactly = 1) { calendarEventListener.onPrepare(any(), any()) } + coVerify(exactly = 0) { calendarEventListener.onUpdate(any(), any()) } + coVerify(exactly = 0) { calendarEventListener.onDelete(any(), any()) } + coVerify(exactly = 1) { calendarEventListener.onCreate(any(), any()) } + coVerify(exactly = 0) { calendarEventListener.onPartial(any(), any()) } + assertFailsWith(ClassCastException::class) { + calendarEventListener.config.asDrive() + } + assertEquals(calendarId, calendarEventListener.config.asCalendar().calendarId) + } } diff --git a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/TestEvents.kt b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/TestEvents.kt index 4a7464111..eddeb1835 100644 --- a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/TestEvents.kt +++ b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/TestEvents.kt @@ -244,4 +244,24 @@ object TestEvents { ] } """.trimIndent() + + val calendarFullEventsResponse = + """ + { + "Code": 1000, + "CalendarModelEventID": "ACXDmTaBub14w==", + "Refresh": 0, + "More": 0, + + "Calendars": [ + { + "ID": "afeaefaeTaBub14w==", + "Action": 1, + "Calendar": { + "ID": "a29olIjFv0rnXxBhSMw==" + } + } + ] + } + """.trimIndent() } diff --git a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/CalendarEventListener.kt b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/CalendarEventListener.kt new file mode 100644 index 000000000..8b12305e2 --- /dev/null +++ b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/CalendarEventListener.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * This file is part of Proton Technologies AG and ProtonCore. + * + * ProtonCore is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonCore is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonCore. If not, see . + */ + +package me.proton.core.eventmanager.data.listener + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig +import me.proton.core.eventmanager.domain.entity.Action +import me.proton.core.eventmanager.domain.entity.Event +import me.proton.core.eventmanager.domain.entity.EventsResponse +import me.proton.core.util.kotlin.deserializeOrNull + +@Serializable +data class CalendarsEvents( + @SerialName("Calendars") + val calendars: List +) + +@Serializable +data class CalendarEvent( + @SerialName("ID") + val id: String, + @SerialName("Action") + val action: Int, + @SerialName("Calendar") + val calendar: CalendarResource? = null +) + +@Serializable +data class CalendarResource( + @SerialName("ID") + val id: String +) + +class CalendarEventListener : EventListener() { + + override val type = Type.Calendar + override val order = 1 + + lateinit var config: EventManagerConfig + + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { + val events = response.body.deserializeOrNull() + return events?.calendars?.map { + Event(requireNotNull(Action.map[it.action]), it.id, it.calendar) + } + } + + override suspend fun inTransaction(block: suspend () -> R): R { + // Db.inTransaction(block) + return block() + } + + override suspend fun onPrepare(config: EventManagerConfig, entities: List) { + super.onPrepare(config, entities) + } + + override suspend fun onCreate(config: EventManagerConfig, entities: List) { + this.config = config + } +} diff --git a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/ContactEventListener.kt b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/ContactEventListener.kt index 80d9548c2..cb0ad16f2 100644 --- a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/ContactEventListener.kt +++ b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/ContactEventListener.kt @@ -22,6 +22,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import me.proton.core.contact.data.api.resource.ContactWithCardsResource import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -48,7 +49,10 @@ class ContactEventListener : EventListener() { override val type = Type.Core override val order = 1 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.contacts?.map { Event(requireNotNull(Action.map[it.action]), it.id, it.contact) } diff --git a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/UserEventListener.kt b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/UserEventListener.kt index 1343a3d32..f6a7ee98e 100644 --- a/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/UserEventListener.kt +++ b/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/listener/UserEventListener.kt @@ -21,6 +21,7 @@ package me.proton.core.eventmanager.data.listener import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -38,7 +39,10 @@ class UserEventListener : EventListener() { override val type = Type.Core override val order = 0 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.let { listOf(Event(Action.Update, it.user.id, it.user)) } diff --git a/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/EventListener.kt b/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/EventListener.kt index 49b6b3df8..e79d8b918 100644 --- a/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/EventListener.kt +++ b/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/EventListener.kt @@ -18,16 +18,15 @@ package me.proton.core.eventmanager.domain -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse import me.proton.core.eventmanager.domain.extension.groupByAction import me.proton.core.util.kotlin.takeIfNotEmpty -abstract class EventListener : TransactionHandler { +abstract class EventListener : TransactionHandler { - private val actionMapByUserId = mutableMapOf>>>() + private val actionMapByConfig = mutableMapOf>>>() /** * Type of Event loop. @@ -58,7 +57,7 @@ abstract class EventListener : TransactionHandler { /** * Listener [type] to associate with this [EventListener]. */ - abstract val type: Type + abstract val type: EventListener.Type /** * The degrees of separation from this entity to the User entity. @@ -76,8 +75,8 @@ abstract class EventListener : TransactionHandler { * * Note: The map is created just before [onPrepare], and cleared after [onComplete]. */ - fun getActionMap(userId: UserId): Map>> { - return actionMapByUserId.getOrPut(userId) { emptyMap() } + fun getActionMap(config: EventManagerConfig): Map>> { + return actionMapByConfig.getOrPut(config) { emptyMap() } } /** @@ -85,8 +84,8 @@ abstract class EventListener : TransactionHandler { * * Note: Called before [notifyPrepare] and after [notifyComplete]. */ - fun setActionMap(userId: UserId, events: List>) { - actionMapByUserId[userId] = events.groupByAction() + fun setActionMap(config: EventManagerConfig, events: List>) { + actionMapByConfig[config] = events.groupByAction() } /** @@ -94,10 +93,10 @@ abstract class EventListener : TransactionHandler { * * Note: No transaction wraps this function. */ - suspend fun notifyPrepare(userId: UserId) { - val actions = getActionMap(userId) + suspend fun notifyPrepare(config: EventManagerConfig) { + val actions = getActionMap(config) val entities = actions[Action.Create].orEmpty() + actions[Action.Update].orEmpty() - entities.takeIfNotEmpty()?.let { list -> onPrepare(userId, list.mapNotNull { it.entity }) } + entities.takeIfNotEmpty()?.let { list -> onPrepare(config, list.mapNotNull { it.entity }) } } /** @@ -105,12 +104,12 @@ abstract class EventListener : TransactionHandler { * * Note: A transaction wraps this function. */ - suspend fun notifyEvents(userId: UserId) { - val actions = getActionMap(userId) - actions[Action.Create]?.takeIfNotEmpty()?.let { list -> onCreate(userId, list.mapNotNull { it.entity }) } - actions[Action.Update]?.takeIfNotEmpty()?.let { list -> onUpdate(userId, list.mapNotNull { it.entity }) } - actions[Action.Partial]?.takeIfNotEmpty()?.let { list -> onPartial(userId, list.mapNotNull { it.entity }) } - actions[Action.Delete]?.takeIfNotEmpty()?.let { list -> onDelete(userId, list.map { it.key }) } + suspend fun notifyEvents(config: EventManagerConfig) { + val actions = getActionMap(config) + actions[Action.Create]?.takeIfNotEmpty()?.let { list -> onCreate(config, list.mapNotNull { it.entity }) } + actions[Action.Update]?.takeIfNotEmpty()?.let { list -> onUpdate(config, list.mapNotNull { it.entity }) } + actions[Action.Partial]?.takeIfNotEmpty()?.let { list -> onPartial(config, list.mapNotNull { it.entity }) } + actions[Action.Delete]?.takeIfNotEmpty()?.let { list -> onDelete(config, list.map { it.key }) } } /** @@ -118,8 +117,8 @@ abstract class EventListener : TransactionHandler { * * Note: No transaction wraps this function. */ - suspend fun notifyResetAll(userId: UserId) { - onResetAll(userId) + suspend fun notifyResetAll(config: EventManagerConfig) { + onResetAll(config) } /** @@ -127,15 +126,15 @@ abstract class EventListener : TransactionHandler { * * Note: No transaction wraps this function. */ - suspend fun notifyComplete(userId: UserId) { - onComplete(userId) - setActionMap(userId, emptyList()) + suspend fun notifyComplete(config: EventManagerConfig) { + onComplete(config) + setActionMap(config, emptyList()) } /** * Deserialize [response] into a typed list of [Event]. */ - abstract suspend fun deserializeEvents(response: EventsResponse): List>? + abstract suspend fun deserializeEvents(config: EventManagerConfig, response: EventsResponse): List>? /** * Called before applying a set of modifications to prepare any additional action (e.g. fetch foreign entities). @@ -144,14 +143,14 @@ abstract class EventListener : TransactionHandler { * * @see onComplete */ - open suspend fun onPrepare(userId: UserId, entities: List) = Unit + open suspend fun onPrepare(config: EventManagerConfig, entities: List) = Unit /** * Called at the end of a set of modifications, whether it is successful or not. * * @see onPrepare */ - open suspend fun onComplete(userId: UserId) = Unit + open suspend fun onComplete(config: EventManagerConfig) = Unit /** * Called to created entities in persistence. @@ -162,7 +161,7 @@ abstract class EventListener : TransactionHandler { * * @see onPrepare */ - open suspend fun onCreate(userId: UserId, entities: List) = Unit + open suspend fun onCreate(config: EventManagerConfig, entities: List) = Unit /** * Called to update or insert entities in persistence. @@ -173,7 +172,7 @@ abstract class EventListener : TransactionHandler { * * @see onPrepare */ - open suspend fun onUpdate(userId: UserId, entities: List) = Unit + open suspend fun onUpdate(config: EventManagerConfig, entities: List) = Unit /** * Called to partially update entities in persistence. @@ -184,19 +183,19 @@ abstract class EventListener : TransactionHandler { * * @see onPrepare */ - open suspend fun onPartial(userId: UserId, entities: List) = Unit + open suspend fun onPartial(config: EventManagerConfig, entities: List) = Unit /** * Called to delete, if exist, entities in persistence. * * Note: A transaction wraps this function and must return as fast as possible. */ - open suspend fun onDelete(userId: UserId, keys: List) = Unit + open suspend fun onDelete(config: EventManagerConfig, keys: List) = Unit /** * Called to reset/delete all entities in persistence, for this type. * * Note: Usually a good time the fetch minimal data after deletion. */ - open suspend fun onResetAll(userId: UserId) = Unit + open suspend fun onResetAll(config: EventManagerConfig) = Unit } diff --git a/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/extension/EventManagerConfig.kt b/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/extension/EventManagerConfig.kt new file mode 100644 index 000000000..2d685755c --- /dev/null +++ b/event-manager/domain/src/main/kotlin/me/proton/core/eventmanager/domain/extension/EventManagerConfig.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * This file is part of Proton Technologies AG and ProtonCore. + * + * ProtonCore is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonCore is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonCore. If not, see . + */ + +package me.proton.core.eventmanager.domain.extension + +import me.proton.core.eventmanager.domain.EventManagerConfig + +fun EventManagerConfig.asCalendar(): EventManagerConfig.Calendar = this as EventManagerConfig.Calendar +fun EventManagerConfig.asDrive(): EventManagerConfig.Drive = this as EventManagerConfig.Drive diff --git a/mail-settings/build.gradle.kts b/mail-settings/build.gradle.kts index 1a5c65e2e..4a49f84ef 100644 --- a/mail-settings/build.gradle.kts +++ b/mail-settings/build.gradle.kts @@ -23,7 +23,7 @@ plugins { kotlin("android") } -libVersion = Version(1, 18, 0) +libVersion = Version(1, 19, 1) android() diff --git a/mail-settings/data/src/main/kotlin/me/proton/core/mailsettings/data/MailSettingsEventListener.kt b/mail-settings/data/src/main/kotlin/me/proton/core/mailsettings/data/MailSettingsEventListener.kt index e99a801a0..f44f0149a 100644 --- a/mail-settings/data/src/main/kotlin/me/proton/core/mailsettings/data/MailSettingsEventListener.kt +++ b/mail-settings/data/src/main/kotlin/me/proton/core/mailsettings/data/MailSettingsEventListener.kt @@ -20,8 +20,8 @@ package me.proton.core.mailsettings.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -48,7 +48,10 @@ class MailSettingsEventListener @Inject constructor( override val type = Type.Core override val order = 1 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.let { listOf(Event(Action.Update, "null", it.settings)) } @@ -58,11 +61,11 @@ class MailSettingsEventListener @Inject constructor( return db.inTransaction(block) } - override suspend fun onUpdate(userId: UserId, entities: List) { - repository.updateMailSettings(entities.first().toMailSettings(userId)) + override suspend fun onUpdate(config: EventManagerConfig, entities: List) { + repository.updateMailSettings(entities.first().toMailSettings(config.userId)) } - override suspend fun onResetAll(userId: UserId) { - repository.getMailSettings(userId, refresh = true) + override suspend fun onResetAll(config: EventManagerConfig) { + repository.getMailSettings(config.userId, refresh = true) } } diff --git a/user-settings/build.gradle.kts b/user-settings/build.gradle.kts index f9306b554..6af70fa8a 100644 --- a/user-settings/build.gradle.kts +++ b/user-settings/build.gradle.kts @@ -23,7 +23,7 @@ plugins { kotlin("android") } -libVersion = Version(1, 18, 2) +libVersion = Version(1, 19, 1) android() diff --git a/user-settings/data/src/main/kotlin/me/proton/core/usersettings/data/UserSettingsEventListener.kt b/user-settings/data/src/main/kotlin/me/proton/core/usersettings/data/UserSettingsEventListener.kt index 24a4c97c9..ec8752a6f 100644 --- a/user-settings/data/src/main/kotlin/me/proton/core/usersettings/data/UserSettingsEventListener.kt +++ b/user-settings/data/src/main/kotlin/me/proton/core/usersettings/data/UserSettingsEventListener.kt @@ -20,8 +20,8 @@ package me.proton.core.usersettings.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -48,7 +48,10 @@ class UserSettingsEventListener @Inject constructor( override val type = Type.Core override val order = 1 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.let { listOf(Event(Action.Update, "null", it.settings)) } @@ -58,11 +61,11 @@ class UserSettingsEventListener @Inject constructor( return db.inTransaction(block) } - override suspend fun onUpdate(userId: UserId, entities: List) { - repository.updateUserSettings(entities.first().toUserSettings(userId)) + override suspend fun onUpdate(config: EventManagerConfig, entities: List) { + repository.updateUserSettings(entities.first().toUserSettings(config.userId)) } - override suspend fun onResetAll(userId: UserId) { - repository.getUserSettings(userId, refresh = true) + override suspend fun onResetAll(config: EventManagerConfig) { + repository.getUserSettings(config.userId, refresh = true) } } diff --git a/user/build.gradle.kts b/user/build.gradle.kts index 9a498c1ef..c04284a3d 100644 --- a/user/build.gradle.kts +++ b/user/build.gradle.kts @@ -23,7 +23,7 @@ plugins { kotlin("android") } -libVersion = Version(1, 18, 0) +libVersion = Version(1, 19, 1) android() diff --git a/user/data/src/main/kotlin/me/proton/core/user/data/UserAddressEventListener.kt.kt b/user/data/src/main/kotlin/me/proton/core/user/data/UserAddressEventListener.kt.kt index 564df2e76..012157a61 100644 --- a/user/data/src/main/kotlin/me/proton/core/user/data/UserAddressEventListener.kt.kt +++ b/user/data/src/main/kotlin/me/proton/core/user/data/UserAddressEventListener.kt.kt @@ -20,8 +20,8 @@ package me.proton.core.user.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -59,7 +59,10 @@ class UserAddressEventListener @Inject constructor( override val type = Type.Core override val order = 1 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.addresses?.map { Event(requireNotNull(Action.map[it.action]), it.address.id, it.address) } @@ -69,20 +72,20 @@ class UserAddressEventListener @Inject constructor( return db.inTransaction(block) } - override suspend fun onCreate(userId: UserId, entities: List) { - userAddressRepository.updateAddresses(entities.map { it.toAddress(userId) }) + override suspend fun onCreate(config: EventManagerConfig, entities: List) { + userAddressRepository.updateAddresses(entities.map { it.toAddress(config.userId) }) } - override suspend fun onUpdate(userId: UserId, entities: List) { - userAddressRepository.updateAddresses(entities.map { it.toAddress(userId) }) + override suspend fun onUpdate(config: EventManagerConfig, entities: List) { + userAddressRepository.updateAddresses(entities.map { it.toAddress(config.userId) }) } - override suspend fun onDelete(userId: UserId, keys: List) { + override suspend fun onDelete(config: EventManagerConfig, keys: List) { userAddressRepository.deleteAddresses(keys.map { AddressId(it) }) } - override suspend fun onResetAll(userId: UserId) { - userAddressRepository.deleteAllAddresses(userId) - userAddressRepository.getAddresses(userId, refresh = true) + override suspend fun onResetAll(config: EventManagerConfig) { + userAddressRepository.deleteAllAddresses(config.userId) + userAddressRepository.getAddresses(config.userId, refresh = true) } } diff --git a/user/data/src/main/kotlin/me/proton/core/user/data/UserEventListener.kt b/user/data/src/main/kotlin/me/proton/core/user/data/UserEventListener.kt index 8bff27aca..2dc1ecd6f 100644 --- a/user/data/src/main/kotlin/me/proton/core/user/data/UserEventListener.kt +++ b/user/data/src/main/kotlin/me/proton/core/user/data/UserEventListener.kt @@ -20,8 +20,8 @@ package me.proton.core.user.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import me.proton.core.domain.entity.UserId import me.proton.core.eventmanager.domain.EventListener +import me.proton.core.eventmanager.domain.EventManagerConfig import me.proton.core.eventmanager.domain.entity.Action import me.proton.core.eventmanager.domain.entity.Event import me.proton.core.eventmanager.domain.entity.EventsResponse @@ -48,7 +48,10 @@ class UserEventListener @Inject constructor( override val type = Type.Core override val order = 0 - override suspend fun deserializeEvents(response: EventsResponse): List>? { + override suspend fun deserializeEvents( + config: EventManagerConfig, + response: EventsResponse + ): List>? { return response.body.deserializeOrNull()?.let { listOf(Event(Action.Update, it.user.id, it.user)) } @@ -58,11 +61,11 @@ class UserEventListener @Inject constructor( return db.inTransaction(block) } - override suspend fun onUpdate(userId: UserId, entities: List) { + override suspend fun onUpdate(config: EventManagerConfig, entities: List) { userRepository.updateUser(entities.first().toUser()) } - override suspend fun onResetAll(userId: UserId) { - userRepository.getUser(userId, refresh = true) + override suspend fun onResetAll(config: EventManagerConfig) { + userRepository.getUser(config.userId, refresh = true) } }