что-то

This commit is contained in:
Пытков Роман
2024-12-25 00:19:01 +03:00
parent e576f4272f
commit e4bef8d4fe
4 changed files with 147 additions and 91 deletions

View File

@@ -1,7 +1,6 @@
package com.github.nullptroma.wallenc.data.vaults.local package com.github.nullptroma.wallenc.data.vaults.local
import com.fasterxml.jackson.core.JacksonException import com.fasterxml.jackson.core.JacksonException
import com.fasterxml.jackson.core.JsonParseException
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.vaults.local.entity.LocalDirectory import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalDirectory
@@ -11,7 +10,6 @@ import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.DataPage import com.github.nullptroma.wallenc.domain.datatypes.DataPage
import com.github.nullptroma.wallenc.domain.models.IDirectory import com.github.nullptroma.wallenc.domain.models.IDirectory
import com.github.nullptroma.wallenc.domain.models.IFile import com.github.nullptroma.wallenc.domain.models.IFile
import com.github.nullptroma.wallenc.domain.models.IMetaInfo
import com.github.nullptroma.wallenc.domain.models.IStorageAccessor import com.github.nullptroma.wallenc.domain.models.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -40,7 +38,6 @@ class LocalStorageAccessor(
private val ioDispatcher: CoroutineDispatcher private val ioDispatcher: CoroutineDispatcher
) : IStorageAccessor { ) : IStorageAccessor {
private val _absolutePath: Path = Path(absolutePath).normalize().absolute() private val _absolutePath: Path = Path(absolutePath).normalize().absolute()
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
private val _size = MutableStateFlow<Long?>(null) private val _size = MutableStateFlow<Long?>(null)
override val size: StateFlow<Long?> = _size override val size: StateFlow<Long?> = _size
@@ -61,7 +58,7 @@ class LocalStorageAccessor(
// запускам сканирование хранилища // запускам сканирование хранилища
CoroutineScope(ioDispatcher).launch { CoroutineScope(ioDispatcher).launch {
Timber.d("Local storage path: $_absolutePath") Timber.d("Local storage path: $_absolutePath")
updateSizeAndNumOfFiles() scanSizeAndNumOfFiles()
} }
} }
@@ -107,12 +104,106 @@ class LocalStorageAccessor(
} }
} }
} }
} } else if (useCallbackForSelf) {
else if (useCallbackForSelf) {
callback(dir) callback(dir)
} }
} }
private class LocalStorageFilePair private constructor(
val file: File,
val metaFile: File,
val meta: LocalMetaInfo
) {
companion object {
private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
fun fromFile(basePath: Path, file: File): LocalStorageFilePair? {
if (!file.exists())
return null
if (file.name.endsWith(META_INFO_POSTFIX))
return fromMetaFile(basePath, file)
val filePath = file.toPath()
val metaFilePath = Path(
if (file.isFile) {
file.absolutePath + META_INFO_POSTFIX
} else {
Path(file.absolutePath, META_INFO_POSTFIX).pathString
}
)
val metaFile = metaFilePath.toFile()
val metaInfo: LocalMetaInfo
val storageFilePath = "/" + filePath.relativeTo(basePath)
if (!metaFile.exists()) {
metaInfo = LocalMetaInfo(
size = filePath.fileSize(),
path = storageFilePath
)
_jackson.writeValue(metaFile, metaInfo)
} else {
var readMeta: LocalMetaInfo
try {
val reader = metaFile.bufferedReader()
readMeta = _jackson.readValue(reader)
} catch (e: JacksonException) {
// если файл повреждён - пересоздать
readMeta = LocalMetaInfo(
size = filePath.fileSize(),
path = storageFilePath
)
_jackson.writeValue(metaFile, readMeta)
}
metaInfo = readMeta
}
return LocalStorageFilePair(
file = file,
metaFile = metaFile,
meta = metaInfo
)
}
fun fromMetaFile(basePath: Path, metaFile: File): LocalStorageFilePair? {
if (!metaFile.exists())
return null
if (!metaFile.name.endsWith(META_INFO_POSTFIX))
return fromFile(basePath, metaFile)
var pair: LocalStorageFilePair? = null
try {
val reader = metaFile.bufferedReader()
val metaInfo: LocalMetaInfo = _jackson.readValue(reader)
val pathString = Path(basePath.pathString, metaInfo.path).pathString
val file = File(pathString)
if (!file.exists()) {
metaFile.delete()
} else {
pair = LocalStorageFilePair(
file = file,
metaFile = metaFile,
meta = metaInfo
)
}
} catch (e: JacksonException) {
metaFile.delete()
}
return pair
}
fun from(basePath: Path, anyFile: File): LocalStorageFilePair? {
return if (anyFile.name.endsWith(META_INFO_POSTFIX))
fromMetaFile(basePath, anyFile)
else
fromFile(basePath, anyFile)
}
fun from(basePath: Path, storagePath: String): LocalStorageFilePair? {
val filePath = Path(basePath.pathString, storagePath)
return from(basePath, filePath.toFile())
}
}
}
/** /**
* Перебирает все файлы и каталоги в relativePath и возвращает с мета-информацией * Перебирает все файлы и каталоги в relativePath и возвращает с мета-информацией
* *
@@ -128,101 +219,33 @@ class LocalStorageAccessor(
val basePath = Path(_absolutePath.pathString, baseStoragePath) val basePath = Path(_absolutePath.pathString, baseStoragePath)
val workedFiles = mutableSetOf<String>() val workedFiles = mutableSetOf<String>()
val workedMetaFiles = mutableSetOf<String>() val workedMetaFiles = mutableSetOf<String>()
var count = 0
scanFileSystem(basePath.toFile(), maxDepth, { file -> scanFileSystem(basePath.toFile(), maxDepth, { file ->
// Если парный файл уже был обработан - скип. Это позволит не читать metaFile дважды // Если парный файл уже был обработан - скип. Это позволит не читать metaFile дважды
if (workedFiles.contains(file.absolutePath) || workedMetaFiles.contains(file.absolutePath)) { if (workedFiles.contains(file.absolutePath) || workedMetaFiles.contains(file.absolutePath)) {
count++
return@scanFileSystem return@scanFileSystem
} }
val filePath = Path(file.absolutePath) val pair = LocalStorageFilePair.from(_absolutePath, file)
if(pair != null) {
workedFiles.add(pair.file.absolutePath)
workedMetaFiles.add(pair.metaFile.absolutePath)
// если это файл с мета-информацией - пропустить if (pair.file.isFile) {
if (filePath.pathString.endsWith(META_INFO_POSTFIX)) { fileCallback?.invoke(pair.file, LocalFile(pair.meta))
// Если не удаётся прочитать метаданные или они указывают на несуществующий файл - удалить
try {
val reader = file.bufferedReader()
val meta : LocalMetaInfo = _jackson.readValue(reader)
val pathString = Path(_absolutePath.pathString, meta.path).pathString
val originalFile = File(pathString)
if (!originalFile.exists())
file.delete()
// если успешно прочитано - отправить колбек и добавить обработанный файл
workedFiles.add(pathString)
workedMetaFiles.add(file.absolutePath)
if (file.isFile) {
fileCallback?.invoke(originalFile, LocalFile(meta))
} else { } else {
dirCallback?.invoke(originalFile, LocalDirectory(meta, null)) dirCallback?.invoke(pair.file, LocalDirectory(pair.meta, null))
}
} catch (e: JacksonException) {
file.delete()
}
return@scanFileSystem
}
else {
val metaFilePath = Path(
if (file.isFile) {
file.absolutePath + META_INFO_POSTFIX
} else {
Path(file.absolutePath, META_INFO_POSTFIX).pathString
}
)
val metaFile = metaFilePath.toFile()
val metaInfo: LocalMetaInfo
val storageFilePath = "/" + filePath.relativeTo(_absolutePath)
if (!metaFile.exists()) {
metaInfo = createNewLocalMetaInfo(storageFilePath, filePath.fileSize())
_jackson.writeValue(metaFile, metaInfo)
} else {
var readMeta: LocalMetaInfo
try {
val reader = metaFile.bufferedReader()
readMeta = _jackson.readValue(reader)
} catch (e: JacksonException) {
// если файл повреждён - пересоздать
readMeta = createNewLocalMetaInfo(storageFilePath, filePath.fileSize())
_jackson.writeValue(metaFile, readMeta)
}
metaInfo = readMeta
}
workedFiles.add(file.absolutePath)
workedMetaFiles.add(metaFile.absolutePath)
if (file.isFile) {
fileCallback?.invoke(file, LocalFile(metaInfo))
} else {
dirCallback?.invoke(file, LocalDirectory(metaInfo, null))
} }
} }
}) })
} }
/**
* Создаёт LocalMetaInfo, не требуя наличие файла в файловой системе
* @param storagePath полный путь в Storage
* @param size размер файла
*/
private fun createNewLocalMetaInfo(storagePath: String, size: Long): LocalMetaInfo {
return LocalMetaInfo(
size = size,
isDeleted = false,
isHidden = false,
lastModified = java.time.Clock.systemUTC().instant(),
path = storagePath
)
}
/** /**
* Считает файлы и их размер. Не бросает исключения, если файлы недоступны * Считает файлы и их размер. Не бросает исключения, если файлы недоступны
* @throws none Если возникла ошибка, оставляет размер и количества файлов равными null * @throws none Если возникла ошибка, оставляет размер и количества файлов равными null
*/ */
private suspend fun updateSizeAndNumOfFiles() { private suspend fun scanSizeAndNumOfFiles() {
if (!checkAvailable()) { if (!checkAvailable()) {
_size.value = null _size.value = null
_numberOfFiles.value = null _numberOfFiles.value = null
@@ -235,6 +258,11 @@ class LocalStorageAccessor(
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, localFile -> scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, localFile ->
size += localFile.metaInfo.size size += localFile.metaInfo.size
numOfFiles++ numOfFiles++
if(numOfFiles % DATA_PAGE_LENGTH == 0) {
_size.value = size
_numberOfFiles.value = numOfFiles
}
}) })
_size.value = size _size.value = size
@@ -297,7 +325,14 @@ class LocalStorageAccessor(
}.flowOn(ioDispatcher) }.flowOn(ioDispatcher)
override suspend fun getAllDirs(): List<IDirectory> = withContext(ioDispatcher) { override suspend fun getAllDirs(): List<IDirectory> = withContext(ioDispatcher) {
TODO("Not yet implemented") if (!checkAvailable())
return@withContext listOf()
val list = mutableListOf<IDirectory>()
scanStorage(baseStoragePath = "/", maxDepth = -1, dirCallback = { _, localDir ->
list.add(localDir)
})
return@withContext list
} }
override suspend fun getDirs(path: String): List<IDirectory> = withContext(ioDispatcher) { override suspend fun getDirs(path: String): List<IDirectory> = withContext(ioDispatcher) {

View File

@@ -6,8 +6,8 @@ import java.time.Instant
data class LocalMetaInfo( data class LocalMetaInfo(
override val size: Long, override val size: Long,
override val isDeleted: Boolean, override val isDeleted: Boolean = false,
override val isHidden: Boolean, override val isHidden: Boolean = false,
override val lastModified: Instant, override val lastModified: Instant = java.time.Clock.systemUTC().instant(),
override val path: String override val path: String
) : IMetaInfo ) : IMetaInfo

View File

@@ -1,5 +1,6 @@
package com.github.nullptroma.wallenc.domain.usecases package com.github.nullptroma.wallenc.domain.usecases
import com.github.nullptroma.wallenc.domain.models.IDirectory
import com.github.nullptroma.wallenc.domain.models.IFile import com.github.nullptroma.wallenc.domain.models.IFile
import com.github.nullptroma.wallenc.domain.models.IStorage import com.github.nullptroma.wallenc.domain.models.IStorage
@@ -14,4 +15,9 @@ class StorageFileManagementUseCase {
val storage = _storage ?: return listOf() val storage = _storage ?: return listOf()
return storage.accessor.getAllFiles() return storage.accessor.getAllFiles()
} }
suspend fun getAllDirs(): List<IDirectory> {
val storage = _storage ?: return listOf()
return storage.accessor.getAllDirs()
}
} }

View File

@@ -1,6 +1,10 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import android.app.Activity
import android.widget.Toast
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.models.IDirectory
import com.github.nullptroma.wallenc.domain.models.IFile
import com.github.nullptroma.wallenc.domain.models.IStorage import com.github.nullptroma.wallenc.domain.models.IStorage
import com.github.nullptroma.wallenc.domain.usecases.GetAllRawStoragesUseCase import com.github.nullptroma.wallenc.domain.usecases.GetAllRawStoragesUseCase
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
@@ -9,6 +13,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis
@HiltViewModel @HiltViewModel
class LocalVaultViewModel @Inject constructor( class LocalVaultViewModel @Inject constructor(
@@ -30,11 +35,21 @@ class LocalVaultViewModel @Inject constructor(
fun printAllFilesToLog(storage: IStorage) { fun printAllFilesToLog(storage: IStorage) {
_storageFileManagementUseCase.setStorage(storage) _storageFileManagementUseCase.setStorage(storage)
viewModelScope.launch { viewModelScope.launch {
val files = _storageFileManagementUseCase.getAllFiles() val files: List<IFile>
val dirs: List<IDirectory>
val time = measureTimeMillis {
files = _storageFileManagementUseCase.getAllFiles()
dirs = _storageFileManagementUseCase.getAllDirs()
}
for (file in files) { for (file in files) {
Timber.tag("File") Timber.tag("Files")
Timber.d(file.metaInfo.toString()) Timber.d(file.metaInfo.toString())
} }
for (dir in dirs) {
Timber.tag("Dirs")
Timber.d(dir.metaInfo.toString())
}
Timber.d("Time: $time ms")
} }
} }
} }