def gcff(config_file):
    config = {
        "username": "",
        "password": "",
        "backup_dir": os.path.join(".", "gc_backup"),
        "export_formats": "fit",
        "ignore_errors": False,
        "max_retries": 3,
        "download_from_date": "1900-01-01 00:00:00"
    }

    config_file = os.path.expanduser(config_file)
    if not os.path.isfile(config_file):
        print("Configuration file not found: " + config_file)
        return

    with open(config_file) as f:
        try:
            user_config = json.load(f)
        except json.decoder.JSONDecodeError as err:
            print("Configuration file error: " + str(err))
            return

    config.update(user_config)

    if not os.path.isdir(config["backup_dir"]):
        os.makedirs(config["backup_dir"])

    rr = Retryer(delay_strategy=ExponentialBackoffDelayStrategy(
        initial_delay=timedelta(seconds=1)),
                 stop_strategy=MaxRetriesStopStrategy(config["max_retries"]))

    with GarminClient(config["username"], config["password"]) as gc:
        # get all activity ids and timestamps
        print("Scanning activities for {}".format(config["username"]))
        activities = rr.call(gc.list_activities)
        print("Total activities: {}".format(len(activities)))
        # print(activities)

        from_date_str = config["download_from_date"]
        new_activities = get_activities_since(activities, from_date_str)
        print("Activities to backup[since: {}]: {}".format(
            from_date_str, len(new_activities)))

        last_date = None
        for i, activity in enumerate(new_activities):
            _id, activity_date = activity
            print("Downloading[{}]: {} - {}".format(i, _id, activity_date))
            try:
                garminexport.backup.download(gc, activity, rr,
                                             config["backup_dir"],
                                             config["export_formats"])
            except Exception as err:
                print("failed with exception: {}".format(err))
                if not config["ignore_errors"]:
                    raise
            if last_date is None or last_date < activity_date:
                last_date = activity_date

        store_last_activity_date_in_config_file(config_file, config, last_date)
Exemple #2
0
def incremental_backup(username: str,
                       password: str = None,
                       backup_dir: str = os.path.join(".", "activities"),
                       export_formats: str = 'ALL',
                       ignore_errors: bool = False,
                       max_retries: int = 7):
    """Performs (incremental) backups of activities for a given Garmin Connect account.

    :param username: Garmin Connect user name
    :param password: Garmin Connect user password. Default: None. If not provided, would be asked interactively.
    :param backup_dir: Destination directory for downloaded activities. Default: ./activities/".
    :param export_formats: Desired output formats (json_summary, json_details, gpx, kml, tcx, fit). Default: ALL.
    :param ignore_errors: Ignore errors and keep going. Default: False.
    :param max_retries: The maximum number of retries to make on failed attempts to fetch an activity.
    Exponential backoff will be used, meaning that the delay between successive attempts
    will double with every retry, starting at one second. Default: 7.

    The activities are stored in a local directory on the user's computer.
    The backups are incremental, meaning that only activities that aren't already
    stored in the backup directory will be downloaded.
    """
    # if no --format was specified, all formats are to be backed up
    export_formats = export_formats if export_formats else supported_export_formats
    log.info("backing up formats: %s", ", ".join(export_formats))

    if not os.path.isdir(backup_dir):
        os.makedirs(backup_dir)

    if not password:
        password = getpass.getpass("Enter password: "******"scanning activities for %s ...", username)
        activities = set(retryer.call(client.list_activities))
        log.info("account has a total of %d activities", len(activities))

        missing_activities = garminexport.backup.need_backup(activities, backup_dir, export_formats)
        backed_up = activities - missing_activities
        log.info("%s contains %d backed up activities", backup_dir, len(backed_up))

        log.info("activities that aren't backed up: %d", len(missing_activities))

        for index, activity in enumerate(missing_activities):
            id, start = activity
            log.info("backing up activity %s from %s (%d out of %d) ...",
                     id, start, index + 1, len(missing_activities))
            try:
                garminexport.backup.download(client, activity, retryer, backup_dir, export_formats)
            except Exception as e:
                log.error("failed with exception: %s", e)
                if not ignore_errors:
                    raise
