Wednesday, November 21, 2012

Windows 8 Professional Upgrade

My Windows 7 install on my laptop became corrupted and I decided to fix things with a clean install of the OS. Since the Dell restore image on the laptop came will all the original bloatware, I thought it would be a good time to see what all the Windows 8 fuss was about (good and bad).

Since I just purchased my laptop, I qualified for a $14.99 upgrade to Windows 8 Pro. That was cheap enough that if I hated it, I wasn't out much for trying it. I chose the save nothing upgrade option and punched go.

After the upgrade completed and I logged in, I was met with the "Start Screen". My first impression while pretty looking, were thoughts of media usability reviews and how I was going to adjust to this radical change.

Sunday, October 21, 2012

Installing Cloudera CDH4 on Ubuntu 12.04 LTS

Presented are some short notes for installing Cloudera CDH4 on Ubuntu 12.04 LTS running as a guest OS on Oracle's VirtualBox. For those unfamiliar with Cloudera and CDH, CDH is Cloudera’s 100% open source Hadoop distribution. What is documented here is not a complete tutorial, but rather pieces of information to be used in conjunction with the product's documentation. Use these tips to make the installation of Cloudera on Ubuntu easier.

Prerequisites

Creating the VM

Create a new virtual machine using the new VM wizard and downloaded Ubuntu ISO. It is important to have the 64-bit LTS ISO or the Cloudera manager will not start.

VM Settings:

  • 4GB RAM (minimum)
  • 2 CPUs
  • 128MB Display Memory
  • 25GB Dynamic Disk

When finished, you should see something similar to the following:

Configuring Ubuntu 12.04 LTS

Once you have started the Ubuntu VM and logged in, set a password for root. The Cloudera manager will need the password to install the cluster. You are also free to use a passwordless sudo setup.

sudo passwd root

Next, you will need to install the SSH server and client. This is needed by the Cloudera manager for cluster installation:

sudo apt-get install openssh-client
sudo apt-get install openssh-server

Make the following changes to /etc/hosts. Not modifying the file will cause a number of cluster startup errors such as not being able to start hBase or creating a number of default directories:

127.0.0.1 KRDAVIS-CLOUDERA localhost
#127.0.0.1 localhost
#127.0.1.1 KRDAVIS-CLOUDERA

Install the GNOME session fallback package:

sudo apt-get install gnome-session-fallback

Logout and select "Gnome Classic (no effects)" for your session. This will prevent any weirdness with running Compiz under the VM. You can now log back in.

Install Cloudera CDH4

Start the cluster installation by running the Cloudera installation manager:

chmod 755 cloudera-manager-installer.bin
sudo ./cloudera-manager-installer.bin

Follow the instructions and accept and default values. When you are done, you should have a single node CDH4 cluster running in your VM!

Shutting down the cluster and VM

When it comes time to shutdown the VM, I found I have fewer problems if I shutdown the cluster by logging into the Cloudera management web app. Select "All Services" from the "Services" menu. For the cluster, select "Stop..." from the Actions dropdown menu. Wait for all services to come to a stop.

After verifying that all cluster services are stopped, shutdown the VM by opening a terminal and running the following command:

sudo /sbin/shutdown -h now

Selecting shutdown from the Ubuntu UI appears to only log out of the system without shutting it down. That is a problem for another day.

Thursday, October 4, 2012

PHP & MySQL - Search Multiple Fields and Terms

Background

I needed to find a simple way to implement a text search on a table without using MySQL's fulltext search functionality. I am currently limited to using a version of MySQL that only supports fulltext search on the MyISAM storage engine. My tables use the InnoDB storage engine, hence the problem. This post will show a quick and dirty way to search multiple database fields with multiple search terms using only SQL.

Requirements

  • Search for multiple terms across multiple database fields.
  • Specify which table fields are to be searched.
  • Search terms to be space delimited.
  • Find partial and exact terms.
  • Find several terms in a specific order preserving whitespace.
  • Identify and work with dates supplied in common formats.
  • Identify and work with numeric search terms.
  • Provide a few hints to control search behavior.
  • Generate SQL suitable for use in a WHERE clause.
  • Table independent for re-usability.

Note: High performance was not a consideration as the number of records searched would be relatively small.

Dates

While strings are straight forward to compare against, dates posed a bit of a problem. I needed to accept a few common date formats, check if the date was valid, and convert the search format to MySQL's internal date format to make a proper comparison.

The first step was to create an is_date() function to determine if a search term was valid date. The function below is straight forward. We break down the string into the appropriate date components and use the PHP checkdate() function to determine if the date is valid. The function is shown below.

    /**
     * Evaluates if a string is a valid date
     *
     * @param $str string - The date string to evaluate
     *
     * @return bool TRUE if the string is a valid date, FALSE otherwise 
     */
    public function is_date($str) { 
        $stamp = strtotime(str_replace(array('-', '.'), '/', $str)); 

        if (!is_numeric($stamp)) 
            return FALSE; 
        
        $month = date( 'm', $stamp ); 
        $day   = date( 'd', $stamp ); 
        $year  = date( 'Y', $stamp ); 
        
        if (checkdate($month, $day, $year)) 
            return TRUE; 
        
        return FALSE; 
    }

