diff options
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 107 |
1 files changed, 97 insertions, 10 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 29b31a2..8d14e37 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -3136,6 +3136,16 @@ An album browser over the image files in the group's configured folders. It is An **album is a directory**, exactly as a season is a folder in Videos. Thumbnails are node-side, orientation-corrected, and delivered through the same chunk path. +**A video in a photo folder belongs to its album**: a phone's clips, backed up +beside its photos (§9.12), are where they were on the phone. The node probes +and thumbnails them with the video enricher (duration and a frame, nothing +looked up anywhere: matching is the Videos app's, asked by its own pages). +The album card counts photos and videos apart, a clip's tile carries a play +mark and its length, and the lightbox shows its thumbnail with a play button +that hands it to the group's video player, so a clip of a gigabyte is streamed +like a film and never downloaded whole into the lightbox. A slideshow is of +photos: it passes a clip by. The television follows photos only. + **EXIF is read locally on the node** and narrowed on purpose: a capture time and a camera, and **never GPS**, anywhere, in any field a client receives. The claim this supports is precise, and the one it must not make matters more: @@ -3414,10 +3424,39 @@ the edit is sent **beside the original**, as `<stem>-edited-<YYYYMMDD-HHMMSS>.<e from the edit's date. A touch that left the bytes alone sends nothing. "Save as copy" makes a new photo and needs no rule. -**Location.** The manifest does not ask for `ACCESS_MEDIA_LOCATION`, so a -photo read through MediaStore has its EXIF location **redacted by the -platform**: a camera roll going to a group does not say where its owner lives, -and nobody had to do anything for it. +**Videos.** Off by default, ticked in the same section: the videos of the +chosen albums (a MediaStore bucket holds both, under one id) go beside the +photos, in the same `YYYY/YYYY-MM` folders, under the same ledger, rules and +unmetered network. `READ_MEDIA_VIDEO` is asked for when they are ticked in. +The confirmation states the videos' count and size apart, because they are +most of it. A file is never whole in the page: the upload reads it a chunk at +a time and each chunk is a ranged request to the phone (`?range=a-b` in the +address, served by `ByteRange.kt`; not a `Range` header, which the WebView +does not hand to the application that answers, found on a Pixel 9), so a +video of gigabytes costs the WebView one chunk of memory; the SHA-256 the ledger keeps is then read from the phone once +the node has the file. The Photos tab shows them in their albums (§9.9). + +**A manifest, for a restore or a merge later.** The node keeps the files; only +the phone knew where each came from. Every file the node takes adds a line to +a log the phone keeps (`ManifestLog.kt`): its path on the node, its path and +album on the phone (`DCIM/Camera/PXL_….jpg`, `Camera`), when it was taken and +last modified, its size, MIME type and the SHA-256 of the bytes sent. A run +that sent anything ends by uploading the waiting lines as +`<kind folder>/meshbay-manifest/manifest-<date>.jsonl`; the phone forgets them +only once the node has that file, so a run cut short sends them with the next +one. Restoring puts each photo back into its album and each file back under +its own name and date; merging onto a phone that already has some of them +compares hashes without downloading anything. Photos, videos and files have +one; contacts, calendars and messages are each one file that describes +itself. What the manifest cannot bring back is a photo's location, removed +before it left the phone. + +**Location.** The application's Android manifest does not ask for +`ACCESS_MEDIA_LOCATION`, so a photo or video read through MediaStore has its +location **redacted by the platform** (Android 10+): a photo's EXIF, and in an MP4 or MOV the location +boxes MediaProvider finds (`IsoInterface`). A camera roll going to a group +does not say where its owner lives, and nobody had to do anything for it. On +Android 8 and 9 nothing redacts it. **Refusals.** A refusal that will hold tomorrow — `disk_full` (§6.4), a folder no longer writable, gone or on a drive that is not plugged, the member removed @@ -3431,10 +3470,10 @@ foreground service (`BackupService`) and the WebView reported visible, like a cast (§11.3). Android 15 limits that type to six hours a day; the service stops when told and the run carries on at the next opening. -### 9.13 Contacts and messages backup (Android) +### 9.13 Contacts, calendar, messages, files and WhatsApp backup (Android) -The Android application sends a copy of the phone's **contacts** and its **text -messages** to a folder of a group, on the photo backup's terms (§9.12): a client +The Android application sends a copy of the phone's **contacts**, its +**calendars** and its **text messages** to a folder of a group, on the photo backup's terms (§9.12): a client feature over the upload path, additive, once a day, the page doing the sending and the phone handing one file over by token (`/phonesync/<token>`, `phonesync/` in the Android package, `phone-sync.js` on the page). Each kind is @@ -3452,6 +3491,18 @@ contact through `ContactsContract.Contacts.CONTENT_VCARD_URI`, the format any phone imports. It is sent only when its SHA-256 differs from the last copy the node took; a day with no change sends nothing. +**Calendar.** `<folder>/<account>-calendar/calendar-2026-10-10-0900.ics`: one +iCalendar file (RFC 5545, `Ics.kt`) of every calendar the person can write to +(`CALENDAR_ACCESS_LEVEL` contributor or above); the ones they only subscribe +to, public holidays or birthdays from contacts, are someone else's and come +back by subscribing again. Timed events keep their Olson zone (`TZID`), so a +weekly 9:00 stays 9:00 across summer time; recurrences keep their RRULE, +RDATE and EXDATE, Android's `P3600S` durations become RFC's `PT3600S`, and a +changed instance carries its series' UID and a RECURRENCE-ID. Reminders, +organizer and attendees come along; each event names its calendar in +`X-MESHBAY-CALENDAR`, which importers ignore. Like contacts it is sent when +its SHA-256, taken without the DTSTAMP lines, differs from the last copy. + **Messages.** `<folder>/<account>-messages/YYYY/sms-2026-10-10-0900.xml`: the SMS added since the last file the node took (the marker is the highest provider `_id` sent; ids only grow, so a message restored onto the phone later @@ -3462,6 +3513,41 @@ character references and leaving out the characters XML 1.0 cannot carry. A contact name is filled in only when the person also allowed contacts. MMS are not included. +**Files.** `<folder>/<account>-drive/<chosen folder>/…`: the files of the +folders the person chose through the system's picker +(`ACTION_OPEN_DOCUMENT_TREE`, a persisted read grant per folder, no storage +permission, which Play keeps for a few kinds of application). It runs as +photos do, and is in fact the same runner (`createBackup` in `photo-sync.js`, +`drive-sync.js`): once a day, on an unmetered network, a file at a time under +a slot, read from the phone a range at a time (`/drivesync/<token>`), a ledger +by document id (`DriveLedger.kt`), additive. A file changed on the phone goes +beside the copy sent, named `-modified-<date>`; nothing is replaced. Nothing +is stored twice: `DCIM`, `Pictures` and `Movies` (or anything in them) and a +whole volume cannot be chosen (`DrivePlan.refusal`), and a file the photo +backup sends, wherever its album is, is left out by its path. Android 11 and +later do not let an application choose the whole of `Download`, only folders +in it; the section says so. Hidden files (`.x`) are skipped; a name the node +would refuse is mended (`DrivePlan.safeName`), never dropped. + +**WhatsApp.** `<folder>/<account>-whatsapp/`: WhatsApp's own folder, +`Android/media/com.whatsapp/WhatsApp`, chosen once through the same picker, +opened on it (`EXTRA_INITIAL_URI`; Android 17 lets it be chosen, checked on a +Pixel 9); any other folder is refused. The same class runs it as the files +backup (`DriveChannels` with `DriveKind.WHATSAPP`), the same runner sends it. +The chats are only ever in WhatsApp's own backups, encrypted with a key only +it (or the person's end-to-end backup password) opens: nothing here can read +them, a restore can use them. WhatsApp writes a full copy weekly +(`Databases/msgstore.db.crypt14`) and an increment a night +(`msgstore-increment-N.db.crypt14`), and renames last week's set with its +date when a new week starts; those dated copies are earlier versions this +backup already kept, so they are not sent again. `Backups/` (contacts, +settings, stickers) goes too. `Media/` only if the person ticks it, as it is +often gigabytes; what the photo backup sends is left out. Because files are +replaced in place, a restore must take one week's copy with that week's +increments: once anything in that set was sent, the manifest gets a +`restore-set` line naming, for each file of the set as it is now, the copy +on the node that goes with it. + **Two builds.** Play's policy gives `READ_SMS` to the default SMS application only, so the APK has a `store` dimension: `full`, distributed directly, and `play`, which has neither the permission (`src/full/AndroidManifest.xml`) nor @@ -3470,10 +3556,10 @@ answers null in `src/play`). The bridge offers `messageSync` only where the shell has a message source (`MESSAGES` in its prelude), so the Play build shows no Messages section rather than one that refuses. -**Unlike photos**, both run on any network: a contacts file is small, and a +**Unlike photos**, these run on any network: a contacts or calendar file is small, and a history of text messages is megabytes, not the gigabytes of a camera roll. -`READ_CONTACTS` and `READ_SMS` are asked for when the person turns each on, -never at start. +`READ_CONTACTS`, `READ_CALENDAR` and `READ_SMS` are asked for when the person +turns each on, never at start. ## 10. Filesystem portability @@ -4246,6 +4332,7 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows. | **The desktop cast relay takes the first chunk for the whole header** | The node's first chunk can be the ftyp alone, the moov in the next; the receiver then gives up. Fixed in the Android relay (§11.4), not in `cast-relay.js` | | **A cast restart may pull far ahead** | Seen once on an emulator with a synthetic film: after the restart's reinit the node reported `duration=None`, and the page pulled most of the film at network speed. Not reproduced on a real film; the suspicion is a read-ahead budget computed without a duration | | **"Copy stream URL" after picking a receiver casts to that receiver** | The player keeps the last device chosen, so the copy-only path reconnects it instead of only starting the relay | +| **A folder renamed on the node's disk empties until the next reconciliation** | Seen live (2026-10-10, ntfs3): renaming a 2,441-file folder inside a root made the watcher drop every entry under the old path and announce none under the new one, so the group read as empty, and the loop was busy long enough for the hub's keepalive to time out. A restart (full scan) brought them back. A directory moved within a root should rescan its new subtree at once, not wait for the periodic scan | | **A photo deleted from the group comes back after a reinstall** | The backup's memory of what it sent is the phone's ledger (§9.12); a reinstall or cleared data empties it, and the folder no longer holds the deleted photo, so it is sent again. Clean fix: the node keeps a tombstone per folder on `file_delete` (name, size, SHA-256 taken before deleting), the index carries them, and the backup records a match as sent. Not done: a protocol field and a node table for a rare case | | **The home page says "No groups yet" when the hub cannot be reached** | An unreachable hub reads as an account with no groups, rather than as an error | | **`meshbay-node init` says "Settings → Link Node"** | The control is on the Profile page, as QUICKSTART says | |