Archive for February, 2007

BestBuy online is lame

I had a few Christmas gift cards to burn, so last week I ordered Marvel Ultimate Alliance for the Wii from BestBuy’s web site. At check out, I noticed the system had a previous address on file. I backed out and updated my profile. But when I completed the order it still had my previous address in the shipping information. Crap.

Read the rest of this entry »

Comments (3)

File system comparison in ColdFusion

One of my first projects in ColdFusion was a tool that compares files between two different folders and reports differences. The tool was intended for developers to identify differences between code in different phases of development. It wasn’t very fast, and when run against a large amount of files it crapped out completely.

I recently overhauled the code and was able to make it much faster. Maybe someone else will find it useful.

In the process I learned a few things:

  • <cfdirectory action=”list” recurse=”yes” /> is not always efficient. Directly using java.io.file can be dramatically faster — up to 200% faster when listing files & directories from a network location.
  • Opening two files into memory using <cffile> and comparing the contents is not the most efficient way to tell if they are different. Duh.
  • Recursively calling a function is a great way to walk through multi-level data.

I ended up tightening things down to two functions. BuildFileDictionary walks through a given base folder using java.io.File path and builds a struct of file information. The struct is keyed on the relative path of each file, thus the FileDictionary naming. This way, I can access information for a specific file using simple code like this: FileDictionary[relativeFilePath]. It also helps determine which files from one source do not exist under another by simply checking for the existence of a struct key.

The second function, CompareFileDictionaries compares two file dictionary structs and returns a couple lists: files only in dictionary one, files only in dictionary two, and files in common but out-of-synch. I settled on file size and last modified date as an acceptable basis to indicate file differences. There are other indicators, but these values were the most efficient to access.

BuildFileDictionary

<cffunction name="BuildFileDictionary" returntype="struct" access="public" output="false"
	hint="Recurses through a directory, building a struct with file information. The result struct is keyed on each file’s path relative to the passed-in base path. ie - details on TopFolder/SubFolder/File.txt are accessed through ReturnStruct[’TopFolder/SubFolder/File.txt’]">

	<cfargument name="BasePath" type="string" 
		hint="Full path to the folder you want to build a dictionary for. UNC paths are acceptable.">
	<cfargument name="ExcludeFileExtensions" type="string" 
		hint="List of file extensions to exclude from results.">
	<cfargument name="ExcludeDirectories" type="string" 
		hint="List of directories to exclude from results. Leave off trailing / for each.">		
	<cfargument name="Files" type="any" default="-999" required="false"
		hint="Optional Java File collection to recurse. Leave this arg off to start with the BasePath location.">
	<cfargument name="FileDictionary" type="struct" default="#StructNew()#" required="false"
		hint="FileDictionary to append results to. Leave this arg off to start fresh.">

	<cfset var fileWalker = ArrayNew(1) />
	<cfset var fileCounter = "" />
	<cfset var relFilePath = "" />

	<!— Grab collection of files from Java.io.File if not passed in —>	
	<!— Java.io.File is *much* faster than cfdirectory action=’list’ especially when accessing UNC paths —>
	<cfif arguments.Files EQ -999>
		<cfif DirectoryExists(BasePath)>
			<cfset arguments.Files = createObject("java","java.io.File").init(arguments.BasePath).listFiles() />
		<cfelse>
			<cfthrow type="Application" message="BasePath does not exist" detail="The provided BasePath does not exist. Please enter a valid BasePath." />
		</cfif>
	</cfif>

	<cfset fileWalker = arguments.Files />

	<!— Loop through current level of files and directories —>
	<cfloop from="1" to="#ArrayLen(fileWalker)#" index="fileCounter">
		<cfset relFilePath = Replace(fileWalker[fileCounter].getAbsolutePath(),arguments.BasePath,"") />

		<!— Recursively call this function for sub-directory —>
		<cfif fileWalker[fileCounter].isDirectory()>
			<cfif ListFindNoCase(arguments.ExcludeDirectories,relFilePath) EQ 0>
				<cfinvoke method="BuildFileDictionary">
					<cfinvokeargument name="BasePath" value="#arguments.BasePath#">
					<cfinvokeargument name="ExcludeFileExtensions" value="#arguments.ExcludeFileExtensions#">
					<cfinvokeargument name="ExcludeDirectories" value="#arguments.ExcludeDirectories#">					
					<cfinvokeargument name="Files" value="#fileWalker[fileCounter].listFiles()#">
					<cfinvokeargument name="FileDictionary" value="#arguments.FileDictionary#">
				</cfinvoke>
			</cfif>
		<!— Grab details about this file —>
		<cfelseif fileWalker[fileCounter].isFile()>
			<cfif ListContainsNoCase(arguments.ExcludeFileExtensions,ListLast(relFilePath,".")) EQ 0>
				<cfset arguments.FileDictionary[relFilePath] = StructNew() />
				<cfset arguments.FileDictionary[relFilePath].FileName = fileWalker[fileCounter].getName() />
				<cfset arguments.FileDictionary[relFilePath].FilePath = fileWalker[fileCounter].getPath() />
				<cfset arguments.FileDictionary[relFilePath].AbsolutePath = fileWalker[fileCounter].getAbsolutePath() />
				<!— The hash value can be used to identify file contents, but it seems to slow things down to grab it —>
				<!— <cfset arguments.FileDictionary[dictionaryKey].HashCode = fileWalker[fileCounter].hashCode() /> —>
				<cfset arguments.FileDictionary[relFilePath].LastModified = fileWalker[fileCounter].lastModified() />
				<cfset arguments.FileDictionary[relFilePath].Size = fileWalker[fileCounter].length() />
			</cfif>
		</cfif>

	</cfloop>

	<cfreturn arguments.FileDictionary />

