All posts by Dean

I unscramble eggs for a living. When not working I make, judge & drink beer. Whether working or playing I enjoy exercise & sporting, folksy-punk-rockish-bluesy-nerdcore music and mathematics. Around me is the best wife, loving family & great friends.

A Request to Friends

It has been sixteen months since Adrienne had a hysterectomy.  Saying “we’ve been through a lot” is meaningless truth.  Adie and I will never be parents.  Ultimately the decision was ours, heavily influenced by cold biological facts.  This blog is small catharsis, but its material is not for those with a weak emotional constitution.

We have seen two Mother’s and Father’s Days since the decision.  Each one is easier than the previous.  The first one Adrienne wrote

“There is no I-had-my-uterus-and-ovaries-taken-at-32-so-I-will-never-have-children-of-my-own day. If someone makes one, I hope it’s in August. I don’t have anything else to celebrate in August.”

I will observe this day on August 11th and I am asking our friends to do so with me.  The name isn’t catchy, the sentiment too raw, and it risks emphasizing our infertility.  With a little effort we can all turn this day into something positive. This day we should:

Celebrate the life and happiness we have

We let go of a lifetime of desires and expectations while simultaneously redefining and reorienting ourselves.  This process is not over, but our goal on this day is to be happy with all we have.

Emphasize accomplishments

Birthing and raising children is often quoted as the biggest accomplishment a person can make.  We do not believe it.  Your affirmation drives us on to other altruistic endeavors.

Show that we are whole people

Any -ectomy takes something away from a person.  We felt “broken” and may not get over that.

We are not the only ones

So many couples and singles struggle with their sense of childless self worth.
Think of them on this day.  If you would like to help, it is simple as a phone call, email, tweet or Facebook post just to say “hi” or to remind someone of the good they do.

This day is not about trying or failing to be a parent.  We need neither sympathy nor reminders of our “enviable freedom” and that parenthood is “not all joy“.  At the same time the day’s purpose is not to belittle family.  Reinforce the good things that all of do.

I would like to recognize Mandy Tompkins’ kind help in putting this together.  She has been there before us and gone on.

Hard-plumbing The Stand

Just a quick homebrew update.  My three-tier stand is great, but I kept melting hoses when they would touch the hot metal.  Luckily none ever burst and spewed hot wort all over, but I knew that was just a matter of time.

So a friend and I got out the torches and hard-plumbed the stand.  The hoses are now short jumpers that connect vessels to the center line.  Brewed a dunkelweiss with the new setup on Thursday.  Works well and no burned hoses.

Some eye candy:

Easy Thunderbird Account Management Using MCD

Ford Thunderbird Console
From myoldpostcards

Continuing my series on Mission Control Desktop, this post covers some enhancements I made for configuring email accounts in Thunderbird.  The mail.* branch is one of the largest group of preferences you can manipulate using autoconfig.  Setting up the right combination of mail.accountmail.server, mail.identity and mail.smtpserver is just a little tricky.

With the exception of the special “local folders” account, an email account in Thunderbird has four components: an account, a server, one or more identities, and an smtp server.  There are a few preference strings gluing each of these elements together. Each account you create is differentiated from the others by a unique label, defaulting to account<num>, with incrementing numbers. You can also use your own, more meaningful labels, such as mail.account.work-email and mail.account.gmail.

Element Branch
Account mail.account.accountN Glues POP/IMAP, SMTP servers and identities together
Server mail.server.serverN Settings for the IMAP or POP server
Identity mail.identity.idN Name, email address, drafts & stationery folders
SMTP Server mail.smtpserver.smtpN Settings for the SMTP server
Glue settings
Accounts list mail.accountmanager.accounts Comma-separated list of mail.account labels
Default account mail.accountmanager.defaultaccount The label of the default mail account
SMTP servers mail.smtpservers Comma-separated list of mail.smtpserver server labels
Default SMTP mail.smtp.defaultserver The label of the default SMTP server

Here is a quick example:

// Identity
defaultPref("mail.identity.id1.fullName", "Dean Brundage");
defaultPref("mail.identity.id1.draft_folder", "imap://dean.brundage@example.com@mail.example.com/Drafts");
defaultPref("mail.identity.id1.smtpServer", "smtp1");
 
// IMAP server settings
defaultPref("mail.server.server1.type", "imap");
defaultPref("mail.server.server1.hostname", "mail.example.com");
// etc
 
// SMTP server settings
defaultPref("mail.smtpserver.smtp1.auth_method", 1);
defaultPref("mail.smtpserver.smtp1.hostname", "smtp.example.com");
// etc
 
// Glue it all together
defaultPref("mail.account.account1.identities", "id1");
defaultPref("mail.account.account1.server", "server1");
 
defaultPref("mail.accountmanager.accounts", "account1");
defaultPref("mail.accountmanager.defaultaccount", "account1");
 
defaultPref("mail.smtp.defaultserver", "smtp1");
defaultPref("mail.smtpservers", "smtp1");

As you add more email accounts the code can get unmanageable when you try to remember to twiddle the right branches so all the accounts, their identities and smtp servers show up. I would like to present an alternative.

