Well, I’m really happy with OSX 10.5.2. Even I’m not the one that blamed Apple for the translucent menu bar that everyone dislikes.. well, I like it. I don’t care about the TM menu bar tool, because I haven’t bought (yet) the nifty Time Capsule, I like the spinner in the Airport menu and, most of all, I really like the updates to the BluetoothSCOAudioDriver.kext that drives my bluetooth headset.

Spotlight also feels faster and faster on every upgrade, and I’m a heavy spotlight user, so this makes me really happy. Thanks Apple engineers!

Back to the topic: why odissey? Because as per my battery hints , I managed to make my MacBook2,1 SHUT DOWN while at 74% of the “Writing files” phase of the combo update… resulting in a completely broken system, as every geek could imagine :). Apple updated some libraries, and upon reboot simply nothing worked, and the darwin console was filled with lots of error messages.

The standard apple fanb^Wuser would have simply archived and installed his system, but hey, I’m a proud geek! I know from experience that disaster recovery situations are the best ones to learn something about an operating system, because you have to help the system boot up, bringing services up by hand, and find some way to re-apply the combo update without using the easy Aqua interface.

Luckily enough, on OSX every GUI has its CLI counterpart, following the best “UNIX guidelines” of interest separation and well designed architecture. Furthermore, OSX takes this approach one step further, following the best software engineering principles, where functionalities are implemented in Frameworks and both the GUI and CLI interfaces use it. Well done!

The odissey started with a CMD-S to boot in single user mode, a /sbin/fsck -fy, and a /sbin/mount -uw / to get a writable root. I started directly with a hdiutil attach -noverify -verbose -mount required /Users/vjt/Downloads/MacOSXUpdCombo10.5.2.dmg in order to mount the update disk image, but it failed because the diskarbitrationd daemon wasn’t running.

So, I fired up launchctl and issued load /System/Librar/LaunchDaemons/com.apple.diskarbitrationd.plist, when I discovered that it needed both the configd daemon and the notifyd daemon, so I loaded them up both via launchctl and.. YAY! The disk image was correctly mounted in /Volumes/Mac OS X Update Combined!

Here things started to complicate a bit, because an easy task like issuing installer -package MacOSXUpdCombo10.5.2.pkg -target / failed with

NSInvalidArgumentException in [IFRunnerProxy requestKeyForRights:askUser:] unrecognized selector sent to instance 0x79ac50.

Well, here the Objective-C method was pretty self-explaining, the installer was trying to ask the user permission to install the package. That’s quite strange, because I was running the installer command as root, so no request should have been asked.

I started scratching my head, and thought about the DirectoryServices, maybe because they were unavailable “something wrong”(tm) was happening?

OK, let’s try injecting the com.apple.DirectoryServices property list into launchd.. it didn’t work and dyld spit out this enlightening error message:

com.apple.DirectoryServices[11980]: dyld: lazy symbol binding failed: Symbol not found: _res_interrupt_requests_enable voyager com.apple.DirectoryServices[11980]: Referenced from: /usr/sbin/DirectoryService com.apple.DirectoryServices[11980]: Expected in: /usr/lib/libresolv.9.dylib

ARGH! Something has changed into libresolv! I was having 10.5.1’s DirectoryServices.. with 10.5.2’s libresolv! Gotta restore the old version to make DS run. I first tried with a raw netcat network copy from another 10.5.1 box, but with my surprise the three-way handshake wasn’t completed between the two endpoints so no data could be transferred. Phew.

But luckily, because I already brought up the diskarbitration daemon, I could easily put the library on an USB storage device, plug it in and have it mounted in /Volumes. It did work and Directory Services were up&running.. but still the same ugly NSInvalidArgumentException error when launching the installer utility. Sigh. :(

At this point, I gave up because my journey had been interesting enough and I had a really more comfortable way to fix up my problem: a USB-attached hard disk with a vanilla Leopard installation, from which I could boot up my MacBook, double click the disk image from the Finder and lazily launch an installer -target /Volumes/disk0 -package /Volumes/Mac OS X Update Combined/MacOSXUpdCombo10.5.2.pkg to re-run all the upgrade procedures that would fix my Leopard installation. So I followed this path, because I had some work to do and could not persevere in my geeky journey with the Darwin console, even if it had been really entertaining. :)

After the installer completed its job, I rebooted and a shiny new 10.5.2 greeted me with the usual Mac OS X login window that on my box sports the “All your base are belong to us” slogan ;).

Hope you enjoyed this journey as I did, and if you’re a Linux fanb^Wuser don’t underestimate the cleanliness and cleverness of Mac OS X that every Apple geek tries to share with you!

3 simple rules:

  • DO NOT leave your charger connected when the battery is charged, even when you go to sleep.

  • DO let it discharge completely, when using it wait till it reaches 0%, when sleeping it leave it alone, when you’ll wake up and you’ll open it, a resume from suspend to disk will greet you. OSX FTW.

  • Monitor it and show off OSX performance counters to your friends (images courtesy of CoconutBattery.app and System Profiler.app)

Battery health Battery cycles

