예제 #1
0
    def _getActivity(self, serviceRecord, dbcl, path):
        activityData = None

        try:
            f, metadata = dbcl.get_file_and_metadata(path)
        except rest.ErrorResponse as e:
            self._raiseDbException(e)

        if not activityData:
            activityData = f.read()

        try:
            if path.lower().endswith(".tcx"):
                act = TCXIO.Parse(activityData)
            else:
                act = GPXIO.Parse(activityData)
        except ValueError as e:
            raise APIExcludeActivity("Invalid GPX/TCX " + str(e),
                                     activityId=path,
                                     userException=UserException(
                                         UserExceptionType.Corrupt))
        except lxml.etree.XMLSyntaxError as e:
            raise APIExcludeActivity("LXML parse error " + str(e),
                                     activityId=path,
                                     userException=UserException(
                                         UserExceptionType.Corrupt))
        return act, metadata["rev"]
예제 #2
0
    def DownloadActivity(self, serviceRecord, activity):
        #http://connect.garmin.com/proxy/activity-service-1.1/tcx/activity/#####?full=true
        activityID = activity.ServiceData["ActivityID"]
        cookies = self._get_cookies(record=serviceRecord)
        self._rate_limit()
        res = requests.get(
            "http://connect.garmin.com/proxy/activity-service-1.1/tcx/activity/"
            + str(activityID) + "?full=true",
            cookies=cookies)
        try:
            TCXIO.Parse(res.content, activity)
        except ValueError as e:
            raise APIExcludeActivity("TCX parse error " + str(e),
                                     userException=UserException(
                                         UserExceptionType.Corrupt))

        if activity.ServiceData["RecalcHR"]:
            logger.debug("Recalculating HR")
            avgHR, maxHR = ActivityStatisticCalculator.CalculateAverageMaxHR(
                activity)
            activity.Stats.HR.coalesceWith(
                ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute,
                                  max=maxHR,
                                  avg=avgHR))

        if len(activity.Laps) == 1:
            activity.Laps[0].Stats.update(
                activity.Stats
            )  # I trust Garmin Connect's stats more than whatever shows up in the TCX
            activity.Stats = activity.Laps[
                0].Stats  # They must be identical to pass the verification

        if activity.Stats.Temperature.Min is not None or activity.Stats.Temperature.Max is not None or activity.Stats.Temperature.Average is not None:
            logger.debug("Retrieving additional temperature data")
            # TCX doesn't have temperature, for whatever reason...
            self._rate_limit()
            res = requests.get(
                "http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/"
                + str(activityID) + "?full=true",
                cookies=cookies)
            try:
                temp_act = GPXIO.Parse(res.content,
                                       suppress_validity_errors=True)
            except ValueError as e:
                pass
            else:
                logger.debug("Merging additional temperature data")
                full_waypoints = activity.GetFlatWaypoints()
                temp_waypoints = temp_act.GetFlatWaypoints()

                merge_idx = 0

                for x in range(len(temp_waypoints)):
                    while full_waypoints[merge_idx].Timestamp < temp_waypoints[
                            x].Timestamp and merge_idx < len(
                                full_waypoints) - 1:
                        merge_idx += 1
                    full_waypoints[merge_idx].Temp = temp_waypoints[x].Temp

        return activity
예제 #3
0
    def DownloadActivity(self, serviceRecord, activity):
        session = self._get_session(serviceRecord)
        activity_id = activity.ServiceData["ActivityID"]

        tcx_data = session.get("{}export/workouts/{}/tcx".format(
            self._urlRoot, activity_id),
                               data=self._with_auth(serviceRecord))
        activity_ex = TCXIO.Parse(tcx_data.text.encode('utf-8'), activity)
        # Obtain more information about activity
        res = session.get(self._workoutUrlJson.format(id=activity_id),
                          data=self._with_auth(serviceRecord))
        activity_data = res.json()
        activity_ex.Name = activity_data["name"]
        # Notes comes as html. Hardly any other service will support this so needs to extract text data
        if "body" in activity_data["post"]:
            post_html = activity_data["post"]["body"]
            soup = BeautifulSoup(post_html)
            # Notes also contains styles, get rid of them
            for style in soup("style"):
                style.decompose()
            activity_ex.Notes = soup.getText()

        # Dirty hack to patch users inventory even if they use aerobia mobile app to record activities
        # Still need to sync with some service though.
        extra_data = {}
        self._put_default_inventory(activity, serviceRecord, extra_data)
        if extra_data:
            self._patch_activity(serviceRecord, extra_data, activity_id)

        return activity_ex
