Skip to content

Keep Frame.io's signed media links out of the disk cache - #47

Merged
TeoVader merged 4 commits into
mainfrom
fix/dont-persist-media-links
Nov 11, 2026
Merged

TeoVader merged 4 commits into
mainfrom
fix/dont-persist-media-links

Conversation

@TeoVader

Copy link
Copy Markdown
Owner

What changes

Before: the API cache wrote file, folder, version and comment responses to disk with Frame.io's signed media_links inside. Folder previews and project covers also stored bare signed thumbnail links. Frame.io asks clients not to: "Do not store or cache these URLs. Always request a fresh set of media_links each time" (developer.frame.io/platform/v4/docs/guides/media-links.md and the FAQ).

After: signed media links live in memory only. What a user sees stays the same:

How

  • Disk copy without links (DiskScrub, used by JsonCache.put):
    • each link in a media_links object loses its url, download_url and inline_url, and records which of them it had in stripped;
    • every signed_url field is dropped;
    • memory keeps the full value, so a session is unchanged.
  • Stable image keys (ImageRef(key, url)):
    • the key is frameio:<asset id>:<rendition> (for stacks, the head version's id), separate from the temporary link;
    • Asset.thumbnail and Asset.thumbnailCandidates return ImageRefs, and FolderPreview.thumbnails is a list of them;
    • RemoteImage(ImageRef) / RemoteImageChain(List<ImageRef>) with no link make a cache-only Coil request (networkCachePolicy(DISABLED), on an .invalid host, so there is never a network call). When a link arrives, the request restarts.
  • Knowing what exists without the link: MediaLink.hasStream / hasDownload read stripped. So a copy read from disk still counts as pictured, still offers Download, and doesn't show "downloads disabled". The actions themselves already fetch the file fresh.
  • Viewer:
    • when the refresh fails (offline) and the cached copy has no links, it keeps polling for the file instead of calling the media withheld (Show a clear card when Frame.io withholds a file's media #35), and plays once links come;
    • the EXIF and zip readers are keyed on the link, so they restart when it arrives.
  • Old cache entries: JsonCache.scrubLegacyFiles() runs once in the background at start-up (a marker file next to the cache folder records it). It rewrites each entry through the scrub, keeping its age, and deletes old previews whose thumbnails were bare links.
  • Image cache keys change for thumbnails (path → asset + rendition), so each thumbnail downloads once more after the update. Images seen only before the update, and not since, don't show offline until they have been seen online once.

Not changed:

Checks

  • gradlew.bat :app:testDebugUnitTest :app:verifyRoborazziDebug :app:lintDebug on the workstation: 240 tests, only the 2 known Windows-only screenshot differences (notifications, tabletNotifications); lint "No issues found". New tests:
    • DiskScrubTest (6): what is stripped and what stays (scrub-sheet metadata, empty links), idempotence, a scrubbed asset still knowing its thumbnail key and download, ImageRef scrubbing, the cache keeping links in memory but not on disk, the one-time legacy migration;
    • ProjectCoverTest (+4): no signed link on disk, covers after a restart (online: fresh links under the same keys; offline: keys only), a file after a restart offline (thumbnail key, download still known);
    • ModelsTest: the stable keys.
  • Existing screenshots unchanged (project_covers passes with ImageRefs)
  • Tried on a phone: not done. Worth checking before release: browse online, force-stop, airplane mode, reopen, and check covers and thumbnails show; then the viewer offline and back online.

🤖 Generated with Claude Code

TeoVader and others added 4 commits November 11, 2026 17:09
Frame.io's docs ask clients not to store or cache media_links. The JSON
cache wrote file, folder and version responses to disk with their
signed links, and folder previews and project covers stored bare
thumbnail links.

The disk copy now goes through DiskScrub: every media link loses its
url, download_url and inline_url and records which it had ("stripped"),
and every ImageRef drops its signed_url. Memory keeps the full values,
so a session works as before. Images get a stable cache key (asset id +
rendition, ImageRef) separate from the link: RemoteImage and
RemoteImageChain show the cached image by that key when no link is at
hand, without a network call, so covers and thumbnails still show
offline after a restart. Previews and covers read back without links
fetch fresh ones when online, as expired ones already did.

Copies read back from disk still know a picture or a download exists
(MediaLink.hasStream / hasDownload), so the download actions and the
"downloads disabled" notice don't change. The viewer waits for fresh
links instead of calling such a copy's media withheld, and the EXIF and
zip readers restart once the links arrive. Entries written by earlier
versions are scrubbed once in the background; old previews are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File.renameTo doesn't replace an existing file on Windows, where the unit
tests also run; Files.move with REPLACE_EXISTING does, atomically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The role-aware menus (#42) check original.downloadUrl, which this branch strips from
disk copies; use hasDownload so a file read from the cache keeps its download actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TeoVader
TeoVader merged commit 4593e06 into main Nov 11, 2026
2 checks passed
@TeoVader
TeoVader deleted the fix/dont-persist-media-links branch November 11, 2026 17:26
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