<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Fetch Softworks</title>
	<atom:link href="http://fetchsoftworks.com/feed" rel="self" type="application/rss+xml" />
	<link>http://fetchsoftworks.com</link>
	<image>
		<url>/i/screen/fetch-softworks-favorite-icon.png</url>
		<title>Fetch Softworks</title>
		<link>http://fetchsoftworks.com</link>
	</image>
	<description>Just another WordPress weblog</description>
	<pubDate>Tue, 09 Feb 2010 13:40:47 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
			<item>
		<title>iPad Déjà Vu</title>
		<link>http://fetchsoftworks.com/blog/ipad-deja-vu</link>
		<comments>http://fetchsoftworks.com/blog/ipad-deja-vu#comments</comments>
		<pubDate>Fri, 05 Feb 2010 14:14:58 +0000</pubDate>
		<dc:creator>Jim Matthews</dc:creator>
				<category><![CDATA[Blog]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=358</guid>
		<description><![CDATA[<p>The announcement of the iPad, and the recognition by some (e.g. <a href="http://stevenf.tumblr.com/post/359224392/i-need-to-talk-to-you-about-computers-ive-been">Steven Frank</a>, <a href="http://speirs.org/blog/2010/1/29/future-shock.html">Frasier Spiers</a> and <a href="http://www.macworld.com/article/146040/2010/02/ipad.html">Dan Moren</a>) that this represents a new era of computing, gives me a powerful sensation of déjà vu.</p>
]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>My son Dylan is away at college, and on January 27 &mdash; the day of the iPad announcement &mdash; I was on my way to visit him when I noticed that I was walking by the campus bookstore. I flashed back to January 24, 1984, when I was a freshman at the same college, and had eagerly hurried into that same bookstore to witness the unveiling of Apple's new Macintosh computer. On both days it felt as if the world of computers was being made anew.</p><span id="more-358"></span><img src="http://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Macintosh_128k_transparency.png/180px-Macintosh_128k_transparency.png" class="floatleft" height="105" width="90" alt="The Original Macintosh" title="The old new world">

<p>The announcement of the iPad, and the recognition by some (e.g. <a href="http://stevenf.tumblr.com/post/359224392/i-need-to-talk-to-you-about-computers-ive-been">Steven Frank</a>, <a href="http://speirs.org/blog/2010/1/29/future-shock.html">Frasier Spiers</a> and <a href="http://www.macworld.com/article/146040/2010/02/ipad.html">Dan Moren</a>) that this represents a new era of computing, gives me a powerful sensation of déjà vu. Like the iPad, the Macintosh that Apple unveiled in 1984 was intended to radically simplify computing. Jef Raskin, who started the Macintosh project at Apple, believed that personal computers should be more like appliances: simple, rather than complicated, focused on specific tasks, rather than general-purpose. Raskin left Apple in 1982, but the idea of computer-as-appliance lived on in the Macintosh. If you wanted to write, you put in your MacWrite floppy disk and it became a word processing machine. If you wanted to draw, you put in your MacPaint disk and it became a digital sketchpad. Every application took over the entire screen, and turned the machine into a specialized device. In that way it was not so different from the iPhone of today (or the iPad of this coming April).</p>


<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Canon_Cat.jpg/180px-Canon_Cat.jpg" class="floatright" height="68" width="90" alt="Canon Cat" title="Not designed by Jonathan Ive">

<p>Raskin went on to create an even more appliance-like computer, the Canon Cat, which was a commercial failure. And the Macintosh itself only barely escaped an early demise. While Apple had planned to sell millions, the first year&rsquo;s sales did not reach 100,000, and it was only the fortuitous  emergence of desktop publishing that saved the Mac from cancellation. Over time the Mac acquired new features and capabilities, and new complexity, such that today we can look at it as an example of &ldquo;Old World&rdquo; computing that we hope to move beyond.</p>

<img src="http://images.apple.com/ipad/features/images/safari_hands_20100127.jpg" class="floatleft" height="95" width="125" alt="iPad" title="The computer for the rest of us?">

<p>For the iPad to truly usher in a new age of user-centric computing it needs to succeed commercially, and it needs to avoid the creeping complexity that turned the 128k Mac into the MacBook Pro of today. There are reasons for optimism on both scores. Past efforts &mdash; the Mac, the Cat, and the Newton &mdash; tried to build a new world from scratch, offering no compatibility with existing platforms. As John Gruber has persuasively <a href="http://daringfireball.net/2004/08/parlay">argued</a>, it was that incompatibility more than anything else that doomed the Mac to its small market share. It is simply too difficult to sell a computer that forces consumers to throw away everything they have learned and done before. The iPad does not have this weakness: it builds on the iPhone, which in turn built on the iPod and iLife and Safari and Mac OS X, which in turn built on the classic Mac OS and NeXTSTEP. The Mac started with three applications (MacWrite, MacPaint, and Multiplan); the iPad will start with over 100,000. The iPad, like the iPhone, also starts with the web, the most critical and adaptable &ldquo;application&rdquo; of all.</p>

<p>The iPad will therefore not succumb to the usual new platform Catch-22 of having neither enough market share to attract software, nor enough software to build market share. But will Apple resist the temptation to add new features until an iPad requires as much training and expertise as a general-purpose personal computer? It&rsquo;s much too early to tell, but the design decisions Apple has made to date suggest an encouraging restraint. They are helped by the fact that the iPhone and iPad can be huge successes without trying to be all things to all users. Customers who need a more flexible and complicated device can buy one, but most will be perfectly happy with an iPad. As new uses and technologies become mainstream Apple can incorporate them, while ensuring that the simplicity and usability of the platform is protected.</p>

<img src="/i/blog/att-logo.png" class="floatright" height="46" width="46" alt="AT&#038;T Bell logo" title="The Anti-Internet">

<p>As a developer I have mixed feelings about this incipient new age. I remember when the U.S. telecommunications system was run by a benevolent monopoly, with awesome reliability and ease of use, and equally awesome resistance to change. Ma Bell imposed a narrow bottleneck for technological progress, a bottleneck that was ultimately smashed by the anarchic personal computer market. It was the wide-open personal computer that let hobbyist developers write and sell BBS systems, PPP drivers, and ultimately web browsers &mdash; without having to first ask permission. It is not difficult to imagine some great new idea dying because it can&rsquo;t get through the App Store, or because the developer decides it is not even worth trying.</p>

<p>That said, I fervently hope that the iPad does represent a new world for computers. We developers build things to help people, but it is all too easy to focus on the things rather than the people. Today&rsquo;s computers are wonderful, but they aren&rsquo;t good enough for people. The iPad is another chance to do better.</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/blog/ipad-deja-vu/feed</wfw:commentRss>
		</item>
		<item>
		<title>Readability in NetNewsWire</title>
		<link>http://fetchsoftworks.com/blog/readability-in-netnewswire</link>
		<comments>http://fetchsoftworks.com/blog/readability-in-netnewswire#comments</comments>
		<pubDate>Thu, 04 Feb 2010 02:47:59 +0000</pubDate>
		<dc:creator>Jim Matthews</dc:creator>
				<category><![CDATA[Blog]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=357</guid>
		<description><![CDATA[<p>I read a lot of articles online, and sometimes the ads and navigation links make it difficult to concentrate on the text of article, much less on the ideas that the text is trying to communicate. The folks at <a href="http://www.arc90.com/">arc90</a> have come to the rescue with <a href="http://lab.arc90.com/experiments/readability/">Readability</a>, a JavaScript bookmarklet that reformats the current web page as a clean page of text:</p><span id="more-357"></span><p class="centerbar">
<a href="/i/blog/nyt-before-readability.png" class="thickbox" title="Before Readability"><img src="/i/blog/nyt-before-readability-thumbnail.png" class="inline" alt="Before Readability"></a>
<span class="bigrightarrow">&#8594;</span>
<a href="/i/blog/nyt-after-readability.png" class="thickbox" title="After Readability"><img src="/i/blog/nyt-after-readability-thumbnail.png" class="inline" alt="After Readability"></a>
</p>

<p>Readability works great in Safari, but I do most of my web reading in <a href="http://www.newsgator.com/INDIVIDUALS/NETNEWSWIRE/">NetNewsWire</a>, the RSS reader by Brent Simmons of Newsgator. And NetNewsWire does not have a bookmarks bar where I could put the Readability bookmarklet.</p>

<p>Fortunately, NetNewsWire does have a Script<span class="ui menu"><img src="/i/blog/script-menu-icon.png"></span>menu, and support for the <span class="ui command">do JavaScript</span> AppleScript command. That makes it possible to add Readability support to NetNewsWire by following these steps:</p>

<ol>
<li><p>Run Script Editor and create an AppleScript like the following: </p>
<pre class="applescript code">
<span class="keyword">set</span> <span class="variable">theJS</span> <span class="keyword">to</span> <span class="literal">"<span class="substitution">bookmarklet link</span>"</span>
set <span class="variable">jsScheme</span> to <span class="literal">"javascript:"</span>
<span class="keyword">if</span> (<span class="command">offset</span> of <span class="variable">jsScheme</span> in <span class="variable">theJS</span>) <span class="keyword">is</span> 1 <span class="keyword">then</span>
	set <span class="variable">theJS</span> to (<span class="class">characters</span> from (1 + (<span class="property">length</span> of <span class="variable">jsScheme</span>)) <span class="keyword">to</span> -1 <span class="keyword">of</span> <span class="variable">theJS</span>) <span class="keyword">as</span> <span class="class">string</span>
<span class="keyword">end if</span>
<span class="keyword">tell</span> <span class="class">application</span> <span class="literal">"NetNewsWire"</span>
	<span class="command">do JavaScript</span> <span class="variable">theJS</span>
<span class="keyword">end tell</span>
</pre>
</li>
<li>Go to the Readability <a href="http://lab.arc90.com/experiments/readability/">web page</a> to create a bookmarklet with your preferred settings</li>
<li>Right-click (or Control-click) the bookmark and choose <span class="ui command">Copy Link</span></li>
<li>Back in Script Editor, paste the link you just copied in place of the text <span class="applescript substitution">bookmarklet link</span>; it will start with "javascript:" and will be quite long
<li>Save your script (e.g. to the Desktop)</li>
<li>Choose <span class="ui command">Open Scripts Folder</span> from the Script<span class="ui menu"><img src="/i/blog/script-menu-icon.png"></span>menu in NetNewsWire</li>
<li>Copy your saved script to the NetNewsWire Scripts folder</li>
</ol>

<p>
Once you&#8217;ve followed these steps your script will be listed in NetNewsWire&#8217;s Script<span class="ui menu"><img src="/i/blog/script-menu-icon.png"></span>menu, and you can choose it to invoke Readability for the currently displayed web page.
</p>
]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>I read a lot of articles online, and sometimes the ads and navigation links make it difficult to concentrate on the text of article, much less on the ideas that the text is trying to communicate. The folks at <a href="http://www.arc90.com/">arc90</a> have come to the rescue with <a href="http://lab.arc90.com/experiments/readability/">Readability</a>, a JavaScript bookmarklet that reformats the current web page as a clean page of text:</p><span id="more-357"></span><p class="centerbar">
<a href="/i/blog/nyt-before-readability.png" class="thickbox" title="Before Readability"><img src="/i/blog/nyt-before-readability-thumbnail.png" class="inline" alt="Before Readability"></a>
<span class="bigrightarrow">&rarr;</span>
<a href="/i/blog/nyt-after-readability.png" class="thickbox" title="After Readability"><img src="/i/blog/nyt-after-readability-thumbnail.png" class="inline" alt="After Readability"></a>
</p>

<p>Readability works great in Safari, but I do most of my web reading in <a href="http://www.newsgator.com/INDIVIDUALS/NETNEWSWIRE/">NetNewsWire</a>, the RSS reader by Brent Simmons of Newsgator. And NetNewsWire does not have a bookmarks bar where I could put the Readability bookmarklet.</p>

<p>Fortunately, NetNewsWire does have a Script<span class="ui menu"><img src="/i/blog/script-menu-icon.png"></span>menu, and support for the <span class="ui command">do JavaScript</span> AppleScript command. That makes it possible to add Readability support to NetNewsWire by following these steps:</p>

<ol>
<li><p>Run Script Editor and create an AppleScript like the following: </p>
<pre class="applescript code">
<span class="keyword">set</span> <span class="variable">theJS</span> <span class="keyword">to</span> <span class="literal">"<span class="substitution">bookmarklet link</span>"</span>
set <span class="variable">jsScheme</span> to <span class="literal">"javascript:"</span>
<span class="keyword">if</span> (<span class="command">offset</span> of <span class="variable">jsScheme</span> in <span class="variable">theJS</span>) <span class="keyword">is</span> 1 <span class="keyword">then</span>
	set <span class="variable">theJS</span> to (<span class="class">characters</span> from (1 + (<span class="property">length</span> of <span class="variable">jsScheme</span>)) <span class="keyword">to</span> -1 <span class="keyword">of</span> <span class="variable">theJS</span>) <span class="keyword">as</span> <span class="class">string</span>
<span class="keyword">end if</span>
<span class="keyword">tell</span> <span class="class">application</span> <span class="literal">"NetNewsWire"</span>
	<span class="command">do JavaScript</span> <span class="variable">theJS</span>
<span class="keyword">end tell</span>
</pre>
</li>
<li>Go to the Readability <a href="http://lab.arc90.com/experiments/readability/">web page</a> to create a bookmarklet with your preferred settings</li>
<li>Right-click (or Control-click) the bookmark and choose <span class="ui command">Copy Link</span></li>
<li>Back in Script Editor, paste the link you just copied in place of the text <span class="applescript substitution">bookmarklet link</span>; it will start with "javascript:" and will be quite long
<li>Save your script (e.g. to the Desktop)</li>
<li>Choose <span class="ui command">Open Scripts Folder</span> from the Script<span class="ui menu"><img src="/i/blog/script-menu-icon.png"></span>menu in NetNewsWire</li>
<li>Copy your saved script to the NetNewsWire Scripts folder</li>
</ol>

<p>
Once you&rsquo;ve followed these steps your script will be listed in NetNewsWire&rsquo;s Script<span class="ui menu"><img src="/i/blog/script-menu-icon.png"></span>menu, and you can choose it to invoke Readability for the currently displayed web page.
</p>
</div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/blog/readability-in-netnewswire/feed</wfw:commentRss>
		</item>
		<item>
		<title>Buy Fetch, Help Haiti</title>
		<link>http://fetchsoftworks.com/fetch/news/buy-fetch-help-haiti</link>
		<comments>http://fetchsoftworks.com/fetch/news/buy-fetch-help-haiti#comments</comments>
		<pubDate>Wed, 20 Jan 2010 13:41:41 +0000</pubDate>
		<dc:creator>Jim Matthews</dc:creator>
				<category><![CDATA[Fetch]]></category>
		<category><![CDATA[News]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=356</guid>
		<description><![CDATA[<p><a href="http://www.indierelief.com"><img src="http://www.indierelief.com/images/ir_250.png"></a>
<p>If you <a href="/fetch/buy/cc">purchase</a> a Fetch 5.5 license on Wednesday, January 20, 2010, 100% of the proceeds will be donated to Partners In Health.</p>
<p><strong>UPDATE:</strong> Thank you to everyone who purchased Fetch 5.5 yesterday &#8212; you raised $3,742 for Partners In Health!</p>
]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p><a href="http://www.indierelief.com"><img src="http://www.indierelief.com/images/ir_250.png"></a>
</p>
<span id="more-356"></span><p>I am a fan of the writer Tracy Kidder (<em>The Soul of a New Machine</em>, <em>House</em>) and a few years ago I picked up his then-latest book, <a href="http://www.amazon.com/Mountains-Beyond-Quest-Farmer-Would/dp/0812973011/ref=tmm_pap_title_0"><em>Mountains Beyond Mountains</em></a>. It tells the story of a doctor, <a href="http://en.wikipedia.org/wiki/Paul_Farmer">Paul Farmer</a>, who (along with friends <a href="http://en.wikipedia.org/wiki/Ophelia_Dahl">Ophelia Dahl</a> and <a href="http://en.wikipedia.org/wiki/Jim_Yong_Kim">Jim Yong Kim</a>) created an organization called <a href="http://pih.org/">Partners In Health</a>. PIH was founded to deliver high quality health care to the people of Haiti&rsquo;s Central Plateau. Working with the people of Haiti, PIH&rsquo;s efforts there have grown from a single clinic in 1985 to a system of hospitals, clinics, and community health workers that provided 2.6 million patient visits in 2009, all of them free of charge. It is a moving and inspiring story that convinced me to become a financial supporter of PIH&rsquo;s work.</p>

<p>In the aftermath of the earthquake that struck Port-au-Prince on January 12, 2010, Partners In Health is perhaps the largest functioning medical provider left in Haiti. Their own facilities are far enough from the epicenter to have escaped serious damage, and their trained medical teams have been working around the clock to treat survivors. If they can afford to purchase the supplies they need, they will be able to save thousands of lives, not only from earthquake injuries, but also from the infections and other public health threats that will emerge in the weeks and months ahead.</p>

<p>To help make that happen, Fetch Softworks is one of nearly 150 independent Mac and iPhone developers participating in <a href="http://www.indierelief.com">Indie+Relief</a>, a group fundraiser for Haiti. If you <a href="/fetch/buy/cc">purchase</a> a Fetch 5.5 license on Wednesday, January 20, 2010, 100% of the proceeds will be donated to Partners In Health. If you already have a Fetch license, please take a look at the other applications that are <a href="http://www.indierelief.com">for sale</a>: you can pick up a great app and help Haiti at the same time. And if you do not need any software, please visit the PIH <a href="http://pih.org">website</a>, learn more about their work, make a <a href="http://standwithhaiti.org/haiti">donation</a>, and spread the word.</p>

<p>Many thanks to Justin Williams of <a href="http://secondgearsoftware.com">Second Gear Software</a> for organizing <a href="http://www.indierelief.com">Indie+Relief</a>.</p>

<p><strong>UPDATE:</strong> Thank you to everyone who purchased Fetch 5.5 yesterday &mdash; you raised $3,742 for Partners In Health!</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/fetch/news/buy-fetch-help-haiti/feed</wfw:commentRss>
		</item>
		<item>
		<title>Believe it or not: a live iPhone reality show and 50% off Fetch</title>
		<link>http://fetchsoftworks.com/fetch/news/believe-it-or-not-a-live-iphone-reality-show-and-50-off-fetch</link>
		<comments>http://fetchsoftworks.com/fetch/news/believe-it-or-not-a-live-iphone-reality-show-and-50-off-fetch#comments</comments>
		<pubDate>Thu, 10 Dec 2009 08:08:52 +0000</pubDate>
		<dc:creator>Jim Matthews</dc:creator>
				<category><![CDATA[Fetch]]></category>
		<category><![CDATA[News]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=355</guid>
		<description><![CDATA[<p>Hello from Italy! While my Fetch colleagues are toiling away at their posts, I am taking a break from Fetch to have an adventure. </p>
]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>Hello from Italy!</p>

<p>While my Fetch colleagues are toiling away at their posts, I am taking a break from Fetch to have an adventure. I am one of ten developers from around the world gathered together in Venice, Italy by <a href="http://commandguru.com">Command Guru</a> to develop an iPhone app from scratch in seven days. If that were not enough, the entire experience is being streamed live at <a href="http://commandguru.com">commandguru.com</a>. </p>

<p>So if you ever wondered what goes on behind the scenes to produce the apps you use, you can tune in to watch us sketch, discuss, argue, and type our way to actual software. We have been at it for three days now, and it&rsquo;s been great fun to work with this terrific group of developers, and to get instant feedback from viewers around the globe. Not to mention all the fantastic Italian food!</p>

<p>In honor of this unlikely event we&rsquo;re offering an equally improbable promotion: a Fetch half-off sale. Today and tomorrow, December 10 and 11, 2009, you can get a 50% discount on Fetch by entering the coupon code <a href="https://secure.esellerate.net/secure/prefill.aspx?s=STR0527091555&#038;page=cart.htm&#038;cmd=BUY&#038;_cartitem0.skurefnum=SKU27707691229&#038;_cartitem0.quantity=1&#038;_Shopper.CouponName=COMMANDGURU&#038;options=PREVALIDATECOUPON">COMMANDGURU</a>.</p>

<p>Ciao!</p>
</div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/fetch/news/believe-it-or-not-a-live-iphone-reality-show-and-50-off-fetch/feed</wfw:commentRss>
		</item>
		<item>
		<title>Subversion and SSH authentication shenanigans</title>
		<link>http://fetchsoftworks.com/blog/subversion-and-ssh-authentication-shenanigans</link>
		<comments>http://fetchsoftworks.com/blog/subversion-and-ssh-authentication-shenanigans#comments</comments>
		<pubDate>Mon, 23 Nov 2009 19:15:28 +0000</pubDate>
		<dc:creator>Ben Artin</dc:creator>
				<category><![CDATA[Blog]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=353</guid>
		<description><![CDATA[<p>The default behavior of Subversion when tunneled over SSH works well for simple cases. I encountered some more complex situations which required digging into advanced SSH features, and built some simple tools that make our Subversion life easier.</p><span id="more-353"></span><h4>Specifying a username for SSH repositories</h4>

<p>By default, when you connect to a Subversion repository over SSH, Subversion will assume that the remote username is the same as the local username. When this is not true, you can force a different username to be used. When you check out the repository, use:</p>

<pre class="code"><span class="keyword">svn</span> <span class="keyword">checkout</span> <span class="literal">svn+ssh://svn-username@svn-hostname/svn-path</span></pre>

<p>Subversion will parse the username out of the URL, and pass it through to SSH, which will then prompt you for the password for the correct account.</p>

<h4>Avoiding multiple password prompts</h4>

<p>If you have a simple SSH repository, you will be asked for your password once at the beginning of a Subversion command. However, if your repository contains <a href="http://svnbook.red-bean.com/en/1.0/ch07s03.html">externals</a>, you will be asked for your password again for each external that requires it. If you have multiple externals on the same server, you get to enter the same password multiple times.</p>

<p>To work around this you can use SSH session sharing. Session sharing lets multiple SSH connections to the same server share a single network connection. That also causes multiple SSH connections to the same server to share credentials, and therefore you don&#8217;t have to reenter your passwords during a single Subversion command.</p>

<p>To set up SSH session sharing, you need to add this to your <code>~/.ssh/config</code>:</p>

<pre class="code"><span class="keyword">ControlMaster</span> <span class="literal">auto</span>
<span class="keyword">ControlPath</span> <span class="literal">~/.ssh/master-%r@%h:%p</span></pre>

<p>To verify that it works, open an SSH connection in Terminal, then open a new Terminal window and open another SSH connection to the same account as the first one. The second will not ask for your password.</p>

<p>The configuration above enables SSH connection sharing for all SSH connections. If, instead, you want to only enable connection sharing for your Subversion server, put the following <strong>at the bottom</strong> of your <code>~/.ssh/config</code>:</p>

<pre class="code"><span class="keyword">Host</span> <span class="literal">svn-hostname</span>
<span class="keyword">ControlMaster</span> <span class="literal">auto</span>
<span class="keyword">ControlPath</span> <span class="literal">~/.ssh/master-%r@%h:%p</span></pre>

<h4>Avoiding multiple password prompts &#8212; the pair.com users&#8217; version</h4>

<p>On <a href="http://www.pair.com/">pair.com</a> &#8212; I imagine this may also be true of some other hosting providers &#8212; the above recipe with SSH session sharing works great as long as your repository has fewer than ten externals. Once you get to ten externals, you see a &#8220;channel 10: open failed&#8221; message and your Subversion command dies.</p>

<p>This is because pair.com limits SSH session sharing to ten simultaneous connections to a given server. Unfortunately, Subversion keeps every SSH connection open until it&#8217;s done with all of them. If you have ten externals on the same server, you wind up with eleven simultaneous SSH connections (ten for the externals and one for the root of the repository), and so your SSH and Subversion error out.</p>

<p>The good news is that SSH already has a mechanism to delegate its password prompting to another tool (this is what programs like Fetch use to give a graphical user interface for entering your SFTP password). If you had a tool that handled SSH password prompting, but was also capable of remembering the passwords you gave it and not asking you to reenter them, you would only get asked for your password once.</p>

<p>An SSH password prompter is relaunched for every prompt, so it needs a place to stash passwords during a single Subversion command. An easy way to handle this is to write a tool which creates a password store and then launches Subversion. Using that tool instead of Subversion will provide the password prompter with the password repository it needs to work correctly.</p>

<p>This is what the Subversion replacement looks like:</p>

<pre id="svn-wrapper" class="code"><span class="comment">#!/usr/local/bin/python</span>

<span class="keyword">from</span> <span class="system module">__future__</span> <span class="keyword">import</span> <span class="system constant">with_statement</span>
<span class="keyword">from</span> <span class="system module">subprocess</span> <span class="keyword">import</span> <span class="system function">call</span>
<span class="keyword">from</span> <span class="system module">tempfile</span> <span class="keyword">import</span> <span class="system function">mkdtemp</span>
<span class="keyword">from</span> <span class="system module">os</span> <span class="keyword">import</span> <span class="system function">mkfifo</span>, <span class="system variable">environ</span>
<span class="keyword">from</span> <span class="system module">os</span>.<span class="system module">path</span> <span class="keyword">import</span> <span class="system function">join</span>, <span class="system function">expanduser</span>
<span class="keyword">from</span> <span class="system module">sys</span> <span class="keyword">import</span> <span class="system variable">argv</span>
<span class="keyword">from</span> <span class="system module">threading</span> <span class="keyword">import</span> <span class="system class">Thread</span>
<span class="keyword">from</span> <span class="system module">pickle</span> <span class="keyword">import</span> <span class="system function">load</span>, <span class="system function">dump</span>
<span class="keyword">from</span> <span class="system module">getpass</span> <span class="keyword">import</span> <span class="system function">getpass</span>

<span class="local variable">request_pipe_path</span> = <span class="system function">join</span>(<span class="system function">mkdtemp</span>(), <span class="literal">"svn-askpass-requests"</span>)
<span class="local variable">response_pipe_path</span> = <span class="system function">join</span>(<span class="system function">mkdtemp</span>(), <span class="literal">"svn-askpass-responses"</span>)

<span class="system function">mkfifo</span>(<span class="local variable">request_pipe_path</span>)
<span class="system function">mkfifo</span>(<span class="local variable">response_pipe_path</span>)

<span class="local variable">args</span> = <span class="system variable">argv</span>[<span class="literal">1</span>:]

<span class="keyword">def</span> <span class="local function">prompt_loop</span>():
    <span class="local variable">cache</span> = {}

    <span class="keyword">while</span> <span class="system constant">True</span>:
        <span class="keyword">with</span> <span class="system function">open</span>(<span class="local variable">request_pipe_path</span>, <span class="literal">"r"</span>) <span class="keyword">as</span> <span class="local variable">request_pipe</span>:
            <span class="local variable">hidden</span>, <span class="local variable">request</span> = <span class="system function">load</span>(<span class="local variable">request_pipe</span>)

        <span class="keyword">if</span> <span class="keyword">not</span> <span class="local variable">request</span> <span class="keyword">in</span> <span class="local variable">cache</span>:
            <span class="keyword">if</span> <span class="local variable">hidden</span>:
                <span class="local variable">cache</span>[<span class="local variable">request</span>] = <span class="system function">getpass</span>(<span class="local variable">request</span>)
            <span class="keyword">else</span>:
                <span class="local variable">cache</span>[<span class="local variable">request</span>] = <span class="system function">raw_input</span>(<span class="local variable">request</span>)
            
        <span class="keyword">with</span> <span class="system function">open</span>(<span class="local variable">response_pipe_path</span>, <span class="literal">"w"</span>) <span class="keyword">as</span> <span class="local variable">response_pipe</span>:
            <span class="system function">dump</span>(<span class="local variable">cache</span>[<span class="local variable">request</span>], <span class="local variable">response_pipe</span>)
            
<span class="local variable">prompter</span> = <span class="system class">Thread</span>(target=<span class="local function">prompt_loop</span>)
<span class="local variable">prompter</span>.<span class="field">daemon</span> = <span class="system constant">True</span>

<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS"</span>] = <span class="system function">expanduser</span>("~/.subversion/svn-ssh-askpass")
<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_REQUEST_PIPE"</span>] = <span class="local variable">request_pipe_path</span>
<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_RESPONSE_PIPE"</span>] = <span class="local variable">response_pipe_path</span>

<span class="local variable">prompter</span>.<span class="system function">start</span>()

<span class="system function">exit</span>(<span class="system function">call</span>([<span class="literal">"/usr/local/bin/svn"</span>] + <span class="local variable">args</span>))</pre>

<p>There isn&#8217;t much to it &#8212; it creates two pipes, on one of which it reads prompts and on the other of which it provides user responses. Before starting Subversion, it spawns a background thread, from which it listens for prompts. If it receives a prompt that it has already seen, it returns the same answer as before; if it receives a prompt it hasn&#8217;t already seen, it presents it to the user and stores the answer for later.</p>

<p>If you weren&#8217;t familiar with them before, you may find these interesting:</p>
<ul>
<li><a href="http://docs.python.org/library/tempfile.html#tempfile.mkdtemp">tempfile.mkdtemp</a>: securely creates a temporary directory readable only by its owner</li>
<li><a href="http://docs.python.org/library/getpass.html#getpass.getpass">getpass.getpass</a>: securely prompts for a password</li>
<li><a href="http://docs.python.org/library/threading.html#threading.Thread.daemon">threading.Thread.daemon</a>: if set on a thread, then the thread is automatically killed off when all other threads are done</li>
<li><a href="http://docs.python.org/library/os.path.html#os.path.expanduser">os.path.expanduser</a>: expands &#8764; to user&#8217;s home path</li>
<li>os.environ["SSH_ASKPASS"]: the way to tell SSH to delegate password prompting</li>
</ul>

<p>This Subversion wrapper sets up SSH to use <code>~/.subversion/svn-ssh-askpass</code> for password prompting. However, SSH doesn't use the value of <code>SSH_ASKPASS</code> unless a few specific conditions are met, and those conditions are not met when Subversion command line invokes SSH. Therefore, we also need to create a wrapper for SSH, which will set things up so that password prompting is delegated to the prompter, and then we need to tell Subversion to invoke that SSH wrapper instead of SSH.</p>

<p>This is what the wrapper looks like:</p>

<pre id="ssh-wrapper" class="code"><span class="comment">#!/usr/local/bin/python</span>
<span class="keyword">from</span> <span class="system module">os</span> <span class="keyword">import</span> <span class="system function">setsid</span>, <span class="system variable">environ</span>
<span class="keyword">from</span> <span class="system module">subprocess</span> <span class="keyword">import</span> <span class="system function">call</span>
<span class="keyword">from</span> <span class="system module">sys</span> <span class="keyword">import</span> <span class="system ">argv</span>

<span class="comment"># We have to invoke SSH from a process with no controlling terminal
# in order to get SSH_ASKPASS to work</span>
<span class="system function">setsid</span>()
<span class="system variable">environ</span>[<span class="literal">"DISPLAY"</span>] = <span class="literal">"0"</span>

<span class="system function">exit</span>(<span class="system function">call</span>([<span class="literal">"/usr/bin/ssh"</span>] + <span class="system variable">argv</span>[<span class="literal">1</span>:]))</pre>

<p>If you don&#8217;t really get all the details of this, that&#8217;s OK. I don&#8217;t either; it&#8217;s Soviet-era UNIX programming, I just Googled until I pieced it together.</p>

<p>Save that at <code>~/.subversion/svn-ssh-detach</code> and tell Subversion to use this SSH wrapper by adding this to your <code>~/.subversion/config</code>:</p>

<pre id="svn-config" class="code">[<span class="keyword">tunnels</span>]
<span class="keyword">ssh</span> = <span class="literal">/Users/ben/.subversion/svn-ssh-detach</span></pre>

<p>Sadly, Subversion doesn&#8217;t expand <code>~</code> in tunnel configuration, so you have to put the full path to your home there.</p>

<p>OK, now we have a Subversion wrapper that can cache passwords, and we have an SSH wrapper that can delegate its password prompting to a custom prompter. All we need to do to close the loop is write a custom prompter that talks to the Subversion wrapper instead of directly prompting you:</p>

<pre id="ssh-askpass" class="code"><span class="comment">#!/usr/local/bin/python</span>

<span class="keyword">from</span> <span class="system module">__future__</span> <span class="keyword">import</span> <span class="system constant">with_statement</span>
<span class="keyword">from</span> <span class="system module">sys</span> <span class="keyword">import</span> <span class="system variable">argv</span>
<span class="keyword">from</span> <span class="system module">os</span> <span class="keyword">import</span> <span class="system variable">environ</span>
<span class="keyword">from</span> <span class="system module">pickle</span> <span class="keyword">import</span> <span class="system function">load</span>, <span class="system function">dump</span>

<span class="local variable">prompt</span> = <span class="system variable">argv</span>[<span class="literal">1</span>]
<span class="local variable">secret</span> = <span class="literal">False</span>

<span class="keyword">if</span> <span class="literal">"password:"</span> <span class="keyword">in</span> <span class="local variable">prompt</span>:
	<span class="local variable">secret</span> = <span class="literal">True</span>

<span class="keyword">with</span> <span class="system function">open</span>(<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_REQUEST_PIPE"</span>], <span class="literal">"w"</span>) <span class="keyword">as</span> <span class="local variable">request_pipe</span>:
	<span class="system function">dump</span>((<span class="local variable">secret</span>, <span class="local variable">prompt</span>), <span class="local variable">request_pipe</span>)

<span class="keyword">with</span> <span class="system function">open</span>(<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_RESPONSE_PIPE"</span>], <span class="literal">"r"</span>) <span class="keyword">as</span> <span class="local variable">response_pipe</span>:
	<span class="keyword">print</span> <span class="system function">load</span>(<span class="local variable">response_pipe</span>)
</pre>

<p>And there you have it. To recap:</p>
<ul>
<li><a href="#svn-wrapper">Subversion wrapper</a>: put it anywhere you like, make sure it&#8217;s executable</li>
<li><a href="#svn-config">Subversion configuration changes</a>: put them in <code>~/.subversion/config</code></li>
<li><a href="#ssh-wrapper">SSH wrapper</a>: put it in <code>~/.subversion/svn-ssh-detach</code>, make sure it&#8217;s executable</li>
<li><a href="#ssh-askpass">SSH prompter</a>: put it in <code>~/.subversion/svn-ssh-askpass</code>, make sure it&#8217;s executable</li>
</ul>

<p>From there on, invoke your Subversion wrapper instead of <code>svn</code> (or create an alias that runs the wrapper when you type <code>svn</code>).</p>]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>The default behavior of Subversion when tunneled over SSH works well for simple cases. I encountered some more complex situations which required digging into advanced SSH features, and built some simple tools that make our Subversion life easier.</p><span id="more-353"></span><h4>Specifying a username for SSH repositories</h4>

<p>By default, when you connect to a Subversion repository over SSH, Subversion will assume that the remote username is the same as the local username. When this is not true, you can force a different username to be used. When you check out the repository, use:</p>

<pre class="code"><span class="keyword">svn</span> <span class="keyword">checkout</span> <span class="literal">svn+ssh://svn-username@svn-hostname/svn-path</span></pre>

<p>Subversion will parse the username out of the URL, and pass it through to SSH, which will then prompt you for the password for the correct account.</p>

<h4>Avoiding multiple password prompts</h4>

<p>If you have a simple SSH repository, you will be asked for your password once at the beginning of a Subversion command. However, if your repository contains <a href="http://svnbook.red-bean.com/en/1.0/ch07s03.html">externals</a>, you will be asked for your password again for each external that requires it. If you have multiple externals on the same server, you get to enter the same password multiple times.</p>

<p>To work around this you can use SSH session sharing. Session sharing lets multiple SSH connections to the same server share a single network connection. That also causes multiple SSH connections to the same server to share credentials, and therefore you don&rsquo;t have to reenter your passwords during a single Subversion command.</p>

<p>To set up SSH session sharing, you need to add this to your <code>~/.ssh/config</code>:</p>

<pre class="code"><span class="keyword">ControlMaster</span> <span class="literal">auto</span>
<span class="keyword">ControlPath</span> <span class="literal">~/.ssh/master-%r@%h:%p</span></pre>

<p>To verify that it works, open an SSH connection in Terminal, then open a new Terminal window and open another SSH connection to the same account as the first one. The second will not ask for your password.</p>

<p>The configuration above enables SSH connection sharing for all SSH connections. If, instead, you want to only enable connection sharing for your Subversion server, put the following <strong>at the bottom</strong> of your <code>~/.ssh/config</code>:</p>

<pre class="code"><span class="keyword">Host</span> <span class="literal">svn-hostname</span>
<span class="keyword">ControlMaster</span> <span class="literal">auto</span>
<span class="keyword">ControlPath</span> <span class="literal">~/.ssh/master-%r@%h:%p</span></pre>

<h4>Avoiding multiple password prompts &mdash; the pair.com users&rsquo; version</h4>

<p>On <a href="http://www.pair.com/">pair.com</a> &mdash; I imagine this may also be true of some other hosting providers &mdash; the above recipe with SSH session sharing works great as long as your repository has fewer than ten externals. Once you get to ten externals, you see a &ldquo;channel 10: open failed&rdquo; message and your Subversion command dies.</p>

<p>This is because pair.com limits SSH session sharing to ten simultaneous connections to a given server. Unfortunately, Subversion keeps every SSH connection open until it&rsquo;s done with all of them. If you have ten externals on the same server, you wind up with eleven simultaneous SSH connections (ten for the externals and one for the root of the repository), and so your SSH and Subversion error out.</p>

<p>The good news is that SSH already has a mechanism to delegate its password prompting to another tool (this is what programs like Fetch use to give a graphical user interface for entering your SFTP password). If you had a tool that handled SSH password prompting, but was also capable of remembering the passwords you gave it and not asking you to reenter them, you would only get asked for your password once.</p>

<p>An SSH password prompter is relaunched for every prompt, so it needs a place to stash passwords during a single Subversion command. An easy way to handle this is to write a tool which creates a password store and then launches Subversion. Using that tool instead of Subversion will provide the password prompter with the password repository it needs to work correctly.</p>

<p>This is what the Subversion replacement looks like:</p>

<pre id="svn-wrapper" class="code"><span class="comment">#!/usr/local/bin/python</span>

<span class="keyword">from</span> <span class="system module">__future__</span> <span class="keyword">import</span> <span class="system constant">with_statement</span>
<span class="keyword">from</span> <span class="system module">subprocess</span> <span class="keyword">import</span> <span class="system function">call</span>
<span class="keyword">from</span> <span class="system module">tempfile</span> <span class="keyword">import</span> <span class="system function">mkdtemp</span>
<span class="keyword">from</span> <span class="system module">os</span> <span class="keyword">import</span> <span class="system function">mkfifo</span>, <span class="system variable">environ</span>
<span class="keyword">from</span> <span class="system module">os</span>.<span class="system module">path</span> <span class="keyword">import</span> <span class="system function">join</span>, <span class="system function">expanduser</span>
<span class="keyword">from</span> <span class="system module">sys</span> <span class="keyword">import</span> <span class="system variable">argv</span>
<span class="keyword">from</span> <span class="system module">threading</span> <span class="keyword">import</span> <span class="system class">Thread</span>
<span class="keyword">from</span> <span class="system module">pickle</span> <span class="keyword">import</span> <span class="system function">load</span>, <span class="system function">dump</span>
<span class="keyword">from</span> <span class="system module">getpass</span> <span class="keyword">import</span> <span class="system function">getpass</span>

<span class="local variable">request_pipe_path</span> = <span class="system function">join</span>(<span class="system function">mkdtemp</span>(), <span class="literal">"svn-askpass-requests"</span>)
<span class="local variable">response_pipe_path</span> = <span class="system function">join</span>(<span class="system function">mkdtemp</span>(), <span class="literal">"svn-askpass-responses"</span>)

<span class="system function">mkfifo</span>(<span class="local variable">request_pipe_path</span>)
<span class="system function">mkfifo</span>(<span class="local variable">response_pipe_path</span>)

<span class="local variable">args</span> = <span class="system variable">argv</span>[<span class="literal">1</span>:]

<span class="keyword">def</span> <span class="local function">prompt_loop</span>():
    <span class="local variable">cache</span> = {}

    <span class="keyword">while</span> <span class="system constant">True</span>:
        <span class="keyword">with</span> <span class="system function">open</span>(<span class="local variable">request_pipe_path</span>, <span class="literal">"r"</span>) <span class="keyword">as</span> <span class="local variable">request_pipe</span>:
            <span class="local variable">hidden</span>, <span class="local variable">request</span> = <span class="system function">load</span>(<span class="local variable">request_pipe</span>)

        <span class="keyword">if</span> <span class="keyword">not</span> <span class="local variable">request</span> <span class="keyword">in</span> <span class="local variable">cache</span>:
            <span class="keyword">if</span> <span class="local variable">hidden</span>:
                <span class="local variable">cache</span>[<span class="local variable">request</span>] = <span class="system function">getpass</span>(<span class="local variable">request</span>)
            <span class="keyword">else</span>:
                <span class="local variable">cache</span>[<span class="local variable">request</span>] = <span class="system function">raw_input</span>(<span class="local variable">request</span>)
            
        <span class="keyword">with</span> <span class="system function">open</span>(<span class="local variable">response_pipe_path</span>, <span class="literal">"w"</span>) <span class="keyword">as</span> <span class="local variable">response_pipe</span>:
            <span class="system function">dump</span>(<span class="local variable">cache</span>[<span class="local variable">request</span>], <span class="local variable">response_pipe</span>)
            
<span class="local variable">prompter</span> = <span class="system class">Thread</span>(target=<span class="local function">prompt_loop</span>)
<span class="local variable">prompter</span>.<span class="field">daemon</span> = <span class="system constant">True</span>

<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS"</span>] = <span class="system function">expanduser</span>("~/.subversion/svn-ssh-askpass")
<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_REQUEST_PIPE"</span>] = <span class="local variable">request_pipe_path</span>
<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_RESPONSE_PIPE"</span>] = <span class="local variable">response_pipe_path</span>

<span class="local variable">prompter</span>.<span class="system function">start</span>()

<span class="system function">exit</span>(<span class="system function">call</span>([<span class="literal">"/usr/local/bin/svn"</span>] + <span class="local variable">args</span>))</pre>

<p>There isn&rsquo;t much to it &mdash; it creates two pipes, on one of which it reads prompts and on the other of which it provides user responses. Before starting Subversion, it spawns a background thread, from which it listens for prompts. If it receives a prompt that it has already seen, it returns the same answer as before; if it receives a prompt it hasn&rsquo;t already seen, it presents it to the user and stores the answer for later.</p>

<p>If you weren&rsquo;t familiar with them before, you may find these interesting:</p>
<ul>
<li><a href="http://docs.python.org/library/tempfile.html#tempfile.mkdtemp">tempfile.mkdtemp</a>: securely creates a temporary directory readable only by its owner</li>
<li><a href="http://docs.python.org/library/getpass.html#getpass.getpass">getpass.getpass</a>: securely prompts for a password</li>
<li><a href="http://docs.python.org/library/threading.html#threading.Thread.daemon">threading.Thread.daemon</a>: if set on a thread, then the thread is automatically killed off when all other threads are done</li>
<li><a href="http://docs.python.org/library/os.path.html#os.path.expanduser">os.path.expanduser</a>: expands &sim; to user&rsquo;s home path</li>
<li>os.environ["SSH_ASKPASS"]: the way to tell SSH to delegate password prompting</li>
</ul>

<p>This Subversion wrapper sets up SSH to use <code>~/.subversion/svn-ssh-askpass</code> for password prompting. However, SSH doesn't use the value of <code>SSH_ASKPASS</code> unless a few specific conditions are met, and those conditions are not met when Subversion command line invokes SSH. Therefore, we also need to create a wrapper for SSH, which will set things up so that password prompting is delegated to the prompter, and then we need to tell Subversion to invoke that SSH wrapper instead of SSH.</p>

<p>This is what the wrapper looks like:</p>

<pre id="ssh-wrapper" class="code"><span class="comment">#!/usr/local/bin/python</span>
<span class="keyword">from</span> <span class="system module">os</span> <span class="keyword">import</span> <span class="system function">setsid</span>, <span class="system variable">environ</span>
<span class="keyword">from</span> <span class="system module">subprocess</span> <span class="keyword">import</span> <span class="system function">call</span>
<span class="keyword">from</span> <span class="system module">sys</span> <span class="keyword">import</span> <span class="system ">argv</span>

<span class="comment"># We have to invoke SSH from a process with no controlling terminal
# in order to get SSH_ASKPASS to work</span>
<span class="system function">setsid</span>()
<span class="system variable">environ</span>[<span class="literal">"DISPLAY"</span>] = <span class="literal">"0"</span>

<span class="system function">exit</span>(<span class="system function">call</span>([<span class="literal">"/usr/bin/ssh"</span>] + <span class="system variable">argv</span>[<span class="literal">1</span>:]))</pre>

<p>If you don&rsquo;t really get all the details of this, that&rsquo;s OK. I don&rsquo;t either; it&rsquo;s Soviet-era UNIX programming, I just Googled until I pieced it together.</p>

<p>Save that at <code>~/.subversion/svn-ssh-detach</code> and tell Subversion to use this SSH wrapper by adding this to your <code>~/.subversion/config</code>:</p>

<pre id="svn-config" class="code">[<span class="keyword">tunnels</span>]
<span class="keyword">ssh</span> = <span class="literal">/Users/ben/.subversion/svn-ssh-detach</span></pre>

<p>Sadly, Subversion doesn&rsquo;t expand <code>~</code> in tunnel configuration, so you have to put the full path to your home there.</p>

<p>OK, now we have a Subversion wrapper that can cache passwords, and we have an SSH wrapper that can delegate its password prompting to a custom prompter. All we need to do to close the loop is write a custom prompter that talks to the Subversion wrapper instead of directly prompting you:</p>

<pre id="ssh-askpass" class="code"><span class="comment">#!/usr/local/bin/python</span>

<span class="keyword">from</span> <span class="system module">__future__</span> <span class="keyword">import</span> <span class="system constant">with_statement</span>
<span class="keyword">from</span> <span class="system module">sys</span> <span class="keyword">import</span> <span class="system variable">argv</span>
<span class="keyword">from</span> <span class="system module">os</span> <span class="keyword">import</span> <span class="system variable">environ</span>
<span class="keyword">from</span> <span class="system module">pickle</span> <span class="keyword">import</span> <span class="system function">load</span>, <span class="system function">dump</span>

<span class="local variable">prompt</span> = <span class="system variable">argv</span>[<span class="literal">1</span>]
<span class="local variable">secret</span> = <span class="literal">False</span>

<span class="keyword">if</span> <span class="literal">"password:"</span> <span class="keyword">in</span> <span class="local variable">prompt</span>:
	<span class="local variable">secret</span> = <span class="literal">True</span>

<span class="keyword">with</span> <span class="system function">open</span>(<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_REQUEST_PIPE"</span>], <span class="literal">"w"</span>) <span class="keyword">as</span> <span class="local variable">request_pipe</span>:
	<span class="system function">dump</span>((<span class="local variable">secret</span>, <span class="local variable">prompt</span>), <span class="local variable">request_pipe</span>)

<span class="keyword">with</span> <span class="system function">open</span>(<span class="system variable">environ</span>[<span class="literal">"SSH_ASKPASS_RESPONSE_PIPE"</span>], <span class="literal">"r"</span>) <span class="keyword">as</span> <span class="local variable">response_pipe</span>:
	<span class="keyword">print</span> <span class="system function">load</span>(<span class="local variable">response_pipe</span>)
</pre>

<p>And there you have it. To recap:</p>
<ul>
<li><a href="#svn-wrapper">Subversion wrapper</a>: put it anywhere you like, make sure it&rsquo;s executable</li>
<li><a href="#svn-config">Subversion configuration changes</a>: put them in <code>~/.subversion/config</code></li>
<li><a href="#ssh-wrapper">SSH wrapper</a>: put it in <code>~/.subversion/svn-ssh-detach</code>, make sure it&rsquo;s executable</li>
<li><a href="#ssh-askpass">SSH prompter</a>: put it in <code>~/.subversion/svn-ssh-askpass</code>, make sure it&rsquo;s executable</li>
</ul>

<p>From there on, invoke your Subversion wrapper instead of <code>svn</code> (or create an alias that runs the wrapper when you type <code>svn</code>).</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/blog/subversion-and-ssh-authentication-shenanigans/feed</wfw:commentRss>
		</item>
		<item>
		<title>Fetch 5.5.3 released</title>
		<link>http://fetchsoftworks.com/fetch/news/fetch-553-released</link>
		<comments>http://fetchsoftworks.com/fetch/news/fetch-553-released#comments</comments>
		<pubDate>Mon, 16 Nov 2009 21:15:09 +0000</pubDate>
		<dc:creator>Scott McGuire</dc:creator>
				<category><![CDATA[Fetch]]></category>
		<category><![CDATA[News]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=352</guid>
		<description><![CDATA[<p>Fetch 5.5.3, a bug-fix update to Fetch 5.5, is now available.  This release fixes a number of problems reported by our users.</p><span id="more-352"></span><p>The changes in Fetch 5.5.3 include:</p>

<ul>
<li>Fixed Mac OS -8905 errors when opening Fetch or saving shortcuts on Mac OS X 10.6 Snow Leopard</li>
<li>Fixed rare freezes when quitting</li>
<li>Fixed a problem where Fetch displayed the copy cursor when moving a file</li>
<li>Fixed problem displaying file lists with Norwegian month names from vsFTPd servers</li>
<li>Fixed a problem where items moved to a parent folder would sometimes not appear in the parent folder's file list</li>
<li>Improved compatibility with certain versions of the VanDyke VShell server that send non-standard TYPE replies</li>
</ul>

<p>For a complete list of changes, see the <a href="/fetch/release-notes"> Fetch 5.5.3 Release Notes</a>.</p>

<p>The Fetch 5.5.3 update is free if you purchased your Fetch license after January 28, 2009; otherwise an upgrade is $10, and a new license is $29.</p>

<p>Please download Fetch 5.5.3 from the <a href="/fetch/download">Fetch Download</a> page, or by choosing <span class="ui command">Check for Update…</span> from the <span class="ui menu">Fetch</span> menu in an earlier version, and let us know what you think of the new release.

<p>Fetch 5.5.3 is a Universal binary, compatible with Mac OS X 10.3.9 or later, including Mac OS X 10.5 Leopard and Mac OS X 10.6 Snow Leopard.</p>]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>Fetch 5.5.3, a bug-fix update to Fetch 5.5, is now available.  This release fixes a number of problems reported by our users.</p><span id="more-352"></span><p>The changes in Fetch 5.5.3 include:</p>

<ul>
<li>Fixed Mac OS -8905 errors when opening Fetch or saving shortcuts on Mac OS X 10.6 Snow Leopard</li>
<li>Fixed rare freezes when quitting</li>
<li>Fixed a problem where Fetch displayed the copy cursor when moving a file</li>
<li>Fixed problem displaying file lists with Norwegian month names from vsFTPd servers</li>
<li>Fixed a problem where items moved to a parent folder would sometimes not appear in the parent folder's file list</li>
<li>Improved compatibility with certain versions of the VanDyke VShell server that send non-standard TYPE replies</li>
</ul>

<p>For a complete list of changes, see the <a href="/fetch/release-notes"> Fetch 5.5.3 Release Notes</a>.</p>

<p>The Fetch 5.5.3 update is free if you purchased your Fetch license after January 28, 2009; otherwise an upgrade is $10, and a new license is $29.</p>

<p>Please download Fetch 5.5.3 from the <a href="/fetch/download">Fetch Download</a> page, or by choosing <span class="ui command">Check for Update…</span> from the <span class="ui menu">Fetch</span> menu in an earlier version, and let us know what you think of the new release.

<p>Fetch 5.5.3 is a Universal binary, compatible with Mac OS X 10.3.9 or later, including Mac OS X 10.5 Leopard and Mac OS X 10.6 Snow Leopard.</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/fetch/news/fetch-553-released/feed</wfw:commentRss>
		</item>
		<item>
		<title>Signed applications are easier to upgrade</title>
		<link>http://fetchsoftworks.com/blog/signed-applications-are-easier-to-upgrade</link>
		<comments>http://fetchsoftworks.com/blog/signed-applications-are-easier-to-upgrade#comments</comments>
		<pubDate>Mon, 09 Nov 2009 18:58:42 +0000</pubDate>
		<dc:creator>Ben Artin</dc:creator>
				<category><![CDATA[Blog]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=350</guid>
		<description><![CDATA[<p>Upgrading an application can be an annoying process. In the best case, you click an Upgrade button and go on with your work; in the worst case, you spend hours in frustration trying to make the new version work. For some applications &#8212; such as Fetch &#8212; the upgrade experience is made simpler by code signing, a Mac OS X 10.5 Leopard technology.</p><span id="more-350"></span><p>From the earliest days of Mac OS X, Apple made it easy to securely store passwords in the keychain. A password in the keychain no longer has to be remembered, making it easy for you to log into different accounts, websites, and servers.</p>

<p>An application can only see those passwords that you let it see. The first time an application tries to access a password saved by another application, you have to give it permission. For example, without your permission, Fetch can&#8217;t access your bank account password, and Safari can&#8217;t access your Fetch passwords.</p>

<p>Before Mac OS X 10.5, every time you upgraded an application, you had to re-approve it for password access. A new version of Fetch would need to get your permission to access your web host password, even though the old version of Fetch already had that permission.</p>

<p>This was because there was no way for Mac OS X to tell a new version from a fake. The fear was not that a new version of Fetch would steal your passwords, but that someone would fool you into installing a Fetch lookalike that emails all your passwords to a miscreant.</p>

<p>Requiring new versions to ask for permissions was a safe thing to do, but it made it rather tedious to upgrade some applications. After you installed a new version, the first time you tried to access a password-protected account, you would see something like this:</p>

<p><img class="centered" src="/i/blog/keychain-allow-1.png" title="An application approval dialog." /></p>

<p>&#8220;Huh?&#8221; you&#8217;d say. <span class="ui button">Change All</span> sounds ominous, given that you were probably just logging in and not yet trying to change anything, but good luck figuring out from that dialog if this was change you could believe in.</p>

<p>Of course, if you click <span class="ui button">Don&#8217;t Change</span>, you&#8217;ll get the same question next time. Your computer is giving you a not-so-subtle hint: if you want to get on with your life, you might want to click <span class="ui button">Change All</span>, whatever in the world that means.

<p>To make matters worse, you wouldn&#8217;t always get the same dialog; depending on how you upgraded, you may see a completely different dialog. Even worse, sometimes you might see the dialog once <strong>for each password</strong>&#8230; every time you upgrade.</p>

<p>All in all, this made upgrading confusing and annoying. Nobody was happy about this state of affairs, and, starting with Mac OS X 10.5 Leopard &#8212; released in 2007 &#8212; Apple made it possible for developers to give the <span class="ui button">Change All</span> mole one final whack. Using a technology called <a href="http://en.wikipedia.org/wiki/Code_signing">code signing</a>, information about who created an application can be stored inside the application in a way that can&#8217;t be tampered with or falsified.</p>

<p>Once an application contains its author&#8217;s identity, Mac OS X can tell the difference between a new version of an application (which has the same author identity as the old version) and a fake (which would have someone else&#8217;s identity, or no identity at all). This way, Mac OS X can give new versions of Fetch access to all the passwords that older versions of Fetch had access to. At the end of the day, this is one less reason for having to sweet-talk your computer into letting you get work done.</p>

<p>This is so obviously good for our users that we ran off to sign Fetch two years ago, when we were working on Fetch 5.3, aiming to release it concurrently with Mac OS X 10.5. To gain some other benefits of code signing, we needed to obtain an official code-signing certificate&#8482;, issued by an official certificate authority&#8482;.</p>

<p>We applied for a certificate, and the issuer conscientiously asked for all sorts of information about the company. Nothing too complicated &#8212; company name, company address, a proof that we are a legitimate business, and so on.</p>

<p>This is when we somewhat unexpectedly found out that, while we were busy shipping software, our company ceased to be. Our corporate registration with the New Hampshire Secretary of State had lapsed; before we could proceed, we had to have our status as a legitimate business reinstated. Several months and layers of red tape later, we finally got our hands on a certificate, but by now it was much too late to sign Fetch 5.3 &#8212; as planned, we had released it shortly after Mac OS X 10.5 came out.</p>

<p>We signed the next version &#8212; Fetch 5.3.1, released in March 2009. Since then, we have been delivering you features and bug fixes without the pesky keychain approval alerts. Enjoy!</p>]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>Upgrading an application can be an annoying process. In the best case, you click an Upgrade button and go on with your work; in the worst case, you spend hours in frustration trying to make the new version work. For some applications &mdash; such as Fetch &mdash; the upgrade experience is made simpler by code signing, a Mac OS X 10.5 Leopard technology.</p><span id="more-350"></span><p>From the earliest days of Mac OS X, Apple made it easy to securely store passwords in the keychain. A password in the keychain no longer has to be remembered, making it easy for you to log into different accounts, websites, and servers.</p>

<p>An application can only see those passwords that you let it see. The first time an application tries to access a password saved by another application, you have to give it permission. For example, without your permission, Fetch can&rsquo;t access your bank account password, and Safari can&rsquo;t access your Fetch passwords.</p>

<p>Before Mac OS X 10.5, every time you upgraded an application, you had to re-approve it for password access. A new version of Fetch would need to get your permission to access your web host password, even though the old version of Fetch already had that permission.</p>

<p>This was because there was no way for Mac OS X to tell a new version from a fake. The fear was not that a new version of Fetch would steal your passwords, but that someone would fool you into installing a Fetch lookalike that emails all your passwords to a miscreant.</p>

<p>Requiring new versions to ask for permissions was a safe thing to do, but it made it rather tedious to upgrade some applications. After you installed a new version, the first time you tried to access a password-protected account, you would see something like this:</p>

<p><img class="centered" src="/i/blog/keychain-allow-1.png" title="An application approval dialog." /></p>

<p>&ldquo;Huh?&rdquo; you&rsquo;d say. <span class="ui button">Change All</span> sounds ominous, given that you were probably just logging in and not yet trying to change anything, but good luck figuring out from that dialog if this was change you could believe in.</p>

<p>Of course, if you click <span class="ui button">Don&rsquo;t Change</span>, you&rsquo;ll get the same question next time. Your computer is giving you a not-so-subtle hint: if you want to get on with your life, you might want to click <span class="ui button">Change All</span>, whatever in the world that means.

<p>To make matters worse, you wouldn&rsquo;t always get the same dialog; depending on how you upgraded, you may see a completely different dialog. Even worse, sometimes you might see the dialog once <strong>for each password</strong>&hellip; every time you upgrade.</p>

<p>All in all, this made upgrading confusing and annoying. Nobody was happy about this state of affairs, and, starting with Mac OS X 10.5 Leopard &mdash; released in 2007 &mdash; Apple made it possible for developers to give the <span class="ui button">Change All</span> mole one final whack. Using a technology called <a href="http://en.wikipedia.org/wiki/Code_signing">code signing</a>, information about who created an application can be stored inside the application in a way that can&rsquo;t be tampered with or falsified.</p>

<p>Once an application contains its author&rsquo;s identity, Mac OS X can tell the difference between a new version of an application (which has the same author identity as the old version) and a fake (which would have someone else&rsquo;s identity, or no identity at all). This way, Mac OS X can give new versions of Fetch access to all the passwords that older versions of Fetch had access to. At the end of the day, this is one less reason for having to sweet-talk your computer into letting you get work done.</p>

<p>This is so obviously good for our users that we ran off to sign Fetch two years ago, when we were working on Fetch 5.3, aiming to release it concurrently with Mac OS X 10.5. To gain some other benefits of code signing, we needed to obtain an official code-signing certificate&trade;, issued by an official certificate authority&trade;.</p>

<p>We applied for a certificate, and the issuer conscientiously asked for all sorts of information about the company. Nothing too complicated &mdash; company name, company address, a proof that we are a legitimate business, and so on.</p>

<p>This is when we somewhat unexpectedly found out that, while we were busy shipping software, our company ceased to be. Our corporate registration with the New Hampshire Secretary of State had lapsed; before we could proceed, we had to have our status as a legitimate business reinstated. Several months and layers of red tape later, we finally got our hands on a certificate, but by now it was much too late to sign Fetch 5.3 &mdash; as planned, we had released it shortly after Mac OS X 10.5 came out.</p>

<p>We signed the next version &mdash; Fetch 5.3.1, released in March 2009. Since then, we have been delivering you features and bug fixes without the pesky keychain approval alerts. Enjoy!</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/blog/signed-applications-are-easier-to-upgrade/feed</wfw:commentRss>
		</item>
		<item>
		<title>The Mythical LSSetApplicationForItem</title>
		<link>http://fetchsoftworks.com/blog/the-mythical-lssetapplicationforitem</link>
		<comments>http://fetchsoftworks.com/blog/the-mythical-lssetapplicationforitem#comments</comments>
		<pubDate>Tue, 27 Oct 2009 23:12:10 +0000</pubDate>
		<dc:creator>Ben Artin</dc:creator>
				<category><![CDATA[Blog]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=345</guid>
		<description><![CDATA[<p>Since the day Snow Leopard came out, <a href="http://db.tidbits.com/article/10537">much</a> <a href="http://daringfireball.net/2009/10/congrtlns-osx">has been</a> <a href="http://www.cultofmac.com/how-snow-leopard-ditched-creator-codes-and-why-it-matters/15928">said</a> about creator codes, preferred applications, and Universal Type Identifiers. Regardless of whether you favor the Leopard behavior &#8212; in which a Mac OS 9-style creator code trumps a file&#8216;s extension &#8212; or the Snow Leopard behavior &#8212; in which a Mac OS 9-style creator code is completely ignored &#8212; you, as a developer, may run into a case when you need to make sure that a particular file will open with a particular app when the user double-clicks it in the Finder.</p><span id="more-345"></span><p>We had the same need in Fetch; we allow Fetch users to specify which app a downloaded file will open with, because users sometimes want a downloaded file to open with a different application from a locally created file of the same type.</p>

<p>The general idea is to add a &#8216;usro&#8217; resource with ID 0 to the file. This will take you about ten lines of code &#8212; then you will spend the next hundred lines of code doing things that make it all work well for the user.</p>

<pre class="code"><span class="keyword">void</span>
<span class="type project">FileRef</span>::<span class="function project">SetApplication</span>(
  <span class="keyword">const</span> <span class="type system">string</span>&#38; <span class="variable local">inIdentifier</span>,
  <span class="type project">U</span>::<span class="type project">ApplicationRef</span> <span class="variable local">inApplication</span>)
{</pre>

<p>This function takes two arguments (besides the implicit <span class="code"><span class="keyword">this</span></span>, which represents the file you are changing): the Universal Type Identifier for the file&#8217;s type (such as &#8216;public.html&#8217;), and a reference to the application you want the file to open with.</p>

<p>An <span class="code"><span class="type project">U</span>::<span class="type project">ApplicationRef</span></span> is our wrapper around application bundles. It provides helpers for looking up bundle IDs, finding apps from a bundle ID, etc. Similarly, a <span class="code"><span class="type project">FileRef</span></span> is our wrapper around a Carbon <span class="code"><span class="type system">FSRef</span></span>. I am not including code for such wrappers here; they&#8217;s self-explanatory.</p>

<p>First, clean up any existing &#8216;usro&#8217; resource in the file:</p>

<pre class="code"><span class="type system">string</span> <span class="variable local">path</span> = <span class="variable local">inApplication</span>.<span class="function project">GetFile</span>()-><span class="function project">GetPath</span>();
<span class="comment">// Put a usro(0) resource in </span>
<span class="type project">U</span>::<span class="type project">ResourceFileRef</span> <span class="variable local">resFile</span>(*<span class="keyword">this</span>, <span class="constant system">fsRdWrPerm</span>);
::<span class="type system">Handle</span> <span class="variable local">oldUsro</span> = ::<span class="function system">Get1Resource</span>(<span class="literal">'usro'</span>, <span class="literal">0</span>);
<span class="keyword">if</span> (<span class="variable local">oldUsro</span>) {
  ::<span class="function system">RemoveResource</span>(<span class="variable local">oldUsro</span>);
}</pre>

<p>Next, create a new &#8216;usro&#8217; resource and add it to the file:</p>

<pre class="code"><span class="comment">// Must be 1028 bytes long or Tiger freaks out</span>
<span class="type project">Handle</span> <span class="variable local">newUsro</span>(::<span class="function system">NewHandleClear</span>(<span class="constant system">PATH_MAX</span> + <span class="keyword">sizeof</span>(<span class="type system">UInt32</span>)));
<span class="keyword">if</span> (<span class="variable local">path</span>.<span class="function system">length</span>() > <span class="constant system">PATH_MAX</span> - 1) {
  <span class="variable local">path</span>.<span class="function system">erase</span>(<span class="variable local">path</span>.<span class="function system">begin</span>() + <span class="constant system">PATH_MAX</span> - 1, <span class="variable local">path</span>.<span class="function system">end</span>());
}

**<span class="keyword">reinterpret_cast</span>&#60;<span class="type system">UInt32</span>**&#62;(<span class="variable local">newUsro</span>.<span class="function project">Get</span>()) = <span class="variable local">path</span>.<span class="function system">length</span>();
<span class="function system">copy</span>(
  <span class="variable local">path</span>.<span class="function system">begin</span>(), <span class="variable local">path</span>.<span class="function system">end</span>(), 
  *<span class="keyword">reinterpret_cast</span>&#60;<span class="type system">char</span>**&#62;(<span class="variable local">newUsro</span>.<span class="function project">Get</span>()) + <span class="keyword">sizeof</span>(<span class="type system">UInt32</span>)
);

::<span class="function system">AddResource</span>(<span class="variable local">newUsro</span>.<span class="function local">Get</span>(), <span class="literal">'usro'</span>, <span class="literal">0</span>, <span class="literal">"\p"</span>);
<span class="function project">ThrowIfResourceError</span>();
<span class="variable local">newUsro</span>.<span class="function project">Abandon</span>();</pre>

<p>I think what that comment is trying to tell me is that Launch Services in some versions of Mac OS X 10.4 Tiger did not recognize a &#8216;usro&#8217; resource unless it was exactly 1028 bytes long. Aside from that, this snippet contains the most salient information about the &#8216;usro&#8217; resource: its first four bytes contain the length of the filesystem path to the application which will open when the file is double-clicked; the remaining 1024 bytes contain the path itself.</p>

<p>It&#8217;s rather interesting that we aren&#8217;t doing anything to make the 4 bytes of length at the front of the resource be of any particular endianness. According to our version history, this code predates the PPC-Intel transition, and therefore I can only assume that it used to work on PPC computers as well as it works on Intel computers today. This suggests that copying a file from a PPC computer to an Intel computer might render its &#8216;usro&#8217; resource useless, depending on whether Launch Services knows to interpret &#8216;usro&#8217; resources with non-native endianness.</p>

<p>If all you are looking for is a file that opens in the given app when double-clicked, you are all set. However, your users will probably be confused because the file&#8217;s icon will not reflect its new affiliation &#8212; you might now have a text file that opens in BBEdit, but the Finder will continue to show it with its default icon, which probably belongs to TextEdit. It would be nice to do something about that.</p>

<p>The app&#8217;s info dictionary holds information about which icon should be used:</p>

<pre class="code"><span class="comment">// Track down the custom icon we should use for this file</span>
<span class="type system">string</span> <span class="variable local">extension</span> = <span class="function project">GetFileNameExtension</span>(<span class="keyword">this</span>-&#62;<span class="function project">GetName</span>());

<span class="type system">optional</span>&#60;<span class="type system">string</span>&#62; <span class="variable local">iconFile</span>;
<span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">appInfo</span> = <span class="variable local">inApplication</span>.<span class="function project">GetInfoDictionary</span>();

<span class="comment">// If the app has no info dictionary, it is probably an old Carbon or</span>
<span class="comment">// Classic app and may require the file creator to be set, so we set it</span>
<span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">appInfo</span>) {
  <span class="keyword">this</span>-&#62;<span class="function project">SetCreatorAndType</span>(<span class="variable local">inApplication</span>.<span class="function project">GetSignature</span>(), <span class="keyword">this</span>-&#62;<span class="function project">GetType</span>());
} <span class="keyword">else</span> {
  <span class="keyword">this</span>-&#62;<span class="function project">SetCreatorAndType</span>(<span class="constant system">kLSUnknownCreator</span>, <span class="constant system">kLSUnknownType</span>);
}</pre>

<p>You may be inclined to summarily ignore old apps that don't have an info dictionary. If your customer support sometimes feels like archeology, you probably want to keep that section of the code.</p>

<p>Finally, extract the icons:</p>

<pre class="code"><span class="keyword">if</span> (<span class="variable local">appInfo</span>) {
  <span class="variable local">iconFile</span> = <span class="function project">IconFileInAppForType</span>(<span class="variable local">appInfo</span>, <span class="variable local">inIdentifier</span>);
  <span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">iconFile</span>) {
     <span class="variable local">iconFile</span> = <span class="function project">IconFileInAppForExtension</span>(<span class="variable local">appInfo</span>, <span class="variable local">extension</span>);
  }
}</pre>

<p>I&#8217;ll elaborate on <span class="code"><span class="function project">IconFileInAppForType</span></span> and <span class="code"><span class="function project">IconFileInAppForExtension</span></span> later; for now, just assume you found the icon file name.</p>

<pre class="code"><span class="comment">// Load the custom icon from app's resources or fall back on default</span>
<span class="type system">IconFamilyHandle</span> <span class="variable local">iconFamily</span> = <span class="literal">0</span>;

<span class="keyword">if</span> (<span class="variable local">iconFile</span>) {
  <span class="comment">// If the app is a bundle, use CFBundle to get at the icon</span>
  <span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">inApplication</span>.<span class="function project">GetFile</span>()-><span class="function project">IsFile</span>()) {
    <span class="type project">U</span>::<span class="type project">CFURLRef</span> <span class="variable local">appURL</span>(<span class="function project">U</span>::<span class="function project">CFAdopt</span>(::<span class="function system">CFURLCreateFromFSRef</span>(
      <span class="constant system">kCFAllocatorDefault</span>,
      &#38;<span class="variable local">inApplication</span>.<span class="function project">GetFile</span>()-><span class="function project">Get</span>()
    )));
    
    <span class="type project">U</span>::<span class="type project">CFURLRef</span> <span class="variable local">icon</span> = <span class="function project">U</span>::<span class="function project">CFAdopt</span>(::<span class="function system">CFBundleCopyResourceURLInDirectory</span>(
      <span class="variable local">appURL</span>.<span class="function project">get</span>(),
      <span class="function system">lexical_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&#62;(<span class="variable local">iconFile</span>.<span class="function project">get</span>()).<span class="function project">get</span>(),
      <span class="function system">CFSTR</span>(<span class="literal">"icns"</span>),
      <span class="literal">0</span>
    ));

    <span class="comment">// Try without extension if we can't find it with extension</span>
    <span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">icon</span>) {
      <span class="variable local">icon</span> = <span class="function project">U</span>::<span class="function project">CFAdopt</span>(::<span class="function system">CFBundleCopyResourceURLInDirectory</span>(
        <span class="variable local">appURL</span>.<span class="function project">get</span>(),
        <span class="function system">lexical_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&#62;(<span class="variable local">iconFile</span>.<span class="function project">get</span>()).<span class="function project">get</span>(),
        <span class="literal">0</span>,
        <span class="literal">0</span>
      ));
    }
    
    <span class="keyword">if</span> (<span class="variable local">icon</span>) {
      <span class="type system">FSRef</span> <span class="variable local">iconFile</span>;
      <span class="keyword">if</span> (::<span class="function system">CFURLGetFSRef</span>(<span class="variable local">icon</span>.<span class="function project">get</span>(), &#38;<span class="variable local">iconFile</span>)) {
        ::<span class="function system">ReadIconFromFSRef</span>(&#38;<span class="variable local">iconFile</span>, &#38;<span class="variable local">iconFamily</span>);
      }
    }
  } <span class="keyword">else</span> {
    <span class="type system">SInt16</span> <span class="variable local">resID</span> = <span class="function system">lexical_cast</span>&#60;<span class="type system">SInt16</span>&#62;(*<span class="variable local">iconFile</span>);

    <span class="type project">U</span>::<span class="type project">ResourceFileRef</span> <span class="variable local">appResFile</span>(*<span class="variable local">inApplication</span>.<span class="function project">Get</span><span class="function project">File</span>(), <span class="constant system">fsRdPerm</span>);
    
    <span class="comment">// Try icns resource first</span>
    <span class="variable local">iconFamily</span> = <span class="keyword">reinterpret_cast</span>&#60;<span class="type system">IconFamilyHandle</span>&#62;(
      ::<span class="function system">Get1Resource</span>(<span class="constant system">kIconFamilyType</span>, <span class="variable local">resID</span>)
    );
    <span class="keyword">if</span> (<span class="variable local">iconFamily</span>) {
      ::<span class="function system">DetachResource</span>(<span class="keyword">reinterpret_cast</span>&#60; ::<span class="type system">Handle</span>(<span class="variable local">iconFamily</span>));
    }
    <span class="comment">// Otherwise, use icon suite resources</span>
    <span class="keyword">else</span> {
      <span class="type system">IconSuiteRef</span> <span class="variable local">iconSuite</span> = <span class="literal">0</span>;
      <span class="function project">ThrowIfOSError</span>(::<span class="function system">GetIconSuite</span>(
        &#38;<span class="variable local">iconSuite</span>, <span class="variable local">resID</span>, 
        <span class="constant system">kSelectorAllAvailableData</span>
      ));
      ::<span class="function system">IconSuiteToIconFamily</span>(
        <span class="variable local">iconSuite</span>, <span class="constant system">kSelectorAllAvailableData</span>, 
        &#38;<span class="variable local">iconFamily</span>
      );
      ::<span class="function system">DisposeIconSuite</span>(<span class="variable local">iconSuite</span>, <span class="literal">true</span>);
    }
  }
}</pre>

<p>What just happened here? First off, an <span class="code"><span class="type system">IconFamilyHandle</span></span> is the Carbon way of representing the collection of several different resolutions of the same icon. I don&#8217;t know if there&#8217;s a newfangled way of managing those, without getting tangled in APIs from the last millennium, so <span class="code"><span class="type system">IconFamilyHandle</span></span> it is.</p>

<p>Second, an app might be a bundle (a maze of twisty little files and directories), or it might be a monolithic file (a maze of twisty little resources).</p>

<p>If it&#8217;s a bundle, then the icon filename you got earlier refers to an icon file somewhere inside the bundle &#8212; which CFBundle APIs will happily hand over to you. Except when they won&#8217;t, because some developers put the complete icon filename (including the extension) in their info dictionary, and some omit the extension, letting the OS tack on &#8216;icns&#8217; as needed. Therefore, you need to look for the icon in two places.</p>

<p>If the app is not a bundle, then the icon filename you got from the info dictionary is not a filename at all &#8212; it&#8217;s a resource ID. It might refer to a single &#8216;icns&#8217; resource (in modern apps) or it might be a collection of individual resources (of which there are approximately zillion, with such prosaic names as &#8216;t8mk&#8217; and &#8216;ics#&#8217;). If you are looking at a single &#8216;icns&#8217; resource, then a simple <span class="code"><span class="function system">Get1Resource</span></span> will fetch it. Otherwise, to load the myriad different separate resources, use <span class="code"><span class="function system">GetIconSuite</span></span>, and then convert the result to an icon family.</p>

<p>There's also the case when you find no icon at all &#8212; it&#8217;s better to give the document a generic icon (which will leave the user uninformed about which app will open the document) than leave it with no custom icon (which will probably suggest that the file will open in some app other than the one you just set it to open with):</p>

<pre class="code"><span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">iconFamily</span>) {
  <span class="keyword">static</span> <span class="type project">U</span>::<span class="type project">Icon</span> <span class="variable local">genericIcon</span> = <span class="type project">U</span>::<span class="type project">Icon</span>::<span class="function project">FromTypeCreator</span>(
    <span class="constant system">kSystemIconsCreator</span>, <span class="constant system">kGenericDocumentIcon</span>
  );
  <span class="function project">ThrowIfOSError</span>(::<span class="function system">IconRefToIconFamily</span>(
    <span class="variable local">genericIcon</span>.<span class="function project">Get</span>(), <span class="constant system">kSelectorAllAvailableData</span>, 
    &#38;<span class="variable local">iconFamily</span>
  ));
}</pre>

<p>Alright! Now you have the icon. Remove the old icon, write the new one to the file, and let the Finder know that the file has a custom icon:</p>

<pre class="code"><span class="type project">U</span>::<span class="type project">Handle</span> <span class="variable local">newIcon</span>(<span class="keyword">reinterpret_cast</span>&#60; <span class="type system">::Handle</span>&#62;(<span class="variable local">iconFamily</span>));

<span class="comment">// Put a custom icon resource in</span>
<span class="type system">::Handle</span> <span class="variable local">oldIcon</span> = ::<span class="function system">Get1Resource</span>(<span class="constant system">kIconFamilyType</span>, <span class="constant system">kCustomIconResource</span>);
<span class="keyword">if</span> (<span class="variable local">oldIcon</span>) {
  ::<span class="function system">RemoveResource</span>(<span class="variable local">oldIcon</span>);
}

::<span class="function system">AddResource</span>(<span class="variable local">newIcon</span>.<span class="function project">Get</span>(), <span class="constant system">kIconFamilyType</span>, <span class="constant system">kCustomIconResource</span>, <span class="literal">"\p"</span>);
<span class="function project">ThrowIfResourceError</span>();
::<span class="function system">SetResInfo</span>(<span class="variable local">newIcon</span>.<span class="function project">Get</span>(), <span class="constant system">kCustomIconResource</span>, <span class="literal">"\pBinding Override"</span>);
<span class="function project">ThrowIfResourceError</span>();
<span class="variable local">newIcon</span>.<span class="function project">Abandon</span>();

<span class="comment">// Set the custom icon flag</span>
<span class="type system">FSCatalogInfo</span> <span class="variable local">info</span>;
<span class="function project">ThrowIfOSError</span>(::<span class="function system">FSGetCatalogInfo</span>(
  &#38;<span class="keyword">this</span>-&#62;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, 
  &#38;<span class="variable local">info</span>, <span class="literal">0</span>, <span class="literal">0</span>, <span class="literal">0</span>
));
<span class="type system">FileInfo</span>&#38; <span class="variable local">finderInfo</span> = *<span class="keyword">reinterpret_cast</span>&#60;<span class="type system">FileInfo</span>*&#62;(&#38;<span class="variable local">info</span>.<span class="field">finderInfo</span>);
<span class="variable local">finderInfo</span>.<span class="field">finderFlags</span> &#124;= <span class="constant system">kHasCustomIcon</span>;
<span class="function project">ThrowIfOSError</span>(::<span class="function system">FSSetCatalogInfo</span>(&#38;<span class="keyword">this</span>-&#62;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, &#38;<span class="variable local">info</span>));</pre>

<p>And now you are done. Oh, wait&#8230; maybe some day you will want to undo this, and let the file open with its default application:</p>

<pre class="code"><span class="keyword">void</span>
<span class="type project">FileRef</span>::<span class="function project">ResetApplication</span>()
{
  <span class="comment">// Remove usro(0) resource, icns(-16455), and clear the custom icon bit</span>
  <span class="type project">U</span>::<span class="type project">ResourceFileRef</span> <span class="variable local">resFile</span>(*<span class="keyword">this</span>, <span class="constant system">fsRdWrPerm</span>);
  <span class="type system">::Handle</span> <span class="variable local">oldUsro</span> = ::<span class="function system">Get1Resource</span>(<span class="literal">'usro'</span>, <span class="literal">0</span>);
  <span class="keyword">if</span> (<span class="variable local">oldUsro</span>) {
    ::<span class="function system">RemoveResource</span>(<span class="variable local">oldUsro</span>);
  }
  ::<span class="type system">Handle</span> <span class="variable local">oldIcons</span> = ::<span class="function system">Get1Resource</span>(<span class="constant system">kIconFamilyType</span>, <span class="constant system">kCustomIconResource</span>);
  <span class="keyword">if</span> (<span class="variable local">oldIcons</span>) {
    ::<span class="function system">RemoveResource</span>(<span class="variable local">oldIcons</span>);
  }

  <span class="type system">FSCatalogInfo</span> <span class="variable local">info</span>;
  <span class="function project">ThrowIfOSError</span>(::<span class="function system">FSGetCatalogInfo</span>(&#38;<span class="keyword">this</span>-&#62;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, &#38;<span class="variable local">info</span>, <span class="literal">0</span>, <span class="literal">0</span>, <span class="literal">0</span>));
  <span class="type system">FileInfo</span>&#38; <span class="variable local">finderInfo</span> = *<span class="keyword">reinterpret_cast</span>&#60;<span class="type system">FileInfo</span>*&#62;(&#38;<span class="variable local">info</span>.<span class="field">finderInfo</span>);
  <span class="variable local">finderInfo</span>.<span class="field">finderFlags</span> &#38;= ~<span class="constant system">kHasCustomIcon</span>;
  <span class="function project">ThrowIfOSError</span>(::<span class="function system">FSSetCatalogInfo</span>(&#38;<span class="keyword">this</span>-&#62;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, &#38;<span class="variable local">info</span>));
}</pre>

<p>If you made it this far, I suppose you probably want to know how to get the icon file name from an app&#8217;s info dictionary. It&#8217;s a simple matter of looking things up in CFBundleDocumentTypes (for apps that haven't adopted UTIs yet) or LSItemContentTypes (for those that have).</p>

<pre class="code"><span class="type system">optional</span>&#60;<span class="type system">string</span>&#62;
<span class="function project">IconFileInAppForExtension</span>(
  <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">inAppInfo</span>,
  <span class="keyword">const</span> <span class="type system">std</span>::<span class="type system">string</span>&#38; <span class="variable local">inExtension</span>)
{
  <span class="keyword">return</span> <span class="function project">IconFileInAppForKeyValue</span>(
    <span class="variable local">inAppInfo</span>, 
    <span class="function system">CFSTR</span>(<span class="literal">"CFBundleTypeExtensions"</span>), <span class="variable local">inExtension</span>
  );
}

<span class="type system">optional</span>&#60;<span class="type system">string</span>&#62;
<span class="function project">IconFileInAppForType</span>(
  <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">inAppInfo</span>,
  <span class="keyword">const</span> <span class="type system">std</span>::<span class="type system">string</span>&#38; <span class="variable local">inExtension</span>)
{
  <span class="keyword">return</span> <span class="function project">IconFileInAppForKeyValue</span>(
    <span class="variable local">inAppInfo</span>, 
    <span class="function system">CFSTR</span>(<span class="literal">"LSItemContentTypes"</span>), <span class="variable local">inExtension</span>
  );
}

<span class="type system">optional</span>&#60;<span class="type system">string</span>&#62;
<span class="function project">IconFileInAppForKeyValue</span>(
  <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">inAppInfo</span>,
  <span class="type system">CFStringRef</span> <span class="variable local">inKey</span>,
  <span class="keyword">const</span> <span class="type system">std</span>::<span class="type system">string</span>&#38; <span class="variable local">inValue</span>)
{
  <span class="type project">U</span>::<span class="type project">CFArrayRef</span> <span class="variable local">docTypes</span> = <span class="function project">cf_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFArrayRef</span>&#62;(::<span class="function system">CFDictionaryGetValue</span>(
    <span class="variable local">inAppInfo</span>.<span class="function system">get</span>(), CFSTR(<span class="literal">"CFBundleDocumentTypes"</span>)
  ));

  <span class="keyword">if</span> (<span class="variable local">docTypes</span>) {
    <span class="type project">U</span>::<span class="type project">CFStringRef</span> <span class="variable local">valueStr</span> = <span class="function system">lexical_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&#62;(<span class="variable local">inValue</span>);
    
    <span class="keyword">for</span> (
      <span class="type system">CFIndex</span> <span class="variable local">i</span> = <span class="literal">0</span>;
      <span class="variable local">i</span> &#60; ::<span class="function system">CFArrayGetCount</span>(<span class="variable local">docTypes</span>.<span class="function system">get</span>());
      ++<span class="variable local">i</span>
    ) {
      <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">docType</span> = <span class="function project">cf_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFDictionaryRef</span>&#62;(
        ::<span class="function system">CFArrayGetValueAtIndex</span>(<span class="variable local">docTypes</span>.<span class="function system">get</span>(), <span class="variable local">i</span>)
      );

      <span class="type project">U</span>::<span class="type project">CFArrayRef</span> <span class="variable local">values</span> = <span class="function project">cf_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFArrayRef</span>&#62;(
        ::<span class="function system">CFDictionaryGetValue</span>(<span class="variable local">docType</span>.<span class="function system">get</span>(), <span class="variable local">inKey</span>)
      );
      <span class="keyword">if</span> (<span class="variable local">values</span>) {
        <span class="keyword">for</span> (
          <span class="type system">CFIndex</span> <span class="variable local">j</span> = <span class="literal">0</span>;
          <span class="variable local">j</span> &#60; ::<span class="function system">CFArrayGetCount</span>(<span class="variable local">values</span>.<span class="function system">get</span>());
          ++<span class="variable local">j</span>
        ) {
          <span class="type project">U</span>::<span class="type project">CFStringRef</span> <span class="variable local">appValue</span> = <span class="function project">cf_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&#62;(
            ::<span class="function system">CFArrayGetValueAtIndex</span>(<span class="variable local">values</span>.<span class="function system">get</span>(), <span class="variable local">j</span>)
          );
          <span class="keyword">if</span> (
            ::<span class="function system">CFStringCompare</span>(
              <span class="variable local">appValue</span>.<span class="function project">get</span>(), <span class="variable local">valueStr</span>.<span class="function project">get</span>(), 
              <span class="constant system">kCFCompareCaseInsensitive</span>
            ) == <span class="constant system">kCFCompareEqualTo</span>
          ) {
            <span class="type project">U</span>::<span class="type project">CFStringRef</span> <span class="variable local">iconFileName</span> = <span class="function project">cf_cast</span>&#60;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&#62;(
              ::<span class="function system">CFDictionaryGetValue</span>(<span class="variable local">docType</span>.<span class="function project">get</span>(), <span class="function system">CFSTR</span>(<span class="literal">"CFBundleTypeIconFile"</span>))
            );
            <span class="keyword">if</span> (<span class="variable local">iconFileName</span>) {
              <span class="keyword">return</span> <span class="type system">optional</span>&#60;<span class="type system">string</span>&#62;(<span class="function system">lexical_cast</span>&#60;<span class="type system">string</span>&#62;(<span class="variable local">iconFileName</span>));
            }
          }
        }
      }
    }
  }
  <span class="keyword">return</span> <span class="type system">optional</span>&#60;<span class="type system">string</span>&#62;();
}</pre>

<p>Of course, if you are only ever going to use this code to set the creator of a file to an application that you control, then you can skip most of the backwards compatibility cases. You won&#8217;t need the code for resource-fork based apps or apps without info dictionaries, and probably not even for apps that don&#8217;t use UTIs.</p>]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>Since the day Snow Leopard came out, <a href="http://db.tidbits.com/article/10537">much</a> <a href="http://daringfireball.net/2009/10/congrtlns-osx">has been</a> <a href="http://www.cultofmac.com/how-snow-leopard-ditched-creator-codes-and-why-it-matters/15928">said</a> about creator codes, preferred applications, and Universal Type Identifiers. Regardless of whether you favor the Leopard behavior &mdash; in which a Mac OS 9-style creator code trumps a file&lsquo;s extension &mdash; or the Snow Leopard behavior &mdash; in which a Mac OS 9-style creator code is completely ignored &mdash; you, as a developer, may run into a case when you need to make sure that a particular file will open with a particular app when the user double-clicks it in the Finder.</p><span id="more-345"></span><p>We had the same need in Fetch; we allow Fetch users to specify which app a downloaded file will open with, because users sometimes want a downloaded file to open with a different application from a locally created file of the same type.</p>

<p>The general idea is to add a &lsquo;usro&rsquo; resource with ID 0 to the file. This will take you about ten lines of code &mdash; then you will spend the next hundred lines of code doing things that make it all work well for the user.</p>

<pre class="code"><span class="keyword">void</span>
<span class="type project">FileRef</span>::<span class="function project">SetApplication</span>(
  <span class="keyword">const</span> <span class="type system">string</span>&amp; <span class="variable local">inIdentifier</span>,
  <span class="type project">U</span>::<span class="type project">ApplicationRef</span> <span class="variable local">inApplication</span>)
{</pre>

<p>This function takes two arguments (besides the implicit <span class="code"><span class="keyword">this</span></span>, which represents the file you are changing): the Universal Type Identifier for the file&rsquo;s type (such as &lsquo;public.html&rsquo;), and a reference to the application you want the file to open with.</p>

<p>An <span class="code"><span class="type project">U</span>::<span class="type project">ApplicationRef</span></span> is our wrapper around application bundles. It provides helpers for looking up bundle IDs, finding apps from a bundle ID, etc. Similarly, a <span class="code"><span class="type project">FileRef</span></span> is our wrapper around a Carbon <span class="code"><span class="type system">FSRef</span></span>. I am not including code for such wrappers here; they&rsquo;s self-explanatory.</p>

<p>First, clean up any existing &lsquo;usro&rsquo; resource in the file:</p>

<pre class="code"><span class="type system">string</span> <span class="variable local">path</span> = <span class="variable local">inApplication</span>.<span class="function project">GetFile</span>()-><span class="function project">GetPath</span>();
<span class="comment">// Put a usro(0) resource in </span>
<span class="type project">U</span>::<span class="type project">ResourceFileRef</span> <span class="variable local">resFile</span>(*<span class="keyword">this</span>, <span class="constant system">fsRdWrPerm</span>);
::<span class="type system">Handle</span> <span class="variable local">oldUsro</span> = ::<span class="function system">Get1Resource</span>(<span class="literal">'usro'</span>, <span class="literal">0</span>);
<span class="keyword">if</span> (<span class="variable local">oldUsro</span>) {
  ::<span class="function system">RemoveResource</span>(<span class="variable local">oldUsro</span>);
}</pre>

<p>Next, create a new &lsquo;usro&rsquo; resource and add it to the file:</p>

<pre class="code"><span class="comment">// Must be 1028 bytes long or Tiger freaks out</span>
<span class="type project">Handle</span> <span class="variable local">newUsro</span>(::<span class="function system">NewHandleClear</span>(<span class="constant system">PATH_MAX</span> + <span class="keyword">sizeof</span>(<span class="type system">UInt32</span>)));
<span class="keyword">if</span> (<span class="variable local">path</span>.<span class="function system">length</span>() > <span class="constant system">PATH_MAX</span> - 1) {
  <span class="variable local">path</span>.<span class="function system">erase</span>(<span class="variable local">path</span>.<span class="function system">begin</span>() + <span class="constant system">PATH_MAX</span> - 1, <span class="variable local">path</span>.<span class="function system">end</span>());
}

**<span class="keyword">reinterpret_cast</span>&lt;<span class="type system">UInt32</span>**&gt;(<span class="variable local">newUsro</span>.<span class="function project">Get</span>()) = <span class="variable local">path</span>.<span class="function system">length</span>();
<span class="function system">copy</span>(
  <span class="variable local">path</span>.<span class="function system">begin</span>(), <span class="variable local">path</span>.<span class="function system">end</span>(), 
  *<span class="keyword">reinterpret_cast</span>&lt;<span class="type system">char</span>**&gt;(<span class="variable local">newUsro</span>.<span class="function project">Get</span>()) + <span class="keyword">sizeof</span>(<span class="type system">UInt32</span>)
);

::<span class="function system">AddResource</span>(<span class="variable local">newUsro</span>.<span class="function local">Get</span>(), <span class="literal">'usro'</span>, <span class="literal">0</span>, <span class="literal">"\p"</span>);
<span class="function project">ThrowIfResourceError</span>();
<span class="variable local">newUsro</span>.<span class="function project">Abandon</span>();</pre>

<p>I think what that comment is trying to tell me is that Launch Services in some versions of Mac OS X 10.4 Tiger did not recognize a &lsquo;usro&rsquo; resource unless it was exactly 1028 bytes long. Aside from that, this snippet contains the most salient information about the &lsquo;usro&rsquo; resource: its first four bytes contain the length of the filesystem path to the application which will open when the file is double-clicked; the remaining 1024 bytes contain the path itself.</p>

<p>It&rsquo;s rather interesting that we aren&rsquo;t doing anything to make the 4 bytes of length at the front of the resource be of any particular endianness. According to our version history, this code predates the PPC-Intel transition, and therefore I can only assume that it used to work on PPC computers as well as it works on Intel computers today. This suggests that copying a file from a PPC computer to an Intel computer might render its &lsquo;usro&rsquo; resource useless, depending on whether Launch Services knows to interpret &lsquo;usro&rsquo; resources with non-native endianness.</p>

<p>If all you are looking for is a file that opens in the given app when double-clicked, you are all set. However, your users will probably be confused because the file&rsquo;s icon will not reflect its new affiliation &mdash; you might now have a text file that opens in BBEdit, but the Finder will continue to show it with its default icon, which probably belongs to TextEdit. It would be nice to do something about that.</p>

<p>The app&rsquo;s info dictionary holds information about which icon should be used:</p>

<pre class="code"><span class="comment">// Track down the custom icon we should use for this file</span>
<span class="type system">string</span> <span class="variable local">extension</span> = <span class="function project">GetFileNameExtension</span>(<span class="keyword">this</span>-&gt;<span class="function project">GetName</span>());

<span class="type system">optional</span>&lt;<span class="type system">string</span>&gt; <span class="variable local">iconFile</span>;
<span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">appInfo</span> = <span class="variable local">inApplication</span>.<span class="function project">GetInfoDictionary</span>();

<span class="comment">// If the app has no info dictionary, it is probably an old Carbon or</span>
<span class="comment">// Classic app and may require the file creator to be set, so we set it</span>
<span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">appInfo</span>) {
  <span class="keyword">this</span>-&gt;<span class="function project">SetCreatorAndType</span>(<span class="variable local">inApplication</span>.<span class="function project">GetSignature</span>(), <span class="keyword">this</span>-&gt;<span class="function project">GetType</span>());
} <span class="keyword">else</span> {
  <span class="keyword">this</span>-&gt;<span class="function project">SetCreatorAndType</span>(<span class="constant system">kLSUnknownCreator</span>, <span class="constant system">kLSUnknownType</span>);
}</pre>

<p>You may be inclined to summarily ignore old apps that don't have an info dictionary. If your customer support sometimes feels like archeology, you probably want to keep that section of the code.</p>

<p>Finally, extract the icons:</p>

<pre class="code"><span class="keyword">if</span> (<span class="variable local">appInfo</span>) {
  <span class="variable local">iconFile</span> = <span class="function project">IconFileInAppForType</span>(<span class="variable local">appInfo</span>, <span class="variable local">inIdentifier</span>);
  <span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">iconFile</span>) {
     <span class="variable local">iconFile</span> = <span class="function project">IconFileInAppForExtension</span>(<span class="variable local">appInfo</span>, <span class="variable local">extension</span>);
  }
}</pre>

<p>I&rsquo;ll elaborate on <span class="code"><span class="function project">IconFileInAppForType</span></span> and <span class="code"><span class="function project">IconFileInAppForExtension</span></span> later; for now, just assume you found the icon file name.</p>

<pre class="code"><span class="comment">// Load the custom icon from app's resources or fall back on default</span>
<span class="type system">IconFamilyHandle</span> <span class="variable local">iconFamily</span> = <span class="literal">0</span>;

<span class="keyword">if</span> (<span class="variable local">iconFile</span>) {
  <span class="comment">// If the app is a bundle, use CFBundle to get at the icon</span>
  <span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">inApplication</span>.<span class="function project">GetFile</span>()-><span class="function project">IsFile</span>()) {
    <span class="type project">U</span>::<span class="type project">CFURLRef</span> <span class="variable local">appURL</span>(<span class="function project">U</span>::<span class="function project">CFAdopt</span>(::<span class="function system">CFURLCreateFromFSRef</span>(
      <span class="constant system">kCFAllocatorDefault</span>,
      &amp;<span class="variable local">inApplication</span>.<span class="function project">GetFile</span>()-><span class="function project">Get</span>()
    )));
    
    <span class="type project">U</span>::<span class="type project">CFURLRef</span> <span class="variable local">icon</span> = <span class="function project">U</span>::<span class="function project">CFAdopt</span>(::<span class="function system">CFBundleCopyResourceURLInDirectory</span>(
      <span class="variable local">appURL</span>.<span class="function project">get</span>(),
      <span class="function system">lexical_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&gt;(<span class="variable local">iconFile</span>.<span class="function project">get</span>()).<span class="function project">get</span>(),
      <span class="function system">CFSTR</span>(<span class="literal">"icns"</span>),
      <span class="literal">0</span>
    ));

    <span class="comment">// Try without extension if we can't find it with extension</span>
    <span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">icon</span>) {
      <span class="variable local">icon</span> = <span class="function project">U</span>::<span class="function project">CFAdopt</span>(::<span class="function system">CFBundleCopyResourceURLInDirectory</span>(
        <span class="variable local">appURL</span>.<span class="function project">get</span>(),
        <span class="function system">lexical_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&gt;(<span class="variable local">iconFile</span>.<span class="function project">get</span>()).<span class="function project">get</span>(),
        <span class="literal">0</span>,
        <span class="literal">0</span>
      ));
    }
    
    <span class="keyword">if</span> (<span class="variable local">icon</span>) {
      <span class="type system">FSRef</span> <span class="variable local">iconFile</span>;
      <span class="keyword">if</span> (::<span class="function system">CFURLGetFSRef</span>(<span class="variable local">icon</span>.<span class="function project">get</span>(), &amp;<span class="variable local">iconFile</span>)) {
        ::<span class="function system">ReadIconFromFSRef</span>(&amp;<span class="variable local">iconFile</span>, &amp;<span class="variable local">iconFamily</span>);
      }
    }
  } <span class="keyword">else</span> {
    <span class="type system">SInt16</span> <span class="variable local">resID</span> = <span class="function system">lexical_cast</span>&lt;<span class="type system">SInt16</span>&gt;(*<span class="variable local">iconFile</span>);

    <span class="type project">U</span>::<span class="type project">ResourceFileRef</span> <span class="variable local">appResFile</span>(*<span class="variable local">inApplication</span>.<span class="function project">Get</span><span class="function project">File</span>(), <span class="constant system">fsRdPerm</span>);
    
    <span class="comment">// Try icns resource first</span>
    <span class="variable local">iconFamily</span> = <span class="keyword">reinterpret_cast</span>&lt;<span class="type system">IconFamilyHandle</span>&gt;(
      ::<span class="function system">Get1Resource</span>(<span class="constant system">kIconFamilyType</span>, <span class="variable local">resID</span>)
    );
    <span class="keyword">if</span> (<span class="variable local">iconFamily</span>) {
      ::<span class="function system">DetachResource</span>(<span class="keyword">reinterpret_cast</span>&lt; ::<span class="type system">Handle</span>(<span class="variable local">iconFamily</span>));
    }
    <span class="comment">// Otherwise, use icon suite resources</span>
    <span class="keyword">else</span> {
      <span class="type system">IconSuiteRef</span> <span class="variable local">iconSuite</span> = <span class="literal">0</span>;
      <span class="function project">ThrowIfOSError</span>(::<span class="function system">GetIconSuite</span>(
        &amp;<span class="variable local">iconSuite</span>, <span class="variable local">resID</span>, 
        <span class="constant system">kSelectorAllAvailableData</span>
      ));
      ::<span class="function system">IconSuiteToIconFamily</span>(
        <span class="variable local">iconSuite</span>, <span class="constant system">kSelectorAllAvailableData</span>, 
        &amp;<span class="variable local">iconFamily</span>
      );
      ::<span class="function system">DisposeIconSuite</span>(<span class="variable local">iconSuite</span>, <span class="literal">true</span>);
    }
  }
}</pre>

<p>What just happened here? First off, an <span class="code"><span class="type system">IconFamilyHandle</span></span> is the Carbon way of representing the collection of several different resolutions of the same icon. I don&rsquo;t know if there&rsquo;s a newfangled way of managing those, without getting tangled in APIs from the last millennium, so <span class="code"><span class="type system">IconFamilyHandle</span></span> it is.</p>

<p>Second, an app might be a bundle (a maze of twisty little files and directories), or it might be a monolithic file (a maze of twisty little resources).</p>

<p>If it&rsquo;s a bundle, then the icon filename you got earlier refers to an icon file somewhere inside the bundle &mdash; which CFBundle APIs will happily hand over to you. Except when they won&rsquo;t, because some developers put the complete icon filename (including the extension) in their info dictionary, and some omit the extension, letting the OS tack on &lsquo;icns&rsquo; as needed. Therefore, you need to look for the icon in two places.</p>

<p>If the app is not a bundle, then the icon filename you got from the info dictionary is not a filename at all &mdash; it&rsquo;s a resource ID. It might refer to a single &lsquo;icns&rsquo; resource (in modern apps) or it might be a collection of individual resources (of which there are approximately zillion, with such prosaic names as &lsquo;t8mk&rsquo; and &lsquo;ics#&rsquo;). If you are looking at a single &lsquo;icns&rsquo; resource, then a simple <span class="code"><span class="function system">Get1Resource</span></span> will fetch it. Otherwise, to load the myriad different separate resources, use <span class="code"><span class="function system">GetIconSuite</span></span>, and then convert the result to an icon family.</p>

<p>There's also the case when you find no icon at all &mdash; it&rsquo;s better to give the document a generic icon (which will leave the user uninformed about which app will open the document) than leave it with no custom icon (which will probably suggest that the file will open in some app other than the one you just set it to open with):</p>

<pre class="code"><span class="keyword">if</span> (<span class="keyword">not</span> <span class="variable local">iconFamily</span>) {
  <span class="keyword">static</span> <span class="type project">U</span>::<span class="type project">Icon</span> <span class="variable local">genericIcon</span> = <span class="type project">U</span>::<span class="type project">Icon</span>::<span class="function project">FromTypeCreator</span>(
    <span class="constant system">kSystemIconsCreator</span>, <span class="constant system">kGenericDocumentIcon</span>
  );
  <span class="function project">ThrowIfOSError</span>(::<span class="function system">IconRefToIconFamily</span>(
    <span class="variable local">genericIcon</span>.<span class="function project">Get</span>(), <span class="constant system">kSelectorAllAvailableData</span>, 
    &amp;<span class="variable local">iconFamily</span>
  ));
}</pre>

<p>Alright! Now you have the icon. Remove the old icon, write the new one to the file, and let the Finder know that the file has a custom icon:</p>

<pre class="code"><span class="type project">U</span>::<span class="type project">Handle</span> <span class="variable local">newIcon</span>(<span class="keyword">reinterpret_cast</span>&lt; <span class="type system">::Handle</span>&gt;(<span class="variable local">iconFamily</span>));

<span class="comment">// Put a custom icon resource in</span>
<span class="type system">::Handle</span> <span class="variable local">oldIcon</span> = ::<span class="function system">Get1Resource</span>(<span class="constant system">kIconFamilyType</span>, <span class="constant system">kCustomIconResource</span>);
<span class="keyword">if</span> (<span class="variable local">oldIcon</span>) {
  ::<span class="function system">RemoveResource</span>(<span class="variable local">oldIcon</span>);
}

::<span class="function system">AddResource</span>(<span class="variable local">newIcon</span>.<span class="function project">Get</span>(), <span class="constant system">kIconFamilyType</span>, <span class="constant system">kCustomIconResource</span>, <span class="literal">"\p"</span>);
<span class="function project">ThrowIfResourceError</span>();
::<span class="function system">SetResInfo</span>(<span class="variable local">newIcon</span>.<span class="function project">Get</span>(), <span class="constant system">kCustomIconResource</span>, <span class="literal">"\pBinding Override"</span>);
<span class="function project">ThrowIfResourceError</span>();
<span class="variable local">newIcon</span>.<span class="function project">Abandon</span>();

<span class="comment">// Set the custom icon flag</span>
<span class="type system">FSCatalogInfo</span> <span class="variable local">info</span>;
<span class="function project">ThrowIfOSError</span>(::<span class="function system">FSGetCatalogInfo</span>(
  &amp;<span class="keyword">this</span>-&gt;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, 
  &amp;<span class="variable local">info</span>, <span class="literal">0</span>, <span class="literal">0</span>, <span class="literal">0</span>
));
<span class="type system">FileInfo</span>&amp; <span class="variable local">finderInfo</span> = *<span class="keyword">reinterpret_cast</span>&lt;<span class="type system">FileInfo</span>*&gt;(&amp;<span class="variable local">info</span>.<span class="field">finderInfo</span>);
<span class="variable local">finderInfo</span>.<span class="field">finderFlags</span> |= <span class="constant system">kHasCustomIcon</span>;
<span class="function project">ThrowIfOSError</span>(::<span class="function system">FSSetCatalogInfo</span>(&amp;<span class="keyword">this</span>-&gt;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, &amp;<span class="variable local">info</span>));</pre>

<p>And now you are done. Oh, wait&hellip; maybe some day you will want to undo this, and let the file open with its default application:</p>

<pre class="code"><span class="keyword">void</span>
<span class="type project">FileRef</span>::<span class="function project">ResetApplication</span>()
{
  <span class="comment">// Remove usro(0) resource, icns(-16455), and clear the custom icon bit</span>
  <span class="type project">U</span>::<span class="type project">ResourceFileRef</span> <span class="variable local">resFile</span>(*<span class="keyword">this</span>, <span class="constant system">fsRdWrPerm</span>);
  <span class="type system">::Handle</span> <span class="variable local">oldUsro</span> = ::<span class="function system">Get1Resource</span>(<span class="literal">'usro'</span>, <span class="literal">0</span>);
  <span class="keyword">if</span> (<span class="variable local">oldUsro</span>) {
    ::<span class="function system">RemoveResource</span>(<span class="variable local">oldUsro</span>);
  }
  ::<span class="type system">Handle</span> <span class="variable local">oldIcons</span> = ::<span class="function system">Get1Resource</span>(<span class="constant system">kIconFamilyType</span>, <span class="constant system">kCustomIconResource</span>);
  <span class="keyword">if</span> (<span class="variable local">oldIcons</span>) {
    ::<span class="function system">RemoveResource</span>(<span class="variable local">oldIcons</span>);
  }

  <span class="type system">FSCatalogInfo</span> <span class="variable local">info</span>;
  <span class="function project">ThrowIfOSError</span>(::<span class="function system">FSGetCatalogInfo</span>(&amp;<span class="keyword">this</span>-&gt;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, &amp;<span class="variable local">info</span>, <span class="literal">0</span>, <span class="literal">0</span>, <span class="literal">0</span>));
  <span class="type system">FileInfo</span>&amp; <span class="variable local">finderInfo</span> = *<span class="keyword">reinterpret_cast</span>&lt;<span class="type system">FileInfo</span>*&gt;(&amp;<span class="variable local">info</span>.<span class="field">finderInfo</span>);
  <span class="variable local">finderInfo</span>.<span class="field">finderFlags</span> &amp;= ~<span class="constant system">kHasCustomIcon</span>;
  <span class="function project">ThrowIfOSError</span>(::<span class="function system">FSSetCatalogInfo</span>(&amp;<span class="keyword">this</span>-&gt;<span class="function project">Get</span>(), <span class="constant system">kFSCatInfoFinderInfo</span>, &amp;<span class="variable local">info</span>));
}</pre>

<p>If you made it this far, I suppose you probably want to know how to get the icon file name from an app&rsquo;s info dictionary. It&rsquo;s a simple matter of looking things up in CFBundleDocumentTypes (for apps that haven't adopted UTIs yet) or LSItemContentTypes (for those that have).</p>

<pre class="code"><span class="type system">optional</span>&lt;<span class="type system">string</span>&gt;
<span class="function project">IconFileInAppForExtension</span>(
  <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">inAppInfo</span>,
  <span class="keyword">const</span> <span class="type system">std</span>::<span class="type system">string</span>&amp; <span class="variable local">inExtension</span>)
{
  <span class="keyword">return</span> <span class="function project">IconFileInAppForKeyValue</span>(
    <span class="variable local">inAppInfo</span>, 
    <span class="function system">CFSTR</span>(<span class="literal">"CFBundleTypeExtensions"</span>), <span class="variable local">inExtension</span>
  );
}

<span class="type system">optional</span>&lt;<span class="type system">string</span>&gt;
<span class="function project">IconFileInAppForType</span>(
  <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">inAppInfo</span>,
  <span class="keyword">const</span> <span class="type system">std</span>::<span class="type system">string</span>&amp; <span class="variable local">inExtension</span>)
{
  <span class="keyword">return</span> <span class="function project">IconFileInAppForKeyValue</span>(
    <span class="variable local">inAppInfo</span>, 
    <span class="function system">CFSTR</span>(<span class="literal">"LSItemContentTypes"</span>), <span class="variable local">inExtension</span>
  );
}

<span class="type system">optional</span>&lt;<span class="type system">string</span>&gt;
<span class="function project">IconFileInAppForKeyValue</span>(
  <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">inAppInfo</span>,
  <span class="type system">CFStringRef</span> <span class="variable local">inKey</span>,
  <span class="keyword">const</span> <span class="type system">std</span>::<span class="type system">string</span>&amp; <span class="variable local">inValue</span>)
{
  <span class="type project">U</span>::<span class="type project">CFArrayRef</span> <span class="variable local">docTypes</span> = <span class="function project">cf_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFArrayRef</span>&gt;(::<span class="function system">CFDictionaryGetValue</span>(
    <span class="variable local">inAppInfo</span>.<span class="function system">get</span>(), CFSTR(<span class="literal">"CFBundleDocumentTypes"</span>)
  ));

  <span class="keyword">if</span> (<span class="variable local">docTypes</span>) {
    <span class="type project">U</span>::<span class="type project">CFStringRef</span> <span class="variable local">valueStr</span> = <span class="function system">lexical_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&gt;(<span class="variable local">inValue</span>);
    
    <span class="keyword">for</span> (
      <span class="type system">CFIndex</span> <span class="variable local">i</span> = <span class="literal">0</span>;
      <span class="variable local">i</span> &lt; ::<span class="function system">CFArrayGetCount</span>(<span class="variable local">docTypes</span>.<span class="function system">get</span>());
      ++<span class="variable local">i</span>
    ) {
      <span class="type project">U</span>::<span class="type project">CFDictionaryRef</span> <span class="variable local">docType</span> = <span class="function project">cf_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFDictionaryRef</span>&gt;(
        ::<span class="function system">CFArrayGetValueAtIndex</span>(<span class="variable local">docTypes</span>.<span class="function system">get</span>(), <span class="variable local">i</span>)
      );

      <span class="type project">U</span>::<span class="type project">CFArrayRef</span> <span class="variable local">values</span> = <span class="function project">cf_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFArrayRef</span>&gt;(
        ::<span class="function system">CFDictionaryGetValue</span>(<span class="variable local">docType</span>.<span class="function system">get</span>(), <span class="variable local">inKey</span>)
      );
      <span class="keyword">if</span> (<span class="variable local">values</span>) {
        <span class="keyword">for</span> (
          <span class="type system">CFIndex</span> <span class="variable local">j</span> = <span class="literal">0</span>;
          <span class="variable local">j</span> &lt; ::<span class="function system">CFArrayGetCount</span>(<span class="variable local">values</span>.<span class="function system">get</span>());
          ++<span class="variable local">j</span>
        ) {
          <span class="type project">U</span>::<span class="type project">CFStringRef</span> <span class="variable local">appValue</span> = <span class="function project">cf_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&gt;(
            ::<span class="function system">CFArrayGetValueAtIndex</span>(<span class="variable local">values</span>.<span class="function system">get</span>(), <span class="variable local">j</span>)
          );
          <span class="keyword">if</span> (
            ::<span class="function system">CFStringCompare</span>(
              <span class="variable local">appValue</span>.<span class="function project">get</span>(), <span class="variable local">valueStr</span>.<span class="function project">get</span>(), 
              <span class="constant system">kCFCompareCaseInsensitive</span>
            ) == <span class="constant system">kCFCompareEqualTo</span>
          ) {
            <span class="type project">U</span>::<span class="type project">CFStringRef</span> <span class="variable local">iconFileName</span> = <span class="function project">cf_cast</span>&lt;<span class="type project">U</span>::<span class="type project">CFStringRef</span>&gt;(
              ::<span class="function system">CFDictionaryGetValue</span>(<span class="variable local">docType</span>.<span class="function project">get</span>(), <span class="function system">CFSTR</span>(<span class="literal">"CFBundleTypeIconFile"</span>))
            );
            <span class="keyword">if</span> (<span class="variable local">iconFileName</span>) {
              <span class="keyword">return</span> <span class="type system">optional</span>&lt;<span class="type system">string</span>&gt;(<span class="function system">lexical_cast</span>&lt;<span class="type system">string</span>&gt;(<span class="variable local">iconFileName</span>));
            }
          }
        }
      }
    }
  }
  <span class="keyword">return</span> <span class="type system">optional</span>&lt;<span class="type system">string</span>&gt;();
}</pre>

<p>Of course, if you are only ever going to use this code to set the creator of a file to an application that you control, then you can skip most of the backwards compatibility cases. You won&rsquo;t need the code for resource-fork based apps or apps without info dictionaries, and probably not even for apps that don&rsquo;t use UTIs.</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/blog/the-mythical-lssetapplicationforitem/feed</wfw:commentRss>
		</item>
		<item>
		<title>Fetch 5.5.2 offers Snow Leopard compatibility</title>
		<link>http://fetchsoftworks.com/fetch/news/fetch-5-5-2-offers-snow-leopard-compatibility</link>
		<comments>http://fetchsoftworks.com/fetch/news/fetch-5-5-2-offers-snow-leopard-compatibility#comments</comments>
		<pubDate>Mon, 31 Aug 2009 21:14:59 +0000</pubDate>
		<dc:creator>Jim Matthews</dc:creator>
				<category><![CDATA[Fetch]]></category>
		<category><![CDATA[News]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=344</guid>
		<description><![CDATA[<p>Fetch Softworks is pleased to announce Fetch 5.5.2, the latest version of the original Macintosh file transfer program. Fetch 5.5.2 has been updated to offer full compatibility with the newly released Mac OS X 10.6 Snow Leopard.</p><span id="more-344"></span><p>This release improves the Quick Look feature introduced in Fetch 5, and adds two new features: a "View as Text" command and SOCKS support for SFTP connections.</p>

<p>Fetch 5.5.2 is a Universal binary, compatible with Mac OS X 10.3.9 or later, including Mac OS X 10.5 Leopard and Mac OS X 10.6 Snow Leopard, and can be downloaded from <a href="http://fetchsoftworks.com">http://fetchsoftworks.com</a>.</p>

<p>Fetch 5.5.2 is free to try for 15 days, and a single-user license is $29. <a href="http://fetchsoftworks.com/fetch/upgrade">Upgrades</a> are free for customers who purchased Fetch after January 28, 2009; otherwise, upgrades are $10.  <a href="http://fetchsoftworks.com/fetch/free">Free licenses</a> and upgrades are available for educational and charitable use.</p>
]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>Fetch Softworks is pleased to announce Fetch 5.5.2, the latest version of the original Macintosh file transfer program. Fetch 5.5.2 has been updated to offer full compatibility with the newly released Mac OS X 10.6 Snow Leopard.</p><span id="more-344"></span><p>This release improves the Quick Look feature introduced in Fetch 5, and adds two new features: a "View as Text" command and SOCKS support for SFTP connections.</p>

<p>Fetch 5.5.2 is a Universal binary, compatible with Mac OS X 10.3.9 or later, including Mac OS X 10.5 Leopard and Mac OS X 10.6 Snow Leopard, and can be downloaded from <a href="http://fetchsoftworks.com">http://fetchsoftworks.com</a>.</p>

<p>Fetch 5.5.2 is free to try for 15 days, and a single-user license is $29. <a href="http://fetchsoftworks.com/fetch/upgrade">Upgrades</a> are free for customers who purchased Fetch after January 28, 2009; otherwise, upgrades are $10.  <a href="http://fetchsoftworks.com/fetch/free">Free licenses</a> and upgrades are available for educational and charitable use.</p>
</div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/fetch/news/fetch-5-5-2-offers-snow-leopard-compatibility/feed</wfw:commentRss>
		</item>
		<item>
		<title>Who We Are, Part 7: So Who Are We Anyway?</title>
		<link>http://fetchsoftworks.com/blog/who-we-are-part-7-so-who-are-we-anyway</link>
		<comments>http://fetchsoftworks.com/blog/who-we-are-part-7-so-who-are-we-anyway#comments</comments>
		<pubDate>Tue, 04 Aug 2009 15:07:40 +0000</pubDate>
		<dc:creator>Jim Matthews</dc:creator>
				<category><![CDATA[Blog]]></category>

		<guid isPermaLink="false">http://fetchsoftworks.com/?p=343</guid>
		<description><![CDATA[<p>I started with the idea that we could redesign our website in 6 months.  It took over two years.</p>
]]></description>
			<content:encoded><![CDATA[<style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/screen.css";
--></style><style type="text/css" media="screen"><!--
@import "http://fetchsoftworks.com/c/feed.css";
--></style><div class="feeditem"><p>This is part 7 in a series on the redesign of fetchsoftworks.com; the series starts at <a href="/blog/who-we-are-part-1-my-original-sin">Part 1: My Original Sin</a>.</p><span id="more-343"></span><h4>What Have We Done?</h4>

<p>Now that the new website is up, I can sit back and appreciate the number of improvements.  The design and typography are no longer embarrassing; they&rsquo;re actually quite attractive.  Every page has a search box, that in turn quickly searches every page, making our content immediately findable in a way it never was before.  The new message board lets you post comments, monitor followups by email and/or RSS, and go back and edit your comments; all without forcing you to pick and remember a username and password.  Underneath this all is a solid foundation of standard-based XHTML markup and CSS style, so that we could change the color of the entire site by editing a couple files.  This last one is of interest mostly to us, but the rest are aimed at the site&rsquo;s visitors, the customers we are trying to serve.</p>

<p>Those are the big things.  But I love the little things just as much, perhaps because they are little:</p>

<ul>

<li>The s-t ligature <img src="/i/blog/best-ligature.png" class="inline nudge-2"></img> on Fetch pages</li>
<li>The little dog <img src="/i/blog/rule-dog.png" class="inline nudge-2"></img> in the horizontal rule</li>
<li>The way the shipping address fields <a href="/fetch/buy/check">collapse</a> when you click the &ldquo;Shipping Address is the same as Billing Address&rdquo; checkbox</li>
<li>The way the <a href="/fetch/upgrade">upgrade form</a> figures out whether you are entitled to a free license, or how much the upgrade will cost, and just does the right thing</li>
<li>The message board <a href="/fetch/messageboard#new-topic">captcha</a> that not only presents an obstacle to spambots, but also teases them a bit in the process</li>
<li>No matter what URL you type to get to the site &mdash; <code>http://www.fetchsoftworks.com</code>, <code>http://fetchworks.com</code>, even <code>http://fetch.net</code> &mdash; your browser will show the canonical URL, <code>http://fetchsoftworks.com</code></li>
<li>
	<p>The URLs (with a few exceptions) aren&rsquo;t ugly things like</p>

	<p><code>http://fetchsoftworks.com/cgi-bin/Ultimate.cgi?action=intro&#38;BypassCookie=true</code></p> 

	<p>or even</p> 

	<p><code>http://fetchsoftworks.com/Licensing/edustore.application.html</code></p>  

	<p>Instead, they&rsquo;re clear, extension-less, beautiful things like</p> 

	<p><code>http://fetchsoftworks.com/fetch/messageboard</code></p> 

	<p>and</p> 

	<p><code>http://fetchsoftworks.com/fetch/free</code></p>
</li>
<li>The way a different testimonial appears each day you visit the Fetch <a href="/fetch/">product page</a> (did anyone ever bother to read the whole list of them on the old site?)</li>
<li>The silly Star Wars joke (no link, I&rsquo;ll let you find that one yourself) that came out of a long discussion on finding a better way to write &ldquo;For more help see Online Help&rdquo;</li>

</ul>

<p>We have a long wishlist of things we still want to change or improve.  You might think it&rsquo;s wrong to have one form for purchasing with a credit card, and a similar-but-slightly-different form for purchasing with a check or PO.  We do too! But for the first time in the company&rsquo;s history, we have good place to work from.</p>

<h4>So Who Are We Anyway?</h4>

<p>I started with the idea that we could redesign our website in 6 months.  It took over two years.  We learned a lot of PHP, XHTML and CSS in that time, and we also learned about ourselves.  We learned anew that we are picky, at least about the project that is in front of us.  We could go 8 years with an ugly mess of a website, but once we were working on it we couldn&rsquo;t let a URL end with &ldquo;.php&rdquo;.  We put in untold hours of customization and testing to save our users the 30 seconds it takes to pick a message board username and password.  Once we get started, we can&rsquo;t seem to help ourselves, even if it means neglecting other possible priorities.</p>

<p>I think we also learned that we have more to say than we&rsquo;ve been in the habit of saying.  Our old site could go months between updates, not because we were on vacation &mdash; code was being written, customers were being supported, the message board was buzzing &mdash; but because we weren&rsquo;t in the habit of telling the world what we were up to, and did not have an attractive platform to speak from.  The new site takes care of the second count, and pushes us on the first.  By putting our news and blog content on the home page we are daring ourselves to keep them fresh.  We aren&rsquo;t a company that talks about ourselves or our products much, but maybe we can be.  We are about to find out.</p>

<h4>Credits and Thanks</h4>

<p>Our thanks to <a href="http://ultramaroon.com">Robin Olson</a>, who first talked to us about our visual identity, and to Leah McManus who helped us get started. Thanks also to Joe Finocchario, and the Happy Cog Philadelphia crew &mdash; Greg Hoy, Robert Jolly, Chris Cashdollar, Dan Mall, Heather Shaw, Jenn Lukas, and Dave DeRuchie.  My personal thanks to my colleagues Ben Artin and Scott McGuire, for reviewing (and improving) these posts, and for all the thought and care and effort they put into the redesign.</p></div>]]></content:encoded>
			<wfw:commentRss>http://fetchsoftworks.com/blog/who-we-are-part-7-so-who-are-we-anyway/feed</wfw:commentRss>
		</item>
	</channel>
</rss>