예제 #4
0
    def DownloadActivity(self, serviceRecord, activity):
        deviceUploadFile = activity.ServiceData.get("DeviceUploadFile")

        # No additional data about this event is available.
        if not deviceUploadFile:
            return activity

        logger.info("Downloading device file %s" % deviceUploadFile)
        session = self._prepare_request(self._getUserToken(serviceRecord))
        res = session.get(deviceUploadFile)

        if res.status_code == 200:
            try:
                contentType = self._mimeTypeMappings[res.headers["content-type"]]
                if not contentType:
                    remoteUrl = urlparse(deviceUploadFile).path
                    extension = os.path.splitext(remoteUrl)[1]
                    contentType = self._fileExtensionMappings[extension]

                if contentType:
                    if contentType == _DeviceFileTypes.FIT:
                        # Oh no! Not supported! So close ....
                        # FITIO.Parse(res.content, activity)
                        return activity
                    if contentType == _DeviceFileTypes.TCX:
                        TCXIO.Parse(res.content, activity)
                    if contentType == _DeviceFileTypes.GPX:
                        GPXIO.Parse(res.content, activity)
            except ValueError as e:
                raise APIExcludeActivity("Parse error " + deviceUploadFile + " " + str(e),
                                         user_exception=UserException(UserExceptionType.Corrupt),
                                         permanent=True)

        return activity
예제 #5
0
    def DownloadActivity(self, serviceRecord, activity):
        # First, download the summary stats and lap stats
        self._downloadActivitySummary(serviceRecord, activity)

        if len(activity.Laps) == 1:
            activity.Stats = activity.Laps[
                0].Stats  # They must be identical to pass the verification

        if activity.Stationary:
            # Nothing else to download
            return activity

        # 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/download-service/export/tcx/activity/{}"
                .format(activityID)), serviceRecord)
        try:
            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))

        return activity
예제 #6
0
    def DownloadActivity(self, serviceRecord, activity):
        session = self._get_session(serviceRecord)

        url = "https://www.polarpersonaltrainer.com/user/calendar/"
        gpxUrl = "index.gpx"
        xmlUrl = "index.jxml"

        gpx_data = {
            ".action": "gpx",
            "items.0.item": activity.ExternalID,
            "items.0.itemType": "OptimizedExercise"
        }

        xml_data = {
            ".action": "export", 
            "items.0.item": activity.ExternalID,
            "items.0.itemType": "OptimizedExercise",
            ".filename": "training.xml"
        }

        xmlResp = session.post(url + xmlUrl, data=xml_data)
        xmlText = xmlResp.text
        gpxResp = session.post(url + gpxUrl, data=gpx_data)
        if gpxResp.status_code == 401:
            logger.debug("Problem completing request. Unauthorized. Activity extId = {}".format(activity.ExternalID))
            raise APIException("Unknown authorization problem during request", user_exception=UserException(UserExceptionType.DownloadError))
        
        gpxText = gpxResp.text
        activity.GPS = not ("The items you are exporting contain no GPS data" in gpxText)

        tcxData = convert(xmlText, activity.StartTime, gpxText if activity.GPS else None)
        activity = TCXIO.Parse(tcxData, activity)

        return activity
예제 #7
0
    def DownloadActivity(self, serviceRecord, activity):
        workout_id = activity.ServiceData["ID"]

        session = self._get_session(record=serviceRecord)

        res = session.get(
            "http://www.trainerroad.com/cycling/rides/download/%d" %
            workout_id)

        if res.status_code == 500:
            # Account is private (or their site is borked), log in the blegh way
            session = self._get_session(record=serviceRecord, cookieAuth=True)
            res = session.get(
                "http://www.trainerroad.com/cycling/rides/download/%d" %
                workout_id)
            activity.Private = True

        try:
            TCXIO.Parse(res.content, activity)
        except ValueError as e:
            raise APIExcludeActivity("TCX parse error " + str(e),
                                     user_exception=UserException(
                                         UserExceptionType.Corrupt))

        return activity
예제 #8
0
 def DownloadActivity(self, serviceRecord, activity):
     # NOTE tcx have to be gzipped but it actually doesn't
     # https://www.polar.com/accesslink-api/?python#get-tcx
     #tcx_data_raw = requests.get(activity_link + "/tcx", headers=self._api_headers(serviceRecord))
     #tcx_data = gzip.GzipFile(fileobj=StringIO(tcx_data_raw)).read()
     tcx_url = serviceRecord.ServiceData[
         "Transaction-uri"] + "/exercises/{}/tcx".format(
             activity.ServiceData["ActivityID"])
     response = requests.get(
         tcx_url,
         headers=self._api_headers(
             serviceRecord, {"Accept": "application/vnd.garmin.tcx+xml"}))
     if response.status_code == 404:
         # Transaction was disbanded, all data linked to it will be returned in next transaction
         raise APIException("Transaction disbanded",
                            user_exception=UserException(
                                UserExceptionType.DownloadError))
     try:
         tcx_data = response.text
         activity = TCXIO.Parse(tcx_data.encode('utf-8'), activity)
         activity.SourceFile = SourceFile(tcx_data, ActivityFileType.TCX)
     except lxml.etree.XMLSyntaxError:
         raise APIException(
             "Cannot recieve training tcx at url: {}".format(tcx_url),
             user_exception=UserException(UserExceptionType.DownloadError))
     return activity
