Сильно улучшен UX при работе с Yandex vault

This commit is contained in:
2026-05-21 01:40:30 +03:00
parent 9c38da76d2
commit c58bcdc35b
19 changed files with 350 additions and 118 deletions

View File

@@ -3,6 +3,7 @@ package com.github.nullptroma.wallenc.domain.vault.storages.common
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.errors.WallencException
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
@@ -70,17 +71,30 @@ abstract class BaseStorage(
}
private suspend fun readMetaInfo() = withContext(ioDispatcher) {
var meta: CommonStorageMetaInfo
var reader: InputStream? = null
try {
reader = accessor.openReadSystemFile(metaInfoFileName)
meta = jackson.readValue(reader, CommonStorageMetaInfo::class.java)
val meta = try {
accessor.openReadSystemFile(metaInfoFileName).use { input ->
val bytes = input.readBytes()
when {
bytes.isEmpty() -> {
val default = CommonStorageMetaInfo()
updateMetaInfo(default)
default
}
else -> try {
jackson.readValue(bytes, CommonStorageMetaInfo::class.java)
} catch (_: Exception) {
// Битый JSON — не перезаписываем файл на диске
CommonStorageMetaInfo()
}
}
}
} catch (_: WallencException.Storage.FileNotFound) {
val default = CommonStorageMetaInfo()
updateMetaInfo(default)
default
} catch (_: Exception) {
// чтение не удалось — пишем дефолт, чтобы файл появился
meta = CommonStorageMetaInfo()
updateMetaInfo(meta)
} finally {
reader?.close()
// Сеть/IO — оставляем дефолт в памяти, существующий файл не трогаем
CommonStorageMetaInfo()
}
_metaInfo.value = meta
}

View File

@@ -272,13 +272,7 @@ class EncryptedStorageAccessor(
override suspend fun openReadSystemFile(name: String): InputStream = scope.run {
val path = Path(systemHiddenDirName, name).pathString
return@run try {
openRead(path)
} catch (_: Exception) {
// Как у Yandex/Local: системного файла ещё нет — создаём пустой и читаем снова.
openWriteSystemFile(name).use { }
openRead(path)
}
openRead(path)
}
override suspend fun openWriteSystemFile(name: String): OutputStream = scope.run {

View File

@@ -553,11 +553,9 @@ class LocalStorageAccessor(
val dirPath = _filesystemBasePath.resolve(SYSTEM_HIDDEN_DIRNAME)
val path = dirPath.resolve(name)
val file = path.toFile()
if(!file.exists()) {
Files.createDirectories(dirPath)
file.createNewFile()
if (!file.exists()) {
throw WallencException.Storage.FileNotFound()
}
return@withContext file.inputStream()
}

View File

@@ -575,12 +575,11 @@ class YandexStorageAccessor(
override suspend fun openReadSystemFile(name: String): InputStream = withContext(ioDispatcher) {
ensureSystemDirExists()
val rel = "/$SYSTEM_HIDDEN_DIRNAME/$name"
try {
guard { repo.openDownloadStream(toDiskPath(rel)) }
} catch (_: Exception) {
// как Local: пустой файл если нет
guard { repo.uploadBytes(toDiskPath(rel), ByteArray(0), overwrite = true) }
guard { repo.openDownloadStream(toDiskPath(rel)) }
val diskPath = toDiskPath(rel)
when (guard { repo.getOrNull(diskPath) }?.type) {
"file" -> guard { repo.openDownloadStream(diskPath) }
null -> throw WallencException.Storage.FileNotFound()
else -> throw WallencException.Storage.FileNotFound()
}
}

View File

@@ -103,6 +103,17 @@ class LocalVault(
return@withContext storage
}
override suspend fun rescanStorages() = withContext(ioDispatcher) {
_storagesScanInProgress.value = true
try {
if (_isAvailable.value) {
readStorages()
}
} finally {
_storagesScanInProgress.value = false
}
}
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
val path = path.value
if (path == null || !_isAvailable.value) {

View File

@@ -13,9 +13,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.UUID
@@ -54,12 +57,20 @@ class YandexVault(
private val _availableSpace = MutableStateFlow<Long?>(null)
override val availableSpace: StateFlow<Long?> = _availableSpace
private val refreshMutex = Mutex()
init {
parentScope.launch {
runCatching { refreshFromDisk() }
}
}
override suspend fun rescanStorages() {
refreshMutex.withLock {
refreshFromDisk()
}
}
private suspend fun refreshFromDisk() {
_storagesScanInProgress.value = true
_vaultReachable.value = false
@@ -111,13 +122,23 @@ class YandexVault(
if (pending.isEmpty()) return emptyList()
return coroutineScope {
pending.map { storage ->
async(ioDispatcher) {
if (runCatching { storage.init() }.isSuccess) storage else null
}
async(ioDispatcher) { initStorageWithRetry(storage) }
}.awaitAll().filterNotNull()
}
}
private suspend fun initStorageWithRetry(storage: YandexStorage): YandexStorage? {
for (attempt in 0 until STORAGE_INIT_ATTEMPTS) {
if (attempt > 0) {
delay(STORAGE_INIT_RETRY_DELAY_MS * attempt)
}
if (runCatching { storage.init() }.isSuccess) {
return storage
}
}
return null
}
override suspend fun createStorage(): IStorage = withContext(ioDispatcher) {
val id = UUID.randomUUID()
repo.createFolder("app:/$id")
@@ -150,5 +171,7 @@ class YandexVault(
private companion object {
private const val APP_LIST_LIMIT = 1000
private const val STORAGE_INIT_ATTEMPTS = 3
private const val STORAGE_INIT_RETRY_DELAY_MS = 400L
}
}