Wednesday, November 14, 2012

Natively Compressing Files on a Mac to Save Space

Since 10.6 (Snow Leopard) native file compression has been built into HFS+ (the MacOS Extended filesystem).  

The feature is hidden away in a command line utility called ditto (you can check the man pages for the command via "man ditto") which is a file/directory copying program.  

Apple hasn't recommended it for the following reasons but if you don't use computers prior to 10.6, you'll be fine.


     --hfsCompression
                   When copying files or extracting content from an archive,
                   if the destination is an HFS+ volume that supports compres-
                   sion, all the content will be compressed if appropriate.
                   This is only supported on Mac OS X 10.6 or later, and is
                   only intended to be used in installation and backup scenar-
                   ios that involve system files. Since files using HFS+ com-
                   pression are not readable on versions of Mac OS X earlier
                   than 10.6, this flag should not be used when dealing with
                   non-system files or other user-generated content that will
                   be used on a version of Mac OS X earlier than 10.6.



In it's simplist form, use it this way to compress a directory:

ditto  --hfsCompression sourceDirectory destinationDirectory

In my case I had a lot of applications that I didn't want to delete but that I might not ever use i.e.

sudo mv /Applications/Xbench.app /Applications/Xbench.app.old
sudo ditto  --hfsCompression /Applications/Xbench.app.old /Applications/Xbench.app
sudo rm -rf /Applications/Xbench.app.old

You can check the compression status with a utility like hfsdebug or fileXray or do a df -h before and after a compression to see that the disk storage used by the command is usually much less than without.






Friday, October 26, 2012

Alfred Applescript Extension Tutorial

This extension for Alfred which will lookup your lastpass url names and launch them in a new tab in safari.  

In this example, we'll lookup Blogger with Alfred and


it will find this entry and run it in safari.



Things you'll need. 

  1. a copy of your last pass CSV file place into your home directory named lastpass.csv.  Get this by going to the Advanced screen and clicking export
  2. Alfred with PowerPack 
Note: If you want to be more secure, edit this lastpass.csv and remove all your passwords 



Go to the Extensions button
Click AppleScript

Name AppleScript

Add code and metadata to alfred and save.

Run it by bringing up Alfred as follows.  Safari will kick off with the matched entry.

Reminder: Make sure you have an exported copy of your last data into your home directory named "lastpass.csv".  I know this is insecure.  I tried to remove the passwords programatically but applescript keeps giving me errors when I try and open a file for write ( Network file permission error., errorNumber: -5000).

Note: I'd rather have the alfred team properly integrate this feature the app.  I want it to search ALL elements and show the list of items that matched, but I can't see a way to make an alfred extension do that as written.

Code to copy:

on write_to_file(this_data, target_file, append_data) -- (string, file path as string, boolean)
try
set the target_file to the target_file as text
set the open_target_file to ¬
open for access file target_file with write permission
if append_data is false then ¬
set eof of the open_target_file to 0
write this_data to the open_target_file starting at eof
close access the open_target_file
return true
on error errorMessage number errorNumber
log ("errorMessage: " & errorMessage & ", errorNumber: " & errorNumber)
try
close access file target_file
end try
return false
end try
end write_to_file
(* Assumes that the CSV text adheres to the convention:
   Records are delimited by LFs or CRLFs (but CRs are also allowed here).
   The last record in the text may or may not be followed by an LF or CRLF (or CR).
   Fields in the same record are separated by commas (unless specified differently by parameter).
   The last field in a record must not be followed by a comma.
   Trailing or leading spaces in unquoted fields are not ignored (unless so specified by parameter).
   Fields containing quoted text are quoted in their entirety, any space outside them being ignored.
   Fields enclosed in double-quotes are to be taken verbatim, except for any included double-quote pairs, which are to be translated as double-quote characters.
       
   No other variations are currently supported. *)