예제 #9
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)
예제 #10
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)
예제 #11
0
    def DownloadActivity(self, svcRecord, activity):

        userID = svcRecord.ExternalID
        activity_id = activity.ServiceData["ActivityID"]

        logging.info("\t\t FITBIT LOADING  : " + str(activity_id))
        activity_tcx_uri = 'https://api.fitbit.com/1/user/' + userID + '/activities/' + str(activity_id) + '.tcx'
        resp = self._requestWithAuth(lambda session: session.get(
            activity_tcx_uri,
            headers={
                'Authorization': 'Bearer ' + svcRecord.Authorization.get('AccessToken')
            }), svcRecord)
        # check if request has error
        if resp.status_code != 204 and resp.status_code != 200:
            raise APIException("Unable to find Fitbit TCX activity")

        # Prepare tcxio params
        ns = copy.deepcopy(TCXIO.Namespaces)
        ns["tcx"] = ns[None]
        del ns[None]

        # Read tcx to know if this is a stationary activity or not
        try:
            root = etree.XML(resp.text.encode('utf-8'))
        except:
            root = etree.fromstring(resp.text.encode('utf-8'))

        xacts = root.find("tcx:Activities", namespaces=ns)
        if xacts is None:
            raise ValueError("No activities element in TCX")

        xact = xacts.find("tcx:Activity", namespaces=ns)
        if xact is None:
            raise ValueError("No activity element in TCX")

        # Define activity type from tcx
        if not activity.Type or activity.Type == ActivityType.Other:
            if xact.attrib["Sport"] == "Biking":
                activity.Type = ActivityType.Cycling
            elif xact.attrib["Sport"] == "Running":
                activity.Type = ActivityType.Running

        # Find all lap in tcx
        xlaps = xact.findall("tcx:Lap", namespaces=ns)
        if len(xlaps) > 0:
            activity = TCXIO.Parse(resp.text.encode('utf-8'), activity)
        else:
            # Define lap for activity
            lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime)
            activity.Laps = [lap]
            lap.Waypoints = []
            activity.GPS = False
            activity.Stationary = len(lap.Waypoints) == 0

        return activity
예제 #12
0
    def DownloadActivity(self, serviceRecord, activity):
        #http://connect.garmin.com/proxy/activity-service-1.1/tcx/activity/#####?full=true
        activityID = [x["ActivityID"] for x in activity.UploadedTo if x["Connection"] == serviceRecord][0]
        cookies = self._get_cookies(record=serviceRecord)
        res = requests.get("http://connect.garmin.com/proxy/activity-service-1.1/tcx/activity/" + str(activityID) + "?full=true", cookies=cookies)
        try:
            TCXIO.Parse(res.content, activity)
        except ValueError as e:
            raise APIExcludeActivity("TCX parse error " + str(e))

        return activity
예제 #13
0
    def DownloadActivity(self, serviceRecord, activity):
        activity_id = activity.ServiceData["id"]

	# Switch URL to /api/sync/activity/fit/ once FITIO.Parse() available
        resp = requests.get(TRAINASONE_SERVER_URL + "/api/sync/activity/tcx/" + activity_id, headers=self._apiHeaders(serviceRecord.Authorization))

        try:
            TCXIO.Parse(resp.content, activity)
        except ValueError as e:
            raise APIExcludeActivity("TCX parse error " + str(e), user_exception=UserException(UserExceptionType.Corrupt))

        return activity
예제 #14
0
    def DownloadActivity(self, serviceRecord, activity):
        if activity.Stationary:
            return activity # Nothing more to download - it doesn't serve these files for manually entered activites
        # https://ridewithgps.com/trips/??????.tcx
        activityID = activity.ServiceData["ActivityID"]
        res = requests.get("https://ridewithgps.com/trips/{}.tcx".format(activityID),
                           params=self._add_auth_params({'sub_format': 'history'}, record=serviceRecord))
        try:
            TCXIO.Parse(res.content, activity)
        except ValueError as e:
            raise APIExcludeActivity("TCX parse error " + str(e), user_exception=UserException(UserExceptionType.Corrupt))

        return activity