</cffunction>

CompareFileDictionaries

<cffunction name="CompareFileDictionaries" access="public" output="false" returntype="struct"
	hint="Compares two file dictionaries and returns a struct containing files only in one, files only in two, and common files out of synch.">

	<cfargument name="fileDictionaryOne" type="struct" required="yes" hint="File dictionary one">
	<cfargument name="fileDictionaryTwo" type="struct" required="yes" hint="File dictionary two">

	<!— Variable declarations —>
	<cfset var comparisonResults = StructNew() /> 
	<cfset var relFilePath = "" />

	<!— Build up struct properties for results —>
	<cfset comparisonResults.NamesOneOnly = ArrayNew(1) />
	<cfset comparisonResults.NamesTwoOnly = ArrayNew(1) />
	<cfset comparisonResults.OutOfSynch = false />
	<cfset comparisonResults.NamesCommonOutOfSynch = ArrayNew(1) />

	<!— Loop through the relative file paths in the first file dictionary checking if the —>
	<!— same file relative path exists in the second file dictionary. If it does exist in —>
	<!— second dictionary, check if files attributes are out of synch between the two.    —>	
	<cfloop list="#ListSort(StructKeyList(arguments.fileDictionaryOne),"textnocase","ASC")#" index="fileRelativePath">
		<cfif StructKeyExists(arguments.fileDictionaryTwo,fileRelativePath)>
			<cfif (arguments.fileDictionaryOne[fileRelativePath].LastModified NEQ arguments.fileDictionaryTwo[fileRelativePath].LastModified)
					AND (arguments.fileDictionaryOne[fileRelativePath].Size NEQ arguments.fileDictionaryTwo[fileRelativePath].Size)>
				<!— File last modified data and file size does not match, add to NamesCommonOutOfSynch array —>
				<cfset ArrayAppend(comparisonResults.NamesCommonOutOfSynch,fileRelativePath) />
			</cfif>
		<cfelse>
			<!— File does not exist in second dictionary, add to NamesOneOnly —>
			<cfset ArrayAppend(comparisonResults.NamesOneOnly,fileRelativePath) />				
		</cfif>
	</cfloop>

	<!— Loop through the relative file paths in the second dictionary checking if each exists —>
	<!— in the first dictionary. —>
	<cfloop list="#ListSort(StructKeyList(arguments.fileDictionaryTwo),"textnocase","ASC")#" index="fileRelativePath">
		<cfif not StructKeyExists(arguments.fileDictionaryOne,fileRelativePath)>
			<cfset ArrayAppend(comparisonResults.NamesTwoOnly,fileRelativePath) />
		</cfif>
	</cfloop>

	<cfif ArrayLen(comparisonResults.NamesCommonOutOfSynch) GT 0 
			OR ArrayLen(comparisonResults.NamesOneOnly) GT 0
			OR ArrayLen(comparisonResults.NamesTwoOnly) GT 0>
		<cfset comparisonResults.OutOfSynch = true />
	</cfif>

	<cfreturn comparisonResults />		

</cffunction>

Comments (2)

Half Nelson

Half NelsonNot since the double-sided funfest that was Requiem for a Dream has a movie left me feeling like I was just punched in the stomach for two hours straight.

Picture sickly teacher dude buying drugs from a disadvantaged teenage student on the bathroom floor of a skeevey hotel room while prostitutes smoke crack and dance about the room. Yeah…

