Пример #1
0
    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")
Пример #2
0
    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
Пример #3
0
    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"]
Пример #4
0
    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
Пример #5
0
    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)
Пример #6
0
    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)
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
    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")
Пример #10
0
    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
Пример #11
0
    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)
Пример #12
0
    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