Exemple #3
0
class ConnectClient:
    _active = False

    def __init__(self,
                 username,
                 password,
                 initial_delay=timedelta(seconds=1),
                 max_retries=3):
        self._client = GarminClient(username, password)
        self._retryer = Retryer(
            delay_strategy=ExponentialBackoffDelayStrategy(
                initial_delay=initial_delay),
            stop_strategy=MaxRetriesStopStrategy(max_retries))

    def __enter__(self):
        self._client.__enter__()
        self._active = True
        return self

    def __exit__(self, *args):
        self._active = False
        self._client.__exit__(*args)

    @property
    def activities(self):
        """
        return set of (activity_id:int, start_time:datetime) tuples
        """
        if not self._active:
            raise RuntimeError("Context manager not active")

        return {
            rv[0]
            for rv in self._retryer.call(self._client.list_activities)
        }

    def download_activity(self, activity_id):
        """
        returns fit data or None
        """
        if not self._active:
            raise RuntimeError("Context manager not active")

        return self._retryer.call(self._client.get_activity_fit, activity_id)
Exemple #4
0
            os.makedirs(backup_dir)

        if not password:
            raise ValueError("Password must be provided")

        # set up a retryer that will handle retries of failed activity
        # downloads
        retryer = Retryer(
            delay_strategy=ExponentialBackoffDelayStrategy(
                initial_delay=timedelta(seconds=1)),
            stop_strategy=MaxRetriesStopStrategy(DEFAULT_MAX_RETRIES))

        with GarminClient(username, password) as client:
            # get all activity ids and timestamps from Garmin account
            log.info("scanning activities for %s ...", username)
            activities = set(retryer.call(client.list_activities))
            log.info("account has a total of %d activities", len(activities))

            missing_activities = garminexport.backup.need_backup(
                activities, backup_dir, formats)
            backed_up = activities - missing_activities
            log.info("%s contains %d backed up activities", backup_dir,
                     len(backed_up))

            log.info("activities that aren't backed up: %d",
                     len(missing_activities))

            for index, activity in enumerate(missing_activities):
                id, start = activity
                log.info("backing up activity %d from %s (%d out of %d) ..." %
                         (id, start, index + 1, len(missing_activities)))
        print("Incorrect Input.  enter either (y/n)")
        DOWNLOAD_GARMIN = input('Download Garmin (y/n)?')

    if DOWNLOAD_GARMIN == 'y':
	    log.info('Getting Garmin Files...')

	    retryer = Retryer(
	        delay_strategy=ExponentialBackoffDelayStrategy(
	            initial_delay=datetime.timedelta(seconds=1)),
	        stop_strategy=MaxRetriesStopStrategy(5))

	    PARTICIPANT_DIR = "C:/Users/Running Injury Clini/Desktop/Marathon Training/" + args.participant_id
	    try:
	        with GarminClient(garmin_uname, garmin_pwd) as gar_client:
	            log.info("activities:")
	            activities = retryer.call(gar_client.list_activities)
	            log.info("Account has a total of %d activities total", len(activities))
	            # Loop through activities here....

	            activity_dates = [int(str(act[1].date()).replace("-", "")) for act in activities]

	            for i in range(0, len(activities) - 1):
	                activity_id = activities[i][0]
	                activity = gar_client.get_activity_summary(activity_id)
	                activity_type = activity["activityTypeDTO"]["typeKey"]
	                if activity_type == 'running':  # only download running activities
	                    activity_date = activities[i][1]
	                    activity_date = str(activity_date.date())
	                    activity_date = activity_date.replace("-", "")

	                    if int(activity_date) >= int(args.min_date) and int(activity_date) <= int(args.max_date):
        hashlib.md5((str(username) + str(date_start) + str(date_end) +
                     file_output).encode('utf-8')).hexdigest()))

# Get tracks recorded between date_start and date_end
print(t.green("Get tracks"))
if os.path.isfile(file_cache):
    with open(file_cache, 'rb') as f:
        ids, dates, tracks = pickle.load(f)
    print("Tracks loaded from cache")
