I've recently seen some interesting videos about using Automator to do more stuff, so I thought I'd give it another try. (See my first post about Automator for some background.)
Turns out that you can pass multiple variables into an action, if you are very, very careful. In general it's easy - if you use two "Get Value of Variable" actions chained together you get an array with the two arguments. Unless you don't. Going back to our example I need to provide two arguments: one is the URL of an image and one is a text string describing the map. The Automator script that I currently use saves the filename into a text file, then asks for the description which it saves into a separate text file. Then it fires off a Ruby script that processes the filename into the URL for the image and makes the necessary changes to the web server. It works, but it's a bit slow and it looks ridiculous.
OK, so you're with me so far right? We have a file that the user selected (via a 'Ask for Finder Items' action), and a description that the user provided (via a 'Ask for Text' action). So let's store each of those in a variable and get them back to back. It works! We have two arguments in the data stream. We can pass that to a 'Run Shell Script' action and $1 will be our filename, and $2 will be the description. I work with that for a bit and it's all great, so I go ahead and start plugging those changes into the "real" script. Except it blows up when I try to retrieve the description variable.
After a lot of messing around I realized the types of the variables is important. The 'Ask for Finder Items' gives you back an file alias. (If you inspect the result it will say 'Alias' in blue and then the filename.) If you manipulate that (say by a 'Rename Finder Items' action) it changes from an alias to the actual file. If you get an alias variable and then a text variable you will be fine. If you get a file then attempt to get a text variable the second get fails. There's a fix for this. You can run a 'Store Disk Item References' action and it will turn the file back into an alias. That seems wonky, but sure whatever.
Next problem. My action *MOVED* the image file from my local drive to the server. So the original file doesn't actually exist by the time you get the variables together. This will also fail, and cause a screwup, even if you have an alias. After some thought I decided to copy the file and then delete the file on my local drive later. Turns out you can delete the "alias" and there's no error, but the original file is there. You have to do the opposite of that 'Store Disk Item References'. Get the variable, run a 'Retrieve Disk Item References', THEN run the 'Move Finder Items to Trash'. Goofy, but I can hang with it.
OK, so now we're set. I've got a script action that is getting the two arguments, I can write the necessary sed mojo (sed is a UNIX command that lets you manipulate text) to convert the filename over to the URL and we're cooking with gas. It all works! Fantastic! Now, there are a couple of actions where Automator seems really slow: both of those Disk Item references take a while and I'm now using Preview to convert the RAW file from my camera to a JPG (which means I don't have to change the filetype on my camera every week.) The convert is instant but Automator takes several seconds before moving to the next step. I wonder if making it into a stand-alone "application" would help? I try that. The application doesn't work. I open the workflow (the one that just worked mind you) and run it. It crashes. After a lot of playing around I confirm what it looked like originally. I can take a running workflow, add a new "Run Shell script" command and paste in the commands I want, and that workflow will run. Save it and run it again and it still runs. Close and reopen Automator and the Run Shell Script action now errors out!
I have no idea what that's about. When I last complained about Automator I felt like I didn't really understand how it worked. Now, after several hours I understand how it's supposed to work, and it just plain doesn't all the times. The distinction between a file and an alias to a file is subtle and it's not documented. The automator documentation calls both of them a Files/Folders object. The 'Rename Finder Items' action claims its input is a 'Files/Folders' and its result is a 'Files/Folders'. Which is true enough, but the change from an alias to a real file changes what you can do with variables later. This second problem with the external script command not working the same on a file load as it does when the command is created is just bullshit.
So, I really need to get rid of Automator. It has a lot of promise, and if I was just automating something simpler it would be OK. But in a way I preferred the way I thought it worked (where it seemed very limited but it worked) to what it actually does (where some things don't work right at all and there are undocumented types that you need to care very much about.)
Read moreOmniFocus, WebDAV, and OS X
Long time blog readers will know that I'm an enthusiastic user for OmniFocus. My biggest outstanding issue with OmniFocus was that syncing the iPhone would take an excruciatingly long time. Lately it had gotten so bad that every couple of weeks or so the phone would power off while still syncing and eventually I'd have to delete the app and data from the phone and start all over again.
I've been syncing from MobileMe's iDisk and lately I've been noticing that iDisk was randomly really slow. I decided today to try switching OmniFocus from iDisk to a WebDAV server. I found a page with good instructions on setting up WebDAV on OS X. If you go do to do this please note two things: I'm able to use a self-signed web certificate just fine, and you should look at comment 19 about using digest authorization. I did both of these, so now OmniFocus (both the computer and phone clients) is using SSL and an encrypted password, so that's pretty nice. And the WebDAV server is inside the LAN, so syncs at hope are pretty much instant.
I've only been running this for a few hours so far but it appears to work much faster than using iDisk.
Read moreI don't like Automator
(Yes, D&D players this is ongoing comedy from the attempt to update Gametable. Quiet!)
I've recently being using Automator and I have to say, I don't care for it. It works pretty good until you hit some sort of hard limit. Here's the part it does well: I can run the action, browse files and select an image. Automator has an image editor (I use Pixelmator) resize the image, do some quick & dirty auto-leveling, resize the image to a suitable-for-the-web size, and then upload it to online storage. I'll admit all this is pretty fantastic, although I suspect I could do it with Applescript.
The problem is after that I have to use a web page, enter the filename of the image and enter a text string description. If I screw that up, even up to the point of typing '.jpg' instead of '.JPG' then the web software screws up. It can derail momentum of our D&D combat. The answer is obvious: the Automator action needs to update the web application as well. It's pretty easy for me to write a new bit of code so there's a URL that takes the information - all I need to do is construct a URL.
Oh.
You see, Automator has variables, but it doesn't have anything like *operations*. And you basically only have one argument passing from one Automator action to another. To have *TWO* arguments you have to save one into a text file and pull it back when you need it. If I have two arguments "foo" and "bar" and I want to create a url that looks like http://www.foo.com/special/url/?&name=foo&description=bar, well that's not a very Automator sort of thing.
I messed with it for a while and got as far as getting Automator to spit out a single text string that looks like (in C terms) "foo\nbar" (in English that's foo, (carriage return) bar.)
I finally decided to punt: I'm going to write a Ruby script that grabs that arguments and builds and sends the URL. Long-term I think I'll see if i can write an Applescript that presents the UI and does all the work. If not I could throw it together pretty quickly as a real Mac application, but c'mon.
Read moreCocoa collections, an advisory note
Now that I'm doing actual useful iPhone development work I'm running into a few… shall we call them quirks? Talking about the iPhone specific stuff possibly needs to be [redacted] because of what's known on Twitter as the FUCKING NDA, but I think if something is also part of the OS X libraries then it is fair game.
I had cause in my recent application to use a NSDictionary (well a NSMutableDictionary but let's not quibble, shall we?). A NSDictionary is quite straightforward, it stores key-value pairs. Imagine that you have a Person class and you have some other data (not part of the Person directly) that you want to keep on a per-person basis. You might well decide to use a NSDictionary and store (as a value) a NSArray for each Person (as a key). That way you can quickly retrieve the data for any Person. Now the documentation says pretty clearly that it's going to copy the keys but the obvious implication isn't stated: that means you need to implement a copy operator for any class you plan on using as a key.
The thing is, nothing tests to make sure you do that. There's not even a warning if you don't but whatever default copy you get won't pass the comparison function. That means you can never say "Give me Bob's data" because the key for "Bob" won't be the same as the "Bob" object you're using. (And as an aside, yes I realize using the entire Person object as the key is slack. This was a temporary thing I was doing just to get the code running. In the particular case the "Person" object only consisted of a NSString anyway, so it wasn't that terrible.) You can happily stuff data in the Dictionary for "Bob" all day long but you'll get a nil when you try to retrieve the data.
So. If you're going to use a NSDictionary with custom key classes you'll want to implement the NSCopying protocol. I suspect you want to implement the isEqual message, and the documentation for that also mentions you need to implement hash. It's not that this isn't in the documentation exactly, but it's not front and center. If say you're writing code on your laptop in a car while driving to a Hooters where you plan on drinking a lot of beer. Hypothetically speaking.
This is pretty obvious, but I was surprised that I didn't get some sort of compile-time warning or error. Since the fix it make sure that object passed for the key was of a class that implemented NSCopying I still think the compiler could have detected this. (I'll admit it seems a little non-Objective-C-like but in my still newbie opinion Objective-C suffers from two much "try it it a runtime and see if it works" sort of behavior.) But it's just the sort of the thing you need to check yourself.
Read moreFlip Mino and OS X
I bought Karin a Flip Mino video camera for her birthday. These cameras come with built-in software for editing video on both Windows and OS X, but you don't want to do that. I knew it was a solvable problem, but I wasn't sure exactly what the solution was. After some quick research I have an answer and I'm willing to share :-)
The Mino stores its video in as 3ivx encoded MPEG-4 video. Perian supports 3ivx files, along with a lot of other formats. Download Perian, install the preference pane and Quicktime can now playback the 3ivx files. iPhoto and iMovie HD (aka iMovie '06) can both process the files directly with Perian installed. iMovie '08 won't process them without another step to convert the file to DV format, but Karin uses iMovie 06 anyway, and if I was going to start editing video I'd probably buy Final Cut Express. I read on the web that QuickTime Pro and VisualHub can both do the conversion and those are both highly recommended for anybody doing video anyway, so I'd look at one of those if you need iMovie '08 compatibility.
Read more