While web apps are all the rage, what with the browser being ubiquitous and everything, there are still some features that don’t scale - usually stuff that requires a lot of high-bandwidth data getting manipulated and displayed. Sorry, but interactive data-heavy apps just don’t yet run well enough on the web to make the jump. For other things, yes; but not for these.
Unfortunately, applications based on these sorts of features are the ones I’ve spent a lot of time writing. They want to be web-enabled, not web-based. It’s one thing to use the web as a communications medium, but another to use it as an interaction medium.
So, I got Lyle Johnson’s book FXRuby: Create Lean and Mean GUIs with Ruby. I’ve been messing with FXRuby lately. Quite a nice little package. I’m moving some big-application stuff into it and may have more to say about it in the upcoming weeks and months. One of the parts I just moved in deals with opening html documents and URLs. A long time ago I decided to write all of my help documents in html and use the browser for displaying them. When the user clicks a help button in an app (or something like that) I need to bring up the requested page in a browser.
Opening a URL in a browser seems like it should be almost trivial, but from a Ruby app there is a little art to it. Different vendors have different mechanisms to do it, and you have to make it work right so the users of your apps don’t get flustered. So I hide the vendor-specific logic under a general method:
This dispatcher will call the open for the specific vendor. I make the separation here, so I can handle each vendor cleanly no matter what sort of crazy stuff they may be doing.
Some vendors make it easy. Take Apple, for instance:
Apple’s made opening a file in your viewer of choice a basic function of the OS. By hiding the details, it’s less work and higher productivity for me.
In Windows it takes more effort. Because the chosen viewer is buried in the registry, I have to dig it out:
Now I can do the open:
On other systems (say Linux, for instance) there’s no direct solution. Because the chosen viewer is buried in desktop preferences it may be different for different window managers, and there’s no definitive way to know what it is and how to pull it out. So I opt for flexibility - I depend on an outsider to inject the name of the URL viewer into the mechanism prior to opening the url.
This isn’t enough, however.
The Kernel’s system
method will block until the application opening the URL returns.
I don’t want the application to stop and wait!
In order for the app to stay in control, the open has to run in it’s own thread.
But there’s a problem. I’d like to catch the raised exceptions from the dispatched opens if something unexpected happens - but since the exceptions come from a new thread that I’m not waiting for, they’ll just go into the ether. I need to do this using another mechanism. So instead of raising, I’ll hypothecate an exception handling mechanism that the thread can use to notify the app that there was a problem during the open.
and we’ll let the caller pre-designate the exception handler. Though this isn’t quite as nice as rescuing in the caller, I’m not as concerned since the limited set of things that can fail when opening a url really come down to configuration issues or the absence of the URL’s target.
While this will open a URL in a viewer from a Ruby app, there’s a little more work needed. I want to ensure the URL is properly-formed enough not to choke the viewer. I’ll do this by normalizing before I do the open.
The normalizing is just a bit funky, but trivial in concept - just return a string containing the normalized URL. My top-level logic is: if it’s a file resource, then normalize it as a file; otherwise, validate it as a URI. Since Ruby already comes with a URI class, I just use it.
It’s a URL file resource if it starts with file:
, a slash or a drive designator (on a pc) or it doesn’t have a colon in it.
Normalizing a file amounts to giving back the normalized file name with file://
prepended to it.
A file path is absolute if it starts with a drive designator and it’s on a PC, or a slash (with no drive designator) if it isn’t.
Finaly, a relative base is prepended to relative file paths. It fits between a drive designator and the relative path or a PC, or otherwise just sits at the front of the path. When I assign it, I make sure it looks like it’ll work.
This wraps everything up nicely. Nice enough that I wrapped it up into a gem I can pull into any of my apps. I added it to my cori project (Chunks Of Ruby Infrastructure) on rubyforge in the eymiha_url rubygem. I added it as the eymiha_url rubygem, available at rubygems.org.
Having done this once, I can now get on with the meat of writing my interactive-but-data-heavy Ruby applications, waiting for enough bandwidth on the Internet to someday move them to the web.