Posts about iracing stats

iRacing Stats App Update

I've been able to get a bunch of work done on my stats app over the last week. Here's a dump from the change log:

  • sync process: improvements to the way the progress bar is updated

  • sync process: improved logging, display % completion of results collection

  • changes to hide / show timing when updating a tab to avoid flickering

  • fixed bug making previous / next buttons on all tabs not work

  • general tab: updated html layout to match updated GUI layout

  • corners per incident graphs now start at 0 like all others

  • fixed crash bug with stats button on all tabs

  • removed a bunch of debug code

  • general tab: series list entry for each car if MC

  • general tab: skip graphs with no content, reformatted general stats

  • fixed bug where All Oval/Road Series menu option displayed when not required

  • refactored messaging between sync thread and main GUI process

  • fixed bug with normal completion of sync

  • only update track and car lists if counts are diff

  • improved aborting sync

  • kill sync threads on close, disable sync button during sync, enable on complete

  • threadify sync process

  • warn user of things that can take a while

  • fixed installer not creating appdata folder

  • fixed main process left running on close of window, added exe version info

  • fixed how multiclass races are detected and fin pos in mc events

  • packaging complete; app installs into program files, user files are in appdata

  • fixed uploading to blogger; moved to new version of google api lib

  • db filename now uses custid

  • new build system, fixed avg fin pos graph for MC events

I'm now able to easily create a user friendly and well polished release version of the application, packaged up with a simple installer. I've done significant testing for fresh installs and brand new users. I'm pretty chuffed with how things are going. I feel like there isn't much more user testing required before I make a version available for wider use. One thing that definitely needs some work is the branding.

Google API's

I've been exporting info from my iRacing Stats application to html to do weekly and end of season updates on my iRacing blog. This process had been fairly manual; I'd manually upload the graph images to blogger, create a new blog post and paste in the exported html and edit the img tags to point to the uploaded images. Its not that it was too painful, but since it was a repetitive task I wanted to see if I could automate it.

So I dived into the Google API doco and pretty quickly worked out how to get my python application to post a new blog update. Getting the images uploaded was more painful, since blogger actually uses picasa to store images and the picasa API is terrible. I ended up using Google Drive to store the images, which means I needed to handle a few more steps than just uploading; namely changing the permissions to public viewing, uploading to a folder and retrieving the public url.

I've got it all working now, including prompting the user for only their blog's URL from which it app pulls the BlogID (rather than having to have the user go off and find it). All in all I'm quite proud of getting it all working. I just hope Google don't go changing their APIs in the near future.

I'm sure the knowledge I've gained here will be useful in many other projects.

Here's a bunch of links which I found helpful:

Here's a bit of code:

from oauth2client import file, client, tools  
from apiclient.discovery import build  
from apiclient.http import MediaFileUpload  
from httplib2 import Http

def blogger_post(outfile):  
 try:  
  html_file = open(outfile)  
  html_lines = html_file.read()  
  chop_start = html_lines.find('')  
  chop_end = html_lines.find('')  
  html_lines = html_lines[chop_start+6:chop_end]

  CLIENT_SECRET = 'client_secrets.json'  
  SCOPE = 'https://www.googleapis.com/auth/blogger'  
  store = file.Storage('storage_blogger.json')  
  creds = store.get()

  if not creds or creds.invalid:  
   flow = client.flow_from_clientsecrets(CLIENT_SECRET, SCOPE)  
   creds = tools.run(flow, store)

  service = build('blogger', 'v3', creds.authorize(Http()))  
  body = {  
   "kind": "blogger#post",  
   "id": cfg.config['Blogger']['blogid'],  
   "title": os.path.basename(os.path.splitext(outfile)[0]),  
   "content":html_lines  
   }

  request = service.posts().insert(blogId=cfg.config['Blogger']['blogid'], isDraft=True, body=body)  
  response = request.execute()  
  return response['url']  
 except:  
  return "Failed"

def blogger_img_upload(filename):  
 try:  
  CLIENT_SECRET = 'client_secrets.json'  
  SCOPE = ('https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file')  
  store = file.Storage('storage_drive.json')  
  creds = store.get()

  if not creds or creds.invalid:  
   flow = client.flow_from_clientsecrets(CLIENT_SECRET, SCOPE)  
   creds = tools.run(flow, store)

  service = build('drive', 'v2', creds.authorize(Http()))

  q = "title = 'iRacing Stats Graphs' and mimeType = 'application/vnd.google-apps.folder'"

  request = service.files().list(q=q)  
  response = request.execute()

  if len(response['items']) == 0:  
   body = {  
    "title": "iRacing Stats Graphs",  
    "mimeType": "application/vnd.google-apps.folder"  
    }

   request = service.files().insert(body=body)  
   response = request.execute()  
   folderId = response['id']  
  else:  
   folderId = response['items'][0]['id']

  pub.sendMessage('Uploading', graph=os.path.basename(os.path.splitext(filename)[0]))  
  body = {  
   "title": os.path.basename(os.path.splitext(filename)[0]),  
   }  
  body['parents'] = [{'id': folderId}]  
  media_body = MediaFileUpload(filename)

  request = service.files().insert(body=body, media_body=media_body)  
  response = request.execute()  
  fileId = response['id']

  body = {  
   "type": "anyone",  
   "role": "reader"  
   }  
  response = service.permissions().insert(fileId=fileId, body=body).execute()  
  response = service.files().get(fileId=fileId).execute()  
  return response['webContentLink'].split('&')[0]  
 except:  
  print("Upload of %s to blogger failed" % os.path.basename(os.path.splitext(filename)[0]))  
  return "Failed"

def blogger_config(url):  
 try:  
  CLIENT_SECRET = 'client_secrets.json'  
  SCOPE = 'https://www.googleapis.com/auth/blogger'  
  store = file.Storage('storage_blogger.json')  
  creds = store.get()

  if not creds or creds.invalid:  
   flow = client.flow_from_clientsecrets(CLIENT_SECRET, SCOPE)  
   creds = tools.run(flow, store)

  service = build('blogger', 'v3', creds.authorize(Http()))  
  response = service.blogs().getByUrl(url=url).execute()  
  cfg.write_blogid(response['id'])  
  return True  
 except:  
  pub.sendMessage('Alert', msg="Unable to find BlogID of: %s" % url, title="Blogger Config Failed")  
  return False