Sunday, August 04, 2013

Using AppleScript and/or Python to create a plaintext list of upcoming events from OS X Calendar.app

[This post starts with the general problem and some basic scripts -- but don't worry -- it ends with two definitive solutions.]

Yesterday I described several clumsy hacks to get a plaintext display of upcoming Calendar events from one or more Mac or Google Calendars. In today's post I look at what's possible with AppleScript [1] (or Python and AppScript) support -- though as of 8/4/13 I'm not done with it.

Specifically I want to get a plaintext list of events on one or more Google Calendars through AppleScript and Calendar.app when Calendar.app is configured to read Calendars from a Google share. Based on some articles I found, it looks possible:
I was able to retrieve the references and names of all my Google Calendars using 2005 code from MacTech

tell application "iCal"
set theCalendars to every calendar
end tell
and
tell application "iCal"
set theCalendarNames to title of every calendar
end tell

I was also able to view a calendar at a specified date and I found it used the selected calendars though not in my desired format.

This script listed events by event id for the current date and it seems easy to modify if I study some AppleScript date arithmetic ...

set {year:y, month:m, day:d} to current date
set str to (m as string) & " " & (d as string) & " " & (y as string)
set today to date str
set tomorrow to today + 60 * 60 * 24
tell application "iCal"
tell calendar "Lotus Notes"
set curr to every event whose start date is greater than or equal to today ¬
and start date is less than or equal to tomorrow
end tell
end tell

This one worked, but search can take a very long time...
tell application "iCal"
tell calendar "Domestic"
set theEventList to every event whose summary contains "Recycling"
end tell
set theEvent to first item of theEventList
return summary of theEvent
end tell

I'll have to leave this task for a bit, but I have a long family car drive coming up and I might be able to play with it on the way. I've also asked about this on MacScripter.net, where respondents have been known to solve problems very thoroughly.

Or I could just try Automator...Using Automator to create a plaintext list of upcoming events from OS X Calendar.app

- fn -

[1] SQL, AppleScript and COBOL all have one thing in common - they were designed for use by "non-programmers". Let that be a lesson.

See also
Update 8/5/13: Nigel Garvey, a moderator at the extraordinary and venerable MacScripter site, did a professional version of this script. It's working quite well for me, though it only works with one Calendar. It's easy to tweak the text result. (See post for his comments.)

-- Ask the user for the range of dates to be covered.on getDateRange()
   set today to (current date)
   set d1 to today's short date string
   set d2 to short date string of (today + 6 * days)
   
   set dateRange to text returned of (display dialog "Enter the required date range:" default answer d1 & " - " & d2)
   set dateRangeStart to date (text from word 1 to word 3 of dateRange)
   set dateRangeEnd to date (text from word -3 to word -1 of dateRange)
   set dateRangeEnd's time to days - 1 -- Sets the last date's time to 23:59:59, the last second of the range.   
   return {dateRangeStart, dateRangeEnd}
end getDateRange

-- Return the start dates and summaries which are in the given date range.on filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
   set {eventDatesInRange, eventSummariesInRange} to {{}, {}}
   repeat with i from 1 to (count theStartDates)
       set thisStartDate to item i of theStartDates
       if (not ((thisStartDate comes before dateRangeStart) or (thisStartDate comes after dateRangeEnd))) then
           set end of eventDatesInRange to thisStartDate
           set end of eventSummariesInRange to item i of theSummaries
       end if
   end repeat
   
   return {eventDatesInRange, eventSummariesInRange}
end filterToDateRange

-- Sort both the start-date and summary lists by start date.on sortByDate(eventDatesInRange, eventSummariesInRange)
   -- A sort-customisation object for sorting the summary list in parallel with the date list.   script custom
       property summaries : eventSummariesInRange
       
       on swap(i, j)
           tell item i of my summaries
               set item i of my summaries to item j of my summaries
               set item j of my summaries to it
           end tell
       end swap
   end script
   
   CustomBubbleSort(eventDatesInRange, 1, -1, {slave:custom})
end sortByDate

-- CustomBubbleSort from "A Dose of Sorts" by Nigel Garvey.-- The number of items to be sorted here is likely to be small.on CustomBubbleSort(theList, l, r, customiser)
   script o
       property comparer : me
       property slave : me
       property lst : theList
       
       on bsrt(l, r)
           set l2 to l + 1
           repeat with j from r to l2 by -1
               set a to item l of o's lst
               repeat with i from l2 to j
                   set b to item i of o's lst
                   if (comparer's isGreater(a, b)) then
                       set item (i - 1) of o's lst to b
                       set item i of o's lst to a
                       slave's swap(i - 1, i)
                   else
                       set a to b
                   end if
               end repeat
           end repeat
       end bsrt
       
       -- Default comparison and slave handlers for an ordinary sort.       on isGreater(a, b)
           (a > b)
       end isGreater
       
       on swap(a, b)
       end swap
   end script
   
   -- Process the input parameters.   set listLen to (count theList)
   if (listLen > 1) then
       -- Negative and/or transposed range indices.       if (l < 0) then set l to listLen + l + 1
       if (r < 0) then set r to listLen + r + 1
       if (l > r) then set {l, r} to {r, l}
       
       -- Supplied or default customisation scripts.       if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
       
       -- Do the sort.       o's bsrt(l, r)
   end if
   
   return -- nothing end CustomBubbleSort

-- Compose the text from the items in the start-date and summary lists.on composeText(eventDatesInRange, eventSummariesInRange)
   set txt to ""
   set gap to linefeed & linefeed
   
   repeat with i from 1 to (count eventDatesInRange)
       set txt to txt & (date string of item i of eventDatesInRange) & (linefeed & item i of eventSummariesInRange & gap)
   end repeat
   
   return text 1 thru -3 of txt
end composeText

on main()
   tell application "iCal" to set {theStartDates, theSummaries} to {start date, summary} of events of calendar "FL Family Calendar"
   
   set {dateRangeStart, dateRangeEnd} to getDateRange()
   set {eventDatesInRange, eventSummariesInRange} to filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
   sortByDate(eventDatesInRange, eventSummariesInRange)
   set txt to composeText(eventDatesInRange, eventSummariesInRange)
   
   tell application "TextEdit"
       make new document with properties {text:txt}
       activate
   end tell
end main

main()

Update 8/6/13: Clark Goble codes up comingevents.py, which uses PyObjC, AppScript, Osax and, of course, Python. It includes Parsedatetime so date entry can be free text and outputs plain text, HTML, Clipboard and probably sends a message to another dimension. I think at this point we've got scripted plaintext event publication covered. Be sure not to miss his links to related posts.

Spotlight Calendar searches in Lion and Mountain Lion - kind of weird

In Lion and Mountain Lion Apple gave this Spotlight search example:

Except I tried in Lion and Mountain Lion and that search string didn't do anything whereas iOS 6 Siri returns a list of meetings for 'tomorrow' (and comments first that I have "quite a lot of them, which is funny).
 
A partial fix  is to use the Events Keyword (see also: Mac 101: Use Spotlight for quick review of Calendar events)

Calendar events

kind:event

kind:events

Alas, when I tested this is Spotlight and Calendar search things got weird. I think because all of my Calendars are synchronized from Gmail/Google I exposed some Spotlight bugs. At first searching for Kind:event would only display "Birthdays" (From Facebook?), but then I added a single Event on my iCloud Calendar (no way to create a purely local Calendar any more?). That seemed to awaken Spotlight and soon kind:even generated a LOT of hits from ALL my Calendars (same as searching on the '.' (dot) operator - old trick post Agenda removal).
 
Then I tried date operators while searching within Calendar.app

kind:event date:today

That returned an event from over a year ago (7/5/2012). Ok, that's bizarre.
 
So then I tried a date range using spotlight as a guide

kind:event date:6/29/13-7/25/13

That returned the same person's Facebook birthday, but from 2013 and 2014.
 
Then I tried the same search string in Spotlight, and I got a funny mixture of Calendars back.
 
So then I filtered out all the calendars I didn't want and I showed that either the old '.' or the newer Kind:Event search in Calendar.app would return a 10.5 style event list. Still, i couldn't get a date operator to work in Calendar.app search though -- it just interpreted it as a string (so 8/5/2013 worked to show events on that date, but 8/5/13 didn't work). The OS X date:tomorrow documentation didn't work.
 
So I hit a wall on this project, but I figure its worth sharing my experience for anyone looking further. If nothing else the links may be useful, and it was interesting to learn why Lion tips on changing days displayed in week view no longer work.
 
Editorial Comment: In my work day on Win 7 and Outlook 2007, I routinely use Calendar searches like <after:today Fred>. Apple's inability to support this kind of basic operation in their Calendar.app isn't a good sign.
 
See also

Saturday, August 03, 2013

How to get a reasonable plaintext listing of calendar events using Google Calendar or OS X Calendar.app

I despise Outlook as much as any other geek, but, damn, there are things I miss when I'm on my Mac with Google Calendar and/or Mountain Lion Calendar.

To put it delicately, calendaring on the Mac is a flaming wad of misery. Google is only a little better. Simple tasks, like a plaintext agenda view one's Calendar, are mysterious or impossible on the modern Mac. (Classic had much better Calendar tools.)

Here's one way to get a plaintext agenda list from Google Calendar.

  1. From Calendar list go to Calendar Settings.
  2. In Calendar Details click on HTML to get web view of your calendar. Go to Agenda view and you can now select a range of dates and paste into your preferred text editor.

For extra points, you can use the top-secret multiple-calendar web sharing feature and get events from more than one calendar.

Update 8/4/13

In Calendar.app Day view you can:

  • Select Calendar
  • Click mini-calendar for start date
  • Click on an event at start of that day
  • Click mini-calendar for end date
  • Shift-click on event at end
  • Paste into text editor

The result is pretty much the same as using Google Calendar.

Or you can search on either '.' or "Kind:Event" in Calendar and select a range from the search results. The '.' search works in Google Calendar too, though in 10/2020 it gCal mysteriously omits some dates from some feeds.

Update 8/4/13b:

iCalBuddy is a terminal app that can "get lists of events and tasks/to-do's from the OS X calendar database"; results can be displayed on the desktop. It was very recently updated, so should be fine on Mountain Lion.

Thursday, August 01, 2013

Kobo Glo: Mini-Review

After much deliberation, and on the advice of @la and other appnetizens, I bought a Kobo Glo from, oddly enough, Common Good Books.

I ended up with the Kobo Glo largely because I can't quite persuade myself to buy the current generation of iPads, my experience with the original Nexus 7 was disappointing, and I assume the Nexus 7 Retina will have awful battery life. At the same time I wanted an ePUB reader for compatibility with iBooks.app and Google Play, and because Adobe's DRM can be removed. I also wanted simplicity of mounting/charging and transferring files via USB, and the expansion of a (micro) SD card option.

So I was left with buying either an eInk Nook or Kobo. I disliked the Nook because the one I tried was misconfigured to do an eInk full screen refresh every page; I was left with an unfair impression. It helped that while is US presence is slight the Kobo seems to have a good international market -- and Kobo's marketing angle is 'freedom' (though I think they use the same Adobe DRM as Google Play and Nook).

I paid $130 for the Glo plus tax. No bargains this time around; it seems to cost the same online. It comes in a compact box with a slender setup guide and a micro-USB cable (no charger) and no slip case or cover.

My initial experience was not good. I connected it to my Mac and ran the setup using Kobo Desktop, which seems to be a well behaved and digitally signed Mac app. After initialization an update started, but it behaved a bit oddly. I had to pull the USB cable to get the update to commence, but then I feared power loss and reinserted the cable. Although it seemed to behave and register normally, the Kobo didn't recognize any of the files I put on it via USB desktop mounting. It also, I later realized, was not showing the proper 'connect' dialog on USB cable connection.

So next I did a factory refresh, and this time instead of a desktop setup I did a WiFi connection setup. The device updated again, but this time it took far longer for the update to continue. It seemed to be stuck, cycling between progress indicators for minutes -- but I ignored it and, somewhat to my surprise, it eventually restarted. I completed my registration using the painful onscreen keyboard.

After the refresh and registration I was able to drag files to the Kobo and they were processed on device. I'd forgotten how compact ePubs are, and how big 2GB is. At 1MB or so per ePUB it will take a long time to use up my remaining 1.35 GB -- even if I don't use the micro-SD card slot.

My initial impressions are guarded; I think the iPad Mini Retina will be a better reading experience -- but it will cost far more and it's not yet available. My iPhone 5 has a better screen, but it's small for reading technical books. The mandatory eInk full page flicker, which occurs at least every 6 pages, is annoying.

On the other hand, the battery life is at least weeks long. It is a boring black and white device, so I don't have to worry about the kids fighting for a turn. When I buy my iPad Mini it will still be a useful device for someone else.

For the moment I carry it in a cloth pouch, I'll see how the plastic screen holds up.

See also:

Sunday, July 28, 2013

More ePUB (eBook) management options: Google drive + iTunes Symlink, upload to Google Play (Corrected)

I was a big fan of Google 1.0; Google 2.0 is bit of a rabid dog. Still, it has its uses.

I've previously noted that the Google Play Store is the best eBookStore for iBooks fans. Since then I've learned of two more Google eBook advantages:

  • I can put all my eBooks into Google Drive, then create Symlinks [2] that sit in iTunes. iTunes properly syncs my eBooks to my iPhone. Since the eBooks are in Google Drive, I can access them from work, etc.
  • I can upload all my non-DRMd ePUB files to Google Play. That means I can view them on any Mac using Chrome (or Safari) and the Google Play site. Even my ancient G5 iMac. No need to wait for Mavericks.

The second of these ought to embarrass Apple.

[1] I've used BookReader for OS X, but it doesn't render all books perfectly. Adobe Digital Editions does a bit better, except when it crashes on startup (Delete the Adobe folder in /Documents).

[2] We really need a version of "SymbolicLinker" for the App Store. The old version doesn't work with Gatekeeper; I think there's a ML bug with 'whitelist' services. I'm back to using 'ln -s'.

Update 8/7/2013: Three corrections.

  • There's no need to go crazy and create Symlinks. Turns out a regular old alias works at least as well if not better. So I put my ePubs in Google Drive so they can be accessed from any of my desktop machines, drag a Favorite on top of the Library Books icon in iTunes 10 (I'm resisting iTunes 11), and it's magically on my iPhone too. From Google Drive/Library I drag to my Kobo. I also put many on Google Play, though that's optional (and eventually I expect Google to unify Drive and Play).
  • If you want to keep only one copy of a file (what's 2-3MB nowadays?) you need to turn off "Copy files to iTunes Media when adding to Library".
  • If you do want a version of SymbolicLinker for OS X, just use AppleScript. jonn8n posted this script in 2006 and it works well on Mountain Lion. Since it's not signed code when compiled I had to choose 'Open' once from the context menu so it would work thereafter.

Update 8/7/2013b: More corrections

  • Even though I have "Copy files to iTunes Media when adding to Library" unchecked, iTunes 10 resolves the alias and copies the books anyway. Sigh. Looks like a bug, no idea if it's fixed in 11.

Update 8/9/13: I got iTunes 10 to use Aliases pointing to the file on Google Drive. Trick is to first create Alias on desktop, drag those to iTunes, then delete them from desktop.

Friday, July 26, 2013

Review: Maxell AirStash WiFi media server: iTunes movies to iPhones for long car trips

The Maxell AirStash Wireless 8GB Flash Drive [1] is designed to stream movies to kid's handheld devices on long car rides [3]. It probably has other uses, such as backing up photos or serving music, but I think movies is what everyone buys it for. That's why we bought it -- so we could stream iTunes purchased FairPlay DRMd .mv4 movies and television episodes to our 3 kids devices.

So, did it work?

Yes, but, unfortunately, not very well. I was able to stream The Avengers (iTunes SD) to two devices fairly well, but once I added a third device watching the same movie the stream became unreliable. The device is supposed to be able to stream 3 movies at once, but perhaps they mean 3 different movies. Or, more likely, it can't really stream 3 SD resolution movies at once. I suspect it can stream 1 HD movie, 2 SD movies, or 3 movies ripped to iPhone screen resolution [4].

Since we have 3 kids, this isn't a great solution. We might still consider using it, but the SanDisk Connect 64GB Wireless Media Drive Streaming is supposed to be available in the next 1-2 weeks. It is less expensive, has much higher starting capacity [2], and claims to stream a movie to up to 5 clients (so probably 3). We've processed an Amazon return on the AirStash, but we may still keep it if we can't get the SanDisk in time for our trip.

Beyond the disappointing, but not surprising, performance issues I'll quickly list a few observations:
  • It's a bit bigger than it looks in the Amazon photos, it can fill a good portion of an adult hand. It fit the chargers in our van, but for some USB chargers you'll need a USB extension cable.
  • Although it has an internal battery, it's clearly designed to run off a car USB charger. The manual suggests leaving it in the charger.
  • It's a pain to turn on/off. I'd kill for a simple switch instead of these quirky push buttons that require a manual to use. The indicator light is worthless when the device is charging, I found I had to unplug it to know it's power state.
  • It takes about 15 seconds to boot up, so be patient waiting for WiFi to appear.
  • The AirStash is controlled by the (WebDav client) iOS AirStash+ app configure settings. You can use this to rename it and play media. FairPlay DRMd media is passed to Safari [5], Safari in turn passes it to iOS QuickTime player (videos.app). As long as the DRM on the movie matches the iTunes account on the iOS device then the movie will play. There are no chapter controls, you can often move through the movie timeline but not always. For a single user movies play well.
  • I was able to lock up iOS AirStash+ fairly easily and had to kill and restart it several times.
  • On initial use I was told a firmware update was available. The installation directions were poorly worded, and, again, it's hard to see the power/firmware update status light when the device is charging. It worked after some fiddly.
  • I don't think you can stream a movie when it's connected to a computer. I'm not positive, but it didn't seem to work in my testing. It's fine when connected to a power supply.
  • You can put movies directly on the FAT32 formatted SD card or plug in the AirStash and it will mount. You can use Folders to organize your media.
  • It comes with a plastic cap that doesn't fit on the end of the AirStash. So it will get lost pretty quickly.
  • When I typed 'airstash.net' into my desktop Safari while connected to the WiFi I did not get anything back. It's supposed to show the file system, can't say why that didn't work. I didn't pursue further since I won't use the device that way.
  • I didn't test how it behaves under prolonged load, but I'd be sure to keep this near a cooling vent in the car. Heat dissipation must be a challenge and prolonged overheating destroys devices like this.
  • I was able to use a 64GB SONY SD Card with the AirStash, but I had to reformat it to FAT32 on my Mac. [6]
See also:
- fn -
[1] The AirStash ships with an 8GB SD card, which is really only practical for testing. You can buy a 16GB version, but obviously that's a waste of money. Most will buy the 8GB AirStash then get a 64GB to 2TB SDXC card - but see [6].
[2] 64GB internal, plus external slot available.
[3] It is perhaps not obvious why one would want this. It's a pain to put movies on/off iPhones when traveling, this way we could take our video library with us.
[4] I suspect the ideal use case would be someone who (illegally) rents Amazon DVDs and rips them to iPhone resolution, building a compact library that is streamable with a relatively low powered device.
[5] Safari is disabled on our kids phones as a minimally effective parental control measure.
[6] SD cards above 32GB come exFAT formatted, and the AirStash won't read Microsoft's patented exFAT. Which is how I learned that whereas Windows machines won't format FAT32 above 32GB, Mountain Lion will happily do at least 64GB and the (Linux powered?) AirStash will read it.

Update: My Amazon review (A minimally edited copy of this one). I'm going to test streaming from my MacBook Air.

Update 7/27/13: Ok, forget the SanDisk: "SanDisk’s drives don’t work with video content you buy from Apple’s iTunes Store at all". So they didn't figure out the Safari workaround AirStash uses. Guess we'll try to make the AirStash work after all.

Update 8/26/2013: We used the AirStash daily for two weeks and it worked quite well. Note we ONLY have SD movies and a lot of what the kids stream is animated and uses much less processing and bandwidth. Also, it was in practice rare for all 3 to stream video at the same time. So, despite failing my 3 stream test, it worked in practice. Kids had no trouble with the necessarily awkward viewing via Safari.

The biggest problem is that the AirStash, with its protruding USB and big body, is an accident waiting to happen. One child stepped on it and partly broke the plastic body. It continued to work, only insert with a flexible USB extension cable and making a protective container from a plastic "tupperware" dish.

The biggest annoyance is the on/off switch switch. I'd love a simple on/off slider. It was often hard to tell if the device was running.

Update 3/27/2014: You can use the AirStash to move files between iOS devices - including ePub files. It shows up as an option in the iOS share list in many apps; from AirStash.app use the copy function to move to AirStash device. Firmware updates are scary; if you plug it into a Mac you need to Eject before update will commence. Really, when doing firmware updates, plug it into a plain charger.