Добавлен Yandex
This commit is contained in:
@@ -4,16 +4,24 @@ import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.StorageMetaInfoDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.YandexAccountDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKeyMap
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageMetaInfo
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbYandexAccount
|
||||
|
||||
interface IAppDb {
|
||||
val storageKeyMapDao: StorageKeyMapDao
|
||||
val storageMetaInfoDao: StorageMetaInfoDao
|
||||
val yandexAccountDao: YandexAccountDao
|
||||
}
|
||||
|
||||
@Database(entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class], version = 3, exportSchema = false)
|
||||
@Database(
|
||||
entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class, DbYandexAccount::class],
|
||||
version = 4,
|
||||
exportSchema = false,
|
||||
)
|
||||
abstract class AppDb : IAppDb, RoomDatabase() {
|
||||
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||
abstract override val storageMetaInfoDao: StorageMetaInfoDao
|
||||
abstract override val yandexAccountDao: YandexAccountDao
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.nullptroma.wallenc.data.db.app.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbYandexAccount
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface YandexAccountDao {
|
||||
@Query("SELECT * FROM yandex_accounts WHERE yandexUserId = :id LIMIT 1")
|
||||
suspend fun getByYandexUserId(id: String): DbYandexAccount?
|
||||
|
||||
@Insert
|
||||
suspend fun insert(account: DbYandexAccount)
|
||||
|
||||
@Query(
|
||||
"UPDATE yandex_accounts SET oauthToken = :token, email = :email WHERE vaultUuid = :vaultUuid",
|
||||
)
|
||||
suspend fun updateCredentials(vaultUuid: String, email: String, token: String)
|
||||
|
||||
@Query("SELECT * FROM yandex_accounts ORDER BY email COLLATE NOCASE ASC")
|
||||
fun observeAll(): Flow<List<DbYandexAccount>>
|
||||
|
||||
@Query("DELETE FROM yandex_accounts WHERE vaultUuid = :vaultUuid")
|
||||
suspend fun deleteByVaultUuid(vaultUuid: String)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.github.nullptroma.wallenc.data.db.app.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "yandex_accounts",
|
||||
indices = [Index(value = ["yandexUserId"], unique = true)],
|
||||
)
|
||||
data class DbYandexAccount(
|
||||
@PrimaryKey val vaultUuid: String,
|
||||
val yandexUserId: String,
|
||||
val email: String,
|
||||
val oauthToken: String,
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.github.nullptroma.wallenc.data.network
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface YandexUserInfoApi {
|
||||
@GET("info")
|
||||
suspend fun userInfo(
|
||||
@Query("format") format: String,
|
||||
@Header("Authorization") authorization: String,
|
||||
): YandexUserInfoDto
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.github.nullptroma.wallenc.data.network
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||
|
||||
object YandexUserInfoApiFactory {
|
||||
fun create(): YandexUserInfoApi {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://login.yandex.ru/")
|
||||
.addConverterFactory(
|
||||
JacksonConverterFactory.create(jacksonObjectMapper().findAndRegisterModules()),
|
||||
)
|
||||
.build()
|
||||
return retrofit.create(YandexUserInfoApi::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.nullptroma.wallenc.data.network
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class YandexUserInfoDto(
|
||||
val id: String,
|
||||
val login: String,
|
||||
@JsonProperty("default_email") val defaultEmail: String? = null,
|
||||
)
|
||||
@@ -12,7 +12,7 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -33,7 +33,7 @@ class UnlockManager(
|
||||
|
||||
init {
|
||||
CoroutineScope(ioDispatcher).launch {
|
||||
vaultsManager.allStorages.collectLatest {
|
||||
vaultsManager.allStorages.collect {
|
||||
mutex.withLock {
|
||||
val allKeys = keymapRepository.getAll()
|
||||
val keysToRemove = mutableListOf<StorageKeyMap>()
|
||||
|
||||
@@ -1,33 +1,84 @@
|
||||
package com.github.nullptroma.wallenc.data.vaults
|
||||
|
||||
import android.content.Context
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.YandexAccountDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbYandexAccount
|
||||
import com.github.nullptroma.wallenc.data.db.app.repository.StorageKeyMapRepository
|
||||
import com.github.nullptroma.wallenc.data.network.YandexUserInfoApi
|
||||
import com.github.nullptroma.wallenc.data.storages.UnlockManager
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IVault
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.UUID
|
||||
|
||||
class VaultsManager(
|
||||
private val ioDispatcher: CoroutineDispatcher,
|
||||
context: Context,
|
||||
keyRepo: StorageKeyMapRepository,
|
||||
private val yandexAccountDao: YandexAccountDao,
|
||||
private val yandexUserInfoApi: YandexUserInfoApi,
|
||||
) : IVaultsManager {
|
||||
private val scope = CoroutineScope(SupervisorJob() + ioDispatcher)
|
||||
|
||||
class VaultsManager(ioDispatcher: CoroutineDispatcher, context: Context, keyRepo: StorageKeyMapRepository) : IVaultsManager {
|
||||
override val localVault = LocalVault(ioDispatcher, context)
|
||||
|
||||
// До unlockManager: UnlockManager в init подписывается на allStorages.
|
||||
override val allStorages: StateFlow<List<IStorage>> = localVault.storages
|
||||
|
||||
override val unlockManager: IUnlockManager = UnlockManager(
|
||||
keymapRepository = keyRepo,
|
||||
ioDispatcher = ioDispatcher,
|
||||
vaultsManager = this
|
||||
)
|
||||
override val remoteVaults: StateFlow<List<IVault>>
|
||||
get() = TODO("Not yet implemented")
|
||||
override val allStorages: StateFlow<List<IStorage>>
|
||||
get() = localVault.storages
|
||||
override val allVaults: StateFlow<List<IVault>>
|
||||
get() = MutableStateFlow(listOf(localVault))
|
||||
|
||||
private val _remoteVaults = yandexAccountDao.observeAll()
|
||||
.map { rows ->
|
||||
rows.map { row ->
|
||||
YandexVault(
|
||||
uuid = UUID.fromString(row.vaultUuid),
|
||||
accountEmail = row.email,
|
||||
oauthToken = row.oauthToken,
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(scope, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
override fun addYandexVault(email: String, token: String) {
|
||||
TODO("Not yet implemented")
|
||||
override val remoteVaults: StateFlow<List<IVault>> = _remoteVaults
|
||||
|
||||
override val allVaults: StateFlow<List<IVault>> = _remoteVaults
|
||||
.map { listOf(localVault) + it }
|
||||
.stateIn(scope, SharingStarted.Eagerly, listOf(localVault))
|
||||
|
||||
override suspend fun addYandexVault(accessToken: String) = withContext(ioDispatcher) {
|
||||
val info = yandexUserInfoApi.userInfo("json", "OAuth $accessToken")
|
||||
val email = info.defaultEmail?.takeIf { it.isNotBlank() }
|
||||
?: "${info.login}@yandex.ru"
|
||||
val existing = yandexAccountDao.getByYandexUserId(info.id)
|
||||
val vaultUuid = existing?.vaultUuid ?: UUID.randomUUID().toString()
|
||||
if (existing != null) {
|
||||
yandexAccountDao.updateCredentials(vaultUuid, email, accessToken)
|
||||
} else {
|
||||
yandexAccountDao.insert(
|
||||
DbYandexAccount(
|
||||
vaultUuid = vaultUuid,
|
||||
yandexUserId = info.id,
|
||||
email = email,
|
||||
oauthToken = accessToken,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
override suspend fun removeRemoteVault(vaultUuid: UUID) = withContext(ioDispatcher) {
|
||||
yandexAccountDao.deleteByVaultUuid(vaultUuid.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.github.nullptroma.wallenc.data.vaults
|
||||
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
|
||||
import com.github.nullptroma.wallenc.domain.enums.VaultType
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IYandexVault
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Удалённый vault Яндекс: OAuth и профиль есть; Yandex.Disk и хранилища — заглушки.
|
||||
*/
|
||||
class YandexVault(
|
||||
override val uuid: UUID,
|
||||
override val accountEmail: String,
|
||||
@Suppress("unused") val oauthToken: String,
|
||||
) : IYandexVault {
|
||||
override val type: VaultType = VaultType.YANDEX
|
||||
|
||||
private val _storages = MutableStateFlow<List<IStorage>>(emptyList())
|
||||
override val storages: StateFlow<List<IStorage>?> = _storages
|
||||
|
||||
private val _isAvailable = MutableStateFlow(true)
|
||||
override val isAvailable: StateFlow<Boolean> = _isAvailable
|
||||
|
||||
private val _totalSpace = MutableStateFlow<Int?>(null)
|
||||
override val totalSpace: StateFlow<Int?> = _totalSpace
|
||||
|
||||
private val _availableSpace = MutableStateFlow<Int?>(null)
|
||||
override val availableSpace: StateFlow<Int?> = _availableSpace
|
||||
|
||||
override suspend fun createStorage(): IStorage {
|
||||
throw UnsupportedOperationException("Yandex.Disk ещё не подключён")
|
||||
}
|
||||
|
||||
override suspend fun createStorage(enc: StorageEncryptionInfo): IStorage {
|
||||
throw UnsupportedOperationException("Yandex.Disk ещё не подключён")
|
||||
}
|
||||
|
||||
override suspend fun remove(storage: IStorage) {
|
||||
// заглушка до интеграции API Диска
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user