Локальное хранилище теперь читает файлы и создаёт .wallenc-meta

This commit is contained in:
Пытков Роман
2024-12-21 22:45:02 +03:00
parent cf443487ee
commit 577939e953
18 changed files with 324 additions and 102 deletions

View File

@@ -12,7 +12,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.github.nullptroma.wallenc.app" applicationId = "com.github.nullptroma.wallenc.app"
minSdk = 24 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"

View File

@@ -2,6 +2,7 @@ package com.github.nullptroma.wallenc.app.di.modules.domain
import com.github.nullptroma.wallenc.domain.models.IVaultsManager import com.github.nullptroma.wallenc.domain.models.IVaultsManager
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 dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@@ -16,4 +17,10 @@ class UseCasesModule {
fun provideGetAllRawStoragesUseCase(vaultsManager: IVaultsManager): GetAllRawStoragesUseCase { fun provideGetAllRawStoragesUseCase(vaultsManager: IVaultsManager): GetAllRawStoragesUseCase {
return GetAllRawStoragesUseCase(vaultsManager) return GetAllRawStoragesUseCase(vaultsManager)
} }
@Provides
@Singleton
fun provideStorageFileManagementUseCase(): StorageFileManagementUseCase {
return StorageFileManagementUseCase()
}
} }

View File

