def DownloadActivityList(self, serviceRecord, exhaustive=False):
cookies = self._get_cookies(record=serviceRecord)
activities = []
exclusions = []
pageUri = self.OpenFitEndpoint + "/fitnessActivities.json"
while True:
logger.debug("Req against " + pageUri)
res = requests.get(pageUri, cookies=cookies)
res = res.json()
for act in res["items"]:
activity = UploadedActivity()
activity.ServiceData = {"ActivityURI": act["uri"]}
if len(act["name"].strip()):
activity.Name = act["name"]
activity.StartTime = dateutil.parser.parse(act["start_time"])
if isinstance(activity.StartTime.tzinfo, tzutc):
activity.TZ = pytz.utc # The dateutil tzutc doesn't have an _offset value.
else:
activity.TZ = pytz.FixedOffset(activity.StartTime.tzinfo._offset.total_seconds() / 60) # Convert the dateutil lame timezones into pytz awesome timezones.
activity.StartTime = activity.StartTime.replace(tzinfo=activity.TZ)
activity.EndTime = activity.StartTime + timedelta(seconds=float(act["duration"]))
activity.Stats.MovingTime = ActivityStatistic(ActivityStatisticUnit.Time, value=timedelta(seconds=float(act["duration"]))) # OpenFit says this excludes paused times.
# Sometimes activities get returned with a UTC timezone even when they are clearly not in UTC.
if activity.TZ == pytz.utc:
# So, we get the first location in the activity and calculate the TZ from that.
try:
firstLocation = self._downloadActivity(serviceRecord, activity, returnFirstLocation=True)
except APIExcludeActivity:
pass
else:
activity.CalculateTZ(firstLocation)
activity.AdjustTZ()
logger.debug("Activity s/t " + str(activity.StartTime))
activity.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=float(act["total_distance"]))
types = [x.strip().lower() for x in act["type"].split(":")]
types.reverse() # The incoming format is like "walking: hiking" and we want the most specific first
activity.Type = None
for type_key in types:
if type_key in self._activityMappings:
activity.Type = self._activityMappings[type_key]
break
if not activity.Type:
exclusions.append(APIExcludeActivity("Unknown activity type %s" % act["type"], activityId=act["uri"]))
continue
activity.CalculateUID()
activities.append(activity)
if not exhaustive or "next" not in res or not len(res["next"]):
break
else:
pageUri = res["next"]
return activities, exclusions
def _populateActivity(self, rawRecord):
''' Populate the 1st level of the activity object with all details required for UID from API data '''
activity = UploadedActivity()
activity.StartTime = dateutil.parser.parse(rawRecord["start"])
activity.EndTime = activity.StartTime + timedelta(seconds=rawRecord["duration"])
activity.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=rawRecord["distance"])
activity.GPS = rawRecord["hasGps"]
activity.Stationary = not rawRecord["hasGps"]
activity.CalculateUID()
return activity
def DownloadActivityList(self, serviceRecord, exhaustive=False):
logger.debug("Checking motivato premium state")
self._applyPaymentState(serviceRecord)
logger.debug("Motivato DownloadActivityList")
session = self._get_session(record=serviceRecord)
activities = []
exclusions = []
self._rate_limit()
retried_auth = False
#headers = {'X-App-With-Tracks': "true"}
headers = {}
res = session.post(self._urlRoot + "/api/workouts/sync", headers=headers)
if res.status_code == 403 and not retried_auth:
retried_auth = True
session = self._get_session(serviceRecord, skip_cache=True)
try:
respList = res.json();
except ValueError:
res_txt = res.text # So it can capture in the log message
raise APIException("Parse failure in Motivato list resp: %s" % res.status_code)
for actInfo in respList:
if "duration" in actInfo:
duration = self._durationToSeconds(actInfo["duration"])
else:
continue
activity = UploadedActivity()
if "time_start" in actInfo["metas"]:
startTimeStr = actInfo["training_at"] + " " + actInfo["metas"]["time_start"]
else:
startTimeStr = actInfo["training_at"] + " 00:00:00"
activity.StartTime = self._parseDateTime(startTimeStr)
activity.EndTime = self._parseDateTime(startTimeStr) + timedelta(seconds=duration)
activity.Type = self._reverseActivityMappings[actInfo["discipline_id"]]
activity.Stats.TimerTime = ActivityStatistic(ActivityStatisticUnit.Seconds, value=duration)
if "distance" in actInfo:
activity.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Kilometers, value=float(actInfo["distance"]))
#activity.Stats.Speed = ActivityStatistic(ActivityStatisticUnit.KilometersPerSecond, value=1.0/float(actInfo["metas"]["pace"]))
activity.ServiceData={"WorkoutID": int(actInfo["id"])}
activity.CalculateUID()
logger.debug("Generated UID %s" % activity.UID)
activities.append(activity)
return activities, exclusions
开发者ID:nall,项目名称:tapiriik,代码行数:54,代码来源:motivato.py
示例5: _populateActivity
def _populateActivity(self, rawRecord):
''' Populate the 1st level of the activity object with all details required for UID from RK API data '''
activity = UploadedActivity()
# can stay local + naive here, recipient services can calculate TZ as required
activity.StartTime = datetime.strptime(rawRecord["start_time"], "%a, %d %b %Y %H:%M:%S")
activity.EndTime = activity.StartTime + timedelta(0, round(rawRecord["duration"])) # this is inaccurate with pauses - excluded from hash
activity.Distance = rawRecord["total_distance"]
if rawRecord["type"] in self._activityMappings:
activity.Type = self._activityMappings[rawRecord["type"]]
activity.CalculateUID()
return activity
def DownloadActivityList(self, svcRecord, exhaustive=False):
activities = []
exclusions = []
before = earliestDate = None
while True:
logger.debug("Req with before=" + str(before) + "/" + str(earliestDate))
resp = requests.get("https://www.strava.com/api/v3/athletes/" + str(svcRecord.ExternalID) + "/activities", headers=self._apiHeaders(svcRecord), params={"before": before})
self._logAPICall("list", (svcRecord.ExternalID, str(earliestDate)), resp.status_code == 401)
if resp.status_code == 401:
raise APIException("No authorization to retrieve activity list", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
earliestDate = None
reqdata = resp.json()
if not len(reqdata):
break # No more activities to see
for ride in reqdata:
activity = UploadedActivity()
activity.TZ = pytz.timezone(re.sub("^\([^\)]+\)\s*", "", ride["timezone"])) # Comes back as "(GMT -13:37) The Stuff/We Want""
activity.StartTime = pytz.utc.localize(datetime.strptime(ride["start_date"], "%Y-%m-%dT%H:%M:%SZ"))
logger.debug("\tActivity s/t " + str(activity.StartTime))
if not earliestDate or activity.StartTime < earliestDate:
earliestDate = activity.StartTime
before = calendar.timegm(activity.StartTime.astimezone(pytz.utc).timetuple())
if ride["start_latlng"] is None or ride["end_latlng"] is None or ride["distance"] is None or ride["distance"] == 0:
exclusions.append(APIExcludeActivity("No path", activityId=ride["id"]))
logger.debug("\t\tNo pts")
continue # stationary activity - no syncing for now
activity.EndTime = activity.StartTime + timedelta(0, ride["elapsed_time"])
activity.UploadedTo = [{"Connection": svcRecord, "ActivityID": ride["id"]}]
actType = [k for k, v in self._reverseActivityTypeMappings.items() if v == ride["type"]]
if not len(actType):
exclusions.append(APIExcludeActivity("Unsupported activity type %s" % ride["type"], activityId=ride["id"]))
logger.debug("\t\tUnknown activity")
continue
activity.Type = actType[0]
activity.Distance = ride["distance"]
activity.Name = ride["name"]
activity.Private = ride["private"]
activity.AdjustTZ()
activity.CalculateUID()
activities.append(activity)
if not exhaustive or not earliestDate:
break
return activities, exclusions
开发者ID:hozn,项目名称:tapiriik,代码行数:55,代码来源:strava.py
示例7: DownloadActivityList
def DownloadActivityList(self, serviceRecord, exhaustive=False):
activities = []
session = self._get_session(record=serviceRecord)
session.headers.update({"Accept": "application/json"})
workouts_resp = session.get("https://api.trainerroad.com/api/careerworkouts")
if workouts_resp.status_code != 200:
if workouts_resp.status_code == 401:
raise APIException("Invalid login", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
raise APIException("Workout listing error")
cached_record = cachedb.trainerroad_meta.find_one({"ExternalID": serviceRecord.ExternalID})
if not cached_record:
cached_workout_meta = {}
else:
cached_workout_meta = cached_record["Workouts"]
workouts = workouts_resp.json()
for workout in workouts:
# Un/f their API doesn't provide the start/end times in the list response
# So we need to pull the extra data, if it's not already cached
workout_id = str(workout["Id"]) # Mongo doesn't do non-string keys
if workout_id not in cached_workout_meta:
meta_resp = session.get("https://api.trainerroad.com/api/careerworkouts?guid=%s" % workout["Guid"])
# We don't need everything
full_meta = meta_resp.json()
meta = {key: full_meta[key] for key in ["WorkoutDate", "WorkoutName", "WorkoutNotes", "TotalMinutes", "TotalKM", "AvgWatts", "Kj"]}
cached_workout_meta[workout_id] = meta
else:
meta = cached_workout_meta[workout_id]
activity = UploadedActivity()
activity.ServiceData = {"ID": int(workout_id)}
activity.Name = meta["WorkoutName"]
activity.Notes = meta["WorkoutNotes"]
activity.Type = ActivityType.Cycling
# Everything's in UTC
activity.StartTime = dateutil.parser.parse(meta["WorkoutDate"]).replace(tzinfo=pytz.utc)
activity.EndTime = activity.StartTime + timedelta(minutes=meta["TotalMinutes"])
activity.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Kilometers, value=meta["TotalKM"])
activity.Stats.Power = ActivityStatistic(ActivityStatisticUnit.Watts, avg=meta["AvgWatts"])
activity.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilojoules, value=meta["Kj"])
activity.Stationary = False
activity.GPS = False
activity.CalculateUID()
activities.append(activity)
cachedb.trainerroad_meta.update({"ExternalID": serviceRecord.ExternalID}, {"ExternalID": serviceRecord.ExternalID, "Workouts": cached_workout_meta}, upsert=True)
return activities, []
def _populateActivity(self, rawRecord):
''' Populate the 1st level of the activity object with all details required for UID from RK API data '''
activity = UploadedActivity()
# can stay local + naive here, recipient services can calculate TZ as required
activity.StartTime = datetime.strptime(rawRecord["start_time"], "%a, %d %b %Y %H:%M:%S")
activity.Stats.MovingTime = ActivityStatistic(ActivityStatisticUnit.Time, value=timedelta(0, float(rawRecord["duration"]))) # P. sure this is moving time
activity.EndTime = activity.StartTime + activity.Stats.MovingTime.Value # this is inaccurate with pauses - excluded from hash
activity.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=rawRecord["total_distance"])
# I'm fairly sure this is how the RK calculation works. I remember I removed something exactly like this from ST.mobi, but I trust them more than I trust myself to get the speed right.
if (activity.EndTime - activity.StartTime).total_seconds() > 0:
activity.Stats.Speed = ActivityStatistic(ActivityStatisticUnit.KilometersPerHour, avg=activity.Stats.Distance.asUnits(ActivityStatisticUnit.Kilometers).Value / ((activity.EndTime - activity.StartTime).total_seconds() / 60 / 60))
activity.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilocalories, value=rawRecord["total_calories"] if "total_calories" in rawRecord else None)
if rawRecord["type"] in self._activityMappings:
activity.Type = self._activityMappings[rawRecord["type"]]
activity.CalculateUID()
return activity
def DownloadActivityList(self, svcRecord, exhaustive=False):
# grumble grumble strava api sucks grumble grumble
# http://app.strava.com/api/v1/rides?athleteId=id
activities = []
exclusions = []
before = earliestDate = None
while True:
resp = requests.get("https://www.strava.com/api/v3/athletes/" + str(svcRecord.ExternalID) + "/activities", headers=self._apiHeaders(svcRecord), params={"before": before})
logger.debug("Req with before=" + str(before) + "/" + str(earliestDate))
earliestDate = None
reqdata = resp.json()
if not len(reqdata):
break # No more activities to see
for ride in reqdata:
activity = UploadedActivity()
activity.TZ = pytz.timezone(re.sub("^\([^\)]+\)\s*", "", ride["timezone"])) # Comes back as "(GMT -13:37) The Stuff/We Want""
activity.StartTime = pytz.utc.localize(datetime.strptime(ride["start_date"], "%Y-%m-%dT%H:%M:%SZ"))
logger.debug("\tActivity s/t " + str(activity.StartTime))
if not earliestDate or activity.StartTime < earliestDate:
earliestDate = activity.StartTime
before = calendar.timegm(activity.StartTime.astimezone(pytz.utc).timetuple())
if ride["start_latlng"] is None or ride["end_latlng"] is None or ride["distance"] is None or ride["distance"] == 0:
exclusions.append(APIExcludeActivity("No path", activityId=ride["id"]))
continue # stationary activity - no syncing for now
if ride["start_latlng"] == ride["end_latlng"]:
exclusions.append(APIExcludeActivity("Only one waypoint", activityId=ride["id"]))
continue # Only one waypoint, one would assume.
activity.EndTime = activity.StartTime + timedelta(0, ride["elapsed_time"])
activity.UploadedTo = [{"Connection": svcRecord, "ActivityID": ride["id"]}]
actType = [k for k, v in self._reverseActivityTypeMappings.items() if v == ride["type"]]
if not len(actType):
exclusions.append(APIExcludeActivity("Unsupported activity type", activityId=ride["id"]))
continue
activity.Type = actType[0]
activity.Distance = ride["distance"]
activity.Name = ride["name"]
activity.AdjustTZ()
activity.CalculateUID()
activities.append(activity)
if not exhaustive or not earliestDate:
break
return activities, exclusions
def DownloadActivityList(self, svcRec, exhaustive=False):
dbcl = self._getClient(svcRec)
if not svcRec.Authorization["Full"]:
syncRoot = "/"
else:
syncRoot = svcRec.Config["SyncRoot"]
cache = cachedb.dropbox_cache.find_one({"ExternalID": svcRec.ExternalID})
if cache is None:
cache = {"ExternalID": svcRec.ExternalID, "Structure": [], "Activities": {}}
if "Structure" not in cache:
cache["Structure"] = []
self._folderRecurse(cache["Structure"], dbcl, syncRoot)
activities = []
exclusions = []
for dir in cache["Structure"]:
for file in dir["Files"]:
path = file["Path"]
if svcRec.Authorization["Full"]:
relPath = path.replace(syncRoot, "", 1)
else:
relPath = path.replace("/Apps/tapiriik/", "", 1) # dropbox api is meh api
existing = [(k, x) for k, x in cache["Activities"].items() if x["Path"] == relPath] # path is relative to syncroot to reduce churn if they relocate it
existing = existing[0] if existing else None
if existing is not None:
existUID, existing = existing
if existing and existing["Rev"] == file["Rev"]:
# don't need entire activity loaded here, just UID
act = UploadedActivity()
act.UID = existUID
act.StartTime = datetime.strptime(existing["StartTime"], "%H:%M:%S %d %m %Y %z")
if "EndTime" in existing: # some cached activities may not have this, it is not essential
act.EndTime = datetime.strptime(existing["EndTime"], "%H:%M:%S %d %m %Y %z")
else:
logger.debug("Retrieving %s (%s)" % (path, "outdated meta cache" if existing else "not in meta cache"))
# get the full activity
try:
act, rev = self._getActivity(svcRec, dbcl, path)
except APIExcludeActivity as e:
logger.info("Encountered APIExcludeActivity %s" % str(e))
exclusions.append(e)
continue
del act.Laps
act.Laps = [] # Yeah, I'll process the activity twice, but at this point CPU time is more plentiful than RAM.
cache["Activities"][act.UID] = {"Rev": rev, "Path": relPath, "StartTime": act.StartTime.strftime("%H:%M:%S %d %m %Y %z"), "EndTime": act.EndTime.strftime("%H:%M:%S %d %m %Y %z")}
tagRes = self._tagActivity(relPath)
act.ServiceData = {"Path": path, "Tagged":tagRes is not None}
act.Type = tagRes if tagRes is not None else ActivityType.Other
logger.debug("Activity s/t %s" % act.StartTime)
activities.append(act)
cachedb.dropbox_cache.update({"ExternalID": svcRec.ExternalID}, cache, upsert=True)
return activities, exclusions
def DownloadActivityList(self, svcRec, exhaustive=False):
dbcl = self._getClient(svcRec)
if not svcRec.Authorization["Full"]:
syncRoot = "/"
else:
syncRoot = svcRec.Config["SyncRoot"]
cache = cachedb.dropbox_cache.find_one({"ExternalID": svcRec.ExternalID})
if cache is None:
cache = {"ExternalID": svcRec.ExternalID, "Structure": [], "Activities": {}}
if "Structure" not in cache:
cache["Structure"] = []
self._folderRecurse(cache["Structure"], dbcl, syncRoot)
activities = []
exclusions = []
for dir in cache["Structure"]:
for file in dir["Files"]:
path = file["Path"]
if svcRec.Authorization["Full"]:
relPath = path.replace(syncRoot, "", 1)
else:
relPath = path.replace("/Apps/tapiriik/", "", 1) # dropbox api is meh api
existing = [(k, x) for k, x in cache["Activities"].items() if x["Path"] == relPath] # path is relative to syncroot to reduce churn if they relocate it
existing = existing[0] if existing else None
if existing is not None:
existUID, existing = existing
if existing and existing["Rev"] == file["Rev"]:
# don't need entire activity loaded here, just UID
act = UploadedActivity()
act.UID = existUID
act.StartTime = datetime.strptime(existing["StartTime"], "%H:%M:%S %d %m %Y %z")
if "EndTime" in existing: # some cached activities may not have this, it is not essential
act.EndTime = datetime.strptime(existing["EndTime"], "%H:%M:%S %d %m %Y %z")
else:
# get the full activity
try:
act, rev = self._getActivity(dbcl, path)
except APIExcludeActivity as e:
exclusions.append(e)
continue
cache["Activities"][act.UID] = {"Rev": rev, "Path": relPath, "StartTime": act.StartTime.strftime("%H:%M:%S %d %m %Y %z"), "EndTime": act.EndTime.strftime("%H:%M:%S %d %m %Y %z")}
act.UploadedTo = [{"Connection": svcRec, "Path": path}]
tagRes = self._tagActivity(relPath)
act.Tagged = tagRes is not None
act.Type = tagRes if tagRes is not None else ActivityType.Other
activities.append(act)
cachedb.dropbox_cache.update({"ExternalID": svcRec.ExternalID}, cache, upsert=True)
return activities, exclusions
请发表评论