package com.vgmlr.shim
import android.content.Context
import android.net.Uri
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
object ShimSave {
private const val KEY_LOCATION = "backup_location" // legacy key
private const val KEY_FORMAT = "save_format"
const val FORMAT_DEFAULT = "db"
val FORMATS = listOf("db", "csv", "txt", "json")
private val STAMPED = Regex("""^shim\d+\.(${FORMATS.joinToString("|")})$""")
fun formatLabel(ext: String): String = when (ext) {
"csv" -> "Comma (.csv)"
"txt" -> "Text (.txt)"
"json" -> "JSON (.json)"
else -> "SQL3 (.db)"
}
private fun mimeOf(ext: String): String = when (ext) {
"csv" -> "text/csv"
"txt" -> "text/plain"
"json" -> "application/json"
else -> "application/octet-stream" // keeps .db
}
private fun prefs(context: Context) =
context.getSharedPreferences("shim_prefs", Context.MODE_PRIVATE)
fun location(context: Context): String =
prefs(context).getString(KEY_LOCATION, "") ?: ""
fun format(context: Context): String =
prefs(context).getString(KEY_FORMAT, FORMAT_DEFAULT)?.takeIf { it in FORMATS }
?: FORMAT_DEFAULT
fun saveSettings(context: Context, location: String, format: String) {
prefs(context).edit {
putString(KEY_LOCATION, location)
putString(KEY_FORMAT, format)
}
}
private fun fileSafe(s: String): String =
s.replace(Regex("""[\\/:*?"<>|#]"""), "").trim()
suspend fun save(
context: Context,
db: ShimDatabase,
shims: List<ShimClass>? = null,
keyword: String? = null
): Boolean =
withContext(Dispatchers.IO) {
runCatching {
val ext = format(context)
val tag = keyword?.let { fileSafe(it) }.orEmpty()
val name =
if (tag.isEmpty()) "shim${ShimOTCClock.getFileNameTimestamp()}.$ext" else "shim_$tag.$ext"
val uri = target(context, name, mimeOf(ext)) ?: return@runCatching false
val rows = shims ?: db.getAllShims()
when (ext) {
"csv" -> db.exportCsv(context, uri, rows)
"txt" -> db.exportTxt(context, uri, rows)
"json" -> db.exportJson(context, uri, rows)
else -> db.exportDb(context, uri, rows)
}
if (tag.isEmpty()) runCatching { prune(context, uri) }
true
}.getOrDefault(false)
}
private fun target(context: Context, name: String, mime: String): Uri? {
val loc = location(context)
if (loc.isEmpty()) {
val dir = context.getExternalFilesDir(null) ?: return null // app/
return Uri.fromFile(File(dir, name))
}
val tree = DocumentFile.fromTreeUri(context, loc.toUri()) ?: return null
return tree.createFile(mime, name)?.uri
}
private fun prune(context: Context, keep: Uri) {
val loc = location(context)
if (loc.isEmpty()) {
val dir = context.getExternalFilesDir(null) ?: return
dir.listFiles()
?.filter { STAMPED.matches(it.name) && Uri.fromFile(it) != keep }
?.forEach { it.delete() }
return
}
val tree = DocumentFile.fromTreeUri(context, loc.toUri()) ?: return
tree.listFiles()
.filter { STAMPED.matches(it.name.orEmpty()) && it.uri != keep }
.forEach { it.delete() }
}
}