Add Half Nelson to the list of movies to watch when seeking out feelings of intense hopelessness.

Comments

Skin Radio

This summer I went with the wife on a road trip that ended in Seattle. As we approached the city, it was like we had stepped back into 1995. The radio stations mixed in songs from Alice in Chains, Soundgarden and Stone Temple Pilots songs with some newer alternative rock songs. Coming from the wasteland of Philly radio, it was pretty damn cool to hear something different again even if it wasn’t new.

I found out today that Philly has a new radio station: Skin Radio. It’s an AM radio station — WHAT 1340, but it’s better than most of the crap on the FM dial right now. I listened to their Internet stream for most of the day today. It’s a little bit free-format college radio, a little bit 90’s throw-back, and lot rough edges. But compared to the local alternatives, I’m interested to see where this goes.

Now if only there was some way to resurrect WDRE

Comments (2)

Swords and porn

Boing boing has the details on why swords and porn do not mix:

Dude in Wisconsin hears woman shrieking for help in apartment upstairs. Said dude rushes upstairs, wielding an antique sword, kicks down the door to save the damsel in distress, and discovers another dude sitting alone, watching a porn DVD.

Comments

Pesky MS SQL GO statements

I have never really grokked Microsoft SQL Server’s GO statement. Sure, I had a vague notion that it grouped execution of SQL statements in Microsoft utilities. But the specifics eluded me. Perhaps this is one of the reasons why I’m not a SQL administrator at work.

But enough about my short-comings, and on to an example of how not understanding GO can cause problems. Here’s a typical CREATE PROCEDURE generated by SQL Server Management Studio:

GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE  PROCEDURE [dbo].[some_procedure_name]
(
	@param_name_one varchar(60),
	@param_name_two integer,
	@param_name_three bit
)
AS

UPDATE some_table
SET some_field_one = @param_name_one
	,some_field_two = @param_name_two
WHERE some_field_three = @param_name_three

When I want to publish some development stored procs to a production server, usually I auto-generate create scripts for the stored procs. My current work environment uses different sql users for each project database, so I need to append a GRANT EXECUTE ON [proc_name] TO app_user_name for each proc to assign appropriate permissions.

But without a GO statment between the CREATE PROCEDURE and the GRANT statement, the grant statement never runs. It actually becomes part of the stored procedure.

SP_HELPTEXT on the procedure shows the resulting proc text:

CREATE  PROCEDURE [dbo].[some_procedure_name]
(
	@param_name_one varchar(60),
	@param_name_two integer,
	@param_name_three bit
)
AS

UPDATE some_table
SET some_field_one = @param_name_one
	,some_field_two = @param_name_two
WHERE some_field_three = @param_name_three

GRANT EXECUTE on [some_procedure_name] TO app_user_name

Eeeg! That’s not what I expected. It seems that placing a GO after the CREATE PROCEDURE script tells the Microsoft script engine to terminate the batch of SQL statements and start a new one for the GRANT statement. That way, the body of the proc ends at the GO, and the GRANT statement actually executes.

Comments (4)

Press the Whoomp button


My sister gave me a Whoomp Button for Christmas. It’s like a Staples Easy Button, but it plays “Whoomp! There It Is.” Now the internets can join in the fun.

Comments (9)

The Good Books

It just ain’t a party until the MLA Handbook comes out.

Comments (4)

Setting Active Directory account expiration date

I needed to set the expiration date for Active Directory accounts in a VB.net web service today. Sounds simple, but it’s actually a bit tricky.

Turns out there’s a way to get around using the nonsensical large integer that AD uses to store the accountExpires field:

Dim NewADObject As DirectoryEntry
Dim ADContainer As DirectoryEntry

ADContainer = New DirectoryEntry(adPath,user,pass,authtype)
NewADObject = ADContainer.Children.Add(cn,objectClass)

NewADObject.Properties("SAMAccountName") = "newusername"

... yadda yadda yadda ...

'Set expiration date for account
Dim expireDate As DateTime = DomainAccountExpDate
NewADObject.NativeObject.AccountExpirationDate = expireDate

NewADObject.CommitChanges()

The bold bit is the pertinent section. NativeObject allows direct access to the properties of the underlying DirectoryEntry COM component. AccountExpirationDate can be set as a DateTime value.

Comments

Step away from the peanut butter

Peanut Butter PoopiePeter Pan peanut butter includes a secret ingredient: salmonella.

We have two jars of it and were already halfway through one of them. Gross.

Comments (2)

« Previous entries