示例#1
0
 def test_initial_delay(self):
     """The initial delay is used to scale the series of delays."""
     self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=2))
     self.assertEqual(self.strategy.next_delay(0), timedelta(seconds=0))
     self.assertEqual(self.strategy.next_delay(1), timedelta(seconds=2 * 1))
     self.assertEqual(self.strategy.next_delay(2), timedelta(seconds=2 * 2))
     self.assertEqual(self.strategy.next_delay(3), timedelta(seconds=2 * 4))
     self.assertEqual(self.strategy.next_delay(4), timedelta(seconds=2 * 8))
     self.assertEqual(self.strategy.next_delay(5),
                      timedelta(seconds=2 * 16))
     self.assertEqual(self.strategy.next_delay(10),
                      timedelta(seconds=2 * 512))
示例#2
0
class TestExponentialBackoffDelayStrategy(unittest.TestCase):
    """Exercise `ExponentialBackoffDelayStrategy`."""
    def setUp(self):
        # object under test
        self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=1))

    def test_calculate_delay(self):
        """`ExponentialBackoffDelayStrategy` should return exponentially increasing delay."""
        self.assertEqual(self.strategy.next_delay(0), timedelta(seconds=0))
        self.assertEqual(self.strategy.next_delay(1), timedelta(seconds=1))
        self.assertEqual(self.strategy.next_delay(2), timedelta(seconds=2))
        self.assertEqual(self.strategy.next_delay(3), timedelta(seconds=4))
        self.assertEqual(self.strategy.next_delay(4), timedelta(seconds=8))
        self.assertEqual(self.strategy.next_delay(5), timedelta(seconds=16))
        self.assertEqual(self.strategy.next_delay(10), timedelta(seconds=512))

    def test_initial_delay(self):
        """The initial delay is used to scale the series of delays."""
        self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=2))
        self.assertEqual(self.strategy.next_delay(0), timedelta(seconds=0))
        self.assertEqual(self.strategy.next_delay(1), timedelta(seconds=2 * 1))
        self.assertEqual(self.strategy.next_delay(2), timedelta(seconds=2 * 2))
        self.assertEqual(self.strategy.next_delay(3), timedelta(seconds=2 * 4))
        self.assertEqual(self.strategy.next_delay(4), timedelta(seconds=2 * 8))
        self.assertEqual(self.strategy.next_delay(5),
                         timedelta(seconds=2 * 16))
        self.assertEqual(self.strategy.next_delay(10),
                         timedelta(seconds=2 * 512))
class TestExponentialBackoffDelayStrategy(unittest.TestCase):
    """Exercise `ExponentialBackoffDelayStrategy`."""

    def setUp(self):
        # object under test
        self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=1))
    
    def test_calculate_delay(self):
        """`ExponentialBackoffDelayStrategy` should return exponentially increasing delay."""
        self.assertEqual(self.strategy.next_delay(0), timedelta(seconds=0))
        self.assertEqual(self.strategy.next_delay(1), timedelta(seconds=1))
        self.assertEqual(self.strategy.next_delay(2), timedelta(seconds=2))
        self.assertEqual(self.strategy.next_delay(3), timedelta(seconds=4))
        self.assertEqual(self.strategy.next_delay(4), timedelta(seconds=8))
        self.assertEqual(self.strategy.next_delay(5), timedelta(seconds=16))
        self.assertEqual(self.strategy.next_delay(10), timedelta(seconds=512))

    def test_initial_delay(self):
        """The initial delay is used to scale the series of delays."""
        self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=2))
        self.assertEqual(self.strategy.next_delay(0), timedelta(seconds=0))
        self.assertEqual(self.strategy.next_delay(1), timedelta(seconds=2*1))
        self.assertEqual(self.strategy.next_delay(2), timedelta(seconds=2*2))
        self.assertEqual(self.strategy.next_delay(3), timedelta(seconds=2*4))
        self.assertEqual(self.strategy.next_delay(4), timedelta(seconds=2*8))
        self.assertEqual(self.strategy.next_delay(5), timedelta(seconds=2*16))
        self.assertEqual(self.strategy.next_delay(10), timedelta(seconds=2*512))
示例#4
0
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)
示例#5
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
示例#6
0
 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 test_initial_delay(self):
     """The initial delay is used to scale the series of delays."""
     self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=2))
     self.assertEqual(self.strategy.next_delay(0), timedelta(seconds=0))
     self.assertEqual(self.strategy.next_delay(1), timedelta(seconds=2*1))
     self.assertEqual(self.strategy.next_delay(2), timedelta(seconds=2*2))
     self.assertEqual(self.strategy.next_delay(3), timedelta(seconds=2*4))
     self.assertEqual(self.strategy.next_delay(4), timedelta(seconds=2*8))
     self.assertEqual(self.strategy.next_delay(5), timedelta(seconds=2*16))
     self.assertEqual(self.strategy.next_delay(10), timedelta(seconds=2*512))
示例#8
0
    formats = formats if formats else export_formats
    log.info("backing up formats: %s", ", ".join(formats))

    logging.root.setLevel(LOG_LEVELS[log_level])

    try:
        if not os.path.isdir(backup_dir):
            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",
示例#9
0
def main():
    parser = argparse.ArgumentParser(
        description=
        "Downloads one particular activity for a given Garmin Connect account."
    )

    # positional args
    parser.add_argument("username",
                        metavar="<username>",
                        type=str,
                        help="Account user name.")
    parser.add_argument("activity",
                        metavar="<activity>",
                        type=int,
                        help="Activity ID.")
    parser.add_argument("format",
                        metavar="<format>",
                        type=str,
                        help="Export format (one of: {}).".format(
                            garminexport.backup.supported_export_formats))

    # optional args
    parser.add_argument("--password", type=str, help="Account password.")
    parser.add_argument(
        "--destination",
        metavar="DIR",
        type=str,
        help=
        "Destination directory for downloaded activity. Default: ./activities/",
        default=os.path.join(".", "activities"))
    parser.add_argument(
        "--log-level",
        metavar="LEVEL",
        type=str,
        help=
        "Desired log output level (DEBUG, INFO, WARNING, ERROR). Default: INFO.",
        default="INFO")

    args = parser.parse_args()

    if args.log_level not in LOG_LEVELS:
        raise ValueError("Illegal log-level argument: {}".format(
            args.log_level))

    if args.format not in garminexport.backup.supported_export_formats:
        raise ValueError(
            "Unrecognized export format: '{}'. Must be one of {}".format(
                args.format, garminexport.backup.supported_export_formats))

    logging.root.setLevel(LOG_LEVELS[args.log_level])

    try:
        if not os.path.isdir(args.destination):
            os.makedirs(args.destination)

        if not args.password:
            args.password = getpass.getpass("Enter password: "******"fetching activity %s ...", args.activity)
            summary = client.get_activity_summary(args.activity)
            # 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(5))

            start_time = dateutil.parser.parse(
                summary["summaryDTO"]["startTimeGMT"])
            garminexport.backup.download(client, (args.activity, start_time),
                                         retryer,
                                         args.destination,
                                         export_formats=[args.format])
    except Exception as e:
        log.error("failed with exception: %s", e)
        raise
示例#10
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
示例#11
0
 def setUp(self):
     # object under test
     self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=1))
示例#12
0
 def setUp(self):
     # object under test
     self.strategy = ExponentialBackoffDelayStrategy(timedelta(seconds=1))
示例#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)