new feature: create custom images
This commit is contained in:
@@ -19,12 +19,16 @@ 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.CreateImgEvent
|
||||
import sh.sar.isodroid.isodrive.CreateImgEventBus
|
||||
import sh.sar.isodroid.isodrive.IsoDriveManager
|
||||
import sh.sar.isodroid.isodrive.MountEvent
|
||||
import sh.sar.isodroid.isodrive.MountEventBus
|
||||
import sh.sar.isodroid.isodrive.SupportStatus
|
||||
import sh.sar.isodroid.notification.NotificationHelper
|
||||
import sh.sar.isodroid.root.RootManager
|
||||
import sh.sar.isodroid.ui.components.CreateImgOptions
|
||||
import sh.sar.isodroid.ui.components.CreateImgProgress
|
||||
import java.io.File
|
||||
|
||||
private val Application.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
@@ -39,6 +43,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
|
||||
|
||||
private var navigationStack = mutableListOf<String>()
|
||||
private var currentCreateImgFileName = ""
|
||||
|
||||
companion object {
|
||||
private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")
|
||||
@@ -62,6 +67,58 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observe create img events
|
||||
viewModelScope.launch {
|
||||
CreateImgEventBus.events.collect { event ->
|
||||
when (event) {
|
||||
is CreateImgEvent.Progress -> {
|
||||
val progress = event.bytesWritten.toFloat() / event.totalBytes.toFloat()
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
createImgProgress = CreateImgProgress(
|
||||
isCreating = true,
|
||||
fileName = currentCreateImgFileName,
|
||||
progress = progress,
|
||||
bytesWritten = event.bytesWritten,
|
||||
totalBytes = event.totalBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
notificationHelper.showCreateProgressNotification(
|
||||
currentCreateImgFileName,
|
||||
(progress * 100).toInt(),
|
||||
event.bytesWritten,
|
||||
event.totalBytes
|
||||
)
|
||||
}
|
||||
is CreateImgEvent.Complete -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
createImgProgress = CreateImgProgress(),
|
||||
successMessage = if (event.success) "Image created successfully" else "Failed to create image"
|
||||
)
|
||||
}
|
||||
notificationHelper.showCreateCompleteNotification(
|
||||
event.filePath?.substringAfterLast("/") ?: "image.img",
|
||||
event.success
|
||||
)
|
||||
if (event.success) {
|
||||
loadFiles()
|
||||
}
|
||||
}
|
||||
is CreateImgEvent.Cancelled -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
createImgProgress = CreateImgProgress(),
|
||||
successMessage = "Image creation cancelled"
|
||||
)
|
||||
}
|
||||
notificationHelper.hideProgressNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
@@ -351,6 +408,99 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
fun createImg(options: CreateImgOptions) {
|
||||
viewModelScope.launch {
|
||||
val fileName = "${options.fileName}.img"
|
||||
currentCreateImgFileName = fileName
|
||||
val filePath = "${_uiState.value.currentPath}/$fileName"
|
||||
val totalBytes = options.totalBytes
|
||||
|
||||
// Check if file already exists
|
||||
val checkResult = RootManager.executeCommand("test -f \"$filePath\" && echo exists")
|
||||
if (checkResult.output.trim() == "exists") {
|
||||
_uiState.update { it.copy(errorMessage = "File already exists: $fileName") }
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Start creating
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
createImgProgress = CreateImgProgress(
|
||||
isCreating = true,
|
||||
fileName = fileName,
|
||||
progress = 0f,
|
||||
bytesWritten = 0,
|
||||
totalBytes = totalBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
CreateImgEventBus.resetCancel()
|
||||
|
||||
// Use dd with progress reporting
|
||||
// We'll create in chunks and report progress
|
||||
val blockSize = 1024 * 1024L // 1MB blocks
|
||||
val totalBlocks = totalBytes / blockSize
|
||||
var writtenBlocks = 0L
|
||||
|
||||
// Create file with dd in background, checking for cancellation
|
||||
val result = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
try {
|
||||
// First, create the file with truncate to reserve space indication
|
||||
val createResult = RootManager.executeCommand(
|
||||
"dd if=/dev/zero of=\"$filePath\" bs=1M count=0 seek=$totalBlocks 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!createResult.success) {
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
// Now fill with zeros in chunks for progress reporting
|
||||
while (writtenBlocks < totalBlocks) {
|
||||
if (CreateImgEventBus.isCancelRequested()) {
|
||||
// Clean up partial file
|
||||
RootManager.executeCommand("rm -f \"$filePath\"")
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
// Write a chunk (up to 64MB at a time for efficiency)
|
||||
val chunksToWrite = minOf(64, totalBlocks - writtenBlocks)
|
||||
val chunkResult = RootManager.executeCommand(
|
||||
"dd if=/dev/zero of=\"$filePath\" bs=1M count=$chunksToWrite seek=$writtenBlocks conv=notrunc 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!chunkResult.success) {
|
||||
RootManager.executeCommand("rm -f \"$filePath\"")
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
writtenBlocks += chunksToWrite
|
||||
val bytesWritten = writtenBlocks * blockSize
|
||||
|
||||
CreateImgEventBus.emitProgress(bytesWritten, totalBytes)
|
||||
}
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
RootManager.executeCommand("rm -f \"$filePath\"")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
if (CreateImgEventBus.isCancelRequested()) {
|
||||
CreateImgEventBus.emitComplete(false, null)
|
||||
} else {
|
||||
CreateImgEventBus.emitComplete(result, if (result) filePath else null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelCreateImg() {
|
||||
viewModelScope.launch {
|
||||
CreateImgEventBus.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
_uiState.update { it.copy(errorMessage = null) }
|
||||
}
|
||||
@@ -370,5 +520,6 @@ data class MainUiState(
|
||||
val currentPath: String = "",
|
||||
val isoDirectory: String = "",
|
||||
val errorMessage: String? = null,
|
||||
val successMessage: String? = null
|
||||
val successMessage: String? = null,
|
||||
val createImgProgress: CreateImgProgress = CreateImgProgress()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user