Сильно улучшен UX при работе с Yandex vault
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user