Merge pull request #1 from dlenski/master
new options, and make incremental_backup.py more intelligent about finding activities
This commit is contained in:
		
						commit
						71c95330ba
					
				@ -42,6 +42,14 @@ if __name__ == "__main__":
 | 
				
			|||||||
        "--log-level", metavar="LEVEL", type=str,
 | 
					        "--log-level", metavar="LEVEL", type=str,
 | 
				
			||||||
        help=("Desired log output level (DEBUG, INFO, WARNING, ERROR). "
 | 
					        help=("Desired log output level (DEBUG, INFO, WARNING, ERROR). "
 | 
				
			||||||
              "Default: INFO."), default="INFO")
 | 
					              "Default: INFO."), default="INFO")
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-f", "--format", choices=garminexport.util.export_formats,
 | 
				
			||||||
 | 
					        default=None, action='append',
 | 
				
			||||||
 | 
					        help=("Desired output formats ("+', '.join(garminexport.util.export_formats)+"). "
 | 
				
			||||||
 | 
					              "Default: ALL."))
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-E", "--ignore-errors", action='store_true',
 | 
				
			||||||
 | 
					        help="Ignore errors and keep going. Default: FALSE")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    args = parser.parse_args()
 | 
					    args = parser.parse_args()
 | 
				
			||||||
    if not args.log_level in LOG_LEVELS:
 | 
					    if not args.log_level in LOG_LEVELS:
 | 
				
			||||||
@ -57,14 +65,18 @@ if __name__ == "__main__":
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        with GarminClient(args.username, args.password) as client:
 | 
					        with GarminClient(args.username, args.password) as client:
 | 
				
			||||||
            log.info("fetching activities for {} ...".format(args.username))
 | 
					            log.info("fetching activities for {} ...".format(args.username))
 | 
				
			||||||
            activity_ids = client.list_activity_ids()
 | 
					            all_activities = client.list_activities()
 | 
				
			||||||
            for index, id in enumerate(activity_ids):
 | 
					            for index, (id, start) in enumerate(activity_ids):
 | 
				
			||||||
                log.info("processing activity {} ({} out of {}) ...".format(
 | 
					                log.info("processing activity {} from {} ({} out of {}) ...".format(
 | 
				
			||||||
                    id, index+1, len(activity_ids)))
 | 
					                    id, start, index+1, len(activity_ids)))
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
                    garminexport.util.export_activity(
 | 
					                    garminexport.util.export_activity(
 | 
				
			||||||
                    client, id, args.destination)
 | 
					                        client, id, args.backup_dir, args.format)
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    log.error(u"failed with exception: %s", e)
 | 
				
			||||||
 | 
					                    if not args.ignore_errors:
 | 
				
			||||||
 | 
					                        raise
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        exc_type, exc_value, exc_traceback = sys.exc_info()
 | 
					        exc_type, exc_value, exc_traceback = sys.exc_info()
 | 
				
			||||||
        log.error(u"failed with exception: %s", e)
 | 
					        log.error(u"failed with exception: %s", e)
 | 
				
			||||||
        raise
 | 
					        raise
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ import requests
 | 
				
			|||||||
from StringIO import StringIO
 | 
					from StringIO import StringIO
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import zipfile
 | 
					import zipfile
 | 
				
			||||||
 | 
					import dateutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Note: For more detailed information about the API services
 | 
					# Note: For more detailed information about the API services
 | 
				
			||||||
