Начало LocalVault

This commit is contained in:
Roman Pytkov
2024-12-20 19:57:42 +03:00
parent 576fc4020c
commit a481868039
18 changed files with 246 additions and 92 deletions

View File

@@ -1,23 +0,0 @@
package com.github.nullptroma.wallenc.data
import com.github.nullptroma.wallenc.domain.models.IStorage
import com.github.nullptroma.wallenc.domain.models.IStorageAccessor
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
class MockStorage(
override val size: StateFlow<Int?> = MutableStateFlow(null),
override val numberOfFiles: StateFlow<Int?> = MutableStateFlow(null),
override val uuid: UUID,
override val name: StateFlow<String> = MutableStateFlow(""),
override val totalSpace: StateFlow<Int?> = MutableStateFlow(null),
override val availableSpace: StateFlow<Int?> = MutableStateFlow(null),
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(false),
override val accessor: IStorageAccessor
) : IStorage {
override suspend fun rename(newName: String) {
TODO("Not yet implemented")
}
}

View File

@@ -0,0 +1,7 @@
package com.github.nullptroma.wallenc.data.vaults
interface IStorageCallbackHandler {
fun changeSize(delta: Int)
fun changeNumOfFiles(delta: Int)
}

View File

@@ -0,0 +1,28 @@
package com.github.nullptroma.wallenc.data.vaults.local
import com.github.nullptroma.wallenc.domain.models.IStorage
import com.github.nullptroma.wallenc.domain.models.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
class LocalStorage(
override val uuid: UUID,
absolutePath: String,
ioDispatcher: CoroutineDispatcher
) : IStorage {
override val size: StateFlow<Long?>
get() = accessor.size
override val numberOfFiles: StateFlow<Int?>
get() = accessor.numberOfFiles
override val name: StateFlow<String>
get() = TODO("Добавить класс в Domain, который с помощью accessor будет читать и сохранять имя в скрытую папку")
override val isAvailable: StateFlow<Boolean>
get() = accessor.isAvailable
override val accessor: IStorageAccessor = LocalStorageAccessor(absolutePath, ioDispatcher)
override suspend fun rename(newName: String) {
TODO("Not yet implemented")
}
}

View File

