aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/ManifestLog.kt
blob: 0d7142cc2f3949455e4e2fa23ef84a9ad00e5e6b (plain) (blame)
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"
    }
}