예제 #15
0
    def _getActivity(self, serviceRecord, dbcl, path, base_activity=None):
        try:
            metadata, file = dbcl.files_download(path)
        except dropbox.exceptions.DropboxException as e:
            self._raiseDbException(e)

        try:
            if path.lower().endswith(".tcx"):
                act = TCXIO.Parse(file.content, base_activity)
            else:
                act = GPXIO.Parse(file.content, base_activity)
        except ValueError as e:
            raise APIExcludeActivity("Invalid GPX/TCX " + str(e), activity_id=path, user_exception=UserException(UserExceptionType.Corrupt))
        except lxml.etree.XMLSyntaxError as e:
            raise APIExcludeActivity("LXML parse error " + str(e), activity_id=path, user_exception=UserException(UserExceptionType.Corrupt))
        return act, metadata.rev
예제 #16
0
    def DownloadActivity(self, serviceRecord, activity):
        # https://ridewithgps.com/trips/??????.gpx
        activityID = [
            x["ActivityID"] for x in activity.UploadedTo
            if x["Connection"] == serviceRecord
        ][0]
        res = requests.get(
            "https://ridewithgps.com/trips/{}.tcx".format(activityID),
            params=self._add_auth_params({'sub_format': 'history'},
                                         record=serviceRecord))
        try:
            TCXIO.Parse(res.content, activity)
        except ValueError as e:
            raise APIExcludeActivity("TCX parse error " + str(e),
                                     userException=UserException(
                                         UserExceptionType.Corrupt))

        return activity
예제 #17
0
    def DownloadActivity(self, serviceRecord, activity):
        activity_id = activity.ServiceData["ActivityID"]

        # reports already contains all data
        if activity.Type == ActivityType.Report:
            return activity

        tcx_data = self._safe_call(serviceRecord, "get", "{}export/workouts/{}/tcx".format(self._urlRoot, activity_id))
        try:
            activity_ex = TCXIO.Parse(tcx_data.text.encode('utf-8'), activity)
        except:
            logger.debug("Unable to parse activity tcx: data corrupted")
            raise APIException("Unable to parse activity tcx: data corrupted")

        # Obtain more information about activity
        res = self._safe_call(serviceRecord, "get", self._workoutUrlJson.format(id=activity_id))
        activity_data = res.json()
        activity_ex.Name = activity_data["name"] if "name" in activity_data else ""

        if "photos" in activity_data["post"]:
            for img_info in activity_data["post"]["photos"]:
                activity_ex.PhotoUrls.append({"id": img_info["id"], "url": img_info["original"]})
        # Notes comes as html. Hardly any other service will support this so needs to extract text data
        if "body" in activity_data["post"]:
            post_html = activity_data["post"]["body"]
            soup = BeautifulSoup(post_html)
            # Notes also contains styles, get rid of them
            for style in soup("style"):
                style.decompose()
            activity_ex.Notes = soup.getText()
            # all notes with photos considered as reports
            if len(activity_ex.Notes) > self.REPORT_MIN_LIMIT or len(activity_ex.PhotoUrls):
                activity_ex.NotesExt = soup.prettify()

        # Dirty hack to patch users inventory even if they use aerobia mobile app to record activities
        # Still need to sync with some service though.
        # TODO should driven by setting
        #extra_data = {}
        #self._put_default_inventory(activity, serviceRecord, extra_data)
        #if extra_data:
        #    self._patch_activity(serviceRecord, extra_data, activity_id)

        return activity_ex
예제 #18
0
파일: aerobia.py 프로젝트: xue35/tapiriik
    def DownloadActivity(self, serviceRecord, activity):
        session = self._get_session(serviceRecord)
        activity_id = activity.ServiceData["ActivityID"]

        tcx_data = session.get("{}export/workouts/{}/tcx".format(
            self._urlRoot, activity_id),
                               data=self._with_auth(serviceRecord))
        activity_ex = TCXIO.Parse(tcx_data.text.encode('utf-8'), activity)
        # Obtain more information about activity
        res = session.get(self._workoutUrl.format(id=activity_id),
                          data=self._with_auth(serviceRecord))
        activity_data = res.json()
        activity_ex.Name = activity_data["name"]
        # Notes comes as html. Hardly any other service will support this so needs to extract text data
        if "body" in activity_data["post"]:
            post_html = activity_data["post"]["body"]
            soup = BeautifulSoup(post_html)
            # Notes also contains styles, get rid of them
            for style in soup("style"):
                style.decompose()
            activity_ex.Notes = soup.getText()

        return activity_ex