While happily installing prerequisites to build an app on Solaris 11 , i enjoyed having Mercurial already installed in the base system.. except for a BIG issue: digest authentication was broken. I tcpdump’ed the traffic exchanged between the mercurial client and the CGI server and I saw that no Authorization header was sent, and obviously the server refused to serve the hg repository.

Before reinstalling python, maybe from source and replacing the default installation or having side by side two different versions, with consequent nuisances and dirt around the system, I tried a very very small patch to urllib2.py that… amusingly enough, fixed my problem:

--- urllib2.py~ Fri Jan 25 02:35:59 2008
+++ urllib2.py  Fri Jan 25 03:27:52 2008
@@ -815,7 +815,7 @@
             auth_val = 'Digest %s' % auth
             if req.headers.get(self.auth_header, None) == auth_val:
                 return None
-            req.add_unredirected_header(self.auth_header, auth_val)
+            req.add_header(self.auth_header, auth_val)
             resp = self.parent.open(req)
             return resp

I’m no fscking python expert (but the language is interesting), so don’t ask me WHY it works, i simply followed the add_header comment that said “this method is useful for adding authentication headers” and replaced the unredirected_header method with the former. I really don’t know why with Python2.5’s urllib2 “everything works” even with that method, something must be broken somewhere else. A diff between the two urllibs gave me nothing, I really should learn Python one day or another.

I also found no information by googling keywords such as «solaris “http {authorization,authentication}” {urllib2,python} {broken,not working} mercurial» (shell interpolation intended), so I hope this post will be useful to someone ;).

Solaris looks like a nice beast, though. I’ll have to learn more about it as well. :).

UPDATE: you don’t need this code, because starting from the 2.2 version of Rails, localization support is built-in.

Localization for Active Record error messages

Today i had to answer to one of the questions every non-english Rails developer stumbles upon now or after.. how to localize AR error messages for pleasant appearance to a non-english customer ;).

First off, thanks to defunkt’s excellent gibberish plugin and to the way AR validation errors are exposed, the task was accomplished in an easy and clean manner, without messing too much with AR’s internals.

I started by translating every default AR error message, with this translation file located in lang/it.yml:

# Active Record errors
#
ar_accepted:     "deve essere accettato" 
ar_not_a_number: "non è un numero" 
ar_blank:        "è un campo obbligatorio" 
ar_empty:        "è un campo obbligatorio" 
ar_inclusion:    "non è nella lista dei valori validi" 
ar_too_long:     "è troppo lungo (massimo %d caratteri)" 
ar_exclusion:    "è riservato" 
ar_too_short:    "è troppo corto (minimo %d caratteri)" 
ar_invalid:      "non è valido" 
ar_wrong_length: "è errato, dovrebbe essere di %d caratteri" 
ar_confirmation: "non corrisponde" 
ar_taken:        "esiste già" 
# This one is not a default key, but I use it in my validations
ar_greater_zero: "deve essere maggiore di zero" 

and four lines in config/environment.rb:

Gibberish.current_language = :it
ActiveRecord::Errors.default_error_messages =
  ActiveRecord::Errors.default_error_messages.inject({}) {|h, (key, string)|
    h.update(key => string["ar_#{key}".intern]) # <em>Gibberish magic</em>
}

The first one simply sets Italian (:it) as the default language, the inject builds a new error_messages hash using Gibberish to translate the default ones. I named every AR error key in my translation file with an “ar_” prefix, in order to avoid possible future key clashes. Finally, AR array is overwritten with the new one freshly built.

This solution assumes that the application will show only in a single language, if you need localized error messages in different language you should put this code in some around_filter, like Gibberish documentation suggests.

OK, messages are translated, but what about field names? I used english field names in my tables, how about translating them as well? First, we need the actual translations in lang/it.yml:

# Field names
#
fld_multiplier:  "La dimensione" 
fld_service_id:  "Il servizio" 
fld_customer_id: "Il contraente" 
fld_address_id:  "L'indirizzo" 
fld_user_id:     "L'utente" 
fld_city_id:     "La città" 
fld_state_id:    "La provincia" 
fld_address:     "L'indirizzo" 
fld_terms_of_service: "Il testo di informativa sulla privacy" 

And then a customized helper that our view will call, loosely based on the default error_messages_for Rails helper:

  # error messages localization
  # uses gibberish.
  def foo_object_error_messages
    return '' if @foo_object.errors.count.zero?
    header_message = "Si prega di correggere i seguenti errori:" 
    error_messages = @foo_objects.errors.map do |field, err|
      field = field["fld_#{field}".intern] # [i]Gibberish magic[/i]
      content_tag(:li, "#{field} #{err}")
    end 
    content_tag(:div,
      content_tag(:h2, header_message) <<
        content_tag(:ul, error_messages),
      :class => 'errorExplanation',
      :id    => 'errorExplanation'
    )   
  end 

Here, easily, every field name is substituted by its translated counterpart, and a div is built with the plain vanilla class and id used by Rails’ [code]scaffold.css[/code], so that I won’t have to write a single line of CSS.

In your view: <%= foo_object_error_messages %>

This helper could be of course improved, but I’m a bit lazy right now.

Have fun! :D