Repository navigation
Task/10283 load tracks and videos from s3 by selecting a time range - #841
Merged
randy-seng merged 23 commits intoSep 29, 2026
Merged
randy-seng merged 23 commits into
randy-seng merged 23 commits into
Conversation
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
3 times, most recently
from
September 10, 2026 12:18
ea11596 to
1f65bd2
Compare
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
from
September 14, 2026 14:02
c8e3561 to
41bb10b
Compare
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
2 times, most recently
from
September 14, 2026 22:31
1784aa6 to
41bb10b
Compare
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
from
September 16, 2026 15:46
41bb10b to
805be28
Compare
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
2 times, most recently
from
September 21, 2026 12:46
ef4479e to
fed67a9
Compare
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
from
September 24, 2026 09:44
fed67a9 to
58c1279
Compare
briemla
approved these changes
Sep 28, 2026
Base automatically changed from
task/10282-keep-the-web-ui-responsive-while-loading-track-files
to
main
September 29, 2026 09:08
S3Connection was folded into S3Store on OP#10256, so the composition root and the MinIO integration test now build a store from the config instead of passing a connection and a config separately. The nicegui application test also has to set the S3 endpoint URL, which became a required environment variable on OP#10256 after these tests were written.
The message goes straight to the user, and it claimed the file did not say which video it belonged to whichever way reading it had failed. A file that never arrived was reported as one whose metadata was lacking, which sends whoever reads it looking inside a file that is not there. That is how the recent CI failure came to be read as a format problem rather than a missing one. Missing, unopenable, unparseable and lacking video metadata now each say so. The parser's own complaint is left out of the unparseable case: ijson quotes the offending line and points a caret into it, which is a diagram nobody can read in a popup, and the traceback still carries it to the log. Also ignore the directory S3_USER_SOURCE stages downloads in, so a stray `git add -A` cannot commit a bucket's worth of tracks and videos.
MinIO stopped publishing free images to Docker Hub and later removed the minio/minio repository entirely, breaking the acceptance and integration tests that spin one up via testcontainers. testcontainers-python has no dedicated RustFS module, so RustFsContainer wraps the official rustfs/rustfs image as a generic container the same way testcontainers.minio.MinioContainer wraps MinIO's; the minio Python client still works unchanged since RustFS speaks the same S3 API. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKAcf8GCJDLgziwYz3uyC2
S3-compatible services such as rustfs report the same object several times in one listing. The repeats were downloaded and loaded again, and publishing a video's metadata twice is rejected.
watch_for_cancel previously only polled while asyncio.gather awaited the downloads, so a cancel could go unnoticed until a slot freed between downloads. Running the downloads and the watcher in the same TaskGroup lets the watcher's cancellation reach downloads still in flight. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…equirement comments
…equirement comments
…Requirement comment
…Requirement comment
randy-seng
force-pushed
the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
2 times, most recently
from
September 29, 2026 09:08
37d6b65 to
d5775a6
Compare
randy-seng
deleted the
task/10283-load-tracks-and-videos-from-s3-by-selecting-a-time-range
branch
September 29, 2026 09:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
In S3 mode, loading tracks and videos used to have no user-facing entry point — this branch gives it one. The user picks a UTC start/end time range in a new
LoadWindowDialog,S3ListObjectslists the bucket under the configured prefix (de-duplicating repeated keys — RustFS, unlike AWS S3, can report the same objectmore than once),
select_in_windowfilters to objects whose filename timestamp falls in the range, andDownloadObjectsfetches the selected keys concurrently into a local staging directory (user_source) before the existing local-file loading path takes over. For tracks,S3TrackFileProvideradditionally reads eachdownloaded
.ottrk's own metadata to resolve and fetch its companion video — the video is not inferred from the track's filename, since a companion may be any supported video type.The load is all-or-nothing: a missing video, an unreadable track file, or a user cancel abandons the whole batch rather than leaving a partial load in the repositories. Cancellation is handled by racing a
watch_for_canceltask against the download tasks inside oneasyncio.TaskGroup, so a cancel reaches downloads already in flight instead of only ones that haven't started yet (previously, checking only betweenasyncio.gatherslots meant a cancel on a multi-gigabyte transfer could go unnoticed for a long time).Staged downloads are wiped on startup, on shutdown (best-effort), and on project reset — they're explicitly not a cache, since nothing reuses them across runs and the
Videoobjects pointing at them don't survive a restart either.Why
RunningProgressbarwas renamed toCompletionProgressand gainedcomplete(item)andis_cancelledbecause the existing progressbar interface assumed work advances by iterating a sequence in order. Concurrent downloads complete out of order, so the abstraction had to change to "report each item as it finishes and poll for cancellation" instead.AskForLoadWindowis a new port in the application layer rather than a method onUiFactory— adding an abstract method there would make OTCloud'sUnimplementedUiFactoryabstract and break it on instantiation. It also had to live outsideplugin_s3so the front-end implementing it doesn't need to depend on the plugin that consumes it.LoadWindowis deliberately a separate type fromDateRange(which filters tracks already loaded) — this window decides what gets fetched in the first place, and is clamped rather than rejected when a selection exceeds the configured maximum duration (continuous recording produces far more data than can be held at once).Matching is strict start-in-range: a chunk that started before the window but runs into it is not loaded. This keeps selection predictable at the cost of a leading partial chunk, and is called out explicitly in
select_in_window's docstring since it's an easy thing to assume works differently.MinIO was replaced with RustFS in test fixtures in an earlier commit on this branch (
58c1279a) because MinIO stopped publishing free Docker Hub images and later removed the repository entirely, breaking the testcontainers-based acceptance/integration suite.Notes for the reviewer
Start with
OTAnalytics/plugin_s3/download_objects.py— theTaskGroup/cancellation logic is the trickiest part of this change and is exercised directly bytests/unit/OTAnalytics/plugin_s3/test_download_objects.py(test_cancelling_stops_the_downloads_still_in_flight,test_cancelling_mid_download_aborts_it_promptly).OTAnalytics/plugin_s3/s3_file_providers.pyis the other file worth close attention: it's where track/video resolution, error translation (MissingVideoForTrackFilevsUnreadableTrackFilevs a generic bucket/network/filesystem failure), and user-facing messaging all meet. The rest —filename_timestamp.py,select_objects.py,cleanup.py, thenicegui_application.pywiring — is comparatively mechanical.domain/progress.py's rename (RunningProgressbar→CompletionProgress) touchesload_track_files.pyandnicegui_gui/nicegui/progressbar.pyas call sites; nothing behavioral changed there beyond the newcomplete/is_cancelledmembers.Risk and rollout
Only active when
DATA_TRANSFER_MODE=s3(transfer_mode_from_env()); local mode is untouched. The download concurrency is configurable (S3Config.download_concurrency) — worth confirming the default is sane for the target deployment's bucket and network.select_in_window's strict start-in-range matching means a recording chunk started just before a window boundary is silently excluded, not partially loaded — expected behavior per the docstring, but worth flagging to whoever signs off on acceptance criteria.Adds
minioandtestcontainersas dev dependencies (for spinning up RustFS in tests) and expandspytesttestpathsto includetests/integration.Testing
Acceptance (
tests/acceptance/test_load_from_s3.py) and integration (tests/integration/OTAnalytics/plugin_s3/test_load_from_rustfs.py) tests spin up a real RustFS container via testcontainers and exercise the dialog end-to-end againsta live S3 API.
OP#10283