Skip to content

feat(media-store): cloud media storage backend, phase 1 (foundation)#550

Merged
ericgriffin merged 52 commits into
mainfrom
worktree-s3-media-store
Jul 11, 2026
Merged

feat(media-store): cloud media storage backend, phase 1 (foundation)#550
ericgriffin merged 52 commits into
mainfrom
worktree-s3-media-store

Conversation

@ericgriffin

@ericgriffin ericgriffin commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Phase 1 (Foundation) of the Media Store: a user-owned cloud location (any S3-compatible service) becomes the canonical home for photo bytes. Photos attached on one device now display on every synced device through the store, instead of today's fragile gallery re-matching, and survive device loss. Design spec: docs/superpowers/specs/2026-07-10-s3-media-storage-design.md (all four goals, independent config from sync, photos+video across four backends over five phases); implementation plan: docs/superpowers/plans/2026-07-10-media-store-phase1.md.

Bytes live at content-addressed keys (smv1/objects/<aa>/<sha256>.<ext>) under the user's bucket prefix; the synced media row carries content_hash + remote_uploaded_at confirmation stamps; display falls back from the native resolver to a hash-verified, LRU-evicted local cache. A store.json identity marker detects bucket wipe/repoint and suspends transfers (same defensive pattern as sync's library epoch).

