что-то
This commit is contained in:
@@ -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 {
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
var readMeta: LocalMetaInfo
|
dirCallback?.invoke(pair.file, LocalDirectory(pair.meta, null))
|
||||||
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
|
||||||
@@ -270,7 +298,7 @@ class LocalStorageAccessor(
|
|||||||
val buf = mutableListOf<IFile>()
|
val buf = mutableListOf<IFile>()
|
||||||
var pageNumber = 0
|
var pageNumber = 0
|
||||||
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, localFile ->
|
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, localFile ->
|
||||||
if(buf.size == DATA_PAGE_LENGTH) {
|
if (buf.size == DATA_PAGE_LENGTH) {
|
||||||
val page = DataPage(
|
val page = DataPage(
|
||||||
list = buf.toList(),
|
list = buf.toList(),
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user