working poc
This commit is contained in:
255
app/src/main/java/sh/sar/isodroid/viewmodel/MainViewModel.kt
Normal file
255
app/src/main/java/sh/sar/isodroid/viewmodel/MainViewModel.kt
Normal file
@@ -0,0 +1,255 @@
|
||||
package sh.sar.isodroid.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Environment
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import sh.sar.isodroid.data.IsoFile
|
||||
import sh.sar.isodroid.data.MountOptions
|
||||
import sh.sar.isodroid.data.MountStatus
|
||||
import sh.sar.isodroid.isodrive.IsoDriveManager
|
||||
import sh.sar.isodroid.isodrive.SupportStatus
|
||||
import sh.sar.isodroid.root.RootManager
|
||||
import java.io.File
|
||||
|
||||
private val Application.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
|
||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val isoDriveManager = IsoDriveManager.getInstance(application)
|
||||
private val dataStore = application.dataStore
|
||||
|
||||
private val _uiState = MutableStateFlow(MainUiState())
|
||||
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
|
||||
|
||||
private var navigationStack = mutableListOf<String>()
|
||||
|
||||
companion object {
|
||||
private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")
|
||||
private val DEFAULT_ISO_DIRECTORY = "${Environment.getExternalStorageDirectory().absolutePath}/isodrive"
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
initialize()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun initialize() {
|
||||
_uiState.update { it.copy(isLoading = true) }
|
||||
|
||||
// Load saved ISO directory
|
||||
val savedDirectory = dataStore.data.map { preferences ->
|
||||
preferences[KEY_ISO_DIRECTORY] ?: DEFAULT_ISO_DIRECTORY
|
||||
}.first()
|
||||
|
||||
_uiState.update { it.copy(isoDirectory = savedDirectory, currentPath = savedDirectory) }
|
||||
navigationStack.add(savedDirectory)
|
||||
|
||||
// Check root access
|
||||
val hasRoot = RootManager.requestRoot()
|
||||
_uiState.update { it.copy(hasRoot = hasRoot) }
|
||||
|
||||
if (hasRoot) {
|
||||
// Initialize isodrive manager
|
||||
isoDriveManager.initialize()
|
||||
|
||||
// Check device support
|
||||
val supportStatus = isoDriveManager.isSupported()
|
||||
val isSupported = supportStatus == SupportStatus.CONFIGFS_SUPPORTED ||
|
||||
supportStatus == SupportStatus.SYSFS_SUPPORTED
|
||||
|
||||
_uiState.update { it.copy(isSupported = isSupported) }
|
||||
|
||||
if (isSupported) {
|
||||
// Load files and check mount status
|
||||
loadFiles()
|
||||
checkMountStatus()
|
||||
}
|
||||
}
|
||||
|
||||
_uiState.update { it.copy(isLoading = false) }
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(isLoading = true) }
|
||||
loadFiles()
|
||||
checkMountStatus()
|
||||
_uiState.update { it.copy(isLoading = false) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadFiles() {
|
||||
val currentPath = _uiState.value.currentPath
|
||||
val directory = File(currentPath)
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!directory.exists()) {
|
||||
RootManager.executeCommand("mkdir -p \"$currentPath\"")
|
||||
}
|
||||
|
||||
// Try multiple methods to list files
|
||||
val files = loadFilesViaFind(currentPath)
|
||||
?: loadFilesViaLs(currentPath)
|
||||
?: loadFilesDirect(directory)
|
||||
|
||||
_uiState.update { it.copy(isoFiles = files) }
|
||||
}
|
||||
|
||||
private suspend fun loadFilesViaFind(currentPath: String): List<IsoFile>? {
|
||||
// Use find command - more reliable for getting full paths
|
||||
val result = RootManager.executeCommand(
|
||||
"find \"$currentPath\" -maxdepth 1 -type f \\( -iname '*.iso' -o -iname '*.img' \\) 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!result.success || result.output.isBlank()) return null
|
||||
|
||||
return result.output.lines()
|
||||
.filter { it.isNotBlank() }
|
||||
.mapNotNull { filePath ->
|
||||
val file = File(filePath.trim())
|
||||
val name = file.name
|
||||
// Get file size via stat
|
||||
val sizeResult = RootManager.executeCommand("stat -c %s \"$filePath\" 2>/dev/null")
|
||||
val size = sizeResult.output.trim().toLongOrNull() ?: 0L
|
||||
IsoFile(
|
||||
path = filePath.trim(),
|
||||
name = name,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
.sortedBy { it.name.lowercase() }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private suspend fun loadFilesViaLs(currentPath: String): List<IsoFile>? {
|
||||
// Simple ls command - just get filenames
|
||||
val result = RootManager.executeCommand(
|
||||
"ls \"$currentPath\" 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!result.success || result.output.isBlank()) return null
|
||||
|
||||
return result.output.lines()
|
||||
.filter { name ->
|
||||
name.isNotBlank() &&
|
||||
(name.endsWith(".iso", true) || name.endsWith(".img", true))
|
||||
}
|
||||
.mapNotNull { name ->
|
||||
val filePath = "$currentPath/$name"
|
||||
// Get file size via stat
|
||||
val sizeResult = RootManager.executeCommand("stat -c %s \"$filePath\" 2>/dev/null")
|
||||
val size = sizeResult.output.trim().toLongOrNull() ?: 0L
|
||||
IsoFile(
|
||||
path = filePath,
|
||||
name = name.trim(),
|
||||
size = size
|
||||
)
|
||||
}
|
||||
.sortedBy { it.name.lowercase() }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun loadFilesDirect(directory: File): List<IsoFile> {
|
||||
// Fallback to direct file access (works if app has storage permission)
|
||||
return directory.listFiles()
|
||||
?.filter { file ->
|
||||
file.isFile && (file.name.endsWith(".iso", true) ||
|
||||
file.name.endsWith(".img", true))
|
||||
}
|
||||
?.map { IsoFile.fromFile(it) }
|
||||
?.sortedBy { it.name.lowercase() }
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
private suspend fun checkMountStatus() {
|
||||
val status = isoDriveManager.getStatus()
|
||||
_uiState.update { it.copy(mountStatus = status) }
|
||||
}
|
||||
|
||||
suspend fun mount(path: String, options: MountOptions) {
|
||||
_uiState.update { it.copy(isLoading = true) }
|
||||
|
||||
val result = isoDriveManager.mount(path, options)
|
||||
|
||||
if (result.success) {
|
||||
checkMountStatus()
|
||||
_uiState.update { it.copy(successMessage = result.message, isLoading = false) }
|
||||
} else {
|
||||
_uiState.update { it.copy(errorMessage = result.message, isLoading = false) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unmount() {
|
||||
_uiState.update { it.copy(isLoading = true) }
|
||||
|
||||
val result = isoDriveManager.unmount()
|
||||
|
||||
if (result.success) {
|
||||
checkMountStatus()
|
||||
_uiState.update { it.copy(successMessage = result.message, isLoading = false) }
|
||||
} else {
|
||||
_uiState.update { it.copy(errorMessage = result.message, isLoading = false) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setIsoDirectory(path: String) {
|
||||
viewModelScope.launch {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[KEY_ISO_DIRECTORY] = path
|
||||
}
|
||||
navigationStack.clear()
|
||||
navigationStack.add(path)
|
||||
_uiState.update { it.copy(isoDirectory = path, currentPath = path) }
|
||||
loadFiles()
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateUp() {
|
||||
if (navigationStack.size > 1) {
|
||||
navigationStack.removeAt(navigationStack.lastIndex)
|
||||
val parentPath = navigationStack.last()
|
||||
_uiState.update { it.copy(currentPath = parentPath) }
|
||||
viewModelScope.launch {
|
||||
loadFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun canNavigateUp(): Boolean {
|
||||
return navigationStack.size > 1
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
_uiState.update { it.copy(errorMessage = null) }
|
||||
}
|
||||
|
||||
fun clearSuccess() {
|
||||
_uiState.update { it.copy(successMessage = null) }
|
||||
}
|
||||
}
|
||||
|
||||
data class MainUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val hasRoot: Boolean = false,
|
||||
val isSupported: Boolean = false,
|
||||
val mountStatus: MountStatus = MountStatus.UNMOUNTED,
|
||||
val isoFiles: List<IsoFile> = emptyList(),
|
||||
val currentPath: String = "",
|
||||
val isoDirectory: String = "",
|
||||
val errorMessage: String? = null,
|
||||
val successMessage: String? = null
|
||||
)
|
||||
Reference in New Issue
Block a user