Начало 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.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
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
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.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream 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> override val isAvailable: StateFlow<Boolean>
get() = TODO("Not yet implemented") get() = _isAvailable
override val filesUpdates: SharedFlow<DataPackage<IFile>> override val filesUpdates: SharedFlow<DataPackage<IFile>>
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override val dirsUpdates: SharedFlow<DataPackage<IDirectory>> override val dirsUpdates: SharedFlow<DataPackage<IDirectory>>
get() = TODO("Not yet implemented") 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> { 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") TODO("Not yet implemented")
} }
override suspend fun getFiles(path: URI): List<IFile> { override fun getFilesFlow(path: String): Flow<DataPackage<IFile>> {
TODO("Not yet implemented")
}
override fun getFilesFlow(path: URI): Flow<DataPackage<IFile>> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@@ -35,43 +97,43 @@ class LocalStorageAccessor : IStorageAccessor {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun getDirs(path: URI): List<IDirectory> { override suspend fun getDirs(path: String): List<IDirectory> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun getDirsFlow(path: URI): Flow<DataPackage<IDirectory>> { override fun getDirsFlow(path: String): Flow<DataPackage<IDirectory>> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun touchFile(path: URI) { override suspend fun touchFile(path: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun touchDir(path: URI) { override suspend fun touchDir(path: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun delete(path: URI) { override suspend fun delete(path: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun getFileInfo(path: URI) { override suspend fun getFileInfo(path: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun getDirInfo(path: URI) { override suspend fun getDirInfo(path: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun openWrite(path: URI): InputStream { override suspend fun openWrite(path: String): InputStream {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun openRead(path: URI): OutputStream { override suspend fun openRead(path: String): OutputStream {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun moveToTrash(path: URI) { override suspend fun moveToTrash(path: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }

View File

@@ -1,7 +1,6 @@
package com.github.nullptroma.wallenc.data.vaults.local package com.github.nullptroma.wallenc.data.vaults.local
import android.content.Context 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.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.enums.VaultType import com.github.nullptroma.wallenc.domain.enums.VaultType
import com.github.nullptroma.wallenc.domain.models.IStorage 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.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
import java.util.UUID import java.util.UUID
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.createDirectory import kotlin.io.path.createDirectory
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 = context.getExternalFilesDir("LocalVault") private val _path = MutableStateFlow<File?>(null)
private val _storages = MutableStateFlow(listOf<IStorage>()) private val _storages = MutableStateFlow(listOf<IStorage>())
private val _totalSpace = MutableStateFlow(null)
private val _availableSpace = MutableStateFlow(null)
private val _isAvailable = MutableStateFlow(false)
init { init {
CoroutineScope(ioDispatcher).launch { CoroutineScope(ioDispatcher).launch {
if(path == null) _path.value = context.getExternalFilesDir("LocalVault")
return@launch _isAvailable.value = _path.value != null
readStorages()
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()
} }
} }
override suspend fun createStorage(name: String): IStorage = withContext(ioDispatcher) { private fun readStorages() {
TODO("Not yet implemented") 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( override suspend fun createStorage(
name: String,
key: EncryptKey key: EncryptKey
): IStorage = withContext(ioDispatcher) { ): IStorage = withContext(ioDispatcher) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun createStorage( override suspend fun createStorage(
name: String,
key: EncryptKey, key: EncryptKey,
uuid: UUID uuid: UUID
): IStorage = withContext(ioDispatcher) { ): IStorage = withContext(ioDispatcher) {
@@ -64,6 +85,9 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
override val storages: StateFlow<List<IStorage>> override val storages: StateFlow<List<IStorage>>
get() = _storages get() = _storages
override val isAvailable: StateFlow<Boolean> 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 package com.github.nullptroma.wallenc.domain.models
import java.net.URI
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -10,5 +9,5 @@ interface IMetaInfo {
val isDeleted: Boolean val isDeleted: Boolean
val isHidden: Boolean val isHidden: Boolean
val lastModified: LocalDateTime val lastModified: LocalDateTime
val path: URI val path: String
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
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.layout.Column 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.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -9,16 +12,22 @@ import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LocalVaultScreen(modifier: Modifier = Modifier, fun LocalVaultScreen(modifier: Modifier = Modifier,
viewModel: LocalVaultViewModel = hiltViewModel()) { viewModel: LocalVaultViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.state.collectAsStateWithLifecycle()
Column { LazyColumn(modifier = modifier) {
for(storage in uiState.storagesList) { items(uiState.storagesList) {
Text(storage.uuid.toString()) 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 { init {
viewModelScope.launch { viewModelScope.launch {
getAllRawStoragesUseCase.localStorage.storages.collect { getAllRawStoragesUseCase.localStorage.storages.collect {
mutableUiState.value = mutableUiState.value.copy( val newState = state.value.copy(
storagesList = it storagesList = it
) )
updateState(newState)
} }
} }
} }

View File

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