Skip to content

feat: background photo backup + move, keep-name, by-album options#56

Open
grigorii-horos wants to merge 3 commits into
siddarthkay:masterfrom
grigorii-horos:pr/photo-backup-background
Open

feat: background photo backup + move, keep-name, by-album options#56
grigorii-horos wants to merge 3 commits into
siddarthkay:masterfrom
grigorii-horos:pr/photo-backup-background

Conversation

@grigorii-horos

Copy link
Copy Markdown

What

Builds on the manual backup with four opt-in capabilities, all surfaced on the Photo Backup settings screen:

  • Periodic background backup — a TaskManager task registered via expo-background-task (WorkManager on Android, BGTaskScheduler on iOS) runs the same backup on an Hourly / 6-hour / Daily cadence. It ensures the Go daemon is up in the headless JS runtime and refreshes the folder path before running. App launch reconciles the OS registration with the saved config so an enabled job survives reinstalls / cleared registrations.
  • Move instead of copy — after an asset is verified present in the backup folder, delete the original from the gallery. One batched delete = at most one system consent dialog (silent with All Files Access, so it also works headless). Assets skipped by the overlap guard are never deleted.
  • Keep original filename (default on) — when off, files are renamed to a creation-time stamp (2024-08-11_143022.HEIC) to avoid collisions.
  • by-album folder structure — group copies by the source album (on-disk parent/bucket name), falling back to the folder root.

Adds deps expo-background-task + expo-task-manager (yarn.lock updated via Yarn 4 lockfile-only resolution — minimal diff).

⚠️ Depends on #54

This branch is stacked on #54 (the overlap-fix), since both edit PhotoBackup.ts. Please merge #54 first; I'll rebase this onto master afterwards. The first two commits here belong to #54.

Test

  • tsc --noEmit

🤖 Generated with Claude Code

grigorii-horos and others added 3 commits June 19, 2026 14:40
Photo backup writes through the Go bridge's CopyFile, which sandboxes the
destination to known roots. Two folder kinds were unreachable:

  - SAF folders, whose Path is an opaque content:// tree URI rather than a
    POSIX path: resolve the destination to its owning SAF tree and the
    path relative to it, then write via the SAF bridge.
  - All-Files-Access folders that live outside foldersRoot (e.g. DCIM) and
    aren't registered as externalRoots on Android: treat every configured
    folder's real filesystem path as an extra sandbox root.

safTreeForPath is unexported so gomobile doesn't try to bind its
three-value return (bind allows at most one value plus error).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the backup target overlaps the media source (the user picks DCIM,
Pictures, or a nested album like DCIM/Archive), gallery assets were
copied back into it, creating duplicates. The previous guard compared raw
strings and missed nested assets whenever the two sides disagreed on
surface form — /sdcard vs /storage/emulated/0 (symlink), trailing or
doubled slashes.

Canonicalize both paths before comparing (resolve the /sdcard and
/storage/self/primary aliases, collapse slashes), decode content:// SAF
tree URIs to their real path, and skip any asset whose source lives inside
the target. A second check runs against the actual candidate URI right
before the copy, since expo may fall back from localUri to asset.uri.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds on the manual backup with four opt-in capabilities, all surfaced
on the Photo Backup settings screen:

  - Periodic background backup: a TaskManager task registered through
    expo-background-task (WorkManager on Android, BGTaskScheduler on iOS)
    runs the same backup on an Hourly / 6-hour / Daily cadence. The task
    ensures the Go daemon is up in the headless JS runtime and refreshes
    the folder path before running. App launch reconciles the OS
    registration with the saved config so an enabled job survives
    reinstalls / cleared registrations.
  - Move instead of copy: after an asset is verified present in the
    backup folder, delete the original from the gallery. One batched
    delete = at most one system consent dialog (silent with All Files
    Access, so it also works headless). Assets skipped by the overlap
    guard are never deleted.
  - Keep original filename (default on): when off, files are renamed to a
    creation-time stamp (2024-08-11_143022.HEIC) to avoid collisions.
  - by-album folder structure: group copies by the source album (the
    on-disk parent/bucket name), falling back to the folder root.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant