Обновление времени при записи, StorageMetaInfo

This commit is contained in:
Пытков Роман
2025-01-22 01:26:55 +03:00
parent b9e73cf197
commit 8e2ac2f68d
14 changed files with 261 additions and 91 deletions

View File

@@ -0,0 +1,92 @@
package com.github.nullptroma.wallenc.data.utils
import java.io.InputStream
import java.io.OutputStream
private class CloseHandledOutputStream(
private val stream: OutputStream,
private val onClose: () -> Unit
) : OutputStream() {
override fun write(b: Int) {
stream.write(b)
}
override fun write(b: ByteArray) {
stream.write(b)
}
override fun write(b: ByteArray, off: Int, len: Int) {
stream.write(b, off, len)
}
override fun flush() {
stream.flush()
}
override fun close() {
try {
stream.close()
} finally {
onClose()
}
}
}
private class CloseHandledInputStream(
private val stream: InputStream,
private val onClose: () -> Unit
) : InputStream() {
override fun read(): Int {
return stream.read()
}
override fun read(b: ByteArray): Int {
return stream.read(b)
}
override fun read(b: ByteArray, off: Int, len: Int): Int {
return stream.read(b, off, len)
}
override fun skip(n: Long): Long {
return stream.skip(n)
}
override fun available(): Int {
return stream.available()
}
override fun close() {
try {
stream.close()
} finally {
onClose()
}
}
override fun mark(readlimit: Int) {
stream.mark(readlimit)
}
override fun reset() {
stream.reset()
}
override fun markSupported(): Boolean {
return stream.markSupported()
}
}
class CloseHandledStreamExtension {
companion object {
fun OutputStream.onClose(callback: ()->Unit): OutputStream {
return CloseHandledOutputStream(this, callback)
}
fun InputStream.onClose(callback: ()->Unit): InputStream {
return CloseHandledInputStream(this, callback)
}
}
}

View File

