working poc

This commit is contained in:
2026-03-10 00:36:59 +05:00
parent 881dbfb648
commit a319a07440
34 changed files with 2067 additions and 37 deletions

View 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
)