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: `-`. */ 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) }