package org.meshbay.client.phonesync import org.json.JSONObject import java.io.File import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import java.util.TimeZone /** * What a backup sent, as the phone knew it, for a restore or a merge later * (docs/MESHBAY_DESIGN.md ยง9.12): one JSON line per file the node took, with * where it came from on the phone, its album or folder, its dates, size and * SHA-256. The node has the files; only the phone knew these. * * Lines wait here until a manifest carrying them is on the node: `issue()` * gathers them into the file to send, `confirm()` forgets them once the node * has it. A manifest that did not arrive is sent again, with whatever was * added since, at the next run: nothing is lost to an interrupted one. */ class ManifestLog(private val pending: File) { private val sending = File(pending.path + ".sending") fun append(line: JSONObject) { pending.parentFile?.mkdirs() pending.appendText(line.toString() + "\n") } /** True when there are lines no manifest on the node holds yet. */ fun waiting(): Boolean = (sending.exists() && sending.length() > 0) || (pending.exists() && pending.length() > 0) /** The file to send now, holding every waiting line, or null. */ fun issue(): File? { if (pending.exists() && pending.length() > 0) { if (sending.exists()) { sending.appendText(pending.readText()); pending.delete() } else if (!pending.renameTo(sending)) return null } return sending.takeIf { it.exists() && it.length() > 0 } } /** The node has it: those lines are done. */ fun confirm() { sending.delete() } /** Everything forgotten: the destination changed, or the backup was turned off. */ fun clear() { pending.delete(); sending.delete() } companion object { /** The folder manifests go into, under a kind's own folder. */ const val DIR = "meshbay-manifest" /** `manifest-2026-10-10-143205.jsonl`, in the phone's time: never one name twice. */ fun nameFor(now: Long, zone: TimeZone): String = "manifest-" + SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.ROOT).apply { timeZone = zone }.format(Date(now)) + ".jsonl" } }