<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;C0EBQnYzeyp7ImA9WhBWFE4.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130</id><updated>2013-04-08T10:34:13.883-04:00</updated><category term="Personal" /><category term="Celebrations" /><category term="Wordpress" /><category term="LibreOffice" /><category term="Performance" /><category term="MVC" /><category term="Relationships" /><category term="Amazon" /><category term="RAID" /><category term="Review" /><category term="Volt" /><category term="Technorati" /><category term="Chromium OS" /><category term="Zend Framework" /><category term="How-To" /><category term="PhalconPHP" /><category term="NAS" /><category term="Programming" /><category term="Froyo" /><category term="Chromium" /><category term="Gentoo" /><category term="Picasa" /><category term="Communications" /><category term="TDD" /><category term="Hosting" /><category term="Backup" /><category term="Git" /><category term="Privacy" /><category term="SSL" /><category term="Series" /><category term="Storage" /><category term="Ruby on Rails" /><category term="Spam" /><category term="Android" /><category term="EC2" /><category term="Design Patterns" /><category term="Docs" /><category term="GMail" /><category term="Online Storage" /><category term="Independence Day" /><category term="Internet" /><category term="MySQL" /><category term="Test Driven Development" /><category term="Drivers" /><category term="Subversion" /><category term="Cloud Computing" /><category term="Nexus One" /><category term="Input" /><category term="Portage" /><category term="RSync" /><category term="Localization" /><category term="Design" /><category term="Google" /><category term="Memorial Day" /><category term="PHP" /><category term="NFS" /><category term="iPhone" /><category term="HA" /><category term="Upgrade" /><category term="Linux" /><category term="VPS" /><category term="PHPUnit" /><category term="Angular" /><category term="mod_rewrite" /><category term="Notebook" /><category term="Update" /><category term="Patterns" /><category term="Ubuntu" /><category term="Rant" /><category term="Bash" /><category term="Databases" /><category term="Information" /><category term="Buzz" /><category term="Google Apps" /><category term="Installation" /><category term="OS" /><title>niden.net</title><subtitle type="html">"Boldly goes where no coder has gone before... and other ramblings"</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.niden.net/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.niden.net/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>53</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/niden" /><feedburner:info uri="niden" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;C0YFQHw8fip7ImA9WhBWFE4.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-336843561236842112</id><published>2012-11-29T00:00:00.000-05:00</published><updated>2013-04-08T10:25:11.276-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-04-08T10:25:11.276-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Volt" /><category scheme="http://www.blogger.com/atom/ns#" term="MVC" /><category scheme="http://www.blogger.com/atom/ns#" term="Angular" /><category scheme="http://www.blogger.com/atom/ns#" term="PhalconPHP" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>Building a web app with PhalconPHP and AngularJS Update [How-To][PhalconPHP][AngularJS]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s1600/phalcon-green.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s200/phalcon-green.jpg" width="173" /&gt;&lt;/a&gt;&lt;/div&gt;
It's been a while since I last wrote a blog post, so I wanted to touch on the effort to upgrade the application that I &lt;a href="https://github.com/niden/phalcon-angular-harryhogfootball"&gt;wrote&lt;/a&gt; for &lt;a href="http://www.harryhogfootball.com/"&gt;Harry Hog Football&lt;/a&gt; using &lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt; and &lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
If you haven't read it, the first two blog posts were &lt;a href="http://www.niden.net/2012/07/building-web-app-with-phalconphp-and.html"&gt;here&lt;/a&gt; and &lt;a href="http://www.niden.net/2012/07/building-web-app-with-phalconphp-and_12.html"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The application was written using the 0.4.5 version of&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp; Since then there have been significant changes to the framework, such as the introduction of a DI container, injectable objects and lately interfaces (in 0.7.0, to be released in a couple of days).


&lt;br /&gt;
&lt;br /&gt;
There are a couple of things that I as a developer would like to see in&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;, which I am pretty sure will appear later on, since let's face it the framework is still very young (not even 1.0 version yet). Despite its "&lt;i&gt;youth&lt;/i&gt;" it is a robust framework with excellent support, features and a growing community. One of these features is behaviors which I had to implement myself, and this was something new that came with this upgrade.&lt;br /&gt;
&lt;br /&gt;
Recently a new &lt;a href="https://github.com/phalcon/incubator"&gt;repo&lt;/a&gt; has been created on Github called the incubator, where developers can share implementations of common tasks, that act as drop ins to the framework and extend it. These implementations are all written in PHP so everyone can just download them and use them. The more submissions come in, the more the framework will grow and eventually these submissions will become part of the framework itself.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Converting the 0.4.x application to 0.5.x&lt;/h4&gt;
The task of converting everything from 0.4 to 0.5 was a bit challenging. The reason behind it was the DI container and how best to use it to suit the needs of the current application. Now these challenges would not even be an issue if one started writing their application from scratch, but since I had everything in place, I ventured into upgrading vs. rewriting. Note that this kind of upgrade will most likely never happen again, since the framework has been changed accordingly so that future upgrades will not require developers to rewrite their code (like I did now). From 0.5.x onward the framework design has been kind of "&lt;i&gt;frozen&lt;/i&gt;".&lt;br /&gt;
&lt;br /&gt;
I decided to create a new library that will help me with my tasks. I therefore created a custom bootstrap class, that would instantiate everything I wanted in my code. A short snippet of the class is below (the full code of course is in my Github &lt;a href="https://github.com/niden/phalcon-angular-harryhogfootball"&gt;repo&lt;/a&gt; which you are more than welcome to download and modify to suit your needs)&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;namespace NDN;

use \Phalcon\Config\Adapter\Ini as PhConfig;
use \Phalcon\Loader as PhLoader;
....
use \Phalcon\Exception as PhException;

class Bootstrap
{
    private $_di;

    /**
     * Constructor
     * 
     * @param $di
     */
    public function __construct($di)
    {
        $this-&amp;gt;_di = $di;
    }

    /**
     * Runs the application performing all initializations
     * 
     * @param $options
     *
     * @return mixed
     */
    public function run($options)
    {
        $loaders = array(
            'config',
            'loader',
            'environment',
            'timezone',
            'debug',
            'flash',
            'url',
            'dispatcher',
            'view',
            'logger',
            'database',
            'session',
            'cache',
            'behaviors',
        );


        try {

            foreach ($loaders as $service)
            {
                $function = 'init' . ucfirst($service);

                $this-&amp;gt;$function($options);
            }

            $application = new PhApplication();
            $application-&amp;gt;setDI($this-&amp;gt;_di);

            return $application-&amp;gt;handle()-&amp;gt;getContent();

        } catch (PhException $e) {
            echo $e-&amp;gt;getMessage();
        } catch (\PDOException $e) {
            echo $e-&amp;gt;getMessage();
        }
    }

    // Protected functions

    /**
     * Initializes the config. Reads it from its location and
     * stores it in the Di container for easier access
     *
     * @param array $options
     */
    protected function initConfig($options = array())
    {
        $configFile = ROOT_PATH . '/app/var/config/config.ini';

        // Create the new object
        $config = new PhConfig($configFile);

        // Store it in the Di container
        $this-&amp;gt;_di-&amp;gt;set('config', $config);
    }

    /**
     * Initializes the loader
     *
     * @param array $options
     */
    protected function initLoader($options = array())
    {
        $config = $this-&amp;gt;_di-&amp;gt;get('config');

        // Creates the autoloader
        $loader = new PhLoader();

        $loader-&amp;gt;registerDirs(
            array(
                ROOT_PATH . $config-&amp;gt;app-&amp;gt;path-&amp;gt;controllers,
                ROOT_PATH . $config-&amp;gt;app-&amp;gt;path-&amp;gt;models,
                ROOT_PATH . $config-&amp;gt;app-&amp;gt;path-&amp;gt;library,
            )
        );

        // Register the namespace
        $loader-&amp;gt;registerNamespaces(
            array("NDN" =&amp;gt; $config-&amp;gt;app-&amp;gt;path-&amp;gt;library)
        );

        $loader-&amp;gt;register();
    }
    
    ....

    /**
     * Initializes the view and Volt
     *
     * @param array $options
     */
    protected function initView($options = array())
    {
        $config = $this-&amp;gt;_di-&amp;gt;get('config');
        $di     = $this-&amp;gt;_di;

        $this-&amp;gt;_di-&amp;gt;set(
            'volt',
            function($view, $di) use($config)
            {
                $volt = new PhVolt($view, $di);
                $volt-&amp;gt;setOptions(
                    array(
                        'compiledPath'      =&amp;gt; ROOT_PATH . $config-&amp;gt;app-&amp;gt;volt-&amp;gt;path,
                        'compiledExtension' =&amp;gt; $config-&amp;gt;app-&amp;gt;volt-&amp;gt;extension,
                        'compiledSeparator' =&amp;gt; $config-&amp;gt;app-&amp;gt;volt-&amp;gt;separator,
                        'stat'              =&amp;gt; (bool) $config-&amp;gt;app-&amp;gt;volt-&amp;gt;stat,
                    )
                );
                return $volt;
            }
        );
    }
    ....

    /**
     * Initializes the model behaviors
     *
     * @param array $options
     */
    protected function initBehaviors($options = array())
    {
        $session = $this-&amp;gt;_di-&amp;gt;getShared('session');

        // Timestamp
        $this-&amp;gt;_di-&amp;gt;set(
            'Timestamp',
            function() use ($session)
            {
                $timestamp = new Models\Behaviors\Timestamp($session);
                return $timestamp;
            }
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
I chose to show a few sections of this bootstrap which I will explain shortly. What this bootstrap class does is it initializes my whole environment and keeps my &lt;span style="font-family: Courier New, Courier, monospace;"&gt;index.php&lt;/span&gt; file small.
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;error_reporting(E_ALL);

try {

    if (!defined('ROOT_PATH')) {
        define('ROOT_PATH', dirname(dirname(__FILE__)));
    }

    // Using require once because I want to get the specific
    // bootloader class here. The loader will be initialized
    // in my bootstrap class
    require_once ROOT_PATH . '/app/library/NDN/Bootstrap.php';
    require_once ROOT_PATH . '/app/library/NDN/Error.php';

    // Instantiate the DI container
    $di  = new \Phalcon\DI\FactoryDefault();

    // Instantiate the boostrap class and inject the DI container 
    // in it so that services can be registered
    $app = new \NDN\Bootstrap($di);
   
    // Here we go!
    echo $app-&amp;gt;run(array());

} catch (\Phalcon\Exception $e) {
    echo $e-&amp;gt;getMessage();
}
&lt;/code&gt;&lt;/pre&gt;
As you can see the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;index.php&lt;/span&gt; is very small in terms of code.&lt;br /&gt;
&lt;br /&gt;
Let's have a look at a couple of the functions that are in the bootstrap.
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;
    /**
     * Initializes the config. Reads it from its location and
     * stores it in the Di container for easier access
     *
     * @param array $options
     */
    protected function initConfig($options = array())
    {
        $configFile = ROOT_PATH . '/app/var/config/config.ini';

        // Create the new object
        $config = new PhConfig($configFile);

        // Store it in the Di container
        $this-&amp;gt;_di-&amp;gt;set('config', $config);
    }
&lt;/code&gt;&lt;/pre&gt;
Pretty straight forward. The config INI file is read from its location and stored in the DI container. I need to do this first, since a lot of the parameters of the application are controlled from that file.
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;
    /**
     * Initializes the loader
     *
     * @param array $options
     */
    protected function initLoader($options = array())
    {
        $config = $this-&amp;gt;_di-&amp;gt;get('config');

        // Creates the autoloader
        $loader = new PhLoader();

        $loader-&amp;gt;registerDirs(
            array(
                ROOT_PATH . $config-&amp;gt;app-&amp;gt;path-&amp;gt;controllers,
                ROOT_PATH . $config-&amp;gt;app-&amp;gt;path-&amp;gt;models,
                ROOT_PATH . $config-&amp;gt;app-&amp;gt;path-&amp;gt;library,
            )
        );

        // Register the namespace
        $loader-&amp;gt;registerNamespaces(
            array("NDN" =&amp;gt; $config-&amp;gt;app-&amp;gt;path-&amp;gt;library)
        );

        $loader-&amp;gt;register();
    }
&lt;/code&gt;&lt;/pre&gt;
The loader is what does all the discovery of classes for me. As you can see I store a lot of the paths in the config INI file, and I register my custom namespace NDN.
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;
    /**
     * Initializes the view and Volt
     *
     * @param array $options
     */
    protected function initView($options = array())
    {
        $config = $this-&amp;gt;_di-&amp;gt;get('config');
        $di     = $this-&amp;gt;_di;

        $this-&amp;gt;_di-&amp;gt;set(
            'volt',
            function($view, $di) use($config)
            {
                $volt = new PhVolt($view, $di);
                $volt-&amp;gt;setOptions(
                    array(
                        'compiledPath'      =&amp;gt; ROOT_PATH . $config-&amp;gt;app-&amp;gt;volt-&amp;gt;path,
                        'compiledExtension' =&amp;gt; $config-&amp;gt;app-&amp;gt;volt-&amp;gt;extension,
                        'compiledSeparator' =&amp;gt; $config-&amp;gt;app-&amp;gt;volt-&amp;gt;separator,
                        'stat'              =&amp;gt; (bool) $config-&amp;gt;app-&amp;gt;volt-&amp;gt;stat,
                    )
                );
                return $volt;
            }
        );
    }
&lt;/code&gt;&lt;/pre&gt;
This is an interesting one. Registering the view and Volt. &lt;a href="http://docs.phalconphp.com/en/latest/reference/volt.html"&gt;Volt&lt;/a&gt; is the template engine that comes with &lt;a href="http://phalconphp.com/"&gt;Phalcon&lt;/a&gt;. It is inspired by &lt;a href="http://twig.sensiolabs.org/"&gt;Twig&lt;/a&gt; and written in C, thus offering maximum performance. I set the compiled path, extension and separator for the template files and also I have a variable (set in the config of course) to allow the application to always create template files or not. In a production environment that variable (stat) will be set to false since templates do not change.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;    /**
     * Initializes the model behaviors
     *
     * @param array $options
     */
    protected function initBehaviors($options = array())
    {
        $session = $this-&amp;gt;_di-&amp;gt;getShared('session');

        // Timestamp
        $this-&amp;gt;_di-&amp;gt;set(
            'Timestamp',
            function() use ($session)
            {
                $timestamp = new Models\Behaviors\Timestamp($session);
                return $timestamp;
            }
        );
    }
&lt;/code&gt;&lt;/pre&gt;
The above is my implementation of behaviors. Of course it is far from perfect but it works the way I want to. A better implementation of this has been written by &lt;a href="https://github.com/theDisco"&gt;Wojtek Gancarczyk&lt;/a&gt; and is available in the &lt;a href="https://github.com/phalcon/incubator"&gt;incubator&lt;/a&gt;. All I do here is go through the behaviors I have (Timestamp only for now) and register them in the DI container so that I can reuse them later on with any model that needs them.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Models&lt;/h4&gt;
Every model I have that interacts with my database tables extends the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;NDN\Mode&lt;/span&gt;l. 

&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class Model extends \Phalcon\Mvc\Model
{
    protected $behaviors = array();

    /**
     * Adds a behavior in the model
     *
     * @param $behavior
     */
    public function addBehavior($behavior)
    {
        $this-&amp;gt;behaviors[$behavior] = true;
    }

    public function beforeSave()
    {
        $di   = Di::getDefault();

        foreach ($this-&amp;gt;behaviors as $behavior =&amp;gt; $active)
        {
            if ($active &amp;amp;&amp;amp; $di-&amp;gt;has($behavior))
            {
                $di-&amp;gt;get($behavior)-&amp;gt;beforeSave($this);
            }
        }
    }

    /**
     * @param array $parameters
     *
     * @static
     * @return Phalcon_Model_Resultset Model[]
     */
    static public function find($parameters = array())
    {
        return parent::find($parameters);
    }

    /**
     * @param array $parameters
     *
     * @static
     * @return  Phalcon_Model_Base   Models
     */
    static public function findFirst($parameters = array())
    {
        return parent::findFirst($parameters);
    }
}
&lt;/code&gt;&lt;/pre&gt;
The class itself is pretty simple, offering &lt;span style="font-family: Courier New, Courier, monospace;"&gt;find&lt;/span&gt; and &lt;span style="font-family: Courier New, Courier, monospace;"&gt;findFirst&lt;/span&gt; to the class that extends this. The interesting thing is that it also registers behaviors and calls the relevant validator function. So for instance the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;beforeSave&lt;/span&gt; validator checks the registered behaviors (&lt;span style="font-family: Courier New, Courier, monospace;"&gt;$behaviors&lt;/span&gt; array), checks if they are active, checks if they exist in the DI container and gets them from there and then calls the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;beforeSave&lt;/span&gt; in the behavior class.

The behavior class is equally simple:
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class Timestamp
{
    protected $session;

    public function __construct($session)
    {
        $this-&amp;gt;session= $session;
    }

    /**
     * beforeSave hook - called prior to any Save (insert/update)
     */
    public function beforeSave($record)
    {
        $auth     = $this-&amp;gt;session-&amp;gt;get('auth');
        $userId   = (isset($auth['id'])) ? (int) $auth['id'] : 0;
        $datetime = date('Y-m-d H:i:s');
        if (empty($record-&amp;gt;created_at_user_id)) {
            $record-&amp;gt;created_at         = $datetime;
            $record-&amp;gt;created_at_user_id = $userId;
        }
        $record-&amp;gt;last_update         = $datetime;
        $record-&amp;gt;last_update_user_id = $userId;
    }
}
&lt;/code&gt;&lt;/pre&gt;
So effectively every time I call the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;save()&lt;/span&gt; function on a model, this piece of code will be executed, populating my fields with the date time and the user that created the record and/or updated it.

In order to get this functionality to work, all I have to do in my model is to register the behavior like so:
&lt;br /&gt;
&lt;pre style="text-align: left;"&gt;&lt;code&gt;class Episodes extends \NDN\Model
{
    /**
     * Initializes the class and sets any relationships with other models
     */
    public function initialize()
    {
        $this-&amp;gt;addBehavior('Timestamp');
        $this-&amp;gt;hasMany('id', 'Awards', 'episode_id');
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style="text-align: left;"&gt;
Controllers&lt;/h4&gt;
Very little has changed in the controller logic, so that was the easiest part of the upgrade. Of course I tweaked a few things but the code works as is. I still extended my custom &lt;span style="font-family: Courier New, Courier, monospace;"&gt;NDN\Controller&lt;/span&gt; class which takes care of my breadcrumbs (&lt;span style="font-family: Courier New, Courier, monospace;"&gt;NDN\Breadcrumbs&lt;/span&gt;) as well as the construction of the top menu. The biggest difference with the previous version is that I stopped using&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;to populate the menu (so I am no longer sending a JSON array in the view) and used Volt instead. It was a matter of preference and nothing more.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Views&amp;nbsp;&lt;/h4&gt;
Quite a bit of work had to be done in the views to switch everything to use Volt. Of course every view extension had to be changed to .volt but that was not the only change. I split the layout to use partials so that the header, navigation and footer are different sections (organizing things a bit better) and kept the master layout &lt;span style="font-family: Courier New, Courier, monospace;"&gt;index.volt&lt;/span&gt;. 

I started using the built in Volt functions to generate content as well as tags and it was a nice surprise to see that everything was easy to use and it worked!
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html ng-app='HHF'&amp;gt;
    {{ partial('partials/header') }}
    &amp;lt;body&amp;gt;


    &amp;lt;div id="spinner" style="display: none;"&amp;gt;
        {{ image('img/ajax-loader.gif') }} Loading ...
    &amp;lt;/div&amp;gt;

    {{ partial('partials/navbar') }}

    &amp;lt;div class='container-fluid'&amp;gt;
        &amp;lt;div class='row-fluid'&amp;gt;
            &amp;lt;ul class='breadcrumb'&amp;gt;
                &amp;lt;li&amp;gt;
                    {% for bc in breadcrumbs %}
                    {% if (bc['active']) %}
                    {{ bc['text'] }}
                    {% else %}
                    &amp;lt;a href='{{ bc['link'] }}'&amp;gt;{{ bc['text'] }}&amp;lt;/a&amp;gt; 
                    &amp;lt;span class='divider'&amp;gt;/&amp;lt;/span&amp;gt;
                    {% endif %}
                    {% endfor %}
                &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;?php echo $this-&amp;gt;flash-&amp;gt;output() ?&amp;gt;

        &amp;lt;div class="row-fluid"&amp;gt;
            &amp;lt;?php echo $this-&amp;gt;getContent() ?&amp;gt;
        &amp;lt;/div&amp;gt; &amp;lt;!-- row --&amp;gt;

        {{ partial('partials/footer') }}
    &amp;lt;/div&amp;gt;

    {{ javascript_include(config.app.js.jquery, config.app.js.local) }}
    {{ javascript_include(config.app.js.jquery_ui, config.app.js.local) }}
    {{ javascript_include(config.app.js.bootstrap, config.app.js.local) }}
    {{ javascript_include(config.app.js.angular, config.app.js.local) }}
    {{ javascript_include(config.app.js.angular_resource, config.app.js.local) }}
    {{ javascript_include(config.app.js.angular_ui, config.app.js.local) }}
    {{ javascript_include('js/utils.js') }}

    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
The above is the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;index.volt&lt;/span&gt;. As you can see I call on the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;partials/header.volt&lt;/span&gt;, then the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;partials/navbar.volt&lt;/span&gt; (where the menu is generated) and then I construct the breadcrumbs (note the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;{% for bc in breadcrumbs %}&lt;/span&gt; block). After that the flash messenger comes into play, the main content displayed, the footer and finally the javascript includes that I need.

I am still using&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;to make the necessary AJAX calls so that the relevant controller to retrieve the data but also to display this data on screen (which is cached to avoid&amp;nbsp;unnecessary&amp;nbsp;database hits).&lt;br /&gt;
&lt;br /&gt;
The Episodes view became&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;{{ content() }}

&amp;lt;div&amp;gt;
    &amp;lt;ul class='nav nav-tabs'&amp;gt;
        &amp;lt;li class='pull-right'&amp;gt;
            {{ addButton }}
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div ng-controller='MainCtrl'&amp;gt;
&amp;lt;table class='table table-bordered table-striped ng-cloak' ng-cloak&amp;gt;
    &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;&amp;lt;a href='' ng-click="predicate='number'; reverse=!reverse"&amp;gt;#&amp;lt;/a&amp;gt;&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;&amp;lt;a href='' ng-click="predicate='air_date'; reverse=!reverse"&amp;gt;Date&amp;lt;/a&amp;gt;&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;&amp;lt;a href='' ng-click="predicate='outcome'; reverse=!reverse"&amp;gt;W/L&amp;lt;/a&amp;gt;&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;&amp;lt;a href='' ng-click="predicate='summary'; reverse=!reverse"&amp;gt;Summary&amp;lt;/a&amp;gt;&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr ng-repeat="episode in data.results | orderBy:predicate:reverse"&amp;gt;
            &amp;lt;td&amp;gt;[[episode.number]]&amp;lt;/td&amp;gt;
            &amp;lt;td width='7%'&amp;gt;[[episode.air_date]]&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;[[episode.outcome]]&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;[[episode.summary]]&amp;lt;/td&amp;gt;
            {% if (addButton) %}
            &amp;lt;td width='1%'&amp;gt;&amp;lt;a href='/episodes/edit/[[episode.id]]'&amp;gt;&amp;lt;i class='icon-pencil'&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
            &amp;lt;td width='1%'&amp;gt;&amp;lt;a href='/episodes/delete/[[episode.id]]'&amp;gt;&amp;lt;i class='icon-remove'&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
            {% endif %}
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
The beauty of&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;! I only have to pass a JSON array with my results. &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ng-repeat&lt;/span&gt; with the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;orderBy&lt;/span&gt; filter allows me to present the data to the user and offer sorting capabilities per column. This is all done at the browser level &lt;b&gt;without&lt;/b&gt; any database hits! Pretty awesome feature.&lt;br /&gt;
&lt;br /&gt;
For those that have used&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;in the past, you will note that I had to change the interpolate provider (i.e. the characters that wrap a string or a piece of code that&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;understands). Usually these characters are the&amp;nbsp;curly brackets&amp;nbsp;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;{{ }}&lt;/span&gt;&amp;nbsp;but I changed them to &lt;span style="font-family: Courier New, Courier, monospace;"&gt;[[ ]]&lt;/span&gt; to avoid collisions with Volt.

This was done with a couple of lines of code in my definition of my&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;model:&lt;br /&gt;
&lt;pre style="text-align: left;"&gt;&lt;code&gt;
var ngModule = angular.module('HHF', ['ngResource', 'ui'])
    .config(function($interpolateProvider) {
        $interpolateProvider.startSymbol('[[');
        $interpolateProvider.endSymbol(']]');
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style="text-align: left;"&gt;
Conclusion&lt;/h4&gt;
I spent at most a day working on this mostly because I wanted to try various things and see how it works. The actual time to convert the application (because let's face it, it is a small application) was a couple of hours inclusive of the time it took me to rename certain fields, restructure the folder structure, compile the new extension on my server and upload the data upstream.&lt;br /&gt;
&lt;br /&gt;
I am very satisfied with both&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;which helps tremendously in my presentation layer as well as&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp; Phalcon's new design makes implementation a breeze, while Volt offers a lot of flexibility on the view layer.&lt;br /&gt;
&lt;br /&gt;
As written before, you are more than welcome to download the &lt;a href="https://github.com/niden/phalcon-angular-harryhogfootball"&gt;source code&lt;/a&gt; of this application here and use it for your own needs. Some resources are:&lt;br /&gt;
&lt;br /&gt;
AngularJS main site: &lt;a href="http://angularjs.org/"&gt;http://angularjs.org&lt;/a&gt;&lt;br /&gt;
AngularJS documentation:&amp;nbsp;&lt;a href="http://docs.angularjs.org/api"&gt;http://docs.angularjs.org/api&lt;/a&gt;&lt;br /&gt;
AngularJS group:&amp;nbsp;&lt;a href="https://groups.google.com/forum/#!forum/angular"&gt;https://groups.google.com/forum/#!forum/angular&lt;/a&gt;&lt;br /&gt;
AngularJS Github: &lt;a href="https://github.com/angular"&gt;https://github.com/angular&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Phalcon PHP main site: &lt;a href="http://phalconphp.com/"&gt;http://phalconphp.com&lt;/a&gt;&lt;br /&gt;
Phalcon PHP documentation:&amp;nbsp;&lt;a href="http://phalconphp.com/documentation"&gt;http://phalconphp.com/documentation&lt;/a&gt;&lt;br /&gt;
Phalcon PHP group:&amp;nbsp;&lt;a href="https://groups.google.com/forum/#!forum/phalcon"&gt;https://groups.google.com/forum/#!forum/phalcon&lt;/a&gt;&lt;br /&gt;
Phalcon PHP Github: &lt;a href="https://github.com/phalcon"&gt;https://github.com/phalcon&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;
&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/Qc1vsaJt_hw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/336843561236842112/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/11/building-web-app-with-phalconphp-and.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/336843561236842112?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/336843561236842112?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/Qc1vsaJt_hw/building-web-app-with-phalconphp-and.html" title="Building a web app with PhalconPHP and AngularJS Update [How-To][PhalconPHP][AngularJS]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s72-c/phalcon-green.jpg" height="72" width="72" /><thr:total>2</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/11/building-web-app-with-phalconphp-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0MBRXg5cSp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-6222430013841440511</id><published>2012-09-02T00:00:00.000-04:00</published><updated>2012-09-27T14:10:54.629-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:10:54.629-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Angular" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>AngularJS - Simplicity in your browser [AngularJS]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-yMjMbxa_nqQ/T_x5D1PfL6I/AAAAAAAB4b0/JAPYBW-9IjQ/s1600/AngularJS-large.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="51" src="http://1.bp.blogspot.com/-yMjMbxa_nqQ/T_x5D1PfL6I/AAAAAAAB4b0/JAPYBW-9IjQ/s200/AngularJS-large.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
Recently I was contacted by an acquaintance through my Google+ circles, who needed some help with a project of hers.&lt;br /&gt;
&lt;br /&gt;
Her task was to redesign a church website. Pretty simple stuff, CSS, HTML and content.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Scope&lt;/h4&gt;
The particular church videotapes all the sermons and posts them on their channel in &lt;a href="http://www.livestream.com/"&gt;LiveStream&lt;/a&gt; for their followers to watch. One of the requirements was to redo the video archives page and to offer a link where followers can download the audio of each sermon for listening.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Design (kinda)&lt;/h4&gt;
After the initial contact, I decided to get rid of all the bloated &lt;a href="http://www.jquery.com/"&gt;jQuery&lt;/a&gt; code that was there to control the video player and use&amp;nbsp;&lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;to control the generation of content. There were two key facts that influenced my decision:&lt;br /&gt;
&lt;ol style="text-align: left;"&gt;
&lt;li&gt;the use of &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ng-repeat&lt;/span&gt; to generate the table that will list all the available sermons and&amp;nbsp;&lt;/li&gt;
&lt;li&gt;the variable binding that &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt; offers to play the video in the available player.&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
I also decided to switch the player to a new updated one that &lt;a href="http://www.livestream.com/"&gt;LiveStream&lt;/a&gt; offered, which features a slider to jump through the video, volume control and more.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Previous code&lt;/h4&gt;
The previous code for that page was around 300 lines. The file had some CSS in it, quite a few lines of HTML but was heavy on javascript. There were a lot of&amp;nbsp;&lt;a href="http://www.jquery.com/"&gt;jQuery&lt;/a&gt;&amp;nbsp;functions which controlled the retrieval of the available videos per playlist. Each playlist would be effectively a collection of videos for a particular year.&amp;nbsp;&lt;a href="http://www.jquery.com/"&gt;jQuery&lt;/a&gt;&amp;nbsp;will observe clicks on specific links and make an AJAX call to the&amp;nbsp;&lt;a href="http://www.livestream.com/"&gt;LiveStream&lt;/a&gt;&amp;nbsp;API to retrieve the list of available data in JSON format, and output the formatted results (as a table) on screen. It was something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;head
title
link - css
style (inline)
....
end inline style
script jQuery
script jQueryUI
script jquery.timer
link jquery-ui CSS
link slider CSS
style (inline)
....
end inline style
script jQuery
$(document).ready ... // Document ready
.....
$("#div_col dd").click // Bind click to a year
.....
getPlaylists(year) // Not sure why this was ever here, not used
.....
getPlaylistClips(playlistID) // Gets the clips of the playlist
.....
playClip(clipID) // Plays the clip in the player
.....
end jQuery script
script Video Player
.....
end head
body
navigation
main container
list of years
instructions
container to show video player
container to show video list
end body
end html
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style="text-align: left;"&gt;
Enter AngularJS&lt;/h4&gt;
I checked the latest video player from&amp;nbsp;&lt;a href="http://www.livestream.com/"&gt;LiveStream&lt;/a&gt;. The code was much cleaner and all I had to do is bind one variable, the GUID of the video, in the relevant call so that the video can be played. I also bound another variable (the video title) above the video so as to offer more information to the user.&lt;br /&gt;
&lt;br /&gt;
With a bit of Bootstrap CSS, I created two tabs and listed the two years 2012, 2011. A function was created in my&amp;nbsp;&lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;module to accept the year and make the relevant call to the&amp;nbsp;&lt;a href="http://www.livestream.com/"&gt;LiveStream&lt;/a&gt;&amp;nbsp;API to receive the data a a JSON object.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-family: Courier New, Courier, monospace;"&gt;ng-repeat&lt;/span&gt; (with &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ng-cloak&lt;/span&gt;) was used to "print" the data on screen and the application was ready.&lt;br /&gt;
&lt;br /&gt;
I removed all the cruft and created one small file that is loaded and offers the functionality that we need. It is 50 lines of code (just the javascript part. The code is below with added comments for the reader to follow:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;// Create the module and inject the Resource object
var ngModule = angular.module("CHF", ['ngResource']);

// The main controller that needs the scope and resource
ngModule.controller("MainCtrl", function ($scope, $resource) {

    // Calculates the current year
    //  ensures we always get the last year on first load
    $scope.currentYear = function () {
        var currentDate = new Date();
        return currentDate.getFullYear();
    };
    
    // This is the playlist array. This is obtained by 
    //  LiveStream and it changes once every year. 
    //  Hardly an effort by the administrator
    $scope.playlists = [
        {"year":"2012", "guid":"63426-xxx-xxx-xxx"},
        {"year":"2011", "guid":"84f84-xxx-xxx-xxx"}
    ];

    // This couldn't be simpler. It merely sets some variables 
    //  in the scope. By doing so, the binding in the relevant
    //  variables will allow the video to play and the title 
    //  to update.
    $scope.playVideo = function (element) {
        $scope.currentVideo  = element.guid;
        $scope.currentTitle  = element.title;
    };

    // This is the core. It makes the AJAX request to the 
    // LiveStream API so that it can get the JSON data back
    $scope.makeRequest = function (year) {
        
        // Calculating the current year and the year selected.
        // Their difference offers an offset which effectively 
        // is the offset of the array stored in $scope.playlists
        var thisYear    = $scope.currentYear();
        var diff        = thisYear - year;

        var objData = $scope.playlists[diff];

        // Just in case something was passed that is not valid
        if (objData.guid)
        {
            var reqData = $resource(
                "http://livestream_url/2.0/:action",
                {
                    action:'listclips.json', 
                    id:objData.guid,
                    query: {isArray: true},
                    maxresults:"500",
                    callback:'JSON_CALLBACK'
                },
                {get:{method:'JSONP'}}
            );

            // Set the year and get the data
            $scope.year    = year;
            $scope.listData = reqData.get();
       }
    };

    // This is the first load - load the current year
    $scope.makeRequest($scope.currentYear());

});
&lt;/code&gt;
&lt;/pre&gt;
Now moving into the HTML side of things:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id="playerContainer" style='text-align:center;'&amp;gt;
    &amp;lt;p ng-cloak&amp;gt;
&lt;b&gt;        {{currentTitle}}&lt;/b&gt;
    &amp;lt;/p&amp;gt;
    &amp;lt;iframe 
        width="560" 
        height="340" 
        src="http://ls_url?clip=&lt;b&gt;{{currentVideo}}&lt;/b&gt;&amp;amp;amp;params" 
        style="border:0;outline:0" 
        frameborder="0" 
        scrolling="no"&amp;gt;
    &amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;br /&amp;gt;
&amp;lt;div&amp;gt;
    &amp;lt;ul class="nav nav-tabs"&amp;gt;
        &amp;lt;li &lt;b&gt;ng-repeat="playlist in playlists" &lt;/b&gt;
               &lt;b&gt;ng-class="{true:'active',false:''}[year == playlist.year]"&lt;/b&gt;&amp;gt;
            &amp;lt;a &lt;b&gt;ng-click="makeRequest(playlist.year)"&lt;/b&gt;&amp;gt;&lt;b&gt;{{playlist.year}}&lt;/b&gt;&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;table class="table table-bordered" style="width: 100%;"&amp;gt;
        &amp;lt;thead&amp;gt;
            &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;Date/Title&amp;lt;/th&amp;gt;
                &amp;lt;th&amp;gt;Audio&amp;lt;/th&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
            &amp;lt;tr &lt;b&gt;ng-cloak ng-repeat="video in listData.channel.item"&lt;/b&gt;&amp;gt;
                &amp;lt;td &lt;b&gt;ng-click="playVideo(video)"&lt;/b&gt;&amp;gt;&lt;b&gt;{{video.title}}&lt;/b&gt;&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;span &lt;b&gt;ng-show="video.description"&lt;/b&gt;&amp;gt;
                        &amp;lt;a href="&lt;b&gt;{{video.description}}&lt;/b&gt;" title="Download Audio"&amp;gt;
                            &amp;lt;i class="icon-download-alt"&amp;gt;&amp;lt;/i&amp;gt;
                        &amp;lt;/a&amp;gt;
                    &amp;lt;/span&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
That is all the HTML I had to change. The full HTML file is 100 lines and 50 for the &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt; related javascript, I can safely say that I had a 50% reduction in code offering the same functionality - and if I might say so, it is much much cleaner.&lt;br /&gt;
&lt;br /&gt;
The final page looks something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-cb69RTLX6OY/UEPm0zxGkeI/AAAAAAAC3dM/f-v_wpBO9fY/s1600/sermon.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-cb69RTLX6OY/UEPm0zxGkeI/AAAAAAAC3dM/f-v_wpBO9fY/s320/sermon.png" width="311" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
A couple of pointers:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id="playerContainer" style='text-align:center;'&amp;gt;
    &amp;lt;p ng-cloak&amp;gt;
    &lt;b&gt;{{currentTitle}}&lt;/b&gt;
    &amp;lt;/p&amp;gt;
    &amp;lt;iframe 
        width="560" 
        height="340" 
        src="http://ls_url?clip=&lt;b&gt;{{currentVideo}}&lt;/b&gt;&amp;amp;amp;params" 
        style="border:0;outline:0" 
        frameborder="0" 
        scrolling="no"&amp;gt;
    &amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
This block displays the video player and due to the variable binding that &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt; offers, the minute those variables change, the video is ready to be played.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;ul class="nav nav-tabs"&amp;gt;
        &amp;lt;li &lt;b&gt;ng-repeat="playlist in playlists" &lt;/b&gt;
               &lt;b&gt;ng-class="{true:'active',false:''}[year == playlist.year]"&lt;/b&gt;&amp;gt;
            &amp;lt;a &lt;b&gt;ng-click="makeRequest(playlist.year)"&lt;/b&gt;&amp;gt;&lt;b&gt;{{playlist.year}}&lt;/b&gt;&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
This block shows the tabs depicting each playlist. In our case these are years. ng-repeat does all the hard work, printing the data that is defined in our JS file. The ng-class is there to change the class of the tab to "active" when the tab is clicked/selected. The ng-click initiates a request through makeRequest, a function defined in our javascript file (see above).&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;        &amp;lt;tbody&amp;gt;
            &amp;lt;tr &lt;b&gt;ng-cloak ng-repeat="video in listData.channel.item"&lt;/b&gt;&amp;gt;
                &amp;lt;td &lt;b&gt;ng-click="playVideo(video)"&lt;/b&gt;&amp;gt;&lt;b&gt;{{video.title}}&lt;/b&gt;&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;span &lt;b&gt;ng-show="video.description"&lt;/b&gt;&amp;gt;
                        &amp;lt;a href="&lt;b&gt;{{video.description}}&lt;/b&gt;" 
                           title="Download Audio"&amp;gt;
                            &amp;lt;i class="icon-download-alt"&amp;gt;&amp;lt;/i&amp;gt;
                        &amp;lt;/a&amp;gt;
                    &amp;lt;/span&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;script&gt;format()&lt;/script&gt;

&lt;br /&gt;
&lt;br /&gt;
Finally the data is displayed on screen. ng-cloak makes sure that the content is displayed only when the data is there (otherwise browsers might show something like {{video.description}} which is not nice from a UI perspective). ng-repeat loops through the data and "prints" the table.&lt;br /&gt;
&lt;br /&gt;
The description of the video is used as a storage for the URL that will point to the MP3 audio file so as the users can download it. Therefore I use ng-show to show the link, if it exists.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Conclusion&lt;/h4&gt;
This whole project was no more than 30 minutes, which included the time I had to research and experiment a bit with the &lt;a href="http://www.livestream.com/"&gt;LiveStream&lt;/a&gt; API. This is effectively a flexible design, with much much cleaner code (and a lot less of it). When the admin needs to add a new playlist (aka year), all they have to do is open the JS file and type a new element in the $scope.playlists array. The application will take care of the rest automatically.&lt;br /&gt;
&lt;br /&gt;
I cannot think of doing this with less lines of code than this.&lt;br /&gt;
&lt;br /&gt;
If you haven't heard of &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt; or used it, I would highly encourage you to give it a shot. Great project, awesome support and a very very responsive, helpful and polite community.&lt;br /&gt;
&lt;br /&gt;
Main Site: &lt;a href="http://www.angularjs.org/"&gt;http://www.angularjs.org&lt;/a&gt;&lt;br /&gt;
Google Group: &lt;a href="https://groups.google.com/forum/?fromgroups=#!forum/angular"&gt;https://groups.google.com/forum/?fromgroups=#!forum/angular&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/Vg8l_UVX1Eg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/6222430013841440511/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/09/angularjs-simplicity-in-your-browser.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/6222430013841440511?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/6222430013841440511?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/Vg8l_UVX1Eg/angularjs-simplicity-in-your-browser.html" title="AngularJS - Simplicity in your browser [AngularJS]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-yMjMbxa_nqQ/T_x5D1PfL6I/AAAAAAAB4b0/JAPYBW-9IjQ/s72-c/AngularJS-large.png" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/09/angularjs-simplicity-in-your-browser.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UNRHs4fSp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-7584893025657391318</id><published>2012-07-12T00:00:00.000-04:00</published><updated>2012-09-27T14:08:15.535-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:08:15.535-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MVC" /><category scheme="http://www.blogger.com/atom/ns#" term="Angular" /><category scheme="http://www.blogger.com/atom/ns#" term="PhalconPHP" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>Building a web app with PhalconPHP and AngularJS Part II [How-To][PhalconPHP][AngularJS]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s1600/phalcon-green.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s200/phalcon-green.jpg" width="173" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style="text-align: left;"&gt;
This is Part II of a series of posts on building an application using&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp;and &lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;. Part I is located &lt;a href="http://www.niden.net/2012/07/building-web-app-with-phalconphp-and.html"&gt;here&lt;/a&gt;.&lt;/div&gt;
&lt;h4 style="text-align: left;"&gt;
Preface&lt;/h4&gt;
I have recently discovered &lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt; and I was impressed with its speed and ease of use. At the time of this writing, PhalconPHP is at version 0.4.2, with some serious &lt;a href="http://blog.phalconphp.com/post/26524358189/moving-towards-phalcon-0-5-x"&gt;redesign&lt;/a&gt; coming down the line on 0.5.x.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp;takes a different approach than any other PHP framework (see &lt;a href="http://framework.zend.com/"&gt;Zend&lt;/a&gt;, &lt;a href="http://symfony.com/"&gt;Symfony&lt;/a&gt;, &lt;a href="http://cakephp.org/"&gt;CakePHP&lt;/a&gt; etc.). It is written in C and compiled as a module which is then loaded on your web server. Effectively the whole framework is in memory for you to use, without needing to access the file system so that you can include a file here or a file there.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Advantages&lt;/i&gt;&lt;/h4&gt;
The core advantage of this approach is speed. The framework is in memory, ready to deliver its functionality, so your application is now only concerned about its files and not the framework itself. Once a framework is mature enough for usage, its files don't change that much. Yet for any of the traditional frameworks, PHP needs to scan the files, load them and the interpret them. This in effect has a serious impact on performance, especially for large projects.&lt;br /&gt;
&lt;br /&gt;
Another advantage is that since the framework is a module on your web server, you don't need to upload library files to each and every application you install on your host.&lt;br /&gt;
&lt;br /&gt;
Finally, you can mix and match whatever you need, using any of the components as '&lt;i&gt;glue&lt;/i&gt;' components rather than the whole framework. Most of the major frameworks also use this methodology for most of their components, however performance still is an issue. Additionally, in the case of any other framework, one might need to upload a very complicated and deep file structure on their web server so as to take advantage of one component to be used in an application.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Disadvantages&lt;/i&gt;&lt;/h4&gt;
Support and bug tracing are the two weaknesses of&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;. By &lt;i&gt;support&lt;/i&gt;&amp;nbsp;I do not mean support from the developers. On the contrary, the developers are doing a great job listening to the relatively young community, and issuing fixes. However, as with any framework, if you find a bug, you will try to trace the code back to each component in an effort to find a solution to your problem. When developing an application and have access to the source files (the library PHP files like Zend Framework has), not only you can learn from those implementations, but you can quickly fix something that might be broken and continue working. With&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp;you will need to wait until the next version is released, unless you are fluent in C and play around with the source code. For most PHP programmers (like myself), the process will be &lt;i&gt;report the bug and wait for the fix&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Since the framework is a module on your web server, you will need to be careful on upgrades. If your applications do not take advantage of the latest functionality the framework offers, you might fix something in one application, while breaking something in another. You cannot mix and match versions of&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp;per application.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Consideration&lt;/i&gt;&lt;/h4&gt;
&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp;is very young as a framework. It does have a lot of power, but there are a lot of things still missing (for instance relationships between models and a query builder). In time these pieces will be implemented and the framework will grow stronger :)&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
Implementation&lt;/h4&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-Y4-KRIf7LvY/T_8SHRBEa2I/AAAAAAAB6GU/H35rLOLLWeU/s1600/db_diagram.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="219" src="http://1.bp.blogspot.com/-Y4-KRIf7LvY/T_8SHRBEa2I/AAAAAAAB6GU/H35rLOLLWeU/s320/db_diagram.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
I downloaded the &lt;a href="https://github.com/phalcon/invo"&gt;INVO&lt;/a&gt; sample application and set it up on my web browser. Using that as a starting point, I started modifying it to fit my needs. I also set up the PhalconPHP&amp;nbsp;&lt;a href="http://vimeo.com/39035250"&gt;developer tools&lt;/a&gt; and &lt;a href="http://vimeo.com/43455647"&gt;PHPStorm support&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
For this application, I needed a table to store information about every podcast episode, a table to store all players and a table to store the users (namely Aaron, Josh and John). The Awards table would be the one that would store all the information regarding the game balls and kick in the balls awards.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Models&lt;/i&gt;&lt;/h4&gt;
Once those were in place I started building my models and relevant controllers/views. Setting a model up was really easy. I would create the table in my database and then run&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;phalcon create-model --table-name episodes&lt;/code&gt;&lt;/pre&gt;
and my model would be ready for me to use (example below for Episodes).&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class Episodes extends Phalcon_Model_Base {
    public $id;
    public $number;
    public $summary;
    public $airDate;
    public $outcome;
    public $createdAt;
    public $createdAtUserId;
    public $lastUpdate;
    public $lastUpdateUserId;
}&lt;/code&gt;&lt;/pre&gt;
After a while I decided I wanted to keep a track on who created a record and when, and who last updated a record and when for certain tables. After some refactoring I created my own model class that would give me the functionality I needed, and extended that class in relevant models.&lt;br /&gt;
&lt;br /&gt;
My custom class (that would take care of the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;createdAt&lt;/span&gt;, &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;createdAtUserId&lt;/span&gt;, &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;lastUpdated&lt;/span&gt;, &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;lastUpdatedUserId&lt;/span&gt; fields) also took advantage of the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;beforeSave&lt;/span&gt; hook to ensure that these fields were transparently updated. The &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;find&lt;/span&gt; and &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;findFirst&lt;/span&gt; static functions are used throughout the models and there is no reason to repeat them in each model, so they end up in this custom class. (Comments removed to preserve space)&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;use NDN_Session as Session;

class NDN_Model extends Phalcon_Model_Base
{
    public $createdAt;
    public $createdAtUserId;
    public $lastUpdate;
    public $lastUpdateUserId;

    public function beforeSave()
    {
        if (empty($this-&amp;gt;createdAtUserId)) {
            $auth     = Session::get('auth');
            $datetime = date('Y-m-d H:i:s');

            $this-&amp;gt;createdAt        = $datetime;
            $this-&amp;gt;createdAtUserId  = (int) $auth['id'];
        }
    }

    static public function find($parameters = array())
    {
        return parent::find($parameters);
    }

    static public function findFirst($parameters = array())
    {
        return parent::findFirst($parameters);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Session&lt;/i&gt;&lt;/h4&gt;
Although&amp;nbsp;&lt;a href="http://phalconphp.com/"&gt;PhalconPHP&lt;/a&gt;&amp;nbsp;provides a flash messenger utility, I had an issue with using the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;_forward&lt;/span&gt; function on a controller, after an action (say Add or Edit) was completed. Effectively the data would not refresh on screen. To combat that I used &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;_redirect&lt;/span&gt;. However, all the messages that I had in the flash messenger (&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;Phalcon_Flash&lt;/span&gt;) would disappear. An easy solution was to extend the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;Phalcon_Session&lt;/span&gt; and create two new functions &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;setFlash&lt;/span&gt; and &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;getFlash&lt;/span&gt;. The &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;setFlash&lt;/span&gt; is called whenever I want to set a message for the user to see. The function stores the message in a session variable. Before the controller is dispatched, the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;getFlash&lt;/span&gt; is called to return any messages waiting to be displayed, and after that the messages are cleared from the session and displayed on screen.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class NDN_Session extends Phalcon_Session
{
    public static function setFlash($class, $message, $css)
    {
        $data = array(
            'class'   =&amp;gt; $class,
            'message' =&amp;gt; $message,
            'css'     =&amp;gt; $css,
        );
        self::set('flash', $data);
    }

    public static function getFlash()
    {
        $data = self::get('flash');
        if (is_array($data)) {
            self::remove('flash');
            return $data;
        } else {
            return null;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Breadcrumbs&lt;/i&gt;&lt;/h4&gt;
&lt;div&gt;
I wanted to show breadcrumbs to the user, as a way to easiliy navigate throughout the application. To do so, I created my own Breadcrumbs class which holds an array of areas that the user is in. Effectively it is the 'depth' the user is in throughout the application. The class has a generate function, which returns back a JSON string. This is to be parsed by &lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt; so as to display the breadcrumbs.&lt;/div&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Controllers&lt;/i&gt;&lt;/h4&gt;
I created my controllers using the &lt;a href="https://github.com/phalcon/phalcon-devtools"&gt;PhalconPHP developer tools&lt;/a&gt;. Whether you use the &lt;a href="http://vimeo.com/42367665"&gt;webtools&lt;/a&gt; or the command line makes no difference. The skeleton of the controller is generated for you to use.&lt;br /&gt;
&lt;br /&gt;
Based on the flash messenger and &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;_redirect&lt;/span&gt; that I mentioned in the previous section, I had to extend the base controller, so as to add functionality that would allow me to show messages on screen after a redirect. Other reasons for this new class were to allow for a prefix on each page title, generate breadcrumbs and menus.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;use Phalcon_Tag as Tag;

use Phalcon_Flash as Flash;
use NDN_Session as Session;

class NDN_Controller extends Phalcon_Controller 
{
    protected $_bc = null;
    public function initialize()
    {
        Tag::prependTitle('HHF G&amp;amp;KB Awards | ');
        $this-&amp;gt;_bc = new NDN_Breadcrumbs();
    }

    public function beforeDispatch()
    {
        $message = Session::getFlash();
        if (is_array($message)) {
            Flash::$message['class'](
                $message['message'], $message['css']
            );
        }
        $this-&amp;gt;view-&amp;gt;setVar('breadcrumbs', $this-&amp;gt;_bc-&amp;gt;generate());
    }

    protected function _constructMenu($controller)
    {
        $commonMenu = array(
            'index'      =&amp;gt; 'Home', 
            'awards'     =&amp;gt; 'Awards', 
            'players'    =&amp;gt; 'Players', 
            'episodes'   =&amp;gt; 'Episodes', 
            'about'      =&amp;gt; 'About', 
            'contact'    =&amp;gt; 'Contact Us', 
        ); 
        $auth = Session::get('auth'); 

        $class  = get_class($controller); 
        $class  = str_replace('Controller', '', $class); 
        $active         = strtolower($class); 
        $sessionCaption = ($auth) ? 'Log Out'         : 'Log In'; 
        $sessionAction  = ($auth) ? '/session/logout' : '/session/index'; 

        $leftMenu = array(); 
        foreach ($commonMenu as $link =&amp;gt; $text) { 
            $isActive   = (bool) ($active == $link); 
            $newLink  = ('index' == $link) ? '/' : '/' . $link; 
            $leftMenu[] = array( 
                'active' =&amp;gt; $isActive, 
                'link'   =&amp;gt; $newLink, 
                'text'   =&amp;gt; $text, 
            ); 
        } 

        $menu = new StdClass(); 
        $menu-&amp;gt;current = $active; 
        $menu-&amp;gt;left    = $leftMenu; 

        if ($auth != false) { 
            $sessionCaption .= ' ' . $auth['name']; 
        } 

        $menu-&amp;gt;rightLink = $sessionAction; 
        $menu-&amp;gt;rightText = $sessionCaption; 

        return json_encode($menu); 
    } 
}&lt;/code&gt;&lt;/pre&gt;
Each controller would extend my base controller. In the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;initialize&lt;/span&gt; function:&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;the page title is set,&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;the breadcrumbs are added (and generated later on in the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;beforeDispatch&lt;/span&gt; of the base controller),&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;the menu is generated and passed to the view for &lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt; to process,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;additional variables would be generated for displaying elements based on whether a user is logged in or not&lt;/span&gt;&lt;span style="background-color: white;"&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i style="background-color: white;"&gt;Views&lt;/i&gt;&lt;/h4&gt;
Creating the views was really easy. I already had the structure ready from the sample application (&lt;a href="https://github.com/phalcon/invo"&gt;INVO&lt;/a&gt;) and with the help of &lt;a href="http://twitter.github.com/bootstrap/"&gt;Bootstrap CSS&lt;/a&gt;, I was done in no time. The views inherit from a base view (index.phtml) located at the root of the views folder. That view holds the skeleton of the web page and content is injected accordingly based on each controller (and its view).&lt;br /&gt;
&lt;br /&gt;
In that file I added the relevant variables that will be used by&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;as well as variables that hold conditional elements (i.e. elements that appear when a user is logged in).&lt;br /&gt;
&lt;br /&gt;
More on the views in the next installment of these series.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;
&lt;i&gt;Conclusion&lt;/i&gt;&lt;/h4&gt;
With all that the application was ready as far as the main structure was concerned. Tying everything &amp;nbsp;with&amp;nbsp;&lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;&amp;nbsp;was the next step, which will be covered in part III of this How-To.&lt;br /&gt;
&lt;br /&gt;
The whole application, from start to finish, took less than 4 hours to develop. This included breaks, reading the manual and making design decisions based on my ever changing requirements.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/Xc81-9v-YRQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/7584893025657391318/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/07/building-web-app-with-phalconphp-and_12.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/7584893025657391318?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/7584893025657391318?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/Xc81-9v-YRQ/building-web-app-with-phalconphp-and_12.html" title="Building a web app with PhalconPHP and AngularJS Part II [How-To][PhalconPHP][AngularJS]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s72-c/phalcon-green.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/07/building-web-app-with-phalconphp-and_12.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UDR309fip7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-6803490038231017297</id><published>2012-07-10T00:00:00.000-04:00</published><updated>2012-09-27T14:07:56.366-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:07:56.366-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MVC" /><category scheme="http://www.blogger.com/atom/ns#" term="Angular" /><category scheme="http://www.blogger.com/atom/ns#" term="PhalconPHP" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>Building a web app with PhalconPHP and AngularJS Part I [How-To][PhalconPHP][AngularJS]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s1600/phalcon-green.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s200/phalcon-green.jpg" width="173" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
There are ample frameworks on the Internet, most free, that a programmer can use to build a web application. Two of these frameworks are &lt;a href="http://www.phalconphp.com/"&gt;PhalconPHP&lt;/a&gt; and &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I decided to use those two frameworks and build a simple application which will keep track of the Game Balls and Kick in the Balls awards of &lt;a href="http://www.harryhogfootball.com/"&gt;Harry Hog Football&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.harryhogfootball.com/"&gt;Harry Hog Football&lt;/a&gt; is a podcast that has been going strong since 2005, created by Redskins fans for Redskins Fans. (for those that do not know, &lt;a href="http://www.redskins.com/"&gt;Washington Redskins&lt;/a&gt; is a team on the &lt;a href="http://www.nfl.com/"&gt;National Football League&lt;/a&gt; in the USA).&lt;br /&gt;
&lt;br /&gt;
Every week during the regular season, Aaron, Josh and John create a podcast, where they discuss the recent game, the injuries, the cuts, the new signings and they offer their Game Balls to the best players of the week as well as the Kick in the Balls awards for the ones that (according to the podcasters) '&lt;i&gt;suck&lt;/i&gt;'.&lt;br /&gt;
&lt;br /&gt;
I therefore created an application to record all those game balls and kick in the balls awards, so that we can all see, who is the most valuable player and who is the least valuable player for the Redskins throughout the years (&lt;i&gt;the term valuable is used loosely here&lt;/i&gt;).&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-yMjMbxa_nqQ/T_x5D1PfL6I/AAAAAAAB4b0/JAPYBW-9IjQ/s1600/AngularJS-large.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="82" src="http://1.bp.blogspot.com/-yMjMbxa_nqQ/T_x5D1PfL6I/AAAAAAAB4b0/JAPYBW-9IjQ/s320/AngularJS-large.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
As a starting point I used the &lt;a href="https://github.com/phalcon/invo"&gt;INVO&lt;/a&gt; application that &lt;a href="http://www.phalconphp.com/"&gt;PhalconPHP&lt;/a&gt; showcases as an easy application to get you started. I modified it significantly to address my needs, refactoring classes as much as possible to get the least amount of code with maximum usability.&lt;br /&gt;
&lt;br /&gt;
After building the application, I listened to all the episodes I could find, and entered the game balls and kick in the balls in the database. The models use the Phalcon_Model_Base class to handle data, while the rest of the application is handled by the Phalcon_Controller (and view of course).&lt;br /&gt;
&lt;br /&gt;
The data transfer between the application and the relevant sections is primarily handled by &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt;, which is dominant in the view layer. &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt; controllers handle menu creation, breadcrumbx as well as displaying results on screen.&lt;br /&gt;
&lt;br /&gt;
Twitter's &lt;a href="http://twitter.github.com/bootstrap/"&gt;Bootstrap CSS&lt;/a&gt; is used to put the final touches for the application.&lt;br /&gt;
&lt;br /&gt;
In subsequent posts I will explain each layer in turn, starting with &lt;a href="http://www.phalconphp.com/"&gt;PhalconPHP&lt;/a&gt; and continuing with &lt;a href="http://www.angularjs.org/"&gt;AngularJS&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="background-color: white;"&gt;This of course is by no means the perfect implementation. It has been a fun project for me, working on it on my own free time. You are more than welcome to fork the project and make any modifications you need. For those that are interested in getting straight to the code, it is available on Github &lt;/span&gt;&lt;a href="https://github.com/niden/phalcon-angular-harryhogfootball" style="background-color: white;"&gt;here&lt;/a&gt;&lt;span style="background-color: white;"&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The Github repository contains code that works with nginx. If you are having problems with Apache, check the public/index.php - there is a note there for nginx (probably will need to remove it) &lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/HofRvikzCnI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/6803490038231017297/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/07/building-web-app-with-phalconphp-and.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/6803490038231017297?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/6803490038231017297?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/HofRvikzCnI/building-web-app-with-phalconphp-and.html" title="Building a web app with PhalconPHP and AngularJS Part I [How-To][PhalconPHP][AngularJS]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-JskAale-yPw/T_x5E_2gsRI/AAAAAAAB4b8/De6cO1rLICQ/s72-c/phalcon-green.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/07/building-web-app-with-phalconphp-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU4ARH08cCp7ImA9WhJSFE8.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-2322972472618564931</id><published>2012-07-04T14:52:00.001-04:00</published><updated>2012-07-04T14:52:25.378-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-07-04T14:52:25.378-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Independence Day" /><category scheme="http://www.blogger.com/atom/ns#" term="Celebrations" /><category scheme="http://www.blogger.com/atom/ns#" term="Personal" /><title>2012 4th of July Independence Day [Celebrations]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-9oOzLMoJieY/T8NwMjvJWJI/AAAAAAABDqE/dvUT0hmzAmo/s1600/american-flag.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="240" src="http://2.bp.blogspot.com/-9oOzLMoJieY/T8NwMjvJWJI/AAAAAAABDqE/dvUT0hmzAmo/s320/american-flag.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
Today we celebrate the Declaration of Independence in the United States, also known widely as the 4th of July.&lt;br /&gt;
&lt;br /&gt;
During this day in 1776, delegates of 13 states signed a document which serves as the foundation of the Constitutional Republic, the United States of America.&lt;br /&gt;
&lt;br /&gt;
Happy 4th of July!&lt;br /&gt;
&lt;br /&gt;
The transcript is below&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div style="text-align: center;"&gt;
&lt;b&gt;The Declaration of Independence&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
IN CONGRESS, July 4, 1776.&lt;br /&gt;
&lt;br /&gt;
The unanimous Declaration of the thirteen united States of America,&lt;br /&gt;
&lt;br /&gt;
When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected them with another, and to assume among the powers of the earth, the separate and equal station to which the Laws of Nature and of Nature's God entitle them, a decent respect to the opinions of mankind requires that they should declare the causes which impel them to the separation.&lt;br /&gt;
&lt;br /&gt;
We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness.--That to secure these rights, Governments are instituted among Men, deriving their just powers from the consent of the governed, --That whenever any Form of Government becomes destructive of these ends, it is the Right of the People to alter or to abolish it, and to institute new Government, laying its foundation on such principles and organizing its powers in such form, as to them shall seem most likely to effect their Safety and Happiness. Prudence, indeed, will dictate that Governments long established should not be changed for light and transient causes; and accordingly all experience hath shewn, that mankind are more disposed to suffer, while evils are sufferable, than to right themselves by abolishing the forms to which they are accustomed. But when a long train of abuses and usurpations, pursuing invariably the same Object evinces a design to reduce them under absolute Despotism, it is their right, it is their duty, to throw off such Government, and to provide new Guards for their future security.--Such has been the patient sufferance of these Colonies; and such is now the necessity which constrains them to alter their former Systems of Government. The history of the present King of Great Britain is a history of repeated injuries and usurpations, all having in direct object the establishment of an absolute Tyranny over these States. To prove this, let Facts be submitted to a candid world.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has refused his Assent to Laws, the most wholesome and necessary for the public good.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has forbidden his Governors to pass Laws of immediate and pressing importance, unless suspended in their operation till his Assent should be obtained; and when so suspended, he has utterly neglected to attend to them.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has refused to pass other Laws for the accommodation of large districts of people, unless those people would relinquish the right of Representation in the Legislature, a right inestimable to them and formidable to tyrants only.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has called together legislative bodies at places unusual, uncomfortable, and distant from the depository of their public Records, for the sole purpose of fatiguing them into compliance with his measures.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has dissolved Representative Houses repeatedly, for opposing with manly firmness his invasions on the rights of the people.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has refused for a long time, after such dissolutions, to cause others to be elected; whereby the Legislative powers, incapable of Annihilation, have returned to the People at large for their exercise; the State remaining in the mean time exposed to all the dangers of invasion from without, and convulsions within.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has endeavoured to prevent the population of these States; for that purpose obstructing the Laws for Naturalization of Foreigners; refusing to pass others to encourage their migrations hither, and raising the conditions of new Appropriations of Lands.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has obstructed the Administration of Justice, by refusing his Assent to Laws for establishing Judiciary powers.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has made Judges dependent on his Will alone, for the tenure of their offices, and the amount and payment of their salaries.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has erected a multitude of New Offices, and sent hither swarms of Officers to harrass our people, and eat out their substance.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has kept among us, in times of peace, Standing Armies without the Consent of our legislatures.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has affected to render the Military independent of and superior to the Civil power.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has combined with others to subject us to a jurisdiction foreign to our constitution, and unacknowledged by our laws; giving his Assent to their Acts of pretended Legislation:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For Quartering large bodies of armed troops among us:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For protecting them, by a mock Trial, from punishment for any Murders which they should commit on the Inhabitants of these States:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For cutting off our Trade with all parts of the world:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For imposing Taxes on us without our Consent:&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For depriving us in many cases, of the benefits of Trial by Jury:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For transporting us beyond Seas to be tried for pretended offences&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For abolishing the free System of English Laws in a neighbouring Province, establishing therein an Arbitrary government, and enlarging its Boundaries so as to render it at once an example and fit instrument for introducing the same absolute rule into these Colonies:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For taking away our Charters, abolishing our most valuable Laws, and altering fundamentally the Forms of our Governments:&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;For suspending our own Legislatures, and declaring themselves invested with power to legislate for us in all cases whatsoever.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has abdicated Government here, by declaring us out of his Protection and waging War against us.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has plundered our seas, ravaged our Coasts, burnt our towns, and destroyed the lives of our people.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He is at this time transporting large Armies of foreign Mercenaries to compleat the works of death, desolation and tyranny, already begun with circumstances of Cruelty &amp;amp; perfidy scarcely paralleled in the most barbarous ages, and totally unworthy the Head of a civilized nation.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has constrained our fellow Citizens taken Captive on the high Seas to bear Arms against their Country, to become the executioners of their friends and Brethren, or to fall themselves by their Hands.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="background-color: white;"&gt;He has excited domestic insurrections amongst us, and has endeavoured to bring on the inhabitants of our frontiers, the merciless Indian Savages, whose known rule of warfare, is an undistinguished destruction of all ages, sexes and conditions.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
In every stage of these Oppressions We have Petitioned for Redress in the most humble terms: Our repeated Petitions have been answered only by repeated injury. A Prince whose character is thus marked by every act which may define a Tyrant, is unfit to be the ruler of a free people.&lt;br /&gt;
&lt;br /&gt;
Nor have We been wanting in attentions to our Brittish brethren. We have warned them from time to time of attempts by their legislature to extend an unwarrantable jurisdiction over us. We have reminded them of the circumstances of our emigration and settlement here. We have appealed to their native justice and magnanimity, and we have conjured them by the ties of our common kindred to disavow these usurpations, which, would inevitably interrupt our connections and correspondence. They too have been deaf to the voice of justice and of consanguinity. We must, therefore, acquiesce in the necessity, which denounces our Separation, and hold them, as we hold the rest of mankind, Enemies in War, in Peace Friends.&lt;br /&gt;
&lt;br /&gt;
We, therefore, the Representatives of the united States of America, in General Congress, Assembled, appealing to the Supreme Judge of the world for the rectitude of our intentions, do, in the Name, and by Authority of the good People of these Colonies, solemnly publish and declare, That these United Colonies are, and of Right ought to be Free and Independent States; that they are Absolved from all Allegiance to the British Crown, and that all political connection between them and the State of Great Britain, is and ought to be totally dissolved; and that as Free and Independent States, they have full Power to levy War, conclude Peace, contract Alliances, establish Commerce, and to do all other Acts and Things which Independent States may of right do. And for the support of this Declaration, with a firm reliance on the protection of divine Providence, we mutually pledge to each other our Lives, our Fortunes and our sacred Honor.&lt;br /&gt;
&lt;br /&gt;
The 56 signatures on the Declaration appear in the positions indicated:&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Georgia
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Button Gwinnett&lt;/span&gt;&lt;br /&gt;
Lyman Hall
George Walton
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;North Carolina&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;William Hooper&lt;/span&gt;&lt;br /&gt;
Joseph Hewes&lt;br /&gt;
John Penn&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;South Carolina
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Edward Rutledge&lt;/span&gt;&lt;br /&gt;
Thomas Heyward, Jr.&lt;br /&gt;
Thomas Lynch, Jr.&lt;br /&gt;
Arthur Middleton
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Massachusetts
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;John Hancock&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Maryland
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Samuel Chase&lt;/span&gt;&lt;br /&gt;
William Paca&lt;br /&gt;
Thomas Stone&lt;br /&gt;
Charles Carroll of Carrollton&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Virginia
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;George Wythe&lt;/span&gt;&lt;br /&gt;
Richard Henry Lee&lt;br /&gt;
Thomas Jefferson&lt;br /&gt;
Benjamin Harrison&lt;br /&gt;
Thomas Nelson, Jr.&lt;br /&gt;
Francis Lightfoot Lee&lt;br /&gt;
Carter Braxton
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Pennsylvania
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Robert Morris&lt;/span&gt;&lt;br /&gt;
Benjamin Rush&lt;br /&gt;
Benjamin Franklin&lt;br /&gt;
John Morton&lt;br /&gt;
George Clymer&lt;br /&gt;
James Smith&lt;br /&gt;
George Taylor&lt;br /&gt;
James Wilson&lt;br /&gt;
George Ross&lt;br /&gt;
&lt;br /&gt;
&lt;span style="background-color: white;"&gt;&lt;b&gt;Delaware&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Caesar Rodney&lt;/span&gt;&lt;br /&gt;
George Read&lt;br /&gt;
Thomas McKean
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;New York&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;William Floyd&lt;/span&gt;&lt;br /&gt;
Philip Livingston&lt;br /&gt;
Francis Lewis&lt;br /&gt;
Lewis Morris&lt;br /&gt;
&lt;br /&gt;
&lt;span style="background-color: white;"&gt;&lt;b&gt;New Jersey&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Richard Stockton&lt;/span&gt;&lt;br /&gt;
John Witherspoon&lt;br /&gt;
Francis Hopkinson&lt;br /&gt;
John Hart&lt;br /&gt;
Abraham Clark
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;New Hampshire
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Josiah Bartlett&lt;/span&gt;&lt;br /&gt;
William Whipple&lt;br /&gt;
Matthew Thornton&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Massachusetts
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Samuel Adams&lt;/span&gt;&lt;br /&gt;
John Adams&lt;br /&gt;
Robert Treat Paine&lt;br /&gt;
Elbridge Gerry&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rhode Island
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Stephen Hopkins&lt;/span&gt;&lt;br /&gt;
William Ellery&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Connecticut&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: white;"&gt;Roger Sherman&lt;/span&gt;&lt;br /&gt;
Samuel Huntington&lt;br /&gt;
William Williams&lt;br /&gt;
Oliver Wolcott&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/niden/~4/-Sg1Bmhfelk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/2322972472618564931/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/07/2012-4th-of-july-independence-day.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2322972472618564931?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2322972472618564931?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/-Sg1Bmhfelk/2012-4th-of-july-independence-day.html" title="2012 4th of July Independence Day [Celebrations]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-9oOzLMoJieY/T8NwMjvJWJI/AAAAAAABDqE/dvUT0hmzAmo/s72-c/american-flag.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/07/2012-4th-of-july-independence-day.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UBRXY6fCp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-2174118524994688725</id><published>2012-06-29T00:00:00.000-04:00</published><updated>2012-09-27T14:07:34.814-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:07:34.814-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Storage" /><category scheme="http://www.blogger.com/atom/ns#" term="RAID" /><category scheme="http://www.blogger.com/atom/ns#" term="NAS" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>How to build an inexpensive RAID for storage and streaming [How-To][Storage][NAS]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-u05g2O8FUvU/Tr1tS_Hv6LI/AAAAAAAAFuk/NblYWiD58Yw/s1600/remote_backup.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="156" src="http://1.bp.blogspot.com/-u05g2O8FUvU/Tr1tS_Hv6LI/AAAAAAAAFuk/NblYWiD58Yw/s200/remote_backup.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h4&gt;


Overview&lt;/h4&gt;
I have written about this before and it is still my favorite mantra&lt;br /&gt;
&lt;blockquote&gt;
&lt;i&gt;There are people that take backups and there are people that never had a hard drive fail
&lt;/i&gt;&lt;/blockquote&gt;
This mantra is basically what should drive everyone to take regular backups of their electronic files, so as to ensure that nothing is lost in case of hardware failures.&lt;br /&gt;
&lt;br /&gt;
Let's face it. Hard drives or any magnetic media are man made and will fail at some point. When is what we don't know, so we have to be prepared for it.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: left;"&gt;
&lt;br /&gt;&lt;/div&gt;
Services like &lt;a href="http://drive.google.com/"&gt;Google Drive&lt;/a&gt;, &lt;a href="https://www.icloud.com/"&gt;iCloud&lt;/a&gt;, &lt;a href="http://www.dropbox.com/"&gt;Dropbox&lt;/a&gt;, &lt;a href="http://boundlesscloud.com/"&gt;BoundlessCloud&lt;/a&gt;&amp;nbsp;and others offer good backup services, ensuring that there is at least one 'safe' copy of your data. But that is not enough. You should ensure that whatever happens, the memories stored in your pictures or videos, the important email communications, the important documents are all kept in a safe place and there are multiple backups of it. Once they are gone, they are gone for good, so backups are the only way to ensure that this does not happen.&lt;br /&gt;
&lt;h4&gt;


Background&lt;/h4&gt;
My current setup at home consists of a few notebooks, a mac-mini and a Shuttle computer with a 1TB hard drive, where I store all my pictures, some movies and my songs. I use &lt;a href="http://music.google.com/"&gt;Google Music Manager&lt;/a&gt; for my songs so that they are available at any time on my android phone, &lt;a href="http://picasaweb.google.com/"&gt;Picasa&lt;/a&gt; to be able to share my photos with my family and friends and&amp;nbsp;&lt;a href="http://drive.google.com/"&gt;Google Drive&lt;/a&gt;&amp;nbsp;so as to keep every folder I have in sync. I also use &lt;a href="http://www.roksbox.com/"&gt;RocksBox&lt;/a&gt; to stream some of that content (especially the movies) upstairs on either of our TVs through the &lt;a href="http://www.roku.com/"&gt;Roku&lt;/a&gt; boxes we have.&lt;br /&gt;
&lt;br /&gt;
Recently I went downstairs and noticed that the Shuttle computer (which run Windows XP at the time) was stuck in the POST screen. I rebooted the machine but it refused to load Windows, getting stuck either in the computer's POST screen or in the "Starting Windows".&lt;br /&gt;
&lt;br /&gt;
Something was wrong and my best guess was the hard drive was failing. I booted up then in Safe mode (and that worked), set the hard drive to be checked for defects and rebooted again to let the whole process finish. After an hour or so, the system was still checking the free space and was stuck at 1%. The hard drive was making some weird noises so I shut the system down.&lt;br /&gt;
&lt;br /&gt;
Although I had backups of all my files on the cloud through Picasa, Google Music Manager and Google Drive, I still wanted to salvage whatever I had in there just in case. I therefore booted up the system with a Linux live CD, mounted the hard drive and used FileZilla to transfer all the data from the Shuttle's hard drive to another computer. There was of course a bit of juggling going on since I had to transfer data in multiple hard drives due to space restrictions.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;


Replacing the storage&lt;/h4&gt;
I had to find something very cheap and practical. I therefore went to Staples and found a very nice (for the money) computer by Lenovo. It was only $300, offering 1TB 7200 SATA hard drive, i5 processor and 6GB of RAM.&lt;br /&gt;
&lt;br /&gt;
As soon as I got the system I booted it up, started setting everything up and before the end of the day everything was back in place, synched to the cloud.&lt;br /&gt;
&lt;br /&gt;
However the question remained: what happens if the hard drive fails again? Let's face it, I did lose a lot of time trying to set everything up again so I wasn't prepared to go through that again.&lt;br /&gt;
&lt;br /&gt;
My solution was simple:&lt;br /&gt;
&lt;br /&gt;
Purchase a cheap RAID (hardware) controller, an identical 1TB hard drive to the one I have, and a second hard drive to have the OS on. This way, the two 1TB hard drives can be connected to the RAID controller on a mirror setup (or RAID 1), while the additional hard drive can keep the operating system.&lt;br /&gt;
&lt;br /&gt;
I opted for a solid state drive from Crucial for the OS. Although it was not necessary to have that kind of speed, I thought it wouldn't hurt. It did hurt my pocket a bit but c'est la vie. For your own setup you can choose whatever you like.&lt;br /&gt;
&lt;h4&gt;


Hardware&lt;/h4&gt;
&lt;a href="http://www.amazon.com/gp/product/B0034CQR4O/ref=oh_details_o00_s00_i00" style="background-color: white;"&gt;HighPoint RocketRAID 620 2 SATA Port PCI-Express 2.0 x1 SATA 6Gb/s RAID Controller&lt;/a&gt;&lt;br /&gt;
($38.15) The RAID controller&lt;br /&gt;
&lt;a href="http://www.amazon.com/gp/product/B004W2JKZI/ref=oh_details_o00_s00_i01"&gt;Crucial 128 GB m4 2.5-Inch Solid State Drive SATA 6Gb/s CT128M4SSD2&lt;/a&gt;&lt;br /&gt;
($122.49) The OS drive&lt;br /&gt;
&lt;a href="http://www.amazon.com/gp/product/B0035WQBOY/ref=oh_details_o01_s00_i00"&gt;HITACHI 0F10383 1TB SATA 3.0Gb/s 3.0 7200 RPM 32MB Buffer Hard Drive&lt;/a&gt;&lt;br /&gt;
($106.99) RAID array drive (the only reason I bought this particular drive is because it is identical to the one that came with the Lenovo system)&lt;br /&gt;
&lt;br /&gt;
&lt;span style="background-color: white;"&gt;&lt;i&gt;NOTE&lt;/i&gt;: For those that are not interested in having a solid state drive for the OS, one can always go with other, much cheaper drives such as &lt;/span&gt;&lt;a href="http://www.amazon.com/Seagate-Momentus-7200RPM-Internal-ST9160412AS-Bare/dp/B0027P9BOC/ref=sr_1_1?s=pc&amp;amp;ie=UTF8&amp;amp;qid=1339943115&amp;amp;sr=1-1&amp;amp;keywords=hybrid+hard+drive" style="background-color: white;"&gt;this one&lt;/a&gt;&lt;span style="background-color: white;"&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;

Setup&lt;/h4&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="http://1.bp.blogspot.com/-588PRkGeoTo/T-4C_mkLwkI/AAAAAAAB1kM/NaDDAAuV0mU/s1600/IMG_5402.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="213" src="http://1.bp.blogspot.com/-588PRkGeoTo/T-4C_mkLwkI/AAAAAAAB1kM/NaDDAAuV0mU/s320/IMG_5402.JPG" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="font-size: 13px;"&gt;Picture 1: The two screws I had to create&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
After all the components arrived, I opened the computer and had a look at what I am facing with. One thing I did not realize was the lack of space for the third hard drive (the one that will hold the OS). I was under the impression that it would fit under the DVD ROM drive, but I did not account for the SD card reader that was installed in that space, so I had to be a bit creative (Picture 1).&lt;br /&gt;
&lt;br /&gt;
A couple of good measurements and two holes with the power drill created a perfect mounting point for the solid state hard drive. It is sitting now secure in front of the card reader connections, without interfering in any way.&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="http://3.bp.blogspot.com/-2g198oy8BLs/T-4CzSZHQaI/AAAAAAAB1kE/SPitrbL-ZRk/s1600/IMG_5401.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="213" src="http://3.bp.blogspot.com/-2g198oy8BLs/T-4CzSZHQaI/AAAAAAAB1kE/SPitrbL-ZRk/s320/IMG_5401.JPG" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="font-size: 13px;"&gt;The HighPoint RocketRaid 620 in place&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
The second hard drive and the raid card were really easy to install, just a couple of screws and everything was set in place (Picture 2).&lt;br /&gt;
&lt;br /&gt;
The second hard drive ended up in the only expansion 'bay' available for this system. This is below the existing drive, mounted on the left side of the case. The actual housing has guides that allow you to slide the drive until the screw holes are aligned and from there it is a two minute job to secure the drive in place.&lt;br /&gt;
&lt;br /&gt;
I also had a generic nVidia 460 1GRAM card, which I also installed in the system. This was not included in the purchases for building this system, but it is around $45 if not less now. I have had it for over a year now and it was installed in the old Shuttle computer, so I wasn't going to let it go to waste.&lt;br /&gt;
&lt;br /&gt;
With everything in place, all I had to do is boot the system up and enter the BIOS screen so as to ensure that the SSD drive had a higher priority than any other drive.&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="http://2.bp.blogspot.com/-2_u7GjdWctM/T-4DQ5fu6ZI/AAAAAAAB1kU/80h0_pFm_m0/s1600/IMG_5403.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/-2_u7GjdWctM/T-4DQ5fu6ZI/AAAAAAAB1kU/80h0_pFm_m0/s320/IMG_5403.JPG" width="213" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="font-size: 13px;"&gt;Hard drive #2 installed&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Once that was done, I put the installation disks in the DVD-ROM and restored the system on the SSD drive. 4 DVDs later (around 30 minutes) the system was installed and booted up. It took however another couple of hours until I had everything set up. The myriad of Windows Updates, (plus my slow Internet connection) contributed to this delay. However I have to admit, that the SSD drive was a very good purchase, since I have never seen Windows boot in less than 10 seconds (from power up to the login screen).&lt;br /&gt;
&lt;span style="background-color: white;"&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style="background-color: white;"&gt;The Windows updates included the nVidia driver so everything was set up (well almost that is). The only driver not installed was for the HighPoint RaidRocket RAID controller.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The installation disk provided that driver, alongside with a web based configuration tool. After installing the driver and a quick reboot, the RAID configuration tool was not easy to understand but I figured it out, even without reading the manual.&lt;br /&gt;
&lt;br /&gt;
Entering the Disk Manager, I initialized and formatted the drive and from then&amp;nbsp;on, I started copying all my filesin there.&lt;br /&gt;
&lt;br /&gt;
As a last minute change, I decided not to install&amp;nbsp;&lt;a href="http://www.roksbox.com/"&gt;RocksBox&lt;/a&gt;&amp;nbsp;and instead go with &lt;a href="http://www.plexapp.com/"&gt;Plex Media Server&lt;/a&gt;. After playing around with Plex, I found out that it was a lot easier to setup than RocksBox (RocksBox requires a web server to be installed on the server machine, whereas Plex automatically discovers servers). Installing the relevant channel on my Roku boxes was really easy and everything was ready to work right out of the box so to speak.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;

Problems&lt;/h4&gt;
The only problem that I encountered had to do with Lenovo itself. I wanted basically to install the system on the SSD drive. Since the main drive is 1TB and the SSD drive 128GB I could not use &lt;a href="http://www.clonezilla.org/"&gt;Clonezilla&lt;/a&gt;&amp;nbsp;or &lt;a href="http://www.terabyteunlimited.com/image-for-windows.htm"&gt;Image for Windows&lt;/a&gt;&amp;nbsp;to 'move' the system from one drive to another. I tried almost everything. I shrank the 1TB system partition so as to make it fit in the 128GB drive. I had to shut hibernation off, reboot a couple of times in Safe Mode to remove unmovable files, in short it was a huge time waster.&lt;br /&gt;
&lt;br /&gt;
Since Lenovo did not include the installation disks (only an applications DVD), I called their support line and inquired about those disks. I was sent from the hardware department to the software department, where a gentleman informed me that I have to pay $65 to purchase the OS disks. You can imagine my frustration to the fact that I had already paid for the OS by purchasing the computer. We went back and forth with the technician and in the end got routed to a manager who told me I can create the disks myself using Lenovo's proprietary software.&lt;br /&gt;
&lt;br /&gt;
The create rescue process required 10 DVDs, so I started the process. On DVD 7 the process halted. I repeated the process, only to see the same error on DVD 4. The following day I called Lenovo hardware support and managed to talk to a lady who was more than willing to send me the installation disks free of charge. Unfortunately right after I gave her my address, the line dropped, so I had to call again.&lt;br /&gt;
&lt;br /&gt;
The second phone call did not go very well. I was transferred to the software department again, where I was told that I have to pay $65 for the disks. The odd thing is that the technician tried to convince me that Lenovo actually doesn't pay money to Microsoft since they get an OEM license. Knowing that this is not correct, and after the fact that the technician was getting really rude, I asked to speak to a supervisor. The supervisor was even worse and having already spent 45 minutes on the phone, I asked to be transferred to the hardware department again. Once there, I spoke to another lady, explained the situation and how long I have been trying to get this resolved (we are at 55 minutes now) and she happily took my information and sent me the installation disks free of charge.&lt;br /&gt;
&lt;h4 style="text-align: left;"&gt;

&lt;span style="background-color: white;"&gt;Conclusion&lt;/span&gt;&lt;/h4&gt;
The setup discussed in this post is an inexpensive and relatively secure way of storing data in your own home/home network. The RAID 1 configuration offers redundancy, while the price of the system does not break the bank.&lt;br /&gt;
&lt;br /&gt;
I am very disappointed with Lenovo, trying to charge me for something I already paid for (the Operating System that is). Luckily the ladies at the hardware department were a lot more accommodating and I got what I wanted in the end.&lt;br /&gt;
&lt;br /&gt;
I hope this guide helped you.&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/Qk_A3Wbhig8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/2174118524994688725/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/06/how-to-build-inexpensive-raid-for.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2174118524994688725?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2174118524994688725?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/Qk_A3Wbhig8/how-to-build-inexpensive-raid-for.html" title="How to build an inexpensive RAID for storage and streaming [How-To][Storage][NAS]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-u05g2O8FUvU/Tr1tS_Hv6LI/AAAAAAAAFuk/NblYWiD58Yw/s72-c/remote_backup.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/06/how-to-build-inexpensive-raid-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8HQH06eyp7ImA9WhVbEk0.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-3815117869067134642</id><published>2012-05-28T08:40:00.001-04:00</published><updated>2012-05-28T08:40:31.313-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-28T08:40:31.313-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Memorial Day" /><category scheme="http://www.blogger.com/atom/ns#" term="Celebrations" /><category scheme="http://www.blogger.com/atom/ns#" term="Personal" /><title>2012 Memorial Day [USA][Celebrations]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-IEOTNCyISYM/T8Nvns_pIFI/AAAAAAABDp8/dtfT3bFldHo/s1600/vvmemorial.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="203" src="http://2.bp.blogspot.com/-IEOTNCyISYM/T8Nvns_pIFI/AAAAAAABDp8/dtfT3bFldHo/s320/vvmemorial.jpeg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Today is Memorial Day in the USA.&lt;br /&gt;
&lt;br /&gt;
It is a special day, dedicated to celebrate, remember and honor the veterans that have gone and fought for all the liberties that we all enjoy today (and in most cases take for granted).&lt;br /&gt;
&lt;br /&gt;
I would like to personally say a thank you - which is not enough for sure - to all those that ensured and continue to do so, &amp;nbsp;that my family, friends and I are safe and free.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;br /&gt;
You might also enjoy material from the following resources:&lt;br /&gt;
&lt;a href="http://www.niden.net/2010/05/2010-memorial-day-usacelebrationsperson.html"&gt;This post&lt;/a&gt;&amp;nbsp;that I wrote in 2010, the site&amp;nbsp;&lt;a href="http://www.usmemorialday.org/"&gt;http://www.usmemorialday.org&lt;/a&gt; or the following video which is one of my favorites:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;object width="320" height="266" class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://3.gvt0.com/vi/I0fQd858cRc/0.jpg"&gt;&lt;param name="movie" value="http://www.youtube.com/v/I0fQd858cRc&amp;fs=1&amp;source=uds" /&gt;
&lt;param name="bgcolor" value="#FFFFFF" /&gt;
&lt;embed width="320" height="266"  src="http://www.youtube.com/v/I0fQd858cRc&amp;fs=1&amp;source=uds" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;
&lt;br /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/niden/~4/4lUOUcmcpu8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/3815117869067134642/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/05/2012-memorial-day-usacelebrations.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/3815117869067134642?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/3815117869067134642?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/4lUOUcmcpu8/2012-memorial-day-usacelebrations.html" title="2012 Memorial Day [USA][Celebrations]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-IEOTNCyISYM/T8Nvns_pIFI/AAAAAAABDp8/dtfT3bFldHo/s72-c/vvmemorial.jpeg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Lewes, DE, USA</georss:featurename><georss:point>38.7745565 -75.1393498</georss:point><georss:box>38.749788 -75.17883180000001 38.799325 -75.0998678</georss:box><feedburner:origLink>http://www.niden.net/2012/05/2012-memorial-day-usacelebrations.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UHRHozeCp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-8804374726233850940</id><published>2012-03-05T14:24:00.000-05:00</published><updated>2012-09-27T14:07:15.480-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:07:15.480-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="PHP" /><category scheme="http://www.blogger.com/atom/ns#" term="Localization" /><title>Localization and Country Regions [Localization]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-RfPOzhbpjkg/T1USxZWpgvI/AAAAAAAAtkI/JqmNSS3uAZQ/s1600/world-globe.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://1.bp.blogspot.com/-RfPOzhbpjkg/T1USxZWpgvI/AAAAAAAAtkI/JqmNSS3uAZQ/s200/world-globe.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
I was recently assigned a task in my current job, to try and standardize address related data.&lt;br /&gt;
&lt;br /&gt;
My approach was to use the ISO codes for countries and ISO codes for regions. A region is defined as the geographical split of areas in a country. For instance in the US regions are called states, in Canada provinces etc.&lt;br /&gt;
&lt;br /&gt;
I have used several sources on the internet but my main source was &lt;a href="http://en.wikipedia.org/wiki/ISO_3166-2"&gt;Wikipedia&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
In order to complete this task I used &lt;a href="http://framework.zend.com/manual/en/zend.locale.html"&gt;Zend_Locale&lt;/a&gt; to retrieve the available countries and using the ISO codes (keys of the returned data).&lt;br /&gt;
&lt;br /&gt;
My method is:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Gets the countries based on a passed (or default) locale
 * 
 * @param string  $locale
 * @param boolean  $sort
 */
public static function getCountries($locale = NULL, $sort = TRUE)
{
    $results = parent::getTranslationList('territory', $locale, 2);
    
    if ($sort) 
    {
        asort($results);
    }

    return $results;
}
&lt;/code&gt;&lt;/pre&gt;
Having the list of countries and their ISO codes, allowed me to have a proper storage of country related data in my addresses table. The use of a select box in the view layer ensures that I keep this standardization in place.&lt;br /&gt;
&lt;br /&gt;
Extending that, I wanted to have an easy way to access the region related data, without having to hit the database. I therefore used JSON encoded files that contain an array with the type of the region as the key and Region ISO Code =&amp;gt; Region Name as the array elements. Again I used a select box for that in the view layer, which is automatically refreshed when a new country is selected.&lt;br /&gt;
&lt;br /&gt;
So for instance for the US the JSON file is as follows:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;{
    "state":{
        "US-AL":"Alabama",
        "US-AK":"Alaska",
        ...
        "US-WV":"West Virginia",
        "US-WI":"Wisconsin",
        "US-WY":"Wyoming"
    }
}
&lt;/code&gt;&lt;/pre&gt;
where the key defines the region - in this case a State, and the array elements are the relevant data. In some instances I wanted to give a bit more selection and visual aids to the end user. I therefore have several sub arrays denoted by &lt;i&gt;optiongroup&lt;/i&gt; tags for the relevant select box showing the regions.&lt;br /&gt;
&lt;br /&gt;
In the case of Great Britain:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;{
    "county":{
        "England":{
            "GB-BKM":"Buckinghamshire",
            "GB-CAM":"Cambridgeshire",
            ...
            "GB-WOK":"Wokingham",
            "GB-YOR":"York"
        },
        "Northern Ireland":{
            "GB-ANT":"Antrim",
            "GB-ARD":"Ards",
            ...
            "GB-OMH":"Omagh",
            "GB-STB":"Strabane"
        },
        "Scotland":{
            "GB-ABE":"Aberdeen City",
            "GB-ABD":"Aberdeenshire",
            ...
            "GB-WDU":"West Dunbartonshire",
            "GB-WLN":"West Lothian"
        },
        "Wales":{
            "GB-BGW":"Blaenau Gwent",
            "GB-BGE":"Bridgend (Pen-y-bont ar Ogwr)",
            ...
            "GB-VGL":"Vale of Glamorgan (Bro Morgannwg)",
            "GB-WRX":"Wrexham (Wrecsam)"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
So with simple commands as file_get_contents and json_decode (or using Zend_Json::decode) I was able to have a flexible system that would load standardized address related data (regions and countries).&lt;br /&gt;
&lt;br /&gt;
You can download the list of JSON files (one per country) from my &lt;a href="https://github.com/niden/Localized_World_Regions"&gt;GitHub&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The list I have there is by no means complete, but it does have a lot of data that one can use. Additions are always welcome :)&lt;br /&gt;
&lt;br /&gt;
Enjoy!&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/xd40M_CEAL8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/8804374726233850940/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/03/localization-and-country-regions.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/8804374726233850940?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/8804374726233850940?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/xd40M_CEAL8/localization-and-country-regions.html" title="Localization and Country Regions [Localization]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-RfPOzhbpjkg/T1USxZWpgvI/AAAAAAAAtkI/JqmNSS3uAZQ/s72-c/world-globe.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://www.niden.net/2012/03/localization-and-country-regions.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0cNRH0_cCp7ImA9WhNTFEU.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-6956483791226250152</id><published>2012-01-15T15:44:00.000-05:00</published><updated>2012-10-17T11:51:35.348-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-17T11:51:35.348-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="PHP" /><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="PHPUnit" /><category scheme="http://www.blogger.com/atom/ns#" term="Installation" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>Downgrading PHPUnit from 3.6 to 3.5 [PHPUnit][Linux][HowTo]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-ed19ux0EFWw/TxQ1jgbyhkI/AAAAAAAArEc/tXwC_QNwzQw/s1600/logo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="178" src="http://2.bp.blogspot.com/-ed19ux0EFWw/TxQ1jgbyhkI/AAAAAAAArEc/tXwC_QNwzQw/s200/logo.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
Recently I had to rebuild my computer, and decided to install &lt;a href="http://www.linuxmint.com/"&gt;Linux Mint&lt;/a&gt; 12 (Lisa), which is a very lean installation - for my taste that is.&lt;br /&gt;
&lt;br /&gt;
Going through the whole process of reinstalling all the packages that I need or had, &lt;a href="http://www.phpunit.de/"&gt;PHPUnit&lt;/a&gt; was one of them. Easy enough a couple commands did the trick&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install php-pear
sudo pear upgrade PEAR
sudo pear config-set auto_discover 1
sudo pear install pear.phpunit.de/PHPUnit
&lt;/code&gt;&lt;/pre&gt;
I wanted to run my tests after that, only to find an error in the execution:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;PHP Fatal error: &amp;nbsp;Call to undefined method PHPUnit_Util_Filter::addFileToFilter()
 in /home/www/project/library/PHPUnit/Framework.php on line 48&lt;/code&gt;&lt;/pre&gt;
At first I thought that it was a path error, so I included the /usr/share/php/PHPUnit and others in the php.ini file but with no luck. With a little bit of Googling I found out that there have been some changes in the 3.6 version of PHPUnit and things don't work as they did before.&lt;br /&gt;
Effectively, 3.6 had some refactoring done and thus the line:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;PHPUnit_Util_Filter::addDirectoryToFilter("$dir/tests");&lt;/code&gt;&lt;/pre&gt;
changed to&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;PHP_CodeCoverage_Filter::getInstance()
        -&amp;gt;addDirectoryToBlacklist("$dir/tests");&lt;/code&gt;&lt;/pre&gt;
Since I didn't want to change my whole test suite, I had to find a solution i.e. downgrade PHPUnit to 3.5.&lt;br /&gt;
&lt;br /&gt;
Unfortunately specifying the version directly did not wok&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;sudo pear install phpunit/PHPUnit-3.5.15&lt;/code&gt;&lt;/pre&gt;
since it would pull the latest version again and I would end up with the 3.6 files.&lt;br /&gt;
&lt;br /&gt;
So I went one step further and installed specific versions of the relevant dependencies to satisfy the 3.5.15 version.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Uninstallation of 3.6&lt;/b&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;pear uninstall phpunit/PHPUnit_Selenium
pear uninstall phpunit/DbUnit
pear uninstall phpunit/PHPUnit
pear uninstall phpunit/PHP_CodeCoverage
pear uninstall phpunit/PHP_Iterator
pear uninstall phpunit/PHPUnit_MockObject
pear uninstall phpunit/Text_Template
pear uninstall phpunit/PHP_Invoker
pear uninstall phpunit/PHP_Timer
pear uninstall phpunit/File_Iterator
pear uninstall pear.symfony-project.com/YAML&lt;/code&gt;&lt;/pre&gt;
&lt;b&gt;Installation of 3.5.15&lt;/b&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;pear install pear.symfony-project.com/YAML-1.0.2
pear install phpunit/PHPUnit_Selenium-1.0.1
pear install phpunit/PHP_Timer-1.0.0
pear install phpunit/Text_Template-1.0.0
pear install phpunit/PHPUnit_MockObject-1.0.3
pear install phpunit/File_Iterator-1.2.3
pear install phpunit/PHP_CodeCoverage-1.0.2
pear install phpunit/DbUnit-1.0.0
pear install phpunit/PHPUnit-3.5.15&lt;/code&gt;&lt;/pre&gt;
I hope you find the above useful :)&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/8U79bve98bQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/6956483791226250152/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2012/01/downgrading-phpunit-from-36-to-35.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/6956483791226250152?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/6956483791226250152?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/8U79bve98bQ/downgrading-phpunit-from-36-to-35.html" title="Downgrading PHPUnit from 3.6 to 3.5 [PHPUnit][Linux][HowTo]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-ed19ux0EFWw/TxQ1jgbyhkI/AAAAAAAArEc/tXwC_QNwzQw/s72-c/logo.png" height="72" width="72" /><thr:total>5</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2012/01/downgrading-phpunit-from-36-to-35.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0IDQXk-fCp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-3094162598562822784</id><published>2011-12-19T22:00:00.000-05:00</published><updated>2012-09-27T14:12:50.754-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:12:50.754-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MySQL" /><category scheme="http://www.blogger.com/atom/ns#" term="PHP" /><category scheme="http://www.blogger.com/atom/ns#" term="Localization" /><category scheme="http://www.blogger.com/atom/ns#" term="Databases" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="Bash" /><title>Change the encoding of a MySQL database to UTF8 [How-To][PHP][MySQL]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-bTmAdRuC_iI/Tr79-FMfucI/AAAAAAAAFx0/WpAAcWzDzik/s1600/logomysql.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="133" src="http://1.bp.blogspot.com/-bTmAdRuC_iI/Tr79-FMfucI/AAAAAAAAFx0/WpAAcWzDzik/s200/logomysql.gif" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;b&gt;Overview&lt;/b&gt;&lt;br /&gt;
As applications grow, so do their audiences. In this day and age, one cannot assume that all the consumers of a web based application will live in a particular region and use only one language. Even if the developer assumes that one country will be served by the particular web application, there are instances that the latin1 character set will not suffice in storing data.&lt;br /&gt;
&lt;br /&gt;
Therefore, developers and database designers need to implement an encoding on their database that will safely store and retrieve any kind of data, not only latin1 based (i.e. the English alphabet).&lt;br /&gt;
&lt;br /&gt;
For MySQL this encoding is &lt;i&gt;utf8_general_ci&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The problem&lt;/b&gt;&lt;br /&gt;
MySQL usually comes with the &lt;i&gt;latin1_swedish_ci&lt;/i&gt; encoding as a default. This encoding will allow the developer to store data of course but when non latin1 characters need to be stored, there will be a problem. Effectively latin1 encoding will store data in 8 bits but some languages like Japanese, Thai, Arabic, even French or German have special characters that need more space in the storage engine. Trying to store a 16 bit character in a 8 bit space will fail all the time.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Latin1 based database:&lt;/i&gt;&lt;br /&gt;
Input: abcdef...ABCD...#$&lt;br /&gt;
Output: abcdef...ABCD...#$&lt;br /&gt;
&lt;br /&gt;
Input: 日本語 ภาษาไทย Ελληνικά&lt;br /&gt;
Output: ??? ??????? ????????&lt;br /&gt;
&lt;br /&gt;
To combat this, all you have to do is change the encoding of your database to &lt;i&gt;utf8_general_ci&lt;/i&gt; and the character set to &lt;i&gt;utf8&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The solution&lt;/b&gt;&lt;br /&gt;
I wrote a script in PHP to analyze a database server and produce ALTER statements to be executed against your database(s). The script needs to run from a web server that supports PHP.&lt;br /&gt;
&lt;br /&gt;
First of all, the encoding of the database will change with the relevant SQL statement. Following that each table's encoding will change, again with the relevant SQL statement. Finally, each TEXT/VARCHAR/CHAR etc. field's encoding will change towards the target encoding you specify in the configuration section (see below).&lt;br /&gt;
&lt;br /&gt;
The safest way to transform data this way is to first change the field to a BINARY field and then change the field to the target encoding and collation.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Configuration&lt;/i&gt;&lt;br /&gt;
There are a few configuration variables that need to be set prior to running the script.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;$db_user       = 'username';
$db_password   = 'password';
$db_host       = 'hostname';
$output_folder = '/home/ndimopoulos'; // Do not include trailing slash
$db_name       = 'mysql';             // Leave this one as is

set_time_limit(0);

/**
 * The old collation (what needs to be changed)
 */
$from_encoding = 'latin1_swedish_ci';

/**
 * The new collation (what we will change it to)
 */
$to_encoding = 'utf8_general_ci';

/**
 * The new character set
 */
$new_collation = 'utf8';

/**
 * Add USE &amp;lt;database&amp;gt;; before each statement?
 */
$use_database = TRUE;
&lt;/code&gt;&lt;/pre&gt;
The &lt;i&gt;$output_folder&lt;/i&gt; is a folder that is writeable from your web server and it is where the .sql files will be created filled with the ALTER statements. The script will output one file &lt;i&gt;&amp;lt;hostname&amp;gt;.sql&lt;/i&gt; which will contain all the ALTER statements for all databases. It will also create files for individual databases &lt;i&gt;&amp;lt;hostname&amp;gt;.&amp;lt;database&amp;gt;.sql&lt;/i&gt;. You can use either the big file or the individual database files. The choice is yours.&lt;br /&gt;
&lt;br /&gt;
The &lt;i&gt;$from_encoding&lt;/i&gt; is what the script will check. In my script I was checking '&lt;i&gt;latin1_swedish_ci&lt;/i&gt;'.&lt;br /&gt;
The &lt;i&gt;$to_encoding&lt;/i&gt; is what we need the encoding to be while the &lt;i&gt;$new_collation&lt;/i&gt; is the new character set.&lt;br /&gt;
&lt;br /&gt;
The &lt;i&gt;$use_database&lt;/i&gt; is a flag that will allow you to generate statements such as:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;USE &amp;lt;database&amp;gt;; ALTER TABLE &amp;lt;table&amp;gt;.....&lt;/code&gt;&lt;/pre&gt;
if it is on, and if off, the statement will be:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE &amp;lt;table&amp;gt;.....&lt;/code&gt;&lt;/pre&gt;
&lt;i&gt;Databases loop&lt;/i&gt;&lt;br /&gt;
The script opens a connection to the server and runs the '&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;SHOW DATABASES&lt;/span&gt;' command. Based on the result returned, it populates an array with the database names.&lt;br /&gt;
&lt;br /&gt;
The script ignores two databases 'information_schema' and 'mysql', but editing the $exclude_databases array will allow you to ignore more databases if you need to.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;mysql_connect($db_host, $db_user, $db_password);
mysql_select_db($db_name);

$dbs = array();

exclude_databases = array('mysql', 'information_schema',);

/**
 * Get the databases available (ignore information_schema and mysql)
 */
$result = mysql_query("SHOW DATABASES");

while ($row = mysql_fetch_row($result))
{
    if (!in_array($row[0], $exclude_databases))
    {
        $dbs[] = $row[0];
    }
}
&lt;/code&gt;&lt;/pre&gt;
The database names are stored in an array, so as not to keep the database resource active all the time. Had I not done that, I would have had to use three different resources (one for the database, one for the table and one for the field being checked - three nested loops).&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Tables loop&lt;/i&gt;&lt;br /&gt;
The script then loops through the $dbs array and selects each database in turn. Once the database is selected, the '&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;SHOW TABLES&lt;/span&gt;' query is run and a $tables array is populated with the names of the tables in that database. At the same time the ALTER DATABASE statements are being generated.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;mysql_select_db($db);

$db_output = '';

$statement  = "\r\n#-------------------------------------------------\r\n\r\n";
$statement .= "USE $db;\r\n";
$statement .= "\r\n#-------------------------------------------------\r\n\r\n";
$statement .= "ALTER DATABASE $db "
           . "CHARACTER SET $new_collation COLLATE $to_encoding;\r\n";
$statement .= "\r\n#-------------------------------------------------\r\n\r\n";

$db_output .= $statement;
$output &amp;nbsp; &amp;nbsp;.= $statement;

$tables &amp;nbsp; &amp;nbsp; = array();

$result = mysql_query("SHOW TABLES");

while ($row = mysql_fetch_row($result))
{
    if (!in_array($row[0], $exclude_tables))
    {
        $tables[] = mysql_real_escape_string($row[0]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Fields loop&lt;/i&gt;&lt;br /&gt;
The script then loops through the $tables array and runs the '&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;SHOW FIELDS&lt;/span&gt;' query so as to analyze each field.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;$fields_modify = array();
$fields_change = array();

$result = mysql_query("SHOW FULL FIELDS FROM `$table`");
while ($row = mysql_fetch_assoc($result)) 
{
    if ($row['Collation'] != $from_encoding)
    {
        continue;
    }
   
    // Is the field allowed to be null?
    $nullable = ($row['Null'] == 'YES') ? ' NULL ' : ' NOT NULL';

    if ($row['Default'] == 'NULL') 
    {
       $default = " DEFAULT NULL";
    } 
    else if ($row['Default']!='') 
    {
       $default = " DEFAULT '" . mysql_real_escape_string($row['Default']) . "'";
    } 
    else 
    {
       $default = '';
    }

    // Alter field collation:
    $field_name = mysql_real_escape_string($row['Field']);

    $fields_modify[] = "MODIFY `$field_name` $row[Type] CHARACTER SET BINARY";
    $fields_change[] = "CHANGE `$field_name` `$field_name` $row[Type] "
                     . "CHARACTER SET $new_collation "
                     . "COLLATE $to_encoding $nullable $default";
}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
The two arrays generated (&lt;i&gt;$fields_modify&lt;/i&gt; and &lt;i&gt;$fields_change&lt;/i&gt; contain the MODIFY and CHANGE statements of each field. Using implode, we can easily construct the ALTER statement.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;$statement .= "ALTER TABLE `$table` " 
            . implode(' , ', $fields_modify) . "; \r\n";
$statement .= "ALTER TABLE `$table` " 
            . implode(' , ', $fields_change) . "; \r\n";
&lt;/code&gt;&lt;/pre&gt;
&lt;b&gt;Notes&lt;/b&gt;&lt;br /&gt;
You can use as mentioned earlier the &lt;i&gt;$exclude_databases&lt;/i&gt; array to not allow certain databases to be processed. You can also use the &lt;i&gt;$exclude_tables&lt;/i&gt; array to not allow certain tables to be processed.&lt;br /&gt;
&lt;br /&gt;
The &lt;i&gt;$exclude_tables_fields&lt;/i&gt; array allows you to exclude a field from being processed. However this is not tied to a database so any database/table that has a field with that particular name will not be processed. With a bit of refactoring you can make the script best work for your needs.&lt;br /&gt;
&lt;br /&gt;
If you set the &lt;i&gt;$use_database&lt;/i&gt; variable to TRUE then each line in your .sql statements will be prefixed with a '&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;USE &amp;lt;database&amp;gt;;&lt;/span&gt;' statement. This is to help the accompanying bash script to execute each statement in the respective database. If you intend on not running this process one statement at a time, you can set this to FALSE. You can then run each database .sql file (or the one that contains all of the statements from all databases) as one single command.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Server processing&lt;/b&gt;&lt;br /&gt;
Now that the relevant .sql files have been created, all you have to do is upload them on your web server. There are three ways of actually running the statements against the database.&lt;br /&gt;
&lt;br /&gt;
&lt;div style="text-align: center;"&gt;
&lt;b&gt;Please make sure you backup your data first!&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;i&gt;Single file processing&amp;nbsp;&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;mysql -h&amp;lt;host&amp;gt; -u&amp;lt;username&amp;gt; -p&amp;lt;password&amp;gt; &amp;lt; /path/to/scripts/&amp;lt;host&amp;gt;.sql&lt;/code&gt;&lt;/pre&gt;
The command above will run all the commands generated in the &amp;lt;host&amp;gt;.sql file for all databases. It is going to be taxing for your database server and there is no error handling or reporting. You can always pipe the results to an output file (just append "&lt;i&gt;&amp;gt; /path/to/output/output.txt&lt;/i&gt;" at the end of the command). If this method fails for some reason (MySQL has gone away), it will be difficult to resume; you will need to edit the &amp;lt;host&amp;gt;.sql file to remove the statements that have already been processed.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Per database&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;mysql -h&amp;lt;host&amp;gt; -u&amp;lt;username&amp;gt; -p&amp;lt;password&amp;gt; &amp;lt;database_name&amp;gt; \
       &amp;lt;/path/to/scripts/&amp;lt;host&amp;gt;.&amp;lt;database&amp;gt;.sql&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
The command above will run all the commands generated in the &amp;lt;host&amp;gt;.&amp;lt;database&amp;gt;.sql file for that particular database. This method is similar to the one above. Again you can always pipe the results to an output file (just append "&lt;i&gt;&amp;gt; /path/to/output/output.txt&lt;/i&gt;" at the end of the command).&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;i&gt;Single file processing (per statement)&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;/path/to/scripts/process.sh&lt;/code&gt;&lt;/pre&gt;
All you need to do is edit the process.sh script and change the relevant parameters to match your environment and upload it to your server. The source file that the process.sh script will read has to be generated with &lt;i&gt;$use_database&lt;/i&gt;&amp;nbsp;set to TRUE.&lt;br /&gt;
&lt;br /&gt;
The process.sh script is:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

DBUSER=root
DBPASS=1234
DBHOST=localhost
SOURCE="/home/ndimopoulos/host.sql"
LOG="/home/ndimopoulos/conversion.log"

while read line
do
    TIMENOW=`date +%Y-%m-%d-%H-%M`
    echo START $TIMENOW $line
    echo START $TIMENOW $line &amp;gt;&amp;gt; $LOG
    /usr/bin/time -f "%E real,%U user,%S sys" -v -o $LOG -a \
        mysql -h$DBHOST -u$DBUSER -p$DBPASS -e "$line"
    
    TIMENOW=`date +%Y-%m-%d-%H-%M`
    echo END $TIMENOW 
    echo END $TIMENOW &amp;gt;&amp;gt; $LOG

done &amp;lt; $SOURCE

exit 0&lt;/code&gt;&lt;/pre&gt;
The script above will start reading the source file (&amp;lt;host&amp;gt;.sql) and execute each statement in turn, using &lt;a href="http://en.wikipedia.org/wiki/Time_(Unix)"&gt;time&lt;/a&gt; to measure the time taken to execute that command. The output ends up in a log file which can easily be tailed to view the progress and used later on for analysis. The results of the processing are also sent to the screen. You can change the parameters for the time command to match your needs.&lt;br /&gt;
&lt;br /&gt;
The output will look something like the block below:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;START 2011-12-08-23-46 USE mydatabase; \
    ALTER TABLE `tablename` DEFAULT CHARACTER SET utf8;
 Command being timed: "mysql -uroot -p1234 -e USE mydatabase; \
     ALTER TABLE `tablename` DEFAULT CHARACTER SET utf8;"
 User time (seconds): 0.01
 System time (seconds): 0.00
 Percent of CPU this job got: 0%
 Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.16
 Average shared text size (kbytes): 0
 Average unshared data size (kbytes): 0
 Average stack size (kbytes): 0
 Average total size (kbytes): 0
 Maximum resident set size (kbytes): 8192
 Average resident set size (kbytes): 0
 Major (requiring I/O) page faults: 0
 Minor (reclaiming a frame) page faults: 610
 Voluntary context switches: 11
 Involuntary context switches: 5
 Swaps: 0
 File system inputs: 0
 File system outputs: 0
 Socket messages sent: 0
 Socket messages received: 0
 Signals delivered: 0
 Page size (bytes): 4096
 Exit status: 0
END 2011-12-08-23-46
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
In order for a database to be best prepared to support localization, you need to make sure that the storage will accept any possible character. You can start by creating all your tables and fields with &lt;i&gt;utf8_general_ci&lt;/i&gt; encoding, but for existing databases and data, you will need to run expensive&amp;nbsp;processing&amp;nbsp;queries on your RDBMS. Ensuring that the data does not get corrupted when performing the transformation process is essential so make sure you backup your databases before trying or running the output statements produced by the db_alter.php script.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;PHP script (db_alter.php)&lt;/b&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;$db_user       = 'username';
$db_password   = 'password';
$db_host       = 'hostname';
$output_folder = '/home/ndimopoulos'; // Do not include trailing slash
$db_name       = 'mysql'; // Leave this one as is

set_time_limit(0);

/**
 * The old collation (what needs to be changed)
 */
$from_encoding = 'latin1_swedish_ci';

/**
 * The new collation (what we will change it to)
 */
$to_encoding = 'utf8_general_ci';

/**
 * The new character set
 */
$new_collation = 'utf8';

/**
 * Add USE &amp;lt;database&amp;gt; before each statement?
 */
$use_database = TRUE;

mysql_connect($db_host, $db_user, $db_password);
mysql_select_db($db_name);

$dbs = array();

$exclude_databases     = array('mysql', 'information_schema',);
$exclude_tables        = array('logs', 'logs_archived',);
$exclude_tables_fields = array('activities');

/**
 * Get the databases available (ignore information_schema and mysql)
 */
$result = mysql_query("SHOW DATABASES");

while ($row = mysql_fetch_row($result)) 
{
    if (!in_array($row[0], $exclude_databases))
    {
        $dbs[] = $row[0];
    }
}

$output = '';
/**
 * Now select each db and start parsing the tables
 */
foreach ($dbs as $db)
{
    mysql_select_db($db);
    $db_output = '';
    
    $statement  = "\r\n#----------------------------------------\r\n\r\n";
    $statement .= "USE $db;\r\n";
    $statement .= "\r\n#----------------------------------------\r\n\r\n";
    $statement .= "ALTER DATABASE $db "
               . "CHARACTER SET $new_collation COLLATE $to_encoding;\r\n";
    $statement .= "\r\n#----------------------------------------\r\n\r\n";
    
    $db_output .= $statement;
    $output    .= $statement;
    $tables     = array();
    
    $result = mysql_query("SHOW TABLES");
    
    while ($row = mysql_fetch_row($result))
    {
        if (!in_array($row[0], $exclude_tables))
        {
            $tables[] = mysql_real_escape_string($row[0]);
        }
    }
    
    /**
     * Alter statements for the tables
     */
    foreach ($tables as $table)
    {
        $statement = '';
        if ($use_database)
        {
            $statement  = "USE $db; ";
        }
        $statement .= "ALTER TABLE `$table` "
                   . "DEFAULT CHARACTER SET $new_collation;\r\n";
        $db_output .= $statement;
        $output    .= $db_output;
    }
    $statement .= "\r\n#----------------------------------------\r\n\r\n";

    $db_output .= $statement;
    $output    .= $statement;
    
    /**
     * Get the fields for each table
     */
    foreach ($tables as $table)
    {
        if (in_array($table, $exclude_tables_fields))
        {
            continue;
        } 
        
        $fields_modify = array();
        $fields_change = array();
        
        $result = mysql_query("SHOW FULL FIELDS FROM `$table`");
        while ($row = mysql_fetch_assoc($result)) 
        {
            if ($row['Collation'] != $from_encoding)
            {
                continue;
            }
            
            // Is the field allowed to be null?
            $nullable = ($row['Null'] == 'YES') ? ' NULL ' : ' NOT NULL';
            
            if ($row['Default'] == 'NULL') 
            {
                $default = " DEFAULT NULL";
            } 
            else if ($row['Default']!='') 
            {
                $default = " DEFAULT '"
                         . mysql_real_escape_string($row['Default']) . "'";
            }
            else 
            {
                $default = '';
            }
            
            // Alter field collation:
            $field_name = mysql_real_escape_string($row['Field']);
            
            $fields_modify[] = "MODIFY `$field_name` $row['Type'] "
                             . "CHARACTER SET BINARY";
            $fields_change[] = "CHANGE `$field_name` `$field_name` $row['Type'] "
                             . "CHARACTER SET $new_collation "
                             . "COLLATE $to_encoding $nullable $default";
        }
        
        if (count($fields_modify) &amp;gt; 0)
        {
            $statement = '';
            if ($use_database)
            {
                $statement = "USE $db; ";
            }
            $statement .= "ALTER TABLE `$table` "
                        . implode(' , ', $fields_modify) . "; \r\n";
            if ($use_database)
            {
                $statement = "USE $db; ";
            }
            $statement .= "ALTER TABLE `$table` "
                        . implode(' , ', $fields_change) . "; \r\n";
            
            $db_output .= $statement;
            $output    .= $statement;
        }
    }
    
    $bytes = file_put_contents(
        $output_folder . '/' . $db_host . '.' . $db . '.sql', $db_output
    );
}
    
$bytes = file_put_contents($output_folder . '/' . $db_host . '.sql', $output);

echo "&amp;lt;pre&amp;gt;$db_host $bytes \r\n$output&amp;lt;/pre&amp;gt;";
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Downloads&lt;/b&gt;&lt;br /&gt;
You can use these scripts at your own risk. Also feel free to distribute them freely - a mention would be nice. Both scripts can be found in my &lt;a href="https://github.com/niden"&gt;GitHub&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/OzwjaCi93Fk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/3094162598562822784/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/12/change-encoding-of-mysql-database-to.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/3094162598562822784?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/3094162598562822784?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/OzwjaCi93Fk/change-encoding-of-mysql-database-to.html" title="Change the encoding of a MySQL database to UTF8 [How-To][PHP][MySQL]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-bTmAdRuC_iI/Tr79-FMfucI/AAAAAAAAFx0/WpAAcWzDzik/s72-c/logomysql.gif" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/12/change-encoding-of-mysql-database-to.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0IHRX4-fyp7ImA9WhBRFks.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-5929435911408963024</id><published>2011-11-22T17:34:00.001-05:00</published><updated>2013-03-07T10:18:54.057-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-03-07T10:18:54.057-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Programming" /><category scheme="http://www.blogger.com/atom/ns#" term="PHP" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="Performance" /><title>Fast serialization of data in PHP [How-To][Performance]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-fKGt9OWGp7U/TswyoFFCBRI/AAAAAAAAMcY/9-cXDD5NCw0/s1600/sleep-wakeup-in-php.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-fKGt9OWGp7U/TswyoFFCBRI/AAAAAAAAMcY/9-cXDD5NCw0/s200/sleep-wakeup-in-php.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;b&gt;Serializing/Unserializing data&lt;/b&gt;&lt;br /&gt;
&lt;blockquote&gt;
&lt;i&gt;Serialization is the process of converting a data structure or object state into a format that can be stored and "resurrected" later in the same or another computer environment. &lt;a href="http://en.wikipedia.org/wiki/Serialization"&gt;source&lt;/a&gt;&lt;/i&gt;&lt;/blockquote&gt;
There are a lot of areas where one can use serialization. A couple are:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;in a database (storing an array of options specific to the user),&amp;nbsp;&lt;/li&gt;
&lt;li&gt;in an AJAX enabled application (call to get a status update and display to the user without refreshing the whole page), etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Based on the the application, serializing and unserializing data can be a very intensive process and can prove to have a big performance hit on the overall system.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Options&lt;/b&gt;&lt;br /&gt;
The most obvious option for serializing and unserializing data are the &lt;a href="http://php.net/manual/en/function.serialize.php"&gt;serialize&lt;/a&gt; and &lt;a href="http://www.php.net/manual/en/function.unserialize.php"&gt;unserialize&lt;/a&gt; PHP functions. A bit less popular are &lt;a href="http://us3.php.net/manual/en/function.json-encode.php"&gt;json_encode&lt;/a&gt; and &lt;a href="http://us3.php.net/manual/en/function.json-decode.php"&gt;json_decode&lt;/a&gt;. There is also a third option, using a third party module that one can easily install on their server. This module is called &lt;a href="http://opensource.dynamoid.com/"&gt;igbinary&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
In this blog post I am comparing the three options, in the hope that it will aid you with your selection of the best option for you so as to increase the performance of your application.&lt;br /&gt;
&lt;br /&gt;
I created a test script that used several arrays of data (strings, integers, floats, booleans, objects, mixed data, all of the data types) to test the speed and size of the serialization and speed of unserialization of each of the three candidate function pairs. I run the same function to serialize or unserialize the data respectively for 1,000,000 times so as to produce the results below.&lt;br /&gt;
&lt;br /&gt;
The script that I have used is listed below:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;$_testStrings = array(
    'AK' =&amp;gt; 'Alaska', 'AZ' =&amp;gt; 'Arizona', 'VT' =&amp;gt; 'Vermont',
    'VA' =&amp;gt; 'Virginia', 'AZ' =&amp;gt; 'West Virginia',
);
 
$_testIntegers = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 84, 144,);

$_testBooleans = array(TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE,);
 
$_testFloats = array(
    0, 1.1, 1.1, 2.22, 3.33, 5.55, 8.88, 13.13, 21.2121, 34.3434, 
    55.5555, 84.8484, 144.144,
);
 
$_testMixed = array(
    'one', 13 =&amp;gt; 'two', 0 =&amp;gt; 25.46, 'four' =&amp;gt; 0.007, 
    'five' =&amp;gt; TRUE, TRUE =&amp;gt; 42,
);
 
$_objectOne            = new stdClass();
$_objectOne-&amp;gt;firstname  = 'Leroy';
$_objectOne-&amp;gt;lastname   = 'Jenkins';
$_objectOne-&amp;gt;profession = 'Gamer';
$_objectOne-&amp;gt;status     = 'Legend';
 
$_objectTwo        = new stdClass();
$_objectTwo-&amp;gt;series = 'Fibonacci';
$_objectTwo-&amp;gt;data   = $_testIntegers;
 
$_testObjects = array($_objectOne, $_objectTwo,);

$_maxLoop = 1000000;

$_templateEncode = "%s [%s]: Size: %s bytes, %s time to encode\r\n";
$_templateDecode = "%s [%s]: %s time to decode\r\n";

set_time_limit(0);

$_output = '';

/**
 * Set the source arrays
 */
$_allTestData = array(
    'str' =&amp;gt; $_testStrings,
    'int' =&amp;gt; $_testIntegers,
    'bln' =&amp;gt; $_testBooleans,
    'flt' =&amp;gt; $_testFloats,
    'mix' =&amp;gt; $_testMixed,
    'obj' =&amp;gt; $_testObjects,
);

$_testSources = array(
    'strings'  =&amp;gt; $_testStrings,
    'integers' =&amp;gt; $_testIntegers,
    'booleans' =&amp;gt; $_testBooleans,
    'floats'   =&amp;gt; $_testFloats,
    'mixed'    =&amp;gt; $_testMixed,
    'objects'  =&amp;gt; $_testObjects,
    'all'      =&amp;gt; $_allTestData,
);
 
/**
 * ENCODE DATA
 */

/**
 * Start each test
 */
foreach ($_testSources as $_area =&amp;gt; $_source)
{
    /**
     * Start the timer
     */
    $_serializeStart = microtime(TRUE);

    for ($_counter = 0; $_counter &amp;lt; $_maxLoop; $_counter++)
    {
        serialize($_source);
    }

    $_serializeEnd = microtime(TRUE);

    $_serializeOutput = serialize($_source);

    $_output .= sprintf(
        $_templateEncode,
        'serialize()', 
        $_area, 
        strlen($_serializeOutput), 
        $_serializeEnd - $_serializeStart
    );
 
    /**
     * JSON
     */
    $_jsonStart = microtime(TRUE);

    for ($_counter = 0; $_counter &amp;lt; $_maxLoop; $_counter++)
    {
        json_encode($_source);
    }

    $_jsonEnd = microtime(TRUE);

    $_jsonOutput = json_encode($_source);

    $_output .= sprintf(
        $_templateEncode,
        'json_encode()', 
        $_area, 
        strlen($_jsonOutput), 
        $_jsonEnd - $_jsonStart
    );
 
    /**
     * igbinary
     */
    $_igbinaryStart = microtime(TRUE);

    for ($_counter = 0; $_counter &amp;lt; $_maxLoop; $_counter++)
    {
        igbinary_serialize($_source);
    }

    $_igbinaryEnd = microtime(TRUE);

    $_igbinaryOutput = igbinary_serialize($_source);

    $_output .= sprintf(
        $_templateEncode,
        'igbinary_serialize()', 
        $_area, 
        strlen($_igbinaryOutput), 
        $_igbinaryEnd - $_igbinaryStart
    );

    $_output .= str_repeat('=', 20) . "\r\n";
}

$_output .= str_repeat('=:=', 20) . "\r\n";


/**
 * DECODE DATA
 */

/**
 * Start each test
 */
foreach ($_testSources as $_area =&amp;gt; $_source)
{
    /**
     * Start the timer
     */
    $_data = serialize($_source);

    $_serializeStart = microtime(TRUE);

    for ($_counter = 0; $_counter &amp;lt; $_maxLoop; $_counter++)
    {
        unserialize($_data);
    }

    $_serializeEnd = microtime(TRUE);

    $_output .= sprintf(
        $_templateDecode,
        'unserialize()', 
        $_area, 
        $_serializeEnd - $_serializeStart
    );

    /**
     * JSON
     */
    $_data = json_encode($_source);

    $_jsonStart = microtime(TRUE);

    for ($_counter = 0; $_counter &amp;lt; $_maxLoop; $_counter++)
    {
        json_decode($_data, TRUE);
    }

    $_jsonEnd = microtime(TRUE);

    $_jsonOutput = json_encode($_source);

    $_output .= sprintf(
        $_templateDecode,
        'json_decode()', 
        $_area, 
        $_jsonEnd - $_jsonStart
    );

    /**
     * igbinary
     */
    $_data = igbinary_serialize($_source);

    $_igbinaryStart = microtime(TRUE);

    for ($_counter = 0; $_counter &amp;lt; $_maxLoop; $_counter++)
    {
        igbinary_unserialize($_data);
    }

    $_igbinaryEnd = microtime(TRUE);

    $_igbinaryOutput = igbinary_serialize($_source);

    $_output .= sprintf(
        $_templateDecode,
        'igbinary_unserialize()', 
        $_area, 
        $_igbinaryEnd - $_igbinaryStart
    );

    $_output .= str_repeat('=', 20) . "\r\n";
}
 
echo '&amp;lt;pre&amp;gt;' . $_output . '&amp;lt;/pre&amp;gt;';
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Serializing results&lt;/b&gt;&lt;br /&gt;
When serializing data we are always concerned about the size of the result but also about the time it took for the data to be serialized.&lt;br /&gt;
&lt;br /&gt;
As far as size is concerned, I have highlighted in bold the winner of each test. json_encode seems to be producing the smallest result in bytes for most of the tests.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Size comparison&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://chart.apis.google.com/chart?chxr=0,0,1600&amp;amp;chxt=y&amp;amp;chbh=a&amp;amp;chs=400x205&amp;amp;cht=bvg&amp;amp;chco=A2C180,3D7930,FF9900&amp;amp;chds=5,1605,5,1605,0,1600&amp;amp;chd=t:105,121,62,709,178,326,1567|67,34,39,77,54,148,478|64,58,27,142,50,177,478&amp;amp;chdl=serialize|json_encode|igbinary&amp;amp;chtt=Size+Comparison" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="164" src="http://chart.apis.google.com/chart?chxr=0,0,1600&amp;amp;chxt=y&amp;amp;chbh=a&amp;amp;chs=400x205&amp;amp;cht=bvg&amp;amp;chco=A2C180,3D7930,FF9900&amp;amp;chds=5,1605,5,1605,0,1600&amp;amp;chd=t:105,121,62,709,178,326,1567|67,34,39,77,54,148,478|64,58,27,142,50,177,478&amp;amp;chdl=serialize|json_encode|igbinary&amp;amp;chtt=Size+Comparison" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;b&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;
&lt;i&gt;Strings
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [strings]: Size: 105 bytes, 1.8710339069366 time to encode
json_encode() [strings]: Size: 67 bytes, 1.5691390037537 time to encode
&lt;b&gt;igbinary_serialize() [strings]: Size: 64 bytes, 3.2276048660278 time to encode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Integers&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [integers]: Size: 121 bytes, 3.0198090076447 time to encode
&lt;b&gt;json_encode() [integers]: Size: 34 bytes, 1.2248229980469 time to encode&lt;/b&gt;
igbinary_serialize() [integers]: Size: 58 bytes, 2.2877519130707 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Booleans&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [booleans]: Size: 62 bytes, 2.0834550857544 time to encode
json_encode() [booleans]: Size: 39 bytes, 1.0889070034027 time to encode
&lt;b&gt;igbinary_serialize() [booleans]: Size: 27 bytes, 1.8252439498901 time to encode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Floats&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [floats]: Size: 709 bytes, 27.496570825577 time to encode
&lt;b&gt;json_encode() [floats]: Size: 77 bytes, 5.0476500988007 time to encode&lt;/b&gt;
igbinary_serialize() [floats]: Size: 142 bytes, 2.4856028556824 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Mixed
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [mixed]: Size: 178 bytes, 6.301619052887 time to encode
json_encode() [mixed]: Size: 54 bytes, 2.0463008880615 time to encode
&lt;b&gt;igbinary_serialize() [mixed]: Size: 50 bytes, 2.3894169330597 time to encode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Objects&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [objects]: Size: 326 bytes, 4.8698291778564 time to encode
&lt;b&gt;json_encode() [objects]: Size: 148 bytes, 2.4744520187378 time to encode&lt;/b&gt;
igbinary_serialize() [objects]: Size: 177 bytes, 6.472992181778 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;All data types&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [all]: Size: 1567 bytes, 42.437592029572 time to encode
&lt;b&gt;json_encode() [all]: Size: 462 bytes, 9.9569129943848 time to encode&lt;/b&gt;
igbinary_serialize() [all]: Size: 478 bytes, 18.053789138794 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Speed comparison&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://chart.apis.google.com/chart?chxr=0,0,50&amp;amp;chxt=y&amp;amp;chbh=a&amp;amp;chs=400x205&amp;amp;cht=bvg&amp;amp;chco=A2C180,3D7930,FF9900&amp;amp;chds=1.87,50,0,50,0,50&amp;amp;chd=t:1.87,3.02,2.08,27.5,6.3,4.87,42.44|1.57,1.22,1.09,5.05,2.05,2.47,9.96|3.23,2.29,1.82,2.48,2.39,6.47,18.05&amp;amp;chdl=serialize|json_encode|igbinary&amp;amp;chtt=Speed+Comparison&amp;amp;chts=676767,10.5" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="164" src="http://chart.apis.google.com/chart?chxr=0,0,50&amp;amp;chxt=y&amp;amp;chbh=a&amp;amp;chs=400x205&amp;amp;cht=bvg&amp;amp;chco=A2C180,3D7930,FF9900&amp;amp;chds=1.87,50,0,50,0,50&amp;amp;chd=t:1.87,3.02,2.08,27.5,6.3,4.87,42.44|1.57,1.22,1.09,5.05,2.05,2.47,9.96|3.23,2.29,1.82,2.48,2.39,6.47,18.05&amp;amp;chdl=serialize|json_encode|igbinary&amp;amp;chtt=Speed+Comparison&amp;amp;chts=676767,10.5" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;b&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;
Analyzing the time it took for each test to be completed, we see again that json_encode is the clear winner (highlighted in bold the shortest time for the function).&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Strings&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [strings]: Size: 105 bytes, 1.8710339069366 time to encode
&lt;b&gt;json_encode() [strings]: Size: 67 bytes, 1.5691390037537 time to encode&lt;/b&gt;
igbinary_serialize() [strings]: Size: 64 bytes, 3.2276048660278 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Integers&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [integers]: Size: 121 bytes, 3.0198090076447 time to encode
&lt;b&gt;json_encode() [integers]: Size: 34 bytes, 1.2248229980469 time to encode&lt;/b&gt;
igbinary_serialize() [integers]: Size: 58 bytes, 2.2877519130707 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Booleans&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [booleans]: Size: 62 bytes, 2.0834550857544 time to encode
&lt;b&gt;json_encode() [booleans]: Size: 39 bytes, 1.0889070034027 time to encode&lt;/b&gt;
igbinary_serialize() [booleans]: Size: 27 bytes, 1.8252439498901 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Floats&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [floats]: Size: 709 bytes, 27.496570825577 time to encode
json_encode() [floats]: Size: 77 bytes, 5.0476500988007 time to encode
&lt;b&gt;igbinary_serialize() [floats]: Size: 142 bytes, 2.4856028556824 time to encode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Mixed&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [mixed]: Size: 178 bytes, 6.301619052887 time to encode
&lt;b&gt;json_encode() [mixed]: Size: 54 bytes, 2.0463008880615 time to encode&lt;/b&gt;
igbinary_serialize() [mixed]: Size: 50 bytes, 2.3894169330597 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Objects&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [objects]: Size: 326 bytes, 4.8698291778564 time to encode
&lt;b&gt;json_encode() [objects]: Size: 148 bytes, 2.4744520187378 time to encode&lt;/b&gt;
igbinary_serialize() [objects]: Size: 177 bytes, 6.472992181778 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;All data types&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;serialize() [all]: Size: 1567 bytes, 42.437592029572 time to encode
&lt;b&gt;json_encode() [all]: Size: 462 bytes, 9.9569129943848 time to encode&lt;/b&gt;
igbinary_serialize() [all]: Size: 478 bytes, 18.053789138794 time to encode
&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Combination&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
Having the smallest result in size might not always be the best metric to base the choice of the serialization algorithm. For instance, looking at the results above in the Strings test, igbinary produces indeed the smallest result in size (64 bytes) but it takes twice as much to serialize the result in comparison to json_encode (3.22 vs. 1.56 seconds) and the size difference is a mere 3 bytes (64 vs. 67).&lt;br /&gt;
&lt;br /&gt;
Similarly, for the Boolean test, igbinary produces 27 bytes and json_encode 39 bytes. It does however take igbinary nearly 80% more time to produce the result compared to json_encode.&lt;br /&gt;
&lt;br /&gt;
For the Floats test the situation is reversed. json_encode produces a result that is around 50% smaller than the one of igbinary but it takes twice as much time to produce it.&lt;br /&gt;
&lt;br /&gt;
As far as serializing data, in my personal opinion, json_encode is the clear winner.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Unserializing Results&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Unserializing data is equally - and at times - more important than serializing. In many applications, developers sacrifice performance in writing but don't compromise when reading data.&lt;br /&gt;
&lt;br /&gt;
In the tests below (shortest time highlighted in bold) once can easily see that igbinary is the clear winner. At times the unserialize function is very close (or outperforms igbinary) but overall, igbinary is the the function that unserializes data the fastest.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Speed comparison&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://chart.apis.google.com/chart?chxr=0,0,50&amp;amp;chxt=y&amp;amp;chbh=a&amp;amp;chs=400x205&amp;amp;cht=bvg&amp;amp;chco=A2C180,3D7930,FF9900&amp;amp;chds=0,35,0,35,0,35&amp;amp;chd=t:1.82,2.39,1.81,18.51,4.68,5.54,31.01|2.65,2.86,2.44,3.79,2.78,5.77,14.57|1.83,2.44,1.76,2.67,1.96,5.27,10.73&amp;amp;chdl=serialize|json_encode|igbinary&amp;amp;chtt=Speed+Comparison&amp;amp;chts=676767,10.5" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="164" src="http://chart.apis.google.com/chart?chxr=0,0,50&amp;amp;chxt=y&amp;amp;chbh=a&amp;amp;chs=400x205&amp;amp;cht=bvg&amp;amp;chco=A2C180,3D7930,FF9900&amp;amp;chds=0,35,0,35,0,35&amp;amp;chd=t:1.82,2.39,1.81,18.51,4.68,5.54,31.01|2.65,2.86,2.44,3.79,2.78,5.77,14.57|1.83,2.44,1.76,2.67,1.96,5.27,10.73&amp;amp;chdl=serialize|json_encode|igbinary&amp;amp;chtt=Speed+Comparison&amp;amp;chts=676767,10.5" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;b&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;
&lt;i&gt;Strings&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;&lt;b&gt;unserialize() [strings]: 1.8259189128876 time to decode&lt;/b&gt;
json_decode() [strings]: 2.6482670307159 time to decode
igbinary_unserialize() [strings]: 1.8359968662262 time to decode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Integers
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;&lt;b&gt;unserialize() [integers]: 2.3886890411377 time to decode&lt;/b&gt;
json_decode() [integers]: 2.8659090995789 time to decode
igbinary_unserialize() [integers]: 2.4441809654236 time to decode
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Booleans
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;unserialize() [booleans]: 1.8097970485687 time to decode
json_decode() [booleans]: 2.4416139125824 time to decode
&lt;b&gt;igbinary_unserialize() [booleans]: 1.7585029602051 time to decode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Floats
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;unserialize() [floats]: 18.512004137039 time to decode
json_decode() [floats]: 3.7896130084991 time to decode
&lt;b&gt;igbinary_unserialize() [floats]: 2.6730649471283 time to decode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Mixed
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;unserialize() [mixed]: 4.6794769763947 time to decode
json_decode() [mixed]: 2.7775249481201 time to decode
&lt;b&gt;igbinary_unserialize() [mixed]: 1.9598047733307 time to decode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Objects
&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;unserialize() [objects]: 5.5468521118164 time to decode
json_decode() [objects]: 5.7660481929779 time to decode
&lt;b&gt;igbinary_unserialize() [objects]: 5.2672090530396 time to decode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;All data types&lt;/i&gt;&lt;br /&gt;
&lt;pre&gt;unserialize() [all]: 31.01339006424 time to decode
json_decode() [all]: 14.574991941452 time to decode
&lt;b&gt;igbinary_unserialize() [all]: 10.734386920929 time to decode&lt;/b&gt;
&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
If your application is mostly focused on reads rather than writes, igbinary is the clear winner, since it will unserialize your data faster than the other two functions. If however you are more focused on storing data, json_encode is the clear choice.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;2013-03-07&lt;/b&gt;: memcached was not used with igbinary. PHP version for tests was 5.3.1 on a Linux Mint machine with 6GB RAM&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/wtPd4Mf9yaM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/5929435911408963024/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/11/fast-serialization-of-data-in-php-how.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/5929435911408963024?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/5929435911408963024?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/wtPd4Mf9yaM/fast-serialization-of-data-in-php-how.html" title="Fast serialization of data in PHP [How-To][Performance]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-fKGt9OWGp7U/TswyoFFCBRI/AAAAAAAAMcY/9-cXDD5NCw0/s72-c/sleep-wakeup-in-php.jpg" height="72" width="72" /><thr:total>2</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/11/fast-serialization-of-data-in-php-how.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAFQ3g7fip7ImA9WhJbFks.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-21020081014996638</id><published>2011-11-13T13:45:00.001-05:00</published><updated>2012-09-26T09:38:32.606-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-26T09:38:32.606-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Programming" /><category scheme="http://www.blogger.com/atom/ns#" term="Git" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>Git pre-commit - Another check to ensure clean code [How-To]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-9aU96_99ZbE/TsAeRgLvRdI/AAAAAAAAGkU/FwdPWCvLFUQ/s1600/git-logo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-9aU96_99ZbE/TsAeRgLvRdI/AAAAAAAAGkU/FwdPWCvLFUQ/s1600/git-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;
Throughout my career I have been using various &lt;a href="http://en.wikipedia.org/wiki/Revision_control"&gt;revision control systems&lt;/a&gt;. I started off with Visual SourceSafe which I thought at the time was great, primarily because of the small size of our team and the ease of use though the Visual Basic's IDE.&lt;br /&gt;
&lt;br /&gt;
Later on I switched to SVN which is a great version control system and it fulfilled all my needs for proper version control.&lt;br /&gt;
&lt;br /&gt;
When I moved to the US and started working here, I introduced SVN to the company that I was working at the time. We adopted the technique of having one branch per project, since we were working on different projects at any given time. Using the plugin through Eclipse or in Windows the integrated Explorer plugin, I was checking in code, switching through projects etc.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.niden.net/2011/11/new-beginnings-sleep6566400.html"&gt;Recently&lt;/a&gt;&amp;nbsp;I changed jobs and although we did use Subversion for a few months, we did switch to Git. The reasons behind it are too many to count - and I will go through them in another blog post.&lt;br /&gt;
&lt;br /&gt;
One really good feature that Git has is its hooks. Although Subversion also supports hooks, I have only been exposed to the ones from Git and have used them - hence this blog post.&lt;br /&gt;
&lt;br /&gt;
Git comes with some predefined hooks that one can use as a starting point. The code is checked before it is being committed and if it does pass whatever the pre-commit (for instance) hook does, it will allow you to commit; alternatively it will stop until you correct the mistakes made.&lt;br /&gt;
&lt;br /&gt;
One very popular pre-commit hook is the one &lt;a href="https://github.com/ReekenX/git-php-syntax-checker"&gt;here&lt;/a&gt; by Remigijus Jarmalavičius. It checkes the files that have been modified/added and runs the php -l on it to ensure that whatever will be committed does not have PHP syntax errors.&lt;br /&gt;
&lt;br /&gt;
I have downloaded that code and easily added it to my repository so everything is being checked prior to any of my commits.&lt;br /&gt;
&lt;br /&gt;
In our &lt;a href="http://www.memberfuse.com/"&gt;MemberFuse™&lt;/a&gt; platform, we have - like any other developer - many helper functions that are used solely for development. One of the mostly used one is the vdd($message); Effectively what this function does is a var_dump of the $message variable, echoes out the file that it was called as well as the line it is in, and dies. It is great for quick and dirty debugging and inspecting a variable as it passes through the code. Granted it is not very TDD but I am sure that all developers have little things like these to aid them with their daily programming and debugging.&lt;br /&gt;
&lt;br /&gt;
From time to time, as we fix bugs and explore certain behaviors that sometimes it is difficult to assess, we use this function and we output the data on the browser. However this is just a development/debugging function and must never be used in the production environment.&lt;br /&gt;
&lt;br /&gt;
I have been the culprit of using the function, forgot that I had it in a certain part of the code, and committed that code in our development branch. That caused some colleagues to experience inconsistent behavior with errors showing up on the screen and time was wasted.&lt;br /&gt;
&lt;br /&gt;
To combat this scenario, I wrote a pre-commit hook that will allow you to check for the existence of certain functions in the code and ensure that those functions (or strings for that matter since I am using &lt;a href="http://en.wikipedia.org/wiki/Grep"&gt;grep&lt;/a&gt;) do not exist in what is to be committed.&lt;br /&gt;
&lt;br /&gt;
The pre-commit hook that I wrote is listed below and the code has been heavily based on &lt;span style="background-color: transparent;"&gt;Remigijus Jarmalavičius's &lt;a href="https://github.com/ReekenX/git-php-syntax-checker"&gt;pre-commit syntax checker&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# Author: Nikolaos Dimopoulos &lt;nikos niden.net="niden.net"&gt;
# Based on code by Remigijus Jarmalavičius &lt;remigijus jarmalavicius.lt="jarmalavicius.lt"&gt; 
# Checks the files to be committed for the presence of print_r(), 
# var_dump(), die()
# The array below can be extended for further checks

checks[1]="var_dump("
checks[2]="print_r("
checks[3]="die"

element_count=${#checks[@]}
let "element_count = $element_count + 1"

ROOT_DIR="$(pwd)/"
LIST=$(git status | grep -e '\#.*\(modified\|added\)')
ERRORS_BUFFER=""
for file in $LIST
do
    if [ "$file" == '#' ]; then
        continue
    fi
    if [ $(echo "$file" | grep 'modified') ]; then
        FILE_ACTION="modified"
    elif [ $(echo "$file" | grep 'added') ]; then
        FILE_ACTION="added"
    else 
        EXTENSION=$(echo "$file" | grep ".php$")
        if [ "$EXTENSION" != "" ]; then

            index=1
            while [ "$index" -lt "$element_count" ]
            do
                echo "Checking $FILE_ACTION file: $file [${checks[$index]}]" 
                ERRORS=$(grep "${checks[$index]}" $ROOT_DIR$file &amp;gt;&amp;amp;1)
                if [ "$ERRORS" != "" ]; then
                    if [ "$ERRORS_BUFFER" != "" ]; then
                        ERRORS_BUFFER="$ERRORS_BUFFER\n$ERRORS"
                    else
                        ERRORS_BUFFER="$ERRORS"
                    fi
                    echo "${checks[$index]} found in file: $file "
                fi
                let "index = $index + 1"
            done
        fi
    fi
done
if [ "$ERRORS_BUFFER" != "" ]; then
    echo 
    echo "These errors were found in try-to-commit files: "
    echo -e $ERRORS_BUFFER
    echo 
    echo "Can't commit, fix errors first."
    exit 1
else
    echo "Commited successfully."
fi
&lt;/remigijus&gt;&lt;/nikos&gt;&lt;/code&gt;&lt;/pre&gt;
If you want to check for any kind of string, just add one extra line (or modify the existing ones) of the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;checks&lt;/span&gt; array. The code will loop through the array and 'grep' the modified/added files for that entry. If it is found the commit will not be allowed and you will have to manually go and change whatever needs to be changed.&lt;br /&gt;
&lt;br /&gt;
I hope you find this hook useful. You can download the file from my &lt;a href="https://github.com/niden/Git-Pre-Commit-Hook-for-certain-words"&gt;github repository&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Note&lt;/b&gt;: If you wish to run more than one pre-commit hooks, you don't need to merge them all in the same file. You can create a pre-commit file which will have the following contents:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &amp;amp;&amp;amp; pwd )"

$DIR/pre-commit.l
$DIR/pre-commit.output

&lt;/code&gt;&lt;/pre&gt;
The *.l file is the one that runs the PHP syntax check, while the *.output one is the one mentioned in this blog post. You can extend the list to your liking and usage.&lt;/div&gt;
&lt;script&gt;format()&lt;/script&gt;&lt;img src="http://feeds.feedburner.com/~r/niden/~4/I3Dzf2UKCK0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/21020081014996638/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/11/git-pre-commit-another-check-to-ensure.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/21020081014996638?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/21020081014996638?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/I3Dzf2UKCK0/git-pre-commit-another-check-to-ensure.html" title="Git pre-commit - Another check to ensure clean code [How-To]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-9aU96_99ZbE/TsAeRgLvRdI/AAAAAAAAGkU/FwdPWCvLFUQ/s72-c/git-logo.png" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/11/git-pre-commit-another-check-to-ensure.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0INR3c4cCp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-9126896630390907927</id><published>2011-11-11T11:11:00.000-05:00</published><updated>2012-09-27T14:13:16.938-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:13:16.938-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Personal" /><category scheme="http://www.blogger.com/atom/ns#" term="Update" /><title>New beginnings – sleep(6566400) [Personal][Update]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-3S214dx8LTA/Tr1uVH87WjI/AAAAAAAAFu0/21RSIwRGjjc/s1600/update.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="176" src="http://4.bp.blogspot.com/-3S214dx8LTA/Tr1uVH87WjI/AAAAAAAAFu0/21RSIwRGjjc/s200/update.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
It has been a very long time since I last blogged.&lt;br /&gt;
&lt;br /&gt;
The primary reason is that I have been busy and somewhat lazy. Hopefully that trend (the laziness) will not last that much and I will be able to post in higher frequency.
&lt;br /&gt;
&lt;br /&gt;
The biggest update for me came this July. I was actively searching for a new employment opportunity for quite some time, but due to the current market situation, it has been a really difficult task.&lt;br /&gt;
&lt;br /&gt;
In July I received (and accepted) an offer from &lt;a href="http://www.avectra.com/"&gt;Avectra Inc.&lt;/a&gt; I am now a PHP developer for &lt;a href="http://www.memberfuse.com/"&gt;MemberFuse™&lt;/a&gt;, a professional networking application that integrates with the client's association management software and builds an online community for that client.
&lt;br /&gt;
&lt;br /&gt;
My colleagues and I are programming in PHP using a frameworks such as &lt;a href="http://www.doctrine-project.org/"&gt;Doctrine&lt;/a&gt;, &lt;a href="http://framework.zend.com/"&gt;Zend Framework&lt;/a&gt;, &lt;a href="http://www.smarty.net/"&gt;Smarty&lt;/a&gt; and &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; on the client side. The application we produce is offered as SAAS (Software As A Service) and we have well over 1 million users.&lt;br /&gt;
&lt;br /&gt;
Although adjusting to the new environment was rough and the learning curve was steep (it didn't help that one colleague decided to resign and I inherited his workload from week 2), I managed to 'survive' and I am more and more confident every day.&lt;br /&gt;
&lt;br /&gt;
I can only say that working with smart people that push you to excel is a blessing!&lt;br /&gt;
&lt;br /&gt;
The second update is this blog's location. With the introduction of the new dynamic templates in Blogger, I decided to move from Wordpress (and my personal installation for my blog) to Blogger.&lt;br /&gt;
&lt;br /&gt;
The downside was that Blogger could not import my blog. I would always get errors when trying to upload the XML file and after a few tries I decided to go the manual way. I therefore sat down and copied and pasted all the content that I had posted in the past to the new platform.&lt;br /&gt;
&lt;br /&gt;
Luckily it was not a very difficult task, but it was time consuming. Last night I managed to finish everything and write this blog post.&lt;br /&gt;
&lt;br /&gt;
With this move unfortunately I lost all the comments on my posts and I am sure that there will be some broken links here and there but in the end it will all work out.&lt;br /&gt;
&lt;br /&gt;
It was my intention to post this message today (11-11-11) at 11:11 but I kinda messed up the scheduling &amp;nbsp;so the post is being posted a few hours later.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/C8MjS-Qx4A8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/9126896630390907927/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/11/new-beginnings-sleep6566400.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/9126896630390907927?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/9126896630390907927?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/C8MjS-Qx4A8/new-beginnings-sleep6566400.html" title="New beginnings – sleep(6566400) [Personal][Update]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-3S214dx8LTA/Tr1uVH87WjI/AAAAAAAAFu0/21RSIwRGjjc/s72-c/update.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/11/new-beginnings-sleep6566400.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0ENSHo7fyp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-8166191078739771994</id><published>2011-05-18T14:49:00.000-04:00</published><updated>2012-09-27T14:14:59.407-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:14:59.407-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Gentoo" /><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="Portage" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><title>Using autounmask to Unmask Packages in Gentoo [How-To][Gentoo]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-1OZGvZ5rtP4/Tr18np_OHhI/AAAAAAAAFww/I6sVo1bD_7o/s1600/gentoo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://3.bp.blogspot.com/-1OZGvZ5rtP4/Tr18np_OHhI/AAAAAAAAFww/I6sVo1bD_7o/s200/gentoo.png" width="191" /&gt;&lt;/a&gt;&lt;/div&gt;
Gentoo is one of my favorite Linux distributions. Although I am comfortable with other distributions, Gentoo has a special place in my heart and whenever I can use it I do :)
&lt;br /&gt;
&lt;br /&gt;
There are however some times that I would like to install a package - mostly to test something - and the package is masked. Masked packages are not "production ready" so they are not included in the portage tree i.e. available to be installed.
&lt;br /&gt;
&lt;br /&gt;
To allow a masked package to be installed, you will need to unmask that package by adding a corresponding entry in the etc/portage/package.keywords file.
&lt;br /&gt;
&lt;br /&gt;
The problem happens when the masked package (that you just unmasked) depends on other packages that are also masked. You will then need to rinse and repeat the process to ensure that everything is in place so that you can install that unmasked package.
&lt;br /&gt;
&lt;blockquote&gt;
&lt;strong&gt;NOTE: Playing with masked packages is like playing with fire. If you don't know what you are doing or you are not ready to potentially have an unusable system, don't follow the instructions below or unmask any packages.&lt;/strong&gt;&lt;/blockquote&gt;
&lt;span style="background-color: transparent;"&gt;&lt;b&gt;app-portage/autounmask&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="background-color: transparent;"&gt;The Gentoo developers have created a little utility that will unmask each package that needs to be unmasked. The utility is &lt;/span&gt;&lt;strong style="background-color: transparent;"&gt;app-portage/autounmask&lt;/strong&gt;&lt;span style="background-color: transparent;"&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
I wanted to unmask www-misc/fcgiwrap so my manual method would be:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash"&gt;echo "=www-misc/fcgiwrap ~amd64" &amp;gt;&amp;gt; /etc/portage/packages.keywords&lt;/pre&gt;
&lt;br /&gt;
and would then emerge the package
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash"&gt;emerge =www-misc/fcgiwrap&lt;/pre&gt;
&lt;br /&gt;
Instead I used autounmask:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash"&gt;emerge app-portage/autounmask&lt;/pre&gt;
&lt;br /&gt;
and
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash"&gt;autounmask www-misc/fcgiwrap-1.0.3

autounmask version 0.27 (using PortageXS-0.02.09 and portage-2.1.9.42)
* Using repository: /usr/portage
* Using package.keywords file: /etc/portage/package.keywords&amp;nbsp;* Using package.unmask file: /etc/portage/package.unmask&amp;nbsp;* Using package.use file: /etc/portage/package.use
* Unmasking www-misc/fcgiwrap-1.0.3 and its dependencies.. this might take a while..
* Added '=www-misc/fcgiwrap-1.0.3 ~amd64' to /etc/portage/package.keywords&amp;nbsp;* done!&lt;/pre&gt;
&lt;br /&gt;
Once that is done I can issue the emerge command and voila!
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash"&gt;emerge www-misc/fcgiwrap&lt;/pre&gt;
&lt;br /&gt;
Although in my case there was only one dependency to unmask, when trying to unmask packages that have multiple dependencies such as gnome, kde etc., autounmask can be a very helpful utility.&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/J1jcog_W3x4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/8166191078739771994/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/05/using-autounmask-to-unmask-packages-in.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/8166191078739771994?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/8166191078739771994?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/J1jcog_W3x4/using-autounmask-to-unmask-packages-in.html" title="Using autounmask to Unmask Packages in Gentoo [How-To][Gentoo]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-1OZGvZ5rtP4/Tr18np_OHhI/AAAAAAAAFww/I6sVo1bD_7o/s72-c/gentoo.png" height="72" width="72" /><thr:total>2</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/05/using-autounmask-to-unmask-packages-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AFQ3c8eSp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-2273927751238789661</id><published>2011-04-21T14:45:00.000-04:00</published><updated>2012-09-27T14:15:12.971-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:15:12.971-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="Ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="Upgrade" /><category scheme="http://www.blogger.com/atom/ns#" term="LibreOffice" /><title>LibreOffice Auto Update [How-To][Linux]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-Hl6wkIBMeL4/Tr174qhF0vI/AAAAAAAAFwo/b1Pmc_E83qo/s1600/libreoffice-3.3RC.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="63" src="http://2.bp.blogspot.com/-Hl6wkIBMeL4/Tr174qhF0vI/AAAAAAAAFwo/b1Pmc_E83qo/s200/libreoffice-3.3RC.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
We all knew it was coming. Ever since &lt;a href="http://www.oracle.com/"&gt;Oracle&lt;/a&gt; took a really surprising turn towards the non-community orientation of &lt;a href="http://www.openoffice.org/"&gt;OpenOffice&lt;/a&gt;, a lot of users on the Internet were curious (and concerned) as to what will happen with the whole OpenOffice saga.
&lt;br /&gt;
&lt;br /&gt;
Thankfully the project was forked and &lt;a href="http://www.libreoffice.org/"&gt;LibreOffice&lt;/a&gt; was born. Within a few months the same (and better) quality product was released for the community. As the project entered its alpha stage, then the beta stage and then the RC stages, I was waiting anxiously to install it on my notebook (which is running &lt;a href="http://www.ubuntu.com/"&gt;Ubuntu&lt;/a&gt; 10.10 at the moment).
&lt;br /&gt;
&lt;br /&gt;
A couple of months ago, I was finally happy with the package and the release, so I downloaded it on my notebook and started the installation. I don't have a problem with getting my hands dirty and working with the command prompt, but I am lazy - so I want my packages to update easily (say though the package manager). At the time I could not use the package manager, so it was all terminal work :)
&lt;br /&gt;
&lt;br /&gt;
The installation was pretty easy using the &lt;strong&gt;dpkg&lt;/strong&gt; command. I run literally two commands and the application was installed :)
&lt;br /&gt;
&lt;br /&gt;
There was a recent announcement about OpenOffice from Oracle. I posted a link at &lt;a href="http://news.ycombinator.com/item?id=2451079"&gt;HackerNews&lt;/a&gt; for an article I read in MarketWire, regarding this "&lt;em&gt;Oracle to move OpenOffice.org to a Community-Based Project&lt;/em&gt;". Unfortunately the link does not work any more but you might be able to read a bit about the subject &lt;a href="http://www.pcworld.com/businesscenter/article/225459/oracles_openoffice_move_may_be_too_little_too_late.html"&gt;here&lt;/a&gt;. In short:
&lt;br /&gt;
&lt;div style="text-align: center;"&gt;
&lt;em&gt;OpenOffice is dead, long live LibreOffice&lt;/em&gt;.&lt;/div&gt;
&lt;br /&gt;
Time for me to start getting serious about updating LibreOffice.
&lt;br /&gt;
&lt;br /&gt;
First of all I wanted to make sure that I did not have any cruft remaining from OpenOffice on my system.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;sudo apt-get remove openoffice*.*&lt;/pre&gt;
&lt;br /&gt;
Surprisingly there were some packages that were still there.
&lt;br /&gt;
&lt;br /&gt;
The second step was to uninstall the current version of LibreOffice
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;sudo apt-get remove libreoffice*.*&lt;/pre&gt;
&lt;br /&gt;
And now comes the easy part. I am going to use a PPA, to ensure that I will get notified about pending updates and upgrade easily. To do so all I had to do was run the following commands on a terminal window:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;sudo add-apt-repository ppa:libreoffice/ppa&lt;/pre&gt;
&lt;br /&gt;
Output:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;Executing: gpg --ignore-time-conflict --no-options --no-default-keyring
--secret-keyring /etc/apt/secring.gpg --trustdb-name /etc/apt/trustdb.gpg
--keyring /etc/apt/trusted.gpg --primary-keyring /etc/apt/trusted.gpg
--keyserver keyserver.ubuntu.com --recv 36E81C9267FD1383FCC4490983FBA1751378B444gpg:
requesting key 1378B444 from hkp server keyserver.ubuntu.com
gpg: key 1378B444: public key "Launchpad PPA for LibreOffice Packaging" imported
gpg: no ultimately trusted keys found
gpg: Total number processed: 1
gpg: &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; imported: 1 &amp;nbsp;(RSA: 1)&lt;/pre&gt;
&lt;br /&gt;
Then update the packages again:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;sudo apt-get update&lt;/pre&gt;
&lt;br /&gt;
And now install LibreOffice:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;sudo apt-get install libreoffice
sudo apt-get install libreoffice-gnome&lt;/pre&gt;
&lt;br /&gt;
Note: KUbuntu users will need to run
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;sudo apt-get install libreoffice-kde&lt;/pre&gt;
&lt;br /&gt;
For those that prefer the graphical interface, all you will have to do is add "ppa:libreoffice/ppa" to your software sources.
&lt;br /&gt;
&lt;br /&gt;
Enjoy your LibreOffice installation!&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/Eb_Xsr7Doyw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/2273927751238789661/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/04/libreoffice-auto-update-how-tolinux.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2273927751238789661?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2273927751238789661?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/Eb_Xsr7Doyw/libreoffice-auto-update-how-tolinux.html" title="LibreOffice Auto Update [How-To][Linux]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-Hl6wkIBMeL4/Tr174qhF0vI/AAAAAAAAFwo/b1Pmc_E83qo/s72-c/libreoffice-3.3RC.png" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/04/libreoffice-auto-update-how-tolinux.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AGQncyfip7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-1174702717697844693</id><published>2011-04-04T14:41:00.000-04:00</published><updated>2012-09-27T14:15:23.996-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:15:23.996-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Hosting" /><category scheme="http://www.blogger.com/atom/ns#" term="Series" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="HA" /><title>Building a HA Cluster on Linode [How-To][HA][Linux][Series]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-bYOhqaAnq5w/Tr17S7nW2kI/AAAAAAAAFwg/whtxx9Pf5N8/s1600/high_vailability.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="102" src="http://4.bp.blogspot.com/-bYOhqaAnq5w/Tr17S7nW2kI/AAAAAAAAFwg/whtxx9Pf5N8/s200/high_vailability.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
I have recently blogged about &lt;a href="http://www.niden.net/2011/03/slicehost-vs-linode/"&gt;Slicehost vs. Linode&lt;/a&gt; and my decision to move my sites to the latter. Since then I can safely say that I made the right choice about the move. &lt;a href="http://l.niden.net/linode"&gt;Linode&lt;/a&gt;'s support is phenomenal. There has never been a ticket unanswered more than 5 minutes and all the tickets have been resolved. Even when I asked about a configuration issue regarding Heartbeat, which was clearly not in the realm of support, the support engineers did look at my configuration and did identify the error area. That alone saved me hours of troubleshooting and trying to find where the error was.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The Task
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: transparent;"&gt;Your website must be available - all the time. Why? Because if you have something to say (a blog, a journal, a rant page, a forum etc.) you need your audience to be able to read your material all the time. Discussion forums, websites with custom applications or services, even information based websites need to have as close to 100% uptime as possible.. How can this be achieved? The solution is a High Availability (or HA) cluster.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Linux has been used for HA tasks for many years. The &lt;a href="http://www.linux-ha.org/wiki/Main_Page"&gt;Linux-HA&lt;/a&gt;'s wiki has a lot of valuable information as well as guides and resources that one can use to increase the redundancy of their websites. In addition to this, Google is your friend. There are numerous bloggers that have shared their experiences with the public on how to create HA resources. Finally, one can check Linode's &lt;a href="http://library.linode.com/"&gt;Library&lt;/a&gt; - a set of guides to allow you to create HA clusters with your Linodes.
&lt;br /&gt;
&lt;br /&gt;
My task for the last few weeks has been to create a High Availability cluster of services to serve PHP and MySQL and also have the ability to grow infinitely (well close to that is). In the next few weeks I will post a series of blog posts outlining how to achieve a HA cluster for your sites.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Architecture
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: transparent;"&gt;The cluster will be build using CentOS as the OS of choice. I have also experimented with Gentoo and Ubuntu. You can do everything I do here with Ubuntu if you wish. There are slight differences in certain commands and steps which the blog posts will not cover. As far as Gentoo is concerned, at the moment there is a block between Pacemaker and Heartbeat. Once that is resolved, I will try to redo the whole thing using Gentoo - as it is my OS of preference.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
We will build two boxes to serve as load balancers. The boxes (or Linodes) will have Heartbeat, Pacemaker and nginx installed on them. A "floating" IP address will be used to move from one node to another in the case of a failure. Each Linode uses nginx as a proxy to forward all requests to a set of web servers using nginx's proxy functionality.
&lt;br /&gt;
&lt;br /&gt;
We will also build a web server. This again is a CentOS box running PHP and nginx. The web server will store the data locally and connect to the database cluster. Once the last part of this How-To is completed (creation of a HA NFS) then we will be able to add as many web servers as we need.
&lt;br /&gt;
&lt;br /&gt;
The next set of boxes form the database cluster. The setup is two servers with Heartbeat, Pacemaker and MySQL setup with a Master/Master replication. Again a "floating" IP address is used to move from one server to another in the case of failure.
&lt;br /&gt;
&lt;br /&gt;
The NFS is also a 2 box setup. Again a "floating" IP address is used to connect to the file system. &lt;a href="http://www.drbd.org/"&gt;DRBD&lt;/a&gt; is installed on them to cater for the replication.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Administration
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: transparent;"&gt;All Linodes are located in the same data center. At the moment there is no way to create Linodes in different data centers and implement the above mentioned setup in an effort to achieve geographical redundancy.&amp;nbsp;The whole setup is using 9 Linodes the last one being used for administrative tasks (note in my count I used 2 boxes as web servers).&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
All Linodes have active iptables configurations. All nodes have been configured to work with 322 as the SSH port to avoid the novice hacker. Every port is blocked from communicating with the Internet apart from the ones needed for essential services. For instance the Load Balancers allow connections on ports 80 and 443 (http and https). However, they only communicate with each other on the Heartbeat port. Equally the web servers do not allow connections to their 80/443 ports to any machine other than the Load Balancers. The Database Servers allow connections only from the Web servers etc.
&lt;br /&gt;
&lt;br /&gt;
The administrative node resides on a different data center. It runs Nagios and it monitors all the nodes of our HA cluster. Naturally the iptables setup of each node is adjusted to allow connections from this particular node.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion
&lt;/b&gt;&lt;br /&gt;
&lt;span style="background-color: transparent;"&gt;I hope that these blog posts will serve as a learning exercise/guide to those who want to delve in High Availability websites. The primary reason I built this cluster is to offer these services to customers that have busy sites that require maximum availability. I am currently setting up the final touches of the hosting service I am going to offer so stay tuned for the details.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;Disclaimer: The Linode link in this blog post is an affiliate one. If you wish to sign up for their services you are more than welcome to click it. The non affiliate link is &lt;a href="https://manager.linode.com/signup/"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/P7vfasO9-dQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/1174702717697844693/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/04/building-ha-cluster-on-linode-how.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/1174702717697844693?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/1174702717697844693?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/P7vfasO9-dQ/building-ha-cluster-on-linode-how.html" title="Building a HA Cluster on Linode [How-To][HA][Linux][Series]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-bYOhqaAnq5w/Tr17S7nW2kI/AAAAAAAAFwg/whtxx9Pf5N8/s72-c/high_vailability.jpg" height="72" width="72" /><thr:total>2</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/04/building-ha-cluster-on-linode-how.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AHRXkyeSp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-8210677066514343368</id><published>2011-03-21T14:10:00.000-04:00</published><updated>2012-09-27T14:15:34.791-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:15:34.791-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Gentoo" /><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="Hosting" /><category scheme="http://www.blogger.com/atom/ns#" term="Information" /><category scheme="http://www.blogger.com/atom/ns#" term="VPS" /><title>Slicehost vs. Linode [Hosting][VPS]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-r1mM0nbKiuk/Tr10IXu7JSI/AAAAAAAAFwI/OtambY19bQg/s1600/VPS.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="166" src="http://2.bp.blogspot.com/-r1mM0nbKiuk/Tr10IXu7JSI/AAAAAAAAFwI/OtambY19bQg/s200/VPS.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
Through the years I have hosted my sites on various hosting companies. I had the really good experiences like &lt;a href="http://www.vertexhost.com/"&gt;Vertexhost&lt;/a&gt; and really terrible ones - I don't remember the name of the host, but that kid, as it turned out to be later on, managed to lose 1.6GB of my data. You can safely say that I have been burned by bad hosting companies but also have enjoyed the services of good ones. In the case of &lt;a href="http://www.vertexhost.com/"&gt;Vertexhost&lt;/a&gt;, I part owned that company a few years back and I know that the current owner is a straight up guy and really cares for his customers.
&lt;br /&gt;
&lt;br /&gt;
Since I moved my emails to &lt;a href="http://www.google.com/a/"&gt;Google Apps&lt;/a&gt; I only need the hosting for my personal sites such as my blog, my wife's sites (&lt;a href="http://www.burntoutmom.com/"&gt;burntoutmom.com&lt;/a&gt;, &lt;a href="http://www.greekmommy.net/"&gt;greekmommy.net&lt;/a&gt;) and a few other small sites.
&lt;br /&gt;
&lt;br /&gt;
I used to host those sites on one of my company's clusters. The bandwidth consumed was nothing to write home about (I think in total it was a couple of GB per month ~ 1.00 USD) so it didn't matter that I had them there. However, recent events forced me to move them out of that cluster. I was on the market for good and relatively cheap hosting. I did not want to purchase my own server or colocate with someone else. My solution was going to be a VPS since I would be in control of what I install and what I need.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Slicehost
&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-wrQI3rybbMk/Tr10PKB6C0I/AAAAAAAAFwQ/eeYtZ_2XxN8/s1600/slicehostLogo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-wrQI3rybbMk/Tr10PKB6C0I/AAAAAAAAFwQ/eeYtZ_2XxN8/s1600/slicehostLogo.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Without much thought, I signed up for &lt;a href="http://www.slicehost.com/"&gt;Slicehost&lt;/a&gt;, which is a&amp;nbsp;subsidiary of &lt;a href="http://www.rackspace.com/"&gt;Rackspace&lt;/a&gt;, a very well known and reputable company.
&lt;br /&gt;
&lt;br /&gt;
I got their 4GB package (250.00 USD per month) and installed &lt;a href="http://www.gentoo.org/"&gt;Gentoo&lt;/a&gt; on it. Apart from the price which was a bit steep, everything else was fine. I was happy to be able to host my sites in a configuration that I was comfortable with, under the understanding that if the VPS failed, then all my sites would go down. That however is the risk that everyone takes while hosting their sites on a single machine. The higher the availability and redundancy the higher the cost.
&lt;br /&gt;
&lt;br /&gt;
I must admit that signing up was not a very happy experience. I went and paid with my credit card, as they pro-rate your month based on your package. Almost immediately after signing up, came the email informing me that my credit card has been charged for the relevant amount. I got into the box through ssh, updated the &lt;i&gt;/etc/make.conf&lt;/i&gt; file with the USE flags that I needed, run &lt;i&gt;emerge --sync&lt;/i&gt; and then &lt;i&gt;emerge --update --deep --newuse --verbose world&lt;/i&gt; so as to update the system.
&lt;br /&gt;
&lt;br /&gt;
It must have been around 5-10 minutes into the process that I received an email from Slicehost saying that they are checking my account information and that I need to confirm my credit card details. I immediately replied to their email (gotta love the desktop notifications on GMail), with the information they needed.
&lt;br /&gt;
&lt;br /&gt;
After I sent the email, I noticed that the box was not responding. I tried to log back in and could not. I was also logged out (and could not log back in) to their management console on slicehost.com site. I was fuming! Effectively they severed the connection to the VPS in the middle of compilation to check my credit card information.I understand that they need to perform checks for fraud but two questions came to mind:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Why did they have to sever the connection and not just send an email, and if I did not reply, just block access to the box? That would have been a heck of a lot of an inconvenience to myself i.e. the end user.&lt;/li&gt;
&lt;li&gt;Why did the initial email say that my credit card has been charged and it had not?&lt;/li&gt;
&lt;/ol&gt;
No more than 10 minutes later the whole thing had been resolved. I received an email saying that "&lt;i&gt;everything is OK and your account has been restored"&lt;/i&gt;, at which point I logged back in to redo the compilations. I also received emails from their support/billing team apologizing but stating that although the initial email states that they charge the credit card, they don't. It is something they need to correct because it pisses people (like me) off.
&lt;br /&gt;
&lt;br /&gt;
There was nothing wrong with my setup - everything was working perfectly but the price was really what was bothering me. I would be able to support the sites for a few months, but since literally none of them is making money (maybe a dollar here or there from my blog but that is about it), I would have to pay out of pocket for the hosting every month. I had to find a different solution that would be:
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;cheaper than Slicehost&lt;/li&gt;
&lt;li&gt;flexible in terms of setup&lt;/li&gt;
&lt;li&gt;easy to use in terms of controlling your VPS&lt;/li&gt;
&lt;/ul&gt;
After a lot of research I ended up with two winners: &lt;a href="http://l.niden.net/linode"&gt;Linode&lt;/a&gt; and &lt;a href="http://www.prgmr.com/"&gt;Prgmr&lt;/a&gt;. I opted for Linode, because although it was quite a bit more expensive than Prgmr, it had the better console in handling your VPS. I will, however, try out Prgmr's services in the near future so as to assess how good they are. They definitely cannot be beat in price.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Linode
&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-H4uCFgYjyOQ/Tr10gJMZB0I/AAAAAAAAFwY/z2H5iqeODR8/s1600/linode.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-H4uCFgYjyOQ/Tr10gJMZB0I/AAAAAAAAFwY/z2H5iqeODR8/s1600/linode.gif" /&gt;&lt;/a&gt;&lt;/div&gt;
Setting up an account with Linode was very easy. I didn't have any of the mini-saga I had with Slicehost. The account was created right there and then, my credit card charged and I was up and running in no time. Immediately I could see a difference in price. Linode's package for 4GB or RAM is 90.00 USD cheaper (159.00 USD vs. 250.00 USD for Slicehost). For the same package, the price difference is huge.&lt;br /&gt;
&lt;br /&gt;
I started testing the network, creating my VPS in the Atlanta, GA datacenter (Linode offers a number of datacenters for you to create your own). The functionality that was available to me was identical and in some cases superior to that of Slicehost. There are a lot more distributions to choose from, and you can partition your VPS the way you want it to name a couple.
&lt;br /&gt;
&lt;br /&gt;
Shifting through the &lt;a href="http://library.linode.com/"&gt;documentation&lt;/a&gt;, I saw a few topics regarding high availability websites. The articles described using &lt;a href="http://www.drbd.org/"&gt;DRBD&lt;/a&gt;, &lt;a href="http://nginx.org/"&gt;nginx&lt;/a&gt;, &lt;a href="http://www.linux-ha.org/wiki/Main_Page"&gt;heartbeat&lt;/a&gt; and pacemaker etc. to keep your sites highly available. I was intrigued by the information and set off to create a load balancer using two VPSs and nginx. I have documented the process and this is another blog post that will come later on this week.
&lt;br /&gt;
&lt;br /&gt;
While experimenting with the load balancer (and it was Saturday evening) I had to add a new IP address to one of the VPS instances. At the time my account would not allow such a change and I had to contact support. I did and got a reply in less than 5 minutes. I was really impressed by this. Subsequent tickets were answered within the 5 minute time frame. Kudos to Linode support for their speed and professionalism.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion
&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
For a lot cheaper, Linode offered the same thing that Slicehost did. Moving my sites from one VPS to another was a matter of changing my DNS records to point &amp;nbsp;to the new IP address.
&lt;br /&gt;
&lt;br /&gt;
I have been using Linode for a week and so far so good. The support is superb and the &lt;a href="http://library.linode.com/"&gt;library&lt;/a&gt; is full of how-to's that allows me to experiment with anything I want to - and the prices are not going to break me.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Resources
&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.vertexhost.com/"&gt;Vertexhost&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.slicehost.com/"&gt;Slicehost&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.rackspace.com/"&gt;Rackspace&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.gentoo.org/"&gt;Gentoo&lt;/a&gt;,&amp;nbsp;&lt;a href="http://l.niden.net/linode"&gt;Linode&lt;/a&gt;,&amp;nbsp;&lt;a href="http://library.linode.com/"&gt;Linode Library&lt;/a&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/0eBsbgq0v_w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/8210677066514343368/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/03/slicehost-vs-linode-hostingvps.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/8210677066514343368?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/8210677066514343368?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/0eBsbgq0v_w/slicehost-vs-linode-hostingvps.html" title="Slicehost vs. Linode [Hosting][VPS]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-r1mM0nbKiuk/Tr10IXu7JSI/AAAAAAAAFwI/OtambY19bQg/s72-c/VPS.jpg" height="72" width="72" /><thr:total>3</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/03/slicehost-vs-linode-hostingvps.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkIMR3s8eip7ImA9WhRSEE4.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-4768088655361555102</id><published>2011-03-10T14:00:00.000-05:00</published><updated>2011-11-11T14:09:46.572-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-11T14:09:46.572-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Review" /><category scheme="http://www.blogger.com/atom/ns#" term="Docs" /><category scheme="http://www.blogger.com/atom/ns#" term="Online Storage" /><category scheme="http://www.blogger.com/atom/ns#" term="Information" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Apps" /><category scheme="http://www.blogger.com/atom/ns#" term="Picasa" /><title>Additional storage for Google Apps users [Information][Review]</title><content type="html">&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-mTZTSrlAoVU/Tr1yc7fWhlI/AAAAAAAAFvs/-Xf-jfj6KfY/s1600/google-apps-logo-150x150.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-mTZTSrlAoVU/Tr1yc7fWhlI/AAAAAAAAFvs/-Xf-jfj6KfY/s1600/google-apps-logo-150x150.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
I have been using Google Apps for a number of years now and I have gotten so used to it that I cannot fathom any other way of operating. I am sure that some of you share that sentiment. :)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Limitations
&lt;/b&gt;&lt;br /&gt;
Up until a few months ago, Google Apps had its limitations. The actual Apps was in some sort of a jailshell, isolated from the whole Google suite of applications. For that reason you could not use your Google Apps login to enjoy the service of Google Reader for instance. You had to be sneaky about it. You had to create a Google Account with the same username (and password if you liked) as your Google Apps domain and although the two did not communicate, you could have effectively "one login" for all services.&lt;br /&gt;
&lt;br /&gt;
This limitation became more apparent with the increased usage of Android phones (where you need to have a Google Account on your phone) as well as Google Voice. Users have been asking about the "merge" and Google responded with significant infrastructure changes to cater for the transition. In my blog post "&lt;a href="http://www.niden.net/2010/08/google-apps-and-google-accounts-merge-information-howto/"&gt;Google Apps and Google Accounts Merge&lt;/a&gt;" I present additional information about this, inclusive of a&amp;nbsp;How-To on the transition for administrators of Google Apps. Unfortunately the process is not perfect and there are still some services that are not fully integrated with the new infrastructure (but will be in the future). For instance in my domain, since I use Google Voice with my domain email account, I am still on the "old" system because the account could not be transitioned. It will happen in the end, it just takes time.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Storage Needs
&lt;/b&gt;&lt;br /&gt;
The biggest issue for me that was related to these two separated accounts (Google Apps Account vs. Google Account) was &lt;a href="http://picasaweb.google.com/nikos.dimopoulos"&gt;Picasa&lt;/a&gt; and &lt;a href="http://docs.google.com/"&gt;Google Docs&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I have been very methodical in my filing, utilizing electronic storage as much as possible. For that reason I have been scanning documents and uploading them to Google Docs (or if they were available in PDF format I would just upload them). The documents would range from personal, utility bills, bank statements, anything that I want to store. Soon I realized that the 1GB that Google Apps offers for documents will not cut it. I therefore created a new account which I named DocsMule1 (clearly to signify its purpose). I created one folder in that account, uploaded as many documents as I could there and shared that document with my own account as well as my wife's. Soon I found more limitations since I ended up with 3 mule accounts. Since there was no option for me to upgrade the storage (even if I paid for it), I had to change my strategy. Managing documents from 3 or more different accounts is not an easy and convenient task.
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-MuwgFdqwTls/Tr1y0um1RmI/AAAAAAAAFv4/3C8bwvG19bU/s1600/google-manage-storage-300x246.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-MuwgFdqwTls/Tr1y0um1RmI/AAAAAAAAFv4/3C8bwvG19bU/s1600/google-manage-storage-300x246.png" /&gt;&lt;/a&gt;&lt;/div&gt;
I downloaded all my documents back to my computer (gotta love Google's &lt;a href="http://www.dataliberation.org/"&gt;Data Liberation&lt;/a&gt;) and deleted them from the Google Apps mule accounts and then deleted those accounts - just to keep everything tidy. I then launched my Gmail account and signed into Google Docs. I created one folder which I shared with my Google Apps accounts (my wife's and mine) and then paid $50.00 for a whole year - which provided me with 200GB of space. You can always check how much space you are using by visiting the &lt;a href="https://www.google.com/accounts/b/0/ManageStorage"&gt;Manage Storage&lt;/a&gt; page of your Google Account.&lt;br /&gt;
&lt;br /&gt;
Once that was done, I started creating my folders (collections now) and uploaded all my documents up there. In addition to that, since my parents live in Greece, they rely on VoIP chat as well as my Picasa to stay in touch with their grandchildren. My wife and I, through the use of our mini camera as well as our Android phones, take a lot of pictures of the kids, documenting the little things that they do on a regular basis. This serves as a good archive for them when they grow up but also as a good way to stay in touch with my parents. Google's additional storage was the solution.
&lt;br /&gt;
&lt;br /&gt;
Problem solved. With minimal money I had everything sorted out. It did however inconvenience me quite a bit in the end, since a lot of my data was scattered now. The GMail account would keep Picasa and Docs, the Google Apps account my email, my Google account my Reader, Web history etc. &amp;nbsp;Not very convenient but it works.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Storage for Google Apps
&lt;/b&gt;&lt;br /&gt;
Around February, Google announced that they will be offering the option to Google Apps users to purchase additional storage. I was really happy about that since I could therefore ditch the GMail account for handling my docs and keep everything under the domain account. However something was wrong. When Google revealed their pricing (the announcement is not there any more but the prices I am quoting are real), I quickly found out that for the storage I currently have, I would need to spend $700.00 a year instead of $50.00. That did not make sense at all. Needless to say, I stayed with my existing plan.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;&lt;b&gt;Initial Pricing Plan
&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-family: monospace; white-space: pre;"&gt; &lt;/span&gt;&lt;strong style="font-family: monospace; white-space: pre;"&gt;Storage&lt;/strong&gt;&lt;span class="Apple-style-span" style="font-family: monospace; white-space: pre;"&gt;   &lt;/span&gt;&lt;strong style="font-family: monospace; white-space: pre;"&gt;Price (per year)&lt;/strong&gt;&lt;br /&gt;
&lt;pre&gt;   5 GB         17.50 USD
  20 GB         70.00 USD
  80 GB        280.00 USD
 &lt;strong&gt;200 GB        700.00 USD&lt;/strong&gt;
 400 GB      1,400.00 USD
   1 TB      3,500.00 USD
   2 TB      7,000.00 USD
   4 TB     14,000.00 USD
   8 TB     28,000.00 USD
  16 TB     56,000.00 USD&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;New Pricing Plan
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
A few days ago, Google announced changes in the pricing of additional storage for Google Apps users as well as changes to the free storage that Google offers for Picasa. Picasa Web Albums does offer 1GB of free storage but now photos of 800x800 pixels or less as well as videos of 15 minutes or less do not count against the 1GB of storage. You can read more about the Picasa Web Albums storage in the relevant &lt;a href="http://picasa.google.com/support/bin/answer.py?answer=43879"&gt;help page&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt; &lt;strong&gt;Storage&lt;/strong&gt;   &lt;strong&gt;Price (per year)&lt;/strong&gt;
  20 GB         5.00 USD
  80 GB        20.00 USD
 200 GB        50.00 USD
 400 GB       100.00 USD
   1 TB       256.00 USD
   2 TB       512.00 USD
   4 TB     1,024.00 USD
   8 TB     2,048.00 USD
  16 TB     4,096.00 USD&lt;/pre&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-2qTyhIRHDCc/Tr1y8dF8rAI/AAAAAAAAFwA/SLtOkUsxppM/s1600/google-apps-purchase-storage-300x215.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-2qTyhIRHDCc/Tr1y8dF8rAI/AAAAAAAAFwA/SLtOkUsxppM/s1600/google-apps-purchase-storage-300x215.png" /&gt;&lt;/a&gt;&lt;/div&gt;
As far as the new pricing is concerned, Google brought everything in line with Google Accounts pricing (effectively scrapping the initial - expensive - pricing plan for extra storage). The help page "&lt;a href="http://picasa.google.com/support/bin/answer.py?answer=39567"&gt;Google Storage - How it Works&lt;/a&gt;" offers additional information for those that want to use/upgrade their storage while using a Google Apps account. Effectively&amp;nbsp;it now costs exactly the same to purchase additional space for your Google Apps account (to store documents) as it would if you were using a different Google Account. That probably means that I have to download everything to my computer and &amp;nbsp;re-upload it to my Google Apps account....
&lt;br /&gt;
&lt;br /&gt;
To take advantage of this feature, you will have to go to the &lt;a href="https://www.google.com/accounts/b/0/PurchaseStorage?hl=en_US"&gt;Purchase additional storage&lt;/a&gt; page while logged in with the Google Apps account that you wish to purchase storage for. Note that there is a warning that appears in red (see image) that warns you that you are using a Google Apps account. Google provides this information since your Google Apps account relies on the Google Apps administrator. If you have an account on Google Apps and you purchase storage, that storage will be gone if the administrator deletes or restricts access to your account.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion
&lt;/b&gt;&lt;br /&gt;
In my view, Google has done it again. They now offer an extremely affordable and secure way of storing your data. There are loads of people that have concerns about where their data is stored, who has ownership of the data stored, what does Google do with the data etc. A lot of these questions can easily be answered if you google (duh) the relevant terms or search in &lt;a href="http://www.google.com/support/"&gt;Google's Help Center&lt;/a&gt;. &lt;a href="http://www.dataliberation.org/"&gt;Data Liberation&lt;/a&gt; allows you to retrieve your data whenever you want to. If on the other hand you are skeptical and do not wish to store your data there, don't. It is your choice.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;References
&lt;/b&gt;&lt;br /&gt;
&lt;a href="http://docs.google.com/"&gt;Google Docs&lt;/a&gt;,&amp;nbsp;&lt;a href="http://picasaweb.google.com/"&gt;Picasa Web Albums&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.dataliberation.org/"&gt;Data Liberation&lt;/a&gt;,&amp;nbsp;&lt;a href="https://www.google.com/accounts/b/0/ManageStorage"&gt;Google Account - Manage Storage&lt;/a&gt;,&amp;nbsp;&lt;a href="http://picasa.google.com/support/bin/answer.py?answer=43879"&gt;Photo and Video Requirements: Size Limits for Picasa Web Albums&lt;/a&gt;,&amp;nbsp;&lt;a href="http://picasa.google.com/support/bin/answer.py?answer=39567"&gt;Google Storage - How it Works&lt;/a&gt;,&amp;nbsp;&lt;a href="https://www.google.com/accounts/b/0/PurchaseStorage?hl=en_US"&gt;Google Account - Purchase additional storage&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.google.com/support/"&gt;Google's Help Center&lt;/a&gt;&lt;img src="http://feeds.feedburner.com/~r/niden/~4/4tmxKN-khWE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/4768088655361555102/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/03/additional-storage-for-google-apps.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/4768088655361555102?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/4768088655361555102?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/4tmxKN-khWE/additional-storage-for-google-apps.html" title="Additional storage for Google Apps users [Information][Review]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-mTZTSrlAoVU/Tr1yc7fWhlI/AAAAAAAAFvs/-Xf-jfj6KfY/s72-c/google-apps-logo-150x150.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/03/additional-storage-for-google-apps.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AMQH06cCp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-4802713170056069724</id><published>2011-02-20T13:56:00.000-05:00</published><updated>2012-09-27T14:16:21.318-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:16:21.318-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="RSync" /><category scheme="http://www.blogger.com/atom/ns#" term="Storage" /><category scheme="http://www.blogger.com/atom/ns#" term="NFS" /><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="Bash" /><title>Keeping your Linux users in sync in a cluster [How-To][Linux][Sync]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-NdE2r_F0_aE/Tr1vwHhES8I/AAAAAAAAFvE/Iwh6z31EHek/s1600/sync.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://3.bp.blogspot.com/-NdE2r_F0_aE/Tr1vwHhES8I/AAAAAAAAFvE/Iwh6z31EHek/s200/sync.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
As websites become more and more popular, your application might not be able to cope with the demand that your users put on your server. To accommodate that, you will need to move out of the one server solution and create a cluster. The easiest cluster to create would be with two servers, one to handle your web requests (HTTP/PHP etc.) and one to handle your database load (MySQL). Again this setup can only get you so far. If your site is growing, you will need a cluster of servers.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Database
&lt;/b&gt;&lt;br /&gt;
The scope of this How-To will not cover database replication; I will need to dedicate a separate blog post for that. However, clustering your database is relatively easy with MySQL replication. You can set a virtual hostname (something like mysql.mydomain.com) which is visible only within your network. You then set up the configuration of your application to use that as the host. The virtual hostname will map to the current master server, while the slave(s) will only replicate.&lt;br /&gt;
&lt;br /&gt;
For instance, if you have two servers A and B, you can configure both of them to become master or slave in MySQL. You then set one of them as master (A) and the other as slave (B). If something happens to A, B gets promoted to master instantly. Once A comes back up, it gets demoted to a slave and the process is repeated if/when B has a problem. This can be a very good solution but you will need to have pretty evenly matched servers to keep with the demand. Alternatively B can be less powered than A and when A comes back up you keep it momentarily as a slave (until everything is replicated) and then promote it back to master.
&lt;br /&gt;
&lt;br /&gt;
One thing to note about replication (that I found through trial and error). MySQL keeps binary logs to handle replication. If you are not cautious in your deployment, MySQL will never recycle those logs and therefore you will soon run out of space when having a busy site. By default those logs will be under &lt;em&gt;/var/lib/mysql&lt;/em&gt;.
&lt;br /&gt;
&lt;br /&gt;
By changing directives in &lt;em&gt;my.cnf&lt;/em&gt; you can store the binary logs in a different folder and even set up 'garbage collection' or recycling. You can for instance set the logs to rotate every X days with the following directive in &lt;em&gt;my.cnf&lt;/em&gt;:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;expire_logs_days &amp;nbsp;= 5&lt;/pre&gt;
&lt;br /&gt;
I set mine to 5 days which is extremely generous. If your replication is broken you must have the means to know about it within minutes (see &lt;a href="http://www.nagios.org/"&gt;nagios&lt;/a&gt; for a good monitoring service). In most cases 2 days is more than enough.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Files
&lt;/b&gt;&lt;br /&gt;
There are numerous ways of keeping your cluster in sync. A really good tool that I have used when playing around with a cluster is &lt;a href="http://oss.linbit.com/"&gt;csync2&lt;/a&gt;. Installation is really easy and and all you will need is to run a cron task every X minutes (up to you) to synchronize the new files. Imagine it as a two way &lt;a href="http://samba.anu.edu.au/rsync/"&gt;rsync&lt;/a&gt;. Another tool that can do this is &lt;a href="http://www.cis.upenn.edu/~bcpierce/unison/"&gt;unison&lt;/a&gt; but I found it to be slow and difficult to implement - that's just me though.&lt;br /&gt;
&lt;br /&gt;
Assume an implementation of a website being served by two (or more) servers behind a load balancer. If your users upload files, you don't know where those files are uploaded, which server that is. As a result if user A uploads the file &lt;em&gt;abc.txt&lt;/em&gt; to server A, user B might be served the content from server B and would not be able to access the file. &lt;a href="http://oss.linbit.com/"&gt;csync2&lt;/a&gt; would synchronize the file across the number of servers, thus providing access to the content and keeping multiple copies of the content (additional backup if you like).
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;NFS
&lt;/b&gt;&lt;br /&gt;
An alternative to keeping everything synchronized is to use a NFS. This approach has many advantages and some disadvantages. It is up to you on whether the disadvantages are something you can live with.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Disadvantages
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;NFS is slow - slower than the direct access to a local hard drive.&lt;/li&gt;
&lt;li&gt;Most likely you will use a symlink to the NFS folder, which can slow things down even more.&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;&lt;i&gt;Advantages&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;The NFS does not rely on the individual web servers for content.&lt;/li&gt;
&lt;li&gt;The web servers can be low to medium spec boxes without the need to have really fast and large hard drives&lt;/li&gt;
&lt;li&gt;A well designed NFS with &lt;a href="http://oss.linbit.com/"&gt;DRDB&lt;/a&gt; provides a raid-1 over a network. Using gigabit Network Interface Cards you can keep performance at really high levels.&lt;/li&gt;
&lt;/ul&gt;
I know that my friend &lt;a href="http://www.axivo.com/"&gt;Floren&lt;/a&gt; does not agree with my approach on the NFS and would definitely have gone with the &lt;a href="http://oss.linbit.com/"&gt;csync2&lt;/a&gt; approach. Your implementation depends on your needs.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Users and Groups
&lt;/b&gt;&lt;br /&gt;
Using the NFS approach, we need to keep the files and permissions properly set up for our application. Assume that we have two servers and we need to create one user to access our application and upload files.&lt;br /&gt;
&lt;br /&gt;
The user has been created on both servers and the files are stored on the NFS. Connecting to server A and looking at the files we can see something like this:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;drwxr-xr-x 17 niden  niden  4096 Feb 18 13:41 www.niden.net
drwxr-xr-x  5 niden  niden  4096 Nov 15 22:10 www.niden.net.files
drwxr-xr-x  7 beauty beauty 4096 Nov 21 17:42 www.beautyandthegeek.it
&lt;/pre&gt;
&lt;br /&gt;
However when connecting to server B, the same listing tells another story:&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;drwxr-xr-x 17 508    510    4096 Feb 18 13:41 www.niden.net
drwxr-xr-x  5 508    510    4096 Nov 15 22:10 www.niden.net.files
drwxr-xr-x  7 510    511    4096 Nov 21 17:42 www.beautyandthegeek.it
&lt;/pre&gt;
&lt;br /&gt;
The problem here is the uid and gid of the users and groups of each user respectively. Somehow (and this is really easy to happen) server A had one or more users added to it, thus the internal counter of the user IDs has been increased by one or more and is not identical to that one of server B. So adding a new user in server A will get the uid 510 while on server B the same process will produce a user with a uid of 508.&lt;br /&gt;
&lt;br /&gt;
To have all users setup on all servers the same way, we need to use two commands: &lt;a href="http://man.he.net/man8/groupadd"&gt;groupadd&lt;/a&gt; and &lt;a href="http://man.he.net/man8/useradd"&gt;useradd&lt;/a&gt; (in some Linux distributions you might find them as addgroup and adduser).
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;groupadd
&lt;/b&gt;&lt;br /&gt;
First of all you will need to add groups. You can of course keep all users in one group but my implementation was to keep one user and one group per access. To cater for that I had to first create a group for every user and then the user account itself. Like users, groups have unique ids (gid). The purpose of gid is:&lt;br /&gt;
&lt;blockquote&gt;
The numerical value of the groups ID. This value must be unique, unless the -o option is used. The value must be non-negative. The default is to use the smallest ID value greater than 999 and greater than every other group. Values between 0 and 999 are typically reserved for system accounts.&lt;/blockquote&gt;
I chose to assign each group a unique id (you can&amp;nbsp;override&amp;nbsp;this behavior by using the -o switch in the command below, thus allowing a gid to be used in more than one group). The arbitrary number that I chose was 2000.
&lt;br /&gt;
&lt;br /&gt;
As an example, I will set &lt;em&gt;niden&lt;/em&gt; as the user/group for accessing this site and &lt;em&gt;beauty&lt;/em&gt; as the user/group that accesses &lt;a href="http://www.beautyandthegeek.it/"&gt;BeautyAndTheGeek.IT&lt;/a&gt;. Note that this is only an example.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;groupadd --gid 2000 niden
groupadd --gid 2001 beauty&lt;/pre&gt;
&lt;br /&gt;
Repeat the process as many times as needed for your setup. Connect to the second server and repeat this process. Of course if you have more than two servers, repeat the process on each of the servers that you have (and each accesses your NFS)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;useradd
&lt;/b&gt;&lt;br /&gt;
The next step is to add the users. Like groups, we will need to set the uid up. The purpose of the uid is:&lt;br /&gt;
&lt;blockquote&gt;
The numerical value of the users ID. This value must be unique, unless the -o option is used. The value must be non-negative. The default is to use the smallest ID value greater than 999 and greater than every other user. Values between 0 and 999 are typically reserved for system accounts.&lt;/blockquote&gt;
Like with the groups, I chose to assign each user a unique id starting from 2000.
&lt;br /&gt;
&lt;br /&gt;
So to in the example above, the commands that I used were:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;useradd --uid 2000 -g niden --create-home niden
useradd --uid 2000 -g beauty --create-home beauty&lt;/pre&gt;
&lt;br /&gt;
You can also use a different syntax, utilizing the numeric gids:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;useradd --uid 2000 --gid 2000 --create-home niden
useradd --uid 2000 --gid 2001 --create-home beauty&lt;/pre&gt;
&lt;br /&gt;
Again, repeat the process as many times as needed for your setup and to as many servers as needed.
&lt;br /&gt;
&lt;br /&gt;
In the example above I issued the --create-home switch (or -m) so as a home folder to be created under /home for each user. Your setup might not need this step. Check the references at the bottom of this blog post for the manual pages for &lt;a href="http://man.he.net/man8/groupadd"&gt;groupadd&lt;/a&gt; and&amp;nbsp;&lt;a href="http://man.he.net/man8/useradd"&gt;useradd&lt;/a&gt;.
&lt;br /&gt;
&lt;br /&gt;
I would suggest that you keep a log of which user/group has which uid/gid. It helps in the long run, plus it is a good habit to keep proper documentation on projects :)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Passwords?
&lt;/b&gt;&lt;br /&gt;
So how about the passwords on all servers? My approach is crude but effective. I connected to the first server, and set the password for each user, writing down what the password was:&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;passwd niden&lt;/pre&gt;
&lt;br /&gt;
Once I had all the passwords set, I opened the /etc/shadow file.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;nano /etc/shadow&lt;/pre&gt;
&lt;br /&gt;
and that revealed a long list of users and their scrambled passwords:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;niden:$$$$long_string_of_characters_goes_here$$$$:13864:0:99999:7:::
beauty:$$$$again_long_string_of_characters_goes_here$$$$:15009:0:99999:7:::&lt;/pre&gt;
&lt;br /&gt;
Since I know that I added niden and beauty as users, I copied these two lines. I then connected to the second server, opened /etc/shadow and located the two lines where the niden and beauty users are referenced. I deleted the existing lines, and pasted the ones that I had copied from server A. Saved the file and now my passwords are synchronized in both servers.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion
&lt;/b&gt;&lt;br /&gt;
The above might not be the best way of keeping users in sync in a cluster but it gives you an idea on where to start. There are different implementations available (Google is your friend) and your mileage might vary. The above has worked for me for a number of years since I never needed to add more than a handful of users on the servers each year.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;References
&lt;/b&gt;&lt;br /&gt;
&lt;a href="http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html"&gt;MySQL System Variables&lt;/a&gt;, &lt;a href="http://www.nagios.org/"&gt;Nagios&lt;/a&gt;, &lt;a href="http://oss.linbit.com/"&gt;csync2&lt;/a&gt;, &lt;a href="http://samba.anu.edu.au/rsync/"&gt;rsync&lt;/a&gt;, &lt;a href="http://www.cis.upenn.edu/~bcpierce/unison/"&gt;Unison File Synchronizer&lt;/a&gt;, &lt;a href="http://oss.linbit.com/"&gt;DRDB&lt;/a&gt;, &lt;a href="http://www.axivo.com/"&gt;Axivo Inc.&lt;/a&gt;, &lt;a href="http://man.he.net/man8/groupadd"&gt;groupadd man page&lt;/a&gt;, &lt;a href="http://man.he.net/man8/useradd"&gt;useradd man page&lt;/a&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/5lnhk1CUWaU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/4802713170056069724/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/02/keeping-your-linux-users-in-sync-in.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/4802713170056069724?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/4802713170056069724?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/5lnhk1CUWaU/keeping-your-linux-users-in-sync-in.html" title="Keeping your Linux users in sync in a cluster [How-To][Linux][Sync]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-NdE2r_F0_aE/Tr1vwHhES8I/AAAAAAAAFvE/Iwh6z31EHek/s72-c/sync.png" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/02/keeping-your-linux-users-in-sync-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0ANR3Y8fip7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-1994966112406252843</id><published>2011-02-06T13:47:00.000-05:00</published><updated>2012-09-27T14:16:36.876-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:16:36.876-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Information" /><category scheme="http://www.blogger.com/atom/ns#" term="Personal" /><title>A look in the past and the future [Update]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-3S214dx8LTA/Tr1uVH87WjI/AAAAAAAAFu0/21RSIwRGjjc/s1600/update.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="176" src="http://4.bp.blogspot.com/-3S214dx8LTA/Tr1uVH87WjI/AAAAAAAAFu0/21RSIwRGjjc/s200/update.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
It has been months since I last posted a blog post. A lot of things have happened since August and I have a lot of material to post - just not the discipline to sit down and proof all the drafts so that I can post them.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;2010
&lt;/b&gt;&lt;br /&gt;
2010 has been a rough year mostly on a personal level. I &lt;a href="http://www.niden.net/2010/05/update-personal.html"&gt;lost my brother in law&lt;/a&gt; in March, &lt;a href="http://www.niden.net/2010/05/update-personal.html"&gt;my daughter was born&lt;/a&gt; in May, there was great uncertainty at work during the summer, an upgrade went bad for Long Hair Care Forum to name a few of the highlights.&lt;br /&gt;
&lt;br /&gt;
Since this is officially my first year of blogging, I was happy to see some of the statistics for that year (well 8 months to be exact since I haven't posted since August).
&lt;br /&gt;
&lt;br /&gt;
&lt;table style="border: none;"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;Visits&lt;/td&gt;
&lt;td style="width: 140px;"&gt;8,940&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;Pageviews&lt;/td&gt;
&lt;td style="width: 140px;"&gt;11,979&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;Pages/Visit&lt;/td&gt;
&lt;td style="width: 140px;"&gt;1.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;Bounce Rate&lt;/td&gt;
&lt;td style="width: 140px;"&gt;81.34%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;Avg Time on Site&lt;/td&gt;
&lt;td style="width: 140px;"&gt;00:01:10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;% New Visits&lt;/td&gt;
&lt;td style="width: 140px;"&gt;85.04%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 140px;"&gt;&lt;/td&gt;
&lt;td style="width: 140px;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
The most visited posts were:
&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.niden.net/2010/08/google-apps-and-google-accounts-merge.html"&gt;Google Apps and Google Accounts merge [Information][How-To]&lt;/a&gt; with&amp;nbsp;1,601 pageviews (13.37%)
&lt;a href="http://www.niden.net/2010/08/subversion-backup-how-to/"&gt;Subversion Backup [How-To]&lt;/a&gt; with&amp;nbsp;1,289 pageviews (10.76%)
&lt;a href="http://www.niden.net/2010/06/android-22-froyo-review.html"&gt; Android 2.2 (Froyo) [Review]&lt;/a&gt; with&amp;nbsp;1,124 pageviews (9.38%)
&lt;br /&gt;
&lt;br /&gt;
Not that bad for a first year.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;2011
&lt;/b&gt;&lt;br /&gt;
When I started blogging my main focus was to mostly address coding issues. However due to the plethora of solutions on the Internet, the content that I wanted to relay was not that interesting or had been covered a hundred times or more. For instance I did start posting about design patterns &lt;a href="http://www.niden.net/2010/01/design-patterns-singleton-serieshow-to.html"&gt;here&lt;/a&gt; and &lt;a href="http://www.niden.net/2010/02/design-patterns-factory-serieshow-to.html"&gt;here&lt;/a&gt;, but at the same time &lt;a href="http://giorgiosironi.blogspot.com/"&gt;Giorgio Sironi&lt;/a&gt; started blogging about design patterns and did an excellent job at it, so that topic was scrapped. My posts then started covering a much broader scope, mostly that of technology with a small personal flare.&lt;br /&gt;
&lt;br /&gt;
For 2011 I will continue on the same scope. I intend on blogging about interesting things technologically, how-to's and topics that I want to share with the community.
&lt;br /&gt;
&lt;br /&gt;
As always, whatever is presented in this blog is my personal opinion. Every post covered here as well with any code are free of copyright and you are free to use them in your projects at your own risk.
&lt;br /&gt;
&lt;br /&gt;
A big thank you to everyone that visited this blog whether you shared your opinion with me or not. I hope to meet the expectations of producing interesting content in the future.&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/yVsmfnuyob8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/1994966112406252843/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2011/02/look-in-past-and-future-update.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/1994966112406252843?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/1994966112406252843?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/yVsmfnuyob8/look-in-past-and-future-update.html" title="A look in the past and the future [Update]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-3S214dx8LTA/Tr1uVH87WjI/AAAAAAAAFu0/21RSIwRGjjc/s72-c/update.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2011/02/look-in-past-and-future-update.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08FQ3cyeyp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-9181400557699761009</id><published>2010-08-21T13:38:00.000-04:00</published><updated>2012-09-27T14:16:52.993-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:16:52.993-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Storage" /><category scheme="http://www.blogger.com/atom/ns#" term="Gentoo" /><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="Installation" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="Backup" /><title>Create an inexpensive hourly remote backup [How-To]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;blockquote&gt;
There are two kinds of people, those who backup regularly, and those that never had a hard drive fail&lt;/blockquote&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-u05g2O8FUvU/Tr1tS_Hv6LI/AAAAAAAAFuk/NblYWiD58Yw/s1600/remote_backup.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="156" src="http://1.bp.blogspot.com/-u05g2O8FUvU/Tr1tS_Hv6LI/AAAAAAAAFuk/NblYWiD58Yw/s200/remote_backup.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
As you can tell &lt;a href="http://www.niden.net/2010/08/subversion-backup-how-to/"&gt;the above is my favorite quote&lt;/a&gt;. It is so true and I believe everyone should evaluate how much their data (emails, documents, files) is worth to them and, based on that value, create a backup strategy that suits them. I know for sure that if I ever lost the pictures and videos of my family I would be devastated since those are irreplaceable.
&lt;br /&gt;
&lt;br /&gt;
So the question is how can I have an inexpensive backup solution? All my documents and emails are stored in Google, since my domain is on &lt;a href="http://www.google.com/a/"&gt;Google Apps&lt;/a&gt;. What happens to the live/development servers though that host all my work? I program on a daily basis and the code has to be backed up regularly so as to avoid any hard drive failures and thus result in loss of time and money.
&lt;br /&gt;
&lt;br /&gt;
So here is my solution. I have an old computer (IBM Thincentre) which I decided to beef up a bit. I bought 4Gb of RAM from eBay for less than $100 for it. Although this is was not necessary since my solution would be based on Linux (&lt;a href="http://www.gentoo.org/"&gt;Gentoo&lt;/a&gt; in particular), I wanted to have faster compilation times for packages.
&lt;br /&gt;
&lt;br /&gt;
I bought two external drives (750Gb and 500Gb respectively) and one 750Gb internal drive. I already have a 120Gb hard drive in the computer. The two external ones are connected to the computer using USB while the internal ones are connected using SATA.
&lt;br /&gt;
&lt;br /&gt;
The external drives are formatted using NTFS while the whole computer is built using ReiserFS.
&lt;br /&gt;
&lt;br /&gt;
Here is the approach:
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;I have installed and have a working Gentoo installation on the machine&lt;/li&gt;
&lt;li&gt;I have an active Internet connection&lt;/li&gt;
&lt;li&gt;I have installed LVM on the machine and set up the core system on the 120Mb drive while the 500Mb is on LVM&lt;/li&gt;
&lt;li&gt;I have 300Mb active on the LVM (from the available 500Mb)&lt;/li&gt;
&lt;li&gt;I have generated a public SSH key (I will need this to exchange it with the target servers)&lt;/li&gt;
&lt;li&gt;I have mounted the internal 500Mb drive to the &lt;strong&gt;/storage&lt;/strong&gt; folder&lt;/li&gt;
&lt;li&gt;I have mounted the external USB 750Mb drive to the &lt;strong&gt;/backup_hourly&lt;/strong&gt; folder&lt;/li&gt;
&lt;li&gt;I have mounted the external USB 500Mb drive to the &lt;strong&gt;/backup_daily&lt;/strong&gt; folder&lt;/li&gt;
&lt;/ul&gt;
Here is how my backup works:
&lt;br /&gt;
&lt;br /&gt;
Every hour a script runs. The script uses rsync to syncrhonize files and folders from a remote server locally. Those files and folders are kept in relevant server named subfolders in the &lt;strong&gt;/storage&lt;/strong&gt; folder (remember this is my LVM). So for instance my subfolders will be &lt;strong&gt;/storage/beryllium.niden.net&lt;/strong&gt;, &lt;strong&gt;/storage/nitrogen.niden.net&lt;/strong&gt;, &lt;strong&gt;/storage/argon.niden.net&lt;/strong&gt; etc.
&lt;br /&gt;
&lt;br /&gt;
Once the rsync completes, the script continues by compressing the relevant 'server' folder and creates the compressed file with a date-time stamp on its name.
&lt;br /&gt;
&lt;br /&gt;
When all compressions are completed, if the time that the script has executed is midnight, the backups are moved from the &lt;strong&gt;/storage&lt;/strong&gt; folder to the &lt;strong&gt;/backup_daily&lt;/strong&gt; folder (which has the external USB 500Gb mounted). If it is any other time, the files are moved in the &lt;strong&gt;/backup_hourly&lt;/strong&gt; folder (which has the external USB 750Gb mounted).
&lt;br /&gt;
&lt;br /&gt;
This way I ensure that I keep a lot of backups (daily and hourly ones). The backups are being recycled, so older ones get deleted. The amount of data that you need to archive as well as the storage space you have available dictate how far back you can go in your hourly and daily cycles.
&lt;br /&gt;
&lt;br /&gt;
So let's get down to business. The script itself:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;#!/bin/bash
DATE=`date +%Y-%m-%d-%H-%M`
DATE2=`date +%Y-%m-%d`
DATEBACK_HOUR=`date --date='6 days ago' +%Y-%m-%d`
DATEBACK_DAY=`date --date='60 days ago' +%Y-%m-%d`
FLAGS="--archive --verbose --numeric-ids --delete --rsh='ssh'"
BACKUP_DRIVE="/storage"
DAY_USB_DRIVE="/backup_daily"
HOUR_USB_DRIVE="/backup_hourly"&lt;/pre&gt;
&lt;br /&gt;
These are some variables that I need for the script to work. The &lt;strong&gt;DATE&lt;/strong&gt; and &lt;strong&gt;DATE2&lt;/strong&gt; are used to date/time stamp the backups, while the &lt;strong&gt;DATEBACK_&lt;/strong&gt;* are used to clear previous backups. In this case it is set to 6 days ago (for my system). It can be set to whatever you want provided that you do not run out of space.
&lt;br /&gt;
&lt;br /&gt;
The &lt;strong&gt;FLAGS&lt;/strong&gt; variable keeps the rsync command options while the &lt;strong&gt;BACKUP_DRIVE&lt;/strong&gt;, &lt;strong&gt;DAY_USB_DRIVE&lt;/strong&gt; and &lt;strong&gt;HOUR_USB_DRIVE&lt;/strong&gt; hold the locations of the rsync folders, daily backup and hourly backup sorage areas.
&lt;br /&gt;
&lt;br /&gt;
The script works with arrays. I have 4 arrays to do the work and the 3 of them must have exactly the same elements.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;# RSync Information
rsync_info[1]="beryllium.niden.net html rsync"
rsync_info[2]="beryllium.niden.net db rsync"
rsync_info[3]="nitrogen.niden.net html rsync"
rsync_info[4]="nitrogen.niden.net html db"
rsync_info[5]="nitrogen.niden.net html svn"
rsync_info[6]="argon.niden.net html rsync"&lt;/pre&gt;
&lt;br /&gt;
This is the first array which holds descriptions to what needs to be done as far as source is concerned. These descriptions get appended to the log and helps me identify what step I am in.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;# RSync Source Folders
rsync_source[1]="beryllium.niden.net:/var/www/localhost/htdocs/"
rsync_source[2]="beryllium.niden.net:/niden_backup/db/"
rsync_source[3]="nitrogen.niden.net:/var/www/localhost/htdocs/"
rsync_source[4]="nitrogen.niden.net:/niden_backup/db"
rsync_source[5]="nitrogen.niden.net:/niden_backup/svn"
rsync_source[6]="argon.niden.net:/var/www/localhost/htdocs/"&lt;/pre&gt;
&lt;br /&gt;
This array holds the source host and folder. Remember that I have already exchanged SSH keys with each server, therefore when the script runs there is a direct connection to the source server. If you need to keep things a bit more secure for you, then you will need to alter the contents of the rsync_source array so that it reflects the user that you log in with as well as the password.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;# RSync Target Folders
rsync_target[1]="beryllium.niden.net/html/"
rsync_target[2]="beryllium.niden.net/db/"
rsync_target[3]="nitrogen.niden.net/html/"
rsync_target[4]="nitrogen.niden.net/db/"
rsync_target[5]="nitrogen.niden.net/svn/"
rsync_target[6]="argon.niden.net/html/"&lt;/pre&gt;
&lt;br /&gt;
This array holds the target locations for the rsync. These folders exist in my case under the &lt;strong&gt;/storage&lt;/strong&gt; subfolder.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;# GZip target files
servers[1]="beryllium.niden.net"
servers[2]="nitrogen.niden.net"
servers[3]="argon.niden.net"&lt;/pre&gt;
&lt;br /&gt;
This array holds the names of the folders to be archived. These are the folders directly under the &lt;strong&gt;/storage&lt;/strong&gt; folder and I am also using this array for the prefix of the compressed files. The suffix of the compressed files is a date/time stamp.
&lt;br /&gt;
&lt;br /&gt;
Here is how the script evolves:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;echo "BACKUP START"  &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
date &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log

echo "BACKUP START" &amp;nbsp;&amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
date &amp;nbsp;&amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log

# Loop through the RSync process
element_count=${#rsync_info[@]}
let "element_count = $element_count + 1"
index=1
while [ "$index" -lt "$element_count" ]
do
    echo ${rsync_info[$index]} &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    rsync $FLAGS ${rsync_source[$index]} $BACKUP_DRIVE/${rsync_target[$index]} &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    let "index = $index + 1"
done&lt;/pre&gt;
&lt;br /&gt;
The snippet above loops through the &lt;strong&gt;rsync_info&lt;/strong&gt; array and prints out the information in the log file. Right after that it uses the &lt;strong&gt;rsync_source&lt;/strong&gt; and &lt;strong&gt;rsync_target&lt;/strong&gt; arrays (as well as the &lt;strong&gt;FLAGS&lt;/strong&gt; variable) to rsync the contents of the source server with the local folder. Remember that all three arrays have to be identical in size (&lt;strong&gt;rsync_info&lt;/strong&gt;, &lt;strong&gt;rsync_source&lt;/strong&gt;, &lt;strong&gt;rsync_target&lt;/strong&gt;).
&lt;br /&gt;
&lt;br /&gt;
The next thing to do is zip the data (I loop through the servers array)
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;# Looping to GZip data
element_count=${#servers[@]}
let "element_count = $element_count + 1"
index=1
while [ "$index" -lt "$element_count" ]
do
    echo "GZip ${servers[$index]}" &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    tar cvfz $BACKUP_DRIVE/${servers[$index]}-$DATE.tgz $BACKUP_DRIVE/${servers[$index]} &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    let "index = $index + 1"
done&lt;/pre&gt;
&lt;br /&gt;
The compression method I use is tar/gzip. I found it to be fast with a good compression ratio. You can choose anything you like.
&lt;br /&gt;
&lt;br /&gt;
Now I need to delete old files from the drives and copy the files on those drives. I use the servers array again.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;# Looping to copy the produced files (if applicable) to the daily drive
element_count=${#servers[@]}
let "element_count = $element_count + 1"
index=1

while [ "$index" -lt "$element_count" ]
do
    # Copy the midnight files
    echo "Removing old daily midnight files" &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    rm -f $DAY_USB_DRIVE/${servers[$index]}/${servers[$index]}-$DATEBACK_DAY*.* &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    echo "Copying daily midnight files" &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
 &amp;nbsp; &amp;nbsp;cp -v $BACKUP_DRIVE/${servers[$index]}-$DATE2-00-*.tgz $DAY_USB_DRIVE/${servers[$index]} &amp;nbsp;&amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
 &amp;nbsp; &amp;nbsp;rm -f $BACKUP_DRIVE/${servers[$index]}-$DATE2-00-*.tgz &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log

    # Now copy the files in the hourly
    echo "Removing old hourly files" &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    rm -f $HOUR_USB_DRIVE/${servers[$index]}/${servers[$index]}-$DATEBACK_HOUR*.* &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    echo "Copying daily midnight files" &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    cp -v $BACKUP_DRIVE/${servers[$index]}-$DATE.tgz $HOUR_USB_DRIVE/${servers[$index]} &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
    rm -f $HOUR_USB_DRIVE/${servers[$index]}/${servers[$index]}-$DATEBACK*.* &amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log
 &amp;nbsp; &amp;nbsp;let "index = $index + 1"
done

echo "BACKUP END" &amp;nbsp;&amp;gt;&amp;gt; $BACKUP_DRIVE/logs/$DATE.log&lt;/pre&gt;
&lt;br /&gt;
The last part of the script loops through the servers array and:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Deletes the old files (recycling of space) from the daily backup drive (&lt;strong&gt;/storage/backup_daily&lt;/strong&gt;) according to the &lt;strong&gt;DATEBACK_DAY&lt;/strong&gt; variable. If the files are not found a warning will appear in the log.&lt;/li&gt;
&lt;li&gt;Copies the daily midnight file to the daily drive (if the file does not exist it will simply echo a warning in the log - I do not worry about warnings of this kind in the log file and was too lazy to use an IF EXISTS condition)&lt;/li&gt;
&lt;li&gt;Removes the daily midnight file from the &lt;strong&gt;/storage&lt;/strong&gt; drive.&lt;/li&gt;
&lt;/ol&gt;
The reason I am using copy and then remove instead of the move (&lt;strong&gt;mv&lt;/strong&gt;) command is that I have found this method to be faster.

Finally the same thing happens with the hourly files
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Old files are removed (&lt;strong&gt;DATEBACK_HOUR&lt;/strong&gt; variable)&lt;/li&gt;
&lt;li&gt;Hourly file gets copied to the &lt;strong&gt;/backup_hourly&lt;/strong&gt; drive&lt;/li&gt;
&lt;li&gt;Hourly file gets deleted from the &lt;strong&gt;/storage&lt;/strong&gt; drive&lt;/li&gt;
&lt;/ol&gt;
All I need now is to add the script in my crontab and let it run every hour.

&lt;strong&gt;NOTE&lt;/strong&gt;: The first time you will run the script you will need to do it manually (not in a cron job). The reason behind it is that the first time rsync will need to download all the contents of the source servers/folders in the &lt;strong&gt;/storage&lt;/strong&gt; drive so as to create an exact mirror. Once that lengthy step is done, the script can be added in the crontab. Subsequent runs of the script will download only the changed/deleted files.
&lt;br /&gt;
&lt;br /&gt;
This method can be very effective while not using a ton of bandwidth every hour. I have used this method for the best part of a year now and it has saved me a couple of times.
&lt;br /&gt;
&lt;br /&gt;
The last thing I need to present you is the backup script that I have for my databases. As you notice above the source folder of beryllium.niden.net as far as databases are concerned is &lt;strong&gt;beryllium.niden.net/db/&lt;/strong&gt;. What I do is I dump and zip the databases every hour on my servers. Although this is not a very efficient way of doing things and it adds to the bandwidth consumption every hour (since the dump will create a new file every hour) I have the following script running on my database servers every hour at the 45th minute:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;#!/bin/bash

DBUSER=mydbuser
DBPASS='dbpassword'
DBHOST=localhost
BACKUPFOLDER="/niden_backup"
DBNAMES="`mysql --user=$DBUSER --password=$DBPASS --host=$DBHOST --batch --skip-column-names -e "show databases"| sed 's/ /%/g'`"
OPTIONS="--quote-names --opt --compress "

# Clear the backu folder
rm -fR $BACKUPFOLDER/db/*.*

for i in $DBNAMES; do
    echo Dumping Database: $i
    mysqldump --user=$DBUSER --password=$DBPASS --host=$DBHOST $OPTIONS $i &amp;gt; $BACKUPFOLDER/db/$i.sql
    tar cvfz $BACKUPFOLDER/db/$i.tqz $BACKUPFOLDER/db/$i.sql
    rm -f $BACKUPFOLDER/db/$i.sql
done&lt;/pre&gt;
&lt;br /&gt;
That's it.
&lt;br /&gt;
&lt;br /&gt;
The backup script can be found in my GitHub&amp;nbsp;&lt;a href="https://github.com/niden/Hourly_Backup_Linux"&gt;here&lt;/a&gt;.
&lt;br /&gt;
&lt;br /&gt;
Update: The metric units for the drives were GB not MB. Thanks to &lt;a href="http://www.codeutopia.net/"&gt;Jani Hartikainen&lt;/a&gt; for pointing it out.&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/1MzZRSLNZV0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/9181400557699761009/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2010/08/create-inexpensive-hourly-remote-backup.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/9181400557699761009?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/9181400557699761009?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/1MzZRSLNZV0/create-inexpensive-hourly-remote-backup.html" title="Create an inexpensive hourly remote backup [How-To]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-u05g2O8FUvU/Tr1tS_Hv6LI/AAAAAAAAFuk/NblYWiD58Yw/s72-c/remote_backup.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2010/08/create-inexpensive-hourly-remote-backup.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08DRHk8eyp7ImA9WhRSEEk.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-2431998592010948917</id><published>2010-08-18T13:17:00.000-04:00</published><updated>2011-11-11T16:11:15.773-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-11T16:11:15.773-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Information" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Apps" /><title>Google Apps and Google Accounts merge [Information][How-To]</title><content type="html">&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-mTZTSrlAoVU/Tr1yc7fWhlI/AAAAAAAAFvs/-Xf-jfj6KfY/s1600/google-apps-logo-150x150.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-mTZTSrlAoVU/Tr1yc7fWhlI/AAAAAAAAFvs/-Xf-jfj6KfY/s1600/google-apps-logo-150x150.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
Anyone that has a Google Apps account and wants to access other services like &lt;a href="http://reader.google.com/"&gt;Google Reader&lt;/a&gt;, &lt;a href="http://voice.google.com/"&gt;Google Voice&lt;/a&gt; etc. knows that the username and password of the Google Apps account does not work for these services, since&amp;nbsp;those are not available for Google Apps accounts. To get around this limitation, what you could do (and what I have done in the past) is to sign up for a new Google Account with the same email address as your Google Apps account. &amp;nbsp;This of course creates confusion at times, duplication of data and&amp;nbsp;disassociation&amp;nbsp;of services. The most obvious example of this is on an Android device. To use my phone, I need to sign in with my Google Apps account. However, to use my Google Voice number, I &amp;nbsp;have to use sign in again for that service but now using my Google Account (which uses the same email address). This works but I still have to keep two sets of contacts - one for the Google Apps and one in Google Voice.
&lt;br /&gt;
&lt;br /&gt;
According to Google, &lt;a href="http://productideas.appspot.com/#25/e=2199b"&gt;9 of the top 20 requests&lt;/a&gt; from Google Apps customers are for their accounts to work with more services from Google. To facilitate this, the Google Apps account had to be merged with the Google Account. This page in Google Apps Help that&amp;nbsp;&lt;a href="http://www.google.com/support/accounts/bin/answer.py?hl=en&amp;amp;answer=182174"&gt;describes&lt;/a&gt; the transition.
&lt;br /&gt;
&lt;br /&gt;
Unfortunately this was not a simple flip of a switch for Google. &lt;a href="http://googleenterprise.blogspot.com/2010/05/more-google-applications-coming-for.html"&gt;Significant infrastructure changes&lt;/a&gt; had to be in place prior to the merging of the accounts. We were promised that this change would be in place by fall and we have not been disappointed. Google is rolling out the update slowly but if you want to 'speed up' the process, you can &lt;a href="https://spreadsheets1.google.com/a/google.com/viewform?hl=en&amp;amp;formkey=dGdfTTA2eGhFT0c0SDVLXzMzMFNwUUE6MA#gid=0"&gt;sign up&lt;/a&gt; for an early round of testing of the new infrastructure. I have signed up for several of my Google Apps domains a couple of weeks ago.
&lt;br /&gt;
&lt;br /&gt;
I am happy to announce that one of my domains has already gone through the merge process. The domain is&amp;nbsp;&lt;a href="http://wwww.beautyandthegeek.it/"&gt;BeautyAndTheGeek.IT&lt;/a&gt; which is a Google Apps Premier edition. My other domains that are hosted in Google Apps have not transitioned yet and I suspect that Google is first merging Premium accounts, then Educational, Government and it will finish up with the Standard edition of Google Apps, which kind of makes sense (paid customers first!).
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Email Invitation
&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-eQuK_0CsPMo/Tr1oAlGyzCI/AAAAAAAAFuE/p-UJPJEpIUA/s1600/8-Google-Apps-Initial-Email.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="305" src="http://3.bp.blogspot.com/-eQuK_0CsPMo/Tr1oAlGyzCI/AAAAAAAAFuE/p-UJPJEpIUA/s320/8-Google-Apps-Initial-Email.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Invitation email to the Admin&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
The day before yesterday I received an email inviting me to participate in the new infrastructure change and merge the Google Apps accounts with the Google Accounts of my users.&lt;br /&gt;
&lt;br /&gt;
The merge process works as follows:
&lt;br /&gt;
&lt;br /&gt;
- If your Google Apps account email address &lt;strong&gt;&lt;span style="text-decoration: underline;"&gt;has never been used&lt;/span&gt;&lt;/strong&gt; to access Google products such as Google Reader, Google Voice, Google Code etc. then your Google Apps account will be converted to a Google Account.
&lt;br /&gt;
&lt;br /&gt;
- If your Google Apps account email address &lt;strong&gt;&lt;span style="text-decoration: underline;"&gt;has been used&lt;/span&gt;&lt;/strong&gt; to access Google products such as Google Reader, Google Voice, Google Code etc. then your Google Apps account is conflicting with your Google Account. This&amp;nbsp;&lt;a href="http://www.google.com/support/a/bin/answer.py?answer=184937"&gt;page&lt;/a&gt; in Google Apps Administrator Help provides an overview of conflicting accounts. In short both of your accounts will merge and the data will be associated with your Google Apps account, which will now be the same as your Google Account.
&lt;br /&gt;
&lt;br /&gt;
There are certain limitations to this merge (as one would expect :)). These are as follows:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;If the offline feature is enabled in GMail, the user has to synchronize before the transition. If they don't, they will lose their offline messages.&lt;/li&gt;
&lt;li&gt;Offline Google Calendar doesn't work for Google Apps accounts that have transitioned.&lt;/li&gt;
&lt;li&gt;The following Google products don't work with Google Apps accounts that have transitioned:
&lt;ul&gt;
&lt;li&gt;Android Market for Developers&lt;/li&gt;
&lt;li&gt;Google Extra Storage (bummer)&lt;/li&gt;
&lt;li&gt;Health (bummer again)&lt;/li&gt;
&lt;li&gt;PowerMeter&lt;/li&gt;
&lt;li&gt;Profiles&lt;/li&gt;
&lt;li&gt;Web History&lt;/li&gt;
&lt;li&gt;YouTube&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The purchase of additional Google Storage is currently unavailable for Google Apps accounts. No storage can be purchased for a Google Apps account, although it will be available later this year. If a user account has already purchased extra storage (like I have) in an existing Google Account, the storage will remain in that account and will not transfer to the respective Google Apps account after the transition.&lt;/li&gt;
&lt;li&gt;Delegating email only works with the same account type (i.e. Google Apps)&lt;/li&gt;
&lt;li&gt;Any Picasa Web Album, Profile, or Wave usernames cannot be moved from an existing account to your Google Apps account.&lt;/li&gt;
&lt;/ol&gt;
Resources:&amp;nbsp;&lt;a href="http://www.google.com/support/a/bin/answer.py?answer=182034"&gt;Transition readiness checklist&lt;/a&gt;, &lt;a href="http://www.google.com/support/a/bin/answer.py?answer=184937"&gt;Conflicting accounts&lt;/a&gt;, &lt;a href="http://www.google.com/support/a/bin/answer.py?answer=181872"&gt;Early adopters&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.google.com/support/a/bin/answer.py?answer=172732"&gt;Additional storage for Google Apps&lt;/a&gt;, &lt;a href="http://www.google.com/support/a/bin/topic.py?topic=28917"&gt;Google Apps Administrator Help Center&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The process
&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - Dashboard Warning&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-JXAri_5DQKQ/Tr1oq2HKljI/AAAAAAAAFuU/Ohs7V1I6Oos/s1600/0-Google-Apps-Warning.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="67" src="http://3.bp.blogspot.com/-JXAri_5DQKQ/Tr1oq2HKljI/AAAAAAAAFuU/Ohs7V1I6Oos/s320/0-Google-Apps-Warning.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Dashboard Warning&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Once I logged in my Google Apps Administration area, the Dashboard presented a new notice at the top. In short the notice states that new services will be available to the Google Apps accounts. This will be achieved with the transition of the Google Apps Account to a Google Account. The update is free and Google will automatically roll it out on &lt;strong&gt;September 30, 2010&lt;/strong&gt;.&lt;br /&gt;
&lt;br /&gt;
Resource: &lt;a href="http://www.google.com/apps/intl/en/business/index.html"&gt;Google Apps core suite&lt;/a&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - Understand Transition&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-yZUCAtPAb58/Tr1n-lLKmPI/AAAAAAAAFtM/uGIRZd6D4As/s1600/1-Google-Apps-Understand-Transition.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="102" src="http://4.bp.blogspot.com/-yZUCAtPAb58/Tr1n-lLKmPI/AAAAAAAAFtM/uGIRZd6D4As/s320/1-Google-Apps-Understand-Transition.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Understanding the transition&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Clicking the "&lt;strong&gt;Get Started&lt;/strong&gt;" button at the bottom of the notice will start a wizard that helps with the transition of the domain's accounts to Google accounts of your users (some or all). The first screen contains information that aids in understanding the transition and what is involved. &amp;nbsp; Moving to the new infrastructure will update your control panel and give you control over which Google services your users can access with their accounts.&lt;br /&gt;
&lt;br /&gt;
Resource: &lt;a href="http://www.google.com/support/a/bin/answer.py?answer=182034"&gt;Transition readiness checklist&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.google.com/support/a/bin/topic.py?topic=28917"&gt;Google Apps Administrator Help Center&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - New Services&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-t7-9HjpiQHA/Tr1n-1egzwI/AAAAAAAAFtU/p6cCsrKS4Y4/s1600/2-Google-Apps-Review-New-Services.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="134" src="http://1.bp.blogspot.com/-t7-9HjpiQHA/Tr1n-1egzwI/AAAAAAAAFtU/p6cCsrKS4Y4/s320/2-Google-Apps-Review-New-Services.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Review New Services&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Based on the services that are available for your domain user accounts, the screen above might be slightly different. It does however give you (the administrator) the ability to enable or disable services for your users. Note that turning off a service will disallow your users to sign up for that service using their Google Apps account for your domain. It will however not stop them from using a totally different Google Account (personal for instance) to access that service.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - Notify Conflicting Accounts&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-TeXdiNbjvBw/Tr1n_ZGBRZI/AAAAAAAAFtc/toBdGEdrBK4/s1600/3-Google-Apps-Notify-Conflicting-Accounts.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="99" src="http://2.bp.blogspot.com/-TeXdiNbjvBw/Tr1n_ZGBRZI/AAAAAAAAFtc/toBdGEdrBK4/s320/3-Google-Apps-Notify-Conflicting-Accounts.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Notify Conflicting Accounts&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Confirming the new services, the wizard will display a new screen, that will allow you to notify those users that have conflicting accounts. This screen provides information on how many users have conflicting accounts (in my case only 1), what happens to those accounts (the merge process) and what should you do as the administrator. Google will not provide information on which of your users have conflicting accounts. What they will however do, is offer you a temporary email address and an email template, that you can use to email your users. The email template is shown in the "&lt;em&gt;Google Apps - Email to User&lt;/em&gt;"&amp;nbsp;section below.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - Select Users&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-eDp78iqlgZk/Tr1n_gCrheI/AAAAAAAAFtk/4GVaq38K4LM/s1600/4-Google-Apps-Select-Users.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="92" src="http://3.bp.blogspot.com/-eDp78iqlgZk/Tr1n_gCrheI/AAAAAAAAFtk/4GVaq38K4LM/s320/4-Google-Apps-Select-Users.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Select Users&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Clicking "&lt;strong&gt;Continue&lt;/strong&gt;", brings up the User Selection screen. Here the administrator can select whose account will be transitioned for now. You can choose to pilot test this transition with a small set of users or everyone. The difference is that if you choose a small set of users to pilot test this transition, you can revert their accounts back to what they were prior to this step. If you choose "&lt;em&gt;Everyone&lt;/em&gt;" from this screen, the change will be across the organization and cannot be undone. Reminder here that the transition will happen either way by the end of September, 2010. Finally you have the option to inform the users when their account transition is complete. This generates an email (in English) to the user with relevant information. You can see the email in section "&lt;em&gt;Google Apps - Email to User&lt;/em&gt;" below.&lt;br /&gt;
&lt;br /&gt;
Resource: &lt;a href="http://www.google.com/support/a/bin/answer.py?answer=181872"&gt;Early adopters&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - Confirmation&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3OXb8vEHTZ8/Tr1n_3pyJ3I/AAAAAAAAFts/uKNEeU60ANI/s1600/5-Google-Apps-Confirm.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="156" src="http://3.bp.blogspot.com/-3OXb8vEHTZ8/Tr1n_3pyJ3I/AAAAAAAAFts/uKNEeU60ANI/s320/5-Google-Apps-Confirm.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Confirmation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Clicking "&lt;strong&gt;Continue&lt;/strong&gt;"again will bring up the final screen which is the confirmation page. Google is very thorough and I like the confirmation screen. The information in this screen is a summary of what the wizard collected in previous steps. You (the administrator) will have to confirm in several places that you have understood the process, read the agreement and then accept the whole process. Clicking "&lt;strong&gt;I accept. Start the transition.&lt;/strong&gt;" will make things happen :)&lt;br /&gt;
&lt;br /&gt;
Resources: &lt;a href="http://www.google.com/support/a/bin/answer.py?answer=182034"&gt;Transition readiness checklist&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;&lt;i&gt;Google Apps - Transition in progress&lt;/i&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-tTcM-E4r6VQ/Tr1p_OKYkSI/AAAAAAAAFuc/PKNxe3YxI00/s1600/6-Google-Apps-Transition-In-Progress.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="27" src="http://3.bp.blogspot.com/-tTcM-E4r6VQ/Tr1p_OKYkSI/AAAAAAAAFuc/PKNxe3YxI00/s320/6-Google-Apps-Transition-In-Progress.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Transition in Progress&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Navigating back to the dashboard, you will see a message notifying you that there is a transition in place for the accounts of your domain and can take up to 24 hours to be completed. This serves as a reminder on what has just happened. The notice will disappear once the transition is completed.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps - Email to User&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-PDLt5BB8C4Q/Tr1oAJHFcrI/AAAAAAAAFt0/OaMslUMWQEQ/s1600/7-Google-Apps-Email-To-User.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="221" src="http://4.bp.blogspot.com/-PDLt5BB8C4Q/Tr1oAJHFcrI/AAAAAAAAFt0/OaMslUMWQEQ/s320/7-Google-Apps-Email-To-User.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Email to User&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
If you (the administrator) chose to notify your users of this transition (see step &lt;em&gt;Select Users&lt;/em&gt; above), they will receive an email like the one shown above. If you chose to notify your users with a different message then that message should have been sent to the temporary email address that Google has provided (see step &lt;em&gt;Notifying Conflicting Accounts&lt;/em&gt; above)&lt;br /&gt;
&lt;br /&gt;
Resources: &lt;a href="http://www.google.com/support/accounts/bin/answer.py?answer=181692"&gt;Data ownership&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.google.com/support/a/bin/answer.py?answer=182034"&gt;Transition readiness checklist&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.google.com/support/a/bin/topic.py?topic=28917"&gt;Google Apps Administrator Help Center&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps -&amp;nbsp;Email to User after Transition&lt;/i&gt;&lt;/b&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-8mqZ84yRvpg/Tr1oAaRbGUI/AAAAAAAAFt8/8G9gPXaLb4Y/s1600/7-Google-Apps-Email-To-User-After.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="297" src="http://2.bp.blogspot.com/-8mqZ84yRvpg/Tr1oAaRbGUI/AAAAAAAAFt8/8G9gPXaLb4Y/s320/7-Google-Apps-Email-To-User-After.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Email to User after transition&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Finally another email will be sent to the user summarizing what has happened with their account. This email contains links to additional resources.&lt;br /&gt;
&lt;br /&gt;
Resources: &lt;a href="http://www.google.com/options/"&gt;List of products&lt;/a&gt;, &lt;a href="http://www.google.com/support/accounts/bin/answer.py?answer=181703"&gt;New sign in option&lt;/a&gt;, &amp;nbsp;&lt;a href="http://www.google.com/support/accounts/bin/answer.py?answer=181692"&gt;Data ownership&lt;/a&gt;, &lt;a href="http://www.whatbrowser.org/en/"&gt;What is a browser?&lt;/a&gt;, &lt;a href="http://www.google.com/support/accounts/bin/answer.py?hl=en&amp;amp;answer=182343"&gt;Using multiple accounts&lt;/a&gt;, &lt;a href="http://www.google.com/support/a/bin/topic.py?topic=28917"&gt;Help Center for Admins&lt;/a&gt;, &lt;a href="http://www.google.com/support/a/bin/topic.py?topic=28917"&gt;Google Apps Administrator Help Center&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Google Apps -&amp;nbsp;User Login message for Account Merge&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;div style="text-align: center;"&gt;
&lt;/div&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-SL3793sD_bQ/Tr1oAxeKJHI/AAAAAAAAFuM/4fGs6KuEkBc/s1600/9-Google-Apps-User-Perspective.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="122" src="http://1.bp.blogspot.com/-SL3793sD_bQ/Tr1oAxeKJHI/AAAAAAAAFuM/4fGs6KuEkBc/s320/9-Google-Apps-User-Perspective.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Message to user after login for account merge&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Once the transition operation is completed, the next time the user logs in their account, they will see the screen above. In short this screen informs the user of what has happened and provides links to online resources for help. The user must click "&lt;strong&gt;I accept. Continue to my account.&lt;/strong&gt;" for them to have access to their account. This is &amp;nbsp;a one off process.
&lt;br /&gt;
&lt;br /&gt;
Resources: &lt;a href="http://www.google.com/support/a/bin/topic.py?topic=28917"&gt;Google Apps Administrator Help Center&lt;/a&gt;, &lt;a href="http://www.google.com/support/a/bin/answer.py?hl=en&amp;amp;answer=60762"&gt;Google Security and Privacy&lt;/a&gt;, &lt;a href="http://www.google.com/support/accounts/bin/answer.py?hl=en&amp;amp;answer=182343"&gt;Using multiple accounts&lt;/a&gt;, &lt;a href="http://www.google.com/apps/intl/en/terms/user_terms.html"&gt;Terms of Service&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
Without a doubt this is a huge step forward for Google and for us as admins or users. The ability to have a single sign on to use their products not only helps them but us by simplifying everything. That also means that if a hacker guesses that my password is "&lt;strong&gt;password1&lt;/strong&gt;" they have access to all my Google related services... but oh well :)&lt;br /&gt;
&lt;br /&gt;
Joking apart though, I am really excited that this change has finally occurred. I am guessing that the next steps would be to give users or admins the ability&amp;nbsp;to purchase additional storage for a single account is something that in my view will make quite a lot of money for Google. A lot of people will want to keep all their emails and are nearing or have reached their email quota. Administrators would be happy to pay for certain users that have&amp;nbsp;depleted their email storage allocation without converting their whole Google Apps edition to the Premier one. After all, if your organization has 100 users and 5 of them are near capacity, even if you pay $20 per user it will cost $100 per year. If however the whole account is changed to a Premier account (with 25Gb per mailbox) the cost will go up to $5,000.
&lt;br /&gt;
&lt;br /&gt;
Some questions come to mind:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Will the user be able to purchase storage for all services or just for email?&lt;/li&gt;
&lt;li&gt;Will the 1Gb limit from Docs be lifted and will it be combined with storage for mail?&lt;/li&gt;
&lt;li&gt;$20 a year gives you 80Gb in Picasa. Will Google follow that model or change it? According to the &lt;a href="http://www.google.com/support/a/bin/answer.py?answer=172732"&gt;Additional storage for Google Apps&lt;/a&gt; page it appears that it will be more expensive now to have more space.&lt;/li&gt;
&lt;li&gt;If storage is so cheap (Picasa) will it be extended to Google Documents? It appears that 7.5Gb is enough for the vast majority of users as far as email is concerned, but 1Gb for Docs is not enough. For a society moving towards a full electronic document storage this could help a lot.&lt;/li&gt;
&lt;/ol&gt;
Still a long way to go until everything clears up. I am not sure that Google has all the answers yet but they are moving forward and this is the most encouraging news of all!&lt;img src="http://feeds.feedburner.com/~r/niden/~4/3I1ZsS0ACDI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/2431998592010948917/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2010/08/google-apps-and-google-accounts-merge.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2431998592010948917?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2431998592010948917?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/3I1ZsS0ACDI/google-apps-and-google-accounts-merge.html" title="Google Apps and Google Accounts merge [Information][How-To]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-mTZTSrlAoVU/Tr1yc7fWhlI/AAAAAAAAFvs/-Xf-jfj6KfY/s72-c/google-apps-logo-150x150.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2010/08/google-apps-and-google-accounts-merge.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08HRX04fip7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-1642939403445780163</id><published>2010-08-01T13:11:00.000-04:00</published><updated>2012-09-27T14:17:14.336-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:17:14.336-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Gentoo" /><category scheme="http://www.blogger.com/atom/ns#" term="Subversion" /><category scheme="http://www.blogger.com/atom/ns#" term="Linux" /><category scheme="http://www.blogger.com/atom/ns#" term="How-To" /><category scheme="http://www.blogger.com/atom/ns#" term="Backup" /><title>Subversion Backup [How-To]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
I will start this post once again with the words of a wise man:
&lt;br /&gt;
&lt;blockquote&gt;
&lt;i&gt;There are two kinds of people, those who backup regularly, and those that never had a hard drive fail&lt;/i&gt;&lt;/blockquote&gt;
So the moral of the story here is &lt;strong&gt;backup often&lt;/strong&gt;. If something is to happen, the impact on your operations will be minimal if your backup strategy is in place and operational.
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-NmcW5e17XbE/Tr1mJtrS3mI/AAAAAAAAFtE/pfdFLYyrku4/s1600/online-backup.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="132" src="http://1.bp.blogspot.com/-NmcW5e17XbE/Tr1mJtrS3mI/AAAAAAAAFtE/pfdFLYyrku4/s200/online-backup.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
There are a lot of backup scenarios and strategies. Most of them suggest a backup once a day, usually at the early hours of the day. This however might not work very well with a fast paced environment where data changes several times per hour. This kind of environment is usually a software development one.
&lt;br /&gt;
&lt;br /&gt;
If you have chosen &lt;a href="http://subversion.tigris.org/"&gt;Subversion&lt;/a&gt; to be your software version control software then you will need a backup strategy for your repositories. Since the code changes very often, this strategy cannot rely on the daily backup schedule. The reason being is that, in software, a day's worth of work usually costs a lot more than the actual daily rate of the programmers.
&lt;br /&gt;
&lt;br /&gt;
Below are some of the scripts I have used over the years for my incremental backups, that I hope will help you too. You are more than welcome to copy and paste the scripts and use them &amp;nbsp;or modify them to suit your needs. Please note though that the scripts are provided as is and that you must check your backup strategy with a full backup/restore cycle. I cannot assume responsibility of something that might happen in your system.
&lt;br /&gt;
&lt;br /&gt;
Now that the 'legal' stuff are out of the way, here are the different strategies that you can adopt. :)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;svn-hot-backup
&lt;/b&gt;&lt;br /&gt;
This is a script that is provided with &lt;a href="http://subversion.tigris.org/"&gt;Subversion&lt;/a&gt;. It copies (and compresses if requested) the whole repository to a specified location. This technique allows for a full copy of the repository to be moved to a different location. The target location can be a resource on the local machine or a network resource. You can also backup on the local drive and then as a next step transfer the target files to an offsite location with &lt;a href="http://en.wikipedia.org/wiki/File_Transfer_Protocol"&gt;FTP&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Secure_copy"&gt;SCP&lt;/a&gt;, &lt;a href="http://www.samba.org/rsync/"&gt;RSync&lt;/a&gt; or any other mechanism you prefer.&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;#!/bin/bash

# Grab listing of repositories and copy each
# repository accordingly

SVNFLD="/var/svn"
BACKUPFLD="/backup"

# First clean up the backup folder
rm -f $BACKUPFLD/*.*

for i in $(ls -1v $SVNFLD); do
    if [ $i != 'conf' ]; then
        /usr/bin/svn-hot-backup --archive-type=bz2 $SVNFLD/$i $BACKUPFLD
    fi
done&lt;/pre&gt;
&lt;br /&gt;
This script will create a copy of each of your repositories and compress it as a bz2 file in the target location. Note that I am filtering for 'conf'. The reason being is that I have a conf file with some configuration scripts in the same SVN folder. You can adapt the script to your needs to include/exclude repositories/folders as needed.
&lt;br /&gt;
&lt;br /&gt;
This technique gives the ability to immediately restore a repository (or more than one) by changing the configuration file of SVN to point to the backup location. If you run the script every hour or so then your downtime and loss will be minimal, should something happens.
&lt;br /&gt;
&lt;br /&gt;
There are some configuration options that you can tweak by editing the actual svn-hot-backup script. In Gentoo it is located under &lt;em&gt;/usr/bin/&lt;/em&gt;. The default number of backups (&lt;em&gt;num_backups&lt;/em&gt;) that the script will keep is 64. You can choose 0 to 'keep them all' but you can adjust it according to your storage or your backup strategy.
&lt;br /&gt;
&lt;br /&gt;
One last thing to note is that you can change the compression mechanism by changing the parameter of the &lt;em&gt;--archive-type&lt;/em&gt; option. The compression types supported are gz (.tar.gz), bz2 (.tar.bz2) and zip (.zip)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Full backup using dump
&lt;/b&gt;&lt;br /&gt;
This method is similar to the svn-hot-backup. It works by 'dumping' the repository in a portable file format and compressing it.&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;#!/bin/bash

# Grab listing of folders and dump each
# repository accordingly

SVNFLD="/var/svn"
BACKUPFLD="/backup"

# First clean up the backup folder
rm -f $BACKUPFLD/svn/*.*

for i in $(ls -1v $SVNFLD); do
    if [ $i != 'conf' ]; then
        svnadmin dump $SVNFLD/$i/ &amp;gt; $BACKUPFLD/$i.svn.dump
        tar cvfz $BACKUPFLD/svn/$i.tgz $BACKUPFLD/$i.svn.dump
        rm -f $BACKUPFLD/$i.svn.dump
    fi
done&lt;/pre&gt;
&lt;br /&gt;
As you can see, this version does the same thing as the &lt;em&gt;svn-hot-backup&lt;/em&gt;. It does however give you a bit more control over the whole backup process and allows for a different compression mechanism - since the compression happens on a separate line in the script.
&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;NOTE:&lt;/strong&gt; If you use the &lt;em&gt;hotcopy&lt;/em&gt; parameter in &lt;em&gt;svnadmin&lt;/em&gt; (&lt;em&gt;svnadmin hotcopy .....&lt;/em&gt;) you will effectively be duplicating the behavior of &lt;em&gt;svn-hot-backup&lt;/em&gt;.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Incremental backup using dump based on revision
&lt;/b&gt;&lt;br /&gt;
This last method is what I use at work. We have our repositories backed up externally and we rely on the backup script to have everything backed up and transferred to the external location within an hour, since our backup strategy is an hourly backup. We have discovered that sometimes the size of a repository can cause problems with the transfer, since the Internet line will not be able to transfer the files across in the allocated time. This happened once in the past with a repository that ended up being 500Mb (don't ask :)).&lt;br /&gt;
&lt;br /&gt;
So in order to minimize the upload time, I have altered the script to dump each repository's revision in a separate file. Here is how it works:
&lt;br /&gt;
&lt;br /&gt;
We backup using rsync. This way the 'old' files are not being transferred.
&lt;br /&gt;
&lt;br /&gt;
Every hour the script loops through each repository name and does the following:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Checks if the *&lt;em&gt;.latest&lt;/em&gt; file exists in the &lt;em&gt;svn-latest&lt;/em&gt; folder. If no, then it sets the &lt;em&gt;LASTDUMP&lt;/em&gt; variable to 0.&lt;/li&gt;
&lt;li&gt;If the file exists, it reads it and obtains the number stored in that file. It then stores that number incremented by 1 in the &lt;em&gt;LASTDUMP&lt;/em&gt; variable.&lt;/li&gt;
&lt;li&gt;Checks the number of the latest revision and stores it in the &lt;em&gt;LASTVERSION&lt;/em&gt; variable&lt;/li&gt;
&lt;li&gt;It loops through the repository, dumps each revision (&lt;em&gt;LASTDUMP&lt;/em&gt; to &lt;em&gt;LASTVERSION&lt;/em&gt;) and compresses it&lt;/li&gt;
&lt;/ol&gt;
This method creates new files every hour so long as new code has been added in each repository via the checkin process. The rsync command will then pick only the new files and nothing else, therefore the data transferred is reduced to a bare minimum allowing easily for hourly external backups. With this method we can also restore a single revision in a repository if we need to.
&lt;br /&gt;
&lt;br /&gt;
The script that achieves that is as follows:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre lang="bash" line="1"&gt;#!/bin/bash

# Grab listing of folders and dump each
# repository accordingly

SVNFLD="/var/svn"
BACKUPFLD="/backup"
CHECKFLD=$BACKUPFLD/svn-latest

for i in $(ls -1v $SVNFLD); do
    if [ $i != 'conf' ]; then
        # Find out what our 'start' will be
        if [ -f $CHECKFLD/$i.latest ]
        then
            LATEST=$(cat $CHECKFLD/$i.latest)
            LASTDUMP=$LATEST+1
        else
            LASTDUMP=0
        fi

        # This is the 'end' for the loop
        LASTREVISION=$(svnlook youngest $SVNFLD/$i/)

        for ((r=$LASTDUMP; r&amp;lt; =$LASTREVISION; r++ )); do
            svnadmin dump $SVNFLD/$i/ --revision $r &amp;gt; $BACKUPFLD/$i-$r.svn.dump
            tar cvfz $BACKUPFLD/svn/$i-$r.tgz $BACKUPFLD/$i-$r.svn.dump
            rm -f $BACKUPFLD/$i-$r.svn.dump
            echo $r &amp;gt; $CHECKFLD/$i.latest
        done
    fi
done&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
You must &lt;strong&gt;always&lt;/strong&gt; backup your data. The frequency is dictated by the rate that your data updates and how critical your data is. I hope that the methods presented in this blog post will complement your programming and source control should you choose to adopt them.&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/zj6PNLqKadw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/1642939403445780163/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2010/08/subversion-backup-how-to.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/1642939403445780163?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/1642939403445780163?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/zj6PNLqKadw/subversion-backup-how-to.html" title="Subversion Backup [How-To]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-NmcW5e17XbE/Tr1mJtrS3mI/AAAAAAAAFtE/pfdFLYyrku4/s72-c/online-backup.jpg" height="72" width="72" /><thr:total>4</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2010/08/subversion-backup-how-to.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08ASXo7fyp7ImA9WhJbF0s.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-2324093775228311902</id><published>2010-06-21T12:31:00.000-04:00</published><updated>2012-09-27T14:17:28.407-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-27T14:17:28.407-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Review" /><category scheme="http://www.blogger.com/atom/ns#" term="Froyo" /><category scheme="http://www.blogger.com/atom/ns#" term="Information" /><category scheme="http://www.blogger.com/atom/ns#" term="Android" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="Nexus One" /><title>Android 2.2 (Froyo) [Review]</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;b&gt;Android 2.2 (Froyo)
&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-G5f0ICcJIQk/Tr1dqhQOY4I/AAAAAAAAFqM/cfTM3XIZdRc/s1600/android.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-G5f0ICcJIQk/Tr1dqhQOY4I/AAAAAAAAFqM/cfTM3XIZdRc/s1600/android.png" /&gt;&lt;/a&gt;&lt;/div&gt;
The newest version of the Android Operating System has been codenamed Froyo (as in Frozen&amp;nbsp;Yogurt).
&lt;br /&gt;
&lt;br /&gt;
Although I have heard of the name, I was not following very closely the development of the said OS, so I did not know what to expect. The &lt;a href="http://youtu.be/IY3U2GXhz44"&gt;presentation on Day 2 at Google I/O&lt;/a&gt; was more than impressive so I had to get my hands on it :)
&lt;br /&gt;
&lt;br /&gt;
After the GoogleIO presentation, I was regularly checking (&lt;em&gt;Settings – About Phone – System Updates&lt;/em&gt;) my phone to see what kind of version I am running and whether the update was waiting for me. Unfortunately I was disappointed every time, therefore I resigned to the idea that it will update when it is pushed to my phone.
&lt;br /&gt;
&lt;br /&gt;
A few days later though, reports on the blogosphere started appearing of users now running on Froyo. The update started rolling out so it was a matter of time for me. Most of the reports were coming from California, so if I were to look at the geography, it would take the update quite a bit of time to reach West Virginia :)
&lt;br /&gt;
&lt;br /&gt;
Through my regular research on the Internet, I found a few interesting blog posts that claimed that one can update the Nexus One without waiting for the update to be rolled out. Having nothing to lose, I decided to try it on my phone and see what happens.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Upgrade to Froyo
&lt;/b&gt;&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-64Ed9JOLRtE/Tr1dyEwakhI/AAAAAAAAFqU/ceQP7yuzhv8/s1600/froyo.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-64Ed9JOLRtE/Tr1dyEwakhI/AAAAAAAAFqU/ceQP7yuzhv8/s1600/froyo.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Android 2.2 (Froyo)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Below is how I upgraded my Nexus One to Froyo. I had a couple of failed attempts, loads of Googling but I finally managed to get it to work. Luckily a couple of days later an &lt;a href="http://lifehacker.com/5545347/get-android-22-on-your-nexus-one-without-the-wait"&gt;article&lt;/a&gt; appeared on Lifehacker which confirmed that the steps I took were the correct ones!&lt;br /&gt;
&lt;br /&gt;
I connected my Nexus One to my computer using the USB cable.
&lt;br /&gt;
&lt;br /&gt;
The phone was detected and I mounted the SD card from the phone (bring down the notification area and select the USB connection – select mount to mount the SD card)
&lt;br /&gt;
&lt;br /&gt;
When I performed these steps I had to navigate to a URL on the Android website (see&amp;nbsp;&lt;a href="http://lifehacker.com/5545347/get-android-22-on-your-nexus-one-without-the-wait"&gt;article&lt;/a&gt; on Lifehacker).
&lt;br /&gt;
&lt;br /&gt;
I copied the file and pasted it on my newly mounted drive which was the SD card of my Nexus One
&lt;br /&gt;
&lt;br /&gt;
The file is 45Mb and when the copy was completed, I unmounted the SD card from the computer initially (click the status area and select &lt;em&gt;Safely remove &amp;lt;X&amp;gt; drive&lt;/em&gt;) and then from the phone (from the notification area select &lt;em&gt;USB connection&lt;/em&gt; and select &lt;em&gt;Unmount&lt;/em&gt;)
&lt;br /&gt;
&lt;br /&gt;
The update instructions below are the original work of SimonNWalker and can be referenced &lt;a href="http://forum.androidspin.com/showthread.php?t=2631"&gt;here&lt;/a&gt;.
&lt;br /&gt;
&lt;br /&gt;
With the update file uploaded on the Nexus One, all I had to do is shut down the device so that I can reboot it in recovery mode. The steps I took are as follows:
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-HtjcxbaRLFk/Tr1eU4raRwI/AAAAAAAAFqk/gxbEm2p0cHQ/s1600/reboot3.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="119" src="http://3.bp.blogspot.com/-HtjcxbaRLFk/Tr1eU4raRwI/AAAAAAAAFqk/gxbEm2p0cHQ/s320/reboot3.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 1: Skating Androids&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
I clicked and held the power button until the menu appeared. I selected &amp;nbsp;'Power Off', confirming that I wish the phone to power off.&lt;br /&gt;
&lt;br /&gt;
Once the phone powered off completely, I pressed and held the trackball button down and then pressed the power button. A new screen appeared (which, like myself, you probably have never seen before) with three androids on skateboards at the bottom (Figure 1) and several options at the top (Figure 2).
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-KiIPjPmBjlE/Tr1eUHX1xOI/AAAAAAAAFqc/1e1evlp7Qa4/s1600/reboot1.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-KiIPjPmBjlE/Tr1eUHX1xOI/AAAAAAAAFqc/1e1evlp7Qa4/s320/reboot1.png" width="275" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 2: Android FastBoot&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
I navigated using the power and the volume buttons. I selected the first option (&lt;strong&gt;Bootloader&lt;/strong&gt;) using the volume buttons and pressed the power button to activate it.&lt;br /&gt;
&lt;br /&gt;
A new menu appeared with the following options:
&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;FASTBOOT&lt;/strong&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color: #339966;"&gt;&lt;strong&gt;RECOVERY&lt;/strong&gt;&lt;/span&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;CLEAR STORAGE&lt;/strong&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;&lt;span style="color: blue;"&gt;SIMLOCK&lt;/span&gt;&lt;/strong&gt;
&lt;br /&gt;
&lt;br /&gt;
I navigated to &lt;strong&gt;RECOVERY &lt;/strong&gt;and selected it
&lt;br /&gt;
&lt;br /&gt;
The familiar Nexus One X appeared on the screen. A few seconds later, a little android with a big exclamation mark in a triangle replaced the X.
&lt;br /&gt;
&lt;br /&gt;
The next step I took was to press simultaneously the power button and the volume up button.
&lt;br /&gt;
&lt;br /&gt;
This brought a new menu at the top (Figure 3).
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-HMwSuJ_Jb8I/Tr1e3y86f0I/AAAAAAAAFqs/lvnukVSG-Os/s1600/recovery2.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="119" src="http://2.bp.blogspot.com/-HMwSuJ_Jb8I/Tr1e3y86f0I/AAAAAAAAFqs/lvnukVSG-Os/s320/recovery2.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 3: Recovery&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
The options available were:
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;reboot system now&lt;/li&gt;
&lt;li&gt;apply sdcard:update.zip&lt;/li&gt;
&lt;li&gt;wipe data/factory reset&lt;/li&gt;
&lt;li&gt;wipe cache partition&lt;/li&gt;
&lt;/ul&gt;
Using the trackball, I navigated to the second option and pressed the trackball button down.
&lt;br /&gt;
&lt;br /&gt;
At this point the update started and some information was flashing on the screen for a while, where some files were patched, some deleted, new ones copied and others replaced accordingly. The whole process took roughly 5 minutes from start to finish.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Features that I found&lt;/b&gt;&lt;br /&gt;
&lt;blockquote&gt;
&lt;em&gt;Please note that these are my own observations. Some of the features here might have been present in the previous version of Android (Eclair) and I simply did not notice them. If that is the case please let me know and I will correct the post accordingly.&lt;/em&gt;&lt;/blockquote&gt;
&lt;b&gt;&lt;i&gt;Phone search (Search throughout the phone itself)&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-VEcxKs_UKtU/Tr1fgHkswqI/AAAAAAAAFq0/xf1EoIIPRzc/s1600/search_phone.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="309" src="http://3.bp.blogspot.com/-VEcxKs_UKtU/Tr1fgHkswqI/AAAAAAAAFq0/xf1EoIIPRzc/s320/search_phone.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 4: Phone Search&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This is one of the updates that I just love. Froyo allows you to search a lot of content that is stored on your phone. The content includes your contacts, sms messages, applications, twitter feeds and many more. With all this power it is very easy to find the information you are looking for with the press of a few keys on the virtual keyboard (Figure 4).&lt;br /&gt;
&lt;br /&gt;
Since "&lt;em&gt;with great power comes great responsibility&lt;/em&gt;", Froyo allows you to select what can be searched (Figure 5). Go to &lt;em&gt;Settings - Search - Searchable Items&lt;/em&gt; and you will find the following options:
&lt;br /&gt;
&lt;br /&gt;
- Web (web search, bookmarks and browser history)
&lt;br /&gt;
&lt;br /&gt;
- Aldiko (only if you have it installed)
&lt;br /&gt;
&lt;br /&gt;
- Apps (names of installed applications)
&lt;br /&gt;
&lt;br /&gt;
- Contacts (names of your contacts)
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-4NgpeeO9HnU/Tr1fw0dK64I/AAAAAAAAFq8/tAOlZqhtJ3Q/s1600/search_areas.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="252" src="http://4.bp.blogspot.com/-4NgpeeO9HnU/Tr1fw0dK64I/AAAAAAAAFq8/tAOlZqhtJ3Q/s320/search_areas.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 5: Search Areas&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
- Finance (stock tickers, company names) (only if you have it installed)&lt;br /&gt;
&lt;br /&gt;
- Google Sky Map (only if you have it installed)
&lt;br /&gt;
&lt;br /&gt;
- Messaging (text in your messages)
&lt;br /&gt;
&lt;br /&gt;
- Music (artists, albums and tracks)
&lt;br /&gt;
&lt;br /&gt;
- Twitter (searchable tweets) (only if you have it installed)
&lt;br /&gt;
&lt;br /&gt;
I am sure that the search extends to other applications. Your results might vary based on the apps you have installed.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Tethering - Wi-Fi Hotspot
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-4ETXEXJXKdI/Tr1f_l4tt3I/AAAAAAAAFrE/5ot_sz_G7b4/s1600/hotspot_on.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="252" src="http://2.bp.blogspot.com/-4ETXEXJXKdI/Tr1f_l4tt3I/AAAAAAAAFrE/5ot_sz_G7b4/s320/hotspot_on.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 6: Wi-Fi Hotspot&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
When I first heard about this feature during the &lt;a href="http://youtu.be/IY3U2GXhz44"&gt;presentation of Froyo at Google I/O&lt;/a&gt;, I was really excited.&lt;br /&gt;
&lt;br /&gt;
I spend every day 3 hours on the train, where I mostly work on my notebook. Having the ability to tether my phone and work online is essential. A couple of years back when I bought my first iPhone, I managed to jailbreak it and installed &lt;a href="http://www.iphonemodem.com/"&gt;iPhoneModem&lt;/a&gt; on it to achieve the functionality that I wanted (tethering). It wasn't fancy (after all don't expect miracles with AT&amp;amp;T's EDGE network) but it worked. I could get my emails reply quickly and disconnect.
&lt;br /&gt;
&lt;br /&gt;
When I got my Nexus One, I bought &lt;a href="http://www.junefabrics.com/android/"&gt;PDA Net&lt;/a&gt; to achieve the same result. It worked too but again in a very basic mode but it was draining the battery very fast and was running the phone very hot.
&lt;br /&gt;
&lt;br /&gt;
After I upgraded to Froyo, I uninstalled &lt;a href="http://www.junefabrics.com/android/"&gt;PDA Net&lt;/a&gt; and have been working with the built in functionality ever since.
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-sAoiGdQmNzo/Tr1garpWOgI/AAAAAAAAFrU/Dvgwkc-K1nk/s1600/configure_hotspot.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="205" src="http://2.bp.blogspot.com/-sAoiGdQmNzo/Tr1garpWOgI/AAAAAAAAFrU/Dvgwkc-K1nk/s320/configure_hotspot.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 7: Configure Wi-Fi Hotspot&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
To access the Tethering options, you will need to go to &lt;em&gt;Settings - Wireless &amp;amp; Networks - Tethering &amp;amp; portable hotspot&lt;/em&gt;. The options shown on Figure 6 appear, which allow you to switch the Wi-Fi hotspot on or off. A USB tethering option also exists for those that want to keep their phone charged with the power that comes from their notebook or attached device. Once the hotspot is enabled, a blue icon appears at the top notification bar. The configuration of the hotspot is really easy and can be seen in figures 7 and 8. For those wondering why I chose the SSID of my hotspot to be one of the most infectious (Windows) viruses ever, it is exactly for that reason. Apart from the WPA2 encryption, the name itself is a&amp;nbsp;deterrent&amp;nbsp;for anyone that might get ideas in stealing bandwidth.&lt;br /&gt;
&lt;br /&gt;
Everything works perfectly apart from AT&amp;amp;T's EDGE network, which is really slow. The Nexus One I have can support T-Mobile's 3G network. I haven't switched to T-Mobile yet since there was no need. I fear though that I will not be able to escape the inevitable.
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-YpWF-71N6Aw/Tr1f_6p2E-I/AAAAAAAAFrM/U4x-j_Cj0HY/s1600/hotspot_settings.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-YpWF-71N6Aw/Tr1f_6p2E-I/AAAAAAAAFrM/U4x-j_Cj0HY/s320/hotspot_settings.png" width="215" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 8: Wi-Fi Hotspot settings&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
On June 2nd AT&amp;amp;T issued &lt;a href="http://www.att.com/gen/press-room?pid=4800&amp;amp;cdvn=news&amp;amp;newsarticleid=30854"&gt;a press release&lt;/a&gt; where they showed us once again that they don't give a damn about their customers since they lie, deceive and overcharge for a mediocre (at best) service. You can read the press release and draw your own conclusions but to me it seemed once again a slap in the face.&lt;br /&gt;
&lt;br /&gt;
No more unlimited data plans for new customers, yet it is not clear what will happen to existing customers such as myself. I read somewhere in the blogosphere that those plans will remain as is, but this is AT&amp;amp;T that we are talking about - the same company that claims that calls to 1-800 numbers are free yet they charge you minutes for it.
&lt;br /&gt;
&lt;br /&gt;
I have signed for the unlimited data plan, yet AT&amp;amp;T does not allow me to tether. Why? What difference does it make? They still offer the same crappy EDGE network whether I watch a YouTube video on my phone or on my computer. &amp;nbsp;The answer is in the press release. They want more money. As phones get 'smarter' they phone companies get greedier. John Gruber offers a good &lt;a href="http://daringfireball.net/2010/06/good_and_bad_regarding_att_data_plans"&gt;analysis&lt;/a&gt; of the new AT&amp;amp;T data plans.
&lt;br /&gt;
&lt;br /&gt;
I do not know how this will evolve but I will definitely continue using the Wi-Fi hotspot on my train ride, whether this will be with AT&amp;amp;T or T-Mobile. Perhaps if I change to T-Mobile I will be able to have better coverage. AT&amp;amp;T in West Virginia is not the greatest carrier. Notable is a recent phone conversation that I had with my wife while I was driving (I am wearing the headsets btw) where the line dropped 9 times :(
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Phone/Web buttons at the bottom of every screen and desktop
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-nYxwKgp5_UM/Tr1hEOBqvjI/AAAAAAAAFrc/ibQfed2lW3o/s1600/mainscreen.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-nYxwKgp5_UM/Tr1hEOBqvjI/AAAAAAAAFrc/ibQfed2lW3o/s320/mainscreen.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 9: Phone - Web buttons&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This was something that was needed in my opinion (Figure 9). I was pressing way too many buttons to get to the phone, especially if I was not on the main screen. This little shortcut is very well received and thought of. I do not think that there is enough space for an additional 2 buttons (for the future release of Android) but you never know.&lt;br /&gt;
&lt;br /&gt;
Perhaps a future version will allow you to customize those buttons.
&lt;br /&gt;
&lt;br /&gt;
Android development though has to be very cautious when releasing functionality. I am sure that Apple is checking everything that Android does with a microscope. With their enormous patent book near by, they will not hesitate to sue Android (or Google for that matter) for patent&amp;nbsp;infringement (see &lt;a href="http://www.engadget.com/2010/03/02/apple-sues-htc-for-infringing-20-iphone-patents/"&gt;Apple sues HTC&lt;/a&gt;).
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;More accounts to sync with
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-wM9jvNPNnaQ/Tr1hR89elYI/AAAAAAAAFrk/w952WgMrAbk/s1600/syncaccounts.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-wM9jvNPNnaQ/Tr1hR89elYI/AAAAAAAAFrk/w952WgMrAbk/s320/syncaccounts.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 10: Sync Accounts&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Another area that I noticed improvements, is the synchronization of the online accounts with the phone (Figure 10).&lt;br /&gt;
&lt;br /&gt;
This area can be accessed via &lt;em&gt;Settings - Accounts &amp;amp; sync settings&lt;/em&gt;.
&lt;br /&gt;
&lt;br /&gt;
I noticed that not only my regular GMail and Google Apps accounts appear but also YouTube as well as Twitter. I have installed the official Twitter application so I am pretty sure that this synchronization appears because of that application.
&lt;br /&gt;
&lt;br /&gt;
I have not tried it with Seesmic or any other twitter application, so if you have any additional information please let me know and I will modify this post accordingly.
&lt;br /&gt;
&lt;br /&gt;
I would also be very interested to know which other applications offer synchronization capabilities or take advantage of Android's synchronization API. If you have any other applications that synchronize on your phone, please let me know and I will include it in this post too.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Easy account switch (Email)
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-SLxVoUGa7w4/Tr1hgcEo8SI/AAAAAAAAFrs/clnVkhtdemU/s1600/emailaccountselection1.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="182" src="http://3.bp.blogspot.com/-SLxVoUGa7w4/Tr1hgcEo8SI/AAAAAAAAFrs/clnVkhtdemU/s320/emailaccountselection1.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 11: Email Account Switch&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
When I &lt;a href="http://www.niden.net/2010/02/from-iphone-to-nexus-one-review/"&gt;switched&lt;/a&gt; from an iPhone to an Android based phone, certain things were just "not right", not because there was something wrong with them - it was how I was used to things being done. One of those areas was the email. With the iPhone I was used to a traditional listing of accounts and then once something was selected I would go into the folders and then emails. If I wanted to change accounts I would have to go back two steps and then enter the account that I wanted.&lt;br /&gt;
&lt;br /&gt;
This seems a very logical approach and it is easy to get used to. Although the Android has a better email management interface, it lacked the ability to quickly switch accounts and thus not spend time tapping away going back or forth. In that area the iPhone &lt;em&gt;was &lt;/em&gt;better. Note the was. It was not because the user will tap less times, but because you would tap the back button twice which was located in the same area of the screen at all times. For the Android you had to press the menu button and then select Accounts. This was again the same amount of steps but the iPhone approach felt more natural.
&lt;br /&gt;
&lt;br /&gt;
With Froyo a new button appears at the top right of your email screen which will allow you to quickly go to the account selection screen. This effectively reduces the steps by one.
&lt;br /&gt;
&lt;br /&gt;
Analyzing briefly my emails, I can say that on average I receive 25 emails on my personal account and 35 on my business (I chose the two accounts that I get the most traffic). So if in theory I get two emails every time I check my email, that would mean that I am checking my phone 12 - 17 times a day (assuming again that I get the batches of emails on both accounts at the same time). It would therefore be safe to assume that I check my emails 15 times a day where I need to switch from one account to another.
&lt;br /&gt;
&lt;br /&gt;
So the math gives us:
&lt;br /&gt;
&lt;table style="width: 100%;"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt; Taps &lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt; Average Email Checks per Day&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt; Week &lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt; Month &lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt; Year &lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2&lt;/td&gt;
&lt;td style="text-align: center;"&gt;15&lt;/td&gt;
&lt;td style="text-align: center;"&gt;210&lt;/td&gt;
&lt;td style="text-align: center;"&gt;900&lt;/td&gt;
&lt;td style="text-align: center;"&gt;10,950&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td style="text-align: center;"&gt;1&lt;/td&gt;
&lt;td style="text-align: center;"&gt;15&lt;/td&gt;
&lt;td style="text-align: center;"&gt;105&lt;/td&gt;
&lt;td style="text-align: center;"&gt;450&lt;/td&gt;
&lt;td style="text-align: center;"&gt;5,475&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
Clearly with the above I am using now half of the screen taps than I used to with Android Eclair or with the iPhone. However the this rough calculation shows how much I was tapping in the past prior to Froyo. Goodbye RSI. :)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Colored labels in your email (like GMail)
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-yUIz7IbA-iY/Tr1hrhRXBFI/AAAAAAAAFr0/_qcS1AmcfBg/s1600/coloredlabels.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-yUIz7IbA-iY/Tr1hrhRXBFI/AAAAAAAAFr0/_qcS1AmcfBg/s320/coloredlabels.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 12: Email Colored Labels&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
If you are like me and use Google Apps or GMail, you are by now accustomed to the colored labels on your emails, that you don't know what you have been doing without them all this time.&lt;br /&gt;
&lt;br /&gt;
With Froyo, this functionality is now available in my mobile device allowing me to visually identify emails of high interest.
&lt;br /&gt;
&lt;br /&gt;
For instance, Figure 12 shows my setup. As you can see I mark clients with a green label color and financial institutions (bills mainly) with a red color. When an email reaches my mailbox and is automatically labeled due to a relevant filter, I can easily identify its importance using this color coding.
&lt;br /&gt;
&lt;br /&gt;
Having this functionality on my mobile device is invaluable!
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Easier navigation between emails - New &amp;lt; &amp;gt; buttons appear on the phone to get you from email to email
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Another huge improvement in the navigation part as far as emails are concerned came in the screen where I read a specific email.&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-eT638dbx-Js/Tr1iEgUVgFI/AAAAAAAAFr8/OqfQxYHa-nU/s1600/emailleftrightbuttons.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-eT638dbx-Js/Tr1iEgUVgFI/AAAAAAAAFr8/OqfQxYHa-nU/s320/emailleftrightbuttons.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 13: Email Navigation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
There are two extra buttons at the bottom of that screen which allow navigation to the previous or next message (Figure 13).
&lt;br /&gt;
&lt;br /&gt;
Another great tap saving feature!
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Better voice recognition
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
I had some problems with this feature from time to time primarily due to my accent – a blend of Greek – English British – German and English American. After a few tries on the new system, I can say that there is improvement since it recognized now queries that it had failed in the past.
&lt;br /&gt;
&lt;br /&gt;
The voice search will probably never be able to detect everything that everyone is saying due to the different accents and voices of people but it is getting pretty close to perfect in my case.
&lt;br /&gt;
&lt;br /&gt;
One thing that I love about voice search is the voice navigation. I have purchased the &lt;a href="http://www.google.com/support/android/bin/answer.py?hl=en&amp;amp;answer=178146"&gt;car dock&lt;/a&gt; for my Nexus One and I use the voice navigation almost everywhere I go. Understanding that I want to go to Rockville, MD instead of Rock Creek is awesome!
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Marketplace – Update all, autoupdate for each application
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-x4utcp1_yU4/Tr1iWqNMVJI/AAAAAAAAFsE/i_z_DSx2OY0/s1600/uploadall.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/-x4utcp1_yU4/Tr1iWqNMVJI/AAAAAAAAFsE/i_z_DSx2OY0/s320/uploadall.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 14: Update All&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This feature was missing and was probably one of the ones that were mostly requested by the users. The &lt;strong&gt;Update All&lt;/strong&gt; to the installed applications.&lt;br /&gt;
&lt;br /&gt;
Luckily the Android developers have heard our pleas and Froyo now features an &lt;strong&gt;Update All&lt;/strong&gt; button at the bottom of the &lt;em&gt;Downloads &lt;/em&gt;section in the &lt;em&gt;Marketplace &lt;/em&gt;application (Figure 14).
&lt;br /&gt;
&lt;br /&gt;
Adding to this functionality, the user now has the ability to automatically update selected (or all) applications. When clicking on one of the applications to update, a checkbox appears which allows for automatic updates. If the checkbox is checked, the next time the selected application has an update, the phone will download it and install it.
&lt;br /&gt;
&lt;br /&gt;
There will always be a notification regarding the action in the notification area, but unless you know where the application is coming from (and you trust the source) you should keep this checked off.&amp;nbsp;I know I might be getting a bit paranoid here but that is what I did.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Different notification for Text Messages (trackball)
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
I am not sure if this existed in Eclair but I just noticed it. When an email arrives, the trackball will start glowing briefly in regular intervals with a white color to visually notify me about the email(s) waiting for me.
&lt;br /&gt;
&lt;br /&gt;
If I receive a Google Voice message though, the trackball will still start glowing but this time it will be with a green color. This way I know that a text message is waiting for me.
&lt;br /&gt;
&lt;br /&gt;
I have sent a text message to my AT&amp;amp;T number and did see the trackball glowing but this time it was only white. It appears that the green trackball notification is a feature of Google Voice on Froyo or again it was always a Google Voice feature and I hadn't noticed, at which point I am getting excited for nothing :)
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;New icons for USB Connect/Disconnect and USB debugging
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style="text-align: left;"&gt;
&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-OtnZT6dJt50/Tr1ijHZHZ3I/AAAAAAAAFsM/93BbtitA37s/s1600/usbdebuggingicon.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-OtnZT6dJt50/Tr1ijHZHZ3I/AAAAAAAAFsM/93BbtitA37s/s1600/usbdebuggingicon.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 15: USB Debugging&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
These ones I loved them the first time I saw them. If &amp;nbsp;USB debugging is enabled &amp;nbsp;(&lt;em&gt;Settings - Applications - Development - USB Debugging&lt;/em&gt;), the minute the phone is connected to the computer using the USB cable, a new icon will appear in the notification area (Figure 15). It appears that it is an android bug of sorts :)&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-LdR4lS4m8rY/Tr1i4IlSxYI/AAAAAAAAFsc/jXQ8d-tb8JY/s1600/usbon.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-LdR4lS4m8rY/Tr1i4IlSxYI/AAAAAAAAFsc/jXQ8d-tb8JY/s320/usbon.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 16: USB Off&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-nIhXdvYyn1Q/Tr1i18IOrBI/AAAAAAAAFsU/ZKMeWUKNHic/s1600/usboff.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-nIhXdvYyn1Q/Tr1i18IOrBI/AAAAAAAAFsU/ZKMeWUKNHic/s320/usboff.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 17: USB On&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
When the phone is connected to the computer via the USB cable, the user has the ability to use the SD card as a storage device. Once the relevant entry in the notification bar is tapped, the screen with the "Turn on USB storage" will appear. If I switch the USB storage on, the screen changes slightly (Figures 16 and 17).
&lt;br /&gt;
&lt;br /&gt;
These were two really cool (in my view) new screens that engage the user even more in exploring their device!
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Camera control
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-zhJDIkzzWEY/Tr1jrDTyDiI/AAAAAAAAFsk/ntkXnealsZ4/s1600/camera_controls.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-zhJDIkzzWEY/Tr1jrDTyDiI/AAAAAAAAFsk/ntkXnealsZ4/s320/camera_controls.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 18: Camera Controls&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
A new enhancement appeared in the Camera application (Figure 18).&lt;br /&gt;
&lt;br /&gt;
The new menu that changes position based on the orientation of your phone (horizontal or vertical) allow for zooming, flash control, white balance control, geolocation and exposure.
&lt;br /&gt;
&lt;br /&gt;
The options available are:
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Zoom: 1x, 1.2x, 1.4x, 1.7x, 2x&lt;/li&gt;
&lt;li&gt;Flash mode: Auto, On, Off&lt;/li&gt;
&lt;li&gt;White balance: Auto, Incandescent, Daylight, Fluorescent, Cloudy&lt;/li&gt;
&lt;li&gt;Store location: Off, On&lt;/li&gt;
&lt;li&gt;Focus mode: Auto, Infinity&lt;/li&gt;
&lt;li&gt;Exposure: -2, -1, 0, +1, +2&lt;/li&gt;
&lt;/ul&gt;
Unfortunately these controls only appear when taking photos and not when shooting video. I am sure however that this functionality (and more) will be extended to the video capturing aspect of the camera application.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;Select text
&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-Yk9sTLglaKM/Tr1j6p0RjRI/AAAAAAAAFss/n-zc94K8kqM/s1600/selecttext.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-Yk9sTLglaKM/Tr1j6p0RjRI/AAAAAAAAFss/n-zc94K8kqM/s320/selecttext.png" width="192" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 19: Select Text&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This is an area where the iPhone was far better than the Eclair and unfortunately still is with Froyo. The gap though has decreased significantly.&lt;br /&gt;
&lt;br /&gt;
With Froyo I can now select text from say an email and paste it somewhere else - even a different application. The &lt;strong&gt;Select Text&lt;/strong&gt; option is hidden under the &lt;strong&gt;More&lt;/strong&gt; menu button and once selected, it creates a small mouse pointer. That is the start of where it will start selecting (Figure 20).
&lt;br /&gt;
&lt;br /&gt;
I simply point to the top left area I want to copy from and drag my finger diagonally to end up at the bottom right of the area I want to select. This will select the text in a very appealing pink color and as soon as I lift my finger from the screen it will copy the text on the keyboard (Figure 21).
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-AC-IiVyyNgg/Tr1j8fYo7EI/AAAAAAAAFs0/y6EkHQGIedU/s1600/select_text_1.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="115" src="http://1.bp.blogspot.com/-AC-IiVyyNgg/Tr1j8fYo7EI/AAAAAAAAFs0/y6EkHQGIedU/s320/select_text_1.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 20: Mouse pointer&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
The technique on the iPhone is better but not by much - as I wrote the gap has decreased significantly. On the iPhone you have a magnifying glass where you can pinpoint exactly where you want to start copying (or inserting text - same functionality).
&lt;br /&gt;
&lt;br /&gt;
Again unfortunately this functionality (the one with the magnifying glass for selecting text) is patented by Apple Inc. and will not be seen on an Android based phone but I am sure that the Android developers will come up with something that will give us the same if not better user experience.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;FLASH Support&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Steve Jobs &lt;a href="http://www.apple.com/hotnews/thoughts-on-flash/"&gt;never kept a secret&lt;/a&gt; of his thoughts about Flash support on an iPhone. Although many people were hopeful that something will be worked out with Adobe in the end, Mr. Jobs shut the door on Adobe. Adobe then invested time and resources to work something out and right before they were able to release their solution Apple changed the iPhone Developer License Agreement to allow only applications written in Objective-C, C, C++ or Javascript and executed by the OS Webkit engine. Adobe &lt;a href="http://mashable.com/2010/04/09/apple-adobe-flash-ban/"&gt;had enough&lt;/a&gt; and issued a '&lt;a href="http://theflashblog.com/?p=1888"&gt;screw you apple&lt;/a&gt;' (not officially of course).
&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-WqplGrFA8Bg/Tr1kTjtMtzI/AAAAAAAAFs8/wQ89EzYqUXs/s1600/pacman.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="192" src="http://4.bp.blogspot.com/-WqplGrFA8Bg/Tr1kTjtMtzI/AAAAAAAAFs8/wQ89EzYqUXs/s320/pacman.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 22: Flash on Android - PacMan&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
The&amp;nbsp;&lt;a href="http://youtu.be/IY3U2GXhz44"&gt;presentation on Day 2 at Google I/O&lt;/a&gt; included the news that Flash will be allowed and supported on Android based phones that run Froyo (or newer versions).&lt;br /&gt;
&lt;br /&gt;
I have to admit, the first version of Flash that I installed (it is still in BETA) was really slow. However three versions later, I am happy to announce that it works as well as a desktop machine. I have not tried to load a heavy flash based website but my brother in law's website (&lt;a href="http://www.dnm.gr/"&gt;www.dnm.gr&lt;/a&gt;) loads just fine and you can see all the information that you need to see :)
&lt;br /&gt;
&lt;br /&gt;
I am sure that in the coming months we will see a lot more progress in that area.
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusions
&lt;/b&gt;&lt;br /&gt;
The Android 2.2 (Froyo) is a huge step forward. It provides users with a lot of functionality that transforms &amp;nbsp;a phone to a multifunction communication device. The only thing that we are missing now is proper coverage from the national carriers (AT&amp;amp;T this one is for you) and without having to sell our first born children to pay for the monthly bills (AT&amp;amp;T this one is for you too).&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Update
&lt;/b&gt;&lt;br /&gt;
There has been another blog post on &lt;a href="http://androidandme.com/"&gt;AndroidAndMe&lt;/a&gt; explaining &lt;a href="http://androidandme.com/2010/06/news/new-android-2-2-update-for-the-nexus-one-is-available-now/"&gt;how to update Froyo&lt;/a&gt; to the latest build (this one is a patch not the real thing).&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Update 2010-07-04
&lt;/b&gt;&lt;br /&gt;
Happy 4th of July! It came with the update for Froyo (officially now) on my cellphone. I am now running the FFR91 build (and so does my wife).&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/niden/~4/iFDw3mu-5VA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/2324093775228311902/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2010/06/android-22-froyo-review.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2324093775228311902?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/2324093775228311902?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/iFDw3mu-5VA/android-22-froyo-review.html" title="Android 2.2 (Froyo) [Review]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-G5f0ICcJIQk/Tr1dqhQOY4I/AAAAAAAAFqM/cfTM3XIZdRc/s72-c/android.png" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2010/06/android-22-froyo-review.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMMQnw-eip7ImA9WhRSEE4.&quot;"><id>tag:blogger.com,1999:blog-1710151540605737130.post-9090282409634711290</id><published>2010-05-31T12:21:00.000-04:00</published><updated>2011-11-11T12:28:03.252-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-11T12:28:03.252-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Memorial Day" /><category scheme="http://www.blogger.com/atom/ns#" term="Celebrations" /><category scheme="http://www.blogger.com/atom/ns#" term="Personal" /><title>2010 Memorial Day [USA][Celebrations][Personal]</title><content type="html">Today, the Memorial Day is celebrated across the USA. There are similar celebrations in Greece, not the same day as in the US but remembering and honoring the veterans for their sacrifices for the freedoms we and our families enjoy.

I received an email this morning which I thought was too precious to be archived in my mail folders. Since I hate mass mailing and chain emails, I thought I would duplicate the email here for my archiving but also for everyone else to see.

Please note that I am not the original author of the email and I do not know if it has appeared as a blog post or an article someplace. I do not own the images presented below either so if there is a copyright issue, please let me know and I will remove them immediately. If you know who the original author is, please let me know so that I can give them the mention they deserve.
&lt;br /&gt;
&lt;h2 style="text-align: center;"&gt;


&lt;strong&gt;&lt;span style="color: navy;"&gt;MEMORIAL DAY&lt;/span&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;div style="text-align: center;"&gt;
It is the&amp;nbsp;VETERAN,&amp;nbsp;not the preacher,&amp;nbsp;who has given us freedom of religion.&lt;/div&gt;
&lt;div style="text-align: center;"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-THIK0BPBU30/Tr1aDEfoHvI/AAAAAAAAFpc/ixJa9ceupP4/s1600/pic18762.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="228" src="http://3.bp.blogspot.com/-THIK0BPBU30/Tr1aDEfoHvI/AAAAAAAAFpc/ixJa9ceupP4/s320/pic18762.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-eIn3mxu_F4g/Tr1aERs7czI/AAAAAAAAFqE/qbHAOcpALB0/s1600/pic32591.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="236" src="http://3.bp.blogspot.com/-eIn3mxu_F4g/Tr1aERs7czI/AAAAAAAAFqE/qbHAOcpALB0/s320/pic32591.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
It is&amp;nbsp;the VETERAN,&amp;nbsp;not the reporter,&amp;nbsp;who has given us freedom of the press.&lt;/div&gt;
&lt;div style="text-align: center;"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-IXJ87Dv7Tjk/Tr1aEKoa35I/AAAAAAAAFp8/oxRPnxu50Ik/s1600/pic27624.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="228" src="http://2.bp.blogspot.com/-IXJ87Dv7Tjk/Tr1aEKoa35I/AAAAAAAAFp8/oxRPnxu50Ik/s320/pic27624.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
It is&amp;nbsp;the VETERAN,&amp;nbsp;not the poet,&amp;nbsp;who has given us freedom of speech.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-f8FnKLCN3Vo/Tr1aC35RY0I/AAAAAAAAFpU/W4Amv4NuLPw/s1600/pic17410.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="212" src="http://3.bp.blogspot.com/-f8FnKLCN3Vo/Tr1aC35RY0I/AAAAAAAAFpU/W4Amv4NuLPw/s320/pic17410.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-8XCdaRQ-M2A/Tr1aDjF8NfI/AAAAAAAAFpg/t6QQAmCxkIc/s1600/pic20537.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="214" src="http://1.bp.blogspot.com/-8XCdaRQ-M2A/Tr1aDjF8NfI/AAAAAAAAFpg/t6QQAmCxkIc/s320/pic20537.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
It is&amp;nbsp;the VETERAN,&amp;nbsp;not the campus organizer,&amp;nbsp;who has given us freedom to assemble.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-8MWoRvWwesU/Tr1aCXr2XAI/AAAAAAAAFo8/mXCznUllLZE/s1600/pic01655.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="225" src="http://4.bp.blogspot.com/-8MWoRvWwesU/Tr1aCXr2XAI/AAAAAAAAFo8/mXCznUllLZE/s320/pic01655.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
It is&amp;nbsp;the VETERAN,&amp;nbsp;not the lawyer,&amp;nbsp;who has given us the right to a fair trial.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-iq8jyu0QvdA/Tr1aC-DvcwI/AAAAAAAAFpM/1-NxP5D-1bE/s1600/pic06359.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="227" src="http://1.bp.blogspot.com/-iq8jyu0QvdA/Tr1aC-DvcwI/AAAAAAAAFpM/1-NxP5D-1bE/s320/pic06359.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
It is&amp;nbsp;the VETERAN,&amp;nbsp;not the politician,&amp;nbsp;Who has given us the right to vote.
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-wK0DDhUpiPo/Tr1aDkNsicI/AAAAAAAAFpo/rl01HePlEg8/s1600/pic21548.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-wK0DDhUpiPo/Tr1aDkNsicI/AAAAAAAAFpo/rl01HePlEg8/s320/pic21548.jpg" width="317" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
It is the&amp;nbsp;VETERAN who&amp;nbsp;salutes the Flag,&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-Niee-M0y6jo/Tr1aDyTqEYI/AAAAAAAAFpw/YJW9WSN0EtY/s1600/pic27595.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-Niee-M0y6jo/Tr1aDyTqEYI/AAAAAAAAFpw/YJW9WSN0EtY/s320/pic27595.jpg" width="219" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&amp;nbsp;

It is&amp;nbsp;the&amp;nbsp;VETERAN&amp;nbsp;who serves&amp;nbsp;under the Flag,&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-aA1xF7c7YLw/Tr1aChU_gRI/AAAAAAAAFpE/KacHLbCuvYI/s1600/pic04041.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="261" src="http://3.bp.blogspot.com/-aA1xF7c7YLw/Tr1aChU_gRI/AAAAAAAAFpE/KacHLbCuvYI/s320/pic04041.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="color: #20124d;"&gt;ETERNAL&amp;nbsp;REST GRANT THEM O LORD,&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span class="Apple-style-span" style="color: #20124d;"&gt;AND LET PERPETUAL LIGHT SHINE UPON&amp;nbsp;THEM.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
No matter what the country of your origin, there are always some people that step up in difficult situations. Those men and women gladly give their lives for the better good, so that others can enjoy a better future. Those people are the Veterans that we must always remember and honor.&lt;br /&gt;
&lt;br /&gt;
There is plenty of material regarding Memorial Day on the Internet. A site worth visiting is &lt;a href="http://www.usmemorialday.org/"&gt;http://www.usmemorialday.org/&lt;/a&gt; and two videos worth watching on YouTube are&amp;nbsp;&lt;a href="http://youtu.be/afd_sDNYbpY"&gt;http://youtu.be/afd_sDNYbpY&lt;/a&gt; and &lt;a href="http://youtu.be/wK0T4pVHP28"&gt;http://youtu.be/wK0T4pVHP28&lt;/a&gt;&lt;img src="http://feeds.feedburner.com/~r/niden/~4/6Kd9BNMUWdg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.niden.net/feeds/9090282409634711290/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.niden.net/2010/05/2010-memorial-day-usacelebrationsperson.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/9090282409634711290?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1710151540605737130/posts/default/9090282409634711290?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/niden/~3/6Kd9BNMUWdg/2010-memorial-day-usacelebrationsperson.html" title="2010 Memorial Day [USA][Celebrations][Personal]" /><author><name>Nikolaos Dimopoulos</name><uri>https://plus.google.com/104235485963468152376</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-tZZYYyDcFfs/AAAAAAAAAAI/AAAAAAADNuU/MJLDTwFBYis/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-THIK0BPBU30/Tr1aDEfoHvI/AAAAAAAAFpc/ixJa9ceupP4/s72-c/pic18762.gif" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Charles Town, WV, USA</georss:featurename><georss:point>39.2889903 -77.8597175</georss:point><georss:box>39.2398328 -77.9386815 39.3381478 -77.7807535</georss:box><feedburner:origLink>http://www.niden.net/2010/05/2010-memorial-day-usacelebrationsperson.html</feedburner:origLink></entry></feed>
