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
70
71
72
73
74
75
76
77
78
79
|
package org.meshbay.client.drive
import org.json.JSONObject
import java.util.Locale
/**
* A backup of folders chosen through the system's picker: the files backup,
* or WhatsApp's own folder (docs/MESHBAY_DESIGN.md §9.13). One class runs both
* (DriveChannels); this is what differs.
*/
data class DriveKind(
/** Names the channels (`drivesync:status`) and the preferences. */
val prefix: String,
/** The folder under the destination: `<account>-<suffix>`. */
val suffix: String,
val notifyId: Int,
/** Why a chosen folder is refused, from its tree document id, or null. */
val refusal: (String) -> String?,
/** One folder only: choosing again replaces it. */
val single: Boolean,
/** The chosen folder's own name as the first level on the node. */
val keepTop: Boolean,
/** Whether a file found there is sent, given the options the person set. */
val include: (DriveFile, JSONObject) -> Boolean,
/** The files a restore needs together, listed in the manifest as one set; null when none. */
val restoreSet: ((DriveFile) -> Boolean)?,
/** Where the picker opens. */
val initialDocId: String?,
) {
val path: String get() = "/$prefix/"
companion object {
val FILES = DriveKind(
prefix = "drivesync", suffix = "drive", notifyId = 13,
refusal = DrivePlan::refusal, single = false, keepTop = true,
include = { _, _ -> true }, restoreSet = null, initialDocId = null,
)
val WHATSAPP = DriveKind(
prefix = "wasync", suffix = "whatsapp", notifyId = 14,
refusal = WhatsApp::refusal, single = true, keepTop = false,
include = { f, options -> WhatsApp.include(f, options.optBoolean("media", false)) },
restoreSet = WhatsApp::inRestoreSet,
initialDocId = "primary:" + WhatsApp.FOLDER,
)
}
}
/**
* WhatsApp's folder, `Android/media/com.whatsapp/WhatsApp` (or the business
* application's): what a restore onto a new phone needs, and the media.
*
* `Databases/` holds the chats, encrypted by WhatsApp with a key only it (or
* the person's end-to-end backup password) opens: a full copy written weekly,
* `msgstore.db.crypt14`, and an increment a night,
* `msgstore-increment-N.db.crypt14`. When a new week starts WhatsApp renames
* the last week's set with its date; those dated copies are what this backup
* already kept as earlier versions, so they are not sent again. `Backups/`
* holds the rest of a restore (contacts, settings, stickers).
*/
object WhatsApp {
const val FOLDER = "Android/media/com.whatsapp/WhatsApp"
private val FOLDERS = setOf(FOLDER, "Android/media/com.whatsapp.w4b/WhatsApp Business")
private val DATED = Regex("^msgstore(-increment-\\d+)?-\\d{4}-\\d{2}-\\d{2}\\..*", RegexOption.IGNORE_CASE)
fun refusal(treeDocId: String): String? =
if (DrivePlan.pathOf(treeDocId) in FOLDERS) null else "not_whatsapp"
private fun top(f: DriveFile) = f.sub.substringBefore('/').lowercase(Locale.ROOT)
fun include(f: DriveFile, media: Boolean): Boolean = when (top(f)) {
"databases" -> !DATED.matches(f.name)
"backups" -> true
"media" -> media
else -> false
}
fun inRestoreSet(f: DriveFile): Boolean = include(f, false)
}
|