At first I was going to detail how to make a friend list, put people in it and edit your privacy settings all in that little box. Instead you get a blog post with pretty images.
An Allegory
LinkedIn is the “social network” for your co-workers. I keep track of a lot of people there. Sometimes you like a person you work with enough to go to a Judas Priest concert with and you totally have to share that picture of you two with K.K. Downing. So now you and Judas Co-worker are friends and she’s friends with that annoying guy in accounting who sees her tagged in a photo with a Grammy award winning guitarist and you. That leads to Ned McNedly pressing the + Add as Friend button on your profile and you don’t really want the boss’s son to see how much you play Dumbo Racer. Sometimes the network works against you.
If you can’t send Ned to you clinically app-free LinkedIn profile you can friend him without giving away too many personal details. Time for some pretty pictures!
Go to that giant time-waster (that’s not the way I really feel about you Facebook, don’t be mad) and click on the Account button, then Edit Friends (where is this functionality IRL? JK I <3 u just the way you r).
Press the + Create a List button. Call it something that would burn the soulless zombies you work with to their very core if they knew they were on your list.
Lists are a great tool for categorizing your friends.
Now to shut out the unworthy. Click Account again and choose Privacy Settings. You should see a giant field titled Sharing on Facebook. Change to Custom, then click Customize Settings.
On the next screen you can “control” all the privacy leakages social networking enables. Try it out. Pull down the control next to Posts by Me and choose Customize.
Type the name of your new list in the Hide this From box and you can safely play Dumbo Racer all day long.
How Do You Know It’s Working?
Facebook gives you a way to check what your profile looks like to other people. Click on Account then Privacy Settings. You get the Sharing on Facebook screen pictured above. Choose Customize Settings again. In the upper right there will be a button labeled Preview my Profile. Try that.
Do like it says and start typing a friend’s name to see the world in their eyes.
About an hour ago I received notice of termination from CSC. Unless I find another position in this company of 94,000 people my last day of employment is December 30th, 2010. I am working the contacts accumulated there over my four years of work there.
I am also looking outside the company. Perhaps you, or someone you know, is looking for a system architect. I specialize in Solaris and linux with experience developing for Blackberry and iPhone. My resume will elaborate.
Although I have only worked for companies with more than 40,000 employees I think I would rather like to join a small or medium sized company.
This morning I heard a story about a large corporation and a judiciary both acting in favor of privacy over taxes. It warmed my cockles.
As the recession continues states look for ways to fix budget shortfalls. Raising or enacting taxes is politically incendiary in an election year but one state saw a way to increase revenue. Last year North Carolina asked Amazon for information about goods it shipped (complete with names & addresses) to residents between 2003 and 2010. It intended to collect sales tax owed by the good people of NC.
Even though Amazon does not charge sales tax on purchases the buyer may be responsible for paying come April 15th. North Carolina asked for records revealing identities and purchases as part of a tax audit of the online retailer. Amazon replied with detailed information regarding the items purchased, dates, amount of purchases, and county to which the items were shipped, but no personally identifying information. The state acknowledged the information is sufficient to assess sales taxes, but pressed for all the requested details.
With the aid of the ACLU Amazon fought back citing the First Amendment, specifically the Video Privacy Protection Act which bars “wrongful disclosure of video tape rental or sale records.” Since passage in 1988 the protection was extended to cover DVDs, video games and books. The federal district court in Seattle, Washington ruled in favor of Amazon.
Withholding the information was financially disadvantageous to the company because Amazon could not claim potential deductions, resulting in a higher tax bill. They did the right thing in the face of monetary loss.
The cynic in me sees Amazon putting up a great public relations campaign in favor of privacy, all the while selling our personal information in secret to other companies. Other states, including my home state of Texas, are pursuing uncollected tax revenue from Amazon and making a strong showing against the Tar Heel State may bolster their other cases.
Like many homebrew beer makers I also roast coffee. It’s quick and simple to produce better java than you can buy in most shops. While not a natural pairing, beer and coffee go together. There’s coffee porter, coffee stout, ..uh… espresso porter…. I like to do things differently.
I thought about putting hops in my coffee grounds just to turn things on their head. May still do that…. Instead, I picked a coffee drink and set about translating it to beer. Astute readers have already picked up that I decided upon cafélatté – a mix of espresso and steamed milk. This decision came mostly from the availability of lactose, milk sugar, for brewing.
The creative process
With one ingredient chosen I went a-browsing for the remainder of the grist. To my surprise I discovered Coffee malt, a moderately roasted 2-row barley (130-170L). My grain bills are almost always simple, with one or two specialty malts, so I decided to limit the beer to that one roast. I also had an idea for a fourth ingredient.
The third grain is, of course, the base malt. My normal favorite, Maris Otter, would likely be too nutty or biscuity for this beer. Again looking for new things to try I cane across Golden Promise. The description promised a “sweet, clean flavor” which I deemed perfect. Although the beer will be malt-dominant, it should show the coffee flavors more than anything else.
To round the grain bill off I decided on rolled oats to give the beer body and a smooth mouthfeel. The beer should feel like you are drinking a creamy latte and oats are just the thing. With the four ingredients selected, I placed my order.
Days later it arrived. Although the ingredients came 32 hours ahead of brew-day I was eager to taste my creation. Originally the coffee malt was to make up 15% of the grist, but I ordered enough to make 20. I mixed some of the Golden Promise and coffee malt and bit into a small handful.
Coffee malt does indeed smell and taste like coffee. It has that burnt bean starbucks flavor that I do not care for. But as the grain stewed in my mouth the base malt stepped forward to compliment the ash and roast. I nearly couldn’t stop myself from eating the entire grainbill.
Enough already, get to the recipe
OG
1.042
IBU
~23
Efficiency
75%
BU:GU
0.5
Fermentables
70% Golden Promise
20% Coffee Malt
5% Lactose
5% Rolled Oats
I use a water to grist ratio of two quarts per pound. Aimed for a single 60 minute step at 158ºF but hit 160º. Collected six gallons of sweet wort at 1.041 SG.
Kettle
23 IBUs of Hallertau at 45 minutes
Irish moss and lactose at 15 minutes
Yes, this is a 45 minute boil. Chilled and pitched onto a healthy yeast cake of White Labs London Ale yeast. OG came out to 1.045.
Whatsit taste like?
I like to taste my beers at many steps. Right out of the chiller is an important place. I am quite pleased with the way this beer matched my expectations of it. The lactose really gives the impression of milk. One of my tasters remarked that he expected a hot coffee drink from the aroma.
The wort smells of burnt grains and steamed milk. The lactose and rolled oats combine to give the beer noticeable body, but a the malty sweetness remains thin, like something you would expect from a beer of ten SG points lower. Hop bitterness is mild and should be nearly undetectable after fermentation.
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.
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.
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.account, mail.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");
// 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 accountvar 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 accountvar myGmailAccount = AccountManager.newAccount({ label:"gmail", type:"imap"});// Go about setting preferences on myGmailAccount as before
// 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
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:
/**
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();// superthis.addPrefBranch("mail");returnthis;}
copyPrototype( PreferenceFactory, Mail );
Mail.prototype.joinIds=function(collection,separator){if(! separator )
separator =",";if( collection ){ ret = collection[0].idfor( 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.accountNthis.addPrefBranch(["account",this.id]);this.identities=[];this.server=new Mail.Server(opts);// Need this either wayif( opts.type.match(/^^imap$|^pop$/i)){this.server.setPref("type", opts.type, opts.lockLevel);// Important that addIdentity be before the SMTP server is set upthis.addIdentity(new Mail.Identity(opts), opts.lockLevel);}elseif( 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.serverNthis.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.idNthis.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.smtpNthis.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 serverif(! 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));returnthis;};
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);}
/**
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 );
}
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
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
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
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
0 malfunction % ls patch_diff
sr-host-01.dir sr-host-02.dir
sr-host-01.pag sr-host-02.pag
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....
// 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 preferencesvar 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});
// 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 treethis.prefBranch="";// String representation of the branchif(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 );}
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));}}
/* 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) );
}
}
/**
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.jsvar result =typeof something;try{switch(result){case'string':break;case'boolean':break;case'number':break;case'object':case'function':switch(something.constructor){casenewString().constructor:
result ='String';break;casenewBoolean().constructor:
result ='Boolean';break;casenewNumber().constructor:
result ='Number';break;casenewArray().constructor:
result ='Array';break;casenew RegExp().constructor:
result ='RegExp';break;casenewDate().constructor:
result ='Date';break;caseFunction:
result ='Function';break;default:var m = something.constructor.toString().match(/function\s*([^( ]+)\(/);if(m)
result = m[1];elsebreak;}break;}}finally{ result = result.substr(0,1).toUpperCase()+ result.substr(1);return result;}}
/**
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;
}
}
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
# $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 undefinedthrow("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 & TB which printers to offer a print dialogue for
defaultPref("print.printer_list", extract_allPrinters());
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 & 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) pathfunction 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 variablethis.iStream= Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);if(!this.iStream)// Bad Thingsthrow("network/file-input-stream component does not exist");// Point the stream at the iLocalFilethis.iStream.init(this.iLocalFile, ioFlags, perm, behaviorFlags);// Transform iStream into a nsILineInputStreamthis.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 Thingsthrow("file/local component does not exist");this.iLocalFile.initWithPath(this.path);}
// 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?