on csvToList(csvText, implementation)
-- The 'implementation' parameter must be a record. Leave it empty ({}) for the default assumptions: ie. comma separator, leading and trailing spaces in unquoted fields not to be trimmed. Otherwise it can have a 'separator' property with a text value (eg. {separator:tab}) and/or a 'trimming' property with a boolean value ({trimming:true}).
set {separator:separator, trimming:trimming} to (implementation & {separator:",", trimming:false})
script o -- Lists for fast access.
property qdti : getTextItems(csvText, "\"")
property currentRecord : {}
property possibleFields : missing value
property recordList : {}
end script
-- o's qdti is a list of the CSV's text items, as delimited by double-quotes.
-- Assuming the convention mentioned above, the number of items is always odd.
-- Even-numbered items (if any) are quoted field values and don't need parsing.
-- Odd-numbered items are everything else. Empty strings in odd-numbered slots
-- (except at the beginning and end) indicate escaped quotes in quoted fields.
set astid to AppleScript's text item delimiters
set qdtiCount to (count o's qdti)
set quoteInProgress to false
considering case
repeat with i from 1 to qdtiCount by 2 -- Parse odd-numbered items only.
set thisBit to item i of o's qdti
if ((count thisBit) > 0) or (i is qdtiCount) then
-- This is either a non-empty string or the last item in the list, so it doesn't
-- represent a quoted quote. Check if we've just been dealing with any.
if (quoteInProgress) then
-- All the parts of a quoted field containing quoted quotes have now been
-- passed over. Coerce them together using a quote delimiter.
set AppleScript's text item delimiters to "\""
set thisField to (items a thru (i - 1) of o's qdti) as string
-- Replace the reconstituted quoted quotes with literal quotes.
set AppleScript's text item delimiters to "\"\""
set thisField to thisField's text items
set AppleScript's text item delimiters to "\""
-- Store the field in the "current record" list and cancel the "quote in progress" flag.
set end of o's currentRecord to thisField as string
set quoteInProgress to false
else if (i > 1) then
-- The preceding, even-numbered item is a complete quoted field. Store it.
set end of o's currentRecord to item (i - 1) of o's qdti
end if
-- Now parse this item's field-separator-delimited text items, which are either non-quoted fields or stumps from the removal of quoted fields. Any that contain line breaks must be further split to end one record and start another. These could include multiple single-field records without field separators.
set o's possibleFields to getTextItems(thisBit, separator)
set possibleFieldCount to (count o's possibleFields)
repeat with j from 1 to possibleFieldCount
set thisField to item j of o's possibleFields
if ((count thisField each paragraph) > 1) then
-- This "field" contains one or more line endings. Split it at those points.
set theseFields to thisField's paragraphs
-- With each of these end-of-record fields except the last, complete the field list for the current record and initialise another. Omit the first "field" if it's just the stub from a preceding quoted field.
repeat with k from 1 to (count theseFields) - 1
set thisField to item k of theseFields
if ((k > 1) or (j > 1) or (i is 1) or ((count trim(thisField, true)) > 0)) then set end of o's currentRecord to trim(thisField, trimming)
set end of o's recordList to o's currentRecord
set o's currentRecord to {}
end repeat
-- With the last end-of-record "field", just complete the current field list if the field's not the stub from a following quoted field.
set thisField to end of theseFields
if ((j < possibleFieldCount) or ((count thisField) > 0)) then set end of o's currentRecord to trim(thisField, trimming)
else
-- This is a "field" not containing a line break. Insert it into the current field list if it's not just a stub from a preceding or following quoted field.
if (((j > 1) and ((j < possibleFieldCount) or (i is qdtiCount))) or ((j is 1) and (i is 1)) or ((count trim(thisField, true)) > 0)) then set end of o's currentRecord to trim(thisField, trimming)
end if
end repeat
-- Otherwise, this item IS an empty text representing a quoted quote.
else if (quoteInProgress) then
-- It's another quote in a field already identified as having one. Do nothing for now.
else if (i > 1) then
-- It's the first quoted quote in a quoted field. Note the index of the
-- preceding even-numbered item (the first part of the field) and flag "quote in
-- progress" so that the repeat idles past the remaining part(s) of the field.
set a to i - 1
set quoteInProgress to true
end if
end repeat
end considering
-- At the end of the repeat, store any remaining "current record".
if (o's currentRecord is not {}) then set end of o's recordList to o's currentRecord
set AppleScript's text item delimiters to astid
return o's recordList
end csvToList

-- Get the possibly more than 4000 text items from a text.
on getTextItems(txt, delim)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set tiCount to (count txt's text items)
set textItems to {}
repeat with i from 1 to tiCount by 4000
set j to i + 3999
if (j > tiCount) then set j to tiCount
set textItems to textItems & text items i thru j of txt
end repeat
set AppleScript's text item delimiters to astid
return textItems
end getTextItems

-- Trim any leading or trailing spaces from a string.
on trim(txt, trimming)
if (trimming) then
repeat with i from 1 to (count txt) - 1
if (txt begins with space) then
set txt to text 2 thru -1 of txt
else
exit repeat
end if
end repeat
repeat with i from 1 to (count txt) - 1
if (txt ends with space) then
set txt to text 1 thru -2 of txt
else
exit repeat
end if
end repeat
if (txt is space) then set txt to ""
end if
return txt
end trim

on theSplit(theString, theDelimiter)
-- save delimiters to restore old settings
set oldDelimiters to AppleScript's text item delimiters
-- set delimiters to delimiter to be used
set AppleScript's text item delimiters to theDelimiter
-- create the array
set theArray to every text item of theString
-- restore the old setting
set AppleScript's text item delimiters to oldDelimiters
-- return the result
return theArray
end theSplit

on fixit(var, num)
try
set extra to item num of var as text
on error errMsg number errNum
set extra to ""
end try
return extra
end fixit


on readFile(unixPath)
set foo to (open for access (POSIX file unixPath))
set txt to (read foo for (get eof foo))
close access foo
return txt
end readFile


on findUrlInLastPassCSV(unixPath, matchVal)
set csvData to readFile(unixPath)
set csvArray to csvToList(csvData, {})
repeat with i from 1 to count csvArray
set csvRow to item i of csvArray
set webpage to fixit(csvRow, 1)
set username to fixit(csvRow, 2)
set pass to fixit(csvRow, 3)
set extra to fixit(csvRow, 4)
set urlname to fixit(csvRow, 5)
set grouping to fixit(csvRow, 6)
set fav to fixit(csvRow, 7)
(*
For some reason I get this error
errorMessage: Network file permission error., errorNumber: -5000
when I try to use this code, so it's commented out for now
set writeWithoutPass to true
set theFile to POSIX path of unixPath & "2"
if (i is equal to 1) then
if (pass is equal to "removed") then
set writeWithoutPass to false
else
my write_to_file("url,username,password,extra,name,grouping,fav", theFile, false)
end if
end if
if (writeWithoutPass) then
log "writing" & i
my write_to_file((webpage & "," & username & "," & "removed" & "," & extra & "," & urlname & "," & grouping & "," & fav), theFile, true)
end if
*)
set AppleScript's text item delimiters to matchVal
set match to text item 1 of urlname
set matched to false
if (match as string is not equal to urlname as string) then
log "matched is true"
set matched to true
else
log "matched is false"
set matched to false
end if
#set match to doesMatch(nextLine, matchVal)
if (matched is true) then
return webpage
end if
end repeat
return ""
end findUrlInLastPassCSV


on alfred_script(q)

set theFile to path to home folder
set theFile to POSIX path of theFile & "lastpass.csv"

set webpage to findUrlInLastPassCSV(theFile, q)
tell application "Safari"
tell window 1
set current tab to (make new tab with properties {URL:webpage})
end tell
end tell
end alfred_script

Tuesday, October 16, 2012

Windows 7 Printing to Mountain Lion Problem Resolved

I spent about 2 days trying to get my Windows 7 Machine to print on a Mountain Lion Mac.  I did the usual things like 
  1. turning on the printer sharing in the System Preferences
  2. turning off the firewalls, 
  3. verifying connectivity, (tracert, ping, nslookup) 
  4. using the windows 7 printer setup and trying 
    1. browsing for the printer (nothing would show)
    2. typing the IP address
    3. typing the host name
    4. typing the full name i.e. \\host\printer
but no matter what I tried, the windows 7 machine would not see the printer.
In the end, I realized that I needed to 
  1. install Bonjour (apple's "zero configuration" networking utility) on the Windows 7 machine 
  2. run the Bonjour printer wizard and connect it to the mac's shared printer,
  3. That's it! Printer installed and working.
Hope that saves someone else some grief!!

Sunday, September 2, 2012

SpringSource STS Install under Linux gives NullPointer / Can't connect to X11 window server using 'localhost:10.0' as the value of the DISPLAY variable.



Run these 2 commands if you get an error like the one shown below:
[user@domain ~]$ chmod 644 .Xauthority 

[user@domain ~]$ export XAUTHORITY=~/.Xauthority 

[user@domain Downloads]$ sudo sh spring-tool-suite-3.0.0.RELEASE-e4.2-linux-gtk-installer.sh
[user@domain Downloads]$ chmod 600 ~/.Xauthority 


Error during install:
[user@domain Downloads]$ ls
spring-tool-suite-3.0.0.RELEASE-e4.2-linux-gtk-installer.sh
[user@domain Downloads]$ sudo sh spring-tool-suite-3.0.0.RELEASE-e4.2-linux-gtk-installer.sh
[sudo] password for user: 

*******************************************************************************
*                               Welcome to the                                *
*                    SpringSource Tool Suite 3.0.0.RELEASE                    *
*                                  Installer                                  *
*******************************************************************************

 preparing the installer... done
 starting UI installer. please follow instructions on screen...X11 connection rejected because of wrong authentication.
java.lang.NullPointerException
at javax.swing.MultiUIDefaults.getUIError(MultiUIDefaults.java:133)
at javax.swing.UIDefaults.getUI(UIDefaults.java:759)
at javax.swing.UIManager.getUI(UIManager.java:1000)
at javax.swing.JOptionPane.updateUI(JOptionPane.java:1878)
at javax.swing.JOptionPane.<init>(JOptionPane.java:1841)
at javax.swing.JOptionPane.showOptionDialog(JOptionPane.java:859)
at javax.swing.JOptionPane.showMessageDialog(JOptionPane.java:668)
at javax.swing.JOptionPane.showMessageDialog(JOptionPane.java:639)
at com.izforge.izpack.installer.GUIInstaller.showFatalError(GUIInstaller.java:138)
at com.izforge.izpack.installer.GUIInstaller.<init>(GUIInstaller.java:129)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
at java.lang.Class.newInstance0(Class.java:372)
at java.lang.Class.newInstance(Class.java:325)
at com.izforge.izpack.installer.Installer.main(Installer.java:118)
Exception in thread "main" java.lang.InternalError: Can't connect to X11 window server using 'localhost:10.0' as the value of the DISPLAY variable.
at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)
at sun.awt.X11GraphicsEnvironment.access$200(X11GraphicsEnvironment.java:62)
at sun.awt.X11GraphicsEnvironment$1.run(X11GraphicsEnvironment.java:178)
at java.security.AccessController.doPrivileged(Native Method)
at sun.awt.X11GraphicsEnvironment.<clinit>(X11GraphicsEnvironment.java:142)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:82)
at sun.swing.SwingUtilities2.isLocalDisplay(SwingUtilities2.java:1393)
at javax.swing.plaf.metal.MetalLookAndFeel.initComponentDefaults(MetalLookAndFeel.java:1563)
at javax.swing.plaf.basic.BasicLookAndFeel.getDefaults(BasicLookAndFeel.java:147)
at javax.swing.plaf.metal.MetalLookAndFeel.getDefaults(MetalLookAndFeel.java:1599)
at javax.swing.UIManager.setLookAndFeel(UIManager.java:530)
at javax.swing.UIManager.setLookAndFeel(UIManager.java:570)
at javax.swing.UIManager.initializeDefaultLAF(UIManager.java:1320)
at javax.swing.UIManager.initialize(UIManager.java:1407)
at javax.swing.UIManager.maybeInitialize(UIManager.java:1395)
at javax.swing.UIManager.getInstalledLookAndFeels(UIManager.java:410)
at javax.swing.UIManager.installLookAndFeel(UIManager.java:453)
at com.incors.plaf.kunststoff.KunststoffLookAndFeel.<init>(KunststoffLookAndFeel.java:58)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
at java.lang.Class.newInstance0(Class.java:372)
at java.lang.Class.newInstance(Class.java:325)
at com.izforge.izpack.installer.GUIInstaller.loadLookAndFeel(GUIInstaller.java:532)
at com.izforge.izpack.installer.GUIInstaller.init(GUIInstaller.java:158)
at com.izforge.izpack.installer.GUIInstaller.<init>(GUIInstaller.java:120)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
at java.lang.Class.newInstance0(Class.java:372)
at java.lang.Class.newInstance(Class.java:325)
at com.izforge.izpack.installer.Installer.main(Installer.java:118)
 done
 cleaning up... done

*******************************************************************************
*                         For more information vist:                          *
*                  http://www.springsource.com/products/sts                   *
*******************************************************************************

Friday, July 20, 2012

Banana/Blueberry Bread Recipe

Makes  9" square pan:

Preparation Time: 30 minutes
Baking Time: 30 minutes

3/4 cup softened unsalted butter
1/2 cup sugar
2 eggs
2 cups flour (I use King Arthur light whole wheat)
1 tsp baking powder
5 banana's mashed
1 cup blueberries mashed

  1. In a large mixing bowl add butter and sugar, mix. Add eggs, make sure you mix well.  If the butter was melted in a microwave, make sure the butter temp isn't too hot or else the eggs will cook which is bad.
  2. Add  banana's and blueberries, mash.  I use a potato masher in the bowl with the butter and sugar.
  3. Add flour and baking powder (others recommend that this be done separately, but I'm lazy and try and mix it all in one bowl).  If the baking power is unevenly mixed, you'll get poorer results.
  4.  Coat the pan with (spray) shortening
  5. Bake at 350 (my oven isn't perfect so I use 360) for 40 minutes (or until a dipped toothpick comes out clean) 
Came up with this recipe yesterday 7/19 on the fly and came out great. Got a thumbs up from the kid crowd as well.

Tuesday, July 10, 2012

Cold Spring Travel Distances and Times to Manhattan. 


Red Light in Cold Spring to 87 (Nyack) is 27.3 miles and takes 32 minutes with no traffic.


Red Light in Cold Spring to George Washington Bridge is 44.2 miles and takes 48 minutes with no traffic.  To the tip of Manhattan it's 46.4 miles and 53 minutes.


Red Light in Cold Spring to Manhattan to 18th St and 9W is 56.2 Miles and takes 1 hr 10 minutes without any traffic. With light traffic on the 9W, it takes 1 hr 33 minutes.



Data is as follows:
ExitTimeMilesTraffic
Cold Spring Light (start)7:310N
Palisades 137:57
98:0327.360
N8:0831.560
48:123664
28:1438.964
18:1944.264
GWB8:2446.464
Cross Bronx 138:2649.3N
W95th8:3052N
798:3152.7N
428:3454.7N
308:3655.2N
188:3855.9N
9th Ave8:4056.2N
18th & 8th (end)8:4156.4N
16th & 8th (start)18:0458.2N
Boat Basin18:1861.4N
96th18:2562.3N
125th18:3263.540
135th18:3765.2slow
GWB18:4667.7ok
9E19:0686.4ok
1519:1696.6fast
Garrison Lib19:30110.1fast
Cold Spring Light (end)19:37114.1fast

Wednesday, March 14, 2012

Free Web Site Monitoring Utility Using Google Docs



I found a very cool web site monitoring utility today and set it up on my account.  I take no credit for this other than to help explain it a little better.  Here is the author's site:

http://joeriks.com/2011/05/17/coding-my-own-automated-web-page-tests-with-google-apps-script-and-a-spreadsheet/

Updated 7/20/2012: Added another column so a single error doesn't trigger an email now the URL must fail twice in a row before an email is sent out.

The instructions were good, but not perfect so here are some improvements to help you along.

Create a new Google Spreadsheet as follows:


Then, Add columns A, B, and C.  Notice that you can check the response-code, and look if text exists in the  response or if text doesn't exist as shown by these examples:


Choose the script editor (later you can view your scripts with the script manager):

Paste in the code

Rename the script

Give it a name and then hit the save button

Now you can test that it runs.  Click the Run/checkUrls function as shown:

Now you have to authorize that the script is allowed to email and update the spreadsheet.  Hit authorize:

If another confirmation comes up, just close it.   Click the Run/checkUrls function again:


Now you can close the script tab (or window) and go back to the spreadsheet window and you should see the spreadsheet (notice that I hovered over column E to show the contents of what was captured):


Now click Resources/All your triggers... and then add new trigger on the window that follows.

Add a trigger as shown (time-driven).  This will allow the script to run continually and update the spreadsheet when it's not open in a browser (very cool!).  Save it and...
That's it!  You now have a functional spreadsheet which will monitor all of your websites and email you if there is an issue.

Here's the code I used for this sample which comes 99% from  http://joeriks.com/2011/05/17/coding-my-own-automated-web-page-tests-with-google-apps-script-and-a-spreadsheet/ .   I implemented this as is with a change to remove the "ERROR" checking which was causing unnecessary error to be emailed AND a change to email more information about the error (including the response code i.e. 200 or 404 and the actual response text (which is useful to find out what went wrong).

Enjoy.

Code follows:

function isCellEmpty(cellData) {
  return typeof(cellData) == "string" && cellData == "";
}
 
function checkUrls() {
 
  // The code below iterates over rows in a sheet and uses the value in
  // column A as an url, requests it and puts the response code in column B
  // and the request string as the comment
 
  var doc = SpreadsheetApp.getActiveSpreadsheet();
  var cell = doc.getRange('a1');
  var mailMessage = "";
 
   
  // leftmost resultcolumn
   
  var resultColumn = 3;
 
  cell.offset(0,resultColumn).setValue(new Date());
  cell.offset(0,resultColumn+1).setValue("Content type");
  //20120704 mhf
  cell.offset(0,resultColumn+2).setValue("Success Prior Run?");
  cell.offset(0,resultColumn+3).setValue("Success?");
  cell.offset(0,resultColumn+4).setValue("Seconds");
  cell.offset(0,resultColumn+5).setValue("Comment");
   
  for (var i = 1; i < 20; ++i)
  {
    var cellData = cell.offset(i,0).getValue();
    if (!isCellEmpty(cellData))
    {
 
      var command = cell.offset(i,1).getValue();
      var optionData = cell.offset(i,2).getValue();
      if (optionData=="") optionData="{}";
      var options = Utilities.jsonParse(optionData);
       
      var hasError = false;
      var startTime = new Date();     
       
      if (command=="" | command=="GET")
      {
 
        var responseCode = 404;
        var requestContentText = "";
        var results = "";       
        var headers;
        var requestType ="";
        var contentType = "";
         
        var expectedResponseCode = 200;
        if (options["response-code"]!=undefined)
          expectedResponseCode = options["response-code"];
 
        try
        {
          var response = UrlFetchApp.fetch(cellData);
          responseCode = response.getResponseCode();
          requestContentText = response.getContentText();
          headers = response.getHeaders();        
          if (headers!=undefined)
            contentType=headers["Content-Type"].toLowerCase();
        }
        catch (e)
        {
          requestContentText = e.message;
        }
                 
        cell.offset(i,resultColumn).setValue(responseCode);
        if (responseCode!=expectedResponseCode)
        {
          hasError = true;     
          results += "Expected response code: " + expectedResponseCode;
        }
         
        if (contentType.indexOf("html")!=-1)
          cell.offset(i,resultColumn).setComment(requestContentText);
        else
          cell.offset(i,resultColumn).setComment("");
         
        cell.offset(i,resultColumn+1).setValue(contentType);
         
        // print results in column
        var colOffset = resultColumn+5;
         
        //// not contain the word ERROR
        //var containsError = (requestContentText.toLowerCase().indexOf("error") != -1);
        ////cell.offset(i,colOffset).setValue("Error: " + containsError);
        //if (containsError)
        //{
        //  results += "Error found. ";
        //  hasError = true;                     
        //}
         
        if (options["should-contain"]!=undefined)
        {
          // not contain the word ERROR
          var shouldContain = options["should-contain"].toLowerCase();
          var doesContain = (requestContentText.toLowerCase().indexOf(shouldContain) != -1);
          if (!doesContain)
          {
            results += "Not found: " + options["should-contain"] + ". ";
            hasError = true;                     
          }
           
        }
 
        if (options["should-not-contain"]!=undefined)
        {
          var shouldNotContain = options["should-not-contain"].toLowerCase();
          var doesContain = (requestContentText.toLowerCase().indexOf(shouldNotContain) != -1);
          if (doesContain)
          {
            results += "Found: " + options["should-not-contain"] + ". ";
            hasError = true;                     
          }
           
        }
        cell.offset(i,colOffset).setValue(results);
         
      }
       
      // timer       
      var endTime = new Date();     
      var timeDiff = endTime-startTime;

      // set the prior success column 20120704 mhf
      cell.offset(i,resultColumn+2).setValue(cell.offset(i,resultColumn+3).getValue());
      cell.offset(i,resultColumn+2).setBackgroundColor(cell.offset(i,resultColumn+3).getBackgroundColor());
      // success? (no errors)
      cell.offset(i,resultColumn+3).setValue(!hasError);
      // Only show an error if it happens 2 times in a row
      var priorStatus = cell.offset(i,resultColumn+2).getValue();
      var currStatus = cell.offset(i,resultColumn+3).getValue();
             
      if (hasError && currStatus == false && priorStatus == false)
      {
        cell.offset(i,resultColumn+3).setBackgroundColor("red");
        mailMessage += "ERROR on " + cellData + "\n" +
          results +
          " actual response code: " + responseCode + "\n" +
          "request content: " + requestContentText + "\n";
      }
      else
        cell.offset(i,resultColumn+3).setBackgroundColor("green");
       
      // time spent (in seconds)     
      cell.offset(i,resultColumn+4).setValue(timeDiff/1000);
       
 
    }
    else
    {
      break;
    }
  }
  if (mailMessage!="")
  {
    MailApp.sendEmail("spam@mitchellfeinstein.com", "ERROR on your web ", mailMessage);
  }
   
}
 
function getTime()
{
  var startTime = new Date();
  Browser.msgBox(startTime);
  var endTime = new Date();
  var timeDiff = endTime-startTime;
  Browser.msgBox(timeDiff);
}