Использования менеджера Tasks

This commit is contained in:
2026-04-27 00:54:37 +03:00
parent 404ff201c4
commit 2b1be58a8e
2 changed files with 169 additions and 90 deletions

View File

@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@@ -92,23 +93,32 @@ class LocalVaultViewModel @Inject constructor(
} }
fun printStorageInfoToLog(storage: IStorageInfo) { fun printStorageInfoToLog(storage: IStorageInfo) {
storageFileManagementUseCase.setStorage(storage) taskOrchestrator.enqueue(
viewModelScope.launch { title = "Dump storage to log",
val files: List<IFile> dispatcher = Dispatchers.IO,
val dirs: List<IDirectory> work = { ctx ->
val time = measureTimeMillis { storageFileManagementUseCase.setStorage(storage)
files = storageFileManagementUseCase.getAllFiles() ctx.log(TaskLogLevel.Info, "Enumerating files and directories…")
dirs = storageFileManagementUseCase.getAllDirs() val files: List<IFile>
} val dirs: List<IDirectory>
for (file in files) { val time = measureTimeMillis {
logger.debug("Files", file.metaInfo.toString()) files = storageFileManagementUseCase.getAllFiles()
} dirs = storageFileManagementUseCase.getAllDirs()
for (dir in dirs) { }
logger.debug("Dirs", dir.metaInfo.toString()) for (file in files) {
} logger.debug("Files", file.metaInfo.toString())
logger.debug("Time", "Time: $time ms") }
logger.debug("Storage", storage.toPrintable()) for (dir in dirs) {
} logger.debug("Dirs", dir.metaInfo.toString())
}
logger.debug("Time", "Time: $time ms")
logger.debug("Storage", storage.toPrintable())
ctx.log(
TaskLogLevel.Info,
"Done: ${files.size} files, ${dirs.size} dirs in ${time}ms (see app log for lines)",
)
},
)
} }
fun createStorage() { fun createStorage() {
@@ -123,71 +133,105 @@ class LocalVaultViewModel @Inject constructor(
) )
} }
private val runningStorages = mutableSetOf<java.util.UUID>() private val storageOpMutex = Any()
private val runningStorages = mutableSetOf<UUID>()
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) { fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
val id = storage.uuid val id = storage.uuid
if (runningStorages.contains(id)) synchronized(storageOpMutex) {
return if (runningStorages.contains(id)) return
tasksCount++ runningStorages.add(id)
runningStorages.add(id) tasksCount++
}
val key = EncryptKey(password) val key = EncryptKey(password)
viewModelScope.launch { taskOrchestrator.enqueue(
try { title = "Enable encryption",
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) { dispatcher = Dispatchers.IO,
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> { work = { ctx ->
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath) try {
manageStoragesEncryptionUseCase.openStorage(storage, key, true) ctx.log(TaskLogLevel.Info, "Checking storage…")
_messages.emit("Encryption enabled") when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
ctx.log(TaskLogLevel.Info, "Encrypting…")
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
ctx.log(TaskLogLevel.Info, "Encryption enabled")
_messages.emit("Encryption enabled")
}
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted -> {
ctx.log(TaskLogLevel.Info, "Storage is already encrypted")
_messages.emit("Storage is already encrypted")
}
ManageStoragesEncryptionUseCase.CanEncryptResult.StorageIsNotEmpty -> {
ctx.log(TaskLogLevel.Info, "Storage is not empty")
_messages.emit("Storage is not empty")
}
ManageStoragesEncryptionUseCase.CanEncryptResult.StorageStateUnknown -> {
ctx.log(TaskLogLevel.Info, "Cannot determine whether storage is empty")
_messages.emit("Cannot determine whether storage is empty")
}
ManageStoragesEncryptionUseCase.CanEncryptResult.UnsupportedStorageType -> {
ctx.log(TaskLogLevel.Info, "Unsupported storage type")
_messages.emit("Unsupported storage type")
}
} }
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted -> { } catch (e: Exception) {
_messages.emit("Storage is already encrypted") ctx.log(TaskLogLevel.Error, e.message ?: "Failed to enable encryption")
} _messages.emit(e.message ?: "Failed to enable encryption")
ManageStoragesEncryptionUseCase.CanEncryptResult.StorageIsNotEmpty -> { } finally {
_messages.emit("Storage is not empty") synchronized(storageOpMutex) {
} runningStorages.remove(id)
ManageStoragesEncryptionUseCase.CanEncryptResult.StorageStateUnknown -> { tasksCount--
_messages.emit("Cannot determine whether storage is empty")
}
ManageStoragesEncryptionUseCase.CanEncryptResult.UnsupportedStorageType -> {
_messages.emit("Unsupported storage type")
} }
} }
} catch (e: Exception) { },
_messages.emit(e.message ?: "Failed to enable encryption") )
}
finally {
runningStorages.remove(id)
tasksCount--
}
}
} }
fun openEncryptedStorage(storage: IStorageInfo, password: String, rememberPassword: Boolean) { fun openEncryptedStorage(storage: IStorageInfo, password: String, rememberPassword: Boolean) {
val id = storage.uuid val id = storage.uuid
if (runningStorages.contains(id)) return synchronized(storageOpMutex) {
tasksCount++ if (runningStorages.contains(id)) return
runningStorages.add(id) runningStorages.add(id)
val key = EncryptKey(password) tasksCount++
viewModelScope.launch {
try {
manageStoragesEncryptionUseCase.openStorage(storage, key, rememberPassword)
} catch (e: Exception) {
_messages.emit(e.message ?: "Failed to open encrypted storage")
} finally {
runningStorages.remove(id)
tasksCount--
}
} }
val key = EncryptKey(password)
taskOrchestrator.enqueue(
title = "Open encrypted storage",
dispatcher = Dispatchers.IO,
work = { ctx ->
try {
ctx.log(TaskLogLevel.Info, "Opening storage…")
manageStoragesEncryptionUseCase.openStorage(storage, key, rememberPassword)
ctx.log(TaskLogLevel.Info, "Storage opened")
} catch (e: Exception) {
ctx.log(TaskLogLevel.Error, e.message ?: "Failed to open encrypted storage")
_messages.emit(e.message ?: "Failed to open encrypted storage")
} finally {
synchronized(storageOpMutex) {
runningStorages.remove(id)
tasksCount--
}
}
},
)
} }
fun closeEncryptedStorage(storage: IStorageInfo) { fun closeEncryptedStorage(storage: IStorageInfo) {
viewModelScope.launch { taskOrchestrator.enqueue(
try { title = "Close encrypted storage",
manageStoragesEncryptionUseCase.closeStorage(storage) dispatcher = Dispatchers.IO,
} catch (e: Exception) { work = { ctx ->
_messages.emit(e.message ?: "Failed to close encrypted storage") try {
} ctx.log(TaskLogLevel.Info, "Closing storage…")
} manageStoragesEncryptionUseCase.closeStorage(storage)
ctx.log(TaskLogLevel.Info, "Storage closed")
} catch (e: Exception) {
ctx.log(TaskLogLevel.Error, e.message ?: "Failed to close encrypted storage")
_messages.emit(e.message ?: "Failed to close encrypted storage")
}
},
)
} }
fun disableEncryption(storage: IStorageInfo) { fun disableEncryption(storage: IStorageInfo) {
@@ -211,9 +255,19 @@ class LocalVaultViewModel @Inject constructor(
} }
fun rename(storage: IStorageInfo, newName: String) { fun rename(storage: IStorageInfo, newName: String) {
viewModelScope.launch { taskOrchestrator.enqueue(
renameStorageUseCase.rename(storage, newName) title = "Rename storage",
} dispatcher = Dispatchers.IO,
work = { ctx ->
try {
ctx.log(TaskLogLevel.Info, "Renaming…")
renameStorageUseCase.rename(storage, newName)
ctx.log(TaskLogLevel.Info, "Renamed")
} catch (e: Exception) {
ctx.log(TaskLogLevel.Error, e.message ?: "Rename failed")
}
},
)
} }
fun remove(storage: IStorageInfo) { fun remove(storage: IStorageInfo) {

View File

@@ -4,18 +4,22 @@ import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.auth.RemoteYandexSignInLauncher import com.github.nullptroma.wallenc.domain.auth.RemoteYandexSignInLauncher
import com.github.nullptroma.wallenc.domain.interfaces.IYandexVault import com.github.nullptroma.wallenc.domain.interfaces.IYandexVault
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
import com.github.nullptroma.wallenc.presentation.ViewModelBase import com.github.nullptroma.wallenc.presentation.ViewModelBase
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RemoteVaultsViewModel @Inject constructor( class RemoteVaultsViewModel @Inject constructor(
private val vaultsManager: IVaultsManager, private val vaultsManager: IVaultsManager,
val yandexSignIn: RemoteYandexSignInLauncher, val yandexSignIn: RemoteYandexSignInLauncher,
private val taskOrchestrator: ITaskOrchestrator,
) : ViewModelBase<RemoteVaultsScreenState>(RemoteVaultsScreenState()) { ) : ViewModelBase<RemoteVaultsScreenState>(RemoteVaultsScreenState()) {
val uiState = combine( val uiState = combine(
@@ -50,15 +54,25 @@ class RemoteVaultsViewModel @Inject constructor(
} }
fun onYandexAuthSuccess(accessToken: String) { fun onYandexAuthSuccess(accessToken: String) {
viewModelScope.launch { setBusy(true)
setBusy(true) taskOrchestrator.enqueue(
try { title = "Add Yandex vault",
vaultsManager.addYandexVault(accessToken) dispatcher = Dispatchers.IO,
} finally { work = { ctx ->
setBusy(false) try {
setAddChoiceVisible(false) ctx.log(TaskLogLevel.Info, "Adding vault…")
} vaultsManager.addYandexVault(accessToken)
} ctx.log(TaskLogLevel.Info, "Vault added")
} catch (e: Exception) {
ctx.log(TaskLogLevel.Error, e.message ?: "Failed to add vault")
} finally {
withContext(Dispatchers.Main.immediate) {
setBusy(false)
setAddChoiceVisible(false)
}
}
},
)
} }
fun requestDeleteVault(item: RemoteVaultListItem) { fun requestDeleteVault(item: RemoteVaultListItem) {
@@ -71,14 +85,25 @@ class RemoteVaultsViewModel @Inject constructor(
fun confirmDeleteVault() { fun confirmDeleteVault() {
val pending = state.value.vaultPendingDelete ?: return val pending = state.value.vaultPendingDelete ?: return
viewModelScope.launch { val uuid = pending.uuid
setBusy(true) setBusy(true)
try { taskOrchestrator.enqueue(
vaultsManager.removeRemoteVault(pending.uuid) title = "Remove remote vault",
} finally { dispatcher = Dispatchers.IO,
setBusy(false) work = { ctx ->
dismissDeleteVault() try {
} ctx.log(TaskLogLevel.Info, "Removing remote vault…")
} vaultsManager.removeRemoteVault(uuid)
ctx.log(TaskLogLevel.Info, "Remote vault removed")
} catch (e: Exception) {
ctx.log(TaskLogLevel.Error, e.message ?: "Failed to remove vault")
} finally {
withContext(Dispatchers.Main.immediate) {
setBusy(false)
dismissDeleteVault()
}
}
},
)
} }
} }