Přeskočit obsah

AVAXs3 — synchronizace složek: vylepšení appky + PC stažení

Status: NÁVRH (2026-06-08). Navazuje na s3-data-exchange.md §3 (device folder sync) a §3.4 (_options.json). Řeší: (a) UX přepracování AVAXs3, (b) reálný upload souborů (zatím chybí), (c) stažení sdílených složek na PC z launcheru.


0. Aktuální stav (ověřeno 2026-06-08)

Na S3 pod users/{uid}/sync/ jsou jen 2 metadata soubory, žádné fotky:

sync/_options.json      1944 B   ← nabídka kategorií (backend)
sync/_selections.json   1068 B   ← výběr uživatele (telefon)
V _selections.json mají všechny folder_uri = null → SAF výběr složky se nedokončil/neuložil. A hlavně: appka zatím soubory vůbec nenahrává — uloží jen výběr. Proto na serveru nejsou žádné fotky.

Závěr: chybí (1) spolehlivý výběr složky + vizuální potvrzení, (2) reálný upload obsahu, (3) PC strana pro stažení. Tento spec to dořeší.


1. AVAXs3 — UX přepracování

1.1 Kamera za tlačítkem (ne při startu)

Dnes se skener spustí hned po otevření appky. Změna: - Domovská obrazovka = stav (spárováno? kolik složek, poslední záloha) + akce. - Kamera/QR jen po tapu „Spárovat mobil" v Nastavení (a po spárování skrytá). - Sekce: Domů · Synchronizace · Nastavení (spodní navigace / taby).

1.2 Seznam synchronizací

Záložka Synchronizace = seznam položek (kategorie z _options.json + vlastní složky uživatele): - řádek: ikona dle kind · název · cílový s3_subdir · stav (●Čeká/⟳Nahrávám/✓Hotovo/⚠Chyba) · velikost/počet. - toggle zapnout/vypnout · tap → detail složky (§1.4).

1.3 Přidat vlastní složku

Tlačítko „+ Přidat složku" → SAF OpenDocumentTree → uloží se jako nová sync položka kind:"custom", s3_subdir = "custom/<slug-názvu>". Vlastní složky se ukládají lokálně (DataStore) — nezávisí na _options.json.

1.4 Detail složky — obsah + postup zálohy

