<?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/"
	>

<channel>
	<title>Jim Walsh</title>
	<atom:link href="http://jimwalsh.name/feed/" rel="self" type="application/rss+xml" />
	<link>http://jimwalsh.name</link>
	<description></description>
	<lastBuildDate>Thu, 09 May 2013 14:00:24 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.3.1</generator>
	<item>
		<title>J Walsh Creative launches!</title>
		<link>http://jimwalsh.name/2012/09/j-walsh-creative-launches/</link>
		<comments>http://jimwalsh.name/2012/09/j-walsh-creative-launches/#comments</comments>
		<pubDate>Tue, 25 Sep 2012 05:51:51 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[News]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=201</guid>
		<description><![CDATA[Just a short post to let everyone know that my freelancing has now launched as an official LLC. You can check it out over at J Walsh Creative. Bear with me while I work through getting the site up to par. The nice thing is that most all of the back end items are taken [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Just a short post to let everyone know that my freelancing has now launched as an official LLC. You can check it out over at <a href="http://jwalshcreative.com" title="J Walsh Creative" target="_blank">J Walsh Creative</a>. Bear with me while I work through getting the site up to par. The nice thing is that most all of the back end items are taken care for the business. That means I&#8217;m ready and able to take on client work as well as look for some speaking opportunities. If you have either of those fee free to contact me!</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2012/09/j-walsh-creative-launches/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Caching Readability Bookmarks</title>
		<link>http://jimwalsh.name/2012/05/caching-readability-bookmarks/</link>
		<comments>http://jimwalsh.name/2012/05/caching-readability-bookmarks/#comments</comments>
		<pubDate>Sat, 12 May 2012 12:00:14 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[Scripting]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[readability]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=184</guid>
		<description><![CDATA[I&#8217;m currently working on a new design for my site and it is going to show recent stories I&#8217;ve saved in Readability. Readability, if you are not familiar, is at its core a site that allows you to save webpages for reading later. You can then read and archive them on the Readability site. They [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;m currently working on a new design for my site and it is going to show recent stories I&#8217;ve saved in <a title="Readability" href="http://readability.com" target="_blank">Readability</a>. Readability, if you are not familiar, is at its core a site that allows you to save webpages for reading later. You can then read and archive them on the Readability site. They have apps on the various platforms, and may applications are adding the ability to save links directly to Readability so you can read them later. I&#8217;ve found this more useful than some of the other various readers/social bookmarking sites. In the new design I wanted to share what I have been reading lately.</p>
<p>In working with a few other APIs, they eventually add rate limits, or get slower to respond to requests as their user base grows. To avoid these issues I decided to cache calls to the Readability API instead of doing them live on the site, client side. It isn&#8217;t anything terribly complicated, but I&#8217;ll review the code for the script.</p>
<pre class="brush: python; title: ; notranslate">
#!/usr/bin/python

import readability
from datetime import date, timedelta
from sqlalchemy import *
</pre>
<p>These are the required libraries for the script.</p>
<pre class="brush: python; title: ; notranslate">
'''		readability config		'''
token = readability.xauth('read_username', 'read_apikey', 'read_email', 'read_pass')
rdd = readability.oauth('read_username', 'read_apikey', token=token)
</pre>
<p>These lines are the config for the oauth call to the Readability API. You&#8217;ll need to acquire your API Key from the Readability site and then substitute all the parts here.</p>
<pre class="brush: python; title: ; notranslate">
'''		sqlalchemy setup		'''
engine = create_engine('mysql://db_username:db_passwd@db_host/db')
connection = engine.connect()
</pre>
<p>In searching and talking to some people it looked like <a title="SQLalchemy" href="http://www.sqlalchemy.org/" target="_blank">SQLalchemy</a> was going to be the best way to get Python to work with MySQL. This library made this script much easier than I expected.</p>
<pre class="brush: python; title: ; notranslate">
'''		Fetch new bookmarks since yesterday		'''
yesterday = date.today() - timedelta(1)

for b in rdd.get_bookmarks(added_since=yesterday.strftime('%m-%d-%y')):
	result = connection.execute(&quot;INSERT INTO bookmarks VALUES(NULL, &quot; + str(b.id) + &quot;, '&quot; + b.article.title.encode(&quot;utf-8&quot;) + &quot;', '&quot; + b.article.url.encode(&quot;utf-8&quot;) + &quot;')&quot; )
</pre>
<p>For your initial pull of your bookmarks into your database you would just need to change the for statement to &#8220;rdd.get_bookmarks()&#8221;. The data that is saved in the database, as you can see, is the readability bookmark id, the title and the URL for the story. There is more available in the API, but this should be sufficient to display a &#8216;currently reading&#8217; list in a widget on a webpage.</p>
<p>The end of this would then be to schedule this script in cron to run once a day. If you find yourself saving bookmarks more often, you may want to update these schedules.</p>
<p>The database structure and code are on <a title="Readability-Import on Github" href="https://github.com/jpwalsh1/readability-import" target="_blank">Github</a></p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2012/05/caching-readability-bookmarks/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Migrating to Amazon AWS</title>
		<link>http://jimwalsh.name/2012/05/migrating-to-amazon-aws/</link>
		<comments>http://jimwalsh.name/2012/05/migrating-to-amazon-aws/#comments</comments>
		<pubDate>Tue, 01 May 2012 12:00:57 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[News]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[aws]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=139</guid>
		<description><![CDATA[Web hosting can be a scary discussion for some people. People that create websites come in all shapes and sizes with all kinds of backgrounds. To some people running your own server and getting it setup can be a bit overwhelming. That is why many of the hosting companies out there that offer &#8216;one click [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Web hosting can be a scary discussion for some people. People that create websites come in all shapes and sizes with all kinds of backgrounds. To some people running your own server and getting it setup can be a bit overwhelming. That is why many of the hosting companies out there that offer &#8216;one click installs&#8217; of popular web software and various easy setups are doing so well. Those have their place, and I&#8217;ve used some that are very nice. I started to outgrow them and slowly realized I was paying for add-on features that I was never using and was better off moving to something different. It was time to go back to managing the entire server with no extra bells and whistles.</p>
<p><span id="more-139"></span></p>
<p>Awhile back I started using Amazon S3 for cloud storage online and for website backups. The more I used it, I started becoming more interested in their other offers in the AWS suite. I finally sat down with their calculator and started doing the math to figure out how much it would cost to at least move this blog over to full Amazon services. Beyond paying for features I wasn&#8217;t using, another thing that always weighed on me was single point of failure for my websites. They had backups, and I could restore them fairly quickly. But how resilient were they if the hosting company had an outage? What if I wasn&#8217;t around to run the site restore? I wanted a more resilient web environment, partly for myself, but for other people I host as well.</p>
<p>I&#8217;ve always ran my websites on the normal LAMP (Linux/Apache/MySQL/Perl|PHP|Python) stack. Simplest way to get this going in the AWS system was to get a reserved EC2 instance setup, and I chose the Amazon distro of Linux for now. I&#8217;m not going to go through a step by step setting up LAMP, there are plenty of guides for that. I&#8217;d really like to talk about Amazon&#8217;s ability to provide me the cheapest redundancy that I could find in a reliable hosting company.</p>
<p>Here is a diagram of what I setup for this site. I&#8217;ve since added a load balancer in front of the server, but the rest is the same.</p>
<p><a href="http://cdn.jimwalsh.name/f/jw.png" title="Diagram of jimwalsh.name" target="_blank">Diagram of jimwalsh.name.</a></p>
<p>If Amazon were to have a regional outage, they have datacenters in multiple regional zones you can deploy servers into. Many of their services are managed services, meaning that you dont need to install and run the applications like you would traditionally. For example some of their managed services are DNS, Load Balancing, Messaging, Queuing, Caching and Database to name a few. You would have to explore the pricing options to see if the managed service is worth it over setting it up manually on a server yourself. For me getting this off the ground I went with managed DNS and Load Balancing and Messaging. Database was a bit expensive, outside of the trial period, for a small blog. If you had a large application it quickly becomes a very attractive offering though.</p>
<p>The versatility and agility in which you can deploy servers let&#8217;s you architect just about anything you can imagine. I went from a single server that could fail and I would be offline until my provider fixed the issue, potentially requiring me to run a restore. To now having an image of my server ready to deploy in any regional zone if needed. Then nightly backups to get the image up to speed. I&#8217;m only using that method now because it keeps costs down. It would be just as easy to have hot spare servers in other regional zones and just turn them on automatically if another one goes down. I can even put up a few more servers behind the load balancer and just run a normal web farm if I needed the power. Those are just a few of the possibilities, all with pricing that is very competitive to other places. </p>
<p>They also have something that is rather unique in their auto scaling offering. You can define thresholds that can kick off jobs to create/start extra servers to handle additional load, as well as jobs to reduce it back down as well. Very cool stuff.</p>
<p>All in all I&#8217;ve been very pleased with the move. Setup was simple and easy and the options available give me more recovery and resiliency options than I&#8217;ve ever had in the past. Definitely check into it if you are starting to outgrow your host.</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2012/05/migrating-to-amazon-aws/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Opera CSS Prefixes, one giant leap backwards</title>
		<link>http://jimwalsh.name/2012/04/opera-css-prefixe-one-giant-leap-backwards/</link>
		<comments>http://jimwalsh.name/2012/04/opera-css-prefixe-one-giant-leap-backwards/#comments</comments>
		<pubDate>Thu, 26 Apr 2012 04:18:48 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[News]]></category>
		<category><![CDATA[browers]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[prefixes]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=152</guid>
		<description><![CDATA[Today, .Net Mag informed the world that Opera was going to implement the &#8216;-webkit&#8217; vendor prefix. We all saw this coming a few months ago, but more surprising than that it happened were the comments by Opera as to why they were doing it. They chose to blame the developers. Myself and others have clamored [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Today, <a href="http://www.netmagazine.com/news/opera-confirms-webkit-prefix-usage-121923" title="Opera caved in" target="_blank">.Net Mag informed the world</a> that Opera was going to implement the &#8216;-webkit&#8217; vendor prefix. We all saw this coming a few months ago, but more surprising than that it happened were the comments by Opera as to why they were doing it.</p>
<p>They chose to blame the developers.</p>
<p><span id="more-152"></span></p>
<p>Myself and others have clamored for quite awhile that vendor prefixes should stay in their development trees and not enter their production releases until they are in the official CSS spec. The vendors could then pressure <a href="http://www.w3.org/Style/CSS/members.en.php3" title="CSS WG" target="_blank">CSS WG</a> to move more quickly and with more agility than they are now. W3C&#8217;s slow pace at formalizing some of these definitions is really why we are in the place we are now. Opera, is more or less calling developers lazy because they were &#8216;only&#8217; including webkit prefixes instead of the &#8216;standard&#8217; five prefixes that are required to have a rule properly work and failback in all browsers. So let me get this straight. Opera is calling out developers for being lazy about addressing a problem that the browser manufacturers are responsible for creating? That makes sense to me, how about you?</p>
<p>The effort should not be spent adding other vendors prefixes to your product. If Opera felt that the web is making a bad shift towards excluding standards, and &#8216;standardizing&#8217; on using one particular vendor prefix, then you should fight with the standards group and get traction made there. Instead they went the other direction, threw their hands up and just went the easy route of just adding those prefixes to their product.</p>
<p>Within the next few weeks I would expect Firefox and Internet Explorer to announce the same. Why would they choose to be &#8216;left behind&#8217;? Time and effort needs to be directed towards advancing the standards at a greater rate. With the recent explosion of CSS preprocessors I think it&#8217;s fair to say that it&#8217;s time to move beyond hand writing CSS code. There should be an Open Source product that all the vendors and the community could contribute. This product should produce standards based CSS that works in all browsers, with the same results across the board. It&#8217;s 2012, why are we still fighting with CSS and cross browser compatibility? Let&#8217;s <a href="http://movethewebforward.org/" title="MTWF" target="_blank">move the web forward</a>, but now even further.</p>
<p>Maybe Opera should kick off that Open Source CSS Generator project, and help repair this mess they are only helping to make worse.</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2012/04/opera-css-prefixe-one-giant-leap-backwards/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Amazon S3 Folder Sync &#8211; OSX Edition</title>
		<link>http://jimwalsh.name/2012/04/amazon-s3-folder-sync-osx-edition/</link>
		<comments>http://jimwalsh.name/2012/04/amazon-s3-folder-sync-osx-edition/#comments</comments>
		<pubDate>Sun, 22 Apr 2012 02:39:40 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Dropbox]]></category>
		<category><![CDATA[S3]]></category>
		<category><![CDATA[shell]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=142</guid>
		<description><![CDATA[I have been a fan for Amazon&#8217;s AWS for quite some time now. Previously I was just using it for backups of my websites and for offsite storage for my Pictures and other files. Like many others I have been using Dropbox for awhile now. Having my own Amazon S3 account and Dropbox seemed a [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>I have been a fan for <a href="http://aws.amazon.com/" title="Amazon AWS" target="_blank">Amazon&#8217;s AWS</a> for quite some time now. Previously I was just using it for backups of my websites and for offsite storage for my Pictures and other files. Like many others I have been using Dropbox for awhile now. Having my own <a href="http://aws.amazon.com/s3/" title="Amazon S3" target="_blank">Amazon S3</a> account and <a href="http://dropbox.com" title="Dropbox" target="_blank">Dropbox</a> seemed a bit redundant, since Dropbox is S3 on the backend, and I&#8217;m already paying for Amazon S3. I like the thought of having more control over my data, and Dropbox has been hacked in the past. I started looking into different ways to leverage S3 storage on my Mac laptop and my PC.</p>
<p><span id="more-142"></span></p>
<p>For now we&#8217;ll focus on the various ways I can use the S3 storage on my Mac. One of the best FTP clients is <a href="http://panic.com/transmit/" title="Transmit by Panic" target="_blank">Panic&#8217;s Transmit</a>. They added the ability to transfer files to S3 some time ago. It also has an option to mount any of the sites you setup in the application as drives. Using Transmit proves to be a very quick and easy way to get files up to S3.</p>
<p>I started looking into various methods I could use to mimic Dropbox&#8217;s filesystem monitoring and what tools I could use. Not many of the people I chat with about web design and development talk about unit testing. I used it when redeveloping the new Gamegossip homepage and backend, and many programmers use it. What some people do is to setup their unit testing software to automatically check files when they change. So I started looking into these types of apps that were watching the filesystem and sending notifications. The end result being that I could pickup these changes, log them, and then sync them to S3.</p>
<p>That search lead me many places and some of them I&#8217;m still working on, but I got a quick and dirty solution working so I thought I&#8217;d share that first. The solution involves an app called <a href="http://www.jets3t.org/" title="JetS3t" target="_blank">JetS3t</a>. The initial setup for JetS3t is trivial so I wont go into detail with that. What I wanted to do with this was schedule some directories to be synchronized and schedule that to happen every so often. I settled in on wanting it to check every five minutes. Knowing that some uploads were probably going to take longer than 5 minutes I needed to figure out a way to not launch multiple syncs of the same data. Here is what I came up with.</p>
<p>Write a wrapper script that checks to see if the sync script is running. If it isn&#8217;t, then fire off then sync script. I wanted to do this PID based but that wasnt working as well as I wanted so I just used trusty old ps.</p>
<p>s3wrapper.sh</p>
<pre class="brush: bash; title: ; notranslate">
#!/usr/bin/bash

if ps -ef | grep -v grep | grep synchronize.sh ; then
  echo &quot;Already running.&quot;
  exit 99
fi

/jets3t/bin/synchronize.sh DOWN jpw/Books /Users/jwalsh/Documents/Books/* &gt; /var/log/s3upload.log &amp;
/jets3t/bin/synchronize.sh UP jpw/Books /Users/jwalsh/Documents/Books/* &gt; /var/log/s3upload.log &amp;
</pre>
<p>You just keep adding lines like the last two lines to add other folders to sync up to S3. The syntax is pretty easy, as noted in the JetS3t documentation. We are using &#8216;UP&#8217; for the upload and &#8216;DOWN&#8217; to download, the next part is the S3 bucket and folder inside it. The last part is the folder to sync, and then logging the actions. You may want to follow this up by adding a logrotate entry so that the log doesnt get too large. We need to use both UP/DOWN to ensure you capture any changes fully.</p>
<p>Once you have that script written you just need to schedule it in Crontab. You type the following to start the Crontab editor.</p>
<pre class="brush: bash; title: ; notranslate">crontab -e</pre>
<p>This line will run the script every five minutes. If you are not familiar with Crontab syntax, there are generators and tutorials around to assist. Once in the Crontab editor, normal vi commands will work.</p>
<pre class="brush: bash; title: ; notranslate">*/5 * * * * /jets3t/s3wrapper.sh</pre>
<p>Now your folder will be checked and synchronized every five minutes as long as your computer is on. The upgrade to this script would be me to find something or write something that integrates with FSEvents API to detect changes to monitored folders and then kick off the syncs then, instead of scheduling them. That version would also have to include caching the changes in case the Internet connection is not available. The advantage to all of this may be minimal to some, others may love being able to have full control over their data. This method should at least get you on the right path. Good luck and let me know how this works out for you!</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2012/04/amazon-s3-folder-sync-osx-edition/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Twitter oEmbed API Caching</title>
		<link>http://jimwalsh.name/2011/12/twitter-oembed-api-caching/</link>
		<comments>http://jimwalsh.name/2011/12/twitter-oembed-api-caching/#comments</comments>
		<pubDate>Sat, 10 Dec 2011 06:56:03 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[oembed]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[twitter]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=109</guid>
		<description><![CDATA[In all of the Twitter teams recent updates this week, one of them was to expose their new oEmbed API. This is a rate limited service meaning that they will throttle and potentially blacklist you if you hammer them with requests. The workaround for this is to cache the results. In this article we will [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>In all of the Twitter teams recent updates this week, one of them was to expose their new <a href="http://oembed.com/" title="oEmbed" target="_blank">oEmbed</a> API. This is a <a href="https://dev.twitter.com/docs/rate-limiting" title="Twitter Rate Limiting" target="_blank">rate limited</a> service meaning that they will throttle and potentially blacklist you if you hammer them with requests. The workaround for this is to cache the results. In this article we will create a simple caching system for this API. This caching system can be extended for other oEmbed API&#8217;s out there. The oEmbed site has a list of some of the others.</p>
<p><span id="more-109"></span></p>
<p>Twitter has a doc on their various <a href="https://dev.twitter.com/docs/embedded-tweets" title="Embedded Tweets" target="_blank">Embedded Tweets</a> options. But we are going to focus on the oEmbed version. We will need some things setup before we get started with the meat and potatoes of this exercise.</p>
<p>We will be utilizing the ID of a Twitter post for this example. This is the long number in the URL of a Tweet. In the new Twitter layout just click a Tweet and hit Details. You will then be taken to the URL for the post and you can copy the number at the end of the URL for this. Example: https://twitter.com/#!/jpwalsh1/status/<strong>145224413200130048</strong>. The API also allows you to just send the whole URL if you choose to write your app that way as well. </p>
<p>Now we need to setup a database for the caching of the Tweets. Here is some code you can use to do that.</p>
<pre class="brush: sql; title: ; notranslate">
CREATE TABLE `twitter` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tweetid` bigint(30) unsigned NOT NULL,
  `html` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='tweet cache';
</pre>
<p>With that setup, now create a new PHP file and add your typical code you use to connect to your database. We are going to create two functions, one to check if the Tweet is in the cache, and one to add it to the cache.</p>
<pre class="brush: php; title: ; notranslate">
function check_cache($tweet){
	
	$qCheck_cache = &quot;SELECT * from twitter where tweetid =&quot; . $tweet;
	$rCheck_cache = mysql_query($qCheck_cache);
	
	if (mysql_num_rows($rCheck_cache) == 0) {
		// Tweet isn't cached, grab and save.		
		$html = add_cache($tweet);
		
	} else {
		// Tweet is in the Cache, grab it.
		$cached_tweet = mysql_fetch_assoc($rCheck_cache);
		$html = stripslashes($cached_tweet[&quot;html&quot;]);
	}
		
return $html;
	
} // end check_cache
</pre>
<p>Now let&#8217;s review what this function does.</p>
<p>Lines 3-4: Builds and checks for the Tweet in the database.<br />
Line 6: Checks to see if the query to find the Tweet returned anything.<br />
Line 8: Call the add_cache function to add this Tweet to the database and return the html to be displayed.<br />
Lines 12-13: Pulls the Tweet out of the database, removes extra slashes in the HTML required to store it in the database and returns it.</p>
<p>Now we will check out that add_cache function and see what it does.</p>
<pre class="brush: php; title: ; notranslate">
function add_cache($tweet){
	
	$twitterapi_url = &quot;https://api.twitter.com/1/statuses/oembed.json?id=&quot;;
	$twitterapi_url = $twitterapi_url . $tweet;
	
	$curl = curl_init($twitterapi_url);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	$response = curl_exec($curl);
	curl_close($curl);
	
	$json_content = json_decode($response, true);
	
	$html = $json_content['html'];
	$qInsert_cache = &quot;INSERT INTO `twitter` (`tweetid`, `html`) VALUES (&quot; . $tweet . &quot;,'&quot; . addslashes($html) .&quot;')&quot;;
	$rInsert_cache = mysql_query($qInsert_cache);
	
	return $html;

} // end add_cache
</pre>
<p>Lines 3-4: Build the API URL<br />
Lines 6-9: Leverage CURL to fetch the API URL and JSON return.<br />
Line 11: Decode said JSON content<br />
Line 13: Grab the HTML element out of the JSON array.<br />
Lines 14-15: Build the SQL to add the Tweet to the database. Adding slashes in the HTML code so that it goes into the database properly, and then execute the query.<br />
Line 17: Return the HTML to be displayed.</p>
<p>Now to use these functions in this test page you will use this code.</p>
<pre class="brush: php; title: ; notranslate">
$tweet_id = $_GET[&quot;id&quot;];	
$tweet_display = check_cache($tweet_id);

echo $tweet_display;
</pre>
<p>Line 1: Grab the ID variable from the URL.<br />
Line 2: Call the functions, passing the Tweet ID and saving the result.<br />
Line 4: Print the result.</p>
<p>Now obviously there is no error checking shown, and this is more a technical demo than something you should copy and paste into your site for use. But this should give you a better understanding how to cache the result from an oEmbed API. Let me know what you think, or what you have come up with as well. You can also add in how you would like to expire your cache, if at all.</p>
<p>Here is the example URL<br />
<a href="http://jimwalsh.name/demos/oembed/oembed.php?id=145224413200130048" title="oEmbed Demo" target="_blank">http://jimwalsh.name/demos/oembed/oembed.php?id=145224413200130048</a><br />
You can change the ID in the URL to the ID of another Tweet to see it change.</p>
<p>The functions are also available on <a href="https://github.com/jpwalsh1/twitter-oembed-php" title="Github" target="_blank">Github</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2011/12/twitter-oembed-api-caching/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Website Backup Redux</title>
		<link>http://jimwalsh.name/2011/08/website-backup-redux/</link>
		<comments>http://jimwalsh.name/2011/08/website-backup-redux/#comments</comments>
		<pubDate>Thu, 18 Aug 2011 23:48:29 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[Scripting]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[backups]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[shell]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=102</guid>
		<description><![CDATA[In my previous post I mentioned that I would update the script to allow for incremental backups of the filesystem and MySQL. Depending on your ability to alter your MySQL config, the manner at which I went about the incremental backups may not work for you. It turned out I had to change parts of [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>In my previous post I mentioned that I would update the script to allow for incremental backups of the filesystem and MySQL. Depending on your ability to alter your MySQL config, the manner at which I went about the incremental backups may not work for you. It turned out I had to change parts of the script dramatically so I decided to create a new post with the changes.</p>
<p><span id="more-102"></span></p>
<p>It also turns out that if I would have read the man page of tar for a second I would have found the simple manner in which you can create incremental tar files.</p>
<pre class="brush: plain; title: ; notranslate">
-g, --listed-incremental F
    create/list/extract new GNU-format incremental backu
</pre>
<p>As you can see the -g option will create our (.snar) file that will store what is in the backup. Before a full backup you will want to remove .snar file so that all the files will be grabbed by tar. I use the Ruby wday function to determine what day of the week it is, zero is Sunday, the day I want to run full backups. I also append the three character abbreviation for the day of the week at the end of the backup file name. This is partly because I wanted to store all the backups for a week in one S3 bucket, so we needed unique names. You will want to backup your .snar file too, dont forget about it.</p>
<p>Here is the new Filesystem backup section.</p>
<pre class="brush: ruby; title: ; notranslate">
## Filesystem Backup ##
#

site_file = &quot;#{backup_name}_#{t.strftime('%a')}.tar.gz&quot;

# On Sunday do Full Backup
if t.wday == 0
# Delete incremental file
        system(&quot;rm -f fs_backup.snar&quot;)
end

# Run filesystem backup
system(&quot;tar -cz -g fs_backup.snar -f #{site_file}&quot;)
#
</pre>
<p>Now we move on to the MySQL incremental backup. This was a bit more involved. This requires a MySQL configuration change to enable <a href="http://dev.mysql.com/doc/refman/5.0/en/binary-log.html" title="MySQL Binary Logging" target="_blank">Binary Logging</a> if you don&#8217;t already have this enabled. I enabled it by placing &#8216;log-bin&#8217; as the first line in my my.cnf and then restarted MySQL and the binary log showed up in the MySQL directory. You will now need to alter the grants setup on your backup user account. You will need to grant Reload and Super to the system. After that is complete you will need to grant Select and Lock on the individual databases that you want to backup with this user account.</p>
<p>Sample Grants</p>
<pre class="brush: plain; title: ; notranslate">
mysql&gt; show grants for backup@localhost;
+-----------------------------------------------------------------------------------------------------------------------+
| Grants for backup@localhost                                                                                           |
+-----------------------------------------------------------------------------------------------------------------------+
| GRANT RELOAD, SUPER ON *.* TO 'backup'@'localhost' IDENTIFIED BY PASSWORD '[encoded passwd]' |
| GRANT SELECT, LOCK TABLES ON `sweetDatabase`.* TO 'backup'@'localhost'                                                          
</pre>
<p>Now that you have your user account setup, and MySQL creating binary logs we can talk about backing them up. Instead of creating individual files for each database, we will dump out all the databases into one file. This seems to be the easiest way to get the backups working with binary logging, if anyone knows a way to get each database individually let me know and I&#8217;ll post an update.</p>
<p>Again on Sunday we will run a full MySQL dump of all the databases. In the mysqldump command when we pass &#8211;flush-logs it will rotate the binary log. We will also pass &#8211;delete-master-logs so that it removes all the old binary logs so that we don&#8217;t manually have to manage the logs filling the drive. I&#8217;m debating finding a way to have these logs removed during incrementals too, since we upload the logs each day, it seems redundant to keep them on the system and upload them each day of the week.</p>
<p>For our incremental backups all we need to do is rotate the binary logs and then back them up. I will grab all but the newest log, and copy them to the temporary backup directory so they get uploaded to Amazon and the FTP. You will need to add each database that you want backed up to the mysql dump command, one after another separated only by spaces. The locations of the binary logs can be changed in the my.cnf, and even your file path may be different than what I have listed below, so update that path to match where your logs are located.</p>
<p>New Database backup section.</p>
<pre class="brush: ruby; title: ; notranslate">
## Database Backup ##
#

if t.wday == 0
#Today is Sunday, run the full backup
        full_backup_cmd = &quot;mysqldump -u #{db_user} -p#{db_pass} --flush-logs --delete-master-logs --master-data=2 --add-drop-table --lock-all-tables --databases database1 database2 database3 | gzip -9 &gt; db-backup.sql.gz&quot;
        system(full_backup_cmd)

else
#Roll logs for incremental backup
        system('mysqladmin -u#{db_user} -p#{db_pass} flush-logs')

        #Copy old logs to directory to be backed up

        logs = Dir.glob(&quot;/var/lib/mysql/mysqld-bin.[0-9]*&quot;).sort
        logs_archive = logs[0..-2]
        logs_archive.each do |log|
                 system(&quot;cp #{log} #{backup_dir}&quot;)
        end #end copy
end #db backup
##
</pre>
<p>The last part to do now is to clean up your backup directory, so just update your rm command you may be using or whatever technique you chose to use.</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2011/08/website-backup-redux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Backup website with Ruby and Amazon S3</title>
		<link>http://jimwalsh.name/2011/08/backup-website-with-ruby-and-amazon-s3/</link>
		<comments>http://jimwalsh.name/2011/08/backup-website-with-ruby-and-amazon-s3/#comments</comments>
		<pubDate>Fri, 12 Aug 2011 00:25:56 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[Scripting]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[backups]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[shell]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=94</guid>
		<description><![CDATA[Recently I migrated to a new server at Media Temple, with a new server comes a new backup script with a couple new features this time around. The new backup script will backup all of your files relating to your websites, as well as run mysqldumps on all the databases that you need. The script [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Recently I migrated to a new server at <a href="http://mediatemple.net/" title="Media Temple" target="_blank">Media Temple</a>, with a new server comes a new backup script with a couple new features this time around. The new backup script will backup all of your files relating to your websites, as well as run mysqldumps on all the databases that you need. The script will then FTP the files offsite, as well as save them to the <a href="http://aws.amazon.com/s3/" title="Amazon S3" target="_blank">Amazon S3</a> cloud storage.</p>
<p><span id="more-94"></span></p>
<p>This time around we&#8217;ll be writing the script in Ruby, mostly because Amazon provides an sdk that will make it very easy for us to upload our files to their S3 service. I&#8217;m going to assume that you signed up for an S3 account (free for a year) and you grabbed your two security keys, and now we&#8217;ll jump into the script. This first version of the script is only doing full backups. I hope to set some time aside and get the incremental parts created.</p>
<h2>Script Configuration</h2>
<p>Here we will look at the required libraries from Ruby, and you can read through the config section it is very self explanatory, so we wont spend much time talking about this. This script does assume that you already created a bucket in S3, if you want to create one each upload, that is just one more line of code you can add to this script. I got lazy and I use the same user/pass for my db backup account and my remote FTP account. This is easily separated if you require that as well.</p>
<pre class="brush: ruby; title: ; notranslate">
#!/usr/local/bin/ruby
require 'rubygems'
require 'aws/s3'
require 'net/ftp'
require 'time'
require 'fileutils'

include AWS::S3

## Timestamp for backup
t=Time.now
timestamp = t.strftime('%d-%m-%Y')

## Config
# Connect to S3
AWS::S3::Base.establish_connection!(
  :access_key_id     =&gt; 'S3 Access Key',
  :secret_access_key =&gt; 'S3 Secret Key'
)
s3_bucket = 'S3 bucket to upload the backups'
db_user = 'mysql/ftp backup username'
db_pass = 'mysql/ftp backup password'
site_dir = '/dir/to/files/to/backup/*'
url = 'hostname/ip of place we want to FTP the files off to'
directory = 'remote ftp directory to upload into'
backup_name = 'name for filesystem tarbal'
##
</pre>
<h2>MySQL Database Backup</h2>
<p>Note: Before you can run this chunk of code you will need to configure your MySQL backup account properly. I recommend doing grants for each database that you want to backup. The &#8216;SELECT&#8217; and &#8216;LOCK TABLES&#8217; are the only permissions that you need to grant to the account. </p>
<p>We start this snippet by building the command to obtain a list of databases from the server. We then loop through this list of databases, generating a mysqldump command, and then executing it. If the script comes to a database that it doesnt have access, it will skip it and no harm will be done. The last two lines in the loop were a clever way to get the loop to wait for the mysqldump command to finish before doing the next object in the array. Otherwise it will go so fast you&#8217;ll have multiple mysqldump threads running and you get errors about hitting your open files limit quite quickly. If some of you have a more elegant way to get around this issue, leave a comment or send me a tweet.</p>
<pre class="brush: ruby; title: ; notranslate">
## Database Backup ##
#

command = &quot;mysql -u #{db_user} -h localhost -p#{db_pass} -Bse 'show databases'&quot;

IO.popen(command) do | databases |
	databases.each_line do | db |
		dbase = db.split.join(&quot;\n&quot;)
		file = &quot;#{dbase}.backup.sql.gz&quot;
		dumpdb = &quot;mysqldump -u #{db_user} -h localhost -p#{db_pass} #{dbase} | gzip -9 &gt; #{file}&quot;
		output = IO.popen(dumpdb)
		output.readlines	
	end
end
##
</pre>
<h2>Filesystem Backup</h2>
<p>The filesystem backup is quite simple. I set my backup path to be the folder all my websites are hosted. Then I just backup this entire folder into one large tarball. Currently S3 has a 5GB file limit, so depending on how large your tarball gets you may want create multiple tarballs. You should also ensure that the user account you choose to run this script has access to all these folders so you get all the files into the tarball.</p>
<pre class="brush: ruby; title: ; notranslate">
## Filesystem Backup ##
#

site_file = &quot;#{backup_name}.tar.gz&quot;
system(&quot;tar -czf #{site_file} #{site_dir}&quot;)

#
</pre>
<h2>Amazon S3 Upload</h2>
<p>Now that we have all the files created, let&#8217;s upload them to S3. This snippet starts by finding the bucket you already created, and then clearing it out, preparing it for the upload. I decided to go this route because I wanted to keep yesterdays backup in the S3 system, meanwhile I&#8217;ll keep a copy of each backup on my remote FTP in the event that I need something older than yesterdays data. After we empty the S3 bucket, we then upload all the files in the current directory.</p>
<pre class="brush: ruby; title: ; notranslate">
## S3 Upload ##
#

bucket = AWS::S3::Bucket.find(s3_bucket)
#empty old backup in S3
while(!bucket.empty?)
	begin
		bucket.objects.each do |object|
		object.delete
		end
	end
end
#upload to S3
uploads = Dir[&quot;*&quot;]
uploads.each do |uploadfile|
	AWS::S3::S3Object.store(uploadfile, open(uploadfile), s3_bucket)
end
#
</pre>
<h2>Offsite FTP and Cleanup</h2>
<p>One last section to go, the Offsite FTP section. We connect to the FTP, authenticate, and then, just like for S3, we upload all the files in the current directory. Then close the FTP connection and your backup is complete! The next command will remove all the SQL and filesystem backup files from the directory. The FTP commands are pretty straightforward so if you need to navigate or setup your FTP in a different manner, it shouldn&#8217;t take too much effort. The only remaining task is to schedule this to run as often as you like in your favorite scheduling utility. I end up running the script as root in crontab. Thanks again for reading and check back as hopefully I&#8217;ll get the incremental backup code written soon.</p>
<pre class="brush: ruby; title: ; notranslate">
## FTP Offsite ##
#

ftp=Net::FTP.new
ftp.connect(url,21)
ftp.login(db_user, db_pass)
ftp.chdir(directory)
ftp.mkdir(timestamp)
ftp.chdir(timestamp)

uploads.each do |f|
	ftp.putbinaryfile(f)
end

ftp.close
##

## Remove backup files
system ('rm -f *.gz')
#
</pre>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2011/08/backup-website-with-ruby-and-amazon-s3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP Bitly Wrapper</title>
		<link>http://jimwalsh.name/2011/06/php-bitly-wrapper/</link>
		<comments>http://jimwalsh.name/2011/06/php-bitly-wrapper/#comments</comments>
		<pubDate>Thu, 02 Jun 2011 04:13:05 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[Bitly]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=84</guid>
		<description><![CDATA[One of the most popular URL Shortening Services is Bitly. In lieu of writing my own URLSS or using a prepackaged solution I decided to leverage the Bitly API and their engine for the URL shortening required for one of my projects. I needed to write up a quick and dirty function that would take [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>One of the most popular URL Shortening Services is <a title="Bitly" href="http://bit.ly" target="_blank">Bitly</a>. In lieu of writing my own URLSS or using a prepackaged solution I decided to leverage the Bitly API and their engine for the URL shortening required for one of my projects. I needed to write up a quick and dirty function that would take the required inputs and give me the Bitly shortened URL for my web application.</p>
<p><span id="more-84"></span></p>
<p>First login to Bitly with the account you will be using and locate your API key in your settings.</p>
<p>Then let&#8217;s take a look at the <a title="Bitly API" href="http://code.google.com/p/bitly-api/wiki/ApiDocumentation" target="_blank">Bitly API definition</a>. Specifically the <a title="Bitly Shorten Documentation" href="http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/v3/shorten" target="_blank">shorten method</a>. Since I&#8217;m using this in a web application many of the variables that the shorten method are going to be hardcoded, as the only variable will be the long url. Here are all the possible options for the shorten method (via Bitly docs).</p>
<ul>
<li><strong> format </strong> (optional) indicates the requested response format. supported formats: json (default), xml, txt.</li>
<li><strong> longUrl </strong> is a long URL to be shortened (example: http://betaworks.com/).</li>
<li><strong> domain </strong> (optional) refers to a preferred domain; either bit.ly default or j.mp. This affects the output value of <tt>url</tt>.</li>
<li><strong>x_login</strong> (optional) is the end-user&#8217;s login when make requests on behalf of  another bitly user. This allows application developers to pass along an  end user&#8217;s bitly login.</li>
<li><strong>x_apiKey</strong> (optional) is  the end-user&#8217;s apiKey when making requests on behalf of another bitly  user. This allows application developers to pass along an end user&#8217;s  bitly apiKey.</li>
</ul>
<p>Whenever possible I use JSON as my return from APIs, choose whichever you are comfortable with, but the rest of this example will using JSON. If your application wants to pass the users login/apikey use the above values, otherwise you will just pass your own, minus the leading &#8216;x_&#8217;.</p>
<p>Let&#8217;s dive into the function.</p>
<pre class="brush: php; title: ; notranslate">
function create_bitly_url($url)
 {
 // Form the Bitly api call URL
 $bitly = 'http://api.bitly.com/v3/shorten?login=ENTER_YOUR_LOGIN_NAME_HERE&amp;apiKey=ENTER_YOUR_KEY_HERE' . '&amp;longUrl=' . urlencode($url) . '&amp;format=json';
</pre>
<p>This is the beginning of the function, and we build the Bitly API URL. Note that the long url you are passing needs to be URL Encoded. Pass the appropriate format you want to deal with as the return as well.</p>
<pre class="brush: php; title: ; notranslate">
 // Fetch Bitly URL
 $response = file_get_contents($bitly);
</pre>
<p>You can use whatever method you prefer to grab urls here as well.</p>
<pre class="brush: php; title: ; notranslate">
 $json = @json_decode($response,true);&lt;/pre&gt;
return $json['data']['url'];
 }
</pre>
<p>The first line will take the returned code from the last line and turn it into a usable php array. The second line will return the short url from the data returned. Let&#8217;s take a closer look at the array returned from the Bitly API.</p>
<pre class="brush: plain; title: ; notranslate">
Array
(
    [status_code] =&gt; 200
    [status_txt] =&gt; OK
    [data] =&gt; Array
        (
            [long_url] =&gt; http://gamegossip.com/
            ['url'] =&gt; http://bit.ly/lcabpe
            [hash] =&gt; lcabpe
            [global_hash] =&gt; ksYvP7
            [new_hash] =&gt; 0
        )
)
URL: 1</pre>
<p>(quotes added around url to get around some WordPress funny business)</p>
<p>The API documentation goes into details on the various possibilities for some of the data. I thought it would be nice to include what the array looks like that is returned to give some context to the line of code that is pulling out the short URL value. This was ran on a URL that was already in the Bitly system, as you can tell from the &#8216;New Hash&#8217; being set to &#8216;0&#8217;. Depending on what you were doing with your application, you may want to consider testing the results a bit more than we do in this function.</p>
<pre class="brush: php; title: ; notranslate">
 $short = create_bitly_url('http://gamegossip.com/');
</pre>
<p>This is the call to create the short URL.</p>
<pre class="brush: php; title: ; notranslate">
echo 'URL: '. $short;
</pre>
<p>Display the URL to confirm all is well.</p>
<p>There it is, like I said not much to this one. This will give you a quick and dirty way to add Bitly URL shortening to any project you may be working on. Enjoy!</p>
<p>This code and database structure are now available on <a href="https://github.com/jpwalsh1/bitly-php" title="Github" target="_blank">Github</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2011/06/php-bitly-wrapper/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>VMware Powershell Snippet &#8211; Check NTP Settings</title>
		<link>http://jimwalsh.name/2010/12/vmware-powershell-snippet-check-ntp-settings/</link>
		<comments>http://jimwalsh.name/2010/12/vmware-powershell-snippet-check-ntp-settings/#comments</comments>
		<pubDate>Wed, 01 Dec 2010 21:39:36 +0000</pubDate>
		<dc:creator><![CDATA[Jim Walsh]]></dc:creator>
				<category><![CDATA[VMware]]></category>
		<category><![CDATA[powershell]]></category>
		<category><![CDATA[vmware]]></category>

		<guid isPermaLink="false">http://jimwalsh.name/?p=53</guid>
		<description><![CDATA[The other day at work I had a colleague ask if there was a way to check a group of ESX servers NTP configuration through Powershell. After searching around and thinking for a bit I found the Get-VMHostNtpServer commandlet. This in a loop will allow us to go through an entire group of servers to [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>The other day at work I had a colleague ask if there was a way to check a group of ESX servers NTP configuration through Powershell. After searching around and thinking for a bit I found the Get-VMHostNtpServer commandlet. This in a loop will allow us to go through an entire group of servers to find out the hosts NTP information.</p>
<p><span id="more-53"></span></p>
<pre class="brush: plain; title: ; notranslate">$NTPHosts = Get-VMHost | Select-Object Name,@{Name=&quot;NTPServer&quot;;Expression={$_ |
Get-VMHostNtpServer}}, @{Name=&quot;NTPRunning&quot;;Expression={($_ |
Get-VMHostService |
Where-Object {$_.key -eq &quot;ntpd&quot;}).Running}} |
Sort-Object -Property &quot;NTPRunning&quot;, &quot;NTPServer&quot;</pre>
<p>Using that command will scan through all of the ESX hosts in a Virtual Center and gather up their NTP server list as well as if NTP is running or not on the host.</p>
<p>To export the result out to a file you can run the following command.</p>
<pre class="brush: plain; title: ; notranslate">$NTPHosts | Export-Csv c:\ntpinfo.csv -NoTypeInformation</pre>
]]></content:encoded>
			<wfw:commentRss>http://jimwalsh.name/2010/12/vmware-powershell-snippet-check-ntp-settings/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