@@ -27,7 +27,7 @@ class UnlockManager(
private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>?>(null) private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>?>(null)
override val openedStorages: StateFlow<Map<UUID, IStorage>?> override val openedStorages: StateFlow<Map<UUID, IStorage>?>
get() = _openedStorages get() = _openedStorages
val mutex = Mutex() private val mutex = Mutex()
init { init {
CoroutineScope(ioDispatcher).launch { CoroutineScope(ioDispatcher).launch {
@@ -51,7 +51,7 @@ class UnlockManager(
private fun createEncryptedStorage(storage: IStorage, key: EncryptKey, uuid: UUID): EncryptedStorage { private fun createEncryptedStorage(storage: IStorage, key: EncryptKey, uuid: UUID): EncryptedStorage {
return EncryptedStorage( return EncryptedStorage(
source = storage, _source = storage,
key = key, key = key,
ioDispatcher = ioDispatcher, ioDispatcher = ioDispatcher,
uuid = uuid uuid = uuid
@@ -63,13 +63,10 @@ class UnlockManager(
key: EncryptKey key: EncryptKey
) = withContext(ioDispatcher) { ) = withContext(ioDispatcher) {
mutex.lock() mutex.lock()
val encInfo = storage.encInfo.value ?: 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))
throw Exception("Incorrect Key") throw Exception("Incorrect Key")
if (_openedStorages.value == null) {
val childScope = CoroutineScope(ioDispatcher)
}
val opened = _openedStorages.first { it != null }!!.toMutableMap() val opened = _openedStorages.first { it != null }!!.toMutableMap()
val cur = opened[storage.uuid] val cur = opened[storage.uuid]
if (cur != null) if (cur != null)

View File

@@ -1,35 +1,46 @@
package com.github.nullptroma.wallenc.data.vaults.local package com.github.nullptroma.wallenc.data.vaults.local
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper 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.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorage import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.io.InputStream
import java.util.UUID import java.util.UUID
class LocalStorage( class LocalStorage(
override val uuid: UUID, override val uuid: UUID,
absolutePath: String, absolutePath: String,
ioDispatcher: CoroutineDispatcher, private val ioDispatcher: CoroutineDispatcher,
) : IStorage { ) : IStorage {
override val size: StateFlow<Long?> override val size: StateFlow<Long?>
get() = accessor.size get() = accessor.size
override val numberOfFiles: StateFlow<Int?> override val numberOfFiles: StateFlow<Int?>
get() = accessor.numberOfFiles get() = accessor.numberOfFiles
private val _metaInfo = MutableStateFlow(
CommonStorageMetaInfo(
encInfo = StorageEncryptionInfo(
isEncrypted = false,
encryptedTestData = null
),
name = null
)
)
override val metaInfo: StateFlow<IStorageMetaInfo>
get() = _metaInfo
override val isAvailable: StateFlow<Boolean> override val isAvailable: StateFlow<Boolean>
get() = accessor.isAvailable get() = accessor.isAvailable
private val _accessor = LocalStorageAccessor(absolutePath, ioDispatcher) private val _accessor = LocalStorageAccessor(absolutePath, ioDispatcher)
override val accessor: IStorageAccessor = _accessor override val accessor: IStorageAccessor = _accessor
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" private val encInfoFileName: String = "$uuid$ENC_INFO_FILE_POSTFIX"
suspend fun init() { suspend fun init() {
@@ -37,15 +48,14 @@ class LocalStorage(
readEncInfo() readEncInfo()
} }
private suspend fun readEncInfo() { private suspend fun readEncInfo() = withContext(ioDispatcher) {
val reader = _accessor.openReadSystemFile(encInfoFileName)
var enc: StorageEncryptionInfo? = null var enc: StorageEncryptionInfo? = null
var reader: InputStream? = null
try { try {
enc = _jackson.readValue(reader, StorageEncryptionInfo::class.java) reader = _accessor.openReadSystemFile(encInfoFileName)
reader.close() enc = jackson.readValue(reader, StorageEncryptionInfo::class.java)
} }
catch(e: Exception) { catch(e: Exception) {
reader.close()
// чтение не удалось, значит нужно записать файл // чтение не удалось, значит нужно записать файл
enc = StorageEncryptionInfo( enc = StorageEncryptionInfo(
isEncrypted = false, isEncrypted = false,
@@ -53,19 +63,22 @@ class LocalStorage(
) )
setEncInfo(enc) setEncInfo(enc)
} }
_encInfo.value = enc finally {
reader?.close()
}
_metaInfo.value = _metaInfo.value.copy(encInfo = enc)
} }
suspend fun setEncInfo(enc: StorageEncryptionInfo) { suspend fun setEncInfo(enc: StorageEncryptionInfo) = withContext(ioDispatcher) {
val writer = _accessor.openWriteSystemFile(encInfoFileName) val writer = _accessor.openWriteSystemFile(encInfoFileName)
try { try {
_jackson.writeValue(writer, enc) jackson.writeValue(writer, enc)
} }
catch (e: Exception) { catch (e: Exception) {
TODO("Это никогда не должно произойти") TODO("Это никогда не должно произойти")
} }
writer.close() writer.close()
_encInfo.value = enc _metaInfo.value = _metaInfo.value.copy(encInfo = enc)
} }
override suspend fun rename(newName: String) { override suspend fun rename(newName: String) {
@@ -74,6 +87,6 @@ class LocalStorage(
companion object { companion object {
const val ENC_INFO_FILE_POSTFIX = ".enc-info" const val ENC_INFO_FILE_POSTFIX = ".enc-info"
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() } private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
} }
} }

View File

@@ -3,6 +3,7 @@ package com.github.nullptroma.wallenc.data.vaults.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.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
@@ -13,6 +14,7 @@ import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
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.MutableStateFlow
@@ -20,6 +22,7 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
@@ -115,7 +118,7 @@ class LocalStorageAccessor(
) { ) {
companion object { companion object {
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() } private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
fun fromFile(filesystemBasePath: Path, file: File): LocalStorageFilePair? { fun fromFile(filesystemBasePath: Path, file: File): LocalStorageFilePair? {
if (!file.exists()) if (!file.exists())
@@ -140,19 +143,19 @@ class LocalStorageAccessor(
size = filePath.fileSize(), size = filePath.fileSize(),
path = storageFilePath path = storageFilePath
) )
_jackson.writeValue(metaFile, metaInfo) jackson.writeValue(metaFile, metaInfo)
} else { } else {
var readMeta: CommonMetaInfo var readMeta: CommonMetaInfo
try { try {
val reader = metaFile.bufferedReader() val reader = metaFile.bufferedReader()
readMeta = _jackson.readValue(reader) readMeta = jackson.readValue(reader)
} catch (e: JacksonException) { } catch (e: JacksonException) {
// если файл повреждён - пересоздать // если файл повреждён - пересоздать
readMeta = CommonMetaInfo( readMeta = CommonMetaInfo(
size = filePath.fileSize(), size = filePath.fileSize(),
path = storageFilePath path = storageFilePath
) )
_jackson.writeValue(metaFile, readMeta) jackson.writeValue(metaFile, readMeta)
} }
metaInfo = readMeta metaInfo = readMeta
} }
@@ -171,7 +174,7 @@ class LocalStorageAccessor(
var pair: LocalStorageFilePair? = null var pair: LocalStorageFilePair? = null
try { try {
val reader = metaFile.bufferedReader() val reader = metaFile.bufferedReader()
val metaInfo: CommonMetaInfo = _jackson.readValue(reader) val metaInfo: CommonMetaInfo = jackson.readValue(reader)
val pathString = Path(filesystemBasePath.pathString, metaInfo.path).pathString val pathString = Path(filesystemBasePath.pathString, metaInfo.path).pathString
val file = File(pathString) val file = File(pathString)
if (!file.exists()) { if (!file.exists()) {
@@ -258,8 +261,8 @@ class LocalStorageAccessor(
var size = 0L var size = 0L
var numOfFiles = 0 var numOfFiles = 0
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, CommonFile -> scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, commonFile ->
size += CommonFile.metaInfo.size size += commonFile.metaInfo.size
numOfFiles++ numOfFiles++
if (numOfFiles % DATA_PAGE_LENGTH == 0) { if (numOfFiles % DATA_PAGE_LENGTH == 0) {
@@ -402,15 +405,17 @@ class LocalStorageAccessor(
override suspend fun setHidden(path: String, hidden: Boolean) { override suspend fun setHidden(path: String, hidden: Boolean) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path) val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Что то пошло не так") // TODO ?: throw Exception("Что то пошло не так") // TODO
if(pair.meta.isHidden == hidden) if (pair.meta.isHidden == hidden)
return return
val newMeta = pair.meta.copy(isHidden = hidden) val newMeta = pair.meta.copy(isHidden = hidden)
writeMeta(pair.metaFile, newMeta) writeMeta(pair.metaFile, newMeta)
_filesUpdates.emit( _filesUpdates.emit(
DataPage( DataPage(
list = listOf(CommonFile( list = listOf(
metaInfo = newMeta CommonFile(
)), metaInfo = newMeta
)
),
pageLength = 1, pageLength = 1,
pageIndex = 0 pageIndex = 0
) )
@@ -419,31 +424,41 @@ class LocalStorageAccessor(
private fun writeMeta(metaFile: File, meta: IMetaInfo) { private fun writeMeta(metaFile: File, meta: IMetaInfo) {
_jackson.writeValue(metaFile, meta) jackson.writeValue(metaFile, meta)
} }
private fun createFile(storagePath: String): CommonFile { private suspend fun createFile(storagePath: String): CommonFile = withContext(ioDispatcher) {
val path = Path(_filesystemBasePath.pathString, storagePath) val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile() val file = path.toFile()
if (file.exists() && file.isDirectory) { if (file.exists() && file.isDirectory) {
throw Exception("Что то пошло не так") // TODO throw Exception("Что то пошло не так") // TODO
} else { } else if(!file.exists()) {
file.createNewFile() file.createNewFile()
val cur = _numberOfFiles.value
_numberOfFiles.value = if (cur == null) null else cur + 1
} }
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())
writeMeta(pair.metaFile, newMeta) writeMeta(pair.metaFile, newMeta)
return CommonFile(newMeta) _filesUpdates.emit(
DataPage(
list = listOf(CommonFile(pair.meta)),
pageLength = 1,
pageIndex = 0
)
)
return@withContext CommonFile(newMeta)
} }
private fun createDir(storagePath: String): CommonDirectory { private suspend fun createDir(storagePath: String): CommonDirectory = withContext(ioDispatcher) {
val path = Path(_filesystemBasePath.pathString, storagePath) val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile() val file = path.toFile()
if (file.exists() && !file.isDirectory) { if (file.exists() && !file.isDirectory) {
throw Exception("Что то пошло не так") // TODO throw Exception("Что то пошло не так") // TODO
} else { } else if(!file.exists()) {
Files.createDirectories(path) Files.createDirectories(path)
} }
@@ -451,11 +466,25 @@ class LocalStorageAccessor(
?: throw Exception("Что то пошло не так") // TODO ?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant()) val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant())
writeMeta(pair.metaFile, newMeta) writeMeta(pair.metaFile, newMeta)
return CommonDirectory(newMeta, 0) _dirsUpdates.emit(
DataPage(
list = listOf(CommonDirectory(pair.meta, null)),
pageLength = 1,
pageIndex = 0
)
)
return@withContext CommonDirectory(newMeta, 0)
} }
override suspend fun touchFile(path: String): Unit = withContext(ioDispatcher) { override suspend fun touchFile(path: String): Unit = withContext(ioDispatcher) {
createFile(path) createFile(path)
// перебор все каталогов и обновление их времени модификации
var parent = Path(path).parent
while(parent != null) {
touchDir(parent.pathString)
parent = parent.parent
}
} }
override suspend fun touchDir(path: String): Unit = withContext(ioDispatcher) { override suspend fun touchDir(path: String): Unit = withContext(ioDispatcher) {
@@ -474,7 +503,11 @@ 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() return@withContext pair.file.outputStream().onClose {
CoroutineScope(ioDispatcher).launch {
touchFile(path)
}
}
} }
override suspend fun openRead(path: String): InputStream = withContext(ioDispatcher) { override suspend fun openRead(path: String): InputStream = withContext(ioDispatcher) {
@@ -519,6 +552,6 @@ class LocalStorageAccessor(
private const val SYSTEM_HIDDEN_DIRNAME = "wallenc-local-storage-meta-dir" private const val SYSTEM_HIDDEN_DIRNAME = "wallenc-local-storage-meta-dir"
private const val META_INFO_POSTFIX = ".wallenc-meta" private const val META_INFO_POSTFIX = ".wallenc-meta"
private const val DATA_PAGE_LENGTH = 10 private const val DATA_PAGE_LENGTH = 10
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() } private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
} }
} }

View File

@@ -34,18 +34,18 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
private val _availableSpace = MutableStateFlow(null) private val _availableSpace = MutableStateFlow(null)
override val availableSpace: StateFlow<Int?> = _availableSpace override val availableSpace: StateFlow<Int?> = _availableSpace
private val _path = MutableStateFlow<File?>(null) private val path = MutableStateFlow<File?>(null)
init { init {
CoroutineScope(ioDispatcher).launch { CoroutineScope(ioDispatcher).launch {
_path.value = context.getExternalFilesDir("LocalVault") path.value = context.getExternalFilesDir("LocalVault")
_isAvailable.value = _path.value != null _isAvailable.value = path.value != null
readStorages() readStorages()
} }
} }
private suspend fun readStorages() { private suspend fun readStorages() {
val path = _path.value val path = path.value
if (path == null || !_isAvailable.value) if (path == null || !_isAvailable.value)
return return
@@ -59,7 +59,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
} }
override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) { override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) {
val path = _path.value val path = path.value
if (path == null || !_isAvailable.value) if (path == null || !_isAvailable.value)
throw Exception("Not available") throw Exception("Not available")

View File

@@ -0,0 +1,15 @@
package com.github.nullptroma.wallenc.domain.common.impl
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import java.time.Clock
import java.time.Instant
data class CommonStorageMetaInfo(
override val encInfo: StorageEncryptionInfo?,
override val name: String?,
override val lastModified: Instant = Clock.systemUTC().instant()
) : IStorageMetaInfo

View File

@@ -2,9 +2,9 @@ package com.github.nullptroma.wallenc.domain.datatypes
class DataPage<T>( class DataPage<T>(
list: List<T>, list: List<T>,
isLoading: Boolean? = false, isLoading: Boolean? = null,
isError: Boolean? = false, isError: Boolean? = null,
val hasNext: Boolean? = false, val hasNext: Boolean? = null,
val pageLength: Int, val pageLength: Int,
val pageIndex: Int val pageIndex: Int
) : DataPackage<List<T>>(data = list, isLoading = isLoading, isError = isError) ) : DataPackage<List<T>>(data = list, isLoading = isLoading, isError = isError)

View File

@@ -1,38 +1,46 @@
package com.github.nullptroma.wallenc.domain.encrypt package com.github.nullptroma.wallenc.domain.encrypt
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
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo 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.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import java.util.UUID import java.util.UUID
class EncryptedStorage( class EncryptedStorage(
private val source: IStorage, private val _source: IStorage,
key: EncryptKey, key: EncryptKey,
ioDispatcher: CoroutineDispatcher, private val ioDispatcher: CoroutineDispatcher,
override val uuid: UUID = UUID.randomUUID() override val uuid: UUID = UUID.randomUUID()
) : IStorage, DisposableHandle { ) : IStorage, DisposableHandle {
override val size: StateFlow<Long?> override val size: StateFlow<Long?>
get() = source.size get() = _source.size
override val numberOfFiles: StateFlow<Int?> override val numberOfFiles: StateFlow<Int?>
get() = source.numberOfFiles get() = _source.numberOfFiles
override val name: StateFlow<String>
get() = TODO("Not yet implemented") private val _metaInfo = MutableStateFlow<IStorageMetaInfo>(
override val isAvailable: StateFlow<Boolean> CommonStorageMetaInfo(
get() = source.isAvailable encInfo = StorageEncryptionInfo(
override val encInfo: StateFlow<StorageEncryptionInfo?>
get() = MutableStateFlow(
StorageEncryptionInfo(
isEncrypted = false, isEncrypted = false,
encryptedTestData = null encryptedTestData = null
) ),
name = null
) )
)
override val metaInfo: StateFlow<IStorageMetaInfo>
get() = _metaInfo
override val isAvailable: StateFlow<Boolean>
get() = _source.isAvailable
override val accessor: EncryptedStorageAccessor = override val accessor: EncryptedStorageAccessor =
EncryptedStorageAccessor(source.accessor, key, ioDispatcher) EncryptedStorageAccessor(_source.accessor, key, ioDispatcher)
override suspend fun rename(newName: String) { override suspend fun rename(newName: String) {
TODO("Not yet implemented") TODO("Not yet implemented")

View File

@@ -7,7 +7,6 @@ import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -30,8 +29,8 @@ class EncryptedStorageAccessor(
key: EncryptKey, key: EncryptKey,
ioDispatcher: CoroutineDispatcher ioDispatcher: CoroutineDispatcher
) : IStorageAccessor, DisposableHandle { ) : IStorageAccessor, DisposableHandle {
private val _job = Job() private val job = Job()
private val _scope = CoroutineScope(ioDispatcher + _job) private val scope = CoroutineScope(ioDispatcher + job)
override val size: StateFlow<Long?> = source.size override val size: StateFlow<Long?> = source.size
override val numberOfFiles: StateFlow<Int?> = source.numberOfFiles override val numberOfFiles: StateFlow<Int?> = source.numberOfFiles
@@ -43,14 +42,14 @@ class EncryptedStorageAccessor(
private val _dirsUpdates = MutableSharedFlow<DataPackage<List<IDirectory>>>() private val _dirsUpdates = MutableSharedFlow<DataPackage<List<IDirectory>>>()
override val dirsUpdates: SharedFlow<DataPackage<List<IDirectory>>> = _dirsUpdates override val dirsUpdates: SharedFlow<DataPackage<List<IDirectory>>> = _dirsUpdates
private val _encryptor = Encryptor(key.toAesKey()) private val encryptor = Encryptor(key.toAesKey())
init { init {
collectSourceState() collectSourceState()
} }
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)
@@ -116,7 +115,7 @@ class EncryptedStorageAccessor(
val path = Path(pathStr) val path = Path(pathStr)
val segments = mutableListOf<String>() val segments = mutableListOf<String>()
for (segment in path) for (segment in path)
segments.add(_encryptor.encryptString(segment.pathString)) segments.add(encryptor.encryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
return res.pathString return res.pathString
} }
@@ -125,7 +124,7 @@ class EncryptedStorageAccessor(
val path = Path(pathStr) val path = Path(pathStr)
val segments = mutableListOf<String>() val segments = mutableListOf<String>()
for (segment in path) for (segment in path)
segments.add(_encryptor.decryptString(segment.pathString)) segments.add(encryptor.decryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
return res.pathString return res.pathString
} }
@@ -198,12 +197,12 @@ class EncryptedStorageAccessor(
override suspend fun openWrite(path: String): OutputStream { override suspend fun openWrite(path: String): OutputStream {
val stream = source.openWrite(encryptPath(path)) val stream = source.openWrite(encryptPath(path))
return _encryptor.encryptStream(stream) return encryptor.encryptStream(stream)
} }
override suspend fun openRead(path: String): InputStream { override suspend fun openRead(path: String): InputStream {
val stream = source.openRead(encryptPath(path)) val stream = source.openRead(encryptPath(path))
return _encryptor.decryptStream(stream) return encryptor.decryptStream(stream)
} }
override suspend fun moveToTrash(path: String) { override suspend fun moveToTrash(path: String) {
@@ -211,7 +210,8 @@ class EncryptedStorageAccessor(
} }
override fun dispose() { override fun dispose() {
_job.cancel() job.cancel()
_encryptor.dispose() encryptor.dispose()
} }
} }

View File

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

View File

@@ -0,0 +1,13 @@
package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import kotlinx.coroutines.flow.StateFlow
import java.time.Clock
import java.time.Instant
interface IStorageMetaInfo {
val encInfo: StorageEncryptionInfo?
val name: String?
val lastModified: Instant
}

View File

@@ -3,5 +3,5 @@ package com.github.nullptroma.wallenc.presentation.extensions
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
fun IStorageInfo.toPrintable(): String { fun IStorageInfo.toPrintable(): String {
return "{ uuid: $uuid, enc: ${encInfo.value} }" return "{ uuid: $uuid, enc: ${metaInfo.value.encInfo} }"
} }

View File

@@ -48,7 +48,9 @@ fun LocalVaultScreen(modifier: Modifier = Modifier,
val available by it.isAvailable.collectAsStateWithLifecycle() val available by it.isAvailable.collectAsStateWithLifecycle()
val numOfFiles by it.numberOfFiles.collectAsStateWithLifecycle() val numOfFiles by it.numberOfFiles.collectAsStateWithLifecycle()
val size by it.size.collectAsStateWithLifecycle() val size by it.size.collectAsStateWithLifecycle()
val enc by it.encInfo.collectAsStateWithLifecycle() val metaInfo by it.metaInfo.collectAsStateWithLifecycle()
val enc = metaInfo.encInfo
Column { Column {
Text(it.uuid.toString()) Text(it.uuid.toString())
Text("IsAvailable: $available") Text("IsAvailable: $available")

View File

@@ -12,7 +12,6 @@ import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCas
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
import com.github.nullptroma.wallenc.presentation.viewmodel.ViewModelBase import com.github.nullptroma.wallenc.presentation.viewmodel.ViewModelBase
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -21,15 +20,15 @@ import kotlin.system.measureTimeMillis
@HiltViewModel @HiltViewModel
class LocalVaultViewModel @Inject constructor( 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 logger: ILogger private val logger: ILogger
) : ) :
ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) { ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) {
init { init {
viewModelScope.launch { viewModelScope.launch {
_manageLocalVaultUseCase.localStorages.combine(_getOpenedStoragesUseCase.openedStorages) { local, opened -> manageLocalVaultUseCase.localStorages.combine(getOpenedStoragesUseCase.openedStorages) { local, opened ->
local + (opened?.map { it.value } ?: listOf()) local + (opened?.map { it.value } ?: listOf())
}.collectLatest { }.collectLatest {
val newState = state.value.copy( val newState = state.value.copy(
@@ -41,13 +40,13 @@ class LocalVaultViewModel @Inject constructor(
} }
fun printStorageInfoToLog(storage: IStorageInfo) { fun printStorageInfoToLog(storage: IStorageInfo) {
_storageFileManagementUseCase.setStorage(storage) storageFileManagementUseCase.setStorage(storage)
viewModelScope.launch { viewModelScope.launch {
val files: List<IFile> val files: List<IFile>
val dirs: List<IDirectory> val dirs: List<IDirectory>
val time = measureTimeMillis { val time = measureTimeMillis {
files = _storageFileManagementUseCase.getAllFiles() files = storageFileManagementUseCase.getAllFiles()
dirs = _storageFileManagementUseCase.getAllDirs() dirs = storageFileManagementUseCase.getAllDirs()
} }
for (file in files) { for (file in files) {
logger.debug("Files", file.metaInfo.toString()) logger.debug("Files", file.metaInfo.toString())
@@ -62,7 +61,7 @@ class LocalVaultViewModel @Inject constructor(
fun createStorage() { fun createStorage() {
viewModelScope.launch { viewModelScope.launch {
_manageLocalVaultUseCase.createStorage(EncryptKey("hello")) manageLocalVaultUseCase.createStorage(EncryptKey("hello"))
} }
} }
} }