Monday, October 1, 2012

Tips for Running the Dynamic Data System (DDS)

The Dynamic Data System (DDS) makes loading a data warehouse much easier and faster. This system uses SSIS to load standard and partitioned tables via SSIS packages created on the fly using metadata. This eliminates the need to maintain complex SSIS packages. Only basic T-SQL skills are needed. The Dynamic Data System is available on CodePlex.

My current data warehouse project uses SQL Server 2008 R2. Out of the box I found a couple minor configuration issues that prevented me from getting started with using DDS. The following notes, in addition to the documentation provided with the product, should get you on your way.

Tuesday, September 11, 2012

Critters Update for September

Well, no code examples this month. My apologies as we are busy working on our new version of Critters. I can share with you a few screenshots from our upcoming new release (click picture below to view the gallery):

Wednesday, July 18, 2012

KB: JavaScript Files in WordPress Child Themes

An example of how to include custom JavaScript files in WordPress using a child theme.

Monday, July 9, 2012

The Last Few Months in Review

It has been quite a while since I have posted anything here and finally decided to make the time. The past few months have been pretty hectic and this is what has been going on:

TechEd 2012

I am still trying to digest all the information they dumped on attendees such as myself, over the course of 4 days. Don't take my comments here to seriously.

The short version: Windows 8 and Metro are an abomination, Server 2012 is almost a real server, SQL Server 2012 has enough features to make upgrading worth while, Visual Studio 2012 is more Metro styled weirdness, and Microsoft finally realizes the value of open source (Hadoop).

I almost forgot, SharePoint... no matter how they try, MS can't make a silk purse from a sow's ear. Speaking of pigs, no amount of lipstick will help it either.

Ending on a positive note, my family and I had a blast at Disney World.

Critters Pet Health Records

We are re-factoring our site which is now based on WordPress. This should help us add new functionality and content in a more expedient manner. We have most of the public side of the new site finished. When this is complete we will be starting over on the pet record side of the site as WordPress is based on PHP and the previous site was based on ASP.NET MVC 3.

So far I have been having fun with WordPress; however, CSS theme conflicts can make you want to gouge your eyes out ;-)

ZuniSoft Blog

My blog is now back after a horrible experience transferring my zunisoft.com domain. The whole process took over two weeks to complete and the blog went off-line. Hopefully, this won't happen again. My original domain management service had vague instructions on what to do and didn't respond to help requests in a very timely manner. The transfer just kept failing, leaving me on my own to figure it out.

New Laptop

My ancient Dell XPS 2nd Generation desktop finally reached it's end of life. In hindsight, it was a pretty good investment considering I got about 8 years out of it. I replaced it with a new Dell Inspiron with an i7 processor, 1TB hard drive, and 8GB RAM. Now I can get some work done!

Hopefully I can find more time to start posting more solutions and code samples in the near future.

Monday, March 26, 2012

SSIS: Simple File Encoding Converter Script Task

Here is a very simple SSIS script task to convert a single file from one code page to another. We will be converting any file that is UTF-8 to ANSI 1252. The original file will be replaced with the converted file.

Start with an SSIS script task and three read-only variables: User::Module, User::DataFlowInputDestFileName, and User::FileEncoding. The first variable is an arbitrary name for the package supplied by the user. The second is the full path and name of the file we want to convert. The third variable is the existing encoding of the file to convert e.g. UTF-8, ANSI, etc.

The code:

using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
using System.IO;
using System.Text;

