aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_DESIGN.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
-rw-r--r--docs/MESHBAY_DESIGN.md88
1 files changed, 87 insertions, 1 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 47fe62a..06ccaca 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -3323,6 +3323,88 @@ are two albums.
---
+### 9.12 Photo backup (Android)
+
+The Android application sends the photos taken on the phone to **one folder of
+one group**, chosen by the member, once a day. It is a client feature over the
+upload path that exists: a backed-up photo is a `file_upload` into a writable
+root under a slot the node granted, with every rule of §6.4, and its owner is
+recorded as for any upload. **No message, no node state and no hub state was
+added for it**; the node cannot tell a backed-up photo from one sent by hand,
+and does not need to.
+
+**Split.** The phone (`photos/` in the Android package, reached as
+`platform.photoSync`) lists the photos through MediaStore, keeps the ledger and
+hands the bytes over. The page (`photo-sync.js`) decides when a run is due and
+does the sending, because the transport and the group key are there. Bytes
+cross by an opaque token the page fetches from its own origin,
+`/photosync/<token>`, served by the shell's request interception: valid for the
+run that issued it and for the one photo it names. The page never receives a
+`content://` URI. A browser and the desktop have no camera roll and no such
+object — absent, not refusing (§11.3).
+
+**When.** A run is due 24 hours after the last run that *finished*; an
+interrupted one is retried at the next chance, at most every fifteen minutes.
+Due-ness is checked at start, on return to the foreground, when the network
+turns unmetered, and by one timer at the due time — nothing polls. A scheduled
+run needs an **unmetered** network (`NET_CAPABILITY_NOT_METERED`, not "Wi-Fi":
+a phone on another phone's hotspot is on Wi-Fi and spending that phone's data).
+"Back up now" runs whenever; on a metered network it first states the count and
+size and asks — the one confirmation in this feature that is not about where
+the photos go. Turning metered mid-run stops after the file in flight; the
+node's partial upload resumes it (§8.5).
+
+**One photo at a time, under a slot.** Each upload asks for a transfer slot
+like a member sending by hand and gives it back, so a family's daily backups
+never occupy the node's upload pool ahead of a person.
+
+**Where.** The member chooses the group (one per phone) and a folder among
+those that are writable, available and shown by the group's Photos tab — a
+folder outside those would take the photos and show them nowhere. Inside it,
+each photo goes under `YYYY/MM` from when it was taken, because an album is a
+directory (§9.9) and one folder of twenty thousand is a slow album. Albums on
+the phone are chosen too; the camera alone is the default, because screenshots
+and saved images are where photos nobody meant to share live. **By default the
+photos already on the phone are sent, newest first**, then each new one; "from
+now on" is an option. A confirmation is drawn **when the destination or the
+starting point changes, never per run and never for a change of albums alone**:
+it names the group, its owner, its member count, the folder, the count and size
+about to go, and says that members can download them and that what they
+downloaded cannot be taken back.
+
+**Additive by construction.** The backup never deletes, renames or replaces
+anything on the node: `photo-sync.js` reaches no such operation
+(`test_photo_sync.py` reads it for them). **The ledger is the memory of what was
+sent, never a mirror of the phone**: per (account, group, folder), each photo's
+MediaStore id, modification date, size, the SHA-256 of the bytes sent and the
+name the node's ack gave. A run sends what the ledger lacks and never compares
+the other way, so a photo deleted on the phone stays in the group, and one
+deleted on the node is not sent again. An empty ledger (a reinstall) is
+reconciled against the folder by name and size before anything is sent.
+
+**Edits.** A photo edited in place keeps its id and changes its modification
+date; when the size also changed, or the bytes no longer hash to what was sent,
+the edit is sent **beside the original**, as `<stem>-edited-<YYYYMMDD-HHMMSS>.<ext>`
+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.
+
+**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
+from the group, the photo permission withdrawn — stops the run, is said once in
+a notification and in Settings, and is retried a day later rather than at every
+opening. Anything else (the node offline, the network gone) is an interruption
+and is retried.
+
+**Screen off.** The sending lives in the page, so a run holds a `dataSync`
+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.
+
## 10. Filesystem portability
**exFAT and NTFS on Windows are the common case, not an edge case.** Most users are
@@ -3457,7 +3539,8 @@ narrow bridge — and Android has all three:
locks and the renderer's priority policy do not prevent it. A cast's pipeline
lives in the page (WebRTC → decrypt → relay), so while one runs the shell
keeps the WebView reported visible and holds a media-playback foreground
- service; nowhere else, because a page never hidden is never throttled.
+ service. A photo backup does the same with a `dataSync` service of its own
+ (§9.12); nowhere else, because a page never hidden is never throttled.
- **Notifications reach a closed application with nothing to install, and
never through a vendor push service.** Android lets nothing hold a connection
for an application that is not running, so there are three ways to wake a
@@ -4048,6 +4131,7 @@ seeking, audio-language and subtitle selection, transfer leases with queueing,
pause and resume, the group-application framework with Chat, Files, Videos,
Music and Photos, cross-group search with source merging, per-account playlists,
casting to a Chromecast with subtitles rebased onto the relay's clock, the
+phone's photo backup (§9.12), the
operator CLI and loopback control API, the desktop client through its identity
and download stages, account recovery, the Windows port through packaging, and
the Android client — the shell, native keys, downloads and uploads, casting,
@@ -4079,6 +4163,8 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows.
| — | **Bitmap subtitles** (PGS, VOBSUB — about a fifth of the embedded streams). No WebVTT without OCR; they are not listed rather than listed and blank. Burn-in covers them and costs `-c:v copy`, which is what the eight-slot sizing assumes never happens |
| — | Delegation (§3.4) |
| — | Tier 3 roster attestation (§3.3) |
+| — | **HEIC/HEIF in Photos.** The indexer does not classify `.heic`/`.heif` as images and no browser but Safari draws them, so a phone that shoots HEIC has its backed-up photos stored and listed in Files but absent from Photos. Needs `pillow-heif` in every package and a JPEG rendition for the viewer (§9.12) |
+| — | **Photo backup with the application closed**, and **"remove what this phone sent"** (§9.12). A closed application has no page and so no transport; it runs at the next opening |
| — | **Android: phone behaviour and release** — the back button driving the page, recovery from a network handover, keeping a download alive with the screen off, lock-screen media controls, and an update channel (§11.3) |
| — | **Federation between two hubs.** The protocol is written and switched off in the code (§7.6); what is not built is one run between two machines |