From 1fd7bbcd038cddf40540bae7083b9adf4c728912 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 10 Oct 2026 15:11:52 +0200 Subject: feat(android): back the videos of the chosen albums up beside the photos A 'Videos too' option, off by default, sends them into the same YYYY/YYYY-MM folders. Files are read from the phone a ranged chunk at a time, so a large video is never whole in the page. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-hub/tests/test_photo_sync.py | 30 +++++++++++++++++++++--- packages/meshbay-hub/tests/test_photo_sync_ui.py | 5 ++++ 2 files changed, 32 insertions(+), 3 deletions(-) (limited to 'packages/meshbay-hub/tests') diff --git a/packages/meshbay-hub/tests/test_photo_sync.py b/packages/meshbay-hub/tests/test_photo_sync.py index 98f49ba..c422f22 100644 --- a/packages/meshbay-hub/tests/test_photo_sync.py +++ b/packages/meshbay-hub/tests/test_photo_sync.py @@ -57,9 +57,13 @@ doc.visibilityState = 'visible'; globalThis.window = win; globalThis.document = doc; globalThis.localStorage = { getItem() { return null; }, setItem() {}, removeItem() {} }; -globalThis.fetch = async (url) => { - fake.log.push(['fetch', url]); - return { ok: true, blob: async () => new Blob([new Uint8Array(4)]) }; +globalThis.fetch = async (url, opts) => { + const range = opts && opts.headers && opts.headers.Range; + fake.log.push(['fetch', url, range || null]); + const m = /bytes=(\d+)-(\d+)/.exec(range || ''); + const n = m ? Number(m[2]) - Number(m[1]) + 1 : 4; + return { ok: true, status: m ? 206 : 200, arrayBuffer: async () => new ArrayBuffer(n), + blob: async () => new Blob([new Uint8Array(n)]) }; }; const NOW = Date.now(); @@ -95,6 +99,12 @@ fake.transport = { }, async uploadFile(file, opts) { fake.log.push(['upload', opts.dir, file.name, opts.tr]); + if (fake.readChunk) { + for (let at = 0; at < file.size; at += fake.readChunk) { + const buf = await file.slice(at, at + fake.readChunk).arrayBuffer(); + fake.log.push(['read', at, buf.byteLength]); + } + } if (fake.uploadError) { const e = new Error('refused'); e.code = fake.uploadError; throw e; } if (fake.afterUpload) fake.afterUpload(); return { stored_as: file.name, dir: opts.dir }; @@ -164,6 +174,20 @@ def test_a_due_run_sends_newest_first_and_records_each(tmp_path): "the screen-off keep-alive was not released after the run") +def test_a_file_is_read_from_the_phone_a_chunk_at_a_time(tmp_path): + """A video of gigabytes is never whole in the page: each slice the upload + reads is its own ranged request to the phone.""" + log = _run(_start(""" +fake.items = [{ token: 'v1', name: 'clip.mp4', dir: 'Media/Photos/Bob/2026/2026-10', size: 25, + edited: false, video: true }]; +fake.readChunk = 10; +"""), tmp_path) + assert _of(log, "fetch") == [["/photosync/v1", "bytes=0-9"], ["/photosync/v1", "bytes=10-19"], + ["/photosync/v1", "bytes=20-24"]] + assert _of(log, "read") == [[0, 10], [10, 10], [20, 5]] + assert ["sent", "v1", "Media/Photos/Bob/2026/2026-10", "clip.mp4"] in log + + def test_only_missing_folders_are_made_and_parents_first(tmp_path): log = _run(_start(), tmp_path) assert _of(log, "mkdir") == [["Media/Photos/Bob/2026/2026-10"]] diff --git a/packages/meshbay-hub/tests/test_photo_sync_ui.py b/packages/meshbay-hub/tests/test_photo_sync_ui.py index cbac80a..8cc9417 100644 --- a/packages/meshbay-hub/tests/test_photo_sync_ui.py +++ b/packages/meshbay-hub/tests/test_photo_sync_ui.py @@ -74,3 +74,8 @@ def test_the_messages_backup_says_where_its_copies_go(probe): def test_the_calendar_backup_says_where_its_copies_go(probe): assert "bob-calendar/calendar-2026-10-10-0900.ics" in probe["configured360"]["text"] + + +def test_videos_are_offered_and_off_by_default(probe): + assert "Videos too" in probe["setup360"]["text"] + assert "Videos too" not in probe["setup360"]["checked"] -- cgit v1.2.3