Tuesday, September 7, 2010

Google Doodle

Today's Doodle seems like a mystery - an highly addictive interactive HTML5 dot thingi:


Usually, the clicking a Doodle leads to an search query that explains the Doodle's meaning - but today you can't click on it, since the Doodle scatters away, when trying to reach it:

Luckily, Google's engineers (intentionally?) placed a a significant clue in the page's HTML: the Doodle tag's id is named "hplogo":

A short Wikipedia research discover that David Packard, co founder of HP, was born in September 7th, 1912, which is today. Eureka! The cool interactive dots are the HP bubble-jet dots!

P.S.
The inkjet was invented in Cannon's labs. Just for the historical accuracy.



Edit - Seems my hypothesis was interesting, but wrong. The Google Italy Blog team has state that it just reflects speed, interactivity and fun.

Monday, July 5, 2010

UUID version 4 for Caché ObjectScript

I had to implement UUID Version 4 generator for Intersystems Caché database in ObjectScript, since I couldn't find anyone that already done it.
UUID's are good as primary keys
So, I've decided to be the first one to share it with the world. Enjoy.
30*16
/// http://en.wikipedia.org/wiki/Universally_Unique_Identifier#Version_4_.28random.29
ClassMethod GetUUID4() As %String
{
 //xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxxxxxx
 Set tGroupLens=$LB(8,4,3,3,12)
 
 Set tGroups=""
 for tI=1:1:$LL(tGroupLens){
  // Group len
  Set tLen=$LG(tGroupLens,tI) 
  // Create number of that hex length
  Set tNumber=$Random($ZPower(16,tLen))
  // Make it hexadecimal
  Set tNumber=$ZConvert($ZHex(tNumber),"L")
  // Pad it with zeros
  Set tNumber=$Translate($Justify(tNumber,tLen)," ","0")
  // Add to list
  Set tGroups=tGroups_$LB(tNumber)
 }
 
 // Add version number (4) to group 3
 Set $List(tGroups,3)="4"_$List(tGroups,3)
 
 // Add 8,9,a or b to group 4 (as UUID version 4 required)
 Set tRnd=$Random(4)
 Set $List(tGroups,4)=$S(
  (tRnd=0):"8",
  (tRnd=1):"9",
  (tRnd=2):"a",
  (tRnd=3):"b",
  1:"b")_$List(tGroups,4)
  
 // Join with dashes
 Quit $LTS(tGroups,"-")
}

Sunday, March 21, 2010

My must-have Firefox add-on list

I know there are plenty of "X must-have Firefox add-on list"'s out there. I decided to create the "I-would-install-those-for-my-grandmother" list - your Firefox will feel naked without them.

  1. AdBlock Plus - Stops those flashing ads. I guess that 74 million users can't be wrong.
  2. LastPass - Sync and manage password between your computers and browsers - never forget password again!
  3. XMarks - Sync and manage your bookmarks between your computers and browsers - never forget your favorite site again! 
  4. Omnibar -  Enjoy Google's ingenious OmniBox within your Firefox.
  5. Coral IE Tab - For those sites that wouldn't support the standards - switch to IE instantly. Replaces the old IE Tab add-on.
  6. DownloadThemAll - Download manager and accelerator. 
  7. SkipScreen - For those nag screen on sites like Rapidshare, Megaupload, zShare, and more.



Wednesday, March 17, 2010

Sync Outlook to a Google Secondary Calendar

Consider the following use case:
You have your office calendar hosted on the enterprise's exchange server, and your private calendar on Google Calendar. It is only natural to see your work meetings in your private calendar, under a secondary calendar. Well, there is a problem - Google Calendar Sync does not allow to sync your Outlook to a secondary calendar.

I suggest a simple walk-around:

  1. Open a new Google Calendar account (YOUR.NAME.work.calendar@gmail.com would do just fine).
  2. Sync your office Outlook calendar with the new account using Google Calendar Sync.
  3. Share your new account with the old one (via "Settings" -> "Share this Calendar"). I recommend to have a full share ("Make changes to events"): it would allow you to schedule and cancel meetings right from your Google Calendar.
  4. Go to your original account, and add the new account by entering the new Google account.



That's it. As long as your work computer is running the sync agent and connected to the Internet, your Office calendar will be synced with a secondary Google calendar of yours.

Sunday, March 14, 2010

ObjectScript JSON decoder/encoder

