Перенос localVault в domain-storage
This commit is contained in:
@@ -18,7 +18,6 @@ import com.github.nullptroma.wallenc.infrastructure.ports.YandexAccountStore
|
|||||||
import com.github.nullptroma.wallenc.task.runtime.TaskOrchestrator
|
import com.github.nullptroma.wallenc.task.runtime.TaskOrchestrator
|
||||||
import com.github.nullptroma.wallenc.infrastructure.vaults.VaultsManager
|
import com.github.nullptroma.wallenc.infrastructure.vaults.VaultsManager
|
||||||
import com.github.nullptroma.wallenc.infrastructure.vaults.local.LocalVault
|
import com.github.nullptroma.wallenc.infrastructure.vaults.local.LocalVault
|
||||||
import com.github.nullptroma.wallenc.infrastructure.vaults.local.LocalVaultIdStore
|
|
||||||
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
||||||
import com.github.nullptroma.wallenc.domain.interfaces.IVault
|
import com.github.nullptroma.wallenc.domain.interfaces.IVault
|
||||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||||
@@ -92,8 +91,7 @@ class SingletonModule {
|
|||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
): IVault = LocalVault(
|
): IVault = LocalVault(
|
||||||
ioDispatcher = ioDispatcher,
|
ioDispatcher = ioDispatcher,
|
||||||
context = context,
|
vaultRoot = context.getExternalFilesDir("LocalVault"),
|
||||||
idStore = LocalVaultIdStore(context),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.github.nullptroma.wallenc.infrastructure.vaults.local
|
package com.github.nullptroma.wallenc.infrastructure.vaults.local
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.github.nullptroma.wallenc.infrastructure.storages.local.LocalStorage
|
|
||||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
|
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
|
||||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||||
|
import com.github.nullptroma.wallenc.infrastructure.storages.local.LocalStorage
|
||||||
import com.github.nullptroma.wallenc.vault.contract.DescribedVault
|
import com.github.nullptroma.wallenc.vault.contract.DescribedVault
|
||||||
import com.github.nullptroma.wallenc.vault.contract.VaultDescriptor
|
import com.github.nullptroma.wallenc.vault.contract.VaultDescriptor
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -13,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.createDirectory
|
import kotlin.io.path.createDirectory
|
||||||
@@ -20,11 +20,13 @@ import kotlin.io.path.pathString
|
|||||||
|
|
||||||
class LocalVault(
|
class LocalVault(
|
||||||
private val ioDispatcher: CoroutineDispatcher,
|
private val ioDispatcher: CoroutineDispatcher,
|
||||||
context: Context,
|
private val vaultRoot: File?,
|
||||||
idStore: LocalVaultIdStore,
|
|
||||||
) : DescribedVault {
|
) : DescribedVault {
|
||||||
|
|
||||||
override val uuid: UUID = idStore.getOrCreate()
|
override val uuid: UUID = vaultRoot?.let { root ->
|
||||||
|
root.mkdirs()
|
||||||
|
readOrCreateVaultUuid(File(root, UUID_FILE_NAME))
|
||||||
|
} ?: UUID.randomUUID()
|
||||||
|
|
||||||
override val descriptor: VaultDescriptor = VaultDescriptor.LocalDevice(uuid)
|
override val descriptor: VaultDescriptor = VaultDescriptor.LocalDevice(uuid)
|
||||||
|
|
||||||
@@ -40,39 +42,42 @@ class LocalVault(
|
|||||||
private val _availableSpace = MutableStateFlow<Long?>(null)
|
private val _availableSpace = MutableStateFlow<Long?>(null)
|
||||||
override val availableSpace: StateFlow<Long?> = _availableSpace
|
override val availableSpace: StateFlow<Long?> = _availableSpace
|
||||||
|
|
||||||
private val path = MutableStateFlow<File?>(null)
|
private val path = MutableStateFlow<File?>(vaultRoot)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
CoroutineScope(ioDispatcher).launch {
|
CoroutineScope(ioDispatcher).launch {
|
||||||
path.value = context.getExternalFilesDir("LocalVault")
|
|
||||||
_isAvailable.value = path.value != null
|
_isAvailable.value = path.value != null
|
||||||
readStorages()
|
if (path.value != null) {
|
||||||
|
readStorages()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun readStorages() {
|
private suspend fun readStorages() {
|
||||||
val path = path.value
|
val path = path.value
|
||||||
if (path == null || !_isAvailable.value)
|
if (path == null || !_isAvailable.value) {
|
||||||
throw Exception("Not available")
|
throw Exception("Not available")
|
||||||
|
}
|
||||||
|
|
||||||
val dirs = path.listFiles()?.filter { it.isDirectory }
|
val dirs = path.listFiles()?.filter { it.isDirectory }
|
||||||
if (dirs != null) {
|
if (dirs != null) {
|
||||||
_storages.value = dirs.map {
|
_storages.value = dirs.map {
|
||||||
val uuid = UUID.fromString(it.name)
|
val storageUuid = UUID.fromString(it.name)
|
||||||
LocalStorage(uuid, it.path, ioDispatcher).apply { init() }
|
LocalStorage(storageUuid, it.path, ioDispatcher).apply { init() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) {
|
override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) {
|
||||||
val path = path.value
|
val path = path.value
|
||||||
if (path == null || !_isAvailable.value)
|
if (path == null || !_isAvailable.value) {
|
||||||
throw Exception("Not available")
|
throw Exception("Not available")
|
||||||
|
}
|
||||||
|
|
||||||
val uuid = UUID.randomUUID()
|
val storageUuid = UUID.randomUUID()
|
||||||
val next = Path(path.path, uuid.toString())
|
val next = Path(path.path, storageUuid.toString())
|
||||||
next.createDirectory()
|
next.createDirectory()
|
||||||
val newStorage = LocalStorage(uuid, next.pathString, ioDispatcher)
|
val newStorage = LocalStorage(storageUuid, next.pathString, ioDispatcher)
|
||||||
newStorage.init()
|
newStorage.init()
|
||||||
_storages.value = _storages.value.toMutableList().apply {
|
_storages.value = _storages.value.toMutableList().apply {
|
||||||
add(newStorage)
|
add(newStorage)
|
||||||
@@ -90,8 +95,9 @@ class LocalVault(
|
|||||||
|
|
||||||
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
|
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
|
||||||
val path = path.value
|
val path = path.value
|
||||||
if (path == null || !_isAvailable.value)
|
if (path == null || !_isAvailable.value) {
|
||||||
throw Exception("Not available")
|
throw Exception("Not available")
|
||||||
|
}
|
||||||
|
|
||||||
val curStorages = _storages.value.toMutableList()
|
val curStorages = _storages.value.toMutableList()
|
||||||
val index = curStorages.indexOfFirst { it.uuid == storage.uuid }
|
val index = curStorages.indexOfFirst { it.uuid == storage.uuid }
|
||||||
@@ -102,4 +108,39 @@ class LocalVault(
|
|||||||
File(localStorage.absolutePath).deleteRecursively()
|
File(localStorage.absolutePath).deleteRecursively()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val UUID_FILE_NAME = ".uuid"
|
||||||
|
|
||||||
|
private val uuidLock = Any()
|
||||||
|
|
||||||
|
private fun readOrCreateVaultUuid(idFile: File): UUID = synchronized(uuidLock) {
|
||||||
|
if (idFile.exists()) {
|
||||||
|
idFile.bufferedReader().use { reader ->
|
||||||
|
val line = reader.readLine()?.trim()
|
||||||
|
if (!line.isNullOrEmpty()) {
|
||||||
|
runCatching { UUID.fromString(line) }.getOrNull()?.let { return@synchronized it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val generated = UUID.randomUUID()
|
||||||
|
val parent = idFile.parentFile ?: throw IllegalStateException("No parent for $idFile")
|
||||||
|
parent.mkdirs()
|
||||||
|
val tmp = File.createTempFile("vault-uuid-", ".tmp", parent)
|
||||||
|
try {
|
||||||
|
FileOutputStream(tmp).use { fos ->
|
||||||
|
fos.write(generated.toString().toByteArray(Charsets.UTF_8))
|
||||||
|
fos.fd.sync()
|
||||||
|
}
|
||||||
|
if (!tmp.renameTo(idFile)) {
|
||||||
|
tmp.copyTo(idFile, overwrite = true)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (tmp.exists()) {
|
||||||
|
tmp.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@synchronized generated
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.github.nullptroma.wallenc.infrastructure.vaults.local
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Хранит/восстанавливает идентификатор единственного локального vault'а
|
|
||||||
* в [android.content.SharedPreferences]. При первом обращении генерирует новый UUID
|
|
||||||
* и записывает его синхронно (`commit`), чтобы к моменту, когда другие подсистемы
|
|
||||||
* (DB, шифр-ключи) начнут связывать с ним записи, значение уже было персистентно.
|
|
||||||
*/
|
|
||||||
class LocalVaultIdStore(context: Context) {
|
|
||||||
|
|
||||||
private val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
/** Возвращает существующий идентификатор или, если его нет, генерирует и сохраняет новый. */
|
|
||||||
fun getOrCreate(): UUID {
|
|
||||||
prefs.getString(KEY_LOCAL_VAULT_UUID, null)?.let { stored ->
|
|
||||||
runCatching { UUID.fromString(stored) }.getOrNull()?.let { return it }
|
|
||||||
}
|
|
||||||
val generated = UUID.randomUUID()
|
|
||||||
prefs.edit().putString(KEY_LOCAL_VAULT_UUID, generated.toString()).commit()
|
|
||||||
return generated
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val PREFS_NAME = "wallenc.vaults"
|
|
||||||
const val KEY_LOCAL_VAULT_UUID = "local_vault_uuid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user