package com.vgmlr.kiln
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import org.json.JSONArray
import org.json.JSONObject
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
data class KilnBundle(
val account: KilnAccount,
val buttons: List<KilnButton>,
val records: List<KilnDayRecord>
)
object KilnTransfer {
const val MIME = "application/json"
private const val VERSION = 1
private val STAMP = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")
private val NAME = Regex("""^kiln_\d{8}_\d{6}.*\.json$""", RegexOption.IGNORE_CASE)
fun fileName(now: LocalDateTime = LocalDateTime.now()): String =
"kiln_${now.format(STAMP)}.json"
fun isKilnFile(name: String?): Boolean = name != null && NAME.matches(name)
fun displayName(context: Context, uri: Uri): String? =
runCatching {
context.contentResolver.query(uri, null, null, null, null)?.use { c ->
val i = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (i != -1 && c.moveToFirst()) c.getString(i) else null
}
}.getOrNull()
fun encode(
account: KilnAccount,
buttons: List<KilnButton>,
records: List<KilnDayRecord>
): String = JSONObject().apply {
put("version", VERSION)
put("account", JSONObject().apply {
put("name", account.name)
put("hair", account.hair)
put("pmsAlert", account.pmsAlert)
put("pmsMessage", account.pmsMessage)
put("menstruationAlert", account.menstruationAlert)
put("menstruationMessage", account.menstruationMessage)
put("menstruationReminder", account.menstruationReminder)
})
put("buttons", JSONArray().apply {
buttons.forEach {
put(JSONObject().apply {
put("id", it.id)
put("label", it.label)
put("kind", it.kind)
put("sort", it.sort)
})
}
})
put("records", JSONArray().apply {
records.forEach { r ->
put(JSONObject().apply {
put("epochDay", r.epochDay)
r.mood?.let { put("mood", it) }
put("checked", JSONArray().apply { r.checked.sorted().forEach { put(it) } })
})
}
})
}.toString(2)
fun decode(text: String): KilnBundle? = runCatching {
val root = JSONObject(text)
if (root.optInt("version") != VERSION) return@runCatching null
val a = root.getJSONObject("account")
val account = KilnAccount(
name = a.getString("name"),
hair = a.optString("hair", KilnHair.BROWN.name),
pmsAlert = a.optBoolean("pmsAlert"),
pmsMessage = a.optString("pmsMessage"),
menstruationAlert = a.optBoolean("menstruationAlert"),
menstruationMessage = a.optString("menstruationMessage"),
menstruationReminder = a.optBoolean("menstruationReminder")
)
val ba = root.getJSONArray("buttons")
val buttons = (0 until ba.length()).map { i ->
val o = ba.getJSONObject(i)
KilnButton(
id = o.getLong("id"),
accountId = 0,
label = o.getString("label"),
kind = o.getInt("kind"),
sort = o.getInt("sort")
)
}
val ra = root.getJSONArray("records")
val records = (0 until ra.length()).map { i ->
val o = ra.getJSONObject(i)
val ca = o.getJSONArray("checked")
KilnDayRecord(
accountId = 0,
epochDay = o.getLong("epochDay"),
mood = if (o.has("mood")) o.getInt("mood") else null,
checked = (0 until ca.length()).mapTo(HashSet()) { ca.getLong(it) }
)
}
KilnBundle(account, buttons, records)
}.getOrNull()
fun write(context: Context, uri: Uri, text: String): Boolean = runCatching {
context.contentResolver.openOutputStream(uri)?.use {
it.write(text.toByteArray())
} != null
}.getOrDefault(false)
fun read(context: Context, uri: Uri): String? = runCatching {
context.contentResolver.openInputStream(uri)?.bufferedReader()?.use { it.readText() }
}.getOrNull()
}