@@ -9,7 +9,7 @@ android {
compileSdk = 34 compileSdk = 34
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 26
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
@@ -34,6 +34,10 @@ android {
} }
dependencies { dependencies {
// jackson
implementation(libs.jackson.module.kotlin)
implementation(libs.jackson.datatype.jsr310)
// Timber // Timber
implementation(libs.timber) implementation(libs.timber)
@@ -45,9 +49,8 @@ dependencies {
// Retrofit // Retrofit
implementation(libs.retrofit) implementation(libs.retrofit)
implementation(libs.retrofit.converter.gson)
implementation(libs.retrofit.converter.scalars) implementation(libs.retrofit.converter.scalars)
implementation(libs.google.gson) implementation(libs.retrofit.converter.jackson)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)

View File

@@ -1,6 +1,14 @@
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.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
import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalFile
import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalMetaInfo
import com.github.nullptroma.wallenc.domain.datatypes.DataPackage 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.IDirectory
import com.github.nullptroma.wallenc.domain.models.IFile import com.github.nullptroma.wallenc.domain.models.IFile
import com.github.nullptroma.wallenc.domain.models.IStorageAccessor import com.github.nullptroma.wallenc.domain.models.IStorageAccessor
@@ -11,90 +19,256 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.nio.file.Path
import java.time.LocalDateTime
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.fileSize import kotlin.io.path.fileSize
import kotlin.io.path.pathString
import kotlin.io.path.relativeTo
class LocalStorageAccessor( class LocalStorageAccessor(
private val absolutePath: String, absolutePath: String,
private val ioDispatcher: CoroutineDispatcher private val ioDispatcher: CoroutineDispatcher
) : IStorageAccessor { ) : IStorageAccessor {
private val _size = MutableStateFlow<Long?>(null) private val _absolutePath: Path = Path(absolutePath).normalize().absolute()
private val _numberOfFiles = MutableStateFlow<Int?>(null) private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
private val _isAvailable = MutableStateFlow(false)
private val _filesUpdates = MutableSharedFlow<DataPackage<IFile>>()
private val _dirsUpdates = MutableSharedFlow<DataPackage<IDirectory>>()
override val size: StateFlow<Long?> private val _size = MutableStateFlow<Long?>(null)
get() = _size override val size: StateFlow<Long?> = _size
override val numberOfFiles: StateFlow<Int?>
get() = _numberOfFiles private val _numberOfFiles = MutableStateFlow<Int?>(null)
override val isAvailable: StateFlow<Boolean> override val numberOfFiles: StateFlow<Int?> = _numberOfFiles
get() = _isAvailable
override val filesUpdates: SharedFlow<DataPackage<IFile>> private val _isAvailable = MutableStateFlow(false)
get() = _filesUpdates override val isAvailable: StateFlow<Boolean> = _isAvailable
override val dirsUpdates: SharedFlow<DataPackage<IDirectory>>
get() = _dirsUpdates private val _filesUpdates = MutableSharedFlow<DataPackage<IFile>>()
override val filesUpdates: SharedFlow<DataPackage<IFile>> = _filesUpdates
private val _dirsUpdates = MutableSharedFlow<DataPackage<IDirectory>>()
override val dirsUpdates: SharedFlow<DataPackage<IDirectory>> = _dirsUpdates
init { init {
// запускам сканирование хранилища
CoroutineScope(ioDispatcher).launch { CoroutineScope(ioDispatcher).launch {
scanStorage() Timber.d("Local storage path: $_absolutePath")
updateSizeAndNumOfFiles()
} }
} }
/**
* Проверяет существование корневого пути Storage в файловой системе, изменяет _isAvailable
*/
private fun checkAvailable(): Boolean { private fun checkAvailable(): Boolean {
_isAvailable.value = File(absolutePath).exists() _isAvailable.value = _absolutePath.toFile().exists()
return _isAvailable.value return _isAvailable.value
} }
private fun forAllFiles(dir: File, callback: (File) -> Unit) { /**
if (dir.exists() == false) * Перебирает все файлы в файловой системе
* @param dir стартовый каталог
* @param maxDepth максимальная глубина (отрицательное для бесконечной)
* @param callback метод обратного вызова для каждого файла и директории
*/
private suspend fun scanFileSystem(
dir: File,
maxDepth: Int,
callback: suspend (File) -> Unit,
useCallbackForSelf: Boolean = true
) {
if (!dir.exists())
return return
val children = dir.listFiles()
if (children != null) {
// вызвать коллбек для каждого элемента директории
for (child in children) {
callback(child)
}
if (maxDepth != 0) {
val nextMaxDepth = if (maxDepth > 0) maxDepth - 1 else maxDepth
for (child in children) {
if (child.isDirectory) {
scanFileSystem(child, nextMaxDepth, callback, false)
}
}
}
}
if (useCallbackForSelf)
callback(dir) callback(dir)
val nextDirs = dir.listFiles()
if (nextDirs != null) {
for (nextDir in nextDirs) {
forAllFiles(nextDir, callback)
}
}
} }
private suspend fun scanStorage() = withContext(ioDispatcher) { /**
_isAvailable.value = File(absolutePath).exists() * Перебирает все файлы и каталоги в relativePath и возвращает с мета-информацией
*
*/
private suspend fun scanStorage(
baseStoragePath: String,
maxDepth: Int,
fileCallback: suspend (LocalFile) -> Unit = {},
dirCallback: suspend (LocalDirectory) -> Unit = {}
) {
if (!checkAvailable())
throw Exception("Not available")
val basePath = Path(_absolutePath.pathString, baseStoragePath)
scanFileSystem(basePath.toFile(), maxDepth, { file ->
val filePath = Path(file.absolutePath)
// если это файл с мета-информацией - пропустить
if (filePath.pathString.endsWith(META_INFO_POSTFIX)) {
// Если не удаётся прочитать метаданные или они указывают на несуществующий файл - удалить
try {
val reader = file.bufferedReader()
val meta : LocalMetaInfo = _jackson.readValue(reader)
val fileInMeta = File(Path(_absolutePath.pathString, meta.path).pathString)
if (!fileInMeta.exists())
file.delete()
} catch (e: JacksonException) {
file.delete()
}
return@scanFileSystem
}
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
}
if (file.isFile) {
fileCallback(LocalFile(metaInfo))
} else {
dirCallback(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 = LocalDateTime.now(),
path = storagePath
)
}
/**
* Считает файлы и их размер. Не бросает исключения, если файлы недоступны
* @throws none Если возникла ошибка, оставляет размер и количества файлов равными null
*/
private suspend fun updateSizeAndNumOfFiles() {
if (!checkAvailable()) {
_size.value = null
_numberOfFiles.value = null
return
}
var size = 0L var size = 0L
var numOfFiles = 0 var numOfFiles = 0
forAllFiles(File(absolutePath)) { scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = {
if (it.isFile) { size += it.metaInfo.size
numOfFiles++ numOfFiles++
size += Path(it.path).fileSize() })
}
}
_size.value = size _size.value = size
_numberOfFiles.value = numOfFiles _numberOfFiles.value = numOfFiles
} }
override suspend fun getAllFiles(): List<IFile> = withContext(ioDispatcher) { override suspend fun getAllFiles(): List<IFile> = withContext(ioDispatcher) {
if(checkAvailable() == false) if (!checkAvailable())
return@withContext listOf() return@withContext listOf()
val list = mutableListOf<IFile>() val list = mutableListOf<IFile>()
return@withContext listOf() scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = {
list.add(it)
})
return@withContext list
} }
override suspend fun getFiles(path: String): List<IFile> = withContext(ioDispatcher) { override suspend fun getFiles(path: String): List<IFile> = withContext(ioDispatcher) {
TODO("Not yet implemented") if (!checkAvailable())
return@withContext listOf()
val list = mutableListOf<IFile>()
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = {
list.add(it)
})
return@withContext list
} }
override fun getFilesFlow(path: String): Flow<DataPackage<IFile>> { override fun getFilesFlow(path: String): Flow<DataPackage<List<IFile>>> = flow {
TODO("Not yet implemented") if (!checkAvailable())
return@flow
val buf = mutableListOf<IFile>()
var pageNumber = 0
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = {
if(buf.size == DATA_PAGE_LENGTH) {
val page = DataPage(
list = buf.toList(),
isLoading = false,
isError = false,
hasNext = true,
pageLength = DATA_PAGE_LENGTH,
pageIndex = pageNumber++
)
emit(page)
buf.clear()
} }
buf.add(it)
})
// отправка последней страницы
val page = DataPage(
list = buf.toList(),
isLoading = false,
isError = false,
hasNext = false,
pageLength = DATA_PAGE_LENGTH,
pageIndex = pageNumber++
)
emit(page)
}.flowOn(ioDispatcher)
override suspend fun getAllDirs(): List<IDirectory> = withContext(ioDispatcher) { override suspend fun getAllDirs(): List<IDirectory> = withContext(ioDispatcher) {
TODO("Not yet implemented") TODO("Not yet implemented")
@@ -104,7 +278,7 @@ class LocalStorageAccessor(
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun getDirsFlow(path: String): Flow<DataPackage<IDirectory>> { override fun getDirsFlow(path: String): Flow<DataPackage<List<IDirectory>>> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@@ -139,4 +313,9 @@ class LocalStorageAccessor(
override suspend fun moveToTrash(path: String) = withContext(ioDispatcher) { override suspend fun moveToTrash(path: String) = withContext(ioDispatcher) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
companion object {
private const val META_INFO_POSTFIX = ".wallenc-meta"
private const val DATA_PAGE_LENGTH = 10
}
} }

View File

@@ -18,12 +18,23 @@ import kotlin.io.path.createDirectory
import kotlin.io.path.pathString import kotlin.io.path.pathString
class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context) : IVault { class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context) : IVault {
private val _path = MutableStateFlow<File?>(null) override val type: VaultType = VaultType.LOCAL
private val _storages = MutableStateFlow(listOf<IStorage>()) override val uuid: UUID
private val _totalSpace = MutableStateFlow(null) get() = TODO("Not yet implemented")
private val _availableSpace = MutableStateFlow(null)
private val _isAvailable = MutableStateFlow(false)
private val _storages = MutableStateFlow(listOf<IStorage>())
override val storages: StateFlow<List<IStorage>> = _storages
private val _isAvailable = MutableStateFlow(false)
override val isAvailable: StateFlow<Boolean> = _isAvailable
private val _totalSpace = MutableStateFlow(null)
override val totalSpace: StateFlow<Int?> = _totalSpace
private val _availableSpace = MutableStateFlow(null)
override val availableSpace: StateFlow<Int?> = _availableSpace
private val _path = MutableStateFlow<File?>(null)
init { init {
CoroutineScope(ioDispatcher).launch { CoroutineScope(ioDispatcher).launch {
@@ -35,7 +46,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
private fun readStorages() { private fun readStorages() {
val path = _path.value val path = _path.value
if (path == null || _isAvailable.value == false) if (path == null || !_isAvailable.value)
return return
val dirs = path.listFiles()?.filter { it.isDirectory } val dirs = path.listFiles()?.filter { it.isDirectory }
@@ -49,7 +60,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
override suspend fun createStorage(): IStorage = withContext(ioDispatcher) { override suspend fun createStorage(): IStorage = withContext(ioDispatcher) {
val path = _path.value val path = _path.value
if (path == null || _isAvailable.value == false) if (path == null || !_isAvailable.value)
throw Exception("Not available") throw Exception("Not available")
val uuid = UUID.randomUUID() val uuid = UUID.randomUUID()
@@ -78,16 +89,4 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) { override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override val type: VaultType = VaultType.LOCAL
override val uuid: UUID
get() = TODO("Not yet implemented")
override val storages: StateFlow<List<IStorage>>
get() = _storages
override val isAvailable: StateFlow<Boolean>
get() = _isAvailable
override val totalSpace: StateFlow<Int?>
get() = _totalSpace
override val availableSpace: StateFlow<Int?>
get() = _availableSpace
} }

View File

@@ -2,7 +2,7 @@ package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.models.IDirectory import com.github.nullptroma.wallenc.domain.models.IDirectory
class LocalDirectory( data class LocalDirectory(
override val metaInfo: LocalMetaInfo, override val metaInfo: LocalMetaInfo,
override val elementsCount: Int override val elementsCount: Int?
) : IDirectory ) : IDirectory

View File

@@ -2,4 +2,4 @@ package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.models.IFile import com.github.nullptroma.wallenc.domain.models.IFile
class LocalFile(override val metaInfo: LocalMetaInfo) : IFile data class LocalFile(override val metaInfo: LocalMetaInfo) : IFile

View File

@@ -3,20 +3,10 @@ package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.models.IMetaInfo import com.github.nullptroma.wallenc.domain.models.IMetaInfo
import java.time.LocalDateTime import java.time.LocalDateTime
class LocalMetaInfo : IMetaInfo { data class LocalMetaInfo(
override val name: String override val size: Long,
override val size: Int override val isDeleted: Boolean,
get() = TODO("Not yet implemented") override val isHidden: Boolean,
override val isDeleted: Boolean override val lastModified: LocalDateTime,
get() = TODO("Not yet implemented")
override val isHidden: Boolean
get() = TODO("Not yet implemented")
override val lastModified: LocalDateTime
get() = TODO("Not yet implemented")
override val path: String override val path: String
get() = TODO("Not yet implemented") ) : IMetaInfo
init {
name = ""
}
}

View File

@@ -1,6 +1,6 @@
package com.github.nullptroma.wallenc.domain.datatypes package com.github.nullptroma.wallenc.domain.datatypes
open class DataPackage<T>( sealed class DataPackage<T>(
val data: T, val data: T,
val isLoading: Boolean? = false, val isLoading: Boolean? = false,
val isError: Boolean? = false val isError: Boolean? = false

View File

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

View File

@@ -2,5 +2,5 @@ package com.github.nullptroma.wallenc.domain.models
interface IDirectory { interface IDirectory {
val metaInfo: IMetaInfo val metaInfo: IMetaInfo
val elementsCount: Int val elementsCount: Int?
} }

View File

@@ -4,8 +4,7 @@ import java.time.LocalDateTime
interface IMetaInfo { interface IMetaInfo {
val name: String val size: Long
val size: Int
val isDeleted: Boolean val isDeleted: Boolean
val isHidden: Boolean val isHidden: Boolean
val lastModified: LocalDateTime val lastModified: LocalDateTime

View File

@@ -21,7 +21,7 @@ interface IStorageAccessor {
* @param path Путь к директории * @param path Путь к директории
* @return Поток файлов * @return Поток файлов
*/ */
fun getFilesFlow(path: String): Flow<DataPackage<IFile>> fun getFilesFlow(path: String): Flow<DataPackage<List<IFile>>>
suspend fun getAllDirs(): List<IDirectory> suspend fun getAllDirs(): List<IDirectory>
suspend fun getDirs(path: String): List<IDirectory> suspend fun getDirs(path: String): List<IDirectory>
@@ -30,7 +30,7 @@ interface IStorageAccessor {
* @param path Путь к директории * @param path Путь к директории
* @return Поток директорий * @return Поток директорий
*/ */
fun getDirsFlow(path: String): Flow<DataPackage<IDirectory>> fun getDirsFlow(path: String): Flow<DataPackage<List<IDirectory>>>
suspend fun touchFile(path: String) suspend fun touchFile(path: String)
suspend fun touchDir(path: String) suspend fun touchDir(path: String)

View File

@@ -0,0 +1,17 @@
package com.github.nullptroma.wallenc.domain.usecases
import com.github.nullptroma.wallenc.domain.models.IFile
import com.github.nullptroma.wallenc.domain.models.IStorage
class StorageFileManagementUseCase {
private var _storage: IStorage? = null
fun setStorage(storage: IStorage) {
_storage = storage
}
suspend fun getAllFiles(): List<IFile> {
val storage = _storage ?: return listOf()
return storage.accessor.getAllFiles()
}
}

View File

@@ -1,5 +1,6 @@
[versions] [versions]
agp = "8.7.1" agp = "8.7.1"
jacksonModuleKotlin = "2.18.2"
kotlin = "2.0.10" kotlin = "2.0.10"
coreKtx = "1.15.0" coreKtx = "1.15.0"
junit = "4.13.2" junit = "4.13.2"
@@ -19,12 +20,13 @@ daggerHilt = "2.52"
ksp = "2.0.10-1.0.24" ksp = "2.0.10-1.0.24"
room = "2.6.1" room = "2.6.1"
retrofit = "2.11.0" retrofit = "2.11.0"
gson = "2.11.0"
appcompat = "1.7.0" appcompat = "1.7.0"
material = "1.12.0" material = "1.12.0"
runtimeAndroid = "1.7.5" runtimeAndroid = "1.7.5"
[libraries] [libraries]
jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" }
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonModuleKotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinReflect" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinReflect" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
@@ -47,8 +49,7 @@ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref =
# Retrofit # Retrofit
retrofit = { group="com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"} retrofit = { group="com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"}
retrofit-converter-scalars = { group="com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit"} retrofit-converter-scalars = { group="com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit"}
retrofit-converter-gson = { group="com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit"} retrofit-converter-jackson = { group="com.squareup.retrofit2", name = "converter-jackson", version.ref = "retrofit"}
google-gson = { group="com.google.code.gson", name = "gson", version.ref = "gson"}
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -74,8 +75,9 @@ androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "daggerHilt" } dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "daggerHilt" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
android-library = { id = "com.android.library", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-parcelize = { id = "kotlin-parcelize" }

View File

@@ -4,7 +4,7 @@ plugins {
alias(libs.plugins.compose.compiler) alias(libs.plugins.compose.compiler)
alias(libs.plugins.dagger.hilt) alias(libs.plugins.dagger.hilt)
alias(libs.plugins.jetbrains.kotlin.serialization) alias(libs.plugins.jetbrains.kotlin.serialization)
id("kotlin-parcelize") alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
} }

View File

@@ -1,5 +1,6 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@@ -7,6 +8,7 @@ import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@@ -20,12 +22,17 @@ fun LocalVaultScreen(modifier: Modifier = Modifier,
val uiState by viewModel.state.collectAsStateWithLifecycle() val uiState by viewModel.state.collectAsStateWithLifecycle()
LazyColumn(modifier = modifier) { LazyColumn(modifier = modifier) {
items(uiState.storagesList) { items(uiState.storagesList) {
Card { Card(modifier = Modifier.clickable {
viewModel.printAllFilesToLog(it)
}) {
val available = it.isAvailable.collectAsStateWithLifecycle()
val numOfFiles = it.isAvailable.collectAsStateWithLifecycle()
val size = it.isAvailable.collectAsStateWithLifecycle()
Column { Column {
Text(it.uuid.toString()) Text(it.uuid.toString())
Text("IsAvailable: ${it.isAvailable.value}") Text("IsAvailable: $available")
Text("Files: ${it.numberOfFiles.value}") Text("Files: $numOfFiles")
Text("Size: ${it.size.value}") Text("Size: $size")
} }
} }
} }

View File

@@ -1,18 +1,24 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
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.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.launch import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class LocalVaultViewModel @Inject constructor(private val getAllRawStoragesUseCase: GetAllRawStoragesUseCase) : class LocalVaultViewModel @Inject constructor(
private val _getAllRawStoragesUseCase: GetAllRawStoragesUseCase,
private val _storageFileManagementUseCase: StorageFileManagementUseCase
) :
ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) { ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) {
init { init {
viewModelScope.launch { viewModelScope.launch {
getAllRawStoragesUseCase.localStorage.storages.collect { _getAllRawStoragesUseCase.localStorage.storages.collect {
val newState = state.value.copy( val newState = state.value.copy(
storagesList = it storagesList = it
) )
@@ -20,4 +26,15 @@ class LocalVaultViewModel @Inject constructor(private val getAllRawStoragesUseCa
} }
} }
} }
fun printAllFilesToLog(storage: IStorage) {
_storageFileManagementUseCase.setStorage(storage)
viewModelScope.launch {
val files = _storageFileManagementUseCase.getAllFiles()
for (file in files) {
Timber.tag("File")
Timber.d(file.metaInfo.toString())
}
}
}
} }