Those who develop in web application using Cache ObjectScript might find the lack of a native JSON library disturbing. The ZEN Google-group offers a JSON encoder/decoder, but I found it missing several important feature - such as supporting escaping and standard quoting (JSON.org allows to quote string only with ", and not with ').

Here I offer a modification for the class which supports character escaping, double quote strings, and some bugs were fixed. This class is supplied with a unitest method as well.

JSON.cls
Class JSON Extends %String
{

Parameter EscapeChar As COSEXPRESSION = "$LB($LB(""\"",""\\""),$LB($C(13),""\n""),$LB($C(10),""\r""),$LB($C(9),""\t""),$LB("""""""",""\""""""),$LB($C(8),""\b""),$LB($C(12),""\f""))";

Parameter UnEscapeChar As COSEXPRESSION = "$LB(""\\"",""\n"",""\r"",""\t"",""\"""""",""\b"",""\f"")";

Parameter JSonSlice [ Final, Internal ] = 1;

Parameter JSonInString [ Final, Internal ] = 2;

Parameter JSonInArray [ Final, Internal ] = 3;

Parameter JSonInObject [ Final, Internal ] = 4;

ClassMethod GetEscapeChars() As %String
{
  Quit ..#EscapeChar
}

ClassMethod SetAux(what As %String, where As %Integer, delim As %String) As %DataType [ Internal ]
{
  aux=##class(%ArrayOfDataTypes).%New()
  aux.SetAt(what,"what")
  aux.SetAt(where,"where")
  aux.SetAt(delim,"delim")
 
  aux
}

/// we know that it's not escaped becase there is _not_ an
/// odd number of backslashes at the end of the string so far
ClassMethod isEscaped(str As %String, As %String) As %Boolean [ Internal ]
{
  pos=$F(str,c)
  ($L($E(str,1,pos))-$L($REPLACE($E(str,1,pos),"\","")))#2=1
}

/// Escapes the string.
ClassMethod Escape(str As %String) As %String [ Internal ]
{
  for tI=1:1:$LL(..#EscapeChar)
    Set tCharPair=$LG(..#EscapeChar,tI)
    Set str=$Replace(str,$LG(tCharPair,1),$LG(tCharPair,2))
  }
  Quit str
}

ClassMethod Unescape(str As %String) As %String [ Internal ]
{
  For tI=1:1:$Length(str){
    Set tChar=$ListFind(..#UnEscapeChar,$E(str,tI,tI+1))
    if (tChar>0){
      Set $E(str,tI,tI+1)=$LG($LG(..#EscapeChar,tChar),1)
    }
  }
  Quit str
}

/// Decode a string JSON.
ClassMethod Decode(str As %String) As %ArrayOfDataTypes
{
  #dim stack as %ListOfDataTypes
  matchType=$ZCVT(str,"L")
 
  q:(matchType="true") "1"
  q:(matchType="false") "0"
  q:(matchType="null") ""  
  q:($ISVALIDNUM(matchType)) matchType 
  q:str?1"""".E1"""" ..Unescape($e(str,2,$l(str)-1))
  //$replace($e(str,2,$l(str)-1),"\""","""")
 
  // array or object notation
  match=str?1(1"[".E1"]",1"{".E1"}")
  stack=##class(%ListOfDataTypes).%New()
 
  if match {
    if $E(str,1)="[" {
      stack.Insert(..#JSonInArray)
      arr=##class(%ListOfDataTypes).%New()
    }  
    else {
      stack.Insert(..#JSonInObject)
      obj=##class(%ArrayOfDataTypes).%New()
    }
   
    stack.Insert(..SetAux(..#JSonSlice,1,"false"))
   
    chars=$E(str,2,$L(str)-1)
   
    if chars="" {
      if stack.GetAt(1)=..#JSonInArray {
        arr
      }
      else {
        obj
      }  
    }

    strlenChars=$L(chars)+1

    escaped=0
    For c=1:1:strlenChars {
      last=stack.Count()
      top=stack.GetAt(last)
     
      s:(escaped=2) escaped=0
      s:(escaped=1) escaped=2
     
      substrC2=$E(chars,c-1,c)
      if ($E(chars,c,c)="\")&&(escaped=0) escaped=1
      
      if $e(chars,c)="" {
        a=22
      }
     
      if (c=strlenChars || ($E(chars,c)=",")) && (top.GetAt("what")=..#JSonSlice) {
        // found a comma that is not inside a string, array, etc.,
        // OR we've reached the end of the character list
        slice = $E(chars, top.GetAt("where"),c-1)
        stack.Insert(..SetAux(..#JSonSlice,c+1,"false"))
        if stack.GetAt(1)=..#JSonInArray {
          // we are in an array, so just push an element onto the stack
          arr.Insert(..Decode(slice))
        }
        elseif stack.GetAt(1)=..#JSonInObject {
          // we are in an object, so figure
          // out the property name and set an
          // element in an associative array,
          // for now
                   
          match=slice?." "1""""1.E1""""." "1":"1.E
          if match {
            //'name':value par
            key1=$p(slice,":")
            key=..Decode(key1)

            val=..Decode($P(slice,":",2,$l(slice,":")))
            obj.SetAt(val, key)
                    
          }
        }
      }
      elseif $E(chars,c)="""" && (top.GetAt("what")'=..#JSonInString) {
        // found a quote, and we are not inside a string
        stack.Insert(..SetAux(..#JSonInString,c,$E(chars,c)))
      }
      elseif $E(chars,c)=top.GetAt("delim") && (top.GetAt("what")=..#JSonInString) && (escaped=0) {
        // found a quote, we're in a string, and it's not escaped (look 3 charachters behind, to see the \" is not \\" )
        last=stack.Count()
        st=stack.RemoveAt(last)
      }
      elseif ($E(chars,c)="[") && (top.GetAt("what")'=..#JSonInString) && ($CASE(top.GetAt("what"),..#JSonInString:1,..#JSonInArray:1,..#JSonSlice:1,:0))
        // found a left-bracket, and we are in an array, object, or slice
        stack.Insert(..SetAux(..#JSonInArray,c,"false"))
      }
      elseif $E(chars,c)="]" && (top.GetAt("what")=..#JSonInArray) {
        // found a right-bracket, and we're in an array
        last=stack.Count()
        st=stack.RemoveAt(last)
      }
      ;modificacio 19/11/08: ..#JSonString -> #JSonInArray
      elseif $E(chars,c)="{" && ($CASE(top.GetAt("what"),..#JSonSlice:1,..#JSonInArray:1,..#JSonInObject:1,:0)) {
        // found a left-brace, and we are in an array, object, or slice
        stack.Insert(..SetAux(..#JSonInObject,c,"false"))
      }
      elseif $E(chars,c)="}" && (top.GetAt("what")=..#JSonInObject) {
        // found a right-brace, and we're in an object
        last=stack.Count()
        st=stack.RemoveAt(last)
      }
     
    }  
   
    if stack.GetAt(1)=..#JSonInObject {
      obj
    }
    elseif stack.GetAt(1)=..#JSonInArray {
      arr
    }
  }
  str
}

/// Encode a Cache string to a JSON string
ClassMethod Encode(data As %DataType) As %String
{

  if $IsObject(data) {  
    key=""
   
    typeData=data.%ClassName()

    if typeData="%ArrayOfDataTypes" {
      //type object
      key=""
      cad=""
      {
        pData=data.GetNext(.key)
        q:key=""
        value=..Encode(pData)
        cad=$S(cad'="":cad_",",1:"")_""""_..Escape(key)_""":"_value  
      
      "{"_cad_"}"
    }
    elseif typeData="%ListOfDataTypes" {
      //type array
     
      cad=""
      i=1:1:data.Count() {
        tmp=..Encode(data.GetAt(i))
        cad=$S(i>1:cad_",",1:"")_tmp
      }
     
      cad="["_cad_"]"
      cad
    }
  }
  elseif $ISVALIDNUM(data) {
    // type number
    data
  }
  else {
    //type string
    q:data="" "null"
    """"_..Escape(data)_""""
  }
}

ClassMethod CreateStringPair(pKey As %String, pValue As %String) As %String
{
  Quit """"_pKey_""":"""_..Escape(pValue)_""""
}

ClassMethod Parse(pStr As JSON) As %ArrayOfDataTypes
{
  Quit ##class(JSON).Decode(pStr)
}

ClassMethod Stringify(pData As %DataType) As JSON
{
  Quit ##class(JSON).Encode(pData)
}

}
 



TestJSON
Method TestJSON()
{
  #Dim tException As %Exception.AbstractException
  Try {    
    Set tEscapeChars=##class(RSA.Data.DT.Simple.JSON).GetEscapeChars()
    Set tEscapeChars=tEscapeChars_$LB($LB("[","["),$LB("]","]"),$LB("{","{"),$LB("}","}"),$LB(":",":"))
    for tI=1:1:$LL(tEscapeChars){
      For tJ=1:1:$LL(tEscapeChars){
        for tK=1:1:$LL(tEscapeChars){
         
        Set tChar1=$LG(tEscapeChars,tI)
        Set tChar2=$LG(tEscapeChars,tJ)
        Set tChar3=$LG(tEscapeChars,tK)
       
        Set tJSON="{""name"":"""_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)_" is char""}"
        Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
        Set tName=tObj.GetAt("name")
        Do $$$AssertEquals(tName,$LG(tChar1,1)_$LG(tChar2,1)_$LG(tChar3,1)_" is char","Escaping for "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2))
       
        Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)_" is char""}"
        Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
        Set tName=tObj.GetAt("name")
        Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1)_$LG(tChar3,1)_" is char","Escaping middle for "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2))
       
        Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)_"""}"
        Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
        Set tName=tObj.GetAt("name")
        Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1)_$LG(tChar3,1),"Escaping end for "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2))
       
       
        if (tK=1){
          if (tI=1){
            Set tJSON="{""name"":"""_$LG(tChar1,2)_" is char""}"
            Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
            Set tName=tObj.GetAt("name")
            Do $$$AssertEquals(tName,$LG(tChar1,1)_" is char","Escaping for "_$LG(tChar1,2))
     
            Set tJSON="{""name"":""the "_$LG(tChar1,2)_" is char""}"
            Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
            Set tName=tObj.GetAt("name")
            Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_" is char","Escaping middle for "_$LG(tChar1,2))
     
            Set tJSON="{""name"":""the "_$LG(tChar1,2)_"""}"
            Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
            Set tName=tObj.GetAt("name")
            Do $$$AssertEquals(tName,"the "_$LG(tChar1,1),"Escaping end for "_$LG(tChar1,2))
           
            for tC=97:1:122{
              Set tJSON="{""name"":"""_$LG(tChar1,2)_$C(tC)_"""}"
              Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
              Set tName=tObj.GetAt("name")
              Do $$$AssertEquals(tName,$LG(tChar1,1)_$C(tC),"Escaping for "_$LG(tChar1,2)_$C(tC))
            }
          }
       
          Set tJSON="{""name"":"""_$LG(tChar1,2)_$LG(tChar2,2)_" is char""}"
          Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
          Set tName=tObj.GetAt("name")
          Do $$$AssertEquals(tName,$LG(tChar1,1)_$LG(tChar2,1)_" is char","Escaping for "_$LG(tChar1,2)_$LG(tChar2,2))
       
          Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_" is char""}"
          Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
          Set tName=tObj.GetAt("name")
          Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1)_" is char","Escaping middle for "_$LG(tChar1,2)_$LG(tChar2,2))
       
          Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_"""}"
          Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON)
          Set tName=tObj.GetAt("name")
          Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1),"Escaping end for "_$LG(tChar1,2)_$LG(tChar2,2))
          }
        }
      }
    }
  catch tException {
    Do $$$AssertEquals(1,0,"Exception thrown - " _ tException.Code_ ": " _ tException.Name _ " " _ tException.Data _ " " _ tException.Location)
  }
}
 

Happy Pi Day!

http://en.wikipedia.org/wiki/Pi_Day

Thursday, March 11, 2010

Import / restore Firefox bookmarks via command-line

Have you ever required to maintain a single centralize source of Firefox bookmarks, and push it to your users using during login?

At first, I thought that bookmark sharing problem can be easily solved using Xmarks, which is an awesome bookmarks synchronization add on, but I discovered that it does not support "read only mode" - if one of the users adds or removes bookmarks locally, sooner or later the change will be synced with the rest of the users. I couldn't find any other add ons that perform that kind of task either.

Surprisingly, I found that the hardcore way to do it is quite easy.

As some of you might know, Firefox 3 (and above) manages its bookmarks in a sqlite database. The bookmarks of the user are stored in the %appdata%\Mozilla\Firefox\Profiles\xxxx.user\places.sqlite file (Windows OS). So, I'll explain how to create a bookmark template from the .sqlite file, and how to import it back to the user from the command line. You should have sqlite for that.
  1. Customize your bookmarks as usual (via Firefox)
  2. Close Firefox (to prevent sqlite locking issues)
  3. Go to the profile directory 
  4. Open places.sqlite via sqlite 
  5. Backup your bookmarks. FILENAME is the file your bookmarks will be saved to
    sqlite> .backup FILENAME
  6. Quit sqlite
    sqlite> .quit
Great! Now you have your bookmarks template file.

Importing it back:
  1. Close Firefox (if it is opened)
  2. Go to the profile directory
  3. Open places.sqlite using sqlite
  4. Restore your bookmarks
    sqlite> .restore FILENAME
  5. Quit sqlite
    sqlite> .quit
That's it. You can open Firefox and see that all the bookmarks were imported. I tested it on Windows environment, but I don't think it should be much different on Linux.
Since it was done from command line, it can be easily implemented in your favorite scripting language.

Please note that not only the bookmarks are copied, but the browser history as well. I suggest to clear the browser's history before performing the export.