Example #1
0
def init_db():
    global config
    
    parser = optparse.OptionParser(usage='%prog -c CONFIGFILE [OPTIONS] --last-ride=[NUM]',
                                   description="Set the last (most recent) sync'd ride ID from Garmin Connect.")
    
    _setup_parser_common(parser)
    #parser.add_option('--user-id', dest='userid', metavar='ID', type='int', 
    #                  help='The Garmin Connect userid.')
    
    parser.add_option('--last-ride', dest='last_ride', metavar='ID', type='int', 
                      help='The last (most recent) ride that has been synchronized.')
        
    (options, args) = parser.parse_args()
    
    if not options.config_file:
        parser.error("No config file specified")
        parser.print_usage()
        sys.exit(2)
    
    if not options.last_ride:
        parser.error("Must specify the --last-ride option.")
        parser.print_help()
        sys.exit(2)
    
    init_config(options.config_file)
    logging.config.fileConfig(options.config_file)
    
    if options.database:
        config.set('main', 'database_path', options.database)
    
    gc_username = config.get('main', 'gc.username')
    gc_password = config.get('main', 'gc.password')
    gc_client = ConnectClient(gc_username, gc_password)
    
    activity = gc_client.get_activity(options.last_ride)
    userid = activity['userId'].encode('latin1') # It's a unicode string in response
    
    with closing(shelve.open(config.get('main', 'database_path'), 'c')) as db:
        db[userid] = options.last_ride
        logging.info("Updated last activity id for user {0} to {1}".format(userid, db[userid]))
Example #2
0
def sync_rides():
    global config
    
    parser = optparse.OptionParser(usage='%prog [OPTIONS] CONFIGFILE')
    
    _setup_parser_common(parser)
    
    parser.add_option('--timeout', dest='timeout', metavar='SECS', type='int', 
                      help='Maximum time before bailing out.', default=60)
    
    (options, args) = parser.parse_args()
    
    if not options.config_file:
        parser.error("No config file specified")
        parser.print_usage()
        sys.exit(2)
    
    init_config(options.config_file)
    
    logging.config.fileConfig(options.config_file)
    
    if options.database:
        config.set('main', 'database_path', options.database)
    
    timeout = options.timeout
    
    gc_username = config.get('main', 'gc.username')
    gc_password = config.get('main', 'gc.password')
    
    strava_username = config.get('main', 'strava.username')
    strava_password = config.get('main', 'strava.password')
    
    gc_client = ConnectClient(gc_username, gc_password)
    strava_client = WebsiteClient(strava_username, strava_password)
    
    activities = gc_client.get_activities()
    
    if len(activities) == 0:
        raise RuntimeError("No activities returned from Garmin Connect.")
    
    # We just grab the first activity to get the athlete id for the purpose of tracking last activity sync 
    userid = activities[0]['activity']['userId'].encode('latin1') # The unicode string is really a number
    
    with closing(shelve.open(config.get('main', 'database_path'), 'c')) as db:
        
        last_sync = db.get(userid)
        if not last_sync:
            raise RuntimeError("Need to initialize database with a last-sync'd activity for userId={0}".format(userid))
        
        # We are relying on reverse chronological order of the activities here.
        candidates = []
        for a in activities:
            a_id = int(a['activity']['activityId'])
            if a_id == last_sync:
                break
            else:
                candidates.append(a['activity'])
        
        if candidates:
            
            logging.info("Found {0} activities that need to be sync'd.".format(len(candidates)))
            
            # First reverse it so we start with the oldest one (this makes it easier to update the last-sync'd record
            # along the way.
            
            candidates = list(reversed(candidates))
            
            upload_ids = []
            for c in candidates:
                a_id = c['activityId']
                
                #tmp_filename = '%s.tcx' % a_id  # TODO: Replace this with io.BytesIO()
                tcx_data = gc_client.download_activity(a_id, fmt='tcx')
                fp = BytesIO()
                fp.write(tcx_data)
                fp.seek(0)
                
                #with open(tmp_filename, 'w') as fp:
                #    fp.write(tcx_data)
                logging.info("Uploading activity {0}".format(a_id))
                upload_ids.extend(strava_client.upload(fp))
                
                # Update our last-sync db "row"
                db[userid] = int(a_id)
                db.sync()
                
                logging.debug("Updated last activity id for user {0} to {1}".format(userid, db[userid]))
        
            logging.info("{0} rides uploaded, last sync id for user {1} set to {2}".format(len(candidates), userid, db[userid]))
            logging.debug("Got upload ids: {0}".format(upload_ids))
            
            error_statuses = []
            success_statuses = []
            pending_ids = set(upload_ids)
            
            start_time = time.time()
            
            while len(pending_ids):
                for upload_id in list(pending_ids): # Make a copy for iteration since we modify it during iteration.
                    status= strava_client.check_upload_status(upload_id)
                    if status['workflow'] == 'Analyzing':
                        logging.debug("Upload still pending: {0}".format(status))
                    elif status.get('activity'): # aka workflow=Uploaded
                        url = 'http://app.strava.com/activities/{0}'.format(status['activity']['id'])
                        logging.info("Upload succeeded, acvitity URL: {0}".format(url))
                        success_statuses.append(status)
                        pending_ids.remove(upload_id)
                    else:
                        logging.error("Unhandled workflow; treating as error: {0}".format(status))
                        error_statuses.append(status)
                        pending_ids.remove(upload_id)
                        
                    # We don't want to flood strava
                    time.sleep(1.0)
                
                if time.time() - start_time > timeout:
                    logging.warning("Bailing out because timeout of {0} exceeded. (last status={1!r})".format(timeout, status))
                    break
                
            if success_statuses:
                logging.info("{0} rides processed. Visit http://app.strava.com/athlete/training/new to update ride settings.".format(len(success_statuses)))
            else:
                logging.warning("Processing complete, but no successfull ride uploads.")
            
        else:
            logging.debug("No activities need to be sync'd.")