@ -147,25 +148,26 @@ class GarminClient(object):
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    @require_session
 | 
					    @require_session
 | 
				
			||||||
    def list_activity_ids(self):
 | 
					    def list_activities(self):
 | 
				
			||||||
        """Return all activity ids stored by the logged in user.
 | 
					        """Return all activity ids stored by the logged in user, along
 | 
				
			||||||
 | 
					        with their starting timestamps.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :returns: The full list of activity identifiers.
 | 
					        :returns: The full list of activity identifiers.
 | 
				
			||||||
        :rtype: list of str
 | 
					        :rtype: tuples of (int, datetime)
 | 
				
			||||||
        """        
 | 
					        """        
 | 
				
			||||||
        ids = []
 | 
					        ids = []
 | 
				
			||||||
        batch_size = 100
 | 
					        batch_size = 100
 | 
				
			||||||
        # fetch in batches since the API doesn't allow more than a certain
 | 
					        # fetch in batches since the API doesn't allow more than a certain
 | 
				
			||||||
        # number of activities to be retrieved on every invocation
 | 
					        # number of activities to be retrieved on every invocation
 | 
				
			||||||
        for start_index in xrange(0, sys.maxint, batch_size):
 | 
					        for start_index in xrange(0, sys.maxint, batch_size):
 | 
				
			||||||
            next_batch = self._fetch_activity_ids(start_index, batch_size)
 | 
					            next_batch = self._fetch_activity_ids_and_ts(start_index, batch_size)
 | 
				
			||||||
            if not next_batch:
 | 
					            if not next_batch:
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
            ids.extend(next_batch)
 | 
					            ids.extend(next_batch)
 | 
				
			||||||
        return ids
 | 
					        return ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @require_session
 | 
					    @require_session
 | 
				
			||||||
    def _fetch_activity_ids(self, start_index, max_limit=100):
 | 
					    def _fetch_activity_ids_and_ts(self, start_index, max_limit=100):
 | 
				
			||||||
        """Return a sequence of activity ids starting at a given index,
 | 
					        """Return a sequence of activity ids starting at a given index,
 | 
				
			||||||
        with index 0 being the user's most recently registered activity.
 | 
					        with index 0 being the user's most recently registered activity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -193,7 +195,10 @@ class GarminClient(object):
 | 
				
			|||||||
        if not "activities" in results:
 | 
					        if not "activities" in results:
 | 
				
			||||||
            # index out of bounds or empty account
 | 
					            # index out of bounds or empty account
 | 
				
			||||||
            return []
 | 
					            return []
 | 
				
			||||||
        entries = [int(entry["activity"]["activityId"]) for entry in results["activities"]]
 | 
					
 | 
				
			||||||
 | 
					        entries = [ (int(entry["activity"]["activityId"]),
 | 
				
			||||||
 | 
					                     dateutil.parser.parse(entry["activity"]["activitySummary"]["BeginTimestamp"]["value"]))
 | 
				
			||||||
 | 
					                    for entry in results["activities"] ]
 | 
				
			||||||
        log.debug("got {} activities.".format(len(entries)))
 | 
					        log.debug("got {} activities.".format(len(entries)))
 | 
				
			||||||
        return entries
 | 
					        return entries
 | 
				
			||||||
               
 | 
					               
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,7 @@ def export_activity(client, activity_id, destination,
 | 
				
			|||||||
    :type formats: list of str
 | 
					    :type formats: list of str
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if formats is None:
 | 
					    if formats is None:
 | 
				
			||||||
        formats = ['json_summary', 'json_details', 'gpx', 'tcx', 'fit']
 | 
					        formats = export_formats
 | 
				
			||||||
    activity_summary = client.get_activity_summary(activity_id)
 | 
					    activity_summary = client.get_activity_summary(activity_id)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # prefix saved activity files with timestamp and activity id
 | 
					    # prefix saved activity files with timestamp and activity id
 | 
				
			||||||
 | 
				
			|||||||
@ -27,23 +27,23 @@ LOG_LEVELS = {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
"""Command-line (string-based) log-level mapping to logging module levels."""
 | 
					"""Command-line (string-based) log-level mapping to logging module levels."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_backed_up_ids(backup_dir):
 | 
					def get_backed_up(activities, backup_dir, formats):
 | 
				
			||||||
    """Return all activitiy ids that have been backed up in the
 | 
					    """Return all activity (id, ts) pairs that have been backed up in the
 | 
				
			||||||
    given backup directory.
 | 
					    given backup directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :rtype: list of int
 | 
					    :rtype: list of int
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # backed up activities follow this pattern: <ISO8601>_<id>_summary.json
 | 
					    # backed up activities follow this pattern: <ISO8601>_<id>_summary.json
 | 
				
			||||||
    activity_file_pattern = r'[\d:T\+\-]+_([0-9]+)_summary\.json'
 | 
					    activity_file_pattern = r'[\d:T\+\-]+_([0-9]+).tcx'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    backed_up_ids = []
 | 
					    format_suffix = dict(json_summary="_summary.json", json_details="_details.json", gpx=".gpx", tcx=".tcx", fit=".fit")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    backed_up = set()
 | 
				
			||||||
    dir_entries = os.listdir(backup_dir)
 | 
					    dir_entries = os.listdir(backup_dir)
 | 
				
			||||||
    for entry in dir_entries:
 | 
					    for id, start in activities:
 | 
				
			||||||
        activity_match = re.search(activity_file_pattern, entry)
 | 
					        if all( "{}_{}{}".format(start.isoformat(), id, format_suffix[f]) in dir_entries for f in formats):
 | 
				
			||||||
        if activity_match:
 | 
					            backed_up.add((id, start))
 | 
				
			||||||
            backed_up_id = int(activity_match.group(1))
 | 
					    return backed_up
 | 
				
			||||||
            backed_up_ids.append(backed_up_id)
 | 
					 | 
				
			||||||
    return backed_up_ids           
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
@ -67,6 +67,14 @@ if __name__ == "__main__":
 | 
				
			|||||||
        "--log-level", metavar="LEVEL", type=str,
 | 
					        "--log-level", metavar="LEVEL", type=str,
 | 
				
			||||||
        help=("Desired log output level (DEBUG, INFO, WARNING, ERROR). "
 | 
					        help=("Desired log output level (DEBUG, INFO, WARNING, ERROR). "
 | 
				
			||||||
              "Default: INFO."), default="INFO")
 | 
					              "Default: INFO."), default="INFO")
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-f", "--format", choices=garminexport.util.export_formats,
 | 
				
			||||||
 | 
					        default=None, action='append',
 | 
				
			||||||
 | 
					        help=("Desired output formats ("+', '.join(garminexport.util.export_formats)+"). "
 | 
				
			||||||
 | 
					              "Default: ALL."))
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-E", "--ignore-errors", action='store_true',
 | 
				
			||||||
 | 
					        help="Ignore errors and keep going. Default: FALSE")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    args = parser.parse_args()
 | 
					    args = parser.parse_args()
 | 
				
			||||||
    if not args.log_level in LOG_LEVELS:
 | 
					    if not args.log_level in LOG_LEVELS:
 | 
				
			||||||
@ -81,28 +89,32 @@ if __name__ == "__main__":
 | 
				
			|||||||
            args.password = getpass.getpass("Enter password: ")
 | 
					            args.password = getpass.getpass("Enter password: ")
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        with GarminClient(args.username, args.password) as client:
 | 
					        with GarminClient(args.username, args.password) as client:
 | 
				
			||||||
            # already backed up activities (stored in backup-dir)
 | 
					            # get all activity ids and timestamps from Garmin account
 | 
				
			||||||
            backed_up_activities = set(get_backed_up_ids(args.backup_dir))
 | 
					 | 
				
			||||||
            log.info("{} contains {} backed up activities.".format(
 | 
					 | 
				
			||||||
                args.backup_dir, len(backed_up_activities)))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # get all activity ids from Garmin account
 | 
					 | 
				
			||||||
            log.info("retrieving activities for {} ...".format(args.username))
 | 
					            log.info("retrieving activities for {} ...".format(args.username))
 | 
				
			||||||
            all_activities = set(client.list_activity_ids())
 | 
					            all_activities = set(client.list_activities())
 | 
				
			||||||
            log.info("account has a total of {} activities.".format(
 | 
					            log.info("account has a total of {} activities.".format(
 | 
				
			||||||
                len(all_activities)))
 | 
					                len(all_activities)))
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					            # get already backed up activities (stored in backup-dir)
 | 
				
			||||||
 | 
					            backed_up_activities = get_backed_up(all_activities, args.backup_dir, args.format)
 | 
				
			||||||
 | 
					            log.info("{} contains {} backed up activities.".format(
 | 
				
			||||||
 | 
					                args.backup_dir, len(backed_up_activities)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            missing_activities = all_activities - backed_up_activities
 | 
					            missing_activities = all_activities - backed_up_activities
 | 
				
			||||||
            log.info("activities that haven't been backed up: {}".format(
 | 
					            log.info("activities that haven't been backed up: {}".format(
 | 
				
			||||||
                len(missing_activities)))
 | 
					                len(missing_activities)))
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            for index, id in enumerate(missing_activities):
 | 
					            for index, (id, start) in enumerate(missing_activities):
 | 
				
			||||||
                log.info("backing up activity {} ({} out of {}) ...".format(
 | 
					                log.info("backing up activity {} from {} ({} out of {}) ...".format(
 | 
				
			||||||
                    id, index+1, len(missing_activities)))
 | 
					                    id, start, index+1, len(missing_activities)))
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
                    garminexport.util.export_activity(
 | 
					                    garminexport.util.export_activity(
 | 
				
			||||||
                    client, id, args.backup_dir)
 | 
					                        client, id, args.backup_dir, args.format)
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    log.error(u"failed with exception: %s", e)
 | 
				
			||||||
 | 
					                    if not args.ignore_errors:
 | 
				
			||||||
 | 
					                        raise
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        exc_type, exc_value, exc_traceback = sys.exc_info()
 | 
					        exc_type, exc_value, exc_traceback = sys.exc_info()
 | 
				
			||||||
        log.error(u"failed with exception: %s", e)
 | 
					        log.error(u"failed with exception: %s", e)
 | 
				
			||||||
        raise
 | 
					        raise
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -30,11 +30,11 @@ if __name__ == "__main__":
 | 
				
			|||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with GarminClient(args.username, args.password) as client:
 | 
					        with GarminClient(args.username, args.password) as client:
 | 
				
			||||||
            log.info("activities:")
 | 
					            log.info("activities:")
 | 
				
			||||||
            activity_ids = client.list_activity_ids()
 | 
					            activity_ids = client.list_activities()
 | 
				
			||||||
            log.info("num ids: {}".format(len(activity_ids)))
 | 
					            log.info("num ids: {}".format(len(activity_ids)))
 | 
				
			||||||
            log.info(activity_ids)
 | 
					            log.info(activity_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            latest_activity = activity_ids[0]
 | 
					            latest_activity, latest_activity_start = activity_ids[0]
 | 
				
			||||||
            activity = client.get_activity_summary(latest_activity)
 | 
					            activity = client.get_activity_summary(latest_activity)
 | 
				
			||||||
            log.info(u"activity id: %s", activity["activity"]["activityId"])
 | 
					            log.info(u"activity id: %s", activity["activity"]["activityId"])
 | 
				
			||||||
            log.info(u"activity name: '%s'", activity["activity"]["activityName"])
 | 
					            log.info(u"activity name: '%s'", activity["activity"]["activityName"])
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user