This code builds upon the PreferenceFactory prototype covered earlier.  I put together an object prototype for an account, containing a server, one or more identities, and an smtp server.  There is also a singleton for the account manager.  Now you can instantiate an object and call preference setters on it.  The code is more readable and less error prone.

   // Create an email account
   var workAccount = AccountManager.newAccount({ isDefault: true,
                                                 label: "work",
                                                 type: "imap" });
 
   // Lock Preferences on the IMAP server
   workAccount.server.setPrefs({ hostname: "mail.example.com",
                                 type: "imap" }, "lock" );
   // Default Preferences
   workAccount.server.setPrefs({ check_new_mail: true,
                                 name: "Corporate eMail" });
 
  // Lock Preferences on the SMTP server
  workAccount.smtpServer.setPrefs({ auth_method: 1,    /* User/pass */
                                    username: "dean.brundage@example.com" }, "lock" );
  // Default Preferences
  workAccount.smtpServer.setPrefs({ description: "Corporate SMTP server",
                                     hostname: "mail.example.com"});
 
  // And now a personal account
  var myGmailAccount = AccountManager.newAccount({ label: "gmail", type: "imap" });
  // Go about setting preferences on myGmailAccount as before

Download the source

That block of code replaces the tedious series of defaultPref and lockPref required to set up an email account.  It produces preference settings like these:

defaultPref("mail.accountmanager.accounts", "work-account1,gmail-account2");
defaultPref("mail.accountmanager.defaultaccount", "work-account1");
 
defaultPref("mail.account.work-account1.identities", "work-id1");
defaultPref("mail.account.work-account1.server", "work-server1");
lockPref("mail.server.work-server1.type", "imap");
 
defaultPref("mail.server.gmail-server2.type", "imap");
// etc
 
defaultPref("mail.smtp.defaultserver", "work-smtp1");
defaultPref("mail.smtpservers", "work-smtp1");

Download the source

/**
   By Dean Brundage
   Originally published here:
     http://blog.deanandadie.net/2010/06/easy-thunderbird-account-management-using-mcd/
*/
 
 
/* "Inheritance" helper
   http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/
   copy all of parent's prototype functions to descendant
*/
function copyPrototype(parent, descendant)
{  var sConstructor = parent.toString(); 
   var aMatch = sConstructor.match( /\s*function (.*)\(/ ); 
   if( aMatch != null )
   {  descendant.prototype[aMatch[1]] = parent;  } 
   for (var m in parent.prototype)
   {  descendant.prototype[m] = parent.prototype[m];  } 
};
 
 
/* "Base" for the Mail.xxxx objects
 
   This requires the PreferenceFactory prototype covered in a previous post:
     http://blog.deanandadie.net/2010/05/manufacturing-user-preferences-for-mcd/
*/
function Mail()
{  this.PreferenceFactory();  // super
   this.addPrefBranch("mail");
   return this;
}
 
copyPrototype( PreferenceFactory, Mail );
 
 
Mail.prototype.joinIds = function(collection,separator)
{  if( ! separator )
      separator = ",";
   if( collection )
   {  ret = collection[0].id
      for( i = 1; i < collection.length; i++ )
         ret = ret + separator + collection[i].id;
   }
   return ret;
};
 
 
/* A thunderbird email "account" consists of
      Mail.Server -- A mail server (IMAP or POP)
      Mail.SMTPServer -- A SMTP server
      One or more Mail.Identities -- Email address and name
 
   A thunderbird local folders "account" has only one Mail.Server, nothing else
      Yup, local folders are "servers"
*/
 
/* Object to manage accounts
      I recommend managing mail accounts with the AccountManager unless you
      know what you are doing.
   var myAccount = AccountManager.newAccount();
   myAccount.doStuff();
 
*/
var AccountManager = new Mail();
 
AccountManager.accounts = [];
AccountManager.addPrefBranch("accountmanager");
 
/* Create a new account
   Alerts Thunderbird to the presence of the new account
   See Mail.Account for valid options
*/
AccountManager.newAccount = function(opts)
{  if( ! opts )
      opts = { };
 
   acct = new Mail.Account(opts);
 
   if( opts.isDefault )
   {  acct.useSMTPServer( new Mail.SMTPServer(opts) );
      this.setDefaultAccount(acct,opts.lockLevel);
   } else
   {  acct.useSMTPServer( Mail.SMTPServer.defaultServer );
   }
 
   this.accounts.push(acct);
   this.setPref( "accounts", this.joinIds(this.accounts) );
   return acct;
}
 
 
AccountManager.setDefaultAccount = function(account,lockLevel)
{  
   this.setPref("defaultaccount", account.id, lockLevel );
 
   /* It's possible to create an account before setting the default
      SMTP server.  Clean them up if this new account is the default
      (Expect Mail.Account to inform Mail.SMTPServer of the new default)
   */
   for( i = 0; i < this.accounts.length; i++ )
      this.accounts[i].useSMTPServer(Mail.SMTPServer.defaultServer);
}
 
 
AccountManager.setLocalFolders = function(folders,lockLevel)
{  this.setPref("localfoldersserver", folders.id, lockLevel);
}
 
 
/* Creates a new generic mail account (identity, server, smtp)
   Arguments
     opts: A hash of options. Valid options are:
           "isDefault": true | false (default false)
           "label": string - A unique label for this account, server & identity
           "lockLevel": "default" | "lock" | "pref" (default "default")
           "type": imap | pop | localFolder
 
   This prototype creates an object representing an email account
   It exposes some objects:
      myAccount.directoryServer  // The (optional) LDAP2Server object
      myAccount.identities       // An array of identites
      myAccount.server           // The IMAP/POP/LocalFolder Mail.Server object
      myAccount.smtpServer       // The Mail.SMTPServer object
 
   (The LDAP2Server object is detailed in a separate post on http://blog.deanandadie.net/)
*/
Mail.Account = function(opts)
{  if( ! opts )
     opts = { };
 
   if( ! opts.type )
      throw("What's my type?");
 
   this.Mail();  // "super"
 
   this.id = "account" + ++Mail.Account.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our preference branch is mail.account.accountN
   this.addPrefBranch( [ "account", this.id ] );
 
   this.identities = [];
 
   this.server = new Mail.Server(opts);  // Need this either way
 
   if( opts.type.match(/^^imap$|^pop$/i) )
   {
      this.server.setPref("type", opts.type, opts.lockLevel);
 
      // Important that addIdentity be before the SMTP server is set up
      this.addIdentity( new Mail.Identity(opts), opts.lockLevel );
 
   } else if( opts.type.match(/^localFolders?$/i) )
   {  this.folders = this.server;
      AccountManager.setLocalFolders(this.folders);
      this.folders.setPref("type", "none", opts.lockLevel );
 
   } else
   {  throw("unrecognized Mail.Account type: " + opts.type);  }
 
   this.useServer(this.server, opts.lockLevel);
 
}
 
copyPrototype( Mail, Mail.Account );
 
Mail.Account.count = 0;                                   // Fake class variable
 
 
// Expects a Mail.Identity object and, optionally, the preference locking level
Mail.Account.prototype.addIdentity = function(identity,lockLevel)
{  if( identity )
   {  if( ! this.identities.contains(identity) )
      {  this.identities.push(identity);
         this.setPref("identities", this.joinIds(this.identities), lockLevel );
      }
   }
}
 
 
/* Helper to set the SMTP server
   Expects a Mail.SMTPServer object & optionally, an options hash
   Valid options:
     lockLevel: lock | default | pref
     force: true | false
*/
Mail.Account.prototype.useDirectory = function(directory, opts)
{  if( ! opts )
      opts = { };
   if( directory )
   {  if( opts.force || typeof(this.directoryServer) == "undefined" )
      {  this.directoryServer = directory;
         for( i = 0; i < this.identities.length; i++ )
            this.identities[i].useDirectory( this.directoryServer, opts );
      }
   }
}
 
/* Helper to set the SMTP server
   Expects a Mail.SMTPServer object & optionally, an options hash
   Valid options:
     lockLevel: lock | default | pref
     force: true | false
*/
Mail.Account.prototype.useSMTPServer = function(smtp,opts)
{  if( ! opts )
      opts = { };
   if( smtp )
   {  if( opts.force || typeof(this.smtpServer) == "undefined" )
      {  this.smtpServer = smtp;
         for( i = 0; i < this.identities.length; i++ )
            this.identities[i].useSMTPServer( this.smtpServer, opts );
      }
   }
}
 
 
// Expects a Mail.Server object and, optionally, the preference locking level
Mail.Account.prototype.useServer = function(server,lockLevel)
{  if( server )
      this.setPref("server", server.id, lockLevel );
}
 
 
/* Creates a new generic mail server
   Arguments
     opts: A hash of options. Valid options are:
           label: string - A unique label for this account, server & identity
*/
Mail.Server = function(opts)
{  if( ! opts )
     opts = { };
 
   this.Mail();
 
   this.id = "server" + ++Mail.Server.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our preference branch is mail.server.serverN
   this.addPrefBranch( [ "server", this.id ] );
}
 
copyPrototype( Mail, Mail.Server );
 
Mail.Server.count = 0;  // Fake class variable
 
 
/* Creates a new mail identity. (name, email address, etc)
   Arguments
     opts: A hash of options. Valid options are:
           "label": string - A unique label for this account, server & identity
*/
Mail.Identity = function(opts)
{  if( ! opts )
     opts = { };
 
   this.Mail();
 
   this.id = "id" + ++Mail.Identity.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our preference branch is mail.identity.idN
   this.addPrefBranch( [ "identity", this.id ] );
   this.hasSMTPServer = false;
};
 
copyPrototype(Mail, Mail.Identity);
 
 
Mail.Identity.count = 0;  // Fake class variable
 
 
/* Expects a LDAP2Server object and a hash of options
   Valid options:
      lockLevel: lock | default | pref
*/
Mail.Identity.prototype.useDirectory = function(ldap2Server,opts)
{  if( ! opts )
      opts = { };
 
   if( ldap2Server )
   {  this.setPref("directoryServer", "ldap_2.servers." + ldap2Server.id, opts.lockLevel);
      this.setPref("overrideGlobal_Pref", true, opts.lockLevel);
   }
};
 
 
/* Expects a SMTPServer object and a hash of options
   Valid options:
      force: true | false
      lockLevel: lock | default | pref
*/
Mail.Identity.prototype.useSMTPServer = function(smtpServer,opts)
{  if( ! opts )
      opts = { }
   if( smtpServer )
   {  if( opts.force || ! this.hasSMTPServer )
      {  this.setPref("smtpServer", smtpServer.id, opts.lockLevel);
         this.hasSMTPServer = true;
      }
   }
};
 
 
/* Creates a new smtp server
   Artuments:
     opts: A hash of options. Valid options are:
       isDefault: true | false (default)
       label: A unique label for this smtp server
   Throws an error if there is already a default smtp server
*/
Mail.SMTPServer = function(opts)
{  if( ! opts )
     opts = { };
 
   this.Mail();
 
   this.id = "smtp" + ++Mail.SMTPServer.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our perference branch is mail.smtpserver.smtpN
   this.addPrefBranch( [ "smtpserver", this.id ] );
 
   if( opts.isDefault && Mail.SMTPServer.defaultServer )
      throw("Default smtp server already configured to " + Mail.SMTPServer.defaultServer);
 
   if( opts.isDefault )
      this.setDefault();
 
   // Register this SMTP server
   if( ! Mail.SMTPServer.servers.contains(this) )
      Mail.SMTPServer.servers.push(this);
 
   // Update mail.smtpservers to include the new one
   defaultPref("mail.smtpservers",  this.joinIds(Mail.SMTPServer.servers) );
 
   return this;
};
 
copyPrototype(Mail, Mail.SMTPServer);
 
// Some "class" variables
Mail.SMTPServer.count = 0;
Mail.SMTPServer.defaultServer = undefined;
Mail.SMTPServer.servers = [];
 
 
Mail.SMTPServer.prototype.setDefault = function()
{  Mail.SMTPServer.defaultServer = this;
   defaultPref("mail.smtp.defaultserver", Mail.SMTPServer.defaultServer.id );
}

Quick diff of Solaris patch levels

Suppose you have two Solaris machines and you want to bring them to the same patch level.  Or you are troubleshooting issues on two machines that are supposed to be identical.  I wrote a perl script that compares patch levels on two machines.

It shows something like this:

malfunction % patch_diff sr-host-01 sr-host-02
 
Patches on each system that are missing from the other.
                        sr-host-01  |  sr-host-02
          Wed Jun  2 11:40:40 2010  |  Wed Jun  2 11:40:59 2010
------------------------------------+-------------------------------------
                         118814-01  |  123252-01
                         118959-03  |  127752-01
                         119090-31  |  137110-01
                                    |  142430-01
                                    |  142436-01
 
Patches on each system that are downrev on the other.
                        sr-host-01  |  sr-host-02
          Wed Jun  2 11:40:40 2010  |  Wed Jun  2 11:40:59 2010
------------------------------------+-------------------------------------
                         137147-04  |  137147-05
                         137000-03  |  137000-06
                         125952-18  |  125952-19

Download patch_diff

First it shows all the patches sr-host-01 has that sr-host-02 does not have and vice-versa.  In this simplified case sr-host-01 has 3 patches sr-host-02 does not have and sr-host-02 has 5 patches sr-host-01 does not have.  It also compares patch revisions and shows the different levels on each host.  The machines share 3 patches that are at different revision levels.

To use patch_diff you must first generate a patch list on each machine.

0 sr-host-01 % patch_diff -g

This makes a directory with a NDBM file containing the patches on sr-host-01. Do the same for sr-host-02 and bring the NDBM files together. (It helps if your home directory is shared between the machines.)

0 malfunction % ls patch_diff
sr-host-01.dir  sr-host-02.dir
sr-host-01.pag  sr-host-02.pag

Download patch_diff

Manufacturing User Preferences For MCD

Bottling Line
From vissago

Nobody likes boring code

Mozilla products like Thunderbird and Firefox represent setting choices in a textual “tree” system. (Read the intro.)  Preferences that begin with print. live on the tree branch dealing with printing while those that begin with app.update. are on the auto-update system’s branch.  An easy concept to grasp. In practice, however, the simplicity will easily become a drone of defalutPref(“app.update.auto”, false); if you are not careful.

// This is a boring autoconfig script
defaultPref("browser.dom.window.dump.enabled", true);
defaultPref("browser.download.manager.retention", 0);
defaultPref("browser.download.manager.showAlertOnComplete", false);
defaultPref("browser.download.manager.showWhenStarting", false);
defaultPref("browser.download.save_converter_index", 0);
defaultPref("browser.feeds.handler", "reader");
defaultPref("browser.feeds.handler.default", "web");
defaultPref("browser.feeds.showFirstRunUI", false);
defaultPref("browser.history_expire_days.mirror", 180);
// and so on....

Working at The Preference Factory

Instead of hand-cranking preferences I developed an object prototype to mechanize large blocks of related settings.  The resulting code is more readable and easier to maintain when it looks something like this:

// Some hypothetical printing preferences
var printPrefs = new PreferenceFactory("print");
 
// Lock out printing background or in color and don't allow font download
printPrefs.setPrefs( { print_bgcolor: false,
                       print_bgimages: false,
                       print_downoadfonts: false,
                       print_in_color: false }, "lock" );
 
// Make some sensible defaults
printPrefs.setPrefs( { print_orientation: 0,  // Letter
                       print_to_file: false } );

Whitespace focuses your attention on the block and the object eliminates monotonous pref-branch statement repetition. For the experienced programmer this approach is the familiar object-oriented one of sending messages to receivers. One goal behind this prototype was to help create elegant code, which can be difficult to do in javascript. There is another purpose to PreferenceFactory which I will cover in a later post.

The Bottling Line

The heart of PreferenceFactory is a wrapper around the autoconfig API.  It performs lockPref(), defaultPref(), pref() and getPref() on a branch of the preference tree that you specify when creating the object. Here is the engine driving the factory:

function PreferenceFactory(arg)
{  this.prefNodes = [];   // Nodes in the preference tree
   this.prefBranch = "";  // String representation of the branch
   if(arg)
      this.addPrefBranch(arg);   // Add nodes to the tree and update the string representation
}
 
/* Preference setting helper function
   myServer.setPref("type", "imap", "lock");
   Arguments
     key: The preference string to set
     value: Value to assign to key
     lockLevel: Locking level.
            Valid values are "default", "lock" and "pref"
            Default level is "defulat"
*/
PreferenceFactory.prototype.setPref = function(key,value,lockLevel)
{  if( lockLevel )
   {  switch( lockLevel.toLowerCase() )
      {  case "default":
            this.defaultPref(key,value);
            break;
         case "lock":
            this.lockPref(key,value);
            break;
         case "pref":
            this.pref(key,value);
            break;
         default:
            throw("Unrecognized locking level: " + lockLevel );
      }
   } else
   {  this.defaultPref(key,value);  }
}
 
PreferenceFactory.prototype.defaultPref = function(key,value)
{  if( key )
      defaultPref( this.prefBranch + "." + key, value );
}
 
PreferenceFactory.prototype.getPref = function(key)
{  if( key )
      getPref( this.prefBranch + "." + key );
}
 
PreferenceFactory.prototype.lockPref = function(key,value)
{  if( key )
      lockPref( this.prefBranch + "." + key, value );
}
 
PreferenceFactory.prototype.pref = function(key,value)
{  if( key )
      pref( this.prefBranch + "." + key, value );
}

The “hypothetical print” code example gets its readability from a final function to set multiple preferences.  It uses a function I copied from JsUnit called trueTypeOf().  Its function is much like javascript’s typeof, but handles more than just the built-in data types.  setPrefs() accepts an array or “hash” and iterates over its elements, calling the above setPref() on each 2-tuple.

/* Sets multiple preferences
   Accepts an array or object "hash" and an optional locking level
*/
PreferenceFactory.prototype.setPrefs = function(prefs,lockLevel)
{  switch( trueTypeOf(prefs) )
   {  case "Object":
         for( thing in prefs )
         {  this.setPref(thing, prefs[thing], lockLevel);  }
         break;
      case "Array":
         if( prefs.length % 2 != 0 )
         {  throw("Need an even number of strings to set multiple preferences with an array");  }
         for( i = 0; i < prefs.length; i = i + 2 )
         {  this.setPref(prefs[i], prefs[i+1], lockLevel);  }
         break;
      default:
         throw("I don't know how to set multiple prefs with a " + trueTypeOf(prefs) );
   }
}

Download the full source

Scene from an upcoming post

I alluded to another purpose for this prototype and this is a glimpse of how I use it with Thunderbird in the real world.  Stay tuned.

   // Create an email account
   var brewingAccount = AccountManager.newAccount({ isDefault: true, type: "imap" });
 
   // Lock Preferences on the IMAP server
   brewingAccount.server.setPrefs({ capability: 81,
                                    hostname: "mail.example.com",
                                    port: 993,
                                    realhostname: "mail.example.com",
                                    realusername: brewingMail,
                                    remember_password: false,
                                    socketType: 3,
                                    type: "imap",
                                    userName: brewingMail }, "lock" );
   // Default Preferences
   brewingAccount.server.setPrefs({ check_new_mail: true,
                                    check_time: 10,
                                    cleanup_inbox_on_exit: true,
                                    delete_model: 1,   /* Move to trash */
                                    directory: userInfo.env_home + "/Mail",
                                    "directory-rel": "[ProfD]../../Mail",
                                    empty_trash_on_exit: false,
                                    empty_trash_threshhold: 0,
                                    name: "Brewing Mail",
                                    login_at_startup: true,
                                    using_subscription: false });
 
  // Lock Preferences on the SMTP server
  brewingAccount.smtpServer.setPrefs({ auth_method: 1,    /* User/pass */
                                       port: 465,
                                       username: brewingMail }, "lock" );
  // Default Preferences
  brewingAccount.smtpServer.setPrefs({ description: "Brewing SMTP server",
                                       hostname: "mail.example.com",
                                       try_ssl: 3 });

The source

/**
   By Dean Brundage
   Originally published here:
     http://blog.deanandadie.net/2010/05/manufacturing-user-preferences-for-mcd/
 
   Mix-in prototype for other objects that want to set defaultPref, lockPref
     or just plain old pref().
   Use the utility function copyPrototype() to copy this object's prototype
     functions to other objects.  This can also be used as a stand-alone object.
     Pass the prefix string to the constructor
   Example [mix-in]:
      function Mail.Server()
      {  // It's very important to update the object's preference path
         this.Mail();
         this.addPrefBranch("server");
         // Continue to define Mail.Server
      }
      copyPrototype(Mail.Server, PreferenceFactory);
      // Define the rest of Mail.Server's prototypes
 
      // Then you can set preferences on your Mail.Server object
      myServer = new Mail.Server();
      myServer.setPref("type", "imap", "lock");  // Lock the type of server to IMAP
 
   Example [stand-alone]:
      var prefFact = new PreferenceFactory( ["mail", "accountmanager"] );
      prefFact.setPref("localfoldersserver", "server2" );
 
*/
function PreferenceFactory(arg)
{  this.prefNodes = [];
   this.prefBranch = "";
   if(arg)
      this.addPrefBranch(arg);
}
 
 
// Add a string or many strings to the preference branch
PreferenceFactory.prototype.addPrefBranch = function(nodes)
{  switch( trueTypeOf(nodes) )
   {  case "String":
         this.prefNodes.push(nodes);
         break;
      case "Array":
         for( i = 0; i < nodes.length; i++ )
            this.prefNodes.push(nodes[i]);
         break;
      default:
         throw("Don't know how to addPrefBranch for a " + trueTypeOf(nodes));
         break;
   }
   this.prefBranch = this.prefNodes.join(".");
}
 
 
PreferenceFactory.prototype.defaultPref = function(key,value)
{  if( key )
      defaultPref( this.prefBranch + "." + key, value );
}
 
 
PreferenceFactory.prototype.getPref = function(key)
{  if( key )
      getPref( this.prefBranch + "." + key );
}
 
 
PreferenceFactory.prototype.lockPref = function(key,value)
{  if( key )
      lockPref( this.prefBranch + "." + key, value );
}
 
 
PreferenceFactory.prototype.pref = function(key,value)
{  if( key )
      pref( this.prefBranch + "." + key, value );
}
 
 
/*
   Preference setting helper function
   myServer.setPref("type", "imap", "lock");
   Arguments
     key: The preference string to set
     value: Value to assign to key
     lockLevel: Locking level.
            Valid values are "default", "lock" and "pref"
            Default level is "defulat"
*/
PreferenceFactory.prototype.setPref = function(key,value,lockLevel)
{  if( lockLevel )
   {  switch(lockLevel.toLowerCase())
      {  case "default":
            this.defaultPref(key,value);
            break;
         case "lock":
            this.lockPref(key,value);
            break;
         case "pref":
            this.pref(key,value);
            break;
         default:
            throw("Unrecognized locking level: " + lockLevel );
      }
   } else
   {  this.defaultPref(key,value);  }
}
 
 
/* Sets multiple preferences
   Accepts an array or object "hash" and an optional locking level
*/
PreferenceFactory.prototype.setPrefs = function(prefs,lockLevel)
{  switch( trueTypeOf(prefs) )
   {  case "Object":
         for( thing in prefs )
         {  this.setPref(thing, prefs[thing], lockLevel);  }
         break;
      case "Array":
         if( prefs.length % 2 != 0 )
         {  throw("Need an even number of strings to set multiple preferences with an array");  }
         for( i = 0; i < prefs.length; i = i + 2 )
         {  this.setPref(prefs[i], prefs[i+1], lockLevel);  }
         break;
      default:
         throw("I don't know how to set multiple prefs with a " + trueTypeOf(prefs) );
   }
}
 
function trueTypeOf(something)
{  // Borrowed from jsUnitCore.js.  Thank you.
   // http://github.com/pivotal/jsunit/blob/master/app/jsUnitCore.js
   var result = typeof something;
   try
   {  switch (result)
      {  case 'string':
            break;
         case 'boolean':
            break;
         case 'number':
            break;
         case 'object':
         case 'function':
            switch (something.constructor)
            {  case new String().constructor:
                        result = 'String';
                        break;
               case new Boolean().constructor:
                        result = 'Boolean';
                        break;
               case new Number().constructor:
                        result = 'Number';
                        break;
               case new Array().constructor:
                        result = 'Array';
                        break;
               case new RegExp().constructor:
                        result = 'RegExp';
                        break;
               case new Date().constructor:
                        result = 'Date';
                        break;
               case Function:
                        result = 'Function';
                        break;
               default:
                  var m = something.constructor.toString().match(/function\s*([^( ]+)\(/);
                  if (m)
                     result = m[1];
                  else
                     break;
            }
            break;
      }
   }
   finally
   {  result = result.substr(0, 1).toUpperCase() + result.substr(1);
      return result;
   }
}

Reading Local Files With Javascript

Security Conscious Javascript

Normally, javascript does not have access to local files.  Rightfully so because almost every web server should be untrusted and allowing anybody to read your files is a large security risk.  Mission Control Desktop, however is a javascript application with access to XPCOM, Mozilla’s component object model. This article is an example of how to use it to read local files.

Solaris Printing Configuration

I use MCD to configure thousands of users’ browser and mail clients.  In Solaris, printer preferences are set according to the Mozilla Postscript subsystem in a .printers file in the home directory.  Firefox and Thunderbird do not support this file interface so I use autoconfig to read the file and set printer preferences accordingly.

I wrote an object prototype to read and parse $HOME/.printers which the autoconfig uses to set the print.printer_list preference.  The prototype searches the file for a string indicating the list of printers to use:

# $HOME/.printers
# Set the default printer to riemann
_default riemann
# Display a chooser for three printers, ignoring all others.
_all newton,poincare,riemann

The line beginning with _all is quite important in my environment with 1,700 printers configured through LDAP.  If Thunderbird or Firefox were to try to load the entire printer list, it would become overwhelmed and crash.

extract_allPrinters utility

First, a look at the utility function I use to find the right line.

if( typeof(FileInterface) == "undefined" )  // Barf if the object prototype is undefined
   throw("autoconfig constructed improperly: need classes/file_interface.js before extract_all_printers.js");
 
function extract_allPrinters(path)
{  if( typeof(path) == "undefined" )
      path = env_home + "/.printers";
 
   // Regular expression to search .printers for _all
   re = /^\s*_all\s+(.+)\s*/i;
   lines = new FileInterface(path).grep(re);
 
   if( typeof(lines) != "undefined" )
   {  // Take the first match
      allPrinters = re.exec( lines[0] )[1].replace(/,\s*/g, " ");
   }
   return allPrinters;
}
 
/*
   *snip*
*/
 
// Tell FF &amp; TB which printers to offer a print dialogue for
defaultPref("print.printer_list", extract_allPrinters() );

The first lines check that the FileInterface object prototype is defined.  This is necessary because my autoconfig script broken into component scripts in the filesystem.  Besides making it easier to maintain, I can share functionality between Firefox and Thunderbird while excluding application-specific parts from the wrong autoconfig.

This function uses a prototype called FileInterface (explained later) to grep .printers for lines beginning with _all. If grep returns a match, the function takes the first one. print.printer_list takes a space-separated list of printers while _all is comma-separated, so the final thing to do is replace the commas with spaces.

Later on in the script I call defaultPref to set the printer list.

The FileInterface object prototype

nsILocalFile is the XPCOM interface used to access files on the client-side filesystem.  This object prototype provides only enough functionality to search files, but remains extensible should I need to create or modify.

// All you need is a (string) path
function FileInterface(path)
{  if( typeof(path) == "undefined" )
     throw("Need a path for FileInterface()");
   this.path = path;
}
 
/* Emulate the functionality of unix grep
   Takes a RegExp as it's only argument
   Returns an array of lines matching the RegExp
     E.G.: passwd = new FileInterface("/etc/passwd");
           lines = passwd.grep(/brundage/);
           // lines[0] = brundage:x:1002:1002::/home/brundage:/bin/zsh
*/
FileInterface.prototype.grep = function(re)
{  matches = undefined;
   if( re )
   {  line = {};
      matches = [];
      this.initIStream();
      do
      {  hasMore = this.iStream.readLine(line);
         if( re.test(line.value) )
            matches.push(line.value);
      } while(hasMore);
      this.iStream.close();
   }
   return matches;
}
 
/* Initializes a nsILineInputStream for higher-level functions
   Takes three arguments, assigning read-only defaults to undefined arguments.
   For possible values see: https://developer.mozilla.org/en/NsIFileInputStream
*/
FileInterface.prototype.initIStream = function(ioFlags,perm,behaviorFlags)
{  if( ! ioFlags )
     ioFlags = -1;  // Default mode (PR_READONLY)
   if( ! perm )
     perm = -1;  // Default mode (0)
   if( ! behaviorFlags )
     behaviorFlags = 0;
 
   // Initializing the stream requires an nsILocalFile.  Make one out of the path attribute.
   this.initLocalFile();
 
   // Get the nsIFileInputStream instance from the global Components variable
   this.iStream =  Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
   if( ! this.iStream )  // Bad Things
      throw("network/file-input-stream component does not exist");
 
   // Point the stream at the iLocalFile
   this.iStream.init(this.iLocalFile, ioFlags, perm, behaviorFlags);
   // Transform iStream into a nsILineInputStream
   this.iStream.QueryInterface(Components.interfaces.nsILineInputStream);
}
 
/* Initialize an nsILocalFile instance with the path attribute of this object
   Required for streams
*/
FileInterface.prototype.initLocalFile = function()
{  this.iLocalFile = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
   if( ! this.iLocalFile )  // Bad Things
     throw("file/local component does not exist");
 
   this.iLocalFile.initWithPath(this.path);
}

There you have it

I have only encountered this single situation that requires access to the local filesystem.  Can you think of others?

That is not who we are now

I don’t remember the year we decided to try, 2003 probably.  It was a New Year’s Eve, near the same hour of the night when I proposed to Adrienne.  I still remember the thrill of intentional unprotected sex.  A mixture of excitement, apprehension, hope and orgasam.  After that, the time between 2004 and 2010 was mostly pain and frustration.

It’s Infertility Awareness Week.  Right now 7.3 million Americans are somewhere along the same path we took; many of them are as lost as we were.  One of those people may be you.  I can not know what you feel, but I know it is crushingly real.

Ours is not a typical infertility story that odds-be-damned ends with a cherished miracle.  Adrienne and I held fast in the medical treatment crucible while the options frustratingly expired despite our efforts.  We wanted children but have physical and financial limits, so leave that struggle behind us, heads bloody but unbowed.

That we are still together evinces the commitment we made for better or worse, in sickness and in health.  We love each other for neither fecundity nor financial motives, but for the people we are; tempered by that crucible.

Although the strength of our relationship carried us, it took the help of an invaluable infertility therapist, kind family and trusted friends.  Without them, mourning the miscarriages and internalizing our situation would have been impossibly arduous.  Letting go of a lifetime of desires and expectations while trying to redefine and reorient yourself is not easy.

We are no longer the-couple-that-can’t-conceive.  The desire will never leave, but I refuse to let it shape me into a bitter childfree person who congregates on forums proclaiming I want nothing to do with children while ranting about affronts of “breeders” and their rugrats.  That is not who we are now.  We moved on.

Instead we find ways to take joy in each other, our careers, hobbies and the people around us.  It feels similar to that first night: excitement, apprehension, disappointment, and hope, but most of all it feels victorious.

Mapping Firefox & Thunderbird Behaviors to Preference Settings

Thunderbird settings

Individual in the Enterprise

Firefox, Thunderbird have full-fledged graphical settings editors.  It is easy for a user to change the behavior of his or her web browser with a few mouse clicks.  While this approach is sensible for the home user, GUIs hamper software configuration in the corporate enterprise.  Although Mozilla products textually represent preferences in a flat file, discovering the right text and value is not always simple.  I will outline some techniques I use to determine preference strings for a given behavior.

Preferences in Mission Control Desktop

Mozilla MCD autoconfig is an invaluable tool to the software administrator.  It runs at browser startup setting preferences according to corporate policy.  After starting, Firefox saves all settings to the user’s local prefs.js file.  The autoconfig API and user  prefs.js work with text preferences.  When you decide to change the application’s behavior your prefs.js is a good place to look.

Start clean

Quit Firefox.  Backup then remove your Firefox profile directory. On linux it is in $HOME/.mozilla/firefox. Then launch the browser starting with a good autoconfigured profile.

Compare

Take a backup of your user prefs.js from Firefox”s profile directory. ($HOME/.mozilla/firefox//prefs.js)  Make the behavior change and apply.  Use diff to compare the preference files.

An example

Suppose your company decides that Firefox should only keep third-party cookies for the lifetime of the browser.  Once the user closes Firefox, it will delete all third-party cookies.  Make the change (for version 3.6.3) in Settings –> Privacy –> Check Accept cookies from sites, then check Accept third-party cookies and change the dropdown to Keep until: I close Firefox.

Your preferences should look something like this:

0 apollo firefox/kzssiknu.default % diff -u prefs.js.pre prefs.js
--- prefs.js.pre    2010-04-22 12:57:30.000000000 -0500
+++ prefs.js        2010-04-22 12:57:39.000000000 -0500
@@ -230,6 +230,7 @@
 user_pref("lightweightThemes.persisted.footerURL", true);
 user_pref("lightweightThemes.persisted.headerURL", true);
 user_pref("metrics.upload.enable", false);
+user_pref("network.cookie.lifetimePolicy", 2);
 user_pref("network.cookie.prefsMigrated", true);
 user_pref("nglayout.debug.disable_xul_cache", true);
 user_pref("nglayout.debug.disable_xul_fastload", true);

See that a new preference called network.cookie.lifetimePolicy was inserted with value 2.  Use these in autoconfig, calling

// Remember third-party cookies until the browser closes
lockPref("network.cookie.lifetimePolicy", 2);

Sometimes it’s not that easy

The above method does not work all the time.  For example, Thunderbird’s password settings are not so obvious.  My company does not allow password storage so I needed to lock out that behavior.
Save Passwords

Inspecting the preferences GUI leads nowhere.  My first resource in these cases is the About:config_entries page on MozillaZine.  It’s a wild wiki page containing mostly-complete setting documentation for all the Mozilla products.  There you will find a table with the right information.

Name Type Meaning of Values
signon. rememberSignons Boolean True: (default): Enable the Password Manager
False Opposite of the above

The About:config_entries page also has pointers to the wiki’s Category:Preferences and The Preferential Project.  Each page has something the others lack.  When these resources fail, I sign on to irc.mozilla.org (as OccamRazor, old-school IRC etiquette rules apply.) or as a last resort, hit a search engine.

Document it

When you find the right text to twiddle document the behavior in your autoconfig as I did for the cookie lifetime above.

LDAP Queries in Mission Control Desktop

Previously, we saw that Mozilla MCD can inspect a user’s environment using getEnv().  It can also retrieve information from an LDAP directory.  I use this feature to inform Firefox and Thunderbird of the user in detail.  The corporate directory knows the user’s full name, mail server and authentication credentials.  autoconfig takes this and, among other useful things, constructs an email account for Thunderbird without user intervention.

The javascript API to deal with LDAP is a bit hackish, however it is all we have.  The first task is to define a function called processLDAPValues() which accepts a queryResults string as its only argument.  Inside processLDAPValues you extract return data from queryResults.

Instead of invoking processLDAPValues() directly, you call getLDAPAttributes() which in turn gets you to your function.  To illustrate, here is the code I use to query the corporate directory server and save the values for later use.

var userInfo = new Object();  // This will hold LDAP results
 
userInfo.envUser = getenv("LOGNAME");   // Unix UID
userInfo.envHome = getenv("HOME");      // User home directory
 
var ldapHost = "ldap.example.com";
var ldapBase = "dc=example,dc=com";
 
if( userInfo.envUser )
{  var ldapFilter = "uid=" + userInfo.envUser;  }
else
{  throw("Couldn't get UID from the environment");  }
 
// LDAP attributes to retrieve from the server
var ldapAttrs = new Array( "cn", "email", "employeenumber", "givenname", "mailhost", "sn", "uid" );
 
// Define how to process LDAP results before we make the call
function processLDAPValues(queryResults)
{  if( queryResults )
   {  // Build the userInfo object for later use
      for( var attr in ldapAttrs )
      {  userInfo[ ldapAttrs[attr] ] = getLDAPValue( queryResults, ldapAttrs[attr] );  }
   } else
   {  throw( "No LDAP results" );  }
}
 
// Call upon LDAP for the values in ldapAttrs array
// Uses the previous processLDAPValues()
getLDAPAttributes( ldapHost, ldapBase, ldapFilter, ldapAttrs.join(",") );

The first thing I do is create a userInfo object that will hold LDAP results for use later in the autoconfig.  To that object I add attributes for the user’s login name and home directory.

The next bit sets variables to contain the directory server’s hostname, base DN and the LDAP filter to use in the search. It’s a good idea to throw an error if there is no $LOGNAME.  (In a later post I will show how to enhance autoconfig error reporting.)

The ldapAttrs array names the attributes I want to return from LDAP.  Change this array to suit your environment.  The last line of code joins the array together with commas and feeds it to getLDAPAttributes along with the hostname, base DN and filter.  getLDAPAttributes is defined in MOZILLA_HOME/defaults/autoconfig/prefcalls.js and does the work to perform the query, then call your predefined processLDAPValues() function.

The example autoconfig script at developer.mozilla.org set preferences inside processLDAPValues, however this is a bad convention.  There are many preferences that require user information and separating pref() calls away from the main block of preference setting can be confusing.  As you can see here I simply run through the array of attributes I’m interested in and get the result from the LDAP query for that attribute, assigning it to the userInfo object.

Later on in the script I ask the userInfo object for those stored LDAP attributes. To set the hostname of the user’s mail server, for example, I call

// IMAP server name from corporate LDAP directory
defaultPref("mail.server.server1.hostname", userInfo.mailhost );

LDAP directories are a great resource. What attributes could you store in your corporate server?