Added EventManager Domain/Data and Event Listeners.
Added EventManager LogTags. Added EventManagerConfig and EventManagerConfigProvider. Added EventWorkManager and EventManagerWorker. Added UserEventListener. Added UserAddressEventListener. Added MailSettingsEventListener. Added UserSettingsEventListener. Added ContactEventListener. Added ContactEmailsEventListener. Added AppLifecycleObserver and AppLifecycleProvider.
This commit is contained in:
parent
14d4416683
commit
351fa5eb7b
144
CHANGELOG.md
144
CHANGELOG.md
|
@ -1,3 +1,147 @@
|
|||
## Version [1.18]
|
||||
|
||||
25 Oct, 2021
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Contact 1.18.
|
||||
- Domain 1.18.
|
||||
- EventManager 1.18.
|
||||
- MailSettings 1.18.
|
||||
- Presentation 1.18.
|
||||
- User 1.18.
|
||||
- UserSettings 1.18.
|
||||
|
||||
### New Migration
|
||||
|
||||
- Please apply changes as follow to your AppDatabase:
|
||||
- Add ```EventMetadataEntity``` to your AppDatabase ```entities```.
|
||||
- Add ```EventManagerConverters``` to your ```TypeConverters```.
|
||||
- Extends ```EventMetadataDatabase```.
|
||||
- Add a migration to your AppDatabase (```addMigration```):
|
||||
```
|
||||
val MIGRATION_X_Y = object : Migration(X, Y) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
EventMetadataDatabase.MIGRATION_0.migrate(database)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### New Dagger Module
|
||||
|
||||
- To provide the various EventManager components:
|
||||
```
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object EventManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@EventManagerCoroutineScope
|
||||
fun provideEventManagerCoroutineScope(): CoroutineScope =
|
||||
CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@JvmSuppressWildcards
|
||||
fun provideEventManagerProvider(
|
||||
eventManagerFactory: EventManagerFactory,
|
||||
eventListeners: Set<EventListener<*, *>>
|
||||
): EventManagerProvider =
|
||||
EventManagerProviderImpl(eventManagerFactory, eventListeners)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEventMetadataRepository(
|
||||
db: EventMetadataDatabase,
|
||||
provider: ApiProvider
|
||||
): EventMetadataRepository = EventMetadataRepositoryImpl(db, provider)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEventWorkManager(
|
||||
workManager: WorkManager,
|
||||
appLifecycleProvider: AppLifecycleProvider
|
||||
): EventWorkManager = EventWorkManagerImpl(workManager, appLifecycleProvider)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ElementsIntoSet
|
||||
@JvmSuppressWildcards
|
||||
fun provideEventListenerSet(
|
||||
userEventListener: UserEventListener,
|
||||
userAddressEventListener: UserAddressEventListener,
|
||||
userSettingsEventListener: UserSettingsEventListener,
|
||||
mailSettingsEventListener: MailSettingsEventListener,
|
||||
contactEventListener: ContactEventListener,
|
||||
contactEmailEventListener: ContactEmailEventListener,
|
||||
): Set<EventListener<*, *>> = setOf(
|
||||
userEventListener,
|
||||
userAddressEventListener,
|
||||
userSettingsEventListener,
|
||||
mailSettingsEventListener,
|
||||
contactEventListener,
|
||||
contactEmailEventListener,
|
||||
)
|
||||
}
|
||||
```
|
||||
- To provide AppLifecycleObserver, AppLifecycleProvider and WorkManager (needed by EventManager):
|
||||
```
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ApplicationModule {
|
||||
...
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppLifecycleObserver(): AppLifecycleObserver =
|
||||
AppLifecycleObserver()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWorkManager(@ApplicationContext context: Context): WorkManager =
|
||||
WorkManager.getInstance(context)
|
||||
...
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class ApplicationBindsModule {
|
||||
@Binds
|
||||
abstract fun provideAppLifecycleStateProvider(observer: AppLifecycleObserver): AppLifecycleProvider
|
||||
}
|
||||
```
|
||||
|
||||
### WorkManager
|
||||
|
||||
**Initialization of WorkManager is up to the client.**
|
||||
|
||||
EventManager/EventWorker assume the client support injecting:
|
||||
```
|
||||
@HiltWorker
|
||||
open class EventWorker @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
...
|
||||
```
|
||||
|
||||
Please refer to:
|
||||
- https://developer.android.com/training/dependency-injection/hilt-jetpack#workmanager
|
||||
- https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration.
|
||||
|
||||
### Changes
|
||||
|
||||
- Added EventManager Domain/Data and EventListeners.
|
||||
- Added EventManager LogTags.
|
||||
- Added EventManagerConfig and EventManagerConfigProvider.
|
||||
- Added EventWorkManager and EventManagerWorker.
|
||||
- Added UserEventListener.
|
||||
- Added UserAddressEventListener.
|
||||
- Added MailSettingsEventListener.
|
||||
- Added UserSettingsEventListener.
|
||||
- Added ContactEventListener.
|
||||
- Added ContactEmailsEventListener.
|
||||
- Added AppLifecycleObserver and AppLifecycleProvider.
|
||||
|
||||
## Presentation [1.17.0]
|
||||
|
||||
### Changes
|
||||
|
|
|
@ -154,6 +154,14 @@ Account Manager Data Db: **1.16** - _released on: Oct 19, 2021_
|
|||
|
||||
Account Manager Dagger: **1.16** - _released on: Oct 19, 2021_
|
||||
|
||||
### Event Manager
|
||||
|
||||
Event Manager: **0** - _released on: Jun 17, 2021_
|
||||
|
||||
Event Manager Domain: **0** - _released on: Jun 17, 2021_
|
||||
|
||||
Event Manager Data: **0** - _released on: Jun 17, 2021_
|
||||
|
||||
### Key
|
||||
|
||||
Key: **1.15.6** - _released on: Oct 15, 2021_
|
||||
|
|
|
@ -23,7 +23,7 @@ plugins {
|
|||
kotlin("android")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 16, channel = Version.Channel.Build, build = 6)
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
android()
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ dependencies {
|
|||
project(Module.domain),
|
||||
project(Module.contactDomain),
|
||||
project(Module.userData),
|
||||
project(Module.eventManagerDomain),
|
||||
|
||||
// Kotlin
|
||||
`serialization-json`,
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.contact.data
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.contact.data.api.resource.ContactEmailResource
|
||||
import me.proton.core.contact.data.local.db.ContactDatabase
|
||||
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.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
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Serializable
|
||||
data class ContactEmailsEvents(
|
||||
@SerialName("ContactEmails")
|
||||
val contactEmails: List<ContactEmailEvent>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContactEmailEvent(
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
@SerialName("Action")
|
||||
val action: Int,
|
||||
@SerialName("ContactEmail")
|
||||
val contactEmail: ContactEmailResource? = null
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class ContactEmailEventListener @Inject constructor(
|
||||
private val db: ContactDatabase,
|
||||
private val contactLocalDataSource: ContactLocalDataSource,
|
||||
private val contactRepository: ContactRepository,
|
||||
private val contactEventListener: ContactEventListener
|
||||
) : EventListener<String, ContactEmailResource>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 2
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, ContactEmailResource>>? {
|
||||
return response.body.deserializeOrNull<ContactEmailsEvents>()?.contactEmails?.map {
|
||||
Event(requireNotNull(Action.map[it.action]), it.id, it.contactEmail)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
return db.inTransaction(block)
|
||||
}
|
||||
|
||||
override suspend fun onPrepare(userId: UserId, entities: List<ContactEmailResource>) {
|
||||
// Don't fetch Contacts that will be created in this set of modifications.
|
||||
val contactActions = contactEventListener.getActionMap(userId)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onCreate(userId: UserId, entities: List<ContactEmailResource>) {
|
||||
entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(userId)) }
|
||||
}
|
||||
|
||||
override suspend fun onUpdate(userId: UserId, entities: List<ContactEmailResource>) {
|
||||
entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(userId)) }
|
||||
}
|
||||
|
||||
override suspend fun onDelete(userId: UserId, keys: List<String>) {
|
||||
contactLocalDataSource.deleteContactEmails(*keys.map { ContactEmailId(it) }.toTypedArray())
|
||||
}
|
||||
|
||||
override suspend fun onResetAll(userId: UserId) {
|
||||
// Already handled in ContactEventListener:
|
||||
// contactLocalDataSource.deleteAllContactEmails(userId)
|
||||
// contactRepository.getAllContactEmails(userId, refresh = true)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.contact.data
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.contact.data.api.resource.ContactWithCardsResource
|
||||
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.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
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Serializable
|
||||
data class ContactsEvents(
|
||||
@SerialName("Contacts")
|
||||
val contacts: List<ContactEvent>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContactEvent(
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
@SerialName("Action")
|
||||
val action: Int,
|
||||
@SerialName("Contact")
|
||||
val contact: ContactWithCardsResource? = null
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class ContactEventListener @Inject constructor(
|
||||
private val db: ContactDatabase,
|
||||
private val contactLocalDataSource: ContactLocalDataSource,
|
||||
private val contactRepository: ContactRepository
|
||||
) : EventListener<String, ContactWithCardsResource>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 1
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, ContactWithCardsResource>>? {
|
||||
return response.body.deserializeOrNull<ContactsEvents>()?.contacts?.map {
|
||||
Event(requireNotNull(Action.map[it.action]), it.id, it.contact)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
return db.inTransaction(block)
|
||||
}
|
||||
|
||||
override suspend fun onCreate(userId: UserId, entities: List<ContactWithCardsResource>) {
|
||||
entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(userId)) }
|
||||
}
|
||||
|
||||
override suspend fun onUpdate(userId: UserId, entities: List<ContactWithCardsResource>) {
|
||||
entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(userId)) }
|
||||
}
|
||||
|
||||
override suspend fun onDelete(userId: UserId, keys: List<String>) {
|
||||
contactLocalDataSource.deleteContacts(*keys.map { ContactId(it) }.toTypedArray())
|
||||
}
|
||||
|
||||
override suspend fun onResetAll(userId: UserId) {
|
||||
contactLocalDataSource.deleteAllContacts(userId)
|
||||
contactRepository.getAllContacts(userId, refresh = true)
|
||||
}
|
||||
}
|
|
@ -95,6 +95,10 @@ class ContactLocalDataSourceImpl @Inject constructor(
|
|||
contactDatabase.contactDao().deleteAllContacts(userId)
|
||||
}
|
||||
|
||||
override suspend fun deleteAllContactEmails(userId: UserId) {
|
||||
contactDatabase.contactEmailDao().deleteAllContactsEmails(userId)
|
||||
}
|
||||
|
||||
override suspend fun deleteAllContacts() {
|
||||
contactDatabase.contactDao().deleteAllContacts()
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import me.proton.core.data.room.db.BaseDao
|
|||
import me.proton.core.domain.entity.UserId
|
||||
|
||||
@Dao
|
||||
abstract class ContactDao: BaseDao<ContactEntity>() {
|
||||
abstract class ContactDao : BaseDao<ContactEntity>() {
|
||||
@Transaction
|
||||
@Query("SELECT * FROM ContactEntity WHERE contactId = :contactId")
|
||||
abstract fun observeContact(contactId: ContactId): Flow<ContactWithMailsAndCardsRelation?>
|
||||
|
|
|
@ -45,7 +45,7 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class ContactRepositoryImpl @Inject constructor(
|
||||
private val remoteDataSource: ContactRemoteDataSource,
|
||||
private val localDataSource: ContactLocalDataSource
|
||||
private val localDataSource: ContactLocalDataSource,
|
||||
) : ContactRepository {
|
||||
|
||||
private data class ContactStoreKey(val userId: UserId, val contactId: ContactId)
|
||||
|
|
|
@ -33,11 +33,13 @@ dependencies {
|
|||
project(Module.cryptoCommon),
|
||||
project(Module.userDomain),
|
||||
project(Module.keyDomain),
|
||||
project(Module.eventManagerDomain),
|
||||
|
||||
// Kotlin
|
||||
`kotlin-jdk8`,
|
||||
`coroutines-core`,
|
||||
`ez-vcard`
|
||||
`ez-vcard`,
|
||||
`javax-inject`
|
||||
)
|
||||
|
||||
testImplementation(project(Module.kotlinTest))
|
||||
|
|
|
@ -74,6 +74,11 @@ interface ContactLocalDataSource {
|
|||
*/
|
||||
suspend fun deleteAllContacts(userId: UserId)
|
||||
|
||||
/**
|
||||
* Delete all contact emails for [userId].
|
||||
*/
|
||||
suspend fun deleteAllContactEmails(userId: UserId)
|
||||
|
||||
/**
|
||||
* Delete all contacts, for every user.
|
||||
*/
|
||||
|
@ -89,4 +94,4 @@ interface ContactLocalDataSource {
|
|||
* @throws SQLiteConstraintException if corresponding user(s) doesn't exist.
|
||||
*/
|
||||
suspend fun mergeContacts(vararg contacts: Contact)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.contact.domain.usecase
|
||||
|
||||
import me.proton.core.contact.domain.entity.ContactCard
|
||||
import me.proton.core.contact.domain.entity.ContactId
|
||||
import me.proton.core.contact.domain.repository.ContactRepository
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.eventmanager.domain.extension.suspend
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpdateContactRemote @Inject constructor(
|
||||
private val contactRepository: ContactRepository,
|
||||
private val eventManagerProvider: EventManagerProvider,
|
||||
) {
|
||||
// Called by UpdateContactWorker (unique name: userId+contactId, ExistingWorkPolicy.APPEND_OR_REPLACE).
|
||||
// Prerequisite: Local optimistic update Contact.
|
||||
suspend operator fun invoke(userId: UserId, contactId: ContactId, contactCards: List<ContactCard>) {
|
||||
eventManagerProvider.suspend(EventManagerConfig.Core(userId)) {
|
||||
contactRepository.updateContact(userId, contactId, contactCards)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -80,6 +80,7 @@ dependencies {
|
|||
project(Module.contactHilt),
|
||||
project(Module.crypto),
|
||||
project(Module.domain),
|
||||
project(Module.eventManager),
|
||||
project(Module.gopenpgp),
|
||||
project(Module.humanVerification),
|
||||
project(Module.key),
|
||||
|
@ -97,14 +98,15 @@ dependencies {
|
|||
// Android
|
||||
`activity`,
|
||||
`appcompat`,
|
||||
`android-work-runtime`,
|
||||
`constraint-layout`,
|
||||
`fragment`,
|
||||
`gotev-cookieStore`,
|
||||
`hilt-android`,
|
||||
`hilt-androidx-workManager`,
|
||||
`lifecycle-extensions`,
|
||||
`lifecycle-viewModel`,
|
||||
`hilt-androidx-annotations`,
|
||||
`material`,
|
||||
`android-work-runtime`,
|
||||
|
||||
// Other
|
||||
`room-ktx`,
|
||||
|
@ -116,7 +118,8 @@ dependencies {
|
|||
kapt(
|
||||
`hilt-android-compiler`,
|
||||
`hilt-androidx-compiler`,
|
||||
`room-compiler`
|
||||
`room-compiler`,
|
||||
`lifecycle-compiler`
|
||||
)
|
||||
|
||||
// Test
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -31,6 +31,16 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/ProtonTheme"
|
||||
tools:replace="android:theme">
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:theme="@style/ProtonTheme.Splash">
|
||||
|
|
|
@ -21,13 +21,23 @@ package me.proton.android.core.coreexample
|
|||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate.setCompatVectorFromResourcesEnabled
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import me.proton.core.eventmanager.data.CoreEventManagerStarter
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import timber.log.Timber
|
||||
import timber.log.Timber.DebugTree
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class CoreExampleApp : Application() {
|
||||
class CoreExampleApp : Application(), Configuration.Provider {
|
||||
|
||||
@Inject
|
||||
lateinit var workerFactory: HiltWorkerFactory
|
||||
|
||||
@Inject
|
||||
lateinit var coreEventManagerStarter: CoreEventManagerStarter
|
||||
|
||||
private class CrashReportingTree : Timber.Tree() {
|
||||
override fun log(priority: Int, tag: String?, message: String, e: Throwable?) {
|
||||
|
@ -54,5 +64,8 @@ class CoreExampleApp : Application() {
|
|||
}
|
||||
|
||||
setCompatVectorFromResourcesEnabled(true)
|
||||
coreEventManagerStarter.start()
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration() = Configuration.Builder().setWorkerFactory(workerFactory).build()
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ import me.proton.core.contact.data.local.db.entity.ContactEntity
|
|||
import me.proton.core.crypto.android.keystore.CryptoConverters
|
||||
import me.proton.core.data.room.db.BaseDatabase
|
||||
import me.proton.core.data.room.db.CommonConverters
|
||||
import me.proton.core.eventmanager.data.db.EventManagerConverters
|
||||
import me.proton.core.eventmanager.data.db.EventMetadataDatabase
|
||||
import me.proton.core.eventmanager.data.entity.EventMetadataEntity
|
||||
import me.proton.core.humanverification.data.db.HumanVerificationConverters
|
||||
import me.proton.core.humanverification.data.db.HumanVerificationDatabase
|
||||
import me.proton.core.humanverification.data.entity.HumanVerificationEntity
|
||||
|
@ -90,6 +93,8 @@ import me.proton.core.usersettings.data.entity.UserSettingsEntity
|
|||
ContactCardEntity::class,
|
||||
ContactEmailEntity::class,
|
||||
ContactEmailLabelEntity::class,
|
||||
// event-manager
|
||||
EventMetadataEntity::class,
|
||||
],
|
||||
version = AppDatabase.version,
|
||||
exportSchema = true
|
||||
|
@ -101,7 +106,8 @@ import me.proton.core.usersettings.data.entity.UserSettingsEntity
|
|||
CryptoConverters::class,
|
||||
HumanVerificationConverters::class,
|
||||
UserSettingsConverters::class,
|
||||
ContactConverters::class
|
||||
ContactConverters::class,
|
||||
EventManagerConverters::class,
|
||||
)
|
||||
abstract class AppDatabase :
|
||||
BaseDatabase(),
|
||||
|
@ -114,11 +120,12 @@ abstract class AppDatabase :
|
|||
MailSettingsDatabase,
|
||||
UserSettingsDatabase,
|
||||
OrganizationDatabase,
|
||||
ContactDatabase {
|
||||
ContactDatabase,
|
||||
EventMetadataDatabase {
|
||||
|
||||
companion object {
|
||||
const val name = "db-account-manager"
|
||||
const val version = 10
|
||||
const val version = 11
|
||||
|
||||
val migrations = listOf(
|
||||
AppDatabaseMigrations.MIGRATION_1_2,
|
||||
|
@ -130,6 +137,7 @@ abstract class AppDatabase :
|
|||
AppDatabaseMigrations.MIGRATION_7_8,
|
||||
AppDatabaseMigrations.MIGRATION_8_9,
|
||||
AppDatabaseMigrations.MIGRATION_9_10,
|
||||
AppDatabaseMigrations.MIGRATION_10_11,
|
||||
)
|
||||
|
||||
fun buildDatabase(context: Context): AppDatabase =
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.room.migration.Migration
|
|||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import me.proton.core.account.data.db.AccountDatabase
|
||||
import me.proton.core.contact.data.local.db.ContactDatabase
|
||||
import me.proton.core.eventmanager.data.db.EventMetadataDatabase
|
||||
import me.proton.core.humanverification.data.db.HumanVerificationDatabase
|
||||
import me.proton.core.key.data.db.KeySaltDatabase
|
||||
import me.proton.core.key.data.db.PublicAddressDatabase
|
||||
|
@ -93,4 +94,10 @@ object AppDatabaseMigrations {
|
|||
PublicAddressDatabase.MIGRATION_1.migrate(database)
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_10_11 = object : Migration(10, 11) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
EventMetadataDatabase.MIGRATION_0.migrate(database)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import dagger.hilt.components.SingletonComponent
|
|||
import me.proton.android.core.coreexample.db.AppDatabase
|
||||
import me.proton.core.account.data.db.AccountDatabase
|
||||
import me.proton.core.contact.data.local.db.ContactDatabase
|
||||
import me.proton.core.eventmanager.data.db.EventMetadataDatabase
|
||||
import me.proton.core.humanverification.data.db.HumanVerificationDatabase
|
||||
import me.proton.core.key.data.db.KeySaltDatabase
|
||||
import me.proton.core.key.data.db.PublicAddressDatabase
|
||||
|
@ -79,4 +80,7 @@ abstract class AppDatabaseBindsModule {
|
|||
|
||||
@Binds
|
||||
abstract fun provideContactDatabase(appDatabase: AppDatabase): ContactDatabase
|
||||
|
||||
@Binds
|
||||
abstract fun provideEventMetadataDatabase(appDatabase: AppDatabase): EventMetadataDatabase
|
||||
}
|
||||
|
|
|
@ -18,15 +18,21 @@
|
|||
|
||||
package me.proton.android.core.coreexample.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.WorkManager
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.android.core.coreexample.api.CoreExampleRepository
|
||||
import me.proton.core.account.domain.entity.AccountType
|
||||
import me.proton.core.auth.domain.ClientSecret
|
||||
import me.proton.core.domain.entity.Product
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import me.proton.core.presentation.app.AppLifecycleObserver
|
||||
import me.proton.core.presentation.app.AppLifecycleProvider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
@ -51,4 +57,21 @@ object ApplicationModule {
|
|||
@Singleton
|
||||
fun provideCoreExampleRepository(apiProvider: ApiProvider): CoreExampleRepository =
|
||||
CoreExampleRepository(apiProvider)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppLifecycleObserver(): AppLifecycleObserver =
|
||||
AppLifecycleObserver()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWorkManager(@ApplicationContext context: Context): WorkManager =
|
||||
WorkManager.getInstance(context)
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class ApplicationBindsModule {
|
||||
@Binds
|
||||
abstract fun provideAppLifecycleStateProvider(observer: AppLifecycleObserver): AppLifecycleProvider
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.core.coreexample.di
|
||||
|
||||
import androidx.work.WorkManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ElementsIntoSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import me.proton.core.contact.data.ContactEmailEventListener
|
||||
import me.proton.core.contact.data.ContactEventListener
|
||||
import me.proton.core.eventmanager.data.EventManagerCoroutineScope
|
||||
import me.proton.core.eventmanager.data.EventManagerFactory
|
||||
import me.proton.core.eventmanager.data.EventManagerProviderImpl
|
||||
import me.proton.core.eventmanager.data.db.EventMetadataDatabase
|
||||
import me.proton.core.eventmanager.data.repository.EventMetadataRepositoryImpl
|
||||
import me.proton.core.eventmanager.data.work.EventWorkerManagerImpl
|
||||
import me.proton.core.eventmanager.domain.EventListener
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.eventmanager.domain.repository.EventMetadataRepository
|
||||
import me.proton.core.eventmanager.domain.work.EventWorkerManager
|
||||
import me.proton.core.mailsettings.data.MailSettingsEventListener
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import me.proton.core.presentation.app.AppLifecycleProvider
|
||||
import me.proton.core.user.data.UserAddressEventListener
|
||||
import me.proton.core.user.data.UserEventListener
|
||||
import me.proton.core.usersettings.data.UserSettingsEventListener
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object EventManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@EventManagerCoroutineScope
|
||||
fun provideEventManagerCoroutineScope(): CoroutineScope =
|
||||
CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@JvmSuppressWildcards
|
||||
fun provideEventManagerProvider(
|
||||
eventManagerFactory: EventManagerFactory,
|
||||
eventListeners: Set<EventListener<*, *>>
|
||||
): EventManagerProvider =
|
||||
EventManagerProviderImpl(eventManagerFactory, eventListeners)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEventMetadataRepository(
|
||||
db: EventMetadataDatabase,
|
||||
provider: ApiProvider
|
||||
): EventMetadataRepository = EventMetadataRepositoryImpl(db, provider)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEventWorkManager(
|
||||
workManager: WorkManager,
|
||||
appLifecycleProvider: AppLifecycleProvider
|
||||
): EventWorkerManager = EventWorkerManagerImpl(workManager, appLifecycleProvider)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ElementsIntoSet
|
||||
@JvmSuppressWildcards
|
||||
fun provideEventListenerSet(
|
||||
userEventListener: UserEventListener,
|
||||
userAddressEventListener: UserAddressEventListener,
|
||||
userSettingsEventListener: UserSettingsEventListener,
|
||||
mailSettingsEventListener: MailSettingsEventListener,
|
||||
contactEventListener: ContactEventListener,
|
||||
contactEmailEventListener: ContactEmailEventListener,
|
||||
): Set<EventListener<*, *>> = setOf(
|
||||
userEventListener,
|
||||
userAddressEventListener,
|
||||
userSettingsEventListener,
|
||||
mailSettingsEventListener,
|
||||
contactEventListener,
|
||||
contactEmailEventListener,
|
||||
)
|
||||
}
|
|
@ -25,7 +25,6 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.android.core.coreexample.Constants
|
||||
import me.proton.core.crypto.common.context.CryptoContext
|
||||
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
|
||||
import me.proton.core.key.data.db.KeySaltDatabase
|
||||
import me.proton.core.key.data.db.PublicAddressDatabase
|
||||
import me.proton.core.key.data.repository.KeySaltRepositoryImpl
|
||||
|
|
|
@ -59,7 +59,7 @@ class AccountViewModel @Inject constructor(
|
|||
private val userManager: UserManager,
|
||||
private val humanVerificationManager: HumanVerificationManager,
|
||||
private var authOrchestrator: AuthOrchestrator,
|
||||
private var humanVerificationOrchestrator: HumanVerificationOrchestrator
|
||||
private var humanVerificationOrchestrator: HumanVerificationOrchestrator,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(State.Processing as State)
|
||||
|
|
|
@ -51,6 +51,7 @@ import me.proton.core.user.domain.UserManager
|
|||
import me.proton.core.user.domain.entity.User
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.core.util.kotlin.exhaustive
|
||||
import me.proton.core.util.kotlin.truncateToLength
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
@ -109,7 +110,7 @@ class ContactDetailViewModel @Inject constructor(
|
|||
contact.contactCards.map { decryptContactCard(it) }
|
||||
}
|
||||
return ViewState.Success(
|
||||
rawContact = contact.prettyPrint(),
|
||||
rawContact = contact.prettyPrint().truncateToLength(10000).toString(),
|
||||
vCardContact = decryptedCards.prettyPrint()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package me.proton.android.core.coreexample.viewmodel
|
|||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
@ -62,6 +63,7 @@ class PublicAddressViewModel @Inject constructor(
|
|||
|
||||
fun getPublicAddressState() = accountManager.getPrimaryAccount()
|
||||
.flatMapLatest { primary -> primary?.let { userManager.getUserFlow(it.userId) } ?: flowOf(null) }
|
||||
.distinctUntilChangedBy { (it as? DataResult.Success)?.value?.keys }
|
||||
.transformLatest { result ->
|
||||
if (result == null || result !is DataResult.Success || result.value == null) {
|
||||
emit(PublicAddressState.Error.NoPrimaryAccount)
|
||||
|
|
|
@ -21,9 +21,10 @@ import studio.forface.easygradle.dsl.android.*
|
|||
plugins {
|
||||
`java-library`
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 16, 0)
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
dependencies {
|
||||
|
||||
|
@ -33,7 +34,8 @@ dependencies {
|
|||
// Kotlin
|
||||
`kotlin-jdk7`,
|
||||
`coroutines-core`,
|
||||
`javax-inject`
|
||||
`javax-inject`,
|
||||
`serialization-json`,
|
||||
)
|
||||
|
||||
testImplementation(project(Module.kotlinTest))
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
|
||||
package me.proton.core.domain.entity
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UserId(val id: String)
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import studio.forface.easygradle.dsl.*
|
||||
import studio.forface.easygradle.dsl.android.*
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
android()
|
||||
|
||||
dependencies {
|
||||
api(
|
||||
project(Module.eventManagerDomain),
|
||||
project(Module.eventManagerData)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import studio.forface.easygradle.dsl.*
|
||||
import studio.forface.easygradle.dsl.android.*
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("plugin.serialization")
|
||||
kotlin("kapt")
|
||||
id("dagger.hilt.android.plugin")
|
||||
}
|
||||
|
||||
libVersion = parent?.libVersion
|
||||
|
||||
android()
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.data),
|
||||
project(Module.dataRoom),
|
||||
project(Module.domain),
|
||||
project(Module.network),
|
||||
project(Module.eventManagerDomain),
|
||||
project(Module.presentation),
|
||||
|
||||
project(Module.account),
|
||||
project(Module.accountManager),
|
||||
project(Module.user), // UserEntity
|
||||
|
||||
`android-work-runtime`,
|
||||
`hilt-android`,
|
||||
`hilt-androidx-workManager`,
|
||||
`kotlin-jdk7`,
|
||||
`serialization-json`,
|
||||
`coroutines-core`,
|
||||
`retrofit`,
|
||||
`retrofit-kotlin-serialization`,
|
||||
`room-ktx`
|
||||
)
|
||||
|
||||
kapt(
|
||||
`hilt-android-compiler`,
|
||||
`hilt-androidx-compiler`
|
||||
)
|
||||
|
||||
testImplementation(
|
||||
project(Module.androidTest),
|
||||
project(Module.crypto),
|
||||
project(Module.account),
|
||||
project(Module.accountManager),
|
||||
project(Module.contact),
|
||||
project(Module.key),
|
||||
)
|
||||
androidTestImplementation(project(Module.androidInstrumentedTest))
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<manifest package="me.proton.core.eventmanager.data" />
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.accountmanager.presentation.observe
|
||||
import me.proton.core.accountmanager.presentation.onAccountReady
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.presentation.app.AppLifecycleProvider
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class CoreEventManagerStarter @Inject constructor(
|
||||
private val appLifecycleProvider: AppLifecycleProvider,
|
||||
private val accountManager: AccountManager,
|
||||
private val eventManagerProvider: EventManagerProvider
|
||||
) {
|
||||
|
||||
private fun startReadyAccount() {
|
||||
accountManager.observe(appLifecycleProvider.lifecycle, minActiveState = Lifecycle.State.CREATED)
|
||||
.onAccountReady { eventManagerProvider.get(EventManagerConfig.Core(it.userId)).start() }
|
||||
}
|
||||
|
||||
fun start() {
|
||||
startReadyAccount()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
import me.proton.core.eventmanager.data.api.response.GetCoreLatestEventIdResponse
|
||||
import me.proton.core.eventmanager.data.api.response.GetDriveEventsResponse
|
||||
import me.proton.core.eventmanager.data.api.response.GetDriveLatestEventIdResponse
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
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.RefreshType
|
||||
import me.proton.core.util.kotlin.deserialize
|
||||
|
||||
interface EventDeserializer {
|
||||
val config: EventManagerConfig
|
||||
val endpoint: String
|
||||
fun deserializeLatestEventId(response: EventIdResponse): EventId
|
||||
fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata
|
||||
}
|
||||
|
||||
internal data class CoreEventDeserializer(
|
||||
override val config: EventManagerConfig.Core
|
||||
) : EventDeserializer {
|
||||
|
||||
override val endpoint = "events"
|
||||
|
||||
override fun deserializeLatestEventId(response: EventIdResponse): EventId =
|
||||
EventId(response.body.deserialize<GetCoreLatestEventIdResponse>().eventId)
|
||||
|
||||
override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata =
|
||||
response.body.deserialize<GetCoreEventsResponse>().let {
|
||||
EventMetadata(
|
||||
userId = userId,
|
||||
eventId = eventId,
|
||||
config = config,
|
||||
nextEventId = EventId(it.eventId),
|
||||
refresh = RefreshType.map[it.refresh],
|
||||
more = it.more > 0,
|
||||
response = response,
|
||||
createdAt = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class CalendarEventDeserializer(
|
||||
override val config: EventManagerConfig.Calendar
|
||||
) : EventDeserializer {
|
||||
|
||||
override val endpoint = "calendar/${config.apiVersion}/${config.calendarId}/modelevents"
|
||||
|
||||
override fun deserializeLatestEventId(response: EventIdResponse): EventId =
|
||||
EventId(response.body.deserialize<GetCalendarLatestEventIdResponse>().eventId)
|
||||
|
||||
override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata =
|
||||
response.body.deserialize<GetCalendarEventsResponse>().let {
|
||||
EventMetadata(
|
||||
userId = userId,
|
||||
eventId = eventId,
|
||||
config = config,
|
||||
nextEventId = EventId(it.eventId),
|
||||
refresh = RefreshType.map[it.refresh],
|
||||
more = it.more > 0,
|
||||
response = response,
|
||||
createdAt = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class DriveEventDeserializer(
|
||||
override val config: EventManagerConfig.Drive
|
||||
) : EventDeserializer {
|
||||
|
||||
override val endpoint = "drive/shares/${config.shareId}/events"
|
||||
|
||||
override fun deserializeLatestEventId(response: EventIdResponse): EventId =
|
||||
EventId(response.body.deserialize<GetDriveLatestEventIdResponse>().eventId)
|
||||
|
||||
override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata =
|
||||
response.body.deserialize<GetDriveEventsResponse>().let {
|
||||
EventMetadata(
|
||||
userId = userId,
|
||||
eventId = eventId,
|
||||
config = config,
|
||||
nextEventId = EventId(it.eventId),
|
||||
refresh = RefreshType.Nothing,
|
||||
more = false,
|
||||
response = response,
|
||||
createdAt = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.SerializationException
|
||||
import me.proton.core.account.domain.entity.Account
|
||||
import me.proton.core.account.domain.entity.AccountState
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.eventmanager.data.extension.runCatching
|
||||
import me.proton.core.eventmanager.data.extension.runInTransaction
|
||||
import me.proton.core.eventmanager.domain.EventListener
|
||||
import me.proton.core.eventmanager.domain.EventManager
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
import me.proton.core.eventmanager.domain.entity.EventMetadata
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
import me.proton.core.eventmanager.domain.entity.RefreshType
|
||||
import me.proton.core.eventmanager.domain.entity.State
|
||||
import me.proton.core.eventmanager.domain.repository.EventMetadataRepository
|
||||
import me.proton.core.eventmanager.domain.work.EventWorkerManager
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.isRetryable
|
||||
import me.proton.core.presentation.app.AppLifecycleProvider
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.core.util.kotlin.exhaustive
|
||||
|
||||
@AssistedFactory
|
||||
interface EventManagerFactory {
|
||||
fun create(deserializer: EventDeserializer): EventManagerImpl
|
||||
}
|
||||
|
||||
class EventManagerImpl @AssistedInject constructor(
|
||||
@EventManagerCoroutineScope private val coroutineScope: CoroutineScope,
|
||||
private val appLifecycleProvider: AppLifecycleProvider,
|
||||
private val accountManager: AccountManager,
|
||||
private val eventWorkerManager: EventWorkerManager,
|
||||
internal val eventMetadataRepository: EventMetadataRepository,
|
||||
@Assisted val deserializer: EventDeserializer
|
||||
) : EventManager {
|
||||
|
||||
private val lock = Mutex()
|
||||
|
||||
private var observeAccountJob: Job? = null
|
||||
private var observeAppStateJob: Job? = null
|
||||
|
||||
internal val eventListenersByOrder = sortedMapOf<Int, MutableSet<EventListener<*, *>>>()
|
||||
|
||||
private suspend fun deserializeEventsByListener(
|
||||
response: EventsResponse
|
||||
): Map<EventListener<*, *>, List<Event<*, *>>> {
|
||||
return eventListenersByOrder.values.flatten().associateWith { eventListener ->
|
||||
eventListener.deserializeEvents(response).orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processFirstFromConfig() {
|
||||
val metadata = eventMetadataRepository.get(config).firstOrNull() ?: return
|
||||
when {
|
||||
metadata.retry > retriesBeforeReset -> {
|
||||
reportFailure(metadata)
|
||||
reset()
|
||||
}
|
||||
metadata.retry > retriesBeforeNotifyResetAll -> {
|
||||
reportFailure(metadata)
|
||||
notifyResetAll(metadata)
|
||||
}
|
||||
else -> when (metadata.state) {
|
||||
State.Enqueued -> fetch(metadata)
|
||||
State.Fetching -> fetch(metadata)
|
||||
State.Persisted -> notify(metadata)
|
||||
State.NotifyPrepare -> notifyPrepare(metadata)
|
||||
State.NotifyEvents -> notifyPrepare(metadata)
|
||||
State.NotifyResetAll -> notifyResetAll(metadata)
|
||||
State.NotifyComplete -> notifyComplete(metadata)
|
||||
State.Completed -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun reportFailure(metadata: EventMetadata) {
|
||||
val list = eventMetadataRepository.get(config).map { it.copy(response = null) }
|
||||
CoreLogger.log(LogTag.REPORT_MAX_RETRY, "Max Failure reached (current: ${metadata.eventId}): $list")
|
||||
}
|
||||
|
||||
private suspend fun reset() {
|
||||
eventMetadataRepository.deleteAll(config)
|
||||
enqueue(eventId = null, immediately = true)
|
||||
}
|
||||
|
||||
private suspend fun fetch(metadata: EventMetadata) {
|
||||
val eventId = metadata.eventId ?: getLatestEventId()
|
||||
runCatching(
|
||||
config = config,
|
||||
eventId = eventId,
|
||||
processingState = State.Fetching,
|
||||
successState = State.Persisted,
|
||||
failureState = State.Enqueued
|
||||
) {
|
||||
val response = getEventResponse(eventId)
|
||||
val deserializedMetadata = deserializeEventMetadata(eventId, response)
|
||||
eventMetadataRepository.update(deserializedMetadata)
|
||||
deserializedMetadata
|
||||
}.onFailure {
|
||||
when {
|
||||
it is ApiException && it.isRetryable().not() -> notifyResetAll(metadata)
|
||||
it is SerializationException -> notifyResetAll(metadata)
|
||||
else -> throw it // Let's use the WorkManager RETRY mechanism (backoff + network constraint).
|
||||
}
|
||||
}.onSuccess {
|
||||
notify(it)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun notify(metadata: EventMetadata) {
|
||||
when (metadata.refresh) {
|
||||
RefreshType.Nothing -> notifyPrepare(metadata)
|
||||
RefreshType.All,
|
||||
RefreshType.Mail,
|
||||
RefreshType.Contact -> notifyResetAll(metadata)
|
||||
else -> notifyResetAll(metadata)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private suspend fun notifyResetAll(metadata: EventMetadata) {
|
||||
runCatching(
|
||||
config = config,
|
||||
eventId = requireNotNull(metadata.eventId),
|
||||
processingState = State.NotifyResetAll,
|
||||
successState = State.NotifyComplete,
|
||||
failureState = State.NotifyResetAll
|
||||
) {
|
||||
// Fully sequential and ordered.
|
||||
eventListenersByOrder.values.flatten().forEach {
|
||||
it.notifyResetAll(metadata.userId)
|
||||
}
|
||||
}.onFailure {
|
||||
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
|
||||
enqueue(requireNotNull(metadata.eventId), immediately = true)
|
||||
}.onSuccess {
|
||||
notifyComplete(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun notifyPrepare(metadata: EventMetadata) {
|
||||
runCatching(
|
||||
config = config,
|
||||
eventId = requireNotNull(metadata.eventId),
|
||||
processingState = State.NotifyPrepare,
|
||||
successState = State.NotifyEvents,
|
||||
failureState = State.NotifyPrepare
|
||||
) {
|
||||
// Set actions for all listeners.
|
||||
val eventsByListener = deserializeEventsByListener(requireNotNull(metadata.response))
|
||||
eventsByListener.forEach { (eventListener, list) ->
|
||||
eventListener.setActionMap(metadata.userId, list as List<Nothing>)
|
||||
}
|
||||
// Notify prepare for all listeners.
|
||||
eventListenersByOrder.values.flatten().forEach { eventListener ->
|
||||
eventListener.notifyPrepare(metadata.userId)
|
||||
}
|
||||
}.onFailure {
|
||||
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
|
||||
enqueue(metadata.eventId, immediately = true)
|
||||
}.onSuccess {
|
||||
notifyEvents(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun notifyEvents(metadata: EventMetadata) {
|
||||
runInTransaction(
|
||||
config = config,
|
||||
eventId = requireNotNull(metadata.eventId),
|
||||
processingState = State.NotifyEvents,
|
||||
successState = State.NotifyComplete,
|
||||
failureState = State.NotifyPrepare
|
||||
) {
|
||||
// Fully sequential and ordered.
|
||||
eventListenersByOrder.values.flatten().forEach { eventListener ->
|
||||
eventListener.notifyEvents(metadata.userId)
|
||||
}
|
||||
}.onFailure {
|
||||
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
|
||||
enqueue(metadata.eventId, immediately = true)
|
||||
}.onSuccess {
|
||||
notifyComplete(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun notifyComplete(metadata: EventMetadata) {
|
||||
runCatching(
|
||||
config = config,
|
||||
eventId = requireNotNull(metadata.eventId),
|
||||
processingState = State.NotifyComplete,
|
||||
successState = State.Completed,
|
||||
failureState = State.Completed
|
||||
) {
|
||||
// Fully sequential and ordered.
|
||||
eventListenersByOrder.values.flatten().forEach { eventListener ->
|
||||
eventListener.notifyComplete(metadata.userId)
|
||||
}
|
||||
}.onFailure {
|
||||
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
|
||||
enqueue(metadata.nextEventId, immediately = metadata.more ?: false)
|
||||
}.onSuccess {
|
||||
enqueue(metadata.nextEventId, immediately = metadata.more ?: false)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun enqueue(eventId: EventId?, immediately: Boolean) {
|
||||
val metadata = eventId?.let { eventMetadataRepository.get(config, it) }
|
||||
eventMetadataRepository.update(
|
||||
metadata?.takeUnless { metadata.eventId == metadata.nextEventId }?.copy(
|
||||
retry = metadata.retry.plus(1)
|
||||
) ?: EventMetadata(
|
||||
userId = config.userId,
|
||||
eventId = eventId,
|
||||
config = config,
|
||||
retry = 0,
|
||||
state = State.Enqueued,
|
||||
createdAt = System.currentTimeMillis()
|
||||
)
|
||||
)
|
||||
eventWorkerManager.enqueue(config, immediately)
|
||||
}
|
||||
|
||||
private suspend fun enqueueOrCancel(account: Account?) {
|
||||
when {
|
||||
account == null || account.userId != config.userId -> cancel()
|
||||
account.state != AccountState.Ready -> cancel()
|
||||
eventMetadataRepository.get(config).isEmpty() -> enqueue(eventId = null, immediately = true)
|
||||
else -> eventWorkerManager.enqueue(config, immediately = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cancel() {
|
||||
eventWorkerManager.cancel(config)
|
||||
eventMetadataRepository.deleteAll(config)
|
||||
}
|
||||
|
||||
private suspend fun internalStart() {
|
||||
if (isStarted) return
|
||||
|
||||
// Observe any Account changes.
|
||||
observeAccountJob = accountManager.getAccount(config.userId)
|
||||
.distinctUntilChangedBy { it?.state }
|
||||
.onEach { account -> enqueueOrCancel(account) }
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
// Observe any Foreground App State changes.
|
||||
observeAppStateJob = appLifecycleProvider.state
|
||||
.filter { it == AppLifecycleProvider.State.Foreground }
|
||||
.onEach { enqueueOrCancel(accountManager.getAccount(config.userId).firstOrNull()) }
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
isStarted = true
|
||||
}
|
||||
|
||||
private suspend fun internalStop() {
|
||||
if (!isStarted) return
|
||||
|
||||
observeAccountJob?.cancel()
|
||||
observeAppStateJob?.cancel()
|
||||
eventWorkerManager.cancel(config)
|
||||
|
||||
isStarted = false
|
||||
}
|
||||
|
||||
private suspend fun <R> internalSuspend(block: suspend () -> R): R {
|
||||
return if (!isStarted) {
|
||||
block.invoke()
|
||||
} else {
|
||||
internalStop()
|
||||
try {
|
||||
block.invoke()
|
||||
} finally {
|
||||
internalStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val config: EventManagerConfig = deserializer.config
|
||||
override var isStarted: Boolean = false
|
||||
|
||||
override suspend fun start() {
|
||||
lock.withLock { internalStart() }
|
||||
}
|
||||
|
||||
override suspend fun stop() {
|
||||
lock.withLock { internalStop() }
|
||||
}
|
||||
|
||||
override suspend fun <R> suspend(block: suspend () -> R): R {
|
||||
lock.withLock { return internalSuspend(block) }
|
||||
}
|
||||
|
||||
override fun subscribe(eventListener: EventListener<*, *>) {
|
||||
eventListenersByOrder.getOrPut(eventListener.order) { mutableSetOf() }.add(eventListener)
|
||||
}
|
||||
|
||||
override suspend fun process() = processFirstFromConfig()
|
||||
|
||||
override suspend fun getLatestEventId(): EventId =
|
||||
eventMetadataRepository.getLatestEventId(config.userId, deserializer.endpoint)
|
||||
.let { deserializer.deserializeLatestEventId(it) }
|
||||
|
||||
override suspend fun getEventResponse(eventId: EventId): EventsResponse =
|
||||
eventMetadataRepository.getEvents(config.userId, eventId, deserializer.endpoint)
|
||||
|
||||
override suspend fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata =
|
||||
deserializer.deserializeEventMetadata(config.userId, eventId, response)
|
||||
|
||||
companion object {
|
||||
// Constraint: retriesBeforeNotifyResetAll < retriesBeforeReset.
|
||||
const val retriesBeforeNotifyResetAll = 3
|
||||
const val retriesBeforeReset = 6
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import me.proton.core.eventmanager.domain.EventListener
|
||||
import me.proton.core.eventmanager.domain.EventManager
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class EventManagerProviderImpl(
|
||||
private val eventManagerFactory: EventManagerFactory,
|
||||
@JvmSuppressWildcards
|
||||
private val eventListeners: Set<EventListener<*, *>>
|
||||
) : EventManagerProvider {
|
||||
|
||||
// 1 EventManager instance per Config.
|
||||
private val managers = mutableMapOf<EventManagerConfig, EventManager>()
|
||||
private val eventListenersByType = eventListeners.groupBy { it.type }
|
||||
|
||||
override fun get(config: EventManagerConfig): EventManager {
|
||||
// Only create a new instance if config is not found.
|
||||
return managers.getOrPut(config) {
|
||||
val deserializer = when (config) {
|
||||
is EventManagerConfig.Core -> CoreEventDeserializer(config)
|
||||
is EventManagerConfig.Calendar -> CalendarEventDeserializer(config)
|
||||
is EventManagerConfig.Drive -> DriveEventDeserializer(config)
|
||||
}
|
||||
// Create a new EventManager associated with this config.
|
||||
eventManagerFactory.create(deserializer).apply {
|
||||
// Subscribe all known Listener for the same type to it.
|
||||
eventListenersByType[config.listenerType]?.forEach { subscribe(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAll(): List<EventManager> {
|
||||
return managers.values.toList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import me.proton.core.util.kotlin.LoggerLogTag
|
||||
|
||||
object LogTag {
|
||||
/** Tag for Worker Errors. */
|
||||
const val WORKER_ERROR = "core.eventmanager.worker"
|
||||
|
||||
/** Tag for Notify Errors. */
|
||||
const val NOTIFY_ERROR = "core.eventmanager.notify"
|
||||
|
||||
/** Tag for Max Retry Reports. */
|
||||
val REPORT_MAX_RETRY = LoggerLogTag("core.eventmanager.report.maxretry")
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class EventManagerCoroutineScope
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api
|
||||
|
||||
import me.proton.core.network.data.protonApi.BaseRetrofitApi
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface EventApi : BaseRetrofitApi {
|
||||
|
||||
@GET("{endpoint}/latest")
|
||||
suspend fun getLatestEventId(
|
||||
@Path("endpoint", encoded = true) endpoint: String
|
||||
): ResponseBody
|
||||
|
||||
@GET("{endpoint}/{eventId}")
|
||||
suspend fun getEvents(
|
||||
@Path("endpoint", encoded = true) endpoint: String,
|
||||
@Path("eventId") eventId: String
|
||||
): ResponseBody
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetCalendarEventsResponse(
|
||||
@SerialName("CalendarModelEventID")
|
||||
val eventId: String,
|
||||
@SerialName("Refresh")
|
||||
val refresh: Int, // Bitmask to know what to refresh, 0: Nothing, 1: MAIL, 2: CONTACTS, 255: Everything.
|
||||
@SerialName("More")
|
||||
val more: Int, // 1 if there is more to poll.
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetCalendarLatestEventIdResponse(
|
||||
@SerialName("CalendarModelEventID")
|
||||
val eventId: String
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetCoreEventsResponse(
|
||||
@SerialName("EventID")
|
||||
val eventId: String,
|
||||
@SerialName("Refresh")
|
||||
val refresh: Int, // Bitmask to know what to refresh, 0: Nothing, 1: MAIL, 2: CONTACTS, 255: Everything.
|
||||
@SerialName("More")
|
||||
val more: Int, // 1 if there is more to poll.
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetCoreLatestEventIdResponse(
|
||||
@SerialName("EventID")
|
||||
val eventId: String
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetDriveEventsResponse(
|
||||
@SerialName("EventID")
|
||||
val eventId: String
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetDriveLatestEventIdResponse(
|
||||
@SerialName("EventID")
|
||||
val eventId: String
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.db
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.util.kotlin.deserialize
|
||||
import me.proton.core.util.kotlin.serialize
|
||||
|
||||
class EventManagerConverters {
|
||||
@TypeConverter
|
||||
fun fromEventManagerConfigToString(value: EventManagerConfig?) = value?.serialize()
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToEventManagerConfig(value: String?): EventManagerConfig? = value?.deserialize()
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.db
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import me.proton.core.data.room.db.Database
|
||||
import me.proton.core.data.room.db.migration.DatabaseMigration
|
||||
import me.proton.core.eventmanager.data.db.dao.EventMetadataDao
|
||||
|
||||
interface EventMetadataDatabase : Database {
|
||||
fun eventMetadataDao(): EventMetadataDao
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* - Added Table EventMetadataEntity.
|
||||
*/
|
||||
val MIGRATION_0 = object : DatabaseMigration {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `EventMetadataEntity` (`userId` TEXT NOT NULL, `config` TEXT NOT NULL, `eventId` TEXT, `nextEventId` TEXT, `refresh` TEXT, `more` INTEGER, `response` TEXT, `retry` INTEGER NOT NULL, `state` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER, PRIMARY KEY(`userId`, `config`), FOREIGN KEY(`userId`) REFERENCES `UserEntity`(`userId`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
database.execSQL("CREATE INDEX IF NOT EXISTS `index_EventMetadataEntity_userId` ON `EventMetadataEntity` (`userId`)")
|
||||
database.execSQL("CREATE INDEX IF NOT EXISTS `index_EventMetadataEntity_config` ON `EventMetadataEntity` (`config`)")
|
||||
database.execSQL("CREATE INDEX IF NOT EXISTS `index_EventMetadataEntity_createdAt` ON `EventMetadataEntity` (`createdAt`)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import me.proton.core.data.room.db.BaseDao
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.data.entity.EventMetadataEntity
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.State
|
||||
|
||||
@Dao
|
||||
abstract class EventMetadataDao : BaseDao<EventMetadataEntity>() {
|
||||
|
||||
@Query("SELECT * FROM EventMetadataEntity WHERE config = :config AND userId = :userId ORDER BY createdAt")
|
||||
abstract fun observe(userId: UserId, config: EventManagerConfig): Flow<List<EventMetadataEntity>>
|
||||
|
||||
@Query("SELECT * FROM EventMetadataEntity WHERE config = :config AND userId = :userId AND eventId = :eventId")
|
||||
abstract fun observe(userId: UserId, config: EventManagerConfig, eventId: String): Flow<EventMetadataEntity?>
|
||||
|
||||
@Query("SELECT * FROM EventMetadataEntity WHERE config = :config AND userId = :userId ORDER BY createdAt")
|
||||
abstract suspend fun get(userId: UserId, config: EventManagerConfig): List<EventMetadataEntity>
|
||||
|
||||
@Query("SELECT * FROM EventMetadataEntity WHERE config = :config AND userId = :userId AND eventId = :eventId")
|
||||
abstract suspend fun get(userId: UserId, config: EventManagerConfig, eventId: String): EventMetadataEntity?
|
||||
|
||||
@Query("UPDATE EventMetadataEntity SET state = :state, updatedAt = :updatedAt WHERE config = :config AND userId = :userId AND eventId = :eventId")
|
||||
abstract suspend fun updateState(userId: UserId, config: EventManagerConfig, eventId: String, state: State, updatedAt: Long)
|
||||
|
||||
@Query("DELETE FROM EventMetadataEntity WHERE config = :config AND userId = :userId AND eventId = :eventId")
|
||||
abstract suspend fun delete(userId: UserId, config: EventManagerConfig, eventId: String)
|
||||
|
||||
@Query("DELETE FROM EventMetadataEntity WHERE config = :config AND userId = :userId")
|
||||
abstract suspend fun deleteAll(userId: UserId, config: EventManagerConfig)
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.RefreshType
|
||||
import me.proton.core.eventmanager.domain.entity.State
|
||||
import me.proton.core.user.data.entity.UserEntity
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["userId", "config"],
|
||||
indices = [
|
||||
Index("userId"),
|
||||
Index("config"),
|
||||
Index("createdAt"),
|
||||
],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = UserEntity::class,
|
||||
parentColumns = ["userId"],
|
||||
childColumns = ["userId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
]
|
||||
)
|
||||
data class EventMetadataEntity(
|
||||
val userId: UserId,
|
||||
val config: EventManagerConfig,
|
||||
val eventId: String?,
|
||||
val nextEventId: String?,
|
||||
val refresh: RefreshType?,
|
||||
val more: Boolean?,
|
||||
val response: String?,
|
||||
val retry: Int,
|
||||
val state: State,
|
||||
val createdAt: Long,
|
||||
val updatedAt: Long?
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.extension
|
||||
|
||||
import me.proton.core.eventmanager.domain.EventListener
|
||||
|
||||
suspend fun <R> Collection<EventListener<*, *>>.inTransaction(block: suspend () -> R): R {
|
||||
return when {
|
||||
isEmpty() -> block()
|
||||
// Base condition.
|
||||
count() == 1 -> last().inTransaction(block)
|
||||
else -> first().inTransaction {
|
||||
// Recursion.
|
||||
drop(1).inTransaction(block)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.extension
|
||||
|
||||
import me.proton.core.eventmanager.data.EventManagerImpl
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
import me.proton.core.eventmanager.domain.entity.State
|
||||
|
||||
@SuppressWarnings("LongParameterList")
|
||||
internal suspend fun <T> EventManagerImpl.runCatching(
|
||||
config: EventManagerConfig,
|
||||
eventId: EventId,
|
||||
processingState: State,
|
||||
successState: State? = null,
|
||||
failureState: State? = null,
|
||||
action: suspend () -> T
|
||||
): Result<T> {
|
||||
return runCatching {
|
||||
eventMetadataRepository.updateState(config, eventId, processingState)
|
||||
action.invoke()
|
||||
}.onFailure {
|
||||
failureState?.let { eventMetadataRepository.updateState(config, eventId, it) }
|
||||
}.onSuccess {
|
||||
successState?.let { eventMetadataRepository.updateState(config, eventId, it) }
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("LongParameterList")
|
||||
internal suspend fun <T> EventManagerImpl.runInTransaction(
|
||||
config: EventManagerConfig,
|
||||
eventId: EventId,
|
||||
processingState: State,
|
||||
successState: State? = null,
|
||||
failureState: State? = null,
|
||||
action: suspend () -> T
|
||||
): Result<T> {
|
||||
return runCatching(
|
||||
config = config,
|
||||
eventId = eventId,
|
||||
processingState = processingState,
|
||||
failureState = failureState,
|
||||
) {
|
||||
eventListenersByOrder.values.flatten().inTransaction {
|
||||
action.invoke().also {
|
||||
successState?.let { eventMetadataRepository.updateState(config, eventId, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.extension
|
||||
|
||||
import me.proton.core.eventmanager.data.entity.EventMetadataEntity
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
import me.proton.core.eventmanager.domain.entity.EventMetadata
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
|
||||
fun EventMetadata.toEntity() = EventMetadataEntity(
|
||||
userId = userId,
|
||||
config = config,
|
||||
eventId = eventId?.id,
|
||||
nextEventId = nextEventId?.id,
|
||||
refresh = refresh,
|
||||
more = more,
|
||||
response = response?.body,
|
||||
retry = retry,
|
||||
state = state,
|
||||
createdAt = createdAt,
|
||||
updatedAt = updatedAt ?: System.currentTimeMillis()
|
||||
)
|
||||
|
||||
fun EventMetadataEntity.fromEntity() = EventMetadata(
|
||||
userId = userId,
|
||||
config = config,
|
||||
eventId = eventId?.let { EventId(it) },
|
||||
nextEventId = nextEventId?.let { EventId(it) },
|
||||
refresh = refresh,
|
||||
more = more,
|
||||
response = response?.let { EventsResponse(it) },
|
||||
retry = retry,
|
||||
state = state,
|
||||
createdAt = createdAt,
|
||||
updatedAt = updatedAt
|
||||
)
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.data.api.EventApi
|
||||
import me.proton.core.eventmanager.data.db.EventMetadataDatabase
|
||||
import me.proton.core.eventmanager.data.entity.EventMetadataEntity
|
||||
import me.proton.core.eventmanager.data.extension.fromEntity
|
||||
import me.proton.core.eventmanager.data.extension.toEntity
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
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.repository.EventMetadataRepository
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
|
||||
open class EventMetadataRepositoryImpl(
|
||||
db: EventMetadataDatabase,
|
||||
private val provider: ApiProvider
|
||||
) : EventMetadataRepository {
|
||||
|
||||
private val eventMetadataDao = db.eventMetadataDao()
|
||||
|
||||
private suspend fun insertOrUpdate(entity: EventMetadataEntity) =
|
||||
eventMetadataDao.insertOrUpdate(entity)
|
||||
|
||||
override fun observe(config: EventManagerConfig): Flow<List<EventMetadata>> =
|
||||
eventMetadataDao.observe(config.userId, config).map { it.map { entity -> entity.fromEntity() } }
|
||||
|
||||
override fun observe(config: EventManagerConfig, eventId: EventId): Flow<EventMetadata?> =
|
||||
eventMetadataDao.observe(config.userId, config, eventId.id).map { entity -> entity?.fromEntity() }
|
||||
|
||||
override suspend fun delete(config: EventManagerConfig, eventId: EventId) =
|
||||
eventMetadataDao.delete(config.userId, config, eventId.id)
|
||||
|
||||
override suspend fun deleteAll(config: EventManagerConfig) =
|
||||
eventMetadataDao.deleteAll(config.userId, config)
|
||||
|
||||
override suspend fun update(metadata: EventMetadata) =
|
||||
insertOrUpdate(metadata.toEntity().copy(updatedAt = System.currentTimeMillis()))
|
||||
|
||||
override suspend fun updateState(config: EventManagerConfig, eventId: EventId, state: State) =
|
||||
eventMetadataDao.updateState(config.userId, config, eventId.id, state, System.currentTimeMillis())
|
||||
|
||||
override suspend fun get(config: EventManagerConfig): List<EventMetadata> =
|
||||
eventMetadataDao.get(config.userId, config).map { it.fromEntity() }
|
||||
|
||||
override suspend fun get(config: EventManagerConfig, eventId: EventId): EventMetadata? =
|
||||
eventMetadataDao.get(config.userId, config, eventId.id)?.fromEntity()
|
||||
|
||||
override suspend fun getLatestEventId(
|
||||
userId: UserId,
|
||||
endpoint: String
|
||||
): EventIdResponse = provider.get<EventApi>(userId).invoke {
|
||||
val response = getLatestEventId(endpoint)
|
||||
EventIdResponse(response.string())
|
||||
}.valueOrThrow
|
||||
|
||||
override suspend fun getEvents(
|
||||
userId: UserId,
|
||||
eventId: EventId,
|
||||
endpoint: String
|
||||
): EventsResponse = provider.get<EventApi>(userId).invoke {
|
||||
val response = getEvents(endpoint, eventId.id)
|
||||
EventsResponse(response.string())
|
||||
}.valueOrThrow
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.work
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import me.proton.core.eventmanager.data.LogTag
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.eventmanager.domain.work.EventWorkerManager
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.core.util.kotlin.deserialize
|
||||
import me.proton.core.util.kotlin.serialize
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration
|
||||
|
||||
@HiltWorker
|
||||
open class EventWorker @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
private val eventManagerProvider: EventManagerProvider
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val config = requireNotNull(inputData.getString(KEY_INPUT_CONFIG)?.deserialize<EventManagerConfig>())
|
||||
val manager = eventManagerProvider.get(config)
|
||||
return runCatching { manager.process() }.fold(
|
||||
onSuccess = {
|
||||
Result.success()
|
||||
},
|
||||
onFailure = {
|
||||
CoreLogger.e(LogTag.WORKER_ERROR, it)
|
||||
Result.retry()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_INPUT_CONFIG = "config"
|
||||
|
||||
fun getRequestFor(config: EventManagerConfig, initialDelay: Duration): PeriodicWorkRequest {
|
||||
val initialDelaySeconds = initialDelay.inWholeSeconds
|
||||
val backoffDelaySeconds = EventWorkerManager.BACKOFF_DELAY.inWholeSeconds
|
||||
val repeatIntervalSeconds = EventWorkerManager.REPEAT_INTERVAL_BACKGROUND.inWholeSeconds
|
||||
val serializedConfig = config.serialize()
|
||||
return PeriodicWorkRequestBuilder<EventWorker>(repeatIntervalSeconds, TimeUnit.SECONDS)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, backoffDelaySeconds, TimeUnit.SECONDS)
|
||||
.setInputData(workDataOf(KEY_INPUT_CONFIG to serializedConfig))
|
||||
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
|
||||
.setInitialDelay(initialDelaySeconds, TimeUnit.SECONDS)
|
||||
.addTag(serializedConfig)
|
||||
.addTag(config.listenerType.name)
|
||||
.addTag(config.userId.id)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.work
|
||||
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
|
||||
import androidx.work.WorkManager
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.work.EventWorkerManager
|
||||
import me.proton.core.presentation.app.AppLifecycleProvider
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
|
||||
class EventWorkerManagerImpl @Inject constructor(
|
||||
private val workManager: WorkManager,
|
||||
private val appLifecycleProvider: AppLifecycleProvider
|
||||
) : EventWorkerManager {
|
||||
|
||||
private fun getUniqueWorkName(config: EventManagerConfig) = "$config"
|
||||
|
||||
override fun enqueue(config: EventManagerConfig, immediately: Boolean) {
|
||||
val uniqueWorkName = getUniqueWorkName(config)
|
||||
val initialDelay = if (immediately) Duration.ZERO else when (appLifecycleProvider.state.value) {
|
||||
AppLifecycleProvider.State.Background -> EventWorkerManager.REPEAT_INTERVAL_BACKGROUND
|
||||
AppLifecycleProvider.State.Foreground -> EventWorkerManager.REPEAT_INTERVAL_FOREGROUND
|
||||
}
|
||||
val request = EventWorker.getRequestFor(config, initialDelay)
|
||||
workManager.enqueueUniquePeriodicWork(uniqueWorkName, REPLACE, request)
|
||||
}
|
||||
|
||||
override fun cancel(config: EventManagerConfig) {
|
||||
val uniqueWorkName = getUniqueWorkName(config)
|
||||
workManager.cancelUniqueWork(uniqueWorkName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.eventmanager.domain.extension.suspend
|
||||
|
||||
class EventManagerApiGoal {
|
||||
|
||||
lateinit var provider: EventManagerProvider
|
||||
|
||||
suspend fun onAccountReady(userId: UserId) {
|
||||
provider.get(EventManagerConfig.Core(userId)).start()
|
||||
}
|
||||
|
||||
suspend fun onPerformAction(userId: UserId) {
|
||||
suspend fun callApi() = Unit
|
||||
provider.suspend(EventManagerConfig.Core(userId)) {
|
||||
callApi()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun onCalendarActive(userId: UserId, calendarId: String) {
|
||||
provider.get(EventManagerConfig.Calendar(userId, calendarId)).start()
|
||||
}
|
||||
|
||||
suspend fun onCalendarInactive(userId: UserId, calendarId: String) {
|
||||
provider.get(EventManagerConfig.Calendar(userId, calendarId)).stop()
|
||||
}
|
||||
|
||||
suspend fun onDriveShareActive(userId: UserId, shareId: String) {
|
||||
provider.get(EventManagerConfig.Drive(userId, shareId)).start()
|
||||
}
|
||||
|
||||
suspend fun onDriveShareInactive(userId: UserId, shareId: String) {
|
||||
provider.get(EventManagerConfig.Drive(userId, shareId)).stop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.TestCoroutineScope
|
||||
import me.proton.core.account.domain.entity.Account
|
||||
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.ContactEventListener
|
||||
import me.proton.core.eventmanager.data.listener.UserEventListener
|
||||
import me.proton.core.eventmanager.domain.EventListener
|
||||
import me.proton.core.eventmanager.domain.EventManager
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
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.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
|
||||
|
||||
class EventManagerImplTest {
|
||||
|
||||
private val coroutineScope = TestCoroutineScope()
|
||||
|
||||
private lateinit var eventManagerFactor: EventManagerFactory
|
||||
private lateinit var eventManagerProvider: EventManagerProvider
|
||||
|
||||
private lateinit var appLifecycleProvider: AppLifecycleProvider
|
||||
private lateinit var accountManager: AccountManager
|
||||
private lateinit var eventWorkerManager: EventWorkerManager
|
||||
private lateinit var eventMetadataRepository: EventMetadataRepository
|
||||
|
||||
private lateinit var userEventListener: UserEventListener
|
||||
private lateinit var contactEventListener: ContactEventListener
|
||||
private lateinit var listeners: Set<EventListener<*, *>>
|
||||
|
||||
private val user1 = Account(
|
||||
userId = UserId("user1"),
|
||||
username = "user1",
|
||||
email = "user1@protonmail.com",
|
||||
state = AccountState.Ready,
|
||||
sessionId = null,
|
||||
sessionState = null,
|
||||
details = AccountDetails(null)
|
||||
)
|
||||
private val user2 = Account(
|
||||
userId = UserId("user2"),
|
||||
username = "user2",
|
||||
email = "user2@protonmail.com",
|
||||
state = AccountState.Ready,
|
||||
sessionId = null,
|
||||
sessionState = null,
|
||||
details = AccountDetails(null)
|
||||
)
|
||||
private val accounts = listOf(user1, user2)
|
||||
|
||||
private val user1Config = EventManagerConfig.Core(user1.userId)
|
||||
private val user2Config = EventManagerConfig.Core(user2.userId)
|
||||
|
||||
private val eventId = "eventId"
|
||||
private val appState = MutableStateFlow(AppLifecycleProvider.State.Foreground)
|
||||
|
||||
private lateinit var user1Manager: EventManager
|
||||
private lateinit var user2Manager: EventManager
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
userEventListener = spyk(UserEventListener())
|
||||
contactEventListener = spyk(ContactEventListener())
|
||||
listeners = setOf<EventListener<*, *>>(userEventListener, contactEventListener)
|
||||
|
||||
appLifecycleProvider = mockk {
|
||||
every { state } returns appState
|
||||
}
|
||||
accountManager = mockk {
|
||||
val userIdSlot = slot<UserId>()
|
||||
every { getAccount(capture(userIdSlot)) } answers {
|
||||
flowOf(accounts.firstOrNull { it.userId == userIdSlot.captured })
|
||||
}
|
||||
}
|
||||
eventWorkerManager = spyk()
|
||||
eventMetadataRepository = spyk()
|
||||
eventManagerFactor = mockk {
|
||||
val deserializerSlot = slot<EventDeserializer>()
|
||||
every { create(capture(deserializerSlot)) } answers {
|
||||
EventManagerImpl(
|
||||
coroutineScope,
|
||||
appLifecycleProvider,
|
||||
accountManager,
|
||||
eventWorkerManager,
|
||||
eventMetadataRepository,
|
||||
deserializerSlot.captured
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
eventManagerProvider = EventManagerProviderImpl(eventManagerFactor, listeners)
|
||||
user1Manager = eventManagerProvider.get(user1Config)
|
||||
user2Manager = eventManagerProvider.get(user2Config)
|
||||
|
||||
coEvery { eventMetadataRepository.getLatestEventId(any(), any()) } returns
|
||||
EventIdResponse("{ \"EventID\": \"$eventId\" }")
|
||||
|
||||
coEvery { eventMetadataRepository.getEvents(any(), any(), any()) } returns
|
||||
EventsResponse(TestEvents.coreFullEventsResponse)
|
||||
|
||||
coEvery { eventMetadataRepository.update(any()) } returns Unit
|
||||
coEvery { eventMetadataRepository.updateState(any(), any(), any()) } returns Unit
|
||||
|
||||
// GIVEN
|
||||
coEvery { eventMetadataRepository.get(user1Config) } returns
|
||||
listOf(EventMetadata(user1.userId, EventId(eventId), user1Config, createdAt = 1))
|
||||
|
||||
coEvery { eventMetadataRepository.get(user2Config) } returns
|
||||
listOf(EventMetadata(user2.userId, EventId(eventId), user2Config, createdAt = 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun callCorrectPrepareUpdateDeleteCreate() = runBlocking {
|
||||
// WHEN
|
||||
user1Manager.process()
|
||||
user2Manager.process()
|
||||
// THEN
|
||||
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) { 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(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) { 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(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyPrepare) }
|
||||
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyEvents) }
|
||||
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyComplete) }
|
||||
coVerify(exactly = 1) { eventMetadataRepository.updateState(user2Config, any(), State.Completed) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun callOnPrepareThrowException() = runBlocking {
|
||||
// GIVEN
|
||||
coEvery { userEventListener.onPrepare(user1.userId, 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.inTransaction(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun callOnUpdateThrowException() = runBlocking {
|
||||
// GIVEN
|
||||
coEvery { userEventListener.onUpdate(user1.userId, 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.inTransaction(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preventMultiSubscribe() = runBlocking {
|
||||
// GIVEN
|
||||
user1Manager.subscribe(userEventListener)
|
||||
user1Manager.subscribe(userEventListener)
|
||||
user1Manager.subscribe(userEventListener)
|
||||
// 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()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preventEventIfNoUser() = runBlocking {
|
||||
// GIVEN
|
||||
coEvery { eventMetadataRepository.get(user1Config) } returns emptyList()
|
||||
// WHEN
|
||||
user1Manager.process()
|
||||
// THEN
|
||||
coVerify(exactly = 0) { userEventListener.onPrepare(any(), any()) }
|
||||
coVerify(exactly = 0) { userEventListener.onUpdate(any(), any()) }
|
||||
coVerify(exactly = 0) { userEventListener.onDelete(any(), any()) }
|
||||
coVerify(exactly = 0) { userEventListener.onCreate(any(), any()) }
|
||||
coVerify(exactly = 0) { userEventListener.onPartial(any(), any()) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data
|
||||
|
||||
object TestEvents {
|
||||
val coreFullEventsResponse =
|
||||
"""
|
||||
{
|
||||
"Code": 1000,
|
||||
"EventID": "ACXDmTaBub14w==",
|
||||
"Refresh": 0,
|
||||
"More": 0,
|
||||
|
||||
"Contacts": [
|
||||
{
|
||||
"ID": "afeaefaeTaBub14w==",
|
||||
"Action": 1,
|
||||
"Contact": {
|
||||
"ID": "a29olIjFv0rnXxBhSMw==",
|
||||
"Name": "ProtonMail Features",
|
||||
"ContactEmails": [
|
||||
{
|
||||
"ID": "aefew4323jFv0BhSMw==",
|
||||
"Name": "test1",
|
||||
"Email": "features@protonmail.black",
|
||||
"Type": [
|
||||
"work"
|
||||
],
|
||||
"Defaults": 1,
|
||||
"Order": 1,
|
||||
"ContactID": "a29olIjFv0rnXxBhSMw==",
|
||||
"LabelIDs": [
|
||||
"I6hgx3Ol-d3HYa3E394T_ACXDmTaBub14w=="
|
||||
]
|
||||
}
|
||||
],
|
||||
"LabelIDs": [
|
||||
"I6hgx3Ol-d3HYa3E394T_ACXDmTaBub14w=="
|
||||
],
|
||||
"Cards": [
|
||||
{
|
||||
"Type": 2,
|
||||
"Data": "BEGIN:VCARD\\r\\nVERSION:4.0\\r\\nFN:ProtonMail Features\\r\\nUID:proton-legacy-139892c2-f691-4118-8c29-061196013e04\\r\\nitem1.EMAIL;TYPE=work;PREF=1:features@protonmail.black\\r\\nitem2.EMAIL;TYPE=home;PREF=2:features@protonmail.ch\\r\\nEND:VCARD\\r\\n",
|
||||
"Signature": "-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"ContactEmails": [
|
||||
{
|
||||
"ID": "sadfaACXDmTaBub14w==",
|
||||
"Action": 1,
|
||||
"ContactEmail": {
|
||||
"ID": "aefew4323jFv0BhSMw==",
|
||||
"Name": "test1",
|
||||
"Email": "features@protonmail.black",
|
||||
"Type": [
|
||||
"work"
|
||||
],
|
||||
"Defaults": 1,
|
||||
"Order": 1,
|
||||
"ContactID": "a29olIjFv0rnXxBhSMw==",
|
||||
"LabelIDs": [
|
||||
"I6hgx3Ol-d3HYa3E394T_ACXDmTaBub14w=="
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"User": {
|
||||
"ID": "MJLke8kWh1BBvG95JBIrZvzpgsZ94hNNgjNHVyhXMiv4g9cn6SgvqiIFR5cigpml2LD_iUk_3DkV29oojTt3eA==",
|
||||
"Name": "jason",
|
||||
"UsedSpace": 96691332,
|
||||
"Currency": "USD",
|
||||
"Credit": 0,
|
||||
"MaxSpace": 10737418240,
|
||||
"MaxUpload": 26214400,
|
||||
"Role": 2,
|
||||
"Private": 1,
|
||||
"ToMigrate": 1,
|
||||
"MnemonicStatus": 1,
|
||||
"Subscribed": 1,
|
||||
"Services": 1,
|
||||
"Delinquent": 0,
|
||||
"Email": "jason@protonmail.ch",
|
||||
"DisplayName": "Jason",
|
||||
"Keys": [{
|
||||
"ID": "IlnTbqicN-2HfUGIn-ki8bqZfLqNj5ErUB0z24Qx5g-4NvrrIc6GLvEpj2EPfwGDv28aKYVRRrSgEFhR_zhlkA==",
|
||||
"Version": 3,
|
||||
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----*-----END PGP PRIVATE KEY BLOCK-----",
|
||||
"Fingerprint": "c93f767df53b0ca8395cfde90483475164ec6353",
|
||||
"Activation": null,
|
||||
"Primary": 1
|
||||
}]
|
||||
},
|
||||
"UserSettings": {
|
||||
"Email": {
|
||||
"Value": "abc@gmail.com",
|
||||
"Status": 0,
|
||||
"Notify": 1,
|
||||
"Reset": 0
|
||||
},
|
||||
"Phone": {
|
||||
"Value": "+18005555555",
|
||||
"Status": 0,
|
||||
"Notify": 0,
|
||||
"Reset": 0
|
||||
},
|
||||
"Password": {
|
||||
"Mode": 2,
|
||||
"ExpirationTime": null
|
||||
},
|
||||
"2FA": {
|
||||
"Enabled": 3,
|
||||
"Allowed": 3,
|
||||
"ExpirationTime": null,
|
||||
"U2FKeys": [
|
||||
{
|
||||
"Label": "A name",
|
||||
"KeyHandle": "aKeyHandle",
|
||||
"Compromised": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"News": 244,
|
||||
"Locale": "en_US",
|
||||
"LogAuth": 2,
|
||||
"InvoiceText": "AnyText",
|
||||
"Density": 0,
|
||||
"Theme": "css",
|
||||
"ThemeType": 1,
|
||||
"WeekStart": 1,
|
||||
"DateFormat": 1,
|
||||
"TimeFormat": 1,
|
||||
"Welcome": "1",
|
||||
"WelcomeFlag": "1",
|
||||
"EarlyAccess": "1",
|
||||
"FontSize": "14",
|
||||
"Flags": {
|
||||
"Welcomed": 0
|
||||
}
|
||||
},
|
||||
"MailSettings": {
|
||||
"DisplayName": "Put Chinese Here",
|
||||
"Signature": "This is my signature",
|
||||
"Theme": "<CSS>",
|
||||
"AutoResponder": {
|
||||
"StartTime": 0,
|
||||
"Endtime": 0,
|
||||
"Repeat": 0,
|
||||
"DaysSelected": [
|
||||
"string"
|
||||
],
|
||||
"Subject": "Auto",
|
||||
"Message": "",
|
||||
"IsEnabled": null,
|
||||
"Zone": "Europe/Zurich"
|
||||
},
|
||||
"AutoSaveContacts": 1,
|
||||
"AutoWildcardSearch": 1,
|
||||
"ComposerMode": 0,
|
||||
"MessageButtons": 0,
|
||||
"ShowImages": 2,
|
||||
"ShowMoved": 0,
|
||||
"ViewMode": 0,
|
||||
"ViewLayout": 0,
|
||||
"SwipeLeft": 3,
|
||||
"SwipeRight": 0,
|
||||
"AlsoArchive": 0,
|
||||
"Hotkeys": 1,
|
||||
"Shortcuts": 1,
|
||||
"PMSignature": 0,
|
||||
"ImageProxy": 0,
|
||||
"NumMessagePerPage": 50,
|
||||
"DraftMIMEType": "text/html",
|
||||
"ReceiveMIMEType": "text/html",
|
||||
"ShowMIMEType": "text/html",
|
||||
"EnableFolderColor": 0,
|
||||
"InheritParentFolderColor": 1,
|
||||
"TLS": 0,
|
||||
"RightToLeft": 0,
|
||||
"AttachPublicKey": 0,
|
||||
"Sign": 0,
|
||||
"PGPScheme": 16,
|
||||
"PromptPin": 0,
|
||||
"Autocrypt": 0,
|
||||
"StickyLabels": 0,
|
||||
"ConfirmLink": 1,
|
||||
"DelaySendSeconds": 10,
|
||||
"KT": 0,
|
||||
"FontSize": null,
|
||||
"FontFace": null
|
||||
},
|
||||
"Addresses": [
|
||||
{
|
||||
"ID": "q_9v-GXEPLagg81jsUz2mHQ==",
|
||||
"Action": 2,
|
||||
"Address": {
|
||||
"ID": "q_9v-GXEPLagg81jsUz2mHQ==",
|
||||
"DomainID": "l8vWAXHBQmvzmKUA==",
|
||||
"Email": "test@protonmail.com",
|
||||
"Send": 0,
|
||||
"Receive": 0,
|
||||
"Status": 0,
|
||||
"Type": 2,
|
||||
"Order": 8,
|
||||
"DisplayName": "Namey",
|
||||
"Signature": "Sent from <a href=\"https://protonmail.ch\">ProtonMail</a>",
|
||||
"HasKeys": 1,
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "a0f5_q7xkcyON1blZKTPxmBceURtzhW5Jc1rhtWUw5w2QXCMkSzHNustWtTjUlma9JmiL8O71aimfMOyY3UUGQ==",
|
||||
"Version": 3,
|
||||
"PublicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----...-----END PGP PUBLIC KEY BLOCK-----",
|
||||
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----...-----END PGP PRIVATE KEY BLOCK-----",
|
||||
"Token": "null or -----BEGIN PGP MESSAGE-----.*-----END PGP MESSAGE-----",
|
||||
"Signature": "null or -----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----",
|
||||
"Fingerprint": "e7e5466d21ff064ef870a7a393526f79e83004b0",
|
||||
"Fingerprints": [
|
||||
"e7e5466d21ff064ef870a7a393526f79e83004b0"
|
||||
],
|
||||
"Activation": null,
|
||||
"Primary": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.data.listener
|
||||
|
||||
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.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 ContactsEvents(
|
||||
@SerialName("Contacts")
|
||||
val contacts: List<ContactEvent>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContactEvent(
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
@SerialName("Action")
|
||||
val action: Int,
|
||||
@SerialName("Contact")
|
||||
val contact: ContactWithCardsResource? = null
|
||||
)
|
||||
|
||||
class ContactEventListener : EventListener<String, ContactWithCardsResource>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 1
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, ContactWithCardsResource>>? {
|
||||
return response.body.deserializeOrNull<ContactsEvents>()?.contacts?.map {
|
||||
Event(requireNotNull(Action.map[it.action]), it.id, it.contact)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
// Db.inTransaction(block)
|
||||
return block()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.entity.Action
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
import me.proton.core.key.data.api.response.UserResponse
|
||||
import me.proton.core.util.kotlin.deserializeOrNull
|
||||
|
||||
@Serializable
|
||||
data class UserEvents(
|
||||
@SerialName("User")
|
||||
val user: UserResponse
|
||||
)
|
||||
|
||||
class UserEventListener : EventListener<String, UserResponse>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 0
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, UserResponse>>? {
|
||||
return response.body.deserializeOrNull<UserEvents>()?.let {
|
||||
listOf(Event(Action.Update, it.user.id, it.user))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
// Db.inTransaction(block)
|
||||
return block()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import studio.forface.easygradle.dsl.*
|
||||
import studio.forface.easygradle.dsl.android.*
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
libVersion = parent?.libVersion
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(
|
||||
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.domain),
|
||||
project(Module.cryptoCommon),
|
||||
project(Module.networkDomain),
|
||||
|
||||
// Kotlin
|
||||
`kotlin-jdk8`,
|
||||
`serialization-json`,
|
||||
`coroutines-core`
|
||||
)
|
||||
|
||||
testImplementation(project(Module.kotlinTest))
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<K, T : Any> : TransactionHandler {
|
||||
|
||||
private val actionMapByUserId = mutableMapOf<UserId, Map<Action, List<Event<K, T>>>>()
|
||||
|
||||
/**
|
||||
* Type of Event loop.
|
||||
*/
|
||||
enum class Type {
|
||||
/**
|
||||
* Core Event loop.
|
||||
*
|
||||
* Contains: Messages, Conversations, Import, Contacts, Filter, Labels, Subscriptions, User, Settings, ...
|
||||
*/
|
||||
Core,
|
||||
|
||||
/**
|
||||
* Calendar Event loop.
|
||||
*
|
||||
* Contains: Calendars, CalendarKeys, CalendarEvents, CalendarAlarms, Settings, CalendarSubscriptions, ...
|
||||
*/
|
||||
Calendar,
|
||||
|
||||
/**
|
||||
* Drive Event loop.
|
||||
*
|
||||
* Contains: Share, Links, Nodes, ...
|
||||
*/
|
||||
Drive
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener [type] to associate with this [EventListener].
|
||||
*/
|
||||
abstract val type: Type
|
||||
|
||||
/**
|
||||
* The degrees of separation from this entity to the User entity.
|
||||
*
|
||||
* Examples:
|
||||
* - UserEventListener: User => 0.
|
||||
* - UserAddressEventListener: UserAddress -> User => 1.
|
||||
* - ContactEventListener: Contact -> User => 1.
|
||||
* - ContactEmailEventListener: ContactEmail -> Contact -> User => 2.
|
||||
*/
|
||||
abstract val order: Int
|
||||
|
||||
/**
|
||||
* Get actions part of the current set of modifications.
|
||||
*
|
||||
* Note: The map is created just before [onPrepare], and cleared after [onComplete].
|
||||
*/
|
||||
fun getActionMap(userId: UserId): Map<Action, List<Event<K, T>>> {
|
||||
return actionMapByUserId.getOrPut(userId) { emptyMap() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the actions part of the current set of modifications.
|
||||
*
|
||||
* Note: Called before [notifyPrepare] and after [notifyComplete].
|
||||
*/
|
||||
fun setActionMap(userId: UserId, events: List<Event<K, T>>) {
|
||||
actionMapByUserId[userId] = events.groupByAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify to prepare any additional data (e.g. foreign key).
|
||||
*
|
||||
* Note: No transaction wraps this function.
|
||||
*/
|
||||
suspend fun notifyPrepare(userId: UserId) {
|
||||
val actions = getActionMap(userId)
|
||||
val entities = actions[Action.Create].orEmpty() + actions[Action.Update].orEmpty()
|
||||
entities.takeIfNotEmpty()?.let { list -> onPrepare(userId, list.mapNotNull { it.entity }) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all events in this order [onCreate], [onUpdate], [onPartial] and [onDelete].
|
||||
*
|
||||
* 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 }) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify to reset all entities.
|
||||
*
|
||||
* Note: No transaction wraps this function.
|
||||
*/
|
||||
suspend fun notifyResetAll(userId: UserId) {
|
||||
onResetAll(userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify complete, whether set of modifications was successful or not.
|
||||
*
|
||||
* Note: No transaction wraps this function.
|
||||
*/
|
||||
suspend fun notifyComplete(userId: UserId) {
|
||||
onComplete(userId)
|
||||
setActionMap(userId, emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize [response] into a typed list of [Event].
|
||||
*/
|
||||
abstract suspend fun deserializeEvents(response: EventsResponse): List<Event<K, T>>?
|
||||
|
||||
/**
|
||||
* Called before applying a set of modifications to prepare any additional action (e.g. fetch foreign entities).
|
||||
*
|
||||
* Note: Delete action entities are filtered out.
|
||||
*
|
||||
* @see onComplete
|
||||
*/
|
||||
open suspend fun onPrepare(userId: UserId, entities: List<T>) = 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
|
||||
|
||||
/**
|
||||
* Called to created entities in persistence.
|
||||
*
|
||||
* There is no guarantee any prior [onCreate] will be called for any needed foreign entity.
|
||||
*
|
||||
* Note: A transaction wraps this function and must return as fast as possible.
|
||||
*
|
||||
* @see onPrepare
|
||||
*/
|
||||
open suspend fun onCreate(userId: UserId, entities: List<T>) = Unit
|
||||
|
||||
/**
|
||||
* Called to update or insert entities in persistence.
|
||||
*
|
||||
* There is no guarantee any prior [onCreate] will be called for any needed foreign entity.
|
||||
*
|
||||
* Note: A transaction wraps this function and must return as fast as possible.
|
||||
*
|
||||
* @see onPrepare
|
||||
*/
|
||||
open suspend fun onUpdate(userId: UserId, entities: List<T>) = Unit
|
||||
|
||||
/**
|
||||
* Called to partially update entities in persistence.
|
||||
*
|
||||
* There is no guarantee any prior [onCreate] will be called for any needed foreign entity.
|
||||
*
|
||||
* Note: A transaction wraps this function and must return as fast as possible.
|
||||
*
|
||||
* @see onPrepare
|
||||
*/
|
||||
open suspend fun onPartial(userId: UserId, entities: List<T>) = 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<K>) = 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
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain
|
||||
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
import me.proton.core.eventmanager.domain.entity.EventMetadata
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
|
||||
interface EventManager {
|
||||
|
||||
/**
|
||||
* [EventManagerConfig] associated with this [EventManager].
|
||||
*/
|
||||
val config: EventManagerConfig
|
||||
|
||||
/**
|
||||
* Returns `true` when the Event loop is started.
|
||||
*
|
||||
* @see start
|
||||
* @see stop
|
||||
*/
|
||||
val isStarted: Boolean
|
||||
|
||||
/**
|
||||
* Start the Event loop. This call can be called multiple consecutive time without affecting the behavior.
|
||||
*
|
||||
* Note: The loop will automatically be paused or resumed depending the associated [config] User account state.
|
||||
*/
|
||||
suspend fun start()
|
||||
|
||||
/**
|
||||
* Stop the Event loop. This call can be called multiple consecutive time without affecting the behavior.
|
||||
*
|
||||
* Note: The loop will not automatically be restarted/resumed after this call.
|
||||
*/
|
||||
suspend fun stop()
|
||||
|
||||
/**
|
||||
* Pause the Event loop, calls the specified function [block], and resume the loop.
|
||||
*
|
||||
* Note: The loop will not be paused/resumed if it was not already started.
|
||||
*/
|
||||
suspend fun <R> suspend(block: suspend () -> R): R
|
||||
|
||||
/**
|
||||
* Subscribe a new [eventListener].
|
||||
*/
|
||||
fun subscribe(eventListener: EventListener<*, *>)
|
||||
|
||||
/**
|
||||
* Process the next task for the associated [config], if exist.
|
||||
*/
|
||||
suspend fun process()
|
||||
|
||||
/**
|
||||
* Fetch the latest EventId for the associated [config].
|
||||
*/
|
||||
suspend fun getLatestEventId(): EventId
|
||||
|
||||
/**
|
||||
* Fetch the Events for the associated [config] and [eventId].
|
||||
*/
|
||||
suspend fun getEventResponse(eventId: EventId): EventsResponse
|
||||
|
||||
/**
|
||||
* Deserialize an [EventMetadata] from [response] for the associated [config] and [eventId].
|
||||
*/
|
||||
suspend fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.domain.entity.UserId
|
||||
|
||||
@Serializable
|
||||
sealed class EventManagerConfig {
|
||||
|
||||
abstract val listenerType: EventListener.Type
|
||||
abstract val userId: UserId
|
||||
|
||||
@Serializable
|
||||
data class Core(
|
||||
override val userId: UserId
|
||||
) : EventManagerConfig() {
|
||||
override val listenerType = EventListener.Type.Core
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Calendar(
|
||||
override val userId: UserId,
|
||||
val calendarId: String,
|
||||
val apiVersion: String = "v1"
|
||||
) : EventManagerConfig() {
|
||||
override val listenerType = EventListener.Type.Calendar
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Drive(
|
||||
override val userId: UserId,
|
||||
val shareId: String
|
||||
) : EventManagerConfig() {
|
||||
override val listenerType = EventListener.Type.Drive
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain
|
||||
|
||||
interface EventManagerProvider {
|
||||
/**
|
||||
* Get an [EventManager] associated with the given [config].
|
||||
*/
|
||||
fun get(config: EventManagerConfig): EventManager
|
||||
|
||||
/**
|
||||
* Get all [EventManager].
|
||||
*/
|
||||
fun getAll(): List<EventManager>
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain
|
||||
|
||||
interface TransactionHandler {
|
||||
/**
|
||||
* Calls the specified suspending [block] in a transaction. The transaction will be marked as successful unless
|
||||
* an exception is thrown in the suspending [block] or the coroutine is cancelled.
|
||||
*/
|
||||
suspend fun <R> inTransaction(block: suspend () -> R): R
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.entity
|
||||
|
||||
data class Event<K, T>(
|
||||
val action: Action,
|
||||
val key: K,
|
||||
val entity: T?
|
||||
)
|
||||
|
||||
enum class Action(val value: Int) {
|
||||
Delete(0),
|
||||
Create(1),
|
||||
Update(2),
|
||||
Partial(3);
|
||||
|
||||
companion object {
|
||||
val map = values().associateBy { it.value }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.entity
|
||||
|
||||
data class EventId(val id: String)
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.entity
|
||||
|
||||
data class EventIdResponse(val body: String)
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.entity
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
|
||||
data class EventMetadata(
|
||||
val userId: UserId,
|
||||
val eventId: EventId?,
|
||||
val config: EventManagerConfig,
|
||||
val nextEventId: EventId? = null,
|
||||
val refresh: RefreshType? = null,
|
||||
val more: Boolean? = null,
|
||||
val response: EventsResponse? = null,
|
||||
val retry: Int = 0,
|
||||
val state: State = State.Enqueued,
|
||||
val createdAt: Long,
|
||||
val updatedAt: Long? = null
|
||||
)
|
||||
|
||||
enum class RefreshType(val value: Int) {
|
||||
Nothing(0),
|
||||
Mail(1),
|
||||
Contact(2),
|
||||
All(255);
|
||||
|
||||
companion object {
|
||||
val map = values().associateBy { it.value }
|
||||
}
|
||||
}
|
||||
|
||||
enum class State(val value: Int) {
|
||||
Enqueued(0),
|
||||
Fetching(1),
|
||||
Persisted(2),
|
||||
NotifyPrepare(3),
|
||||
NotifyEvents(4),
|
||||
NotifyResetAll(5),
|
||||
NotifyComplete(6),
|
||||
Completed(7),
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.entity
|
||||
|
||||
data class EventsResponse(val body: String)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.extension
|
||||
|
||||
import me.proton.core.eventmanager.domain.entity.Action
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
|
||||
/**
|
||||
* Group all [Event] by [Event.action].
|
||||
*/
|
||||
fun <K, T> List<Event<K, T>>.groupByAction(): Map<Action, List<Event<K, T>>> =
|
||||
groupBy(keySelector = { event -> event.action }, valueTransform = { event -> event })
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.extension
|
||||
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
|
||||
/**
|
||||
* Pause the Event loop, calls the specified function [block], and resume the loop.
|
||||
*
|
||||
* Note: The loop will not be paused/resumed if it was not already started.
|
||||
*/
|
||||
suspend fun <R> EventManagerProvider.suspend(config: EventManagerConfig, block: suspend () -> R): R {
|
||||
return get(config).suspend(block)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import me.proton.core.eventmanager.domain.entity.EventId
|
||||
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
|
||||
|
||||
interface EventMetadataRepository {
|
||||
|
||||
fun observe(config: EventManagerConfig): Flow<List<EventMetadata>>
|
||||
fun observe(config: EventManagerConfig, eventId: EventId): Flow<EventMetadata?>
|
||||
|
||||
suspend fun deleteAll(config: EventManagerConfig)
|
||||
suspend fun delete(config: EventManagerConfig, eventId: EventId)
|
||||
|
||||
suspend fun update(metadata: EventMetadata)
|
||||
suspend fun updateState(config: EventManagerConfig, eventId: EventId, state: State)
|
||||
|
||||
suspend fun get(config: EventManagerConfig): List<EventMetadata>
|
||||
suspend fun get(config: EventManagerConfig, eventId: EventId): EventMetadata?
|
||||
|
||||
suspend fun getLatestEventId(userId: UserId, endpoint: String): EventIdResponse
|
||||
suspend fun getEvents(userId: UserId, eventId: EventId, endpoint: String): EventsResponse
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.eventmanager.domain.work
|
||||
|
||||
import me.proton.core.eventmanager.domain.EventManagerConfig
|
||||
import kotlin.time.Duration
|
||||
|
||||
interface EventWorkerManager {
|
||||
|
||||
/**
|
||||
* Enqueue a Worker for this [config].
|
||||
*
|
||||
* @param immediately if true, start/process the task immediately, if possible.
|
||||
*/
|
||||
fun enqueue(config: EventManagerConfig, immediately: Boolean)
|
||||
|
||||
/**
|
||||
* Cancel Worker(s) for this [config].
|
||||
*/
|
||||
fun cancel(config: EventManagerConfig)
|
||||
|
||||
companion object {
|
||||
val REPEAT_INTERVAL_FOREGROUND = Duration.seconds(30)
|
||||
val REPEAT_INTERVAL_BACKGROUND = Duration.minutes(30)
|
||||
val BACKOFF_DELAY = REPEAT_INTERVAL_FOREGROUND
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<manifest package="me.proton.core.eventmanager" />
|
|
@ -77,6 +77,11 @@ object Module {
|
|||
const val cryptoCommon = "$crypto:crypto-common"
|
||||
const val cryptoAndroid = "$crypto:crypto-android"
|
||||
|
||||
// Account
|
||||
const val eventManager = ":event-manager"
|
||||
const val eventManagerDomain = "$eventManager:event-manager-domain"
|
||||
const val eventManagerData = "$eventManager:event-manager-data"
|
||||
|
||||
// Key
|
||||
const val key = ":key"
|
||||
const val keyDomain = "$key:key-domain"
|
||||
|
|
|
@ -60,7 +60,6 @@ internal fun initVersions() {
|
|||
|
||||
// region Android
|
||||
const val `android-tools version` = "30.0.2" // Updated: Jun, 2020
|
||||
const val `androidUi version` = "0.1.0-dev08" // Released: Apr 03, 2020
|
||||
// endregion
|
||||
|
||||
// region Other
|
||||
|
|
|
@ -23,7 +23,7 @@ plugins {
|
|||
kotlin("android")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 15, 0)
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
android()
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ plugins {
|
|||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 15, 0)
|
||||
libVersion = parent?.libVersion
|
||||
|
||||
android()
|
||||
|
||||
|
@ -38,6 +38,7 @@ dependencies {
|
|||
project(Module.network),
|
||||
project(Module.mailSettingsDomain),
|
||||
project(Module.userData),
|
||||
project(Module.eventManagerDomain),
|
||||
|
||||
// Kotlin
|
||||
`kotlin-jdk7`,
|
||||
|
@ -45,6 +46,7 @@ dependencies {
|
|||
`coroutines-core`,
|
||||
|
||||
// Other
|
||||
`hilt-android`,
|
||||
`okHttp-logging`,
|
||||
`retrofit`,
|
||||
`retrofit-kotlin-serialization`,
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.entity.Action
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
import me.proton.core.mailsettings.data.api.response.MailSettingsResponse
|
||||
import me.proton.core.mailsettings.data.db.MailSettingsDatabase
|
||||
import me.proton.core.mailsettings.data.extension.toMailSettings
|
||||
import me.proton.core.mailsettings.domain.repository.MailSettingsRepository
|
||||
import me.proton.core.util.kotlin.deserializeOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Serializable
|
||||
data class MailSettingsEvents(
|
||||
@SerialName("MailSettings")
|
||||
val settings: MailSettingsResponse
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class MailSettingsEventListener @Inject constructor(
|
||||
private val db: MailSettingsDatabase,
|
||||
private val repository: MailSettingsRepository
|
||||
) : EventListener<String, MailSettingsResponse>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 1
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, MailSettingsResponse>>? {
|
||||
return response.body.deserializeOrNull<MailSettingsEvents>()?.let {
|
||||
listOf(Event(Action.Update, "null", it.settings))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
return db.inTransaction(block)
|
||||
}
|
||||
|
||||
override suspend fun onUpdate(userId: UserId, entities: List<MailSettingsResponse>) {
|
||||
repository.updateMailSettings(entities.first().toMailSettings(userId))
|
||||
}
|
||||
|
||||
override suspend fun onResetAll(userId: UserId) {
|
||||
repository.getMailSettings(userId, refresh = true)
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ plugins {
|
|||
kotlin("jvm")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 15, 0)
|
||||
libVersion = parent?.libVersion
|
||||
|
||||
dependencies {
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ plugins {
|
|||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 17, 0)
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
android(useViewBinding = true)
|
||||
|
||||
|
@ -48,6 +48,7 @@ dependencies {
|
|||
`appcompat`,
|
||||
`constraint-layout`,
|
||||
`fragment`,
|
||||
`lifecycle-extensions`,
|
||||
`material`
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.presentation.app
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
open class AppLifecycleObserver : AppLifecycleProvider, LifecycleObserver {
|
||||
|
||||
private val mutableSharedState = MutableSharedFlow<AppLifecycleProvider.State>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.SUSPEND
|
||||
)
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
open fun onEnterForeground() {
|
||||
mutableSharedState.tryEmit(AppLifecycleProvider.State.Foreground)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
open fun onEnterBackground() {
|
||||
mutableSharedState.tryEmit(AppLifecycleProvider.State.Background)
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle by lazy {
|
||||
ProcessLifecycleOwner.get().lifecycle
|
||||
}
|
||||
|
||||
override val state: StateFlow<AppLifecycleProvider.State> by lazy {
|
||||
mutableSharedState
|
||||
.onSubscription { withContext(Dispatchers.Main) { lifecycle.addObserver(this@AppLifecycleObserver) } }
|
||||
.stateIn(lifecycle.coroutineScope, SharingStarted.Lazily, AppLifecycleProvider.State.Background)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.presentation.app
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface AppLifecycleProvider {
|
||||
val lifecycle: Lifecycle
|
||||
val state: StateFlow<State>
|
||||
|
||||
enum class State {
|
||||
Foreground,
|
||||
Background
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ plugins {
|
|||
kotlin("android")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 16, 1)
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
android()
|
||||
|
||||
|
|
|
@ -40,8 +40,10 @@ dependencies {
|
|||
project(Module.userData),
|
||||
project(Module.cryptoCommon),
|
||||
project(Module.key),
|
||||
project(Module.eventManagerDomain),
|
||||
|
||||
// Other
|
||||
`javax-inject`,
|
||||
`retrofit`,
|
||||
`retrofit-kotlin-serialization`,
|
||||
`room-ktx`,
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.entity.Action
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
import me.proton.core.usersettings.data.api.response.UserSettingsResponse
|
||||
import me.proton.core.usersettings.data.db.UserSettingsDatabase
|
||||
import me.proton.core.usersettings.data.extension.toUserSettings
|
||||
import me.proton.core.usersettings.domain.repository.UserSettingsRepository
|
||||
import me.proton.core.util.kotlin.deserializeOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Serializable
|
||||
data class UserSettingsEvents(
|
||||
@SerialName("UserSettings")
|
||||
val settings: UserSettingsResponse
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class UserSettingsEventListener @Inject constructor(
|
||||
private val db: UserSettingsDatabase,
|
||||
private val repository: UserSettingsRepository
|
||||
) : EventListener<String, UserSettingsResponse>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 1
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, UserSettingsResponse>>? {
|
||||
return response.body.deserializeOrNull<UserSettingsEvents>()?.let {
|
||||
listOf(Event(Action.Update, "null", it.settings))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
return db.inTransaction(block)
|
||||
}
|
||||
|
||||
override suspend fun onUpdate(userId: UserId, entities: List<UserSettingsResponse>) {
|
||||
repository.updateUserSettings(entities.first().toUserSettings(userId))
|
||||
}
|
||||
|
||||
override suspend fun onResetAll(userId: UserId) {
|
||||
repository.getUserSettings(userId, refresh = true)
|
||||
}
|
||||
}
|
|
@ -20,11 +20,6 @@ package me.proton.core.usersettings.data.api.response
|
|||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.usersettings.domain.entity.Flags
|
||||
import me.proton.core.usersettings.domain.entity.PasswordSetting
|
||||
import me.proton.core.usersettings.domain.entity.TwoFASetting
|
||||
import me.proton.core.usersettings.domain.entity.U2FKeySetting
|
||||
import me.proton.core.util.kotlin.toBoolean
|
||||
|
||||
@Serializable
|
||||
data class UserSettingsResponse(
|
||||
|
|
|
@ -23,7 +23,7 @@ plugins {
|
|||
kotlin("android")
|
||||
}
|
||||
|
||||
libVersion = Version(1, 16, 2)
|
||||
libVersion = Version(1, 18, 0)
|
||||
|
||||
android()
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ dependencies {
|
|||
project(Module.dataRoom),
|
||||
project(Module.domain),
|
||||
project(Module.userDomain),
|
||||
project(Module.eventManagerDomain),
|
||||
project(Module.cryptoCommon),
|
||||
|
||||
// Features
|
||||
|
@ -70,5 +71,6 @@ dependencies {
|
|||
project(Module.gopenpgp),
|
||||
project(Module.userSettings),
|
||||
project(Module.contact),
|
||||
project(Module.eventManager),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -68,7 +68,6 @@ import kotlin.test.assertEquals
|
|||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class UserAddressRepositoryImplTests {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.entity.Action
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
import me.proton.core.key.data.api.response.AddressResponse
|
||||
import me.proton.core.user.data.db.AddressDatabase
|
||||
import me.proton.core.user.data.extension.toAddress
|
||||
import me.proton.core.user.domain.entity.AddressId
|
||||
import me.proton.core.user.domain.repository.UserAddressRepository
|
||||
import me.proton.core.util.kotlin.deserializeOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Serializable
|
||||
data class UserAddressEvents(
|
||||
@SerialName("Addresses")
|
||||
val addresses: List<UserAddressEvent>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UserAddressEvent(
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
@SerialName("Action")
|
||||
val action: Int,
|
||||
@SerialName("Address")
|
||||
val address: AddressResponse
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class UserAddressEventListener @Inject constructor(
|
||||
private val db: AddressDatabase,
|
||||
private val userAddressRepository: UserAddressRepository
|
||||
) : EventListener<String, AddressResponse>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 1
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, AddressResponse>>? {
|
||||
return response.body.deserializeOrNull<UserAddressEvents>()?.addresses?.map {
|
||||
Event(requireNotNull(Action.map[it.action]), it.address.id, it.address)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
return db.inTransaction(block)
|
||||
}
|
||||
|
||||
override suspend fun onCreate(userId: UserId, entities: List<AddressResponse>) {
|
||||
userAddressRepository.updateAddresses(entities.map { it.toAddress(userId) })
|
||||
}
|
||||
|
||||
override suspend fun onUpdate(userId: UserId, entities: List<AddressResponse>) {
|
||||
userAddressRepository.updateAddresses(entities.map { it.toAddress(userId) })
|
||||
}
|
||||
|
||||
override suspend fun onDelete(userId: UserId, keys: List<String>) {
|
||||
userAddressRepository.deleteAddresses(keys.map { AddressId(it) })
|
||||
}
|
||||
|
||||
override suspend fun onResetAll(userId: UserId) {
|
||||
userAddressRepository.deleteAllAddresses(userId)
|
||||
userAddressRepository.getAddresses(userId, refresh = true)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.entity.Action
|
||||
import me.proton.core.eventmanager.domain.entity.Event
|
||||
import me.proton.core.eventmanager.domain.entity.EventsResponse
|
||||
import me.proton.core.key.data.api.response.UserResponse
|
||||
import me.proton.core.user.data.db.UserDatabase
|
||||
import me.proton.core.user.data.extension.toUser
|
||||
import me.proton.core.user.domain.repository.UserRepository
|
||||
import me.proton.core.util.kotlin.deserializeOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Serializable
|
||||
data class UserEvents(
|
||||
@SerialName("User")
|
||||
val user: UserResponse
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class UserEventListener @Inject constructor(
|
||||
private val db: UserDatabase,
|
||||
private val userRepository: UserRepository
|
||||
) : EventListener<String, UserResponse>() {
|
||||
|
||||
override val type = Type.Core
|
||||
override val order = 0
|
||||
|
||||
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, UserResponse>>? {
|
||||
return response.body.deserializeOrNull<UserEvents>()?.let {
|
||||
listOf(Event(Action.Update, it.user.id, it.user))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <R> inTransaction(block: suspend () -> R): R {
|
||||
return db.inTransaction(block)
|
||||
}
|
||||
|
||||
override suspend fun onUpdate(userId: UserId, entities: List<UserResponse>) {
|
||||
userRepository.updateUser(entities.first().toUser())
|
||||
}
|
||||
|
||||
override suspend fun onResetAll(userId: UserId) {
|
||||
userRepository.getUser(userId, refresh = true)
|
||||
}
|
||||
}
|
|
@ -49,7 +49,6 @@ import me.proton.core.user.domain.entity.UserAddress
|
|||
import me.proton.core.user.domain.entity.UserAddressKey
|
||||
import me.proton.core.user.domain.repository.UserAddressRepository
|
||||
import me.proton.core.user.domain.repository.UserRepository
|
||||
import me.proton.core.util.kotlin.takeIfNotEmpty
|
||||
|
||||
class UserAddressRepositoryImpl(
|
||||
private val db: AddressDatabase,
|
||||
|
@ -120,6 +119,9 @@ class UserAddressRepositoryImpl(
|
|||
private suspend fun delete(userId: UserId) =
|
||||
addressDao.deleteAll(userId)
|
||||
|
||||
private suspend fun deleteAll(userId: UserId) =
|
||||
addressDao.deleteAll(userId)
|
||||
|
||||
private suspend fun deleteAll() =
|
||||
addressDao.deleteAll()
|
||||
|
||||
|
@ -138,6 +140,9 @@ class UserAddressRepositoryImpl(
|
|||
override suspend fun deleteAddresses(addressIds: List<AddressId>) =
|
||||
delete(*addressIds.toTypedArray())
|
||||
|
||||
override suspend fun deleteAllAddresses(userId: UserId) =
|
||||
deleteAll(userId)
|
||||
|
||||
override fun getAddressesFlow(sessionUserId: SessionUserId, refresh: Boolean): Flow<DataResult<List<UserAddress>>> =
|
||||
store.stream(StoreRequest.cached(StoreKey(sessionUserId), refresh = refresh)).map { it.toDataResult() }
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ package me.proton.core.user.domain.repository
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.domain.entity.SessionUserId
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.user.domain.entity.AddressId
|
||||
import me.proton.core.user.domain.entity.UserAddress
|
||||
|
||||
|
@ -56,6 +57,15 @@ interface UserAddressRepository {
|
|||
addressIds: List<AddressId>
|
||||
)
|
||||
|
||||
/**
|
||||
* Delete all [UserAddress] for a given [userId], locally.
|
||||
*
|
||||
* Note: This function is usually used for Events handling.
|
||||
*/
|
||||
suspend fun deleteAllAddresses(
|
||||
userId: UserId
|
||||
)
|
||||
|
||||
/**
|
||||
* Get all [UserAddress], using [sessionUserId].
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue