From e4bef8d4fee6bb3471f7e47d51d8546f7faba97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=8B=D1=82=D0=BA=D0=BE=D0=B2=20=D0=A0=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Wed, 25 Dec 2024 00:19:01 +0300 Subject: [PATCH] =?UTF-8?q?=D1=87=D1=82=D0=BE-=D1=82=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/vaults/local/LocalStorageAccessor.kt | 207 ++++++++++-------- .../data/vaults/local/entity/LocalMetaInfo.kt | 6 +- .../usecases/StorageFileManagementUseCase.kt | 6 + .../local/vault/LocalVaultViewModel.kt | 19 +- 4 files changed, 147 insertions(+), 91 deletions(-) diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt index 29c3ff1..95c3a14 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt @@ -1,7 +1,6 @@ package com.github.nullptroma.wallenc.data.vaults.local 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.readValue 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.models.IDirectory 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 kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -40,7 +38,6 @@ class LocalStorageAccessor( private val ioDispatcher: CoroutineDispatcher ) : IStorageAccessor { private val _absolutePath: Path = Path(absolutePath).normalize().absolute() - private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() } private val _size = MutableStateFlow(null) override val size: StateFlow = _size @@ -61,7 +58,7 @@ class LocalStorageAccessor( // запускам сканирование хранилища CoroutineScope(ioDispatcher).launch { Timber.d("Local storage path: $_absolutePath") - updateSizeAndNumOfFiles() + scanSizeAndNumOfFiles() } } @@ -107,12 +104,106 @@ class LocalStorageAccessor( } } } - } - else if (useCallbackForSelf) { + } else if (useCallbackForSelf) { 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 и возвращает с мета-информацией * @@ -128,101 +219,33 @@ class LocalStorageAccessor( val basePath = Path(_absolutePath.pathString, baseStoragePath) val workedFiles = mutableSetOf() val workedMetaFiles = mutableSetOf() - var count = 0 + scanFileSystem(basePath.toFile(), maxDepth, { file -> // Если парный файл уже был обработан - скип. Это позволит не читать metaFile дважды - if(workedFiles.contains(file.absolutePath) || workedMetaFiles.contains(file.absolutePath)) { - count++ + if (workedFiles.contains(file.absolutePath) || workedMetaFiles.contains(file.absolutePath)) { 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 (filePath.pathString.endsWith(META_INFO_POSTFIX)) { - // Если не удаётся прочитать метаданные или они указывают на несуществующий файл - удалить - 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 { - dirCallback?.invoke(originalFile, LocalDirectory(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) + if (pair.file.isFile) { + fileCallback?.invoke(pair.file, LocalFile(pair.meta)) } 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)) + dirCallback?.invoke(pair.file, LocalDirectory(pair.meta, 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 */ - private suspend fun updateSizeAndNumOfFiles() { + private suspend fun scanSizeAndNumOfFiles() { if (!checkAvailable()) { _size.value = null _numberOfFiles.value = null @@ -235,6 +258,11 @@ class LocalStorageAccessor( scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, localFile -> size += localFile.metaInfo.size numOfFiles++ + + if(numOfFiles % DATA_PAGE_LENGTH == 0) { + _size.value = size + _numberOfFiles.value = numOfFiles + } }) _size.value = size @@ -270,7 +298,7 @@ class LocalStorageAccessor( val buf = mutableListOf() var pageNumber = 0 scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, localFile -> - if(buf.size == DATA_PAGE_LENGTH) { + if (buf.size == DATA_PAGE_LENGTH) { val page = DataPage( list = buf.toList(), isLoading = false, @@ -297,7 +325,14 @@ class LocalStorageAccessor( }.flowOn(ioDispatcher) override suspend fun getAllDirs(): List = withContext(ioDispatcher) { - TODO("Not yet implemented") + if (!checkAvailable()) + return@withContext listOf() + + val list = mutableListOf() + scanStorage(baseStoragePath = "/", maxDepth = -1, dirCallback = { _, localDir -> + list.add(localDir) + }) + return@withContext list } override suspend fun getDirs(path: String): List = withContext(ioDispatcher) { diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt index 7563d62..a7397db 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt @@ -6,8 +6,8 @@ import java.time.Instant data class LocalMetaInfo( override val size: Long, - override val isDeleted: Boolean, - override val isHidden: Boolean, - override val lastModified: Instant, + override val isDeleted: Boolean = false, + override val isHidden: Boolean = false, + override val lastModified: Instant = java.time.Clock.systemUTC().instant(), override val path: String ) : IMetaInfo \ No newline at end of file diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt index fa6586e..82f71f7 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt @@ -1,5 +1,6 @@ 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.IStorage @@ -14,4 +15,9 @@ class StorageFileManagementUseCase { val storage = _storage ?: return listOf() return storage.accessor.getAllFiles() } + + suspend fun getAllDirs(): List { + val storage = _storage ?: return listOf() + return storage.accessor.getAllDirs() + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt index e7e83e1..aadd6e1 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt @@ -1,6 +1,10 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault +import android.app.Activity +import android.widget.Toast 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.usecases.GetAllRawStoragesUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase @@ -9,6 +13,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import kotlin.system.measureTimeMillis @HiltViewModel class LocalVaultViewModel @Inject constructor( @@ -30,11 +35,21 @@ class LocalVaultViewModel @Inject constructor( fun printAllFilesToLog(storage: IStorage) { _storageFileManagementUseCase.setStorage(storage) viewModelScope.launch { - val files = _storageFileManagementUseCase.getAllFiles() + val files: List + val dirs: List + val time = measureTimeMillis { + files = _storageFileManagementUseCase.getAllFiles() + dirs = _storageFileManagementUseCase.getAllDirs() + } for (file in files) { - Timber.tag("File") + Timber.tag("Files") Timber.d(file.metaInfo.toString()) } + for (dir in dirs) { + Timber.tag("Dirs") + Timber.d(dir.metaInfo.toString()) + } + Timber.d("Time: $time ms") } } } \ No newline at end of file