else:
    try:
        with GarminClient(username, password) as client:
            retryer = Retryer(delay_strategy=ExponentialBackoffDelayStrategy(
                initial_delay=timedelta(seconds=1)),
                              stop_strategy=MaxRetriesStopStrategy(5))
            activities = set(retryer.call(client.list_activities))
            ids = [
                a[0] for a in activities
                if a[1] > date_start and a[1] < date_end + timedelta(days=1)
            ]
            dates = [
                a[1] for a in activities
                if a[1] > date_start and a[1] < date_end + timedelta(days=1)
            ]
            tracks = [
                retryer.call(client.get_activity_gpx, i) for i in tqdm(ids)
            ]
    except RuntimeError as e:
        if str(e).startswith('auth failure'):
            keyring.delete_password('garmin-gpx-combiner', username)
        raise
        if not args.password:
            args.password = getpass.getpass("Enter password: "******"scanning activities for %s ...", args.username)
            activities = set(retryer.call(client.list_activities))
            log.info("account has a total of %d activities", len(activities))

            missing_activities = garminexport.backup.need_backup(
                activities, args.backup_dir, args.format)
            backed_up = activities - missing_activities
            log.info("%s contains %d backed up activities",
                args.backup_dir, len(backed_up))

            log.info("activities that aren't backed up: %d",
                     len(missing_activities))

            for index, activity in enumerate(missing_activities):
                id, start = activity
                log.info("backing up activity %d from %s (%d out of %d) ..." % (id, start, index+1, len(missing_activities)))
                try:
Exemple #8
0
    def upload_activity(self,
                        file,
                        format=None,
                        name=None,
                        description=None,
                        activity_type=None,
                        private=None):
        """Upload a GPX, TCX, or FIT file for an activity.

        :param file: Path or open file
        :param format: File format (gpx, tcx, or fit); guessed from filename if :obj:`None`
        :type format: str
        :param name: Optional name for the activity on Garmin Connect
        :type name: str
        :param description: Optional description for the activity on Garmin Connect
        :type description: str
        :param activity_type: Optional activityType key (lowercase: e.g. running, cycling)
        :type activityType: str
        :param private: If true, then activity will be set as private.
        :type private: bool
        :returns: ID of the newly-uploaded activity
        :rtype: int
        """

        if isinstance(file, str):
            file = open(file, "rb")

        # guess file type if unspecified
        fn = os.path.basename(file.name)
        _, ext = os.path.splitext(fn)
        if format is None:
            if ext.lower() in ('.gpx', '.tcx', '.fit'):
                format = ext.lower()[1:]
            else:
                raise Exception(u"could not guess file type for {}".format(fn))

        # upload it
        files = dict(data=(fn, file))
        response = self.session.post(
            "https://connect.garmin.com/modern/proxy/upload-service/upload/.{}"
            .format(format),
            files=files,
            headers={"nk": "NT"})

        # check response and get activity ID
        try:
            j = response.json()["detailedImportResult"]
        except (json.JSONDecodeError, KeyError):
            raise Exception(u"failed to upload {} for activity: {}\n{}".format(
                format, response.status_code, response.text))

        # single activity, immediate success
        if len(j["successes"]) == 1 and len(j["failures"]) == 0:
            activity_id = j["successes"][0]["internalId"]

        # duplicate of existing activity
        elif len(j["failures"]) == 1 and len(
                j["successes"]) == 0 and response.status_code == 409:
            log.info(u"duplicate activity uploaded, continuing")
            activity_id = j["failures"][0]["internalId"]

        # need to poll until success/failure
        elif len(j["failures"]) == 0 and len(
                j["successes"]) == 0 and response.status_code == 202:
            retryer = Retryer(
                returnval_predicate=bool,
                delay_strategy=ExponentialBackoffDelayStrategy(
                    initial_delay=timedelta(seconds=1)),
                stop_strategy=MaxRetriesStopStrategy(
                    6),  # wait for up to 64 seconds (2**6)
                error_strategy=None)
            activity_id = retryer.call(self._poll_upload_completion,
                                       j["uploadUuid"]["uuid"],
                                       j["creationDate"])

        # don't know how to handle multiple activities
        elif len(j["successes"]) > 1:
            raise Exception(
                u"uploading {} resulted in multiple activities ({})".format(
                    format, len(j["successes"])))

        # all other errors
        else:
            raise Exception(u"failed to upload {} for activity: {}\n{}".format(
                format, response.status_code, j["failures"]))

        # add optional fields
        data = {}
        if name is not None:
            data['activityName'] = name
        if description is not None:
            data['description'] = description
        if activity_type is not None:
            data['activityTypeDTO'] = {"typeKey": activity_type}
        if private:
            data['privacy'] = {"typeKey": "private"}
        if data:
            data['activityId'] = activity_id
            encoding_headers = {
                "Content-Type": "application/json; charset=UTF-8"
            }  # see Tapiriik
            response = self.session.put(
                "https://connect.garmin.com/proxy/activity-service/activity/{}"
                .format(activity_id),
                data=json.dumps(data),
                headers=encoding_headers)
            if response.status_code != 204:
                raise Exception(
                    u"failed to set metadata for activity {}: {}\n{}".format(
                        activity_id, response.status_code, response.text))

        return activity_id
    def test_bla(self):
        retryer = Retryer()
        func = lambda: int(time.time())

        returnval = retryer.call(func)
        print returnval
 def test_with_returnval_predicate(self):
     """`Retryer` should only return when the returnval_predicate says so."""
     retryer = Retryer(returnval_predicate=lambda r: r == 20)
     self.assertEqual(retryer.call(Counter().next_value), 20)
 def test_bla(self):
     retryer = Retryer()
     func = lambda : int(time.time())
     
     returnval = retryer.call(func)
     print(returnval)
 def test_with_returnval_predicate(self):
     """`Retryer` should only return when the returnval_predicate says so."""
     retryer = Retryer(returnval_predicate=lambda r: r == 20)
     self.assertEqual(retryer.call(Counter().next_value), 20)
Exemple #13
0
def incremental_backup(username: str,
                       password: str = None,
                       user_agent_fn: Callable[[],str] = None,
                       backup_dir: str = os.path.join(".", "activities"),
                       export_formats: List[str] = None,
                       ignore_errors: bool = False,
                       max_retries: int = 7):
    """Performs (incremental) backups of activities for a given Garmin Connect account.

    :param username: Garmin Connect user name
    :param password: Garmin Connect user password. Default: None. If not provided, would be asked interactively.
    :keyword user_agent_fn: A function that, when called, produces a
    `User-Agent` string to be used as `User-Agent` for the remainder of the
    session. If set to None, the default user agent of the http request
    library is used.
    :type user_agent_fn: Callable[[], str]
    :param backup_dir: Destination directory for downloaded activities. Default: ./activities/".
    :param export_formats: List of desired output formats (json_summary, json_details, gpx, tcx, fit).
    Default: `None` which means all supported formats will be backed up.
    :param ignore_errors: Ignore errors and keep going. Default: False.
    :param max_retries: The maximum number of retries to make on failed attempts to fetch an activity.
    Exponential backoff will be used, meaning that the delay between successive attempts
    will double with every retry, starting at one second. Default: 7.

    The activities are stored in a local directory on the user's computer.
    The backups are incremental, meaning that only activities that aren't already
    stored in the backup directory will be downloaded.

    """
    # if no --format was specified, all formats are to be backed up
    export_formats = export_formats if export_formats else supported_export_formats
    log.info("backing up formats: %s", ", ".join(export_formats))

    if not os.path.isdir(backup_dir):
        os.makedirs(backup_dir)

    if not password:
        password = getpass.getpass("Enter password: "******"scanning activities for %s ...", username)
        activities = set(retryer.call(client.list_activities))
        log.info("account has a total of %d activities", len(activities))

        missing_activities = garminexport.backup.need_backup(activities, backup_dir, export_formats)
        backed_up = activities - missing_activities
        log.info("%s contains %d backed up activities", backup_dir, len(backed_up))

        log.info("activities that aren't backed up: %d", len(missing_activities))

        for index, activity in enumerate(missing_activities):
            id, start = activity
            if id not in [578518094]:
                log.info("backing up activity %s from %s (%d out of %d) ...",
                     id, start, index + 1, len(missing_activities))
                try:
                    garminexport.backup.download(client, activity, retryer, backup_dir, export_formats)
                except Exception as e:
                    log.error("failed with exception: %s", e)
                    if not ignore_errors:
                        raise

        # gewicht herunterladen
        import urllib.request, json 
        import datetime
        import time
        WeightURL = "https://connect.garmin.com/modern/proxy/userprofile-service/userprofile/personal-information/weightWithOutbound/filterByDay?from="+str(int(datetime.datetime(2008,1,1,0,0).timestamp()))+"999"+"&until="+str(int(time.time()))+"999"
        response = client.session.get(WeightURL)
        if response.status_code in (404, 204):
            log.info("failed to download weight 404,204")
        if response.status_code != 200:
            log.info("failed to download weight ungleich 200")
        else:
            data = json.loads(response.text)
            heute = datetime.date.today()
            with open(os.path.join(backup_dir,'weight_'+str(heute.year)+'-'+str(heute.month)+'-'+str(heute.day)+'.json'), 'w') as outfile:
                json.dump(data,outfile)