Changes

  • Schema v103: four nullable media columns (content_hash, content_size_bytes, remote_uploaded_at, remote_thumb_uploaded_at) plus the synced, secret-free media_stores descriptor table; idempotent DDL with a beforeOpen backstop against parallel-branch version collisions
  • mediaStores registered across the sync engine (serializer, merge order, _hlcTargets), mirroring buddyRoles; covered by the existing registration guard tests
  • Local cache DB v2: per-device media_transfer_queue (retry with backoff, terminal after 5 attempts) and media_cache_entries tables
  • New lib/core/services/media_store/: MediaObjectStore interface (file-based, Phase 3-ready), S3 adapter over the existing S3ApiClient/SigV4 stack (single-shot; multipart lands in Phase 3), smv1 key derivation, streamed SHA-256, store identity marker, keychain credentials store (media_store_s3_config), SharedPreferences attach state
  • Upload pipeline: resolve bytes via the existing resolver registry, hash, head() dedup (content addressing makes cross-device races converge), upload, stamp; enqueued automatically on photo attach; single-flight worker with a marker preflight
  • MediaStoreResolver engaged as a MediaItemView fallback (deliberately NOT registered under a MediaSourceType; rows keep their native source type, so disconnecting degrades to exactly today's behavior); downloads are hash-verified before entering the content-addressed cache (originals 2 GiB / thumbs 256 MiB pools)
  • Settings -> Media Storage page: S3 form (test-connection probe, "Copy settings from Sync" prefill, insecure-endpoint warning), connect/disconnect flows; new route + settings tile; settings_mediaStorage_* strings added to all 11 locales

Test Plan

  • flutter test passes (targeted per repo practice: sync suite 422, database suite 133, media + media_store 605, settings 608 -- includes new migration, sync round-trip/incremental/e2e, pipeline, cache eviction, resolver fallback widget tests, and a two-device store round-trip)
  • flutter analyze passes (no issues)
  • Manual testing on: macOS against MinIO (Task 13 Step 5 of the plan has the docker commands; Docker daemon was not running during implementation), plus a two-device cross-device smoke

Screenshots

New Settings -> Media Storage page follows the existing S3 sync config form; screenshots to follow with the macOS manual pass.

Phases 2-5 (thumbnails/transfers UI/backfill, multipart + video, Drive/Dropbox/iCloud adapters, deletion/GC/verify) follow as separate PRs per the spec's delivery plan.

Copilot AI review requested due to automatic review settings July 10, 2026 13:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Phase 1 (foundation) of the Media Store feature: adds a user-owned, S3-compatible cloud “canonical home” for media bytes, with synced metadata stamps (content_hash, upload confirmations) and a local, content-addressed cache + upload queue to support cross-device media display and resilience.

Changes:

  • Adds Media Store schema foundations: media content identity + upload stamps and new synced media_stores descriptor table, wired into the sync engine.
  • Introduces a file-based MediaObjectStore abstraction with an S3 adapter, plus store marker identity (smv1/store.json) and key derivation (smv1/objects/...).
  • Adds per-device local-cache DB v2 tables for transfer queue + cache index, upload pipeline/worker, store-backed resolver fallback, and a new Settings → Media Storage page + localized strings.

Reviewed changes

Copilot reviewed 71 out of 72 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/helpers/in_memory_media_object_store.dart In-memory MediaObjectStore test double with failure injection
test/features/media/presentation/media_item_view_store_fallback_test.dart Widget test: MediaItemView falls back to store when native source unavailable
test/features/media/data/media_store_resolver_test.dart Unit tests for store-backed resolver (cache + hash verification)
test/features/media/data/media_repository_store_stamps_test.dart Tests for new media repository stamp fields round-tripping
test/features/media_store/support/fake_local_file_resolver.dart Test resolver for localFile sources in media-store tests
test/features/media_store/media_upload_pipeline_test.dart Pipeline tests: upload, dedup, replay, failure scheduling, eligibility
test/features/media_store/media_transfer_queue_repository_test.dart Queue repository tests + local cache DB v2 migration coverage
test/features/media_store/media_store_service_test.dart MediaStoreService tests for connect/test/disconnect flows
test/features/media_store/media_store_end_to_end_test.dart E2E test: device A upload → device B resolve via shared store
test/features/media_store/media_storage_page_test.dart Widget test for Media Storage settings page form/connect behavior
test/features/media_store/media_cache_store_test.dart Cache store tests: put/get, LRU eviction, self-heal, pool separation
test/core/services/sync/sync_media_stores_test.dart Sync tests for new mediaStores replication plumbing
test/core/services/media_store/store_marker_test.dart Store marker ensure/read/corrupt-json behavior tests
test/core/services/media_store/store_keys_test.dart StoreKeys + streamed SHA-256 tests
test/core/services/media_store/s3_media_object_store_test.dart S3 adapter tests via MockClient
test/core/services/media_store/media_store_credentials_test.dart Keychain credential store + attach-state round-trip tests
test/core/services/media_store/media_object_store_contract.dart Behavioral contract tests for MediaObjectStore implementations
test/core/services/media_store/media_object_store_contract_test.dart Contract test runner for InMemoryMediaObjectStore
test/core/database/migration_v103_media_store_test.dart Main DB v103 migration + backstop coverage
lib/l10n/arb/app_zh.arb Adds Media Storage strings (zh)
lib/l10n/arb/app_pt.arb Adds Media Storage strings (pt)
lib/l10n/arb/app_nl.arb Adds Media Storage strings (nl)
lib/l10n/arb/app_localizations.dart Generated localization API: new Media Storage keys
lib/l10n/arb/app_localizations_zh.dart Generated zh localization implementation updates
lib/l10n/arb/app_localizations_pt.dart Generated pt localization implementation updates
lib/l10n/arb/app_localizations_nl.dart Generated nl localization implementation updates
lib/l10n/arb/app_localizations_it.dart Generated it localization implementation updates
lib/l10n/arb/app_localizations_hu.dart Generated hu localization implementation updates
lib/l10n/arb/app_localizations_he.dart Generated he localization implementation updates
lib/l10n/arb/app_localizations_fr.dart Generated fr localization implementation updates
lib/l10n/arb/app_localizations_es.dart Generated es localization implementation updates
lib/l10n/arb/app_localizations_en.dart Generated en localization implementation updates
lib/l10n/arb/app_localizations_de.dart Generated de localization implementation updates
lib/l10n/arb/app_localizations_ar.dart Generated ar localization implementation updates
lib/l10n/arb/app_it.arb Adds Media Storage strings (it)
lib/l10n/arb/app_hu.arb Adds Media Storage strings (hu)
lib/l10n/arb/app_he.arb Adds Media Storage strings (he)
lib/l10n/arb/app_fr.arb Adds Media Storage strings (fr)
lib/l10n/arb/app_es.arb Adds Media Storage strings (es)
lib/l10n/arb/app_en.arb Adds Media Storage strings (en) + formatting adjustments
lib/l10n/arb/app_de.arb Adds Media Storage strings (de)
lib/l10n/arb/app_ar.arb Adds Media Storage strings (ar)
lib/features/settings/presentation/pages/settings_page.dart Adds Settings tile linking to Media Storage page
lib/features/media/presentation/widgets/media_item_view.dart Adds store-backed fallback resolution when native source unavailable
lib/features/media/presentation/providers/photo_picker_providers.dart Enqueues uploads on media creation via provider hook
lib/features/media/domain/entities/media_item.dart Adds content hash/size + remote upload stamp fields to domain model
lib/features/media/data/services/media_import_service.dart Calls onMediaCreated hook after create/import for upload enqueue
lib/features/media/data/resolvers/media_store_resolver.dart Store-backed resolver: download → hash verify → cache → FileData
lib/features/media/data/repositories/media_repository.dart Persists new media fields + adds stampContentIdentity/stampRemoteUploaded
lib/features/media_store/presentation/providers/media_store_providers.dart Runtime construction: store, cache, resolver, upload worker + status hint
lib/features/media_store/presentation/providers/media_store_enqueue_provider.dart Small bridge provider for enqueue hook wiring
lib/features/media_store/presentation/pages/media_storage_page.dart New S3 media-store configuration UI + connect/test/disconnect flows
lib/features/media_store/data/media_upload_pipeline.dart Upload pipeline: materialize, hash, dedup via head, upload, stamp, retry
lib/features/media_store/data/media_transfer_queue_repository.dart Local queue repo: enqueue, nextPending, state transitions, backoff
lib/features/media_store/data/media_stores_repository.dart Synced secret-free store descriptor persistence + pending sync marking
lib/features/media_store/data/media_store_worker.dart Single-flight queue drainer + enqueue-and-kick
lib/features/media_store/data/media_store_service.dart Connect/test/disconnect service using marker + credential persistence
lib/features/media_store/data/media_cache_store.dart Content-addressed cache + index + LRU eviction across two pools
lib/core/services/sync/sync_service.dart Registers mediaStores in sync merge order and updatedAt map
lib/core/services/sync/sync_data_serializer.dart Adds mediaStores to SyncData + export/import/upsert/delete plumbing
lib/core/services/media_store/store_marker.dart Store marker read/ensure implementation (smv1/store.json)
lib/core/services/media_store/store_keys.dart Key derivation + streamed sha256OfFile helper
lib/core/services/media_store/s3_media_object_store.dart S3-backed MediaObjectStore adapter over S3ApiClient
lib/core/services/media_store/media_store_credentials_store.dart Keychain storage for media store S3 config under its own key
lib/core/services/media_store/media_store_attach_state.dart SharedPreferences attach state (storeId)
lib/core/services/media_store/media_object_store.dart MediaObjectStore interface + StoreObjectInfo + error taxonomy
lib/core/router/app_router.dart Adds /settings/media-storage route
lib/core/database/local_cache_database.dart Local cache DB v2: transfer queue + cache entries tables + migration
lib/core/database/database.dart Main DB v103: media store columns + media_stores table + backstop DDL
lib/core/data/repositories/sync_repository.dart Registers mediaStores entity mapping for sync pending records
docs/superpowers/specs/2026-07-10-s3-media-storage-design.md Media Store design spec (Phase plan + goals + architecture)

Comment thread lib/features/media_store/data/media_upload_pipeline.dart
Comment thread lib/features/media_store/presentation/pages/media_storage_page.dart
Comment thread lib/core/services/media_store/store_marker.dart
Comment thread lib/core/services/media_store/store_marker.dart
Comment thread lib/core/services/media_store/store_marker.dart
Comment thread lib/features/media_store/data/media_store_service.dart
Comment thread lib/features/media_store/data/media_store_service.dart
Comment thread lib/features/media_store/data/media_store_service.dart Outdated
@ericgriffin ericgriffin changed the base branch from main to media-store July 10, 2026 20:49
Copilot AI review requested due to automatic review settings July 10, 2026 20:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 11 comments.

Comment thread lib/core/services/media_store/store_marker.dart
Comment thread lib/core/services/media_store/store_marker.dart
Comment thread lib/core/services/media_store/store_marker.dart
Comment thread lib/features/media_store/data/media_store_service.dart
Comment thread lib/features/media_store/data/media_store_service.dart
Comment thread lib/features/media_store/data/media_upload_pipeline.dart
Comment thread lib/features/media_store/data/media_transfer_queue_repository.dart Outdated
Comment thread lib/core/services/media_store/s3_media_object_store.dart
Comment thread lib/features/media_store/data/media_store_service.dart
Comment thread lib/features/media_store/data/media_store_service.dart
Copilot AI review requested due to automatic review settings July 10, 2026 21:00
@ericgriffin ericgriffin force-pushed the worktree-s3-media-store branch from 5719d23 to 845e022 Compare July 10, 2026 21:00
@ericgriffin ericgriffin changed the base branch from media-store to main July 10, 2026 21:00
@ericgriffin ericgriffin requested a review from Copilot July 10, 2026 21:00
@ericgriffin ericgriffin merged commit 4b499c2 into main Jul 11, 2026
25 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in Submersion Release Tracker Jul 11, 2026
@ericgriffin ericgriffin deleted the worktree-s3-media-store branch July 11, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants