1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
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"
}
}
|