def UploadActivity(self, serviceRecord, activity): # https://ridewithgps.com/trips.json tcx_file = TCXIO.Dump(activity) files = { "data_file": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + ".tcx", tcx_file) } params = {} params['trip[name]'] = activity.Name params[ 'trip[visibility]'] = 1 if activity.Private else 0 # Yes, this logic seems backwards but it's how it works res = requests.post("https://ridewithgps.com/trips.json", files=files, params=self._add_auth_params(params, record=serviceRecord)) if res.status_code % 100 == 4: raise APIException("Invalid login", block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) res.raise_for_status() res = res.json() if res["success"] != 1: raise APIException("Unable to upload activity")
def UploadActivity(self, serviceRecord, activity): format = serviceRecord.GetConfiguration()["Format"] if format == "tcx": if "tcx" in activity.PrerenderedFormats: logger.debug("Using prerendered TCX") data = activity.PrerenderedFormats["tcx"] else: data = TCXIO.Dump(activity) else: if "gpx" in activity.PrerenderedFormats: logger.debug("Using prerendered GPX") data = activity.PrerenderedFormats["gpx"] else: data = GPXIO.Dump(activity) dbcl = self._getClient(serviceRecord) fname = self._format_file_name(serviceRecord.GetConfiguration()["Filename"], activity)[:250] + "." + format # DB has a max path component length of 255 chars, and we have to save for the file ext (4) and the leading slash (1) if not serviceRecord.Authorization["Full"]: fpath = "/" + fname else: fpath = serviceRecord.Config["SyncRoot"] + "/" + fname try: metadata = dbcl.files_upload(data.encode("UTF-8"), fpath, mode=dropbox.files.WriteMode.overwrite) except dropbox.exceptions.DropboxException as e: self._raiseDbException(e) # Fake this in so we don't immediately redownload the activity next time 'round cache = cachedb.dropbox_cache.find_one({"ExternalID": serviceRecord.ExternalID}) cache["Activities"][self._hash_path("/" + fname)] = {"Rev": metadata.rev, "UID": activity.UID, "StartTime": activity.StartTime.strftime("%H:%M:%S %d %m %Y %z"), "EndTime": activity.EndTime.strftime("%H:%M:%S %d %m %Y %z")} cachedb.dropbox_cache.update({"ExternalID": serviceRecord.ExternalID}, cache) # not upsert, hope the record exists at this time... return fpath
def UploadActivity(self, serviceRecord, activity): session = self._get_session(serviceRecord) tcx_data = None # If some service provides ready-to-use tcx data why not to use it? # TODO: please use this code when activity will have SourceFile property #if activity.SourceFile: # tcx_data = activity.SourceFile.getContent(ActivityFileType.TCX) # # Set aerobia-understandable sport name # tcx_data = re.sub(r'(<Sport=\")\w+(\">)', r'\1{}\2'.format(self._activityMappings[activity.Type]), tcx_data) if tcx_data else None if not tcx_data: # We pass an explicit activityType as the TCX schema doesn't define all the types this service supports. tcx_data = TCXIO.Dump( activity, activityType=self._activityMappings[activity.Type]) data = {"name": activity.Name, "description": activity.Notes} files = { "file": ("tap-sync-{}-{}.tcx".format(os.getpid(), activity.UID), tcx_data) } res = session.post(self._uploadsUrl, data=self._with_auth(serviceRecord, data), files=files) res_obj = res.json() if "error" in res_obj: raise APIException(res_obj["error"], user_exception=UserException( UserExceptionType.UploadError)) # return just uploaded activity id return res_obj["workouts"][0]["id"]
def UploadActivity(self, serviceRecord, activity): tcx_data = None # If some service provides ready-to-use tcx data why not to use it? if "tcx" in activity.PrerenderedFormats: tcx_data = activity.PrerenderedFormats["tcx"] # Set aerobia-understandable sport name tcx_data = re.sub(r'(Sport=\")[\w\s]+(\">)', r'\1{}\2'.format(self._activityMappings[activity.Type]), tcx_data) if tcx_data else None if not tcx_data: tcx_data = TCXIO.Dump(activity, self._activityMappings[activity.Type]) # Aerobia support activity names not longer than 60 characters activity_name = activity.Name[:60] if activity.Name else None data = {"name": activity_name, "description": activity.Notes} files = {"file": ("tap-sync-{}-{}.tcx".format(os.getpid(), activity.UID), tcx_data)} res = self._safe_call(serviceRecord, "post", self._uploadsUrl, data, files) res_obj = res.json() uploaded_id = res_obj["workouts"][0]["id"] if "error" in res_obj: raise APIException(res_obj["error"], user_exception=UserException(UserExceptionType.UploadError)) extra_data = {} if activity_name: extra_data.update({"workout[name]": activity_name}) self._put_default_inventory(activity, serviceRecord, extra_data) # Post extra data to newly uploaded activity if extra_data: self._patch_activity(serviceRecord, extra_data, uploaded_id) # return just uploaded activity id return uploaded_id
def test_constant_representation(self): ''' ensures that tcx import/export is symetric ''' script_dir = os.path.dirname(__file__) rel_path = "data/test1.tcx" source_file_path = os.path.join(script_dir, rel_path) with open(source_file_path, 'r') as testfile: data = testfile.read() act = TCXIO.Parse(data.encode('utf-8')) new_data = TCXIO.Dump(act) act2 = TCXIO.Parse(new_data.encode('utf-8')) rel_path = "data/output1.tcx" new_file_path = os.path.join(script_dir, rel_path) with open(new_file_path, "w") as new_file: new_file.write(new_data) self.assertActivitiesEqual(act2, act)
def test_garmin_tcx_export(self): ''' ensures that tcx exported from Garmin Connect can be correctly parsed ''' script_dir = os.path.dirname(__file__) rel_path = "data/garmin_parse_1.tcx" source_file_path = os.path.join(script_dir, rel_path) with open(source_file_path, 'r') as testfile: data = testfile.read() act = TCXIO.Parse(data.encode('utf-8')) act.PrerenderedFormats.clear() new_data = TCXIO.Dump(act) act2 = TCXIO.Parse(new_data.encode('utf-8')) rel_path = "data/output2.tcx" new_file_path = os.path.join(script_dir, rel_path) with open(new_file_path, "w") as new_file: new_file.write(new_data) self.assertActivitiesEqual(act2, act)
def UploadActivity(self, serviceRecord, activity): session = self._get_session(serviceRecord) tcx_data = None # If some service provides ready-to-use tcx data why not to use it? if activity.SourceFile: tcx_data = activity.SourceFile.getContent(ActivityFileType.TCX) # Set aerobia-understandable sport name tcx_data = re.sub( r'(<Sport=\")\w+(\">)', r'\1{}\2'.format( self._activityMappings[activity.Type]), tcx_data) if tcx_data else None if not tcx_data: tcx_data = TCXIO.Dump(activity, self._activityMappings[activity.Type]) data = {"name": activity.Name, "description": activity.Notes} files = { "file": ("tap-sync-{}-{}.tcx".format(os.getpid(), activity.UID), tcx_data) } res = session.post(self._uploadsUrl, data=self._with_auth(serviceRecord, data), files=files) res_obj = res.json() uploaded_id = res_obj["workouts"][0]["id"] if "error" in res_obj: raise APIException(res_obj["error"], user_exception=UserException( UserExceptionType.UploadError)) extra_data = {} if activity.Name is not None: extra_data.update({"workout[name]": activity.Name}) self._put_default_inventory(activity, serviceRecord, extra_data) # Post extra data to newly uploaded activity if extra_data: self._patch_activity(serviceRecord, extra_data, uploaded_id) # return just uploaded activity id return uploaded_id
def UploadActivity(self, serviceRecord, activity): # Upload the workout as a .TCX file uploaddata = TCXIO.Dump(activity) headers = self._apiHeaders(serviceRecord.Authorization) headers['Content-Type'] = 'application/xml' resp = requests.post(TRAINASONE_SERVER_URL + "/api/sync/activity/tcx", data=uploaddata, headers=headers) if resp.status_code != 200: raise APIException("Error uploading activity - " + str(resp.status_code), block=False) responseJson = resp.json() if not responseJson["id"]: raise APIException("Error uploading activity - " + resp.Message, block=False) activityId = responseJson["id"] return activityId
def UploadActivity(self, serviceRecord, activity): #/proxy/upload-service-1.1/json/upload/.tcx activity.EnsureTZ() tcx_file = TCXIO.Dump(activity) files = {"data": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + ".tcx", tcx_file)} cookies = self._get_cookies(record=serviceRecord) res = requests.post("http://connect.garmin.com/proxy/upload-service-1.1/json/upload/.tcx", files=files, cookies=cookies) res = res.json()["detailedImportResult"] if len(res["successes"]) != 1: raise APIException("Unable to upload activity") actid = res["successes"][0]["internalId"] if activity.Type not in [ActivityType.Running, ActivityType.Cycling, ActivityType.Other]: # Set the legit activity type - whatever it is, it's not supported by the TCX schema acttype = [k for k, v in self._reverseActivityMappings.items() if v == activity.Type] if len(acttype) == 0: raise APIWarning("GarminConnect does not support activity type " + activity.Type) else: acttype = acttype[0] res = requests.post("http://connect.garmin.com/proxy/activity-service-1.2/json/type/" + str(actid), data={"value": acttype}, cookies=cookies) res = res.json() if "activityType" not in res or res["activityType"]["key"] != acttype: raise APIWarning("Unable to set activity type")
def UploadActivity(self, serviceRecord, activity): """ POST a Multipart-Encoded File URL: https://app.velohero.com/upload/file Parameters: user = username pass = password view = json file = multipart-encodes file (fit, tcx, pwx, gpx, srm, hrm...) Maximum file size per file is 16 MB. """ has_location = has_distance = has_speed = False for lap in activity.Laps: for wp in lap.Waypoints: if wp.Location and wp.Location.Latitude and wp.Location.Longitude: has_location = True if wp.Distance: has_distance = True if wp.Speed: has_speed = True if has_location and has_distance and has_speed: format = "fit" data = FITIO.Dump(activity) elif has_location and has_distance: format = "tcx" data = TCXIO.Dump(activity) elif has_location: format = "gpx" data = GPXIO.Dump(activity) else: format = "fit" data = FITIO.Dump(activity) # Upload files = {"file": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + "." + format, data)} params = self._add_auth_params({"view":"json"}, record=serviceRecord) res = requests.post(self._urlRoot + "/upload/file", headers=self._obligatory_headers, files=files, params=params) if res.status_code != 200: if res.status_code == 403: raise APIException("Invalid login", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) raise APIException("Unable to upload activity") res.raise_for_status() try: res = res.json() except ValueError: raise APIException("Could not decode activity list") if "error" in res: raise APIException(res["error"]) # Set date, start time, comment and sport if "id" in res: workoutId = res["id"] params = self._add_auth_params({ "workout_date" : activity.StartTime.strftime("%Y-%m-%d"), "workout_start_time" : activity.StartTime.strftime("%H:%M:%S"), "workout_comment" : activity.Notes, "sport_id" : self._activityMappings[activity.Type], "workout_hide": "yes" if activity.Private else "no" }, record=serviceRecord) res = requests.get(self._urlRoot + "/workouts/change/{}".format(workoutId), headers=self._obligatory_headers, params=params) if res.status_code != 200: if res.status_code == 403: raise APIException("No authorization to change activity with workout ID: {}".format(workoutId), block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) raise APIException("Unable to change activity with workout ID: {}".format(workoutId)) return workoutId
def UploadActivity(self, serviceRecord, activity): logger.info("Activity tz " + str(activity.TZ) + " dt tz " + str(activity.StartTime.tzinfo) + " starttime " + str(activity.StartTime)) req = { "id": 0, "data_type": "tcx", "external_id": "tap-sync-" + str(os.getpid()) + "-" + activity.UID + "-" + activity.UploadedTo[0]["Connection"].Service.ID, "activity_name": activity.Name, "activity_type": self._activityTypeMappings[activity.Type], "private": activity.Private } if "tcx" in activity.PrerenderedFormats: logger.debug("Using prerendered TCX") tcxData = activity.PrerenderedFormats["tcx"] else: activity.EnsureTZ() tcxData = TCXIO.Dump(activity) # TODO: put the tcx back into PrerenderedFormats once there's more RAM to go around and there's a possibility of it actually being used. files = {"file": (req["external_id"] + ".tcx", tcxData)} response = requests.post("http://www.strava.com/api/v3/uploads", data=req, files=files, headers=self._apiHeaders(serviceRecord)) if response.status_code != 201: if response.status_code == 401: raise APIException("No authorization to upload activity " + activity.UID + " response " + response.text + " status " + str(response.status_code), block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) raise APIException("Unable to upload activity " + activity.UID + " response " + response.text + " status " + str(response.status_code)) upload_id = response.json()["id"] while not response.json()["activity_id"]: time.sleep(1) response = requests.get("http://www.strava.com/api/v3/uploads/%s" % upload_id, headers=self._apiHeaders(serviceRecord)) logger.debug( "Waiting for upload - status %s id %s" % (response.json()["status"], response.json()["activity_id"])) if response.json()["error"]: error = response.json()["error"] if "duplicate of activity" in error: logger.debug("Duplicate") return # I guess we're done here? raise APIException( "Strava failed while processing activity - last status %s" % response.text)
def UploadActivity(self, serviceRecord, activity): tcx_data = None # Patch tcx with notes if activity.Type != ActivityType.Report: if not activity.NotesExt and activity.Notes: tcx_data = TCXIO.Dump(activity) elif "tcx" in activity.PrerenderedFormats: tcx_data = activity.PrerenderedFormats["tcx"] else: tcx_data = TCXIO.Dump(activity) folder_base = os.path.join(USER_DATA_FILES, serviceRecord.ExternalID) # store reports in the separate folder if activity.Type == ActivityType.Report: folder_base = os.path.join(folder_base, "Posts") day_name_chunk = activity.StartTime.strftime("%Y-%m-%d") filename_base = "{}_{}".format(day_name_chunk, activity.Type) if activity.Name: if activity.Type == ActivityType.Report: filename_base = "{}_{}".format(day_name_chunk, activity.Name) else: filename_base = "{}_{}_{}".format(day_name_chunk, activity.Type, activity.Name) filename_base = django.utils.text.get_valid_filename(filename_base) name_base = os.path.join(folder_base, filename_base) if tcx_data: ext = ".tcx" file_exists = 1 while os.path.exists(name_base + ext): ext = "_{}.tcx".format(file_exists) file_exists = file_exists + 1 tcx_file_name = name_base + ext with open(tcx_file_name.decode("utf8"), 'w', encoding="utf-8") as file: file.write(tcx_data) if activity.NotesExt or len(activity.PhotoUrls): # Create subfolder only when need to save multiple files if tcx_data or len(activity.PhotoUrls): ext = "" folders_exists = 1 while os.path.exists(name_base + ext): ext = "_{}".format(folders_exists) folders_exists = folders_exists + 1 folder_base = name_base + ext os.mkdir(folder_base.decode("utf8")) for url_data in activity.PhotoUrls: img_file_name = "{}.jpg".format(url_data["id"]) img_file = os.path.join(folder_base, img_file_name) if activity.NotesExt: activity.NotesExt = activity.NotesExt.replace(url_data["url"], os.path.join(".", img_file_name)) self._download_image(url_data["url"], img_file) if activity.NotesExt: report_file_name = "{}.html".format(filename_base) note_file = os.path.join(folder_base, report_file_name) with open(note_file.decode("utf8"), 'w', encoding="utf-8") as file: file.write(activity.NotesExt) return serviceRecord.ExternalID + activity.UID