Опциональное шифрование имён файлов
This commit is contained in:
@@ -53,12 +53,10 @@ class SingletonModule {
|
|||||||
fun provideUnlockManager(
|
fun provideUnlockManager(
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
keyRepo: StorageKeyMapRepository,
|
keyRepo: StorageKeyMapRepository,
|
||||||
metaRepo: StorageMetaInfoRepository,
|
|
||||||
vaultsManager: IVaultsManager
|
vaultsManager: IVaultsManager
|
||||||
): IUnlockManager {
|
): IUnlockManager {
|
||||||
return UnlockManager(
|
return UnlockManager(
|
||||||
keymapRepository = keyRepo,
|
keymapRepository = keyRepo,
|
||||||
metaInfoRepository = metaRepo,
|
|
||||||
ioDispatcher = ioDispatcher,
|
ioDispatcher = ioDispatcher,
|
||||||
vaultsManager = vaultsManager
|
vaultsManager = vaultsManager
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
|||||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
|
||||||
|
import com.github.nullptroma.wallenc.domain.usecases.ManageStoragesEncryptionUseCase
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@@ -38,4 +39,10 @@ class UseCasesModule {
|
|||||||
fun provideRenameStorageUseCase(): RenameStorageUseCase {
|
fun provideRenameStorageUseCase(): RenameStorageUseCase {
|
||||||
return RenameStorageUseCase()
|
return RenameStorageUseCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideManageStoragesEncryptionUseCase(unlockManager: IUnlockManager): ManageStoragesEncryptionUseCase {
|
||||||
|
return ManageStoragesEncryptionUseCase(unlockManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
|||||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@@ -22,7 +23,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
class UnlockManager(
|
class UnlockManager(
|
||||||
private val keymapRepository: StorageKeyMapRepository,
|
private val keymapRepository: StorageKeyMapRepository,
|
||||||
private val metaInfoRepository: StorageMetaInfoRepository,
|
|
||||||
private val ioDispatcher: CoroutineDispatcher,
|
private val ioDispatcher: CoroutineDispatcher,
|
||||||
vaultsManager: IVaultsManager
|
vaultsManager: IVaultsManager
|
||||||
) : IUnlockManager {
|
) : IUnlockManager {
|
||||||
@@ -36,23 +36,32 @@ class UnlockManager(
|
|||||||
vaultsManager.allStorages.collectLatest {
|
vaultsManager.allStorages.collectLatest {
|
||||||
mutex.lock()
|
mutex.lock()
|
||||||
val allKeys = keymapRepository.getAll()
|
val allKeys = keymapRepository.getAll()
|
||||||
|
val usedKeys = mutableListOf<StorageKeyMap>()
|
||||||
val keysToRemove = mutableListOf<StorageKeyMap>()
|
val keysToRemove = mutableListOf<StorageKeyMap>()
|
||||||
val allStorages = it.associateBy({ it.uuid }, { it })
|
val allStorages = it.toMutableList()
|
||||||
val map = _openedStorages.value?.toMutableMap() ?: mutableMapOf()
|
val map = _openedStorages.value?.toMutableMap() ?: mutableMapOf()
|
||||||
for(keymap in allKeys) {
|
while(allStorages.size > 0) {
|
||||||
if(map.contains(keymap.sourceUuid))
|
val storage = allStorages[allStorages.size-1]
|
||||||
|
val key = allKeys.find { key -> key.sourceUuid == storage.uuid }
|
||||||
|
if(key == null) {
|
||||||
|
allStorages.removeAt(allStorages.size - 1)
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val storage = allStorages[keymap.sourceUuid] ?: continue
|
val encStorage = createEncryptedStorage(storage, key.key, key.destUuid)
|
||||||
val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid)
|
|
||||||
map[storage.uuid] = encStorage
|
map[storage.uuid] = encStorage
|
||||||
|
usedKeys.add(key)
|
||||||
|
allStorages.removeAt(allStorages.size - 1)
|
||||||
|
allStorages.add(encStorage)
|
||||||
}
|
}
|
||||||
catch (_: Exception) {
|
catch (_: Exception) {
|
||||||
keysToRemove.add(keymap)
|
// ключ не подошёл
|
||||||
|
keysToRemove.add(key)
|
||||||
|
allStorages.removeAt(allStorages.size - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_openedStorages.value = map
|
|
||||||
keymapRepository.delete(*keysToRemove.toTypedArray()) // удалить мёртвые ключи
|
keymapRepository.delete(*keysToRemove.toTypedArray()) // удалить мёртвые ключи
|
||||||
|
_openedStorages.value = map.toMap()
|
||||||
mutex.unlock()
|
mutex.unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +72,6 @@ class UnlockManager(
|
|||||||
source = storage,
|
source = storage,
|
||||||
key = key,
|
key = key,
|
||||||
ioDispatcher = ioDispatcher,
|
ioDispatcher = ioDispatcher,
|
||||||
metaInfoProvider = metaInfoRepository.createSingleStorageProvider(uuid),
|
|
||||||
uuid = uuid
|
uuid = uuid
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -71,7 +79,7 @@ class UnlockManager(
|
|||||||
override suspend fun open(
|
override suspend fun open(
|
||||||
storage: IStorage,
|
storage: IStorage,
|
||||||
key: EncryptKey
|
key: EncryptKey
|
||||||
) = withContext(ioDispatcher) {
|
): EncryptedStorage = withContext(ioDispatcher) {
|
||||||
mutex.lock()
|
mutex.lock()
|
||||||
val encInfo = storage.metaInfo.value.encInfo ?: throw Exception("EncInfo is null") // TODO
|
val encInfo = storage.metaInfo.value.encInfo ?: throw Exception("EncInfo is null") // TODO
|
||||||
if (!Encryptor.checkKey(key, encInfo))
|
if (!Encryptor.checkKey(key, encInfo))
|
||||||
@@ -92,6 +100,7 @@ class UnlockManager(
|
|||||||
_openedStorages.value = opened
|
_openedStorages.value = opened
|
||||||
keymapRepository.add(keymap)
|
keymapRepository.add(keymap)
|
||||||
mutex.unlock()
|
mutex.unlock()
|
||||||
|
return@withContext encStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun close(storage: IStorage) = withContext(ioDispatcher) {
|
override suspend fun close(storage: IStorage) = withContext(ioDispatcher) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.github.nullptroma.wallenc.data.storages.encrypt
|
package com.github.nullptroma.wallenc.data.storages.encrypt
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.github.nullptroma.wallenc.data.db.app.repository.StorageMetaInfoRepository
|
import com.github.nullptroma.wallenc.data.db.app.repository.StorageMetaInfoRepository
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo
|
||||||
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
|
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
|
||||||
@@ -13,24 +14,27 @@ import kotlinx.coroutines.DisposableHandle
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.InputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class EncryptedStorage private constructor(
|
class EncryptedStorage private constructor(
|
||||||
private val source: IStorage,
|
private val source: IStorage,
|
||||||
private val key: EncryptKey,
|
private val key: EncryptKey,
|
||||||
ioDispatcher: CoroutineDispatcher,
|
ioDispatcher: CoroutineDispatcher,
|
||||||
private val metaInfoProvider: StorageMetaInfoRepository.SingleStorageMetaInfoProvider,
|
|
||||||
override val uuid: UUID = UUID.randomUUID()
|
override val uuid: UUID = UUID.randomUUID()
|
||||||
) : IStorage, DisposableHandle {
|
) : IStorage, DisposableHandle {
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
private val scope = CoroutineScope(ioDispatcher + job)
|
private val scope = CoroutineScope(ioDispatcher + job)
|
||||||
private val encInfo = source.metaInfo.value.encInfo ?: throw Exception("Storage is not encrypted") // TODO
|
private val encInfo =
|
||||||
|
source.metaInfo.value.encInfo ?: throw Exception("Storage is not encrypted") // TODO
|
||||||
|
private val metaInfoFileName: String = "${uuid.toString().take(8)}$STORAGE_INFO_FILE_POSTFIX"
|
||||||
|
|
||||||
override val size: StateFlow<Long?>
|
override val size: StateFlow<Long?>
|
||||||
get() = source.size
|
get() = accessor.size
|
||||||
override val numberOfFiles: StateFlow<Int?>
|
override val numberOfFiles: StateFlow<Int?>
|
||||||
get() = source.numberOfFiles
|
get() = accessor.numberOfFiles
|
||||||
|
|
||||||
private val _metaInfo = MutableStateFlow<IStorageMetaInfo>(
|
private val _metaInfo = MutableStateFlow<IStorageMetaInfo>(
|
||||||
CommonStorageMetaInfo()
|
CommonStorageMetaInfo()
|
||||||
@@ -42,11 +46,11 @@ class EncryptedStorage private constructor(
|
|||||||
override val isAvailable: StateFlow<Boolean>
|
override val isAvailable: StateFlow<Boolean>
|
||||||
get() = source.isAvailable
|
get() = source.isAvailable
|
||||||
override val accessor: EncryptedStorageAccessor =
|
override val accessor: EncryptedStorageAccessor =
|
||||||
EncryptedStorageAccessor(source.accessor, encInfo.pathIv, key, scope)
|
EncryptedStorageAccessor(source.accessor, encInfo.pathIv, key, "${uuid.toString().take(8)}$SYSTEM_HIDDEN_DIRNAME_POSTFIX", scope)
|
||||||
|
|
||||||
private suspend fun init() {
|
private suspend fun init() {
|
||||||
checkKey()
|
checkKey()
|
||||||
readMeta()
|
readMetaInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkKey() {
|
private fun checkKey() {
|
||||||
@@ -54,33 +58,52 @@ class EncryptedStorage private constructor(
|
|||||||
throw Exception("Incorrect key") // TODO
|
throw Exception("Incorrect key") // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun readMeta() = scope.run {
|
private suspend fun readMetaInfo() = scope.run {
|
||||||
var meta = metaInfoProvider.get()
|
var meta: CommonStorageMetaInfo
|
||||||
if(meta == null) {
|
var reader: InputStream? = null
|
||||||
|
try {
|
||||||
|
reader = accessor.openReadSystemFile(metaInfoFileName)
|
||||||
|
meta = jackson.readValue(reader, CommonStorageMetaInfo::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// чтение не удалось, значит нужно записать файл
|
||||||
meta = CommonStorageMetaInfo()
|
meta = CommonStorageMetaInfo()
|
||||||
metaInfoProvider.set(meta)
|
updateMetaInfo(meta)
|
||||||
|
} finally {
|
||||||
|
reader?.close()
|
||||||
|
}
|
||||||
|
_metaInfo.value = meta
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateMetaInfo(meta: IStorageMetaInfo) = scope.run {
|
||||||
|
val writer = accessor.openWriteSystemFile(metaInfoFileName)
|
||||||
|
try {
|
||||||
|
jackson.writeValue(writer, meta)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
writer.close()
|
||||||
}
|
}
|
||||||
_metaInfo.value = meta
|
_metaInfo.value = meta
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun rename(newName: String) = scope.run {
|
override suspend fun rename(newName: String) = scope.run {
|
||||||
val cur = _metaInfo.value
|
val curMeta = metaInfo.value
|
||||||
val newMeta = CommonStorageMetaInfo(
|
updateMetaInfo(
|
||||||
encInfo = cur.encInfo,
|
CommonStorageMetaInfo(
|
||||||
|
encInfo = curMeta.encInfo,
|
||||||
name = newName
|
name = newName
|
||||||
)
|
)
|
||||||
_metaInfo.value = newMeta
|
)
|
||||||
metaInfoProvider.set(newMeta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = scope.run {
|
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = scope.run {
|
||||||
val cur = _metaInfo.value
|
val curMeta = metaInfo.value
|
||||||
val newMeta = CommonStorageMetaInfo(
|
updateMetaInfo(
|
||||||
|
CommonStorageMetaInfo(
|
||||||
encInfo = encInfo,
|
encInfo = encInfo,
|
||||||
name = cur.name
|
name = curMeta.name
|
||||||
|
)
|
||||||
)
|
)
|
||||||
_metaInfo.value = newMeta
|
|
||||||
metaInfoProvider.set(newMeta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
@@ -93,24 +116,25 @@ class EncryptedStorage private constructor(
|
|||||||
source: IStorage,
|
source: IStorage,
|
||||||
key: EncryptKey,
|
key: EncryptKey,
|
||||||
ioDispatcher: CoroutineDispatcher,
|
ioDispatcher: CoroutineDispatcher,
|
||||||
metaInfoProvider: StorageMetaInfoRepository.SingleStorageMetaInfoProvider,
|
|
||||||
uuid: UUID = UUID.randomUUID()
|
uuid: UUID = UUID.randomUUID()
|
||||||
): EncryptedStorage = withContext(ioDispatcher) {
|
): EncryptedStorage = withContext(ioDispatcher) {
|
||||||
val storage = EncryptedStorage(
|
val storage = EncryptedStorage(
|
||||||
source = source,
|
source = source,
|
||||||
key = key,
|
key = key,
|
||||||
ioDispatcher = ioDispatcher,
|
ioDispatcher = ioDispatcher,
|
||||||
metaInfoProvider = metaInfoProvider,
|
|
||||||
uuid = uuid
|
uuid = uuid
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
storage.init()
|
storage.init()
|
||||||
}
|
} catch (e: Exception) {
|
||||||
catch (e: Exception) {
|
|
||||||
storage.dispose()
|
storage.dispose()
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
return@withContext storage
|
return@withContext storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SYSTEM_HIDDEN_DIRNAME_POSTFIX = "-enc-dir"
|
||||||
|
const val STORAGE_INFO_FILE_POSTFIX = ".enc-meta"
|
||||||
|
private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.github.nullptroma.wallenc.data.storages.encrypt
|
package com.github.nullptroma.wallenc.data.storages.encrypt
|
||||||
|
|
||||||
import android.util.Log
|
import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosed
|
||||||
|
import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosing
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonFile
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonFile
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo
|
||||||
@@ -16,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.DisposableHandle
|
import kotlinx.coroutines.DisposableHandle
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -27,12 +29,17 @@ import kotlin.io.path.pathString
|
|||||||
|
|
||||||
class EncryptedStorageAccessor(
|
class EncryptedStorageAccessor(
|
||||||
private val source: IStorageAccessor,
|
private val source: IStorageAccessor,
|
||||||
pathIv: ByteArray,
|
pathIv: ByteArray?,
|
||||||
key: EncryptKey,
|
key: EncryptKey,
|
||||||
|
private val systemHiddenDirName: String,
|
||||||
private val scope: CoroutineScope
|
private val scope: CoroutineScope
|
||||||
) : IStorageAccessor, DisposableHandle {
|
) : IStorageAccessor, DisposableHandle {
|
||||||
override val size: StateFlow<Long?> = source.size
|
private val _size = MutableStateFlow<Long?>(null)
|
||||||
override val numberOfFiles: StateFlow<Int?> = source.numberOfFiles
|
override val size: StateFlow<Long?> = _size
|
||||||
|
|
||||||
|
private val _numberOfFiles = MutableStateFlow<Int?>(null)
|
||||||
|
override val numberOfFiles: StateFlow<Int?> = _numberOfFiles
|
||||||
|
|
||||||
override val isAvailable: StateFlow<Boolean> = source.isAvailable
|
override val isAvailable: StateFlow<Boolean> = source.isAvailable
|
||||||
|
|
||||||
private val _filesUpdates = MutableSharedFlow<DataPackage<List<IFile>>>()
|
private val _filesUpdates = MutableSharedFlow<DataPackage<List<IFile>>>()
|
||||||
@@ -42,44 +49,68 @@ class EncryptedStorageAccessor(
|
|||||||
override val dirsUpdates: SharedFlow<DataPackage<List<IDirectory>>> = _dirsUpdates
|
override val dirsUpdates: SharedFlow<DataPackage<List<IDirectory>>> = _dirsUpdates
|
||||||
|
|
||||||
private val dataEncryptor = Encryptor(key.toAesKey())
|
private val dataEncryptor = Encryptor(key.toAesKey())
|
||||||
private val pathEncryptor = EncryptorWithStaticIv(key.toAesKey(), pathIv)
|
private val pathEncryptor: EncryptorWithStaticIv? = if(pathIv != null) EncryptorWithStaticIv(key.toAesKey(), pathIv) else null
|
||||||
|
|
||||||
|
private var systemHiddenFiles: List<IFile>? = null
|
||||||
|
private var systemHiddenFilesIsActual = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
collectSourceState()
|
collectSourceState()
|
||||||
|
|
||||||
for(i in 1..5) {
|
|
||||||
val orig = "/hello/path/test.txt"
|
|
||||||
val enc = encryptPath(orig)
|
|
||||||
val dec = decryptPath(enc)
|
|
||||||
|
|
||||||
Log.d("MyTag", "Path $orig to $enc to $dec")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectSourceState() {
|
private fun collectSourceState() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
launch {
|
launch {
|
||||||
source.filesUpdates.collect {
|
source.filesUpdates.collect {
|
||||||
val files = it.data.map(::decryptEntity)
|
val files = it.data.map(::decryptEntity).filterSystemHiddenFiles()
|
||||||
_filesUpdates.emit(DataPackage(
|
_filesUpdates.emit(
|
||||||
|
DataPackage(
|
||||||
data = files,
|
data = files,
|
||||||
isLoading = it.isLoading,
|
isLoading = it.isLoading,
|
||||||
isError = it.isError
|
isError = it.isError
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
source.dirsUpdates.collect {
|
source.dirsUpdates.collect {
|
||||||
val dirs = it.data.map(::decryptEntity)
|
val dirs = it.data.map(::decryptEntity).filterSystemHiddenDirs()
|
||||||
_dirsUpdates.emit(DataPackage(
|
_dirsUpdates.emit(
|
||||||
|
DataPackage(
|
||||||
data = dirs,
|
data = dirs,
|
||||||
isLoading = it.isLoading,
|
isLoading = it.isLoading,
|
||||||
isError = it.isError
|
isError = it.isError
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
source.numberOfFiles.collect {
|
||||||
|
if(it == null)
|
||||||
|
_numberOfFiles.value = null
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_numberOfFiles.value = it - getSystemFiles().size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
source.size.collect { sourceSize ->
|
||||||
|
if(sourceSize == null)
|
||||||
|
_size.value = null
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_size.value = sourceSize - getSystemFiles().sumOf { it.metaInfo.size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getSystemFiles(): List<IFile> {
|
||||||
|
return source.getFiles(encryptPath(systemHiddenDirName))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encryptEntity(file: IFile): IFile {
|
private fun encryptEntity(file: IFile): IFile {
|
||||||
@@ -119,6 +150,8 @@ class EncryptedStorageAccessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun encryptPath(pathStr: String): String {
|
private fun encryptPath(pathStr: String): String {
|
||||||
|
if(pathEncryptor == null)
|
||||||
|
return pathStr
|
||||||
val path = Path(pathStr)
|
val path = Path(pathStr)
|
||||||
val segments = mutableListOf<String>()
|
val segments = mutableListOf<String>()
|
||||||
for (segment in path)
|
for (segment in path)
|
||||||
@@ -128,6 +161,9 @@ class EncryptedStorageAccessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptPath(pathStr: String): String {
|
private fun decryptPath(pathStr: String): String {
|
||||||
|
if(pathEncryptor == null)
|
||||||
|
return pathStr
|
||||||
|
|
||||||
val path = Path(pathStr)
|
val path = Path(pathStr)
|
||||||
val segments = mutableListOf<String>()
|
val segments = mutableListOf<String>()
|
||||||
for (segment in path)
|
for (segment in path)
|
||||||
@@ -137,17 +173,17 @@ class EncryptedStorageAccessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAllFiles(): List<IFile> {
|
override suspend fun getAllFiles(): List<IFile> {
|
||||||
return source.getAllFiles().map(::decryptEntity)
|
return source.getAllFiles().map(::decryptEntity).filterSystemHiddenFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getFiles(path: String): List<IFile> {
|
override suspend fun getFiles(path: String): List<IFile> {
|
||||||
return source.getFiles(encryptPath(path)).map(::decryptEntity)
|
return source.getFiles(encryptPath(path)).map(::decryptEntity).filterSystemHiddenFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilesFlow(path: String): Flow<DataPackage<List<IFile>>> {
|
override fun getFilesFlow(path: String): Flow<DataPackage<List<IFile>>> {
|
||||||
val flow = source.getFilesFlow(encryptPath(path)).map {
|
val flow = source.getFilesFlow(encryptPath(path)).map {
|
||||||
DataPackage(
|
DataPackage(
|
||||||
data = it.data.map(::decryptEntity),
|
data = it.data.map(::decryptEntity).filterSystemHiddenFiles(),
|
||||||
isLoading = it.isLoading,
|
isLoading = it.isLoading,
|
||||||
isError = it.isError
|
isError = it.isError
|
||||||
)
|
)
|
||||||
@@ -156,17 +192,18 @@ class EncryptedStorageAccessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAllDirs(): List<IDirectory> {
|
override suspend fun getAllDirs(): List<IDirectory> {
|
||||||
return source.getAllDirs().map(::decryptEntity)
|
return source.getAllDirs().map(::decryptEntity).filterSystemHiddenDirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getDirs(path: String): List<IDirectory> {
|
override suspend fun getDirs(path: String): List<IDirectory> {
|
||||||
return source.getDirs(encryptPath(path)).map(::decryptEntity)
|
return source.getDirs(encryptPath(path)).map(::decryptEntity).filterSystemHiddenDirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDirsFlow(path: String): Flow<DataPackage<List<IDirectory>>> {
|
override fun getDirsFlow(path: String): Flow<DataPackage<List<IDirectory>>> {
|
||||||
val flow = source.getDirsFlow(encryptPath(path)).map {
|
val flow = source.getDirsFlow(encryptPath(path)).map {
|
||||||
DataPackage(
|
DataPackage(
|
||||||
data = it.data.map(::decryptEntity),
|
// включать все папки, кроме системной
|
||||||
|
data = it.data.map(::decryptEntity).filterSystemHiddenDirs(),
|
||||||
isLoading = it.isLoading,
|
isLoading = it.isLoading,
|
||||||
isError = it.isError
|
isError = it.isError
|
||||||
)
|
)
|
||||||
@@ -220,4 +257,33 @@ class EncryptedStorageAccessor(
|
|||||||
dataEncryptor.dispose()
|
dataEncryptor.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun openReadSystemFile(name: String): InputStream = scope.run {
|
||||||
|
val path = Path(systemHiddenDirName, name).pathString
|
||||||
|
return@run openRead(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun openWriteSystemFile(name: String): OutputStream = scope.run {
|
||||||
|
val path = Path(systemHiddenDirName, name).pathString
|
||||||
|
systemHiddenFilesIsActual = false
|
||||||
|
return@run openWrite(path).onClosing {
|
||||||
|
systemHiddenFilesIsActual = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Iterable<IFile>.filterSystemHiddenFiles(): List<IFile> {
|
||||||
|
return this.filter { file ->
|
||||||
|
!file.metaInfo.path.contains(
|
||||||
|
systemHiddenDirName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Iterable<IDirectory>.filterSystemHiddenDirs(): List<IDirectory> {
|
||||||
|
return this.filter { dir ->
|
||||||
|
!dir.metaInfo.path.contains(
|
||||||
|
systemHiddenDirName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package com.github.nullptroma.wallenc.data.storages.local
|
|||||||
import com.fasterxml.jackson.core.JacksonException
|
import com.fasterxml.jackson.core.JacksonException
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClose
|
import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosed
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonFile
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonFile
|
||||||
import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo
|
import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo
|
||||||
@@ -433,6 +433,8 @@ class LocalStorageAccessor(
|
|||||||
if (file.exists() && file.isDirectory) {
|
if (file.exists() && file.isDirectory) {
|
||||||
throw Exception("Что то пошло не так") // TODO
|
throw Exception("Что то пошло не так") // TODO
|
||||||
} else if(!file.exists()) {
|
} else if(!file.exists()) {
|
||||||
|
val parent = Path(storagePath).parent
|
||||||
|
createDir(parent.pathString)
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
|
|
||||||
val cur = _numberOfFiles.value
|
val cur = _numberOfFiles.value
|
||||||
@@ -441,7 +443,7 @@ class LocalStorageAccessor(
|
|||||||
|
|
||||||
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
|
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
|
||||||
?: throw Exception("Что то пошло не так") // TODO
|
?: throw Exception("Что то пошло не так") // TODO
|
||||||
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant())
|
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant(), size = Files.size(pair.file.toPath()))
|
||||||
writeMeta(pair.metaFile, newMeta)
|
writeMeta(pair.metaFile, newMeta)
|
||||||
_filesUpdates.emit(
|
_filesUpdates.emit(
|
||||||
DataPage(
|
DataPage(
|
||||||
@@ -503,9 +505,10 @@ class LocalStorageAccessor(
|
|||||||
touchFile(path)
|
touchFile(path)
|
||||||
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
|
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
|
||||||
?: throw Exception("Файла нет") // TODO
|
?: throw Exception("Файла нет") // TODO
|
||||||
return@withContext pair.file.outputStream().onClose {
|
return@withContext pair.file.outputStream().onClosed {
|
||||||
CoroutineScope(ioDispatcher).launch {
|
CoroutineScope(ioDispatcher).launch {
|
||||||
touchFile(path)
|
touchFile(path)
|
||||||
|
scanSizeAndNumOfFiles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.io.OutputStream
|
|||||||
|
|
||||||
private class CloseHandledOutputStream(
|
private class CloseHandledOutputStream(
|
||||||
private val stream: OutputStream,
|
private val stream: OutputStream,
|
||||||
|
private val onClosing: () -> Unit,
|
||||||
private val onClose: () -> Unit
|
private val onClose: () -> Unit
|
||||||
) : OutputStream() {
|
) : OutputStream() {
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ private class CloseHandledOutputStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
onClosing()
|
||||||
try {
|
try {
|
||||||
stream.close()
|
stream.close()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -35,6 +37,7 @@ private class CloseHandledOutputStream(
|
|||||||
|
|
||||||
private class CloseHandledInputStream(
|
private class CloseHandledInputStream(
|
||||||
private val stream: InputStream,
|
private val stream: InputStream,
|
||||||
|
private val onClosing: () -> Unit,
|
||||||
private val onClose: () -> Unit
|
private val onClose: () -> Unit
|
||||||
) : InputStream() {
|
) : InputStream() {
|
||||||
|
|
||||||
@@ -59,6 +62,7 @@ private class CloseHandledInputStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
onClosing()
|
||||||
try {
|
try {
|
||||||
stream.close()
|
stream.close()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -81,12 +85,20 @@ private class CloseHandledInputStream(
|
|||||||
|
|
||||||
class CloseHandledStreamExtension {
|
class CloseHandledStreamExtension {
|
||||||
companion object {
|
companion object {
|
||||||
fun OutputStream.onClose(callback: ()->Unit): OutputStream {
|
fun OutputStream.onClosed(callback: ()->Unit): OutputStream {
|
||||||
return CloseHandledOutputStream(this, callback)
|
return CloseHandledOutputStream(this, {}, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun InputStream.onClose(callback: ()->Unit): InputStream {
|
fun InputStream.onClosed(callback: ()->Unit): InputStream {
|
||||||
return CloseHandledInputStream(this, callback)
|
return CloseHandledInputStream(this, {}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun OutputStream.onClosing(callback: ()->Unit): OutputStream {
|
||||||
|
return CloseHandledOutputStream(this, callback, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InputStream.onClosing(callback: ()->Unit): InputStream {
|
||||||
|
return CloseHandledInputStream(this, callback, {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ package com.github.nullptroma.wallenc.domain.datatypes
|
|||||||
|
|
||||||
data class StorageEncryptionInfo(
|
data class StorageEncryptionInfo(
|
||||||
val encryptedTestData: String,
|
val encryptedTestData: String,
|
||||||
val pathIv: ByteArray
|
val pathIv: ByteArray?
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class Encryptor(private var secretKey: SecretKey) : DisposableHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
secretKey.destroy()
|
//secretKey.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -83,13 +83,13 @@ class Encryptor(private var secretKey: SecretKey) : DisposableHandle {
|
|||||||
private const val TEST_DATA_LEN = 512
|
private const val TEST_DATA_LEN = 512
|
||||||
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
fun generateEncryptionInfo(key: EncryptKey) : StorageEncryptionInfo {
|
fun generateEncryptionInfo(key: EncryptKey, encryptPath: Boolean) : StorageEncryptionInfo {
|
||||||
val encryptor = Encryptor(key.toAesKey())
|
val encryptor = Encryptor(key.toAesKey())
|
||||||
val testData = ByteArray(TEST_DATA_LEN)
|
val testData = ByteArray(TEST_DATA_LEN)
|
||||||
val encryptedData = encryptor.encryptBytes(testData)
|
val encryptedData = encryptor.encryptBytes(testData)
|
||||||
return StorageEncryptionInfo(
|
return StorageEncryptionInfo(
|
||||||
encryptedTestData = Base64.Default.encode(encryptedData),
|
encryptedTestData = Base64.Default.encode(encryptedData),
|
||||||
pathIv = Random.nextBytes(IV_LEN)
|
pathIv = if(encryptPath) Random.nextBytes(IV_LEN) else null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ interface IUnlockManager {
|
|||||||
*/
|
*/
|
||||||
val openedStorages: StateFlow<Map<UUID, IStorage>?>
|
val openedStorages: StateFlow<Map<UUID, IStorage>?>
|
||||||
|
|
||||||
suspend fun open(storage: IStorage, key: EncryptKey)
|
suspend fun open(storage: IStorage, key: EncryptKey): IStorage
|
||||||
suspend fun close(storage: IStorage)
|
suspend fun close(storage: IStorage)
|
||||||
}
|
}
|
||||||
@@ -17,12 +17,6 @@ class ManageLocalVaultUseCase(private val manager: IVaultsManager, private val u
|
|||||||
manager.localVault.createStorage()
|
manager.localVault.createStorage()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createStorage(key: EncryptKey) {
|
|
||||||
val encInfo = Encryptor.generateEncryptionInfo(key)
|
|
||||||
val storage = manager.localVault.createStorage(encInfo)
|
|
||||||
unlockManager.open(storage, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun remove(storage: IStorageInfo) {
|
suspend fun remove(storage: IStorageInfo) {
|
||||||
when(storage) {
|
when(storage) {
|
||||||
is IStorage -> manager.localVault.remove(storage)
|
is IStorage -> manager.localVault.remove(storage)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.github.nullptroma.wallenc.domain.usecases
|
||||||
|
|
||||||
|
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
|
||||||
|
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor
|
||||||
|
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||||
|
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
|
||||||
|
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
||||||
|
|
||||||
|
class ManageStoragesEncryptionUseCase(private val unlockManager: IUnlockManager) {
|
||||||
|
suspend fun enableEncryption(storage: IStorageInfo, key: EncryptKey, encryptPath: Boolean) {
|
||||||
|
when(storage) {
|
||||||
|
is IStorage -> {
|
||||||
|
if(storage.metaInfo.value.encInfo != null)
|
||||||
|
throw Exception() // TODO
|
||||||
|
storage.setEncInfo(Encryptor.generateEncryptionInfo(key, encryptPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun openStorage(storage: IStorageInfo, key: EncryptKey): IStorageInfo {
|
||||||
|
when(storage) {
|
||||||
|
is IStorage -> {
|
||||||
|
return unlockManager.open(storage, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
@@ -57,6 +58,7 @@ fun StorageTree(
|
|||||||
onClick: (Tree<IStorageInfo>) -> Unit,
|
onClick: (Tree<IStorageInfo>) -> Unit,
|
||||||
onRename: (Tree<IStorageInfo>, String) -> Unit,
|
onRename: (Tree<IStorageInfo>, String) -> Unit,
|
||||||
onRemove: (Tree<IStorageInfo>) -> Unit,
|
onRemove: (Tree<IStorageInfo>) -> Unit,
|
||||||
|
onEncrypt: (Tree<IStorageInfo>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val cur = tree.value
|
val cur = tree.value
|
||||||
val available by cur.isAvailable.collectAsStateWithLifecycle()
|
val available by cur.isAvailable.collectAsStateWithLifecycle()
|
||||||
@@ -66,7 +68,9 @@ fun StorageTree(
|
|||||||
val borderColor =
|
val borderColor =
|
||||||
if (cur.isVirtualStorage) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.primary
|
if (cur.isVirtualStorage) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.primary
|
||||||
Column(modifier) {
|
Column(modifier) {
|
||||||
Box(modifier = Modifier.height(IntrinsicSize.Min).zIndex(100f)) {
|
Box(modifier = Modifier
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
.zIndex(100f)) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -171,6 +175,9 @@ fun StorageTree(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Button(onClick = { onEncrypt(tree) }, enabled = metaInfo.encInfo == null) {
|
||||||
|
Text("Encrypt")
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -190,7 +197,16 @@ fun StorageTree(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i in tree.children ?: listOf()) {
|
for (i in tree.children ?: listOf()) {
|
||||||
StorageTree(Modifier.padding(16.dp, 0.dp, 0.dp, 0.dp).offset(y = (-4).dp), i, onClick, onRename, onRemove)
|
StorageTree(
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp, 0.dp, 0.dp, 0.dp)
|
||||||
|
.offset(y = (-4).dp),
|
||||||
|
i,
|
||||||
|
onClick,
|
||||||
|
onRename,
|
||||||
|
onRemove,
|
||||||
|
onEncrypt
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ fun LocalVaultScreen(
|
|||||||
},
|
},
|
||||||
onRemove = { tree ->
|
onRemove = { tree ->
|
||||||
viewModel.remove(tree.value)
|
viewModel.remove(tree.value)
|
||||||
|
},
|
||||||
|
onEncrypt = { tree ->
|
||||||
|
viewModel.enableEncryptionAndOpenStorage(tree.value)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import com.github.nullptroma.wallenc.domain.interfaces.ILogger
|
|||||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
|
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
|
||||||
|
import com.github.nullptroma.wallenc.domain.usecases.ManageStoragesEncryptionUseCase
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
|
||||||
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
||||||
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
|
|
||||||
import com.github.nullptroma.wallenc.presentation.ViewModelBase
|
import com.github.nullptroma.wallenc.presentation.ViewModelBase
|
||||||
|
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
@@ -25,6 +26,7 @@ class LocalVaultViewModel @Inject constructor(
|
|||||||
private val manageLocalVaultUseCase: ManageLocalVaultUseCase,
|
private val manageLocalVaultUseCase: ManageLocalVaultUseCase,
|
||||||
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
|
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
|
||||||
private val storageFileManagementUseCase: StorageFileManagementUseCase,
|
private val storageFileManagementUseCase: StorageFileManagementUseCase,
|
||||||
|
private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase,
|
||||||
private val renameStorageUseCase: RenameStorageUseCase,
|
private val renameStorageUseCase: RenameStorageUseCase,
|
||||||
private val logger: ILogger
|
private val logger: ILogger
|
||||||
) : ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) {
|
) : ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) {
|
||||||
@@ -50,6 +52,11 @@ class LocalVaultViewModel @Inject constructor(
|
|||||||
updateState(newState)
|
updateState(newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
getOpenedStoragesUseCase.openedStorages.collectLatest {
|
||||||
|
logger.debug("ViewModel", "Collected opened: ${it?.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printStorageInfoToLog(storage: IStorageInfo) {
|
fun printStorageInfoToLog(storage: IStorageInfo) {
|
||||||
@@ -74,7 +81,24 @@ class LocalVaultViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun createStorage() {
|
fun createStorage() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
manageLocalVaultUseCase.createStorage(EncryptKey("Hello"))
|
manageLocalVaultUseCase.createStorage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val runningStorages = mutableSetOf<IStorageInfo>()
|
||||||
|
fun enableEncryptionAndOpenStorage(storage: IStorageInfo) {
|
||||||
|
if(runningStorages.contains(storage))
|
||||||
|
return
|
||||||
|
runningStorages.add(storage)
|
||||||
|
val key = EncryptKey("Hello")
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
manageStoragesEncryptionUseCase.enableEncryption(storage, key, false)
|
||||||
|
manageStoragesEncryptionUseCase.openStorage(storage, key)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
runningStorages.remove(storage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user