@@ -4,30 +4,92 @@ import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.models.IDirectory
import com.github.nullptroma.wallenc.domain.models.IFile
import com.github.nullptroma.wallenc.domain.models.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.net.URI
import kotlin.io.path.Path
import kotlin.io.path.fileSize
class LocalStorageAccessor : IStorageAccessor {
class LocalStorageAccessor(
private val absolutePath: String,
private val ioDispatcher: CoroutineDispatcher
) : IStorageAccessor {
private val _size = MutableStateFlow<Long?>(null)
private val _numberOfFiles = MutableStateFlow<Int?>(null)
private val _isAvailable = MutableStateFlow(false)
override val size: StateFlow<Long?>
get() = _size
override val numberOfFiles: StateFlow<Int?>
get() = _numberOfFiles
override val isAvailable: StateFlow<Boolean>
get() = TODO("Not yet implemented")
get() = _isAvailable
override val filesUpdates: SharedFlow<DataPackage<IFile>>
get() = TODO("Not yet implemented")
override val dirsUpdates: SharedFlow<DataPackage<IDirectory>>
get() = TODO("Not yet implemented")
init {
CoroutineScope(ioDispatcher).launch {
scanStorage()
}
}
private fun checkAvailable(): Boolean {
_isAvailable.value = File(absolutePath).exists()
return _isAvailable.value
}
private fun forAllFiles(dir: File, callback: (File) -> Unit) {
if (dir.exists() == false)
return
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()
var size = 0L
var numOfFiles = 0
forAllFiles(File(absolutePath)) {
if (it.isFile) {
numOfFiles++
size += Path(it.path).fileSize()
}
}
_size.value = size
_numberOfFiles.value = numOfFiles
}
override suspend fun getAllFiles(): List<IFile> {
if(checkAvailable() == false)
return listOf()
val list = mutableListOf<IFile>()
}
override suspend fun getFiles(path: String): List<IFile> {
TODO("Not yet implemented")
}
override suspend fun getFiles(path: URI): List<IFile> {
TODO("Not yet implemented")
}
override fun getFilesFlow(path: URI): Flow<DataPackage<IFile>> {
override fun getFilesFlow(path: String): Flow<DataPackage<IFile>> {
TODO("Not yet implemented")
}
@@ -35,43 +97,43 @@ class LocalStorageAccessor : IStorageAccessor {
TODO("Not yet implemented")
}
override suspend fun getDirs(path: URI): List<IDirectory> {
override suspend fun getDirs(path: String): List<IDirectory> {
TODO("Not yet implemented")
}
override fun getDirsFlow(path: URI): Flow<DataPackage<IDirectory>> {
override fun getDirsFlow(path: String): Flow<DataPackage<IDirectory>> {
TODO("Not yet implemented")
}
override suspend fun touchFile(path: URI) {
override suspend fun touchFile(path: String) {
TODO("Not yet implemented")
}
override suspend fun touchDir(path: URI) {
override suspend fun touchDir(path: String) {
TODO("Not yet implemented")
}
override suspend fun delete(path: URI) {
override suspend fun delete(path: String) {
TODO("Not yet implemented")
}
override suspend fun getFileInfo(path: URI) {
override suspend fun getFileInfo(path: String) {
TODO("Not yet implemented")
}
override suspend fun getDirInfo(path: URI) {
override suspend fun getDirInfo(path: String) {
TODO("Not yet implemented")
}
override suspend fun openWrite(path: URI): InputStream {
override suspend fun openWrite(path: String): InputStream {
TODO("Not yet implemented")
}
override suspend fun openRead(path: URI): OutputStream {
override suspend fun openRead(path: String): OutputStream {
TODO("Not yet implemented")
}
override suspend fun moveToTrash(path: URI) {
override suspend fun moveToTrash(path: String) {
TODO("Not yet implemented")
}
}

View File

@@ -1,7 +1,6 @@
package com.github.nullptroma.wallenc.data.vaults.local
import android.content.Context
import com.github.nullptroma.wallenc.data.MockStorage
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.enums.VaultType
import com.github.nullptroma.wallenc.domain.models.IStorage
@@ -12,42 +11,64 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.UUID
import kotlin.io.path.Path
import kotlin.io.path.createDirectory
import kotlin.io.path.pathString
class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context) : IVault {
private val path = context.getExternalFilesDir("LocalVault")
private val _path = MutableStateFlow<File?>(null)
private val _storages = MutableStateFlow(listOf<IStorage>())
private val _totalSpace = MutableStateFlow(null)
private val _availableSpace = MutableStateFlow(null)
private val _isAvailable = MutableStateFlow(false)
init {
CoroutineScope(ioDispatcher).launch {
if(path == null)
return@launch
val dirs = path.listFiles()?.filter { it.isDirectory }
if(dirs != null)
_storages.value = dirs.map {
MockStorage(uuid = UUID.fromString(it.name), accessor = LocalStorageAccessor())
}
val next = Path(path.path, UUID.randomUUID().toString())
next.createDirectory()
_path.value = context.getExternalFilesDir("LocalVault")
_isAvailable.value = _path.value != null
readStorages()
}
}
override suspend fun createStorage(name: String): IStorage = withContext(ioDispatcher) {
TODO("Not yet implemented")
private fun readStorages() {
val path = _path.value
if (path == null || _isAvailable.value == false)
return
val dirs = path.listFiles()?.filter { it.isDirectory }
if (dirs != null) {
_storages.value = dirs.map {
val uuid = UUID.fromString(it.name)
LocalStorage(uuid, it.path, ioDispatcher)
}
}
}
override suspend fun createStorage(): IStorage = withContext(ioDispatcher) {
val path = _path.value
if (path == null || _isAvailable.value == false)
throw Exception("Not available")
val uuid = UUID.randomUUID()
val next = Path(path.path, uuid.toString())
next.createDirectory()
val newStorage = LocalStorage(uuid, next.pathString, ioDispatcher)
_storages.value = _storages.value.toMutableList().apply {
add(newStorage)
}
return@withContext newStorage
}
override suspend fun createStorage(
name: String,
key: EncryptKey
): IStorage = withContext(ioDispatcher) {
TODO("Not yet implemented")
}
override suspend fun createStorage(
name: String,
key: EncryptKey,
uuid: UUID
): IStorage = withContext(ioDispatcher) {
@@ -64,6 +85,9 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
override val storages: StateFlow<List<IStorage>>
get() = _storages
override val isAvailable: StateFlow<Boolean>
get() = TODO("Not yet implemented")
get() = _isAvailable
override val totalSpace: StateFlow<Int?>
get() = _totalSpace
override val availableSpace: StateFlow<Int?>
get() = _availableSpace
}

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.models.IMetaInfo
import java.time.LocalDateTime
class LocalMetaInfo : IMetaInfo {
init {
}
override val name: String
get() = TODO("Not yet implemented")
override val size: Int
get() = TODO("Not yet implemented")
override val isDeleted: Boolean
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
get() = TODO("Not yet implemented")
}

View File

@@ -1,6 +1,5 @@
package com.github.nullptroma.wallenc.domain.models
import java.net.URI
import java.time.LocalDateTime
@@ -10,5 +9,5 @@ interface IMetaInfo {
val isDeleted: Boolean
val isHidden: Boolean
val lastModified: LocalDateTime
val path: URI
val path: String
}

View File

@@ -4,12 +4,10 @@ import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
interface IStorage {
val size: StateFlow<Int?>
val size: StateFlow<Long?>
val numberOfFiles: StateFlow<Int?>
val uuid: UUID
val name: StateFlow<String>
val totalSpace: StateFlow<Int?>
val availableSpace: StateFlow<Int?>
val isAvailable: StateFlow<Boolean>
val accessor: IStorageAccessor

View File

@@ -6,37 +6,38 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import java.io.InputStream
import java.io.OutputStream
import java.net.URI
interface IStorageAccessor {
val size: StateFlow<Long?>
val numberOfFiles: StateFlow<Int?>
val isAvailable: StateFlow<Boolean>
val filesUpdates: SharedFlow<DataPackage<IFile>>
val dirsUpdates: SharedFlow<DataPackage<IDirectory>>
suspend fun getAllFiles(): List<IFile>
suspend fun getFiles(path: URI): List<IFile>
suspend fun getFiles(path: String): List<IFile>
/**
* Получение списка файлов в директории
* @param path Путь к директории
* @return Поток файлов
*/
fun getFilesFlow(path: URI): Flow<DataPackage<IFile>>
fun getFilesFlow(path: String): Flow<DataPackage<IFile>>
suspend fun getAllDirs(): List<IDirectory>
suspend fun getDirs(path: URI): List<IDirectory>
suspend fun getDirs(path: String): List<IDirectory>
/**
* Получение списка директорий в директории
* @param path Путь к директории
* @return Поток директорий
*/
fun getDirsFlow(path: URI): Flow<DataPackage<IDirectory>>
fun getDirsFlow(path: String): Flow<DataPackage<IDirectory>>
suspend fun touchFile(path: URI)
suspend fun touchDir(path: URI)
suspend fun delete(path: URI)
suspend fun getFileInfo(path: URI)
suspend fun getDirInfo(path: URI)
suspend fun openWrite(path: URI): InputStream
suspend fun openRead(path: URI): OutputStream
suspend fun moveToTrash(path: URI)
suspend fun touchFile(path: String)
suspend fun touchDir(path: String)
suspend fun delete(path: String)
suspend fun getFileInfo(path: String)
suspend fun getDirInfo(path: String)
suspend fun openWrite(path: String): InputStream
suspend fun openRead(path: String): OutputStream
suspend fun moveToTrash(path: String)
}

View File

@@ -1,10 +1,9 @@
package com.github.nullptroma.wallenc.domain.models
import kotlinx.coroutines.flow.StateFlow
import java.net.URL
interface IStorageExplorer {
val currentPath: StateFlow<URL>
val currentPath: StateFlow<String>
// TODO
// пока бесполезный интерфейс

View File

@@ -4,8 +4,8 @@ import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import java.util.UUID
interface IVault : IVaultInfo {
suspend fun createStorage(name: String): IStorage
suspend fun createStorage(name: String, key: EncryptKey): IStorage
suspend fun createStorage(name: String, key: EncryptKey, uuid: UUID): IStorage
suspend fun createStorage(): IStorage
suspend fun createStorage(key: EncryptKey): IStorage
suspend fun createStorage(key: EncryptKey, uuid: UUID): IStorage
suspend fun remove(storage: IStorage)
}

View File

@@ -9,4 +9,6 @@ interface IVaultInfo {
val uuid: UUID
val storages: StateFlow<List<IStorage>>
val isAvailable: StateFlow<Boolean>
val totalSpace: StateFlow<Int?>
val availableSpace: StateFlow<Int?>
}

View File

@@ -47,6 +47,9 @@ android {
}
dependencies {
// Timber
implementation(libs.timber)
implementation(libs.navigation)
implementation(libs.navigation.hilt.compose)

View File

@@ -1,6 +1,9 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -9,16 +12,22 @@ import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LocalVaultScreen(modifier: Modifier = Modifier,
viewModel: LocalVaultViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column {
for(storage in uiState.storagesList) {
Text(storage.uuid.toString())
val uiState by viewModel.state.collectAsStateWithLifecycle()
LazyColumn(modifier = modifier) {
items(uiState.storagesList) {
Card {
Column {
Text(it.uuid.toString())
Text("IsAvailable: ${it.isAvailable.value}")
Text("Files: ${it.numberOfFiles.value}")
Text("Size: ${it.size.value}")
}
}
}
}
}

View File

@@ -13,9 +13,10 @@ class LocalVaultViewModel @Inject constructor(private val getAllRawStoragesUseCa
init {
viewModelScope.launch {
getAllRawStoragesUseCase.localStorage.storages.collect {
mutableUiState.value = mutableUiState.value.copy(
val newState = state.value.copy(
storagesList = it
)
updateState(newState)
}
}
}

View File

@@ -4,11 +4,19 @@ package com.github.nullptroma.wallenc.presentation.viewmodel
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import timber.log.Timber
abstract class ViewModelBase<TState>(initState: TState) : ViewModel() {
protected val mutableUiState = MutableStateFlow<TState>(initState)
private val _state = MutableStateFlow<TState>(initState)
val uiState: StateFlow<TState>
get() = mutableUiState.asStateFlow()
init {
Timber.d("Init ViewModel ${this.javaClass.name}")
}
val state: StateFlow<TState>
get() = _state
protected fun updateState(newState: TState) {
_state.value = newState
}
}