Strava sync: skip import when a FIT-file upload already covers the same start time
Before importing each Strava activity, build a set of existing timestamp prefixes (YYYY-MM-DDTHHMMSSZ) from the activities directory. If the incoming Strava activity matches an existing prefix, record its Strava ID as done and skip — preventing duplicate entries when a FIT file and a Strava sync both cover the same ride. Also reports skipped-existing count in the summary line.
This commit is contained in:
@@ -343,9 +343,24 @@ def sync(
|
|||||||
owner = index_data.get("owner", {})
|
owner = index_data.get("owner", {})
|
||||||
summaries: dict[str, dict] = {s["id"]: s for s in index_data.get("activities", [])}
|
summaries: dict[str, dict] = {s["id"]: s for s in index_data.get("activities", [])}
|
||||||
|
|
||||||
|
# ── build timestamp-prefix index of existing activities ──────────────────
|
||||||
|
# Maps "YYYY-MM-DDTHHMMSSZ" → first matching activity filename (stem).
|
||||||
|
# Used to detect when a FIT-file upload already covers a Strava activity.
|
||||||
|
acts_dir = output_dir / "activities"
|
||||||
|
existing_ts: set[str] = set()
|
||||||
|
if acts_dir.is_dir():
|
||||||
|
for p in acts_dir.iterdir():
|
||||||
|
if p.suffix == ".json" and not p.name.endswith(".timeseries.json"):
|
||||||
|
stem = p.stem
|
||||||
|
# ID format: YYYY-MM-DDTHHMMSSZ[-optional-slug]
|
||||||
|
z_pos = stem.find("Z")
|
||||||
|
if z_pos != -1:
|
||||||
|
existing_ts.add(stem[: z_pos + 1])
|
||||||
|
|
||||||
# ── import loop ───────────────────────────────────────────────────────────
|
# ── import loop ───────────────────────────────────────────────────────────
|
||||||
errors: list[tuple[str, str]] = []
|
errors: list[tuple[str, str]] = []
|
||||||
imported = 0
|
imported = 0
|
||||||
|
skipped_existing = 0
|
||||||
|
|
||||||
with Progress(
|
with Progress(
|
||||||
TextColumn("[progress.description]{task.description}"),
|
TextColumn("[progress.description]{task.description}"),
|
||||||
@@ -362,11 +377,20 @@ def sync(
|
|||||||
try:
|
try:
|
||||||
streams = client.get_streams(act["id"])
|
streams = client.get_streams(act["id"])
|
||||||
parsed = _strava_to_parsed(act, streams)
|
parsed = _strava_to_parsed(act, streams)
|
||||||
|
|
||||||
|
# Skip if any activity already exists for the same start time
|
||||||
|
ts_part = parsed.started_at.astimezone(timezone.utc).strftime("%Y-%m-%dT%H%M%SZ")
|
||||||
|
if ts_part in existing_ts:
|
||||||
|
imported_ids.add(strava_id)
|
||||||
|
skipped_existing += 1
|
||||||
|
continue
|
||||||
|
|
||||||
metrics = compute(parsed)
|
metrics = compute(parsed)
|
||||||
metrics = _patch_from_summary(metrics, act)
|
metrics = _patch_from_summary(metrics, act)
|
||||||
act_id = make_activity_id(parsed)
|
act_id = make_activity_id(parsed)
|
||||||
write_activity(parsed, metrics, output_dir, privacy="public")
|
write_activity(parsed, metrics, output_dir, privacy="public")
|
||||||
summaries[act_id] = build_summary(parsed, metrics, act_id, "public")
|
summaries[act_id] = build_summary(parsed, metrics, act_id, "public")
|
||||||
|
existing_ts.add(ts_part)
|
||||||
imported_ids.add(strava_id)
|
imported_ids.add(strava_id)
|
||||||
imported += 1
|
imported += 1
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -384,9 +408,11 @@ def sync(
|
|||||||
from bincio.render.merge import merge_all
|
from bincio.render.merge import merge_all
|
||||||
merge_all(output_dir)
|
merge_all(output_dir)
|
||||||
|
|
||||||
|
skipped_msg = f", skipped [bold]{skipped_existing}[/bold] already covered by local uploads" if skipped_existing else ""
|
||||||
console.print(
|
console.print(
|
||||||
f"\n[green]Done.[/green] "
|
f"\n[green]Done.[/green] "
|
||||||
f"Imported [bold]{imported}[/bold] activities, "
|
f"Imported [bold]{imported}[/bold] activities"
|
||||||
|
f"{skipped_msg}, "
|
||||||
f"errors [bold]{len(errors)}[/bold]."
|
f"errors [bold]{len(errors)}[/bold]."
|
||||||
)
|
)
|
||||||
if errors:
|
if errors:
|
||||||
|
|||||||
Reference in New Issue
Block a user