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
57
58
59
60
61
62
63
64
65
66
67
68
69
|
package org.meshbay.client.drive
import org.json.JSONObject
import java.io.File
/**
* What this phone has sent of its chosen folders, by document id: the memory
* of what was sent, never a mirror (PhotoLedger, the same idea for photos). A
* file deleted on the phone stays on the node; one deleted on the node is not
* sent again. One line of JSON per send, the last line for an id winning,
* compacted on load once dead lines outnumber live ones.
*/
class DriveLedger(private val file: File) {
data class Entry(
val docId: String,
val modified: Long,
val size: Long,
/** SHA-256 of the bytes sent, hex: a touched file is sent again only if this changed. */
val sha256: String,
val dir: String,
val name: String,
val sentAt: Long,
)
private val entries = HashMap<String, Entry>()
private var lines = 0
init { load() }
val size: Int get() = entries.size
operator fun get(docId: String): Entry? = entries[docId]
fun record(entry: Entry) {
entries[entry.docId] = entry
file.parentFile?.mkdirs()
file.appendText(encode(entry) + "\n")
lines += 1
}
private fun load() {
if (!file.exists()) return
file.forEachLine { line ->
if (line.isBlank()) return@forEachLine
lines += 1
decode(line)?.let { entries[it.docId] = it }
}
if (lines > 2 * entries.size + COMPACT_SLACK) {
val tmp = File(file.path + ".tmp")
tmp.writeText(entries.values.joinToString("") { encode(it) + "\n" })
if (tmp.renameTo(file)) lines = entries.size else tmp.delete()
}
}
companion object {
private const val COMPACT_SLACK = 64
fun encode(e: Entry): String = JSONObject()
.put("id", e.docId).put("m", e.modified).put("s", e.size).put("h", e.sha256)
.put("d", e.dir).put("n", e.name).put("t", e.sentAt).toString()
fun decode(line: String): Entry? = try {
val o = JSONObject(line)
Entry(o.getString("id"), o.getLong("m"), o.getLong("s"), o.getString("h"),
o.getString("d"), o.getString("n"), o.getLong("t"))
} catch (e: Exception) { null }
}
}
|