Skip to content

Commit c37071a

Browse files
committed
Updates rwgps colors too
1 parent 83e6de5 commit c37071a

6 files changed

Lines changed: 93 additions & 14 deletions

File tree

TODO.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ Tracked here for visibility. PRs and issues welcome.
55
## Overall capabilitites
66

77
next:
8-
- [ ] update rwgps logo assetts https://ridewithgps.com/api/v1/doc/reference/logos_and_assets
98
- [ ] for web app, require subscription to be active for providers to sync
10-
- [ ] user docs -> tracekit will not modify unless you click individual button, FAQ, how to setup
119
- [ ] discord?
1210
- [ ] file provider should have an upload button that uploads a new file. dont overwrite existing files! (check filename on clientside and server side). if its a zip, extrct safely
1311

@@ -21,8 +19,7 @@ later
2119
- [ ] outbound email sending for email verificaiton on login, and password reset
2220
- [ ] add new source data files to file data when found in other places
2321
- [ ] i think we need a websocket or something for realtime updates
24-
- [ ] download file from ridewithgps, strava if file is missing (like garmin)
25-
- [ ] add inbound webhooks from ridewithgps on trips
22+
- [ ] add inbound webhooks from ridewithgps on trips -> verify
2623

2724
## Providers & Sync
2825

app/static/style.css

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ select#timezone:focus {
791791
/* Per-provider synced colors */
792792
.provider-status.provider-strava.synced { background: #FC4C02; }
793793
.provider-status.provider-garmin.synced { background: #007CC3; }
794-
.provider-status.provider-ridewithgps.synced { background: #52AF4A; }
794+
.provider-status.provider-ridewithgps.synced { background: #F96400; }
795795
.provider-status.provider-spreadsheet.synced { background: #5a6b7c; }
796796
.provider-status.provider-file.synced { background: #7a7f84; }
797797

@@ -1087,15 +1087,15 @@ select#timezone:focus {
10871087
font-size: 1rem;
10881088
padding: 11px 22px;
10891089
border-radius: 4px;
1090-
background: #52AF4A;
1090+
background: #F96400;
10911091
color: #ffffff;
10921092
border: none;
10931093
cursor: pointer;
10941094
font-weight: 700;
10951095
letter-spacing: .015em;
10961096
transition: background .15s;
10971097
}
1098-
.ridewithgps-auth-btn:hover { background: #429b3b; }
1098+
.ridewithgps-auth-btn:hover { background: #D95500; }
10991099

11001100
/* Personal-credentials toggle — displayed subtly below the auth button */
11011101
.personal-creds-toggle-row {
@@ -1191,13 +1191,13 @@ select#timezone:focus {
11911191
color: #005f99 !important;
11921192
text-decoration: underline;
11931193
}
1194-
/* RideWithGPS brand green: #52AF4A */
1194+
/* RideWithGPS brand orange: #F96400 */
11951195
.attr-ridewithgps {
1196-
color: #52AF4A !important;
1196+
color: #F96400 !important;
11971197
font-weight: 600;
11981198
}
11991199
.attr-ridewithgps:hover {
1200-
color: #3d8836 !important;
1200+
color: #D95500 !important;
12011201
text-decoration: underline;
12021202
}
12031203

@@ -1225,10 +1225,10 @@ select#timezone:focus {
12251225
}
12261226
.provider-attr.attr-garmin:hover { background: rgba(0,124,195,.08); }
12271227
.provider-attr.attr-ridewithgps {
1228-
color: #52AF4A;
1229-
border: 1px solid rgba(82,175,74,.35);
1228+
color: #F96400;
1229+
border: 1px solid rgba(249,100,0,.35);
12301230
}
1231-
.provider-attr.attr-ridewithgps:hover { background: rgba(82,175,74,.08); }
1231+
.provider-attr.attr-ridewithgps:hover { background: rgba(249,100,0,.08); }
12321232

12331233
/* Attribution links inside synced (green) provider cards — use white */
12341234
.provider-status.synced .provider-attr {

app/templates/month.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ <h2 class="section-title">Pending Changes</h2>
8181
case 'Download Source File':
8282
return `Download source file from Garmin for activity ${c.activity_id} `
8383
+ `(<em>${c.new_value || ''}</em>) to file provider`;
84+
case 'Download Source File from RideWithGPS':
85+
return `Download source file from RideWithGPS for activity ${c.activity_id} `
86+
+ `(<em>${c.new_value || ''}</em>) to file provider`;
8487
default:
8588
return JSON.stringify(c);
8689
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies = [
1717
"dateparser==1.3.0",
1818
"pytz==2025.2",
1919
"requests>=2.31.0,<3.0.0",
20-
"pyrwgps==0.2.0",
20+
"pyrwgps==0.2.1",
2121
"python-dotenv==1.2.1",
2222
"stravalib==2.4",
2323
"flask>=3.0.0,<4.0.0",

tracekit/providers/ridewithgps/ridewithgps_provider.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,42 @@ def sync_single_activity(self, trip_id: str) -> RideWithGPSActivity | None:
314314
print(f"RideWithGPS webhook: created local trip {trip_id}")
315315
return local
316316

317+
def download_activity_file(self, rwgps_id: str, dest_dir: str) -> str:
318+
"""Download an activity source file from RideWithGPS.
319+
320+
Tries formats in preference order: TCX, GPX, KML. Saves the file
321+
under *dest_dir* with a stable name derived from *rwgps_id*.
322+
323+
Returns the full path of the saved file.
324+
325+
Raises:
326+
FileExistsError: if the destination file already exists (never overwrites).
327+
RuntimeError: if the activity cannot be downloaded in any format.
328+
"""
329+
import os
330+
331+
os.makedirs(dest_dir, exist_ok=True)
332+
333+
for fmt in ("tcx", "gpx", "kml"):
334+
try:
335+
content = self.client.download_trip_file(rwgps_id, fmt)
336+
if content:
337+
dest_path = os.path.join(dest_dir, f"ridewithgps_{rwgps_id}.{fmt}")
338+
if os.path.exists(dest_path):
339+
raise FileExistsError(f"File already exists: {dest_path}")
340+
with open(dest_path, "wb") as f:
341+
f.write(content)
342+
print(f"Downloaded {fmt.upper()} for RideWithGPS trip {rwgps_id}: {dest_path}")
343+
return dest_path
344+
except FileExistsError:
345+
raise
346+
except Exception as e:
347+
print(f"Could not download {fmt.upper()} for RideWithGPS trip {rwgps_id}: {e}")
348+
349+
raise RuntimeError(
350+
f"Could not download RideWithGPS trip {rwgps_id} in any supported format (tried TCX, GPX, KML)"
351+
)
352+
317353
def reset_activities(self, date_filter: str | None = None) -> int:
318354
"""Reset (delete) RideWithGPS activities from local database."""
319355
from tracekit.providers.ridewithgps.ridewithgps_activity import (

tracekit/sync.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class ChangeType(Enum):
3333
ADD_ACTIVITY = "Add Activity"
3434
LINK_ACTIVITY = "Link Activity"
3535
DOWNLOAD_FROM_GARMIN = "Download Source File"
36+
DOWNLOAD_FROM_RIDEWITHGPS = "Download Source File from RideWithGPS"
3637

3738

3839
class ActivityChange(NamedTuple):
@@ -74,6 +75,11 @@ def __str__(self) -> str:
7475
f"Download source file from Garmin for activity {self.activity_id} "
7576
f"('{self.new_value}') to file provider"
7677
)
78+
elif self.change_type == ChangeType.DOWNLOAD_FROM_RIDEWITHGPS:
79+
return (
80+
f"Download source file from RideWithGPS for activity {self.activity_id} "
81+
f"('{self.new_value}') to file provider"
82+
)
7783
return "Unknown change"
7884

7985
def to_dict(self) -> dict:
@@ -407,6 +413,22 @@ def compute_month_changes(
407413
)
408414
)
409415

416+
# Same for RideWithGPS.
417+
if "ridewithgps" in provider_priorities and "file" in provider_priorities:
418+
for _key, group in grouped.items():
419+
by_provider = {a["provider"]: a for a in group}
420+
if "ridewithgps" in by_provider and "file" not in by_provider:
421+
rwgps_act = by_provider["ridewithgps"]
422+
all_changes.append(
423+
ActivityChange(
424+
change_type=ChangeType.DOWNLOAD_FROM_RIDEWITHGPS,
425+
provider="file",
426+
activity_id=str(rwgps_act["id"]),
427+
new_value=rwgps_act["name"],
428+
source_provider="ridewithgps",
429+
)
430+
)
431+
410432
return dict(grouped), all_changes
411433

412434

@@ -693,6 +715,27 @@ def apply_change(change: ActivityChange, tracekit: Tracekit, grouped: dict | Non
693715
return True, f"Downloaded and ingested {result.get('file')}"
694716
return False, f"Download succeeded but ingestion failed: {result.get('reason', 'unknown')}"
695717

718+
elif change_type == ChangeType.DOWNLOAD_FROM_RIDEWITHGPS:
719+
rwgps_prov = tracekit.get_provider("ridewithgps")
720+
file_prov = tracekit.get_provider("file")
721+
if not rwgps_prov:
722+
return False, "RideWithGPS provider not available"
723+
if not file_prov:
724+
return False, "File provider not available"
725+
726+
rwgps_id = change.activity_id
727+
dest_dir = file_prov.data_folder
728+
729+
try:
730+
file_path = rwgps_prov.download_activity_file(rwgps_id, dest_dir)
731+
except FileExistsError as exc:
732+
return False, f"File already exists — will not overwrite: {exc}"
733+
734+
result = file_prov.process_single_file(file_path)
735+
if result.get("status") in ("ok", "skipped"):
736+
return True, f"Downloaded and ingested {result.get('file')}"
737+
return False, f"Download succeeded but ingestion failed: {result.get('reason', 'unknown')}"
738+
696739
else:
697740
return False, f"Unsupported change type: {change_type}"
698741

0 commit comments

Comments
 (0)