From 1223416923cd8d2da34245dbd2f727dcfa64ad8c Mon Sep 17 00:00:00 2001 From: Hasinjato Date: Tue, 19 May 2026 12:21:26 +0300 Subject: [PATCH] Update editor: build using temp files and save directly to source --- .../mg/dot/feufaro/AndroidPathValidator.kt | 22 ++++++ .../kotlin/mg/dot/feufaro/PathValidator.kt | 5 ++ .../kotlin/mg/dot/feufaro/Platform.kt | 5 +- .../mg/dot/feufaro/solfa/TimeUnitObject.kt | 73 ++++++++++++++----- .../mg/dot/feufaro/DesktopPathValidator.kt | 29 ++++++++ 5 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidPathValidator.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/PathValidator.kt create mode 100644 composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopPathValidator.kt diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidPathValidator.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidPathValidator.kt new file mode 100644 index 0000000..c45a59b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidPathValidator.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaro + + +class AndroidPathValidator : PathValidator { + override fun isRestrictedPath(path: String): Boolean { + if (path.isEmpty()) return true + + val lowerPath = path.lowercase() + + val isAndroidAsset = lowerPath.contains("android_asset") || + lowerPath.startsWith("assets/") + + val isAndroidCacheOrTmp = lowerPath.contains("/cache/") || + lowerPath.contains("/code_cache/") || + lowerPath.contains("/tmp/") || + lowerPath.contains("_tmp") + + return isAndroidAsset || isAndroidCacheOrTmp + } +} + +actual fun getPathValidator(): PathValidator = AndroidPathValidator() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/PathValidator.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/PathValidator.kt new file mode 100644 index 0000000..e4d26a2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/PathValidator.kt @@ -0,0 +1,5 @@ +package mg.dot.feufaro + +interface PathValidator { + fun isRestrictedPath(path: String): Boolean +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/Platform.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/Platform.kt index ae9706e..9ad02ff 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/Platform.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/Platform.kt @@ -1,7 +1,10 @@ package mg.dot.feufaro + interface Platform { val name: String } -expect fun getPlatform(): Platform \ No newline at end of file +expect fun getPlatform(): Platform + +expect fun getPathValidator(): PathValidator \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt index 5ab81bc..e253ad0 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBackIos import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Close @@ -51,6 +50,7 @@ import mg.dot.feufaro.ContextualMenu import mg.dot.feufaro.FileRepository import mg.dot.feufaro.TUODetailDialog import mg.dot.feufaro.data.GridTUOData +import mg.dot.feufaro.getPathValidator import mg.dot.feufaro.ui.rememberFileSaveLauncher import mg.dot.feufaro.viewmodel.MidiMarkers import mg.dot.feufaro.viewmodel.SolfaScreenModel @@ -1039,10 +1039,15 @@ private suspend fun expandInclusions( val lastSlash = currentFilePath.lastIndexOf('/') val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else "" + val inclusionRegex = Regex("^I([0-9]):(.*)") + lines.forEach { line -> - if (line.startsWith("I0:")) { + val match = inclusionRegex.find(line) + + if (match != null) { try { - val parts = line.substring(3).split(":") + val body = match.groupValues[2] + val parts = body.split(":") val fileName = parts[0] val ignorePattern = if (parts.size > 1) parts[1] else "" @@ -1057,7 +1062,7 @@ private suspend fun expandInclusions( } } } catch (e: Exception) { - result.append("// Erreur inclusion: ${e.message}\n") + result.append("// Erreur inclusion sur ${line}: ${e.message}\n") } } else { result.append(line).append("\n") @@ -1092,8 +1097,20 @@ fun EditSourceCompose( var currentTempFile by remember { mutableStateOf(null) } var codeContent by remember { mutableStateOf(sourceContent?: "") } + + val pathValidator = remember { getPathValidator() } + // restricted path pour les fichiers interne de l'app + val isRestrictedPath = remember(sourcePath) { + pathValidator.isRestrictedPath(sourcePath) + } + + val isModified = remember(codeContent, sourceContent) { codeContent != sourceContent } + + var previousCodeContentBeforeBuild by remember { mutableStateOf(null) } + LaunchedEffect(sourceContent) { codeContent = sourceContent + previousCodeContentBeforeBuild = null } var textLayoutResult by remember { mutableStateOf(null) } @@ -1140,6 +1157,8 @@ fun EditSourceCompose( Spacer(modifier = Modifier.weight(1f)) EditorActionButtons( + isSaveVisible = isModified || isRestrictedPath, + isUndoVisible = isModified, onUndo = { scope.launch { codeContent = sourceContent @@ -1162,19 +1181,20 @@ fun EditSourceCompose( if (currentTempFile == null || !currentTempFile!!.exists()) { val tempDir = System.getProperty("java.io.tmpdir") - currentTempFile = File(tempDir, fileName) + currentTempFile = File(tempDir, fileName.ifEmpty { "preview_solfa.txt" }) currentTempFile?.deleteOnExit() } - currentTempFile?.writeText(expandedContent) - - currentTempFile?.let { file -> - withContext(Dispatchers.Main) { - solfaScreenModel.justeCompile(file.absolutePath) - } + withContext(Dispatchers.IO) { + currentTempFile?.writeText(expandedContent) } - println("Compilation sur le fichier : ${currentTempFile?.absolutePath}") + withContext(Dispatchers.Main) { + solfaScreenModel.justeCompile(currentTempFile!!.absolutePath) + + println("Compilation temporaire réussie : ${currentTempFile?.absolutePath}") + currentTempFile?.deleteOnExit() + } } catch (e: Exception) { e.printStackTrace() @@ -1184,8 +1204,23 @@ fun EditSourceCompose( onSave = { scope.launch { try { - val initialDir = solfaScreenModel.fileRepository.getAppPublicFolder().absolutePath - saveLauncher.launch(fileName, initialDir) + if (!isRestrictedPath) { + val externalFile = File(sourcePath) + withContext(Dispatchers.IO) { + externalFile.writeText(codeContent) + } + + withContext(Dispatchers.Main) { + sharedScreenModel.setFileContent(codeContent, sourcePath) + solfaScreenModel.loadExternalFile(sourcePath) + } + println("Vraie source écrasée avec succès : $sourcePath") + } else { + val initialDir = solfaScreenModel.fileRepository.getAppPublicFolder().absolutePath + saveLauncher.launch(fileName, initialDir) + } + currentTempFile?.delete() + currentTempFile = null } catch (e: Exception) { e.printStackTrace() } @@ -1221,11 +1256,15 @@ fun EditSourceCompose( } @Composable -fun EditorActionButtons(onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> Unit, onClose: () -> Unit) { +fun EditorActionButtons(isSaveVisible: Boolean, isUndoVisible: Boolean, onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> Unit, onClose: () -> Unit) { Row(horizontalArrangement = Arrangement.SpaceBetween) { - IconButton(onClick = onUndo) { Icon(Icons.AutoMirrored.Filled.Undo, null, tint = Color.Gray) } + if(isUndoVisible) { + IconButton(onClick = onUndo) { Icon(Icons.AutoMirrored.Filled.Undo, null, tint = Color.Gray) } + } IconButton(onClick = onBuild) { Icon(Icons.Default.Build, null, tint = Color(0xFF2196F3)) } - IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) } + if(isSaveVisible) { + IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) } + } IconButton(onClick = onClose) { Icon(Icons.Default.Close, null, tint = Color.Red) } } } diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopPathValidator.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopPathValidator.kt new file mode 100644 index 0000000..d001d2a --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopPathValidator.kt @@ -0,0 +1,29 @@ +package mg.dot.feufaro + +import java.io.File + +class DesktopPathValidator : PathValidator { + private val systemTmpDir: String by lazy { + try { + File(System.getProperty("java.io.tmpdir")).absolutePath.lowercase() + } catch (e: Exception) { + "/tmp" + } + } + + override fun isRestrictedPath(path: String): Boolean { + if (path.isEmpty()) return true + + val lowerPath = path.lowercase() + + val isInSystemTmp = lowerPath.contains(systemTmpDir) + + val isOtherRestricted = lowerPath.contains("/tmp/") || + lowerPath.contains("_tmp") || + lowerPath.contains("assets") + + return isInSystemTmp || isOtherRestricted + } +} + +actual fun getPathValidator(): PathValidator = DesktopPathValidator() \ No newline at end of file