release: v1.0.1 - add restore backup button and bump version
This commit is contained in:
1 parent
7f6583df17
commit
e7ca8d26af
3 files changed
+114
-33
No files matched your search
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.catlas.escapepenacony"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "v1.0.0"
|
||||
versionCode = 2
|
||||
versionName = "v1.0.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ object StorageManager {
|
||||
private const val PREFS_NAME = "escape_penacony_prefs"
|
||||
private const val KEY_PERSISTENT_URI = "persistent_uri"
|
||||
private const val KEY_SELECTED_LANG = "selected_language"
|
||||
private const val KEY_PREFER_ROOT = "prefer_root_mode"
|
||||
|
||||
const val PPSR_PACKAGE_NAME = "com.afn.ppSR"
|
||||
const val PPSR_INTERNAL_PERSISTENT_PATH =
|
||||
"/data/data/com.afn.ppSR/files/server/build/build/misc/persistent.json"
|
||||
const val PPSR_INTERNAL_BACKUP_PATH =
|
||||
"/data/data/com.afn.ppSR/files/server/build/build/misc/persistent.json.bak"
|
||||
|
||||
fun savePersistentUri(context: Context, uri: Uri) {
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
@@ -101,6 +102,16 @@ object StorageManager {
|
||||
val originalContent = readProcess.inputStream.bufferedReader().use { it.readText().trim() }
|
||||
readProcess.waitFor()
|
||||
|
||||
// Save backup both inside ppSR folder and in local app cache
|
||||
if (originalContent.isNotEmpty()) {
|
||||
try {
|
||||
val bakCmd = "cp \"$PPSR_INTERNAL_PERSISTENT_PATH\" \"$PPSR_INTERNAL_BACKUP_PATH\""
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", bakCmd)).waitFor()
|
||||
val cacheBackup = File(context.cacheDir, "persistent_backup.json")
|
||||
cacheBackup.writeText(originalContent, Charsets.UTF_8)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
val updatedJson = PersistentHelper.updateJsonString(
|
||||
if (originalContent.isNotEmpty()) originalContent else "{}",
|
||||
newConfig
|
||||
@@ -127,9 +138,55 @@ object StorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun restoreBackup(context: Context, isRootMode: Boolean, uri: Uri?): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (isRootMode) {
|
||||
// Try root backup file first
|
||||
val checkCmd = "[ -f \"$PPSR_INTERNAL_BACKUP_PATH\" ] && echo 'BAK_EXISTS'"
|
||||
val checkP = Runtime.getRuntime().exec(arrayOf("su", "-c", checkCmd))
|
||||
val checkOut = checkP.inputStream.bufferedReader().use { it.readText().trim() }
|
||||
checkP.waitFor()
|
||||
|
||||
if (checkOut == "BAK_EXISTS") {
|
||||
val restoreCmd = "cp \"$PPSR_INTERNAL_BACKUP_PATH\" \"$PPSR_INTERNAL_PERSISTENT_PATH\" && chmod 666 \"$PPSR_INTERNAL_PERSISTENT_PATH\""
|
||||
val p = Runtime.getRuntime().exec(arrayOf("su", "-c", restoreCmd))
|
||||
val exit = p.waitFor()
|
||||
if (exit == 0) return@withContext Result.success(Unit)
|
||||
}
|
||||
|
||||
// Fallback to cache backup
|
||||
val cacheBackup = File(context.cacheDir, "persistent_backup.json")
|
||||
if (cacheBackup.exists()) {
|
||||
val copyCmd = "cp \"${cacheBackup.absolutePath}\" \"$PPSR_INTERNAL_PERSISTENT_PATH\" && chmod 666 \"$PPSR_INTERNAL_PERSISTENT_PATH\""
|
||||
val p = Runtime.getRuntime().exec(arrayOf("su", "-c", copyCmd))
|
||||
val exit = p.waitFor()
|
||||
if (exit == 0) return@withContext Result.success(Unit)
|
||||
}
|
||||
|
||||
Result.failure(Exception("No backup file found to restore"))
|
||||
} else {
|
||||
// SAF fallback
|
||||
if (uri == null) return@withContext Result.failure(Exception("No persistent.json selected"))
|
||||
val cacheBackup = File(context.cacheDir, "persistent_backup.json")
|
||||
if (!cacheBackup.exists()) return@withContext Result.failure(Exception("No backup file found"))
|
||||
|
||||
val backupContent = cacheBackup.readText(Charsets.UTF_8)
|
||||
context.contentResolver.openOutputStream(uri, "wt")?.use { output ->
|
||||
output.write(backupContent.toByteArray(Charsets.UTF_8))
|
||||
output.flush()
|
||||
} ?: return@withContext Result.failure(Exception("Failed to open output stream"))
|
||||
|
||||
Result.success(Unit)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun restartPpsrServerViaRoot(): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// Force-stop then relaunch MainActivity
|
||||
val restartCmd = "am force-stop $PPSR_PACKAGE_NAME && sleep 0.5 && am start -n $PPSR_PACKAGE_NAME/.MainActivity"
|
||||
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", restartCmd))
|
||||
val exit = process.waitFor()
|
||||
@@ -162,7 +219,7 @@ object StorageManager {
|
||||
|
||||
try {
|
||||
val backupFile = File(context.cacheDir, "persistent_backup.json")
|
||||
backupFile.writeText(originalContent)
|
||||
backupFile.writeText(originalContent, Charsets.UTF_8)
|
||||
} catch (_: Exception) {}
|
||||
|
||||
val updatedJson = PersistentHelper.updateJsonString(originalContent, newConfig)
|
||||
|
||||
@@ -303,42 +303,66 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (isRootMode) {
|
||||
// Restart ppSR Server button
|
||||
OutlinedButton(
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
// Restore / Revert Backup Button
|
||||
IconButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
Toast.makeText(context, "Restarting ppSR Server...", Toast.LENGTH_SHORT).show()
|
||||
val res = StorageManager.restartPpsrServerViaRoot()
|
||||
val res = StorageManager.restoreBackup(context, isRootMode, currentPersistentUri)
|
||||
res.onSuccess {
|
||||
Toast.makeText(context, "ppSR Server restarted successfully!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure {
|
||||
Toast.makeText(context, "Restart failed: ${it.message}", Toast.LENGTH_SHORT).show()
|
||||
if (isRootMode) {
|
||||
currentActiveConfig = StorageManager.readConfigViaRoot()
|
||||
} else if (currentPersistentUri != null) {
|
||||
currentActiveConfig = StorageManager.readCurrentConfig(context, currentPersistentUri)
|
||||
}
|
||||
Toast.makeText(context, "Restored backup persistent.json successfully!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { err ->
|
||||
Toast.makeText(context, "Restore failed: ${err.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
|
||||
modifier = Modifier.height(32.dp)
|
||||
modifier = Modifier.size(34.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Restart", modifier = Modifier.size(13.dp), tint = NeonCyan)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Restart ppSR", fontSize = 11.sp, color = NeonCyan)
|
||||
Icon(Icons.Default.Restore, contentDescription = "Restore Backup", tint = StarGold, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = onPickPersistentFile,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (currentPersistentUri != null) DarkSurfaceElevated else PurplePrimary
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
|
||||
modifier = Modifier.height(32.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (currentPersistentUri != null) "Change" else "Choose File",
|
||||
fontSize = 11.sp
|
||||
)
|
||||
|
||||
if (isRootMode) {
|
||||
// Restart ppSR Server button
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
Toast.makeText(context, "Restarting ppSR Server...", Toast.LENGTH_SHORT).show()
|
||||
val res = StorageManager.restartPpsrServerViaRoot()
|
||||
res.onSuccess {
|
||||
Toast.makeText(context, "ppSR Server restarted successfully!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure {
|
||||
Toast.makeText(context, "Restart failed: ${it.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
|
||||
modifier = Modifier.height(32.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Restart", modifier = Modifier.size(13.dp), tint = NeonCyan)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Restart ppSR", fontSize = 11.sp, color = NeonCyan)
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = onPickPersistentFile,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (currentPersistentUri != null) DarkSurfaceElevated else PurplePrimary
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
|
||||
modifier = Modifier.height(32.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (currentPersistentUri != null) "Change" else "Choose File",
|
||||
fontSize = 11.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user