Pith - kiln
kiln/app/src/main/java/com/vgmlr/kiln/KilnTransfer.kt [4.4 kb]
Modified: 18:59:26 154 026 (20 Aug 026)
3 Days Ago
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()
}
Updates
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00243
257,104 (+358)