Обновление времени при записи, 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)
override val openedStorages: StateFlow<Map<UUID, IStorage>?>
get() = _openedStorages
val mutex = Mutex()
private val mutex = Mutex()
init {
CoroutineScope(ioDispatcher).launch {
@@ -51,7 +51,7 @@ class UnlockManager(
private fun createEncryptedStorage(storage: IStorage, key: EncryptKey, uuid: UUID): EncryptedStorage {
return EncryptedStorage(
source = storage,
_source = storage,
key = key,
ioDispatcher = ioDispatcher,
uuid = uuid
@@ -63,13 +63,10 @@ class UnlockManager(
key: EncryptKey
) = withContext(ioDispatcher) {
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))
throw Exception("Incorrect Key")
if (_openedStorages.value == null) {
val childScope = CoroutineScope(ioDispatcher)
}
val opened = _openedStorages.first { it != null }!!.toMutableMap()
val cur = opened[storage.uuid]
if (cur != null)

View File

@@ -1,35 +1,46 @@
package com.github.nullptroma.wallenc.data.vaults.local
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.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.io.InputStream
import java.util.UUID
class LocalStorage(
override val uuid: UUID,
absolutePath: String,
ioDispatcher: CoroutineDispatcher,
private val ioDispatcher: CoroutineDispatcher,
) : IStorage {
override val size: StateFlow<Long?>
get() = accessor.size
override val numberOfFiles: StateFlow<Int?>
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>
get() = accessor.isAvailable
private val _accessor = LocalStorageAccessor(absolutePath, ioDispatcher)
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"
suspend fun init() {
@@ -37,15 +48,14 @@ class LocalStorage(
readEncInfo()
}
private suspend fun readEncInfo() {
val reader = _accessor.openReadSystemFile(encInfoFileName)
private suspend fun readEncInfo() = withContext(ioDispatcher) {
var enc: StorageEncryptionInfo? = null
var reader: InputStream? = null
try {
enc = _jackson.readValue(reader, StorageEncryptionInfo::class.java)
reader.close()
reader = _accessor.openReadSystemFile(encInfoFileName)
enc = jackson.readValue(reader, StorageEncryptionInfo::class.java)
}
catch(e: Exception) {
reader.close()
// чтение не удалось, значит нужно записать файл
enc = StorageEncryptionInfo(
isEncrypted = false,
@@ -53,19 +63,22 @@ class LocalStorage(
)
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)
try {
_jackson.writeValue(writer, enc)
jackson.writeValue(writer, enc)
}
catch (e: Exception) {
TODO("Это никогда не должно произойти")
}
writer.close()
_encInfo.value = enc
_metaInfo.value = _metaInfo.value.copy(encInfo = enc)
}
override suspend fun rename(newName: String) {
@@ -74,6 +87,6 @@ class LocalStorage(
companion object {
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.module.kotlin.jacksonObjectMapper
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.CommonFile
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.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,6 +22,7 @@ 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
@@ -115,7 +118,7 @@ class LocalStorageAccessor(
) {
companion object {
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
fun fromFile(filesystemBasePath: Path, file: File): LocalStorageFilePair? {
if (!file.exists())
@@ -140,19 +143,19 @@ class LocalStorageAccessor(
size = filePath.fileSize(),
path = storageFilePath
)
_jackson.writeValue(metaFile, metaInfo)
jackson.writeValue(metaFile, metaInfo)
} else {
var readMeta: CommonMetaInfo
try {
val reader = metaFile.bufferedReader()
readMeta = _jackson.readValue(reader)
readMeta = jackson.readValue(reader)
} catch (e: JacksonException) {
// если файл повреждён - пересоздать
readMeta = CommonMetaInfo(
size = filePath.fileSize(),
path = storageFilePath
)
_jackson.writeValue(metaFile, readMeta)
jackson.writeValue(metaFile, readMeta)
}
metaInfo = readMeta
}
@@ -171,7 +174,7 @@ class LocalStorageAccessor(
var pair: LocalStorageFilePair? = null
try {
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 file = File(pathString)
if (!file.exists()) {
@@ -258,8 +261,8 @@ class LocalStorageAccessor(
var size = 0L
var numOfFiles = 0
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, CommonFile ->
size += CommonFile.metaInfo.size
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, commonFile ->
size += commonFile.metaInfo.size
numOfFiles++
if (numOfFiles % DATA_PAGE_LENGTH == 0) {
@@ -402,15 +405,17 @@ class LocalStorageAccessor(
override suspend fun setHidden(path: String, hidden: Boolean) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw Exception("Что то пошло не так") // TODO
if(pair.meta.isHidden == hidden)
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
)),
list = listOf(
CommonFile(
metaInfo = newMeta
)
),
pageLength = 1,
pageIndex = 0
)
@@ -419,31 +424,41 @@ class LocalStorageAccessor(
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 file = path.toFile()
if (file.exists() && file.isDirectory) {
if (file.exists() && file.isDirectory) {
throw Exception("Что то пошло не так") // TODO
} else {
} else if(!file.exists()) {
file.createNewFile()
val cur = _numberOfFiles.value
_numberOfFiles.value = if (cur == null) null else cur + 1
}
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)
_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 file = path.toFile()
if (file.exists() && !file.isDirectory) {
throw Exception("Что то пошло не так") // TODO
} else {
} else if(!file.exists()) {
Files.createDirectories(path)
}
@@ -451,11 +466,25 @@ class LocalStorageAccessor(
?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant())
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) {
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) {
@@ -474,7 +503,11 @@ class LocalStorageAccessor(
touchFile(path)
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: 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) {
@@ -519,6 +552,6 @@ class LocalStorageAccessor(
private const val SYSTEM_HIDDEN_DIRNAME = "wallenc-local-storage-meta-dir"
private const val META_INFO_POSTFIX = ".wallenc-meta"
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)
override val availableSpace: StateFlow<Int?> = _availableSpace
private val _path = MutableStateFlow<File?>(null)
private val path = MutableStateFlow<File?>(null)
init {
CoroutineScope(ioDispatcher).launch {
_path.value = context.getExternalFilesDir("LocalVault")
_isAvailable.value = _path.value != null
path.value = context.getExternalFilesDir("LocalVault")
_isAvailable.value = path.value != null
readStorages()
}
}
private suspend fun readStorages() {
val path = _path.value
val path = path.value
if (path == null || !_isAvailable.value)
return
@@ -59,7 +59,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
}
override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) {
val path = _path.value
val path = path.value
if (path == null || !_isAvailable.value)
throw Exception("Not available")