Po tapu na položku: - Obsah složky: seznam souborů (název, velikost, datum) + souhrn („128 souborů · 412 MB"). Čte se přes SAF DocumentFile.listFiles() z vybraného tree URI. - Postup zálohy: per soubor stav (čeká/nahrává %/hotovo/chyba) + celkový progress bar + „24/128 · 78 MB / 412 MB". Tlačítka Zálohovat teď / Pozastavit.

Tím se vyřeší dnešní problém „nevím jestli se něco nahrálo" — uživatel vidí obsah i postup.


2. Reálný upload (F1) — manifest protokol

Per s3-data-exchange.md §3.1–3.2. Pro každou zapnutou složku:

Cíl v S3 (company bucket, user prefix):

users/{uid}/sync/<s3_subdir>/
├── manifest.json          ← { device_id, updated_at, files:[{path, size, sha256, mtime}] }
└── files/<relativní cesta>

Cyklus: 1. Projdi tree URI (DocumentFile), spočítej sha256 změněných (cache mtime/size → přeskoč nezměněné). 2. Stáhni vzdálený manifest.json (S3Client, existuje). 3. Diff: lokální nový/změněný → upload files/<path> (multipart pro velké) → progress callback. 4. manifest.json zapsat POSLEDNÍ = commit marker (konzument nikdy nečte half-upload). 5. Foreground service + notifikace s progresem; WiFi-only toggle, retry/backoff.

Smazání: soubor zmizí lokálně → varianta A: nech na S3 (jen přibývá), varianta B: tombstone v manifestu + GC. Default A (bezpečnější), GC později.


3. PC strana — stažení sdílených složek z mobilu (launcher2)

Odpověď na „jak je načtu v PC + předvyplněný vzor, já jen vyberu kam ukládat".

Nová sekce v launcheru (vedle Zálohování): „📱 Sdílené z mobilu". Launcher má S3 přístup (SyncAgent/boto3) → čte tytéž users/{uid}/sync/*/.

3.1 Předvyplněný vzor (mapování)

Launcher zná výchozí strukturu — uživatel vybere jeden základní PC adresář (<base>), zbytek se rozloží automaticky:

Mobilní s3_subdir Výchozí PC cíl
photos <base>/Fotky/
service-settings <base>/Nastaveni/
apps/<slug> <base>/Aplikace/<slug>/
custom/<name> <base>/<name>/

Per-položka lze cíl přepsat. Mapování se uloží do launcher configu.

3.2 Stažení / zrcadlení

  • Launcher vylistuje users/{uid}/sync/ (prefixy = sdílené složky) + jejich manifest.json.
  • Per složka: Stáhnout → mirror files/* do namapovaného PC adresáře (diff dle sha256, jen nové/změněné). Progress + „naposledy staženo".
  • Volitelně auto-pull (SyncAgent tick) — periodicky tahá nové soubory z mobilu.

3.3 Tok celkem

Telefon (AVAXs3)  ──upload──▶  S3 users/{uid}/sync/<subdir>/  ──pull──▶  PC launcher <base>/…
                manifest + files                              manifest + files

4. Datový model na S3 (manifest/v1)

// users/{uid}/sync/<subdir>/manifest.json
{
  "schema": "sync-manifest/v1",
  "device_id": "<android-id>",
  "subdir": "photos",
  "updated_at": "2026-06-08T…Z",
  "files": [
    { "path": "IMG_2026.jpg", "size": 3145728, "sha256": "…", "mtime": 1733… }
  ]
}
Obě strany (telefon upload, PC pull) čtou/píší stejný manifest. Atomicita = manifest poslední.


5. Bezpečnost / poznámky

  • Telefon i launcher = company creds (test) / STS prefix-scope users/{uid}/sync/ (produkce).
  • sha256 integrita na obou koncích.
  • SAF persistovaná oprávnění (takePersistableUriPermission) přežijí restart.
  • Foreground service = Android notifikace povinná (jinak systém zabije upload).
  • Velké soubory: multipart upload + resumable (uchovat upload stav).

6. Moje změny + doporučení

# Komponenta Změna
1 AVAXs3 nav Domů/Synchronizace/Nastavení taby; kamera za tlačítkem (ne autostart)
2 AVAXs3 „+ Přidat složku" (SAF) → vlastní sync položka
3 AVAXs3 Detail složky: obsah (DocumentFile.listFiles) + progress UI
4 AVAXs3 Reálný upload (manifest + foreground service + progress) — SyncEngine.kt
5 launcher2 Sekce „Sdílené z mobilu" + předvyplněný vzor + výběr <base> + mirror pull
6 backend (volitelně) manifest/v1 helper; jinak čistě klient↔S3

Doporučení: 1. Upload prioritně (§2) — bez něj je vše ostatní prázdné; teď na S3 nejsou žádné fotky. 2. Opravit SAF výběr — dnes folder_uri=null; přidat vizuální potvrzení („Vybráno: …, N souborů"). 3. WiFi-only + foreground service od začátku (jinak Android upload zabije / sežere data). 4. Manifest jako jediný kontrakt obou stran → launcher i telefon nezávisle. 5. Pořadí: §1.1 nav + §1.4 detail → §2 upload → §3 PC pull.


7. Fáze

  • A ✅ AVAXs3 nav rework (Domů/Synchronizace/Nastavení, kamera za tlačítkem) + detail složky (obsah DocumentFile.listFiles + progress) + „➕ Přidat složku". → MainActivity.kt (AppRoot/HomeScreen/SettingsTab/PairingScreen), SettingsScreen.kt (SyncScreen + FolderDetailScreen). APK 0.3.0.
  • B ✅ Reálný upload — SyncEngine.kt (walk + diff dle path/size/mtime + sha256
  • manifest poslední), SyncService.kt (foreground service + notifikace progress), SyncState.kt, SyncManifest.kt. S3Client putFile/listUnderPrefix. Manifest users/{uid}/sync/<subdir>/manifest.json + files/.
  • C ✅ Launcher screens/mobile_shared.py — „📱 Sdílené z mobilu" (nav v _dd_storage), předvyplněný vzor _local_subpath, výběr <base> (filedialog, persist ~/.avax/mobile_sync.json), download/mirror dle velikosti.
  • D ⏳ Auto-pull (SyncAgent tick) + smazání/GC + WiFi-only + multipart resumable.

8. v2 Sync engine — škálování (40 GB+), baterie, jen nové soubory

Problém (2026-06-08): přidání adresáře se 40 GB fotek appku zaseklo (freeze / ANR). v1 engine nešká­luje na desítky tisíc souborů. v2 to přepisuje.

8.1 Tři příčiny záseku (v1)

  1. Enumerace přes DocumentFile (SyncEngine.enumerate rekurze + listFiles()) — každý soubor = víc IPC dotazů na DocumentsProvider; ~40k souborů = 100k+ IPC + velký List<DocumentFile> v paměti → minuty / OOM / ANR. Hlavní příčina.
  2. Enumerate-then-processSyncService napřed projde VŠE + sumOf totals, teprve pak uploaduje → dlouho „appka nic nedělá".
  3. Diff přes remote manifest.json + double-read (copy→sha, pak put) každý běh.

8.2 v2 princip

Vrstva v1 v2
Walk DocumentFile rekurze, List všeho streaming DocumentsContract cursor (1 dotaz/adresář, žádný DocumentFile objekt)
„Co je hotové" remote manifest fetch lokální index (Room/SQLite) = source of truth
Zpracování enumerate → process stream: walk → diff → enqueue → upload inkrementálně
Běh foreground service (vše naráz) WorkManager (resumable, constraint-aware)
Velké soubory 1× PUT (93 MB video) S3 multipart (resume per part)
Integrita copy+sha (2× čtení) 1× čtení (stream + DigestInputStream)
Concurrency sériově bounded 2–3 paralelně

8.3 Lokální index = jádro „jen nové soubory"

Room synced_file(folder_id, doc_id, rel_path, size, mtime, etag, uploaded_at). - Re-sync: cursor walk → per soubor lookup index[doc_id]; size+mtime sedí → SKIP (žádné čtení, síť ani sha). Jen chybějící/změněné → upload + upsert. - Re-sync nezměněného 40 GB adresáře = jen cursor walk (sekundy), nula uploadu, minimum baterie. (Přesně požadavek „už neřeš synchronizované, jen nové".) - Self-healing: ztracený index (clear app data) → reconcile ze S3 listingu (list files/ + size → seed index) → nepřeuploaduje, co na S3 už je. - Remote manifest.json pro diff netřeba — launcher PC-pull diffuje přes S3 listing (mobile_shared.py). Manifest volitelný/legacy.

8.4 Baterie & data

  • WorkManager constraints: velký initial backup default WiFi (unmetered) + nabíjení (nebo battery-not-low) → 40 GB přes noc na nabíječce, baterie se neřeší. Inkrementál (pár fotek) i na baterii.
  • Žádné re-čtení/re-sha synced (index) → žádné mrhání CPU/I/O.
  • 1 čtení na soubor; WorkManager backoff + zastaví při odpojení a sám pokračuje.

8.5 40 GB initial backup

Resumable: WorkManager přežije kill/reboot; index = hotové → po přerušení pokračuje (skip indexovaných). Multipart resume velkých videí. Uživatel zapojí nabíječku na WiFi → projede 40 GB, pak už se těch souborů nikdy nedotkne.

8.6 UI (žádný freeze)

  • Detail složky NEenumeruje SAF strom — souhrn z indexu (synced X / pending Y / GB)
  • WorkInfo progress + malý recent vzorek.
  • Přidání složky = jen registrace; walk až v jobu na pozadí.

8.7 Fáze

  • E1 (fix záseku + jádro): streaming cursor walk (nahradí DocumentFile) + Room index (jen nové) + stream-process + 1-read upload. ⇒ odstraní freeze + „jen nové" + velká část baterie.
  • E2 (baterie/robustnost): WorkManager + constraints (WiFi+nabíjení) + bounded concurrency + backoff.
  • E3 (velké soubory): S3 multipart resumable (>8 MB).

9. Související

  • docs/spec/s3-data-exchange.md — §3 device sync, §3.4 _options.json, QR pairing.
  • docs/spec/android-app-distribution.md — AVAXs3 store/updater (sesterský feature).
  • desktop/launcher2/screens/backup.py — vzor S3 sync UI (SyncAgent).
  • backend/app/services/devices.pyDeviceSyncDir model.