<?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"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>SmugMug Sorcery</title>
	<atom:link href="http://sorcery.smugmug.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://sorcery.smugmug.com</link>
	<description>Any sufficiently advanced technology is indistinguishable from magic. - Arthur C. Clarke</description>
	<lastBuildDate>Mon, 06 May 2013 16:57:46 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='sorcery.smugmug.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>SmugMug Sorcery</title>
		<link>http://sorcery.smugmug.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://sorcery.smugmug.com/osd.xml" title="SmugMug Sorcery" />
	<atom:link rel='hub' href='http://sorcery.smugmug.com/?pushpress=hub'/>
		<item>
		<title>Push-button deployments with Arduino</title>
		<link>http://sorcery.smugmug.com/2013/01/30/push-button-deployments-with-arduino/</link>
		<comments>http://sorcery.smugmug.com/2013/01/30/push-button-deployments-with-arduino/#comments</comments>
		<pubDate>Wed, 30 Jan 2013 20:20:34 +0000</pubDate>
		<dc:creator>Ryan Doherty</dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[devops]]></category>

		<guid isPermaLink="false">http://sorcery.smugmug.com/?p=163</guid>
		<description><![CDATA[TL;DR: Thanks to an Arduino, an Arduino ethernet shield, some Arduino code, buttons from eBay, LEDs from Fry&#8217;s, and SmugMug&#8217;s deployment web app, we&#8217;ve created a push-button deployment process that looks like this: When I first started working at SmugMug, we deployed infrequently by manually merging branches, tagging, double-checking, then running a bunch of commands [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=163&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
				<content:encoded><![CDATA[<p>TL;DR: Thanks to an Arduino, an Arduino ethernet shield, some Arduino code, buttons from eBay, LEDs from Fry&#8217;s, and SmugMug&#8217;s deployment web app, we&#8217;ve created a push-button deployment process that looks like this:</p>
<div id="v-XHCalhTJ-1" class="video-player" style="width:595px;height:334px">
<embed id="v-XHCalhTJ-1-video" src="http://s0.videopress.com/player.swf?v=1.03&amp;guid=XHCalhTJ&amp;isDynamicSeeking=true" type="application/x-shockwave-flash" width="595" height="334" title="Deployinator demo" wmode="direct" seamlesstabbing="true" allowfullscreen="true" allowscriptaccess="always" overstretch="true"></embed></div>
<p>When I first started working at SmugMug, we deployed infrequently by manually merging branches, tagging, double-checking, then running a bunch of commands (some via sudo and some not). It was an eleven step process that not all developers had access to. Developers were usually uneasy about pushing due to the complexity involved.</p>
<p>Eventually the process was consolidated into a shell script, which still had to be run via sudo on a designated server. More recently, the shell script was wrapped in a web app that made things much easier.</p>
<p><a href="http://smugsorcery.files.wordpress.com/2013/01/screen-shot-2013-01-30-at-11-58-49-am.jpg"><img src="http://smugsorcery.files.wordpress.com/2013/01/screen-shot-2013-01-30-at-11-58-49-am.jpg?w=595&#038;h=578" alt="Pushit" width="595" height="578" class="alignnone size-large wp-image-194" /></a></p>
<p>While the web app is pretty awesome and easy to use, I thought using a real physical button to deploy code would be even better:</p>
<p>Introducing the SmugMug Deployinator 5000!</p>
<h3>Front</h3>
<p><a href="http://rdoherty.smugmug.com/Other/Deployinator-5000/27781222_FCFQ9P#!i=2342434404&amp;k=nr5nc87&amp;lb=1&amp;s=A"><img src="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-nr5nc87/0/L/CA_01291316264639-L.jpg" alt="Deployinator 5000" width="450" height="600"></a></p>
<h3>Inside</h3>
<p><a href="http://rdoherty.smugmug.com/Other/Deployinator-5000/27781222_FCFQ9P#!i=2342434387&amp;k=zNbZH3P&amp;lb=1&amp;s=A"><img src="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-zNbZH3P/0/M/CA_01291316264492-M.jpg" alt="Deployinator 5000 inside" width="600" height="450"></a></p>
<h3>Buttons</h3>
<p><a href="http://rdoherty.smugmug.com/Other/Deployinator-5000/27781222_FCFQ9P#!i=2342434714&amp;k=stZ4Jtn&amp;lb=1&amp;s=A"><img src="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-stZ4Jtn/1/M/CA_01291316265695-M.jpg" width="600" height="450" alt="Buttons" /><br />
</a></p>
<h3>Arduino and Ethernet Shield</h3>
<p><a href="http://rdoherty.smugmug.com/Other/Deployinator-5000/27781222_FCFQ9P#!i=2342434839&amp;k=qGnTdpd&amp;lb=1&amp;s=A"><img src="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-qGnTdpd/1/M/CA_01291316270184-M.jpg" alt="Arduino" width="600" height="450" /></a></p>
<p>The Deployinator 5000 consists of the following components:</p>
<ul>
<li><a href="http://www.amazon.com/Starter-Kit-Newsite-Uno-Breadboard/dp/B0051QHPJM/ref=sr_1_1?ie=UTF8&amp;qid=1359496465&amp;sr=8-1&amp;keywords=arduino+starter+kit">Arduino Starter Kit</a></li>
<li><a href="http://store.arduino.cc/ww/index.php?main_page=product_info&amp;cPath=11_5&amp;products_id=199">Arduino Ethernet Shield</a></li>
<li><a href="http://store.arduino.cc/ww/index.php?main_page=product_info&amp;cPath=6_31&amp;products_id=151">Green</a>, <a href="http://store.arduino.cc/ww/index.php?main_page=product_info&amp;cPath=6_31&amp;products_id=150">red</a> and <a href="http://store.arduino.cc/ww/index.php?main_page=product_info&amp;cPath=6_31&amp;products_id=152">yellow</a> 5v LEDs</li>
<li>Lots of wires</li>
<li>2-button momentary switch with key lock station <a href="http://www.ebay.com/sch/i.html?_odkw=momentary+switch+key&amp;_osacat=0&amp;_from=R40&amp;_trksid=p2045573.m570.l1313&amp;_nkw=momentary+switch+key+station&amp;_sacat=0">from eBay</a></li>
<li><a href="http://www.amazon.com/Pilot-PLSW26-Safety-Racing-Toggle/dp/B000GTMUUI/ref=sr_1_2?ie=UTF8&amp;qid=1359496767&amp;sr=8-2&amp;keywords=toggle+switch">Protected toggle switch</a></li>
<li>Used weather monitoring station enclosure from <a href="http://www.weirdstuff.com/">Weird Stuff</a></li>
</ul>
<p>The setup is relatively simple. The toggle switch, key lock, and two momentary switches are all wired up in sequence so the Arduino sees them as one button. When all four are pressed, the Arduino makes an HTTP POST request to our deployment server, which then pushes any pending code live. While the Arduino is waiting for the deployment to finish, it blinks the yellow LED. When the push is deployed, the green LED lights up. If something goes horribly wrong, the red LED strikes fear into the deployer&#8217;s heart.</p>
<p>It wasn&#8217;t too hard to wire up the Arduino, the buttons, and the LEDs, even for someone with no electronics experience (although I had a bit of help from other SmugMug employees with more experience). The fun and challenging part was finding an enclosure and mounting all the pieces inside it. Trips to <a href="http://weirdstuff.com/">Weird Stuff</a> and Home Depot solved that problem easily!</p>
<p>After gutting the enclosure, I superglued the Arduino holder to the inside and drilled holes into the backing plate to mount the buttons with machine screws. I then reattached a few wires and circuitry for dramatic effect.</p>
<p>Long-term ideas for the Deployinator include adding larger lights, a disco ball, and playing music when a push occurs. <a href="https://www.sparkfun.com/products/10747">PowerSwitch Tails</a> would allow the Arduino to control anything that runs on 120V power.</p>
<p>Deploying code doesn&#8217;t have to be boring!</p>
<p>- Ryan Doherty, SmugMug DevOps</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/smugsorcery.wordpress.com/163/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/smugsorcery.wordpress.com/163/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=163&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" /><div><a href="http://sorcery.smugmug.com/2013/01/30/push-button-deployments-with-arduino/"><img alt="Deployinator demo" src="http://videos.videopress.com/XHCalhTJ/deployinator-demo_std.original.jpg" width="160" height="120" /></a></div>]]></content:encoded>
			<wfw:commentRss>http://sorcery.smugmug.com/2013/01/30/push-button-deployments-with-arduino/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
	<enclosure url="http://videos.videopress.com/XHCalhTJ/deployinator-demo_hd.mp4" length="31953920" type="video/mp4" />

		<media:content url="http://0.gravatar.com/avatar/032a06f3003dfe3706436938d4c196d9?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">rdohertysmugmug</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2013/01/screen-shot-2013-01-30-at-11-58-49-am.jpg?w=595" medium="image">
			<media:title type="html">Pushit</media:title>
		</media:content>

		<media:content url="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-nr5nc87/0/L/CA_01291316264639-L.jpg" medium="image">
			<media:title type="html">Deployinator 5000</media:title>
		</media:content>

		<media:content url="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-zNbZH3P/0/M/CA_01291316264492-M.jpg" medium="image">
			<media:title type="html">Deployinator 5000 inside</media:title>
		</media:content>

		<media:content url="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-stZ4Jtn/1/M/CA_01291316265695-M.jpg" medium="image">
			<media:title type="html">Buttons</media:title>
		</media:content>

		<media:content url="http://rdoherty.smugmug.com/Other/Deployinator-5000/i-qGnTdpd/1/M/CA_01291316270184-M.jpg" medium="image">
			<media:title type="html">Arduino</media:title>
		</media:content>

		<media:group>
			<media:content url="http://videos.videopress.com/XHCalhTJ/deployinator-demo_hd.mp4" fileSize="31953920" type="video/mp4" medium="video" bitrate="3160" isDefault="true" duration="79" width="1280" height="720" />

			<media:content url="http://videos.videopress.com/XHCalhTJ/deployinator-demo_dvd.mp4" fileSize="15451136" type="video/mp4" medium="video" bitrate="1528" isDefault="false" duration="79" width="640" height="360" />

			<media:content url="http://videos.videopress.com/XHCalhTJ/deployinator-demo_std.mp4" fileSize="8049152" type="video/mp4" medium="video" bitrate="796" isDefault="false" duration="79" width="400" height="224" />

			<media:content url="http://videos.videopress.com/XHCalhTJ/deployinator-demo_fmt1.ogv" fileSize="8049152" type="video/ogg" medium="video" bitrate="796" isDefault="false" duration="79" width="400" height="224" />

			<media:rating scheme="urn:mpaa">g</media:rating>
			<media:title type="plain">Deployinator demo</media:title>
			<media:thumbnail url="http://videos.videopress.com/XHCalhTJ/deployinator-demo_std.original.jpg" width="256" height="144" />
			<media:player url="http://s0.videopress.com/player.swf?v=1.03&#38;guid=XHCalhTJ&#38;isDynamicSeeking=true" width="400" height="225" />
		</media:group>
	</item>
		<item>
		<title>Scaling Puppet in EC2</title>
		<link>http://sorcery.smugmug.com/2013/01/14/scaling-puppet-in-ec2/</link>
		<comments>http://sorcery.smugmug.com/2013/01/14/scaling-puppet-in-ec2/#comments</comments>
		<pubDate>Mon, 14 Jan 2013 21:00:31 +0000</pubDate>
		<dc:creator>shane</dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Amazon Web Services]]></category>
		<category><![CDATA[ec2]]></category>
		<category><![CDATA[puppet]]></category>
		<category><![CDATA[s3]]></category>

		<guid isPermaLink="false">http://sorcery.smugmug.com/?p=150</guid>
		<description><![CDATA[At SmugMug, we&#8217;re constantly scaling up and down the number of machines running in ec2. We&#8217;re big fans of puppet, using it to configure all of our machines from scratch. We use generic Ubuntu AMIs provided by Canonical as our base, saving ourselves the trouble of building custom AMIs every time there is a new [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=150&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
				<content:encoded><![CDATA[<p>At <a href="http://www.smugmug.com/">SmugMug</a>, we&#8217;re constantly scaling up and down the number of machines running in <a href="https://aws.amazon.com/ec2/">ec2</a>. We&#8217;re big fans of <a href="http://puppetlabs.com/puppet/puppet-open-source/">puppet</a>, using it to configure all of our machines from scratch. We use generic Ubuntu AMIs provided by Canonical as our base, saving ourselves the trouble of building custom AMIs every time there is a new operating system release.</p>
<p>To help us scale automatically without intervention (we use <a href="http://aws.amazon.com/autoscaling/">AutoScaling</a>), we run puppet in a <a href="https://github.com/jordansissel/puppet-examples/tree/master/nodeless-puppet/">nodeless configuration</a>. This means we do not run a puppet master on any machines in our infrastructure. All machines run puppet independent of any other, removing dependencies and improving reliability.</p>
<p>I will first explain nodeless puppet, then I will dive into how we use it.</p>
<h2>Understanding Nodeless Puppet</h2>
<p>Most instructions for setting up puppet tell you to create a puppet master instance that all puppet agents talk to. When an agent needs to apply a configuration, the master compiles a config and hands it back to the agent. With nodeless, the puppet agent compiles its own configuration and applies it to the host.</p>
<p>We start with a simple puppet.conf file that is pretty generic:</p>
<pre class="brush: plain; title: ; notranslate">
[main]
logdir=/var/log/puppet
vardir=/var/lib/puppet
ssldir=/var/lib/puppet/ssl
rundir=/var/run/puppet
factpath=$vardir/lib/facter
templatedir=$confdir/templates
modulepath=$confdir/modules</pre>
<p>Then we create a top-level manifest called <code>mainrun.pp</code>. In our setup this manifest lives in a directory called <code>/manifests</code>. An example <code>mainrun.pp</code>:</p>
<pre class="brush: plain; title: ; notranslate">
include ntp
include puppet
include ssh
include sudo

if $hostname == &quot;foo&quot; {
    include apache2
}</pre>
<p>There is also a <code>/modules</code> directory that contains <a href="http://docs.puppetlabs.com/puppet/2.7/reference/modules_fundamentals.html">puppet modules</a>. Each <code>include</code> statement in the <code>mainrun.pp</code> manifest exists as a module.</p>
<p>Once we have all of our modules created and listed appropriately in <code>mainrun.pp</code>, we run puppet with the <strong>apply</strong> command: <code>sudo puppet apply /etc/puppet/manifests/mainrun.pp</code>. Puppet will then run and do all that our manifests tell it to.</p>
<h2>Scaling Puppet</h2>
<p>Upon booting, all machines download their entire puppet manifest code tree from an <a href="https://aws.amazon.com/s3/">Amazon S3</a> bucket. Then puppet is run and the machine is configured. By using S3, we&#8217;re leveraging Amazon&#8217;s ability to provide highly available access to files despite server or data center outages.</p>
<p>To help keep our changes to puppet sane, we use a <a href="http://git-scm.com/">git</a> repository. When anyone does a push to the central repository server, it copies our files to our Amazon S3 bucket. The S3 bucket has custom IAM access rules applied so puppet can only see its bucket and no other.</p>
<p>When we launch a new instance in ec2, we use the <code>--user-data-file</code> option in <code>ec2-run-instances</code> to run a first-boot script that sets us up with puppet.</p>
<p>A simple first-boot script:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
AWS_ACCESS_KEY=&quot;abcdefghijlmnopqrstu&quot;
AWS_SECRET_KEY=&quot;abcdefghijlmnopqrstuabcdefghijlmnopqrstu&quot;
BUCKET_PUPPET=&quot;puppet&quot;

apt-get update
apt-get --yes --force-yes install puppet s3cmd
S3CMDCFG=&quot;/etc/s3cmd.cfg&quot;
wget --output-document=$S3CMDCFG http://s3.amazonaws.com/$BUCKET_PUPPET/s3cmd.cfg
sed -i -e &quot;s#__AWS_ACCESS_KEY__#$AWS_ACCESS_KEY#&quot; \
    -e &quot;s#__AWS_SECRET_KEY__#$AWS_SECRET_KEY#&quot; $S3CMDCFG
chmod 400 $S3CMDCFG

until \
    s3cmd -c $S3CMDCFG sync --no-progress --delete-removed \
    s3://$BUCKET_PUPPET/ /etc/puppet/ &amp;&amp; \
    /usr/bin/puppet apply /etc/puppet/manifests/mainrun.pp ; \
do sleep 5 ; done</pre>
<p><code>s3cmd.cfg</code> in s3 is a publicly accessible template file, containing placeholders for the AccessKey and SecretKey that is supplied by the first-boot script. As <code>s3cmd.cfg</code> is a publicly accessible file, do not place any real credential data in it.</p>
<p>Puppet will install additional tools for keeping puppet running on all machines.</p>
<h2>Keeping Puppet Running</h2>
<p>As puppet is not running in agent mode, it does not wake up from a sleeping state to apply manifests that have changed since booting. We use cron to run puppet every 30 minutes. Our cron entry:</p>
<pre class="brush: plain; title: ; notranslate">*/30 * * * * sleep `perl -e 'print int(rand(300));'` &amp;&amp; /usr/local/sbin/puppet-run.sh &amp;gt; /dev/null</pre>
<p>We have the 5 minute sleep in the cron to ensure that machines run puppet at a staggered interval. This is to prevent all machines from restarting a service at the same moment (Apache for example), causing an interruption for our customers.</p>
<p>We have three simple scripts that are also installed by puppet for puppet:<br />
<code>/usr/local/sbin/puppet-run.sh</code>:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
/usr/local/sbin/puppet-update.sh
/usr/local/sbin/puppet-apply.sh</pre>
<p><code>/usr/local/sbin/puppet-update.sh</code>:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
BUCKET_PUPPET=&quot;puppet&quot;
/usr/bin/s3cmd -c /etc/s3cmd.cfg sync --no-progress \
    --delete-removed s3://$BUCKET_PUPPET/ /etc/puppet/</pre>
<p><code>/usr/local/sbin/puppet-apply.sh</code>:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
/usr/bin/puppet apply /etc/puppet/manifests/mainrun.pp</pre>
<p>We split puppet runs into three scripts to help with manual maintenance of a box. We run <code>sudo puppet-run.sh</code> if we simply want puppet to run immediately. <code>sudo puppet-apply.sh</code> is handy when making manual changes to the puppet manifests and modules for testing purposes; once testing is complete we copy our changes back into the git repository. <code>sudo puppet-update.sh</code> is infrequently run manually, mostly for resetting the puppet config when making manual testing changes.</p>
<h2>Final Thoughts</h2>
<p>As you can imagine there is a lot more involved in our manifests. There are a large number of conditional operators that enable and disable different parts of the manifests depending on what role an instance has.</p>
<p>EC2 tags have proven to be invaluable for us; each machine is assigned two tags that exactly describe any role. A script for reading the ec2 tags at boot combined with a <a href="http://docs.puppetlabs.com/guides/custom_facts.html">custom fact</a> is used to expose the ec2 tags to puppet.</p>
<p>Future posts about puppet may include:</p>
<ul>
<li>How we use EC2 tags for determining instance roles</li>
<li>Speeding up initial booting of instances</li>
<li>Using custom facts to enable one-off configurations for testing or debugging</li>
</ul>
<p>&#8211; Shane Meyers, SmugMug Operations</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/smugsorcery.wordpress.com/150/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/smugsorcery.wordpress.com/150/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=150&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sorcery.smugmug.com/2013/01/14/scaling-puppet-in-ec2/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/79f17f5c0a284698d096a71b2e5284a0?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">slmeyers</media:title>
		</media:content>
	</item>
		<item>
		<title>Speeding up SmugMug Search</title>
		<link>http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/</link>
		<comments>http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/#comments</comments>
		<pubDate>Thu, 09 Aug 2012 23:14:32 +0000</pubDate>
		<dc:creator>Ryan Grove</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[search]]></category>
		<category><![CDATA[YUI]]></category>

		<guid isPermaLink="false">http://sorcery.smugmug.com/?p=108</guid>
		<description><![CDATA[SmugMug users have uploaded millions of awesome photos, and one of the things we work hard on is making it easy and fun for people to discover them. SmugMug Search is an important part of this, since it allows anyone on the web to search among all public photos on SmugMug. It also helps drive [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=108&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
				<content:encoded><![CDATA[<p>SmugMug users have uploaded millions of awesome photos, and one of the things we work hard on is making it easy and fun for people to discover them. <a href="http://www.smugmug.com/search#q=sunset&amp;c=photos" target="_blank">SmugMug Search</a> is an important part of this, since it allows anyone on the web to search among all public photos on SmugMug. It also helps drive traffic to our Pros, many of whom make a living selling prints of their work.</p>
<p>Naturally we want Search to be fast, intuitive, and beautiful. But more importantly, we want to showcase our users&#8217; gorgeous photos. When people search for photos on SmugMug, they want to see photos, not a bunch of pagination links and other user interface clutter. So, a few months ago, we launched a redesigned Search page that does just that.</p>
<p style="text-align:center;"><a href="http://www.smugmug.com/search#q=sunset&amp;c=photos" target="_blank"><img class="size-full wp-image-109 aligncenter" title="SmugMug search results for &quot;sunset&quot;" src="http://smugsorcery.files.wordpress.com/2012/08/search-sunset1.jpg?w=595&#038;h=499" alt="SmugMug search results for &quot;sunset&quot;" width="595" height="499" /></a></p>
<p>We put those big gorgeous photos front and center, and we got rid of the ugly pagination links in favor of infinite scrolling&mdash;as you scroll down, more results are loaded automatically. This looks great and works well, but keeping the interface fast and responsive presented a lot of challenges, especially on older browsers or slower computers.</p>
<h3 style="margin:1.2em 0 .5em;">Performance Problems</h3>
<p>The first issue we faced is that executing JavaScript as the user scrolls&mdash;which is necessary in order to implement infinite scrolling&mdash;can really bog things down if you&#8217;re not careful, especially when the browser has to render all those big beautiful images. The solution was conceptually simple: just do less stuff as the user scrolls! In practice, though, that&#8217;s not always as simple as it sounds.</p>
<p>Behind the scenes, the search results are loaded into a <a href="http://yuilibrary.com/yui/docs/model-list/" target="_blank">YUI Model List</a> via <a href="https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest" target="_blank">XHR</a> and rendered using <a href="http://yuilibrary.com/yui/docs/view/" target="_blank">YUI Views</a>. As the user scrolls down, more results are loaded automatically, appended to the list, and rendered.</p>
<p>In the initial version of the search page, both a <a href="http://yuilibrary.com/yui/docs/model/" target="_blank">Model</a> instance and a View instance were created for each image&mdash;a classic MVC approach. This made logical sense and made the images easy to work with in code, but it really sucked from a performance perspective. Creating all those Model and View instances as the user scrolls meant that not only was the browser trying to load and render lots of images, it was also having to execute lots of complex JavaScript at the same time. Even in modern browsers on fast machines, this could be a burden.</p>
<p>Even worse, as the user scrolled further and further down and more results were loaded, memory usage skyrocketed. With potentially thousands of results on the page at once and a Model and View instance for each, users without lots of RAM sometimes saw things grind to a halt as the browser was forced to rely on virtual memory. This clearly wasn&#8217;t a good experience.</p>
<h3 style="margin:1.2em 0 .5em;">Performance Improvements</h3>
<p>To improve things, we dug deep into each of these performance issues, profiled the hell out of everything (<a href="https://developers.google.com/chrome-developer-tools/docs/profiles" target="_blank">Google Chrome&#8217;s CPU and Heap profilers</a> were a godsend for this), and then refactored every line of JavaScript on the search page based on our findings.</p>
<p>Here are some of the things we did to speed stuff up:</p>
<ul>
<li style="list-style-position:outside;padding-left:0;margin-left:20px;">
<p>We wrote a new <a href="http://yuilibrary.com/yui/docs/api/classes/LazyModelList.html" target="_blank">LazyModelList</a> class that extends YUI&#8217;s ModelList and makes it possible for the list to store and manipulate plain JavaScript objects rather than fully-instantiated Model instances. Plain objects are <em>much</em> cheaper to create and work with, both in terms of CPU overhead and memory usage, and LazyModelList makes it easy to revive a simple object into a full Model instance as needed, saving the work of doing it up front for every item. We contributed LazyModelList back to YUI, and it&#8217;s now available for everyone to use in YUI 3.6.0.</p>
</li>
<li style="list-style-position:outside;padding-left:0;margin-left:20px;">
<p>Instead of creating a View instance for each image tile, we now use a single master View instance for the entire list of results. As new results are added to the page, the master result view is responsible for rendering those new results without re-rendering any of the existing results on the page. Now, even when there are thousands of images on the page, there&#8217;s just a single view managing them all.</p>
</li>
<li style="list-style-position:outside;padding-left:0;margin-left:20px;">
<p>We wrote a new <a href="https://github.com/rgrove/yui3/tree/node-scroll-info/src/node-scroll-info" target="_blank">ScrollInfo plugin</a> for YUI&#8217;s Node component, which provides a highly efficient, throttled wrapper around the browser&#8217;s native <code>scroll</code> event. Since the <code>scroll</code> event can fire hundreds of times per second, throttling ensures that our event handlers only run, say, once every 50 or 100 milliseconds rather than on every single event. This puts less of a burden on the browser and ensures that more system resources are available to render content and keep the page feeling responsive as the user scrolls. This plugin isn&#8217;t yet available as part of YUI, but we&#8217;ve <a href="https://github.com/yui/yui3/pull/217" target="_blank">sent a pull request</a> and we hope they&#8217;ll accept it.</p>
</li>
<li style="list-style-position:outside;padding-left:0;margin-left:20px;">
<p>Our profiling revealed several memory leaks in YUI&#8217;s event system, which we were able to work around to improve memory usage even above and beyond our other improvements. The YUI team is aware of these issues. Some have already been fixed in YUI 3.6.0, and others will be addressed in an upcoming release.</p>
</li>
<li style="list-style-position:outside;padding-left:0;margin-left:20px;">
<p>Naturally we also took the opportunity to make lots of other minor improvements and fix lots of little bugs, but those weren&#8217;t directly related to the performance effort.</p>
</li>
</ul>
<h3 style="margin:1em 0 .5em;">Benchmarks &amp; Pretty Graphs</h3>
<p>Here are some pretty graphs demonstrating the effect of the performance improvements we made. Results were gathered using Google Chrome&#8217;s profiling tools on a Mac Pro (2.8 GHz Quad-Core) running OS X 10.8.</p>

<a href='http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/chart-01-time-to-initial-results/' title='Graph: Time to initial results'><img data-liked='0' data-reblogged='0' data-attachment-id="130" data-orig-file="http://smugsorcery.files.wordpress.com/2012/08/chart-01-time-to-initial-results.png" data-orig-size="595,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="Graph: Time to initial results" data-image-description="" data-medium-file="http://smugsorcery.files.wordpress.com/2012/08/chart-01-time-to-initial-results.png?w=300" data-large-file="http://smugsorcery.files.wordpress.com/2012/08/chart-01-time-to-initial-results.png?w=595" width="150" height="113" src="http://smugsorcery.files.wordpress.com/2012/08/chart-01-time-to-initial-results.png?w=150&#038;h=113" class="attachment-thumbnail" alt="Graph: Time to initial results" /></a>
<a href='http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/chart-02-time-to-1000-results/' title='Graph: Time to 1,000 results'><img data-liked='0' data-reblogged='0' data-attachment-id="131" data-orig-file="http://smugsorcery.files.wordpress.com/2012/08/chart-02-time-to-1000-results.png" data-orig-size="595,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="Graph: Time to 1,000 results" data-image-description="" data-medium-file="http://smugsorcery.files.wordpress.com/2012/08/chart-02-time-to-1000-results.png?w=300" data-large-file="http://smugsorcery.files.wordpress.com/2012/08/chart-02-time-to-1000-results.png?w=595" width="150" height="113" src="http://smugsorcery.files.wordpress.com/2012/08/chart-02-time-to-1000-results.png?w=150&#038;h=113" class="attachment-thumbnail" alt="Graph: Time to 1,000 results" /></a>
<a href='http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/chart-03-initial-memory-usage/' title='Graph: Initial memory usage'><img data-liked='0' data-reblogged='0' data-attachment-id="132" data-orig-file="http://smugsorcery.files.wordpress.com/2012/08/chart-03-initial-memory-usage.png" data-orig-size="595,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="Graph: Initial memory usage" data-image-description="" data-medium-file="http://smugsorcery.files.wordpress.com/2012/08/chart-03-initial-memory-usage.png?w=300" data-large-file="http://smugsorcery.files.wordpress.com/2012/08/chart-03-initial-memory-usage.png?w=595" width="150" height="113" src="http://smugsorcery.files.wordpress.com/2012/08/chart-03-initial-memory-usage.png?w=150&#038;h=113" class="attachment-thumbnail" alt="Graph: Initial memory usage" /></a>
<a href='http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/chart-04-memory-after-1000-results/' title='Graph: Memory usage after 1,000 results'><img data-liked='0' data-reblogged='0' data-attachment-id="133" data-orig-file="http://smugsorcery.files.wordpress.com/2012/08/chart-04-memory-after-1000-results.png" data-orig-size="595,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="Graph: Memory usage after 1,000 results" data-image-description="" data-medium-file="http://smugsorcery.files.wordpress.com/2012/08/chart-04-memory-after-1000-results.png?w=300" data-large-file="http://smugsorcery.files.wordpress.com/2012/08/chart-04-memory-after-1000-results.png?w=595" width="150" height="113" src="http://smugsorcery.files.wordpress.com/2012/08/chart-04-memory-after-1000-results.png?w=150&#038;h=113" class="attachment-thumbnail" alt="Graph: Memory usage after 1,000 results" /></a>

<p>We also created a <a href="http://jsperf.com/yui-modellist-vs-lazymodellist/4" target="_blank">jsPerf test for LazyModelList</a> to demonstrate how much faster it is than ModelList.</p>
<h3 style="margin:1.2em 0 .5em;">That Ain&#8217;t All</h3>
<p>It took some work, but now we&#8217;ve got a gorgeous search page that performs well even in older browsers and on slower machines. We&#8217;re pretty happy with it, and we hope you are too. But that&#8217;s not all! We&#8217;ve still got plenty more improvements planned, so keep your eyes peeled.</p>
<p>&mdash; Ryan Grove and Brian Strong, SmugMug Sorcerers</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/smugsorcery.wordpress.com/108/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/smugsorcery.wordpress.com/108/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=108&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sorcery.smugmug.com/2012/08/09/speeding-up-smugmug-search/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/683e9380d7cc0724a35dadfb4eeb142b?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">bitwiseplatypus</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/08/search-sunset1.jpg" medium="image">
			<media:title type="html">SmugMug search results for &#34;sunset&#34;</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/08/chart-01-time-to-initial-results.png?w=150" medium="image">
			<media:title type="html">Graph: Time to initial results</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/08/chart-02-time-to-1000-results.png?w=150" medium="image">
			<media:title type="html">Graph: Time to 1,000 results</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/08/chart-03-initial-memory-usage.png?w=150" medium="image">
			<media:title type="html">Graph: Initial memory usage</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/08/chart-04-memory-after-1000-results.png?w=150" medium="image">
			<media:title type="html">Graph: Memory usage after 1,000 results</media:title>
		</media:content>
	</item>
		<item>
		<title>Using HTML5&#8242;s Fullscreen API for Fun and Profit</title>
		<link>http://sorcery.smugmug.com/2012/06/06/using-html5s-fullscreen-api-for-fun-and-profit/</link>
		<comments>http://sorcery.smugmug.com/2012/06/06/using-html5s-fullscreen-api-for-fun-and-profit/#comments</comments>
		<pubDate>Wed, 06 Jun 2012 21:31:14 +0000</pubDate>
		<dc:creator>Ryan Doherty</dc:creator>
				<category><![CDATA[HTML5]]></category>
		<category><![CDATA[fullscreen]]></category>
		<category><![CDATA[html5]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://sorcery.smugmug.com/?p=85</guid>
		<description><![CDATA[For the past few weeks I&#8217;ve been working on a new super magical awesome feature that involves using the new HTML5 Fullscreen API. As with most brand spankin&#8217; new web APIs, its support and implementation varies per browser. I think it&#8217;s worth the effort considering how freaking awesome it is to do fullscreen web apps. [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=85&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
				<content:encoded><![CDATA[<p>For the past few weeks I&#8217;ve been working on a new super magical awesome feature that involves using the new <a href="http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html">HTML5 Fullscreen API</a>. As with most brand spankin&#8217; new web APIs, its support and implementation varies per browser. I think it&#8217;s worth the effort considering how freaking awesome it is to do fullscreen web apps.</p>
<h3>The Basics</h3>
<p>OK, let&#8217;s get started with the basics of how this new API works. Via the JavaScript function requestFullscreen you tell the browser you want a specific HTML DOM element to fill the entire screen with no browser chrome displayed.</p>
<pre class="brush: jscript; title: ; notranslate">
var myNode = document.querySelector(&quot;#myFullscreenNode&quot;);
myNode.requestFullscreen();
</pre>
<p>This is not the normal fullscreen mode that many browsers have where the browser&#8217;s viewport is simply stretched to the edges of the edges of the screen and the browser chrome is hidden. As far as I know that type of fullscreen mode is not standardized and is not accessible via JavaScript.</p>
<p>Currently Firefox, Safari and Chrome support the fullscreen API. But of course each implements it slightly differently, which is exactly why I&#8217;m writing this article and you&#8217;re reading it.</p>
<h3>Getting started</h3>
<p>According to the W3C specification, the first thing you should do is determine if the browser supports the fullscreen API <strong>and</strong> is currently in a state where it&#8217;s safe to go fullscreen. This is achieved via the `fullscreenEnabled` property on the document object. If the property exists and is true this means you can request the browser&#8217;s fullscreen mode. (Note the terminology: request. There&#8217;s no guarantee it will always work so don&#8217;t expect it to.)</p>
<p>You want to use this flag (if available) because a browser may support the fullscreen API but be in a state where it can&#8217;t go fullscreen (still loading content, a browser preference pane may be focused, etc).</p>
<p>To determine if fullscreen mode is available, check the .fullscreenEnabled property on the document object like this:</p>
<pre class="brush: jscript; title: ; notranslate">
if(document.fullscreenenabled) {
	var myNode = document.querySelector(&quot;#myFullscreenNode&quot;);
	myNode.requestFullscreen();
} else {
	dont();
}
</pre>
<p>Currently only Firefox has this property on the document object as &#8216;mozFullScreenEnabled&#8217; (note the capitalization), so it&#8217;s not worth relying on unless you <em>really</em> want to adhere to a draft spec.</p>
<p>The easier way to check if a browser supports the fullscreen API is to create a test HTML Node object and check if it has the requestFullscreen function on it:</p>
<pre class="brush: jscript; title: ; notranslate">
var testNode = document.createElement('div');

if(testNode.requestFullscreen) {
	var myNode = document.querySelector(&quot;#myFullscreenNode&quot;);
	myNode.requestFullscreen();
} else {
	//Fail
}
</pre>
<p>The above snippet is the spec format, for use in Firefox/Chrome use .mozRequestFullScreen and .webkitRequestFullScreen (again note capitalization!).</p>
<h3>Are we there yet?</h3>
<p>Let&#8217;s assume we have a browser that supports fullscreen mode. We can just call requestFullscreen() on the DOM Node we specify and we&#8217;re golden, right? Wrong! Just because we call the function doesn&#8217;t mean we&#8217;re guaranteed to go fullscreen. The user could press the Escape key during the transition to fullscreen or something could occur in the browser itself where it needs to abort. This is where listening for the events &#8216;fullscreenchange&#8217; and &#8216;fullscreenerror&#8217; is helpful (both are available prefixed in Firefox and Chrome, fullscreenerror is not available in Safari).</p>
<p>These events are fired on the document object, not on the node that was requested to go fullscreen. Adding to our code snippet above we get this:</p>
<pre class="brush: jscript; title: ; notranslate">
var testNode = document.createElement('div');

if(testNode.requestFullscreen) {
	document.onfullscreenchange = function(event) {
		//Fullscreen mode has changed
	}

	document.onfullscreenerror = function(event) {
		//Error!
	}

	var myNode = document.querySelector(&quot;#myFullscreenNode&quot;);
	myNode.requestFullscreen();
}
</pre>
<p>Again, the above code is per the spec, for Firefox and Chrome/Safari use &#8216;onmozfullscreenchange&#8217; and &#8216;onwebkitfullscreenchange&#8217;, respectively.</p>
<p>Given there&#8217;s a fullscreen change event object you&#8217;d assume that it will tell you which mode the browser is currently in, right? Wrong! You can&#8217;t tell which mode the browser is in from the event fired. Luckily there is a document property to determine which mode the browser is in. (Are we having fun yet?!)</p>
<p>To determine which mode the browser is in check the &#8216;fullscreenElement&#8217; property of the document object. If this property is not null the browser is in fullscreen mode (and the value is the DOM node that is fullscreen). Firefox, Chrome and Safari all support this property (namespaced).</p>
<pre class="brush: jscript; title: ; notranslate">
if(document.fullscreenElement) {
    //Yay, we're fullscreen!
}
</pre>
<h3>Checking for errors</h3>
<p>Even if we&#8217;ve checked the &#8216;fullscreenEnabled&#8217; and &#8216;fullscreenElement&#8217; properties betore we request fullscreenmode, it&#8217;s still possible that the browser will deny our request. When this happens the browser will fire a &#8216;fullscreenerror&#8217; event on the document object.</p>
<p>This can happen if there&#8217;s a user preference, security risk or platform limitation regarding fullscreen mode. Fullscreen mode is also only triggerable via user input (click, key press, etc), so if it is requested outside of those events the fullscreenerror event will be fired.</p>
<pre class="brush: jscript; title: ; notranslate">
var testNode = document.createElement('div');
if(testNode.requestFullscreen) {
	document.onfullscreenerror = function(event) {
		//Error!
	}
}
</pre>
<p>Firefox and Chrome support the onfullscreenerror events (prefixed), Safari does not.</p>
<h3>All together now</h3>
<p>Combining all our code examples above we get the following:</p>
<pre class="brush: jscript; title: ; notranslate">
if(document.fullscreenEnabled) {
	document.onfullscreenchange = function(event) {
		if(document.fullscreenElement) {
			//We are fullscreen! Rejoice!
		} else {
			//We're not fullscreen <img src='http://s0.wp.com/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> 
		}
	}

	document.onfullscreenerror = function(event) {
		//Something went wrong...
	}

	var fullscreenNode = document.querySelector(&quot;#myFullscreenNode&quot;);
	fullscreenNode.requestFullscreen();
}</pre>
<p>Unfortunately none of this code will work in any current browsers. A lot of conditional logic is needed to determine which set of fullscreen APIs are available (Firefox, Chrome and Safari all differ).</p>
<p>Fortunately for you, I&#8217;ve wrapped it all up into a convenience function that will return the correct set of events and fullscreen function for the browser or false if the current browser does not have fullscreen support:</p>
<pre class="brush: jscript; title: ; notranslate">function FullScreenSupport() {
    var TEST_NODE = document.createElement('div');
        REQUEST_FULLSCREEN_FUNCS = {
            'requestFullscreen': {'change':'onfullscreenchange',
                                  'request':'requestFullscreen',
                                  'error':'onfullscreenerror',
                                  'enabled':'fullscreenEnabled',
                                  'cancel': 'exitFullscreen',
                                  'fullScreenElement':'fullscreenElement'
            },
            'mozRequestFullScreen':{'change':'onmozfullscreenchange',
                                    'request':'mozRequestFullScreen',
                                    'error':'onmozfullscreenerror',
                                    'cancel': 'mozCancelFullScreen',
                                    'enabled':'mozFullScreenEnabled',
                                    'fullScreenElement':'mozFullScreenElement'
            },
            'webkitRequestFullScreen':{'change': 'onwebkitfullscreenchange',
                                       'request': 'webkitRequestFullScreen',
                                       'cancel': 'webkitCancelFullScreen',
                                       'error': 'onwebkitfullscreenerror',
                                       'fullScreenElement': 'webkitCurrentFullScreenElement'
            }
        },

        fullscreen = false;

        for(var prop in REQUEST_FULLSCREEN_FUNCS) {
            if(REQUEST_FULLSCREEN_FUNCS.hasOwnProperty(prop)) {
                if(prop in TEST_NODE) {
                    fullscreen = REQUEST_FULLSCREEN_FUNCS[prop];
                    //Still need to verify all properties are there as
                    //Chrome and Safari have different versions of Webkit
                    for(var item in fullscreen) {
                        if(!(fullscreen[item] in document) &amp;&amp;
                            !(fullscreen[item] in TEST_NODE)) {
                            delete fullscreen[item];
                        }
                    }
                }
            }

            if(fullscreen) {
                break;
            }
        }

        return fullscreen;
}
</pre>
<p>It ain&#8217;t pretty, but it does work. The function will return false if the browser doesn&#8217;t support fullscreen mode. Also note that just because a browser supports full screen mode doesn&#8217;t mean every function and property related to full screen mode is available, make sure it&#8217;s in the object FullScreenSupport returns.</p>
<h3>Styling it all</h3>
<p>Thought you were done? <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Along with the long list of JavaScript functions for fullscreen mode, there&#8217;s a little bit of CSS styling that is applied to the element that is shown fullscreen. According to the spec, an element that is fullscreen gets this CSS:</p>
<pre class="brush: css; title: ; notranslate">
  position:fixed;
  top:0; right:0; bottom:0; left:0;
  margin:0;
  box-sizing:border-box;
  width:100%;
  height:100%;
  object-fit:contain;
</pre>
<p>Firefox applies this by default along with background-color: black, Safari and Chrome do not apply the width and height properties. To make Chrome and Safari match the spec and Firefox&#8217;s behavior, you can use the :fullscreen pseudo class to apply these styles:</p>
<pre class="brush: css; title: ; notranslate">
#myFullscreenNode:-webkit-full-screen { //webkit prefix
  width:100%;
  height:100%;
  background-color: black;
}
</pre>
<p>Combine that with the FullScreenSupport function and you&#8217;ll have a relatively easy to use fullscreen API in three browsers! Also, if you happen to know anyone on the IE team please let them know they should implement it!</p>
<h3>References</h3>
<ul>
<li><a href="http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html">W3C Fullscreen API Spec</a></li>
<li><a href="https://developer.mozilla.org/en/DOM/Using_full-screen_mode">MDN Fullscreen API Docs</a></li>
</ul>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/smugsorcery.wordpress.com/85/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/smugsorcery.wordpress.com/85/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=85&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sorcery.smugmug.com/2012/06/06/using-html5s-fullscreen-api-for-fun-and-profit/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/032a06f3003dfe3706436938d4c196d9?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">rdohertysmugmug</media:title>
		</media:content>
	</item>
		<item>
		<title>Deriving JSON types in Go</title>
		<link>http://sorcery.smugmug.com/2012/04/06/deriving-json-types-in-go/</link>
		<comments>http://sorcery.smugmug.com/2012/04/06/deriving-json-types-in-go/#comments</comments>
		<pubDate>Fri, 06 Apr 2012 18:20:03 +0000</pubDate>
		<dc:creator>bradsmugmugcom</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://sorcery.smugmug.com/?p=63</guid>
		<description><![CDATA[At SmugMug, I am currently writing code in Go to support a proxy to a remote service that formats messages in JSON. A good strategy in Go is to create a type that matches the shape of the expected data. The example below is a trivial example of matching JSON of a known shape to [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=63&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
				<content:encoded><![CDATA[<p>
At SmugMug, I am currently writing code in <a href="http://golang.org/">Go</a> to support a proxy to a remote service that formats messages in JSON. A good strategy in Go is to create a type that matches the shape of the expected data. The example below is a trivial example of matching JSON of a known shape to a native type:</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import (
        &quot;encoding/json&quot;
        &quot;fmt&quot;
)

type MyJSONType struct {
        A int
        B string
}

func main() {
        s := &quot;{\&quot;A\&quot;:123,\&quot;B\&quot;:\&quot;hello\&quot;}&quot;
        var mjt MyJSONType
        um_err := json.Unmarshal([]byte(s),&amp;amp;mjt)                                  
        if um_err == nil {
                fmt.Printf(&quot;%v\n&quot;,mjt)                                            
        }                                                                         
}
</pre>
<p>
Using a technique like this, we can use robust native typing to help us detect when the JSON doesn&#8217;t match its expected shape.
</p>
<p>But what do we do when the JSON has a shape that varies? Typically in Go we utilize <b><tt>interface {}</tt></b> to match structures of an unknown shape, and let Go automatically create an <i>opaque</i> data structure. But what if we want to derive more useful native types? Consider some variations in JSON that show up in messages seen from our remote service:
</p>
<pre class="brush: cpp; title: ; notranslate">
{&quot;S&quot;:&quot;a string&quot;}

{&quot;N&quot;:&quot;123456&quot;}

{&quot;SS&quot;:[&quot;a string&quot;,&quot;another string&quot;]}

{&quot;NS&quot;:[&quot;123&quot;,&quot;456&quot;]}
</pre>
<p>
Go has powerful value inspection mechanisms for helping us determine how to get these bits into native types, and we can also use some intuition &#8211; we know that when we see a key of either <b>SS</b> or <b>NS</b>, we have <i>lists</i> of interface {} values. Using what we know, four functions emerge to help derive native types:
</p>
<pre class="brush: cpp; title: ; notranslate">
// take an interface{} string and turn it into a real string
func To_S(i interface{}) (string,error) {
        i_str,ok := i.(string)
        if !ok {
                e := fmt.Sprintf(&quot;cannot convert %v to string\n&quot;,i)
                return &quot;&quot;, errors.New(e)
        }
        return i_str,nil
}

// take an interface{} string and turn it into a real string
func To_N(i interface{}) (int64,error) {
        i_int64,ok := i.(int64)
        if !ok {
                e := fmt.Sprintf(&quot;cannot convert %v to int64\n&quot;,i)
                return 0, errors.New(e)
        }
        return i_int64,nil
}

// take an interface{} string and turn it into a list of real strings.
// also return a list of error elements if there were any
func To_SS(i interface{}) ([]string,[]string,error) {
        i_int,ok := i.([]interface {})
        if !ok {
                e := fmt.Sprintf(&quot;cannot convert %v to []interface {}\n&quot;,i)
                return nil,nil, errors.New(e)
        }
        var i_str_list []string
        var error_list []string
        for _,v := range i_int {
                i_str,ok := v.(string)
                if ok {
                        i_str_list = append(i_str_list,i_str)
                } else {
                        e_str := fmt.Sprint(&quot;%v&quot;,v)
                        error_list = append(error_list,string(e_str))
                }
        }
        if len(error_list) &amp;gt; 0 {
                return i_str_list,error_list,errors.New(&quot;some conversion errs&quot;)
        }
        return i_str_list,error_list,nil
}

// take an interface{} int64 and turn it into a list of real int64s.
// also return a list of error elements if there were any
func To_NS(i interface{}) ([]int64,[]string,error) {
        i_int,ok := i.([]interface {})
        if !ok {
                e := fmt.Sprintf(&quot;cannot convert %v to []interface {}\n&quot;,i)
                return nil,nil, errors.New(e)
        }
        var i_int64_list []int64
        var error_list []string
        for _,v := range i_int {
                i_int64,ok := v.(int64)
                if ok {
                        i_int64_list = append(i_int64_list,i_int64)
                } else {
                        e_str := fmt.Sprint(&quot;%v&quot;,v)
                        error_list = append(error_list,string(e_str))
                }
        }
        if len(error_list) &amp;gt; 0 {
                return i_int64_list,error_list,errors.New(&quot;some conversion errs&quot;)
        }
 return i_int64_list,error_list,nil
}
</pre>
<p>
Now we have native types that preserve the safety of the rest of our system.
</p>
<p>
submitted by Brad Clawsie</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/smugsorcery.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/smugsorcery.wordpress.com/63/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=63&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sorcery.smugmug.com/2012/04/06/deriving-json-types-in-go/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
	
		<media:content url="http://2.gravatar.com/avatar/87135c56a53b54fbb7bb70de8b372ae1?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">bradsmugmugcom</media:title>
		</media:content>
	</item>
		<item>
		<title>You shall not commit! (Without passing tests)</title>
		<link>http://sorcery.smugmug.com/2012/01/24/you-shall-not-commit-without-passing-tests/</link>
		<comments>http://sorcery.smugmug.com/2012/01/24/you-shall-not-commit-without-passing-tests/#comments</comments>
		<pubDate>Tue, 24 Jan 2012 22:21:04 +0000</pubDate>
		<dc:creator>Ryan Doherty</dc:creator>
				<category><![CDATA[Best Practices]]></category>
		<category><![CDATA[bazaar]]></category>
		<category><![CDATA[best practices]]></category>
		<category><![CDATA[bzr]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[phpunit]]></category>
		<category><![CDATA[revision control]]></category>
		<category><![CDATA[testing]]></category>
		<category><![CDATA[unit tests]]></category>

		<guid isPermaLink="false">http://smugsorcery.wordpress.com/?p=15</guid>
		<description><![CDATA[Welcome to the SmugMug engineering blog! Here at SmugMug we refer to our engineers as &#8216;Sorcerers&#8217; and this blog will be all about the magic behind smugmug.com. Our engineering landscape is as vast and varied as Middle Earth so we&#8217;ll start with something everyone&#8217;s familiar with: committing code and testing. At SmugMug our SCM tool [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=15&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
				<content:encoded><![CDATA[<p><a href="http://smugsorcery.files.wordpress.com/2012/01/shallnotcommit.jpg"><img class="size-full wp-image-16 aligncenter" title="You shall not commit" src="http://smugsorcery.files.wordpress.com/2012/01/shallnotcommit.jpg?w=595" alt=""   /></a></p>
<p>Welcome to the SmugMug engineering blog! Here at SmugMug we refer to our engineers as &#8216;Sorcerers&#8217; and this blog will be all about the magic behind <a href="http://www.smugmug.com">smugmug.com</a>. Our engineering landscape is as vast and varied as Middle Earth so we&#8217;ll start with something everyone&#8217;s familiar with: committing code and testing.</p>
<p>At SmugMug our SCM tool is <a href="http://bazaar.canonical.com/en/">Bazaar</a> (soon to be <a href="http://git-scm.com/">Git</a>) and the unit test framework we fancy is <a href="https://github.com/sebastianbergmann/phpunit">PHPUnit</a> since our website codebase is written in PHP. Running unit tests before committing is always a Good Thing for developers to do, but sometimes we forget and can commit code with broken tests. This is where a pre-commit hook comes in handy.</p>
<p>A pre-commit hook is a piece of code/script you tell your SCM tool to run after you type `bzr/git/svn commit` but <em>before </em>it actually commits your new code. A pre-commit hook can do just about anything, and with ours we run our unit tests and if they fail, we abort the commit.</p>
<p>The way we prevent ourselves from committing code with broken tests is by creating a short shell script that runs our unit tests via PHPUnit&#8217;s command line tool. Our script is named `precommit.sh&#8217; and is pretty simple:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
phpunit --bootstrap include/tests/bootstrap.php include/tests/
</pre>
<p>That script runs from the root of our Bazaar repo, telling PHPUnit where our bootstrap file and tests are. The script will return 0 if all tests passed (in Unix 0 means the command succeeded). If tests fail the script will return something other than 0. Now that we have that working (and our tests pass <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ), we can get Bazaar to run it before committing our code.</p>
<p>The way you get Bazaar to run a pre-commit hook is by writing a plugin that adds the hook to Bazaar every time you run `bzr commit`. Plugins are simply Python files placed in ~/.bazaar/plugins/[plugin-name].py . Bazaar executes them and the plugins can then do just about anything they please. Here&#8217;s our pre-commit hook plugin, which I&#8217;ll walk us through:</p>
<pre class="brush: python; title: ; notranslate">
from bzrlib import branch

def check_tests(local_branch, master_branch, old_revision_number, old_revision_id, future_revision_number, future_revision_id, tree_delta, future_tree):
    import os,subprocess
    from bzrlib import errors

    #Only execute our script if it exists in the current directory
    if not os.path.exists(&quot;precommit.sh&quot;):
        return

    #Run the pre-commit script
    tests_result = subprocess.call(os.path.abspath(&quot;precommit.sh&quot;), shell=True)

    #If our script returns something other than 0, tests have failed
    if tests_result != 0:
        raise errors.BzrError(&quot;Tests failed, no soup for you!&quot;)

#Install the hook with Bazaar
branch.Branch.hooks.install_named_hook('pre_commit', check_tests, 'Run PHPUnit tests pre-commit hook')
</pre>
<p>Our plugin does the following:</p>
<ul>
<li>Imports &#8216;branch&#8217; from the Python module bzrlib.</li>
<li>Defines our function to run before committing new code. A pre-commit hook function accepts eight arguments, none of which we&#8217;ll need though. (For more info on hooks with Bazaar, read the <a href="http://doc.bazaar.canonical.com/development/en/user-reference/hooks-help.html">hooks help page</a>)</li>
<li>Checks for the existence of precommit.sh, which runs our tests. We do this because we could be working with multiple repos, some which may not have a pre-commit script to run.</li>
<li>If it does not exist we return and Bazaar will commit our code.</li>
<li>If it does exist we run precommit.sh via subprocess.call() and save the return code as &#8216;tests_result&#8217;.</li>
<li>If tests_result is is not 0, our tests have failed and we raise a Bazaar error, which will abort the commit.</li>
<li>Installs the pre-commit hook via branch.Branch.hooks.install_named_hook(). The first argument is where the hook should be run, the second is the function to run and the third is a name for the hook to be used in progress and error messages.</li>
</ul>
<p>Here&#8217;s what it looks like for us if we commit code with broken tests:</p>
<p><a href="http://smugsorcery.files.wordpress.com/2012/01/brokentests2.jpg"><img class="alignnone size-full wp-image-42" title="Broken tests 3" src="http://smugsorcery.files.wordpress.com/2012/01/brokentests2.jpg?w=595" alt=""   /></a></p>
<p>With just a little bit of engineering we can have our own wizard preventing us from committing broken tests and code! Most other SCM tools have pre-commit hooks also (<a href="http://book.git-scm.com/5_git_hooks.html">Git</a>, <a href="http://svnbook.red-bean.com/en/1.7/svn.ref.reposhooks.pre-commit.html">SVN</a>). Stay tuned for more posts about our architecture and engineering processes here at SmugMug.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/smugsorcery.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/smugsorcery.wordpress.com/15/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sorcery.smugmug.com&#038;blog=31219926&#038;post=15&#038;subd=smugsorcery&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sorcery.smugmug.com/2012/01/24/you-shall-not-commit-without-passing-tests/feed/</wfw:commentRss>
		<slash:comments>22</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/032a06f3003dfe3706436938d4c196d9?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">rdohertysmugmug</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/01/shallnotcommit.jpg" medium="image">
			<media:title type="html">You shall not commit</media:title>
		</media:content>

		<media:content url="http://smugsorcery.files.wordpress.com/2012/01/brokentests2.jpg" medium="image">
			<media:title type="html">Broken tests 3</media:title>
		</media:content>
	</item>
	</channel>
</rss>
