Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
pauljohanneskraft committed Feb 12, 2025
1 parent d6c4d95 commit 21cdb21
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 54 deletions.
24 changes: 24 additions & 0 deletions app/src/main/kotlin/edu/stanford/bdh/engagehf/di/AccountModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package edu.stanford.bdh.engagehf.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import edu.stanford.spezi.module.account.account.AccountConfiguration
import edu.stanford.spezi.module.account.firebase.account.FirebaseAccountService
import edu.stanford.spezi.module.account.firebase.account.FirebaseAuthProvider
import java.util.EnumSet
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AccountModule {

@Provides
@Singleton
fun provideAccountConfiguration() = AccountConfiguration(
service = FirebaseAccountService(
providers = EnumSet.of(FirebaseAuthProvider.EMAIL_AND_PASSWORD, FirebaseAuthProvider.SIGN_IN_WITH_GOOGLE)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import edu.stanford.spezi.core.testing.CoroutineTestRule
import edu.stanford.spezi.core.testing.coVerifyNever
import edu.stanford.spezi.core.testing.runTestUnconfined
import edu.stanford.spezi.core.utils.MessageNotifier
import edu.stanford.spezi.module.account.account.AccountEvents
import edu.stanford.spezi.module.account.AccountEvents
import edu.stanford.spezi.module.account.manager.UserSessionManager
import edu.stanford.spezi.module.account.manager.UserState
import edu.stanford.spezi.module.onboarding.OnboardingNavigationEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@ import edu.stanford.spezi.module.account.account.value.keys.password
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

class Account(
class Account internal constructor(
val accountService: AccountService,
val configuration: AccountValueConfiguration = AccountValueConfiguration.default,
standard: Standard,
externalAccountStorage: ExternalAccountStorage,
details: AccountDetails? = null,
) {
internal val logger by speziLogger()

var details: AccountDetails?
private set

val notifications = AccountNotifications()
val notifications = AccountNotifications(standard, externalAccountStorage)

var signedIn: Boolean = details != null
private set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ import edu.stanford.spezi.module.account.account.value.AccountKeys
import edu.stanford.spezi.module.account.account.value.collections.AccountDetails
import edu.stanford.spezi.module.account.account.value.configuration.AccountValueConfiguration
import edu.stanford.spezi.module.account.account.value.keys.accountId
import javax.inject.Inject
import kotlin.system.exitProcess

interface Standard

class AccountConfiguration(
service: AccountService,
standard: Standard,
private val storageProvider: AccountStorageProvider? = null,
configuration: AccountValueConfiguration = AccountValueConfiguration.default,
activeDetails: AccountDetails? = null,
) {
private val logger by speziLogger()

val externalStorage = ExternalAccountStorage(storageProvider)
val account = Account(
service,
configuration,
standard,
externalStorage,
activeDetails
)
val externalStorage = ExternalAccountStorage(storageProvider)

@Inject internal lateinit var standard: Standard

init {
verify(account.configuration, service)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package edu.stanford.spezi.module.account.account
import edu.stanford.spezi.core.logging.speziLogger
import edu.stanford.spezi.module.account.account.value.AccountKey
import edu.stanford.spezi.module.account.account.value.collections.AccountDetails
import edu.stanford.spezi.module.account.account.value.collections.AccountDetailsSerializer
import edu.stanford.spezi.module.account.account.value.collections.AccountModifications
import edu.stanford.spezi.module.account.account.value.collections.serializer
import edu.stanford.spezi.modules.storage.local.LocalStorage
import edu.stanford.spezi.modules.storage.local.LocalStorageSetting
import javax.inject.Inject
Expand All @@ -22,20 +24,24 @@ class AccountDetailsCache(
return it
}

localStorage.read("edu.stanford.spezi.account.details-cache", storageSettings) {
val details = AccountDetails()
for (key in keys) {
key.identifier
}
return localStorage.read(
key = storageKey(accountId),
settings = storageSettings,
serializer = AccountDetailsSerializer(keys = keys)
)?.also {
localCache[accountId] = it
}

return null // TODO: lead from persistency as well
}

fun clearEntry(accountId: String) {
localCache.remove(accountId)

// TODO: Delete persistence as well
@SuppressWarnings("detekt:TooGenericExceptionCaught")
try {
localStorage.delete(storageKey(accountId))
} catch (error: Throwable) {
logger.e(error) { "Failed to clear cached account details from disk." }
}
}

internal fun purgeMemoryCache(accountId: String) {
Expand All @@ -45,17 +51,31 @@ class AccountDetailsCache(
fun communicateModifications(accountId: String, modifications: AccountModifications) {
val details = AccountDetails()
localCache[accountId]?.let {
// TODO("AccountDetails.add(contentsOf:) missing!")
details.addContentsOf(it)
}
// TODO("AccountDetails.add(contentsOf:merge:) missing!")
// TODO("AccountDetails.removeAll() missing!")
details.addContentsOf(modifications.modifiedDetails, merge = true)
details.removeAll(modifications.removedAccountKeys)

communicateRemoteChanges(accountId, details)
}

fun communicateRemoteChanges(accountId: String, details: AccountDetails) {
localCache[accountId] = details

// TODO: Persistent store missing
@SuppressWarnings("detekt:TooGenericExceptionCaught")
try {
localStorage.store(
storageKey(accountId),
details,
storageSettings,
details.serializer()
)
} catch (error: Throwable) {
logger.e(error) { "Failed to clear cached account details from disk." }
}
}

private fun storageKey(accountId: String): String {
return "edu.stanford.spezi.account.details-cache.$accountId"
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
package edu.stanford.spezi.module.account.account

import edu.stanford.spezi.module.account.account.value.collections.AccountDetails
import edu.stanford.spezi.module.account.account.value.keys.accountId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.sync.Mutex

class AccountNotifications {
import kotlinx.coroutines.sync.withLock
import java.util.UUID
import javax.inject.Inject

class AccountNotifications @Inject constructor(
var standard: Standard,
var storage: ExternalAccountStorage,
) {
sealed interface Event {
data class DeletingAccount(val accountId: String) : Event
data class AssociatedAccount(val details: AccountDetails) : Event
data class DetailsChanged(val previous: AccountDetails, val new: AccountDetails) : Event
data class DisassociatingAccount(val details: AccountDetails) : Event
}

// private val standard: Standard = TODO()
// private val storage: ExternalAccountStorage = TODO()
// private val collectors = mutableMapOf<UUID, FlowCollector<Event>>()
private val collectors = mutableMapOf<UUID, FlowCollector<Event>>()
private val mutex = Mutex()

val events: Flow<Event> get() = error("")
val events: Flow<Event> get() {
val id = edu.stanford.spezi.core.utils.UUID()
return flow<Event> {
mutex.withLock {
collectors[id] = this
}
}.onCompletion {
mutex.withLock {
collectors.remove(id)
}
}
}

suspend fun reportEvent(event: Event) {
/*
(standard as? AccountNotifyConstraint)?.respondToEvent(event)

when (event) {
Expand All @@ -38,22 +56,5 @@ class AccountNotifications {
collector.emit(event)
}
}
*/
}

/*
private fun newSubscription(): Flow<Event> {
val key = UUID.randomUUID()
return flow {
mutex.withLock {
collectors[key] = this
}
}.onCompletion {
mutex.withLock {
collectors.remove(key)
}
}
}
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import kotlinx.serialization.encoding.encodeStructure
class AccountDetailsEmptySerializer : AccountDetailsSerializer(keys = emptyList())

fun AccountDetails.serializer(
keys: List<AccountKey<*>>,
keys: List<AccountKey<*>>? = null,
identifierMapping: Map<String, AccountKey<*>>? = null,
requireAllKeys: Boolean = false,
lazyDecoding: Boolean = false,
) = AccountDetailsSerializer(
keys = keys,
keys = keys ?: this.keys,
identifierMapping = identifierMapping,
requireAllKeys = requireAllKeys,
lazyDecoding = lazyDecoding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,67 @@ import edu.stanford.spezi.module.account.account.value.keys.password
import edu.stanford.spezi.module.account.account.value.keys.userId
import edu.stanford.spezi.modules.storage.local.LocalStorage
import edu.stanford.spezi.modules.storage.local.LocalStorageSetting
import io.mockk.mockk
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.Json
import org.junit.Test
import java.nio.charset.StandardCharsets

class AccountDetailsCacheTest {
private val accountId = UUID("b730ebce-e153-44fc-a547-d47ac9c9d190")
private val localStorage: LocalStorage = mockk(relaxed = true)
private val localStorage = object : LocalStorage {
private val storage = mutableMapOf<String, ByteArray>()

override fun <T : Any> store(
key: String,
value: T,
settings: LocalStorageSetting,
serializer: SerializationStrategy<T>,
) {
store(key, value, settings) {
Json.encodeToString(serializer, value).toByteArray(StandardCharsets.UTF_8)
}
}

override fun <T : Any> store(
key: String,
value: T,
settings: LocalStorageSetting,
encoding: (T) -> ByteArray,
) {
storage[key] = encoding(value)
}

override fun <T : Any> read(
key: String,
settings: LocalStorageSetting,
serializer: DeserializationStrategy<T>,
): T? {
return read(key, settings) {
Json.decodeFromString(serializer, it.toString(StandardCharsets.UTF_8))
}
}

override fun <T : Any> read(
key: String,
settings: LocalStorageSetting,
decoding: (ByteArray) -> T,
): T? {
return storage[key]?.let {
decoding(it)
}
}

override fun delete(key: String) {
storage.remove(key)
}
}

@Test
fun testCache() {
val cache = AccountDetailsCache(LocalStorageSetting.Unencrypted)
cache.localStorage = localStorage

val details = mockAccountDetails(accountId)
cache.clearEntry(details.accountId)

Expand All @@ -45,6 +96,7 @@ class AccountDetailsCacheTest {
@Test
fun testApplyModifications() {
val cache = AccountDetailsCache(LocalStorageSetting.Unencrypted)
cache.localStorage = localStorage

val details = mockAccountDetails(accountId)
val keys = details.keys
Expand Down
Loading

0 comments on commit 21cdb21

Please sign in to comment.