Добавлено StorageEncryptionInfo для локальных хранилищ

This commit is contained in:
Roman Pytkov
2025-01-05 02:39:26 +03:00
parent f0f0c0f195
commit 407530e9bb
12 changed files with 194 additions and 56 deletions

View File

@@ -2,7 +2,10 @@ package com.github.nullptroma.wallenc.app.di.modules.data
import android.content.Context
import com.github.nullptroma.wallenc.app.di.modules.app.IoDispatcher
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao
import com.github.nullptroma.wallenc.data.vaults.UnlockManager
import com.github.nullptroma.wallenc.data.vaults.VaultsManager
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import dagger.Module
import dagger.Provides
@@ -21,4 +24,14 @@ class SingletonModule {
@ApplicationContext context: Context): IVaultsManager {
return VaultsManager(ioDispatcher, context)
}
@Provides
@Singleton
fun provideUnlockManager(@IoDispatcher ioDispatcher: CoroutineDispatcher,
dao: StorageKeyDao): IUnlockManager {
return UnlockManager(
dao = dao,
ioDispatcher = ioDispatcher
)
}
}

View File

@@ -1,14 +1,17 @@
package com.github.nullptroma.wallenc.data.vaults
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.encrypt.EncryptedStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
class UnlockManager: IUnlockManager {
private val _openedStorages = MutableStateFlow<Map<UUID, IStorage>>(mapOf())
class UnlockManager(dao: StorageKeyDao, ioDispatcher: CoroutineDispatcher): IUnlockManager {
private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>>(mapOf())
override val openedStorages: StateFlow<Map<UUID, IStorage>>
get() = _openedStorages

View File

@@ -1,29 +1,89 @@
package com.github.nullptroma.wallenc.data.vaults.local
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
class LocalStorage(
override val uuid: UUID,
override val isEncrypted: Boolean,
absolutePath: String,
ioDispatcher: CoroutineDispatcher
ioDispatcher: CoroutineDispatcher,
) : IStorage {
override val size: StateFlow<Long?>
get() = accessor.size
override val numberOfFiles: StateFlow<Int?>
get() = accessor.numberOfFiles
override val name: StateFlow<String>
get() = TODO("Добавить класс в Domain, который с помощью accessor будет читать и сохранять имя в скрытую папку")
override val isAvailable: StateFlow<Boolean>
get() = accessor.isAvailable
override val accessor: IStorageAccessor = LocalStorageAccessor(absolutePath, ioDispatcher)
override val accessor = LocalStorageAccessor(absolutePath, ioDispatcher)
private val _encInfo = MutableStateFlow<StorageEncryptionInfo?>(null)
override val encInfo: StateFlow<StorageEncryptionInfo?>
get() = _encInfo
override val name: StateFlow<String>
get() = TODO("Добавить класс в Domain, который с помощью accessor будет читать и сохранять имя в скрытую папку")
private val encInfoFileName: String = "$uuid$ENC_INFO_FILE_POSTFIX"
suspend fun init() {
accessor.init()
readEncInfo()
}
private suspend fun readEncInfo() {
accessor.touchFile(encInfoFileName)
accessor.setHidden(encInfoFileName, true)
val reader = accessor.openRead(encInfoFileName)
var enc: StorageEncryptionInfo? = null
try {
enc = _jackson.readValue(reader, StorageEncryptionInfo::class.java)
reader.close()
}
catch(e: Exception) {
reader.close()
// чтение не удалось, значит нужно записать файл
enc = StorageEncryptionInfo(
isEncrypted = false,
encryptedTestData = null
)
val writer = accessor.openWrite(encInfoFileName)
try {
_jackson.writeValue(writer, enc)
}
catch (e: Exception) {
TODO("Это никогда не должно произойти")
}
writer.close()
}
_encInfo.value = enc
}
suspend fun setEncInfo(enc: StorageEncryptionInfo) {
accessor.touchFile(encInfoFileName)
accessor.setHidden(encInfoFileName, true)
val writer = accessor.openWrite(encInfoFileName)
try {
_jackson.writeValue(writer, enc)
}
catch (e: Exception) {
TODO("Это никогда не должно произойти")
}
writer.close()
_encInfo.value = enc
}
override suspend fun rename(newName: String) {
TODO("Not yet implemented")
}
companion object {
const val ENC_INFO_FILE_POSTFIX = ".enc-info"
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
}
}

View File

@@ -10,9 +10,9 @@ import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.DataPage
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -20,13 +20,13 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.time.Clock
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.fileSize
@@ -38,7 +38,6 @@ class LocalStorageAccessor(
private val ioDispatcher: CoroutineDispatcher
) : IStorageAccessor {
private val _filesystemBasePath: Path = Path(filesystemBasePath).normalize().absolute()
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
private val _size = MutableStateFlow<Long?>(null)
override val size: StateFlow<Long?> = _size
@@ -55,12 +54,10 @@ class LocalStorageAccessor(
private val _dirsUpdates = MutableSharedFlow<DataPage<IDirectory>>()
override val dirsUpdates: SharedFlow<DataPage<IDirectory>> = _dirsUpdates
init {
suspend fun init() {
// запускам сканирование хранилища
CoroutineScope(ioDispatcher).launch {
scanSizeAndNumOfFiles()
}
}
/**
* Проверяет существование корневого пути Storage в файловой системе, изменяет _isAvailable
@@ -201,6 +198,10 @@ class LocalStorageAccessor(
val filePath = Path(filesystemBasePath.pathString, storagePath)
return from(filesystemBasePath, filePath.toFile())
}
fun from(filesystemBasePath: Path, meta: IMetaInfo): LocalStorageFilePair? {
return from(filesystemBasePath, meta.path)
}
}
}
@@ -227,7 +228,7 @@ class LocalStorageAccessor(
}
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
if(pair != null) {
if (pair != null) {
workedFiles.add(pair.file.absolutePath)
workedMetaFiles.add(pair.metaFile.absolutePath)
@@ -259,7 +260,7 @@ class LocalStorageAccessor(
size += CommonFile.metaInfo.size
numOfFiles++
if(numOfFiles % DATA_PAGE_LENGTH == 0) {
if (numOfFiles % DATA_PAGE_LENGTH == 0) {
_size.value = size
_numberOfFiles.value = numOfFiles
}
@@ -379,22 +380,58 @@ class LocalStorageAccessor(
emit(page)
}.flowOn(ioDispatcher)
private fun writeMeta(metaFile: File, meta: CommonMetaInfo) {
override suspend fun getFileInfo(path: String): IFile {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Что то пошло не так") // TODO
return CommonFile(
metaInfo = pair.meta,
)
}
override suspend fun getDirInfo(path: String): IDirectory {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Что то пошло не так") // TODO
return CommonDirectory(
metaInfo = pair.meta,
elementsCount = null
)
}
override suspend fun setHidden(path: String, hidden: Boolean) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Что то пошло не так") // TODO
if(pair.meta.isHidden == hidden)
return
val newMeta = pair.meta.copy(isHidden = hidden)
writeMeta(pair.metaFile, newMeta)
_filesUpdates.emit(
DataPage(
list = listOf(CommonFile(
metaInfo = newMeta
)),
pageLength = 1,
pageIndex = 0
)
)
}
private fun writeMeta(metaFile: File, meta: IMetaInfo) {
_jackson.writeValue(metaFile, meta)
}
private fun createFile(storagePath: String): CommonFile {
val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile()
if(file.exists() && file.isDirectory) {
if (file.exists() && file.isDirectory) {
throw Exception("Что то пошло не так") // TODO
}
else {
} else {
file.createNewFile()
}
val pair = LocalStorageFilePair.from(_filesystemBasePath, file) ?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = java.time.Clock.systemUTC().instant())
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant())
writeMeta(pair.metaFile, newMeta)
return CommonFile(newMeta)
}
@@ -402,15 +439,15 @@ class LocalStorageAccessor(
private fun createDir(storagePath: String): CommonDirectory {
val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile()
if(file.exists() && !file.isDirectory) {
if (file.exists() && !file.isDirectory) {
throw Exception("Что то пошло не так") // TODO
}
else {
} else {
Files.createDirectories(path)
}
val pair = LocalStorageFilePair.from(_filesystemBasePath, file) ?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = java.time.Clock.systemUTC().instant())
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant())
writeMeta(pair.metaFile, newMeta)
return CommonDirectory(newMeta, 0)
}
@@ -425,7 +462,7 @@ class LocalStorageAccessor(
override suspend fun delete(path: String) = withContext(ioDispatcher) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
if(pair != null) {
if (pair != null) {
pair.file.delete()
pair.metaFile.delete()
}
@@ -433,17 +470,20 @@ class LocalStorageAccessor(
override suspend fun openWrite(path: String): OutputStream = withContext(ioDispatcher) {
touchFile(path)
val pair = LocalStorageFilePair.from(_filesystemBasePath, path) ?: throw Exception("Файла нет") // TODO
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Файла нет") // TODO
return@withContext pair.file.outputStream()
}
override suspend fun openRead(path: String): InputStream = withContext(ioDispatcher) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path) ?: throw Exception("Файла нет") // TODO
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Файла нет") // TODO
return@withContext pair.file.inputStream()
}
override suspend fun moveToTrash(path: String) = withContext(ioDispatcher) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path) ?: throw Exception("Файла нет") // TODO
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Файла нет") // TODO
val newMeta = pair.meta.copy(isDeleted = true)
writeMeta(pair.metaFile, newMeta)
}
@@ -451,5 +491,6 @@ class LocalStorageAccessor(
companion object {
private const val META_INFO_POSTFIX = ".wallenc-meta"
private const val DATA_PAGE_LENGTH = 10
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
}
}

View File

@@ -1,7 +1,7 @@
package com.github.nullptroma.wallenc.data.vaults.local
import android.content.Context
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.enums.VaultType
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IVault
@@ -44,7 +44,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
}
}
private fun readStorages() {
private suspend fun readStorages() {
val path = _path.value
if (path == null || !_isAvailable.value)
return
@@ -53,12 +53,12 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
if (dirs != null) {
_storages.value = dirs.map {
val uuid = UUID.fromString(it.name)
LocalStorage(uuid, false, it.path, ioDispatcher)
return@map LocalStorage(uuid, it.path, ioDispatcher).apply { init() }
}
}
}
override suspend fun createStorage(): IStorage = withContext(ioDispatcher) {
override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) {
val path = _path.value
if (path == null || !_isAvailable.value)
throw Exception("Not available")
@@ -66,7 +66,8 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
val uuid = UUID.randomUUID()
val next = Path(path.path, uuid.toString())
next.createDirectory()
val newStorage = LocalStorage(uuid, false, next.pathString, ioDispatcher)
val newStorage = LocalStorage(uuid, next.pathString, ioDispatcher)
newStorage.init()
_storages.value = _storages.value.toMutableList().apply {
add(newStorage)
}
@@ -74,15 +75,9 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
}
override suspend fun createStorage(
key: EncryptKey
): IStorage = withContext(ioDispatcher) {
TODO("Not yet implemented")
}
enc: StorageEncryptionInfo
): LocalStorage = withContext(ioDispatcher) {
override suspend fun createStorage(
key: EncryptKey,
uuid: UUID
): IStorage = withContext(ioDispatcher) {
TODO("Not yet implemented")
}

View File

@@ -1,5 +1,6 @@
package com.github.nullptroma.wallenc.domain.common.impl
import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
data class CommonFile(override val metaInfo: CommonMetaInfo) : IFile
data class CommonFile(override val metaInfo: IMetaInfo) : IFile

View File

@@ -0,0 +1,6 @@
package com.github.nullptroma.wallenc.domain.datatypes
data class StorageEncryptionInfo(
val isEncrypted: Boolean,
val encryptedTestData: String?
)

View File

@@ -1,6 +1,7 @@
package com.github.nullptroma.wallenc.domain.encrypt
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
@@ -13,7 +14,6 @@ class EncryptedStorage(
key: EncryptKey,
logger: ILogger,
ioDispatcher: CoroutineDispatcher,
override val isEncrypted: Boolean
) : IStorage {
override val size: StateFlow<Long?>
get() = TODO("Not yet implemented")
@@ -25,6 +25,8 @@ class EncryptedStorage(
get() = TODO("Not yet implemented")
override val isAvailable: StateFlow<Boolean>
get() = TODO("Not yet implemented")
override val encInfo: StateFlow<StorageEncryptionInfo>
get() = TODO("Not yet implemented")
override val accessor: IStorageAccessor =
EncryptedStorageAccessor(source.accessor, key, logger, ioDispatcher)

View File

@@ -191,6 +191,22 @@ class EncryptedStorageAccessor(
return flow
}
override suspend fun getFileInfo(path: String): IFile {
val file = source.getFileInfo(encryptPath(path))
val meta = decryptMeta(file.metaInfo)
return CommonFile(meta)
}
override suspend fun getDirInfo(path: String): IDirectory {
val dir = source.getDirInfo(encryptPath(path))
val meta = decryptMeta(dir.metaInfo)
return CommonDirectory(meta, dir.elementsCount)
}
override suspend fun setHidden(path: String, hidden: Boolean) {
source.setHidden(encryptPath(path), hidden)
}
override suspend fun touchFile(path: String) {
source.touchFile(encryptPath(path))
}

View File

@@ -31,7 +31,9 @@ interface IStorageAccessor {
* @return Поток директорий
*/
fun getDirsFlow(path: String): Flow<DataPackage<List<IDirectory>>>
suspend fun getFileInfo(path: String): IFile
suspend fun getDirInfo(path: String): IDirectory
suspend fun setHidden(path: String, hidden: Boolean)
suspend fun touchFile(path: String)
suspend fun touchDir(path: String)
suspend fun delete(path: String)

View File

@@ -1,13 +1,14 @@
package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
sealed interface IStorageInfo {
val uuid: UUID
val isAvailable: StateFlow<Boolean>
val size: StateFlow<Long?>
val numberOfFiles: StateFlow<Int?>
val uuid: UUID
val isEncrypted: Boolean
val name: StateFlow<String>
val isAvailable: StateFlow<Boolean>
val encInfo: StateFlow<StorageEncryptionInfo?>
val name: StateFlow<String?>
}

View File

@@ -1,14 +1,12 @@
package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
interface IVault : IVaultInfo {
override val storages: StateFlow<List<IStorage>>
suspend fun createStorage(): IStorage
suspend fun createStorage(key: EncryptKey): IStorage
suspend fun createStorage(key: EncryptKey, uuid: UUID): IStorage
suspend fun createStorage(enc: StorageEncryptionInfo): IStorage
suspend fun remove(storage: IStorage)
}