public void Main()
{
 Variables vars = Dts.Variables;
 
 string component = vars["System::TaskName"].Value.ToString();
 string fileEncoding = vars["User::FileEncoding"].Value.ToString();
 string filePath = vars["User::DataFlowInputDestFileName"].Value.ToString();
 string fileExt = Path.GetExtension(filePath);
 bool fireAgain = false;
 
 if (String.Compare(fileEncoding, "UTF-8", true) == 0)
 {
  try
        {
   // /////////////////////////////////////////////////////////////////
   // Setup the file streams for the conversion
   // /////////////////////////////////////////////////////////////////
   Dts.Events.FireInformation(0, component,
                    "Converting input file",
                    String.Empty, 0, ref fireAgain);
   
   StreamReader fileStream = new StreamReader(filePath);
   StreamWriter ansiWriter = new StreamWriter(
          filePath.Replace(fileExt, "-ansi" + fileExt),
          false,
          Encoding.GetEncoding(1252));
    
   // /////////////////////////////////////////////////////////////////
   // Read the UTF8 file and convert it
   // /////////////////////////////////////////////////////////////////
   while (fileStream.Peek() >= 0)
   {
    ansiWriter.WriteLine(fileStream.ReadLine());
   }

   // /////////////////////////////////////////////////////////////////
   // Close and dispose of the file streams
   // /////////////////////////////////////////////////////////////////
   Dts.Events.FireInformation(0, component,
                    "Closing and disposing of file streams",
                    String.Empty, 0, ref fireAgain);
   
   fileStream.Close();
   fileStream.Dispose;
   ansiWriter.Close();
   ansiWriter.Dispose;

   // /////////////////////////////////////////////////////////////////
   // Delete the original file. One could do a number of things here
   // such as keeping both files so the original is on hand in case
   // a something became corrupted.
   // /////////////////////////////////////////////////////////////////
   if (File.Exists(filePath))
   {
    Dts.Events.FireInformation(0, component,
                        "Deleting original file",
                        String.Empty, 0, ref fireAgain);
     
    File.Delete(filePath);
   }

   // /////////////////////////////////////////////////////////////////
   // Rename the converted file to the original file
   // /////////////////////////////////////////////////////////////////
   Dts.Events.FireInformation(0, component,
                    "Renaming temporary converted file to orginal file",
                    String.Empty, 0, ref fireAgain);
   
   File.Move(filePath.Replace(fileExt, "-ansi" + fileExt), filePath);

  // /////////////////////////////////////////////////////////////////
  // Exceptions
  // ///////////////////////////////////////////////////////////////// 
  catch (COMException cexp)
  {
   Dts.Events.FireError(cexp.ErrorCode, component, "Exception occured : "
          + cexp.TargetSite + cexp, String.Empty, 0);
   Dts.TaskResult = (int)ScriptResults.Failure;
  }
  catch (System.ArgumentException argex)
  {
   Dts.Events.FireError(0, component, argex.Message, String.Empty, 0);
   Dts.TaskResult = (int)ScriptResults.Failure;
  }
  catch (DtsRuntimeException runex)
  {
   Dts.Events.FireError(0, component, runex.Message, String.Empty, 0);
   Dts.TaskResult = (int)ScriptResults.Failure;
  }
  finally
  {
   if(!fileStream.IsClosed())
   {
    fileStream.Close();
    fileStream.Dispose;
   }
   
   if(!ansiWriter.IsClosed())
   {
    ansiWriter.Close();
    ansiWriter.Dispose;
   }
  }
 }
 // TODO: Add your code here
 Dts.TaskResult = (int)ScriptResults.Success;
}

Keep in mind that this is a naive approach to code page conversions and we have not touched on subjects such as the use of BOMs (Byte Order Marks). It should start you in the right direction if you are importing a flat file that needs to be converted before running it through the rest of your package.

Wednesday, February 1, 2012

ASP.NET MVC - LabelFor htmlAttributes Extension

Part of using any new framework such as ASP.NET MVC, is finding out what it can and cannot do. I was designing the layout for my application's web forms and found out the LabelFor HTML helper didn't allow one to pass html attributes like many of the other HTML helpers. Digging around on the web, I found this handy bit of code on Imran Baloch's blog that neatly solved my problem.

I have re-posted it here for convenience.

LabelExtensions.cs (comments removed for clarity):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Linq.Expressions;

namespace CritterHealthRecords.Web.Helpers
{
    public static class LabelExtensions
    {
        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
        {
            return LabelFor(html, expression, new RouteValueDictionary(htmlAttributes));
        }

        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();

            if (String.IsNullOrEmpty(labelText))
            {
                return MvcHtmlString.Empty;
            }

            TagBuilder tag = new TagBuilder("label");
            tag.MergeAttributes(htmlAttributes);
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);

            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }
    }
}

Using the extension is easy:


<%=Html.LabelFor(m => m.UserName, new { @class="field-label"})%>

Which renders the following HTML:


<label class="field-label" for="UserName">User Name</label>

Thursday, January 26, 2012

Android Critters Retired

The Android version of Critters has been retired so we can focus our resources on our next version. Trying to keep the application viable on three different versions of Android (2.x, 3.x, and 4.x) for no cost and creating a new version was consuming more resources than were available.

For those currently using the Android version, you may certainly continue to use it; however, we will not be releasing any future updates and the application has been removed from the Android Market. If you need to re-install the old application, please contact us and we will e-mail you an APK of the last release. You can install the APK directly from e-mail, if you have set your security to install applications from "Unknown sources".

The next generation of Critters will be web based and work on a variety of traditional and mobile platforms. By focusing on the web, we hope to make updates and fixes more timely and transparent. See our page on facebook so we can get your thoughts and ideas of what we need to make Critters the best personal veterinary record ever!

Thank you for using Critters!