diff --git a/.travis.yml b/.travis.yml index 8ba8563c1..c7302acec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python python: - - "3.3" + - "3.6" services: - mongodb - redis-server diff --git a/tapiriik/services/GarminConnect/garminconnect.py b/tapiriik/services/GarminConnect/garminconnect.py index 45a07548d..024db6a8d 100644 --- a/tapiriik/services/GarminConnect/garminconnect.py +++ b/tapiriik/services/GarminConnect/garminconnect.py @@ -447,86 +447,15 @@ def DownloadActivity(self, serviceRecord, activity): # Nothing else to download return activity - # https://connect.garmin.com/modern/proxy/activity-service/activity/###/details + # https://connect.garmin.com/modern/proxy/download-service/export/tcx/activity/### activityID = activity.ServiceData["ActivityID"] - res = self._request_with_reauth(lambda session: session.get("https://connect.garmin.com/modern/proxy/activity-service/activity/{}/details?maxSize=999999999".format(activityID)), serviceRecord) + res = self._request_with_reauth(lambda session: session.get("https://connect.garmin.com/modern/proxy/download-service/export/tcx/activity/{}".format(activityID)), serviceRecord) try: - raw_data = res.json() + tcx_data = res.text + activity = TCXIO.Parse(tcx_data.encode('utf-8'), activity) except ValueError: raise APIException("Activity data parse error for %s: %s" % (res.status_code, res.text)) - if "metricDescriptors" not in raw_data: - activity.Stationary = True # We were wrong, oh well - return activity - - attrs_map = {} - def _map_attr(gc_key, wp_key, units, in_location=False, is_timestamp=False): - attrs_map[gc_key] = { - "key": wp_key, - "to_units": units, - "in_location": in_location, # Blegh - "is_timestamp": is_timestamp # See above - } - - _map_attr("directSpeed", "Speed", ActivityStatisticUnit.MetersPerSecond) - _map_attr("sumDistance", "Distance", ActivityStatisticUnit.Meters) - _map_attr("directHeartRate", "HR", ActivityStatisticUnit.BeatsPerMinute) - _map_attr("directBikeCadence", "Cadence", ActivityStatisticUnit.RevolutionsPerMinute) - _map_attr("directDoubleCadence", "RunCadence", ActivityStatisticUnit.StepsPerMinute) # 2*x mystery solved - _map_attr("directAirTemperature", "Temp", ActivityStatisticUnit.DegreesCelcius) - _map_attr("directPower", "Power", ActivityStatisticUnit.Watts) - _map_attr("directElevation", "Altitude", ActivityStatisticUnit.Meters, in_location=True) - _map_attr("directLatitude", "Latitude", None, in_location=True) - _map_attr("directLongitude", "Longitude", None, in_location=True) - _map_attr("directTimestamp", "Timestamp", None, is_timestamp=True) - - # Figure out which metrics we'll be seeing in this activity - attrs_indexed = {} - for measurement in raw_data["metricDescriptors"]: - key = measurement["key"] - if key in attrs_map: - if attrs_map[key]["to_units"]: - attrs_map[key]["from_units"] = self._unitMap[measurement["unit"]["key"]] - if attrs_map[key]["to_units"] == attrs_map[key]["from_units"]: - attrs_map[key]["to_units"] = attrs_map[key]["from_units"] = None - attrs_indexed[measurement["metricsIndex"]] = attrs_map[key] - - # Process the data frames - frame_idx = 0 - active_lap_idx = 0 - for frame in raw_data["activityDetailMetrics"]: - wp = Waypoint() - for idx, attr in attrs_indexed.items(): - value = frame["metrics"][idx] - target_obj = wp - if attr["in_location"]: - if not wp.Location: - wp.Location = Location() - target_obj = wp.Location - - # Handle units - if attr["is_timestamp"]: - value = pytz.utc.localize(datetime.utcfromtimestamp(value / 1000)) - elif attr["to_units"]: - value = ActivityStatistic.convertValue(value, attr["from_units"], attr["to_units"]) - - # Write the value (can't use __dict__ because __slots__) - setattr(target_obj, attr["key"], value) - - # Fix up lat/lng being zero (which appear to represent missing coords) - if wp.Location and wp.Location.Latitude == 0 and wp.Location.Longitude == 0: - wp.Location.Latitude = None - wp.Location.Longitude = None - # Please visit a physician before complaining about this - if wp.HR == 0: - wp.HR = None - # Bump the active lap if required - while (active_lap_idx < len(activity.Laps) - 1 and # Not the last lap - activity.Laps[active_lap_idx + 1].StartTime <= wp.Timestamp): - active_lap_idx += 1 - activity.Laps[active_lap_idx].Waypoints.append(wp) - frame_idx += 1 - return activity def UploadActivity(self, serviceRecord, activity): diff --git a/tapiriik/services/tcx.py b/tapiriik/services/tcx.py index b2b6f5b98..5f899f619 100644 --- a/tapiriik/services/tcx.py +++ b/tapiriik/services/tcx.py @@ -229,11 +229,19 @@ def Parse(tcxData, act=None): sum_stats.update(act.Stats) act.Stats = sum_stats + try: + act.PrerenderedFormats["tcx"] = tcxData.decode('utf-8') + except: + act.PrerenderedFormats["tcx"] = tcxData + act.CalculateUID() return act def Dump(activity, activityType=None): + if "tcx" in activity.PrerenderedFormats: + return activity.PrerenderedFormats["tcx"] + root = etree.Element("TrainingCenterDatabase", nsmap=TCXIO.Namespaces) activities = etree.SubElement(root, "Activities") act = etree.SubElement(activities, "Activity") @@ -385,5 +393,4 @@ def _writeStat(parent, elName, value, wrapValue=False, naturalValue=False, defau etree.SubElement(xver, "BuildMajor").text = "0" etree.SubElement(xver, "BuildMinor").text = "0" - return etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="UTF-8").decode("UTF-8")