So normally, adding a Google Calendar to your iOS device is straightforward, functionality I’ve been using since the first iPod touch I bought. I say normally, because if you’re like me, you also have to deal with a Google Apps account managed by your company that uses federated identity. No apps will properly authenticate with my work account unless they use a browser-based login page, so that it can redirect to the company IDM server.
Also, normally, Google Calendar is excellent for sharing calendar information among different accounts. One of their policies is that they removed the capability for allowing subscriptions to your calendar. I’m not sure why, since it seems that I can make the calendar public anyway.
I could of course browse the calendar with the mobile Chrome or Safari, but it’s not forgiving to use a web calendar on a poor cellular connection. It also means no integration with apps like OmniFocus which is very useful part of my daily workflow. I also did not want to start having to invite my personal account for all of my work events.
The only other option I found was to export my calendar into an ics formatted file, something Google provides within the calendar settings page. I could then put that file somewhere accessible via my phone. However, this process would have to be:
Automated because out-of-date calendar information is a useless resource.
Accessible because I would be using the calendar on my mobile device.
Robust because I don’t want to spend my time working on this system in the future.
I came up with 2 scripts that I could run as scheduled tasks on my Mac at home. The first is a Groovy script that uses the Geb browser automation framework. The second is an Applescript that automates importing the ICS file into an iCloud calendar I have.
@Grapes([@Grab("org.codehaus.geb:geb-core:0.7.0"),@Grab("org.seleniumhq.selenium:selenium-firefox-driver:2.21.0"),@Grab("org.seleniumhq.selenium:selenium-chrome-driver:2.21.0"),@Grab("org.seleniumhq.selenium:selenium-support:2.21.0")])importgeb.Browserimportgeb.Configurationimportorg.openqa.selenium.firefox.FirefoxDriverimportorg.openqa.selenium.chrome.ChromeDriverimportorg.openqa.selenium.KeysgebConfig="""/* This is the Geb configuration file. See: http://www.gebish.org/manual/current/configuration.html*/import org.openqa.selenium.firefox.FirefoxDriverimport org.openqa.selenium.chrome.ChromeDriverimport org.openqa.selenium.remote.DesiredCapabilitiesreportsDir = "page-reports"driver = { driver = new FirefoxDriver()}waiting { timeout = 60 retryInterval = 0.5}environments { // run as “grails -Dgeb.env=chrome test-app” // See: http://code.google.com/p/selenium/wiki/ChromeDriver chrome { driver = { new ChromeDriver() } } // run as “grails -Dgeb.env=firefox test-app” // See: http://code.google.com/p/selenium/wiki/FirefoxDriver firefox { driver = { new FirefoxDriver() } }}"""rawGebConfig=newConfigSlurper().parse(gebConfig)configuration=newConfiguration(rawGebConfig)println"Starting ICS download script at ${new Date()}"download_google_ics_file()println"ICS download script finished at ${new Date()}"Browser.drive(configuration){}.quit()voiddownload_google_ics_file(){Browser.drive(configuration){go"http://gmail.com"// make sure we actually got to the pageasserttitle=="Gmail: Email from Google"$("#Email").value('my.email@domain.com')defloginLink=$("#signIn")loginLink.click()// check IDM titleasserttitle=='CrappyIDM(Login)'$("#IDToken1").value('myusername')$("#IDToken2").value(getPwdText())// click login link$("#IDToken2")<<Keys.ENTER// Goto calendar and get ICS exportgo"https://www.google.com/calendar?tab=mc"defdownloadLink="https://www.google.com/calendar/exporticalzip"// now get the bytesdefbytes=downloadBytes(downloadLink)// process byte input stream through a zip input streamdefbyteInputStream=newByteArrayInputStream(bytes)defzipInputStream=newjava.util.zip.ZipInputStream(byteInputStream)defzipEntry=zipInputStream.getNextEntry()assertzipEntry!=null// write zip entry to filedefoutputFile=newFile('/Users/cerwin/Desktop/work.ics')outputFile.withOutputStream{out->out<<zipInputStream}}}
setadd_eventsto"Add events"-- The title of the "Add Events" dialog on your system.setfile_pathto(path todesktopas Unicode text)&"work.ics"setcalendar_nameto"Work"tellapplication"Finder"activateopenfilefile_pathendtelltellapplication"System Events"tellapplicationprocess"iCal"setfrontmosttotruetellwindowadd_eventsrepeatuntil(itexists)delay0.2endrepeatclickpopupbutton1repeatuntil(menu1ofpopupbutton1exists)delay0.2endrepeatclickmenuitemcalendar_nameofmenu1ofpopupbutton1repeatwhile(menu1ofpopupbutton1exists)delay0.2endrepeatclickbutton"OK"endtelldelay2clickbutton"Remove Unsafe Alerts"offrontwindowendtellendtelltellapplication"Finder"deletefilefile_path-- moves it to the trashendtell
Thanks to posts on macscripter.net and a StackOverflow answer for how to do the window interaction with iCal. A couple of caveats for the applescript file, I made sure the calendar’s name I import into only exists under the ICLOUD group so that it doesn’t accidentally import into a local calendar. I also haven’t published the method through which I’ll automate this, because I haven’t decided whether to put it into an Automator workflow or just a simple cron job. For me it’s another example of using what is best suited for the job at hand. Geb is still the most useful browser automation framework I’ve found, and, well, Applescript is really the only way I know to directly interact with Mac applications.