Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/70HyphenFormat.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/70HyphenFormat.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/70HyphenFormat.pm	(revision 30347)
@@ -32,4 +32,8 @@
 #
 ###############################################################################
+
+# Note: This driver may be a candidate for further splitting, maybe into a
+# PipedExecutableDriver and a 70HyphenFormatDriver... but for now all piped
+# drivers are 70 hyphen format ones, so, yeah.
 
 package DBDrivers::70HyphenFormat;
@@ -59,4 +63,5 @@
     $self->{'read_executable'} = 'error';
     $self->{'write_executable'} = 'error';
+    $self->{'forced_affinity'} = -1; # Set to processor number for forced affinity
     bless($self, $class);
     return $self;
@@ -68,16 +73,4 @@
 
 
-## @function close_infodb_handle(filehandle)
-#
-sub close_infodb_handle
-{
-    my $self = shift(@_);
-    my $infodb_handle = shift(@_);
-    $self->debugPrintFunctionHeader();
-    close($infodb_handle);
-}
-## close_infodb_handle(filehandle) => void ##
-
-
 ## @function close_infodb_write_handle(filehandle)
 #
@@ -85,5 +78,12 @@
 {
     my $self = shift(@_);
-    $self->close_infodb_handle(@_);
+    $self->debugPrintFunctionHeader(@_);
+    my $handle = shift(@_);
+    my $force_close = shift(@_); # Undefined most of the time
+    my $continue_close = $self->removeConnectionIfPersistent($handle, $force_close);
+    if ($continue_close) {
+	close($handle);
+    }
+    return;
 }
 ## close_infodb_write_handle(filehandle) => void ##
@@ -142,10 +142,9 @@
 {
     my $self = shift(@_);
+    $self->debugPrintFunctionHeader(@_);
     my $infodb_handle = shift(@_);
     my $infodb_key = shift(@_);
-
     # A minus at the end of a key (after the ]) signifies 'delete'
-    print $infodb_handle "[$infodb_key]-\n";
-
+    print $infodb_handle '[' . $infodb_key . ']-' . "\n";
     # The 70 minus signs are also needed, to help make the parsing by db2txt simple
     print $infodb_handle '-' x 70, "\n";
@@ -160,5 +159,12 @@
     my $self = shift(@_);
     $self->debugPrintFunctionHeader(@_);
-    my $infodb_file_handle = $self->openWriteHandle(@_);
+    my $path = shift(@_);
+    my $append = shift(@_);
+    my $infodb_file_handle = $self->retrieveConnectionIfPersistent($path, $append);;
+    # No available existing connection
+    if (!defined $infodb_file_handle || !$infodb_file_handle) {
+        $infodb_file_handle = $self->openWriteHandle($path, $append, @_);
+	$self->registerConnectionIfPersistent($infodb_file_handle, $path, $append);
+    }
     return $infodb_file_handle;
 }
@@ -181,6 +187,15 @@
     }
     my $infodb_file_handle = undef;
-    my $cmd = '"' . $exe . '" ' . $default_args;
+    my $cmd = '';
+    if ($self->{'forced_affinity'} >= 0)
+    {
+        $cmd = 'taskset -c ' . $self->{'forced_affinity'} . ' ';
+    }
+    $cmd .= '"' . $exe . '" ' . $default_args;
     foreach my $open_arg (@_) {
+	# Special - append is typically missing a hyphen
+	if ($open_arg eq 'append') {
+	    $open_arg = '-append';
+	}
 	$cmd .= ' ' . $open_arg;
     }
@@ -199,4 +214,5 @@
 
 ## @function openReadHandle(string, string) => filehandle
+#
 sub openReadHandle
 {
@@ -207,4 +223,6 @@
 
 
+## @function openWriteHandle(*) => filehandle
+#
 sub openWriteHandle
 {
@@ -212,4 +230,6 @@
     return $self->openPipedHandle(RWMODE_WRITE, $self->{'write_executable'}, @_);
 }
+## openWriteHandle(*) => filehandle ##
+
 
 ## @function read_infodb_entry(string, string) => hashmap
@@ -232,4 +252,5 @@
     my $infodb_file_path = shift(@_);
     my $infodb_map = shift(@_);
+    $self->debugPrintFunctionHeader($infodb_file_path, $infodb_map);
     my $infodb_file_handle = $self->openReadHandle($infodb_file_path);
     my $infodb_line = "";
@@ -250,5 +271,5 @@
         }
     }
-  $self->close_infodb_handle($infodb_file_handle);
+  $self->close_infodb_write_handle($infodb_file_handle);
 }
 ## read_infodb_file(string, hashmap) => void ##
@@ -291,5 +312,5 @@
 	}
     }
-    $self->close_infodb_handle($infodb_file_handle);
+    $self->close_infodb_write_handle($infodb_file_handle);
 }
 ## read_infodb_keys(string, hashmap) => void ##
@@ -354,5 +375,5 @@
 	print $infodb_file_handle "[$infodb_key]\n";
 	print $infodb_file_handle "$serialized_infodb_map\n";
-	$self->close_infodb_handle($infodb_file_handle);
+	$self->close_infodb_write_handle($infodb_file_handle);
 	$status = 0; # as in exit status of cmd OK
     }
Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/BaseDBDriver.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/BaseDBDriver.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/BaseDBDriver.pm	(revision 30347)
@@ -44,8 +44,15 @@
     # Debug messages for this driver
     $self->{'debug'} = $debug; # 1 to enable
+    # Keep track of all opened file handles, but only for drivers that support
+    # persistent connections
+    $self->{'handle_pool'} = {};
     # Default file extension - in this case it is an error to create a DB from
     # BaseDBDriver
     $self->{'default_file_extension'} = 'err';
     # Support
+    $self->{'supports_datestamp'} = 0;
+    $self->{'supports_merge'} = 0;
+    $self->{'supports_persistentconnection'} = 0;
+    $self->{'supports_rss'} = 0;
     $self->{'supports_set'} = 0;
     bless($self, $class);
@@ -68,5 +75,5 @@
     if ($self->{'debug'}) {
 	my ($seconds, $microseconds) = gettimeofday();
-	print STDERR '[DEBUG:' . $seconds . '.' . $microseconds . '] ' . (caller 1)[3] . $message . "\n";
+	print STDERR '[DEBUG:' . $seconds . '.' . $microseconds . '] ' . (caller 1)[3] . '() ' . $message . "\n";
     }
 }
@@ -112,4 +119,130 @@
 ## errorPrint(string, integer) => void ##
 
+
+## @function registerConnectionIfPersistent(filehandle, string, string) => void
+#
+sub registerConnectionIfPersistent
+{
+    my $self = shift(@_);
+    my $conn = shift(@_);
+    my $path = shift(@_);
+    my $append = shift(@_);
+    if ($self->{'supports_persistentconnection'}) {
+	$self->debugPrintFunctionHeader($conn, $path, $append);
+	my $fhid = $path;
+	if (defined $append && $append eq '-append') {
+	    $fhid .= ' [APPEND]';
+	}
+	$self->debugPrint('Registering connection: "' . $fhid . '"');
+	$self->{'handle_pool'}->{$fhid} = $conn;
+    }
+    return;
+}
+## registerConnectionIfPersistent(filehandle, string, string) => void ##
+
+
+## @function removeConnectionIfPersistent(filehandle, string) => integer
+#
+sub removeConnectionIfPersistent
+{
+    my $self = shift(@_);
+    my $handle = shift(@_);
+    my $force_close = shift(@_);
+    my $continue_close = 1;
+    if ($self->{'supports_persistentconnection'}) {
+	$self->debugPrintFunctionHeader($handle, $force_close);
+	if (defined($force_close)) {
+	    # We'll need the file path so we can locate and remove the entry
+	    # in the handle pool (plus possibly the [APPEND] suffix for those
+	    # connections in opened in append mode)
+	    my $fhid = undef;
+	    # Sometimes we can cheat, as the force_close variable will have the
+	    # file_path in it thanks to the DESTROY block above. Doing a regex
+	    # on force_close will treat it like a string no matter what it was,
+	    # and we can search for the appropriate file extension that should
+	    # be there for valid paths.
+	    my $pattern = '\.' . $self->{'default_file_extension'} . '(\s\[APPEND\])?$';
+	    if ($force_close =~ /$pattern/) {
+		$fhid = $force_close;
+	    }
+	    # If we can't cheat then we are stuck finding which connection in
+	    # the handle_pool we are about to close. Need to compare objects
+	    # using refaddr()
+	    else {
+		foreach my $possible_fhid (keys %{$self->{'handle_pool'}}) {
+		    my $possible_handle = $self->{'handle_pool'}->{$possible_fhid};
+		    if (ref($handle) && ref($possible_handle) && refaddr($handle) == refaddr($possible_handle)) {
+			$fhid = $possible_fhid;
+			last;
+		    }
+		}
+	    }
+	    # If we found the fhid we can proceed to close the connection
+	    if (defined($fhid)) {
+		$self->debugPrint('Closing persistent connection: ' . $fhid);
+		delete($self->{'handle_pool'}->{$fhid});
+		$continue_close = 1;
+	    }
+	    else {
+		print STDERR "Warning! About to close persistent database handle, but couldn't locate in open handle pool.\n";
+	    }
+	}
+	# Persistent connection don't close *unless* force close is set
+	else {
+	    $continue_close = 0;
+	}
+    }
+    return $continue_close;
+}
+## removeConnectionIfPersistent(filehandle, string) => integer ##
+
+
+##
+#
+sub retrieveConnectionIfPersistent
+{
+    my $self = shift(@_);
+    my $path = shift(@_);
+    my $append = shift(@_); # -append support
+    my $conn; # This should be populated
+    if ($self->{'supports_persistentconnection'}) {
+	$self->debugPrintFunctionHeader($path, $append);
+	my $fhid = $path;
+	# special case: if the append mode has changed for a persistent
+	# connection, we need to close the old connection first or things
+	# will get wiggy.
+	if (defined $append && $append eq '-append') {
+	    # see if there is a non-append mode connection already open
+	    if (defined $self->{'handle_pool'}->{$path}) {
+		$self->debugPrint("Append mode added - closing existing non-append mode connection");
+		my $old_conn = $self->{'handle_pool'}->{$path};
+		$self->close_infodb_write_handle($old_conn, $path);
+	    }
+	    # Append -append so we know what happened.
+	    $fhid .= ' [APPEND]';
+	}
+	else {
+	    my $fhid_append = $path . ' [APPEND]';
+	    if (defined $self->{'handle_pool'}->{$fhid_append}) {
+		$self->debugPrint("Append mode removed - closing existing append mode connection");
+		my $old_conn = $self->{'handle_pool'}->{$fhid_append};
+		$self->close_infodb_write_handle($old_conn, $fhid_append);
+	    }
+	}
+	if (defined $self->{'handle_pool'}->{$fhid}) {
+	    $self->debugPrint('Retrieving existing connection: ' . $fhid);
+	    $conn = $self->{'handle_pool'}->{$fhid};
+	}
+    }
+    return $conn;
+}
+## ##
+
+
+
+
+
+
+
 ###############################################################################
 ## Public Functions
@@ -126,4 +259,10 @@
     my $infodb_file_name = &util::get_dirsep_tail($collection_name) . '.' . $self->{'default_file_extension'};
     my $infodb_file_path = &FileUtils::filenameConcatenate($infodb_directory_path, $infodb_file_name);
+    # Correct the path separators to work in Cygwin
+    if ($^O eq "cygwin") {
+	$infodb_file_path = `cygpath -w "$infodb_file_path"`;
+	chomp($infodb_file_path);
+	$infodb_file_path =~ s%\\%\\\\%g;
+    }
     return $infodb_file_path;
 }
@@ -131,12 +270,12 @@
 
 
-## @function supportsDatestamp(void) => boolean
+## @function supportsDatestamp(void) => integer
 #
 sub supportsDatestamp
 {
     my $self = shift(@_);
-    return 0;
-}
-## supportsDatestamp(void) => boolean ##
+    return $self->{'supports_datestamp'};
+}
+## supportsDatestamp(void) => integer ##
 
 
@@ -146,17 +285,27 @@
 {
     my $self = shift(@_);
-    return 0;
-}
-## supportsMerge(void) => boolean ##
-
-
-## @function supportsRSS(void) => boolean
+    return $self->{'supports_merge'};
+}
+## supportsMerge(void) => integer ##
+
+
+## @function supportsPersistentConnection(void) => integer
+#
+sub supportsPersistentConnection
+{
+    my $self = shift(@_);
+    return $self->{'supports_persistentconnection'};
+}
+## supportsPersistentConnection(void) => integer ##
+
+
+## @function supportsRSS(void) => integer
 #
 sub supportsRSS
 {
     my $self = shift(@_);
-    return 0;
-}
-## supportsRSS(void) => boolean ##
+    return $self->{'supports_rss'};
+}
+## supportsRSS(void) => integer ##
 
 
Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/GDBM.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/GDBM.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/GDBM.pm	(revision 30347)
@@ -29,4 +29,11 @@
 use strict;
 
+BEGIN
+{
+    if (!defined $ENV{'GSDLHOME'} || !defined $ENV{'GSDLOS'}) {
+        die("Error! Environment not prepared. Have you sourced setup.bash?\n");
+    }
+}
+
 # Libraries
 use util;
@@ -47,4 +54,5 @@
     $self->{'keyread_executable'} = 'gdbmkeys';
     $self->{'write_executable'} = 'txt2db';
+    # Optional Support
     $self->{'supports_set'} = 1;
     bless($self, $class);
@@ -59,7 +67,8 @@
 
 # Handled by BaseDBDriver
-# sub get_infodb_file_path(string, string)
+# sub get_infodb_file_path(string, string) => string
 
 # Handled by 70HyphenFormat
+# sub open_infodb_write_handle(string, string?) => filehandle
 # sub close_infodb_write_handle(filehandle) => void
 # sub delete_infodb_entry(filehandle, string) => void
@@ -72,28 +81,3 @@
 # sub write_infodb_rawentry(filehandle, string, string) => void
 
-
-## @function open_infodb_write_handle(string, string*) => filehandle
-#
-#  Handles legacy use of optional 'append' argument where '-append' is required
-#
-sub open_infodb_write_handle
-{
-    my $self = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $opt_append = shift(@_);
-    my $infodb_file_handle;
-    if (defined $opt_append) {
-	if ($opt_append eq 'append') {
-	    $opt_append = '-append';
-	}
-	$infodb_file_handle = $self->SUPER::open_infodb_write_handle($infodb_file_path, $opt_append);
-    }
-    else
-    {
-	$infodb_file_handle = $self->SUPER::open_infodb_write_handle($infodb_file_path);
-    }
-    return $infodb_file_handle;
-}
-## open_infodb_write_handle(string, string*) => filehandle ##
-
 1;
Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/GDBMTXTGZ.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/GDBMTXTGZ.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/GDBMTXTGZ.pm	(revision 30347)
@@ -77,6 +77,6 @@
 # other
 #
-# All this function does now is turn the optional 'append' argument into the
-# appropriate operator for either appending to or clobbering gzip file.
+# Now only responsible for transforming the optional append argument into the
+# correct redirection operand (either > for clobber or >> for append)
 #
 sub open_infodb_write_handle
@@ -98,5 +98,4 @@
 ## open_infodb_write_handle(string) => filehandle ##
 
-
 ## @function set_infodb_entry(string, string, hashmap)
 #
Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/JDBM.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/JDBM.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/JDBM.pm	(revision 30347)
@@ -33,5 +33,5 @@
 use FileUtils;
 # - OO inheritence
-use parent 'DBDrivers::BaseDBDriver';
+use parent 'DBDrivers::70HyphenFormat';
 
 sub BEGIN
@@ -48,6 +48,26 @@
 {
     my $class = shift(@_);
-    my $self = DBDrivers::BaseDBDriver->new();
+    my $self = DBDrivers::70HyphenFormat->new(@_);
     $self->{'default_file_extension'} = 'jdb';
+
+    # Executables need a little extra work since we are using Java
+    # - we need to build up the classpath continue the Jar libraries to use
+    my $jdbmwrap_jar = &FileUtils::filenameConcatenate($ENV{'GSDLHOME'}, 'bin', 'java', 'JDBMWrapper.jar');
+    my $jdbm_jar = &FileUtils::filenameConcatenate($ENV{'GSDLHOME'}, 'lib', 'java', 'jdbm.jar');
+    my $classpath = &util::pathname_cat($jdbmwrap_jar,$jdbm_jar);
+    # Massage paths for Cygwin. Away to run a java program, using a binary that
+    # is native to Windows, so need Windows directory and path separators
+    if ($^O eq "cygwin") {
+	$classpath = `cygpath -wp "$classpath"`;
+	chomp($classpath);
+	$classpath =~ s%\\%\\\\%g;
+    }
+    $self->{'executable_path'} = '';
+    $self->{'read_executable'} = 'java -cp "' . $classpath . '" Jdb2Txt';
+    $self->{'keyread_executable'} = 'java -cp "' . $classpath . '" JdbKeys';
+    $self->{'write_executable'} = 'java -cp "' . $classpath . '" Txt2Jdb';
+    # Support
+    $self->{'supports_set'} = 1;
+
     bless($self, $class);
     return $self;
@@ -68,278 +88,13 @@
 # sub get_infodb_file_path {}
 
-
-
-sub open_infodb_write_handle
-{
-    my $self = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $opt_append = shift(@_);
-    if (!defined $opt_append) {
-	$opt_append = '';
-    }
-    $self->_debugPrint('("' . $infodb_file_path . '","' . $opt_append . '")');
-
-    my $jdbmwrap_jar = &FileUtils::filenameConcatenate($ENV{'GSDLHOME'},"bin","java", "JDBMWrapper.jar");
-    my $jdbm_jar = &FileUtils::filenameConcatenate($ENV{'GSDLHOME'},"lib","java", "jdbm.jar");
-
-  my $classpath = &util::pathname_cat($jdbmwrap_jar,$jdbm_jar);
-
-  if ($^O eq "cygwin") {
-      # Away to run a java program, using a binary that is native to Windows, so need
-      # Windows directory and path separators
-
-      $classpath = `cygpath -wp "$classpath"`;
-      chomp($classpath);
-      $classpath =~ s%\\%\\\\%g;
-  }
-
-  my $infodb_file_handle = undef;
-  my $txt2jdb_cmd = "java -cp \"$classpath\" Txt2Jdb";
-
-  if ($opt_append eq "append") {
-      $txt2jdb_cmd .= " -append";
-      print STDERR "Append operation to $infodb_file_path\n";
-  }
-  else {
-      print STDERR "Create database $infodb_file_path\n";
-  }
-  
-  # Lop off file extension, as JDBM does not expect this to be present
-  $infodb_file_path =~ s/\.jdb$//;
-
-  if ($^O eq "cygwin") {
-      $infodb_file_path = `cygpath -w "$infodb_file_path"`;
-      chomp($infodb_file_path);
-      $infodb_file_path =~ s%\\%\\\\%g;
-  }
-
-  $txt2jdb_cmd .= " \"$infodb_file_path\"";
-
-  if (!open($infodb_file_handle, "| $txt2jdb_cmd"))
-  {
-      print STDERR "Error: Failed to open pipe to $txt2jdb_cmd";
-      print STDERR "       $!\n";
-      return undef;
-  }
-  
-  binmode($infodb_file_handle,":utf8");
-  return $infodb_file_handle;
-}
-
-
-
-sub close_infodb_write_handle
-{
-  my $infodb_handle = shift(@_);
-
-  close($infodb_handle);
-}
-
-
-sub read_infodb_file
-{
-  my $infodb_file_path = shift(@_);
-  my $infodb_map = shift(@_);
-
-  my $jdbmwrap_jar = &util::filename_cat($ENV{'GSDLHOME'},"bin","java", "JDBMWrapper.jar");
-  my $jdbm_jar = &util::filename_cat($ENV{'GSDLHOME'},"lib","java", "jdbm.jar");
-
-  my $classpath = &util::pathname_cat($jdbmwrap_jar,$jdbm_jar);
-
-  if ($^O eq "cygwin") {
-      # Away to run a java program, using a binary that is native to Windows, so need
-      # Windows directory and path separators
-      
-      $classpath = `cygpath -wp "$classpath"`;
-      chomp($classpath);
-      $classpath =~ s%\\%\\\\%g;
-
-      $infodb_file_path = `cygpath -w "$infodb_file_path"`;
-      chomp($infodb_file_path);
-      $infodb_file_path =~ s%\\%\\\\%g;
-  }
-
-  my $jdb2txt_cmd = "java -cp \"$classpath\" Jdb2Txt";
-
-  open (PIPEIN, "$jdb2txt_cmd \"$infodb_file_path\" |") || die "couldn't open pipe from db2txt \$infodb_file_path\"\n";
-  binmode(PIPEIN,":utf8");
-  my $infodb_line = "";
-  my $infodb_key = "";
-  my $infodb_value = "";
-  while (defined ($infodb_line = <PIPEIN>))
-  {
-    $infodb_line =~ s/(\r\n)+$//; # more general than chomp
-
-    if ($infodb_line =~ /^\[([^\]]+)\]$/)
-    {
-      $infodb_key = $1;
-    }
-    elsif ($infodb_line =~ /^-{70}$/)
-    {
-      $infodb_map->{$infodb_key} = $infodb_value;
-      $infodb_key = "";
-      $infodb_value = "";
-    }
-    else
-    {
-      $infodb_value .= $infodb_line;
-    }
-  }
-
-  close (PIPEIN);
-}
-
-sub read_infodb_keys
-{
-  my $infodb_file_path = shift(@_);
-  my $infodb_map = shift(@_);
-
-  my $jdbmwrap_jar = &util::filename_cat($ENV{'GSDLHOME'},"bin","java", "JDBMWrapper.jar");
-  my $jdbm_jar = &util::filename_cat($ENV{'GSDLHOME'},"lib","java", "jdbm.jar");
-
-  my $classpath = &util::pathname_cat($jdbmwrap_jar,$jdbm_jar);
-
-  my $jdbkeys_cmd = "java -cp \"$classpath\" JdbKeys";
-
-  open (PIPEIN, "$jdbkeys_cmd \"$infodb_file_path\" |") || die "couldn't open pipe from jdbmkeys \$infodb_file_path\"\n";
-  binmode(PIPEIN,":utf8");
-  my $infodb_line = "";
-  my $infodb_key = "";
-  my $infodb_value = "";
-  while (defined ($infodb_line = <PIPEIN>))
-  {
-      # chomp $infodb_line; # remove end of line 
-      $infodb_line =~ s/(\r\n)+$//; # more general than chomp
-
-      $infodb_map->{$infodb_line} = 1;
-  }
-
-  close (PIPEIN);
-}
-
-
-    
-sub write_infodb_entry
-{
-
-  my $infodb_handle = shift(@_);
-  my $infodb_key = shift(@_);
-  my $infodb_map = shift(@_);
-
-  print $infodb_handle "[$infodb_key]\n";
-  foreach my $infodb_value_key (keys(%$infodb_map))
-  {
-    foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
-    {
-      if ($infodb_value =~ /-{70,}/)
-      {
-        # if value contains 70 or more hyphens in a row we need to escape them
-        # to prevent txt2db from treating them as a separator
-        $infodb_value =~ s/-/&\#045;/gi;
-      }
-      print $infodb_handle "<$infodb_value_key>" . $infodb_value . "\n";
-    }
-  }
-  print $infodb_handle '-' x 70, "\n";
-}
-
-
-sub write_infodb_rawentry
-{
-
-  my $infodb_handle = shift(@_);
-  my $infodb_key = shift(@_);
-  my $infodb_val = shift(@_);
-  
-  print $infodb_handle "[$infodb_key]\n";
-  print $infodb_handle "$infodb_val\n";
-  print $infodb_handle '-' x 70, "\n";
-}
-
-sub set_infodb_entry
-{
-    my $infodb_file_path = shift(@_);
-    my $infodb_key = shift(@_);
-    my $infodb_map = shift(@_);
-  
-    # HTML escape anything that is not part of the "contains" metadata value
-    foreach my $k (keys %$infodb_map) {
-	  my @escaped_v = ();
-	  foreach my $v (@{$infodb_map->{$k}}) {
-	    if ($k eq "contains") {
-		  push(@escaped_v, $v);
-	    }
-	    else {
-		  my $ev = &ghtml::unescape_html($v);
-		  push(@escaped_v, $ev);
-	    }
-	  }
-	  $infodb_map->{$k} = \@escaped_v;
-	}
-	
-    # Generate the record string
-    my $serialized_infodb_map = &dbutil::convert_infodb_hash_to_string($infodb_map);
-###    print STDERR "**** ser dr\n$serialized_infodb_map\n\n\n";
-
-    # Store it into JDBM using 'Txt2Jdb .... -append' which despite its name
-    # actually replaces the record if it already exists
-
-    my $jdbmwrap_jar = &util::filename_cat($ENV{'GSDLHOME'},"bin","java", "JDBMWrapper.jar");
-    my $jdbm_jar = &util::filename_cat($ENV{'GSDLHOME'},"lib","java", "jdbm.jar");
-    
-    my $classpath = &util::pathname_cat($jdbmwrap_jar,$jdbm_jar);
-
-    # Lop off file extension, as JDBM does not expect this to be present
-    $infodb_file_path =~ s/\.jdb$//;
-
-    if ($^O eq "cygwin") {
- 	# Away to run a java program, using a binary that is native to Windows, so need
-	# Windows directory and path separators
-	
-      $classpath = `cygpath -wp "$classpath"`;
-      chomp($classpath);
-      $classpath =~ s%\\%\\\\%g;
-
-      $infodb_file_path = `cygpath -w "$infodb_file_path"`;
-      chomp($infodb_file_path);
-      $infodb_file_path =~ s%\\%\\\\%g;
-    }
-
-    my $cmd = "java -cp \"$classpath\" Txt2Jdb -append \"$infodb_file_path\"";
-
-    my $status = undef;
-    if(!open(GOUT, "| $cmd"))
-    {
-	print STDERR "Error: jdbm::set_infodb_entry() failed to open pipe to: $cmd\n";
-	print STDERR "       $!\n";
-	$status = -1;
-    }
-    else {
-	binmode(GOUT,":utf8");
-	
-	print GOUT "[$infodb_key]\n";
-	print GOUT "$serialized_infodb_map\n";
-
-	close(GOUT);
-	$status = 0; # as in exit status of cmd OK
-    }
-
-    return $status;  
-}
-
-
-
-
-sub delete_infodb_entry
-{
-  my $infodb_handle = shift(@_);
-  my $infodb_key = shift(@_);
-  
-  # A minus at the end of a key (after the ]) signifies 'delete'
-  print $infodb_handle "[$infodb_key]-\n"; 
-
-  # The 70 minus signs are also needed, to help make the parsing by db2txt simple
-  print $infodb_handle '-' x 70, "\n";
-}
+# Handles by 70HyphenFormat
+# sub open_infodb_write_handle(string, string?) => filehandle
+# sub close_infodb_write_handle(filehandle) => void
+# sub read_infodb_file(string, hashmap) => void
+# sub read_infodb_keys(string, hashmap) => void
+# sub write_infodb_entry(filehandle, string, hashmap) => void
+# sub write_infodb_rawentry(filehandle, string, string) => void
+# sub set_infodb_entry(filehandle, string, string) => void
+# sub delete_infodb_entry(filehandle, string) => void
 
 1;
Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/Readme.txt
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/Readme.txt	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/Readme.txt	(revision 30347)
@@ -1,8 +1,22 @@
 ===== DBDriver =====
+
+Note that there are a couple of Drivers that could be further separated to
+have even better OO, but I started to get bogged down in multiple inheritence
+problems so I left them as is for now. For instance, separating PipedExecutable
+support from the 70HyphenFormat driver would increase flexibility, but then it
+becomes tricky to say which should inherit from which (in a single inheritence)
+or what order methods should be resolved (in multiple inheritence).
 
 ==== Inheritence Overview ====
 
-  * BaseDBDriver - superclass of all drivers. Some shared utility methods.
-    * PipedDrivers - drivers that write and read their data via pipes to external executables. Data is in simple Greenstone archive form (i.e. key/value pairs and separated by seventy hyphens)
+  * BaseDBDriver - superclass of all drivers. Some shared utility methods
+                   including support for persistent connections (ala TDB).
+		   Thus this is a candidate for separating out the the
+		   PersistentConnectionsDriver.
+    * 70HyphenFormat - drivers that write and read their data via pipes to
+                       external executables. Data is in simple Greenstone
+		       archive form (i.e. key/value pairs and separated by
+		       seventy hyphens) - this is a candidate for further
+		       separating out a PipedExecutableDriver.
       * GDBM - makes use of GDBM utils (txt2db, db2txt etc)
       * GDBMTXTGZ - makes use of gzip (for later use with GDBM)
Index: /gs2-extensions/tdb/trunk/perllib/DBDrivers/TDB.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/DBDrivers/TDB.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/DBDrivers/TDB.pm	(revision 30347)
@@ -53,5 +53,5 @@
     my $class = shift(@_);
 
-    my $self = DBDrivers::GDBM->new();
+    my $self = DBDrivers::GDBM->new(@_);
 
     # Default TDB file extension
@@ -59,8 +59,16 @@
     # Should the TDB used a specific affinity?
     $self->{'forced_affinity'} = -1; # zero upwards indicates the affinity
-    # Keep track of all opened file handles
-    $self->{'handle_pool'} = {};
     # Ask TDB executables to display debugging information?
-    $self->{'tdb_debug'} = 0; # 1 to enable
+    $self->{'tdb_debug'} = 1; # 1 to enable
+
+    # note: file separator agnostic
+    $self->{'executable_path'} = $ENV{GEXTTDBEDIT_INSTALLED} . '/bin/';
+    $self->{'read_executable'} = 'tdb2txt';
+    $self->{'keyread_executable'} = 'tdbkeys';
+    $self->{'write_executable'} = 'txt2tdb';
+
+    # Optional Support
+    $self->{'supports_persistentconnection'} = 1;
+    $self->{'supports_set'} = 1;
 
     bless($self, $class);
@@ -96,32 +104,17 @@
 # -----------------------------------------------------------------------------
 
+# Handled by BaseDBDriver
+# sub debugPrint(string) => void
+# sub debugPrintFunctionHeader(*) => void
+# sub get_infodb_file_path(string, string) => string
 
-## @function _get_tdb_executable(string)
-#
-sub _get_tdb_executable
-{
-    my $self = shift(@_);
-    my $program = shift(@_);
-    if (!defined $ENV{GEXTTDBEDIT_INSTALLED} || !-d $ENV{GEXTTDBEDIT_INSTALLED})
-    {
-	die('Fatal Error! Path to TDB binaries not found. Have you sourced setup.bash?');
-    }
-    my $program_exe = &util::filename_cat($ENV{GEXTTDBEDIT_INSTALLED} . '/bin/' . $program . &util::get_os_exe());
-    if (!-x $program_exe)
-    {
-	die('Fatal Error! File doesn\'t exist or isn\'t executable: ' . $program_exe);
-    }
-    return $program_exe;
-}
-## _get_tdb_executable(string) => string ##
-
-
-# Handled by BaseDBDriver
-# sub get_infodb_file_path(string, string)
-
-# With infodb_handle already set up, these functions work the same as parent version
-# sub delete_infodb_entry {}
-# sub write_infodb_entry {}
-# sub write_infodb_rawentry {}
+# Handled by 70HyphenFormat
+# sub read_infodb_entry(string, string) => hashmap
+# sub read_infodb_file(string, hashmap) => void
+# sub read_infodb_keys(string, hashmap) => void
+# sub read_infodb_rawentry(string, string) => string
+# sub set_infodb_entry(string, string, hashmap) => integer
+# sub write_infodb_entry(filehandle, string, hashmap) => void
+# sub write_infodb_rawentry(filehandle, string, string) => void
 
 
@@ -144,8 +137,8 @@
 sub close_infodb_write_handle {
     my $self = shift(@_);
+    $self->debugPrintFunctionHeader(@_);
     my $infodb_handle = shift(@_);
     my $actually_close = shift(@_); # Undefined most of the time
     if (defined($actually_close)) {
-	$self->_debugPrint('(<infodb_handle>,"' . $actually_close . '")');
 	# We'll need the file path so we can locate and remove the entry in the
 	# handle pool
@@ -156,5 +149,5 @@
 	# we can search for the appropriate file extension that should be there
 	# for valid paths.
-	my $pattern = '\.' . $self->{'default_file_extension'} . '$';
+	my $pattern = '\.' . $self->{'default_file_extension'} . '(\s\[APPEND\])?$';
 	if ($actually_close =~ /$pattern/) {
 	    $infodb_file_path = $actually_close;
@@ -173,4 +166,5 @@
 	}
 	if (defined($infodb_file_path)) {
+            $self->debugPrint('Closing connection: ' . $infodb_file_path);
 	    delete($self->{'handle_pool'}->{$infodb_file_path});
 	}
@@ -181,6 +175,11 @@
 	$self->SUPER::close_infodb_write_handle($infodb_handle);
     }
+    else {
+	$self->debugPrint('Connection persists for later use.');
+    }
 }
 ## close_infodb_write_handle(filehandle) => void ##
+
+# sub delete_infodb_entry {}
 
 
@@ -190,159 +189,11 @@
 {
     my $self = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $opt_append = shift(@_);
-
-    my $txt2tdb_exe = $self->_get_tdb_executable('txt2tdb');
-
-    my $cmd = '"' . $txt2tdb_exe . '"';
-    if ((defined $opt_append) && ($opt_append eq "append")) {
-        $cmd .= ' -append';
+    if ($self->{'tdb_debug'}) {
+        push(@_, '-debug');
     }
-    $cmd .= ' "' . $infodb_file_path . '"';
-    # Optional flags
-    if ($self->{'forced_affinity'} >= 0) {
-        $cmd = 'taskset -c 5 ' . $cmd;
-    }
-    if ($self->{'debug'}) {
-        $cmd .= ' -debug';
-    }
-
-    # we're going to pipe the key value pairs, in the appropriate format, from
-    # within the buildproc, so we create a piped handle here
-    my $infodb_file_handle = undef;
-    # if the connection is already open, simply return it.
-    if (defined $self->{'handle_pool'}->{$infodb_file_path}) {
-	$infodb_file_handle = $self->{'handle_pool'}->{$infodb_file_path};
-    }
-    else {
-	$self->_debugPrint('(' . $infodb_file_path . ')');
-	if(!open($infodb_file_handle, "| $cmd")) {
-	    print STDERR "Error: Failed to open pipe to $cmd\n";
-	    print STDERR "       $!\n";
-	    return undef;
-	}
-	binmode($infodb_file_handle,":utf8");
-	# Remember to store the newly created connection in the pool so we can
-	# re-use for subsequent calls.
-	$self->{'handle_pool'}->{$infodb_file_path} = $infodb_file_handle;
-    }
-    return $infodb_file_handle;
+    my $handle = $self->SUPER::open_infodb_write_handle(@_);
+    return $handle;
 }
 ## open_infodb_write_handle(string, string) => filehandle ##
 
-
-## @function read_infodb_file
-#
-sub read_infodb_file
-{
-    my $self = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $infodb_map = shift(@_);
-
-    $self->_debugPrint('(' . $infodb_file_path . ', <hashmap>)');
-
-    my $tdb2txt_exe = $self->_get_tdb_executable('tdb2txt');
-
-    if (!open (PIPEIN, '"' . $tdb2txt_exe . '" "' . $infodb_file_path . '" |')) {
-        print STDERR 'Error: Failed to open pipe to ' . $tdb2txt_exe . "\n";
-        print STDERR "       $!\n";
-        return undef;
-    }
-
-    binmode(PIPEIN,":utf8");
-
-    my $infodb_line = "";
-    my $infodb_key = "";
-    my $infodb_value = "";
-    while (defined ($infodb_line = <PIPEIN>)) {
-        if ($infodb_line =~ /^\[([^\]]+)\]$/) {
-            $infodb_key = $1;
-        }
-        elsif ($infodb_line =~ /^-{70}$/) {
-            $infodb_map->{$infodb_key} = $infodb_value;
-            $infodb_key = "";
-            $infodb_value = "";
-        }
-        else {
-            $infodb_value .= $infodb_line;
-        }
-    }
-    close (PIPEIN);
-}
-## read_infodb_file(string, hashmap) => void ##
-
-
-## @function read_infodb_keys(string, hashmap)
-#
-sub read_infodb_keys
-{
-    my $self = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $infodb_map = shift(@_);
-
-    $self->_debugPrint('(' . $infodb_file_path . ', <hashmap>)');
-
-    my $tdbkeys_exe = $self->_get_tdb_executable('tdbkeys');
-
-    if (!open (PIPEIN, '"' . $tdbkeys_exe . '" "' . $infodb_file_path . '" |')) {
-        die("Error! Couldn't open pipe from read_infodb_keys: $infodb_file_path\n$!\n");
-    }
-
-    binmode(PIPEIN,":utf8");
-
-    my $infodb_line = "";
-    my $infodb_key = "";
-    my $infodb_value = "";
-    while (defined ($infodb_line = <PIPEIN>)) {
-        # remove end of line
-        chomp $infodb_line;
-        $infodb_map->{$infodb_line} = 1;
-    }
-
-    close (PIPEIN);
-}
-## read_infodb_keys(string, hashmap) => void ##
-
-
-## @function set_infodb_entry(string, string, hashmap)
-#
-sub set_infodb_entry
-{
-    my $self = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $infodb_key = shift(@_);
-    my $infodb_map = shift(@_);
-
-    $self->_debugPrint('(' . $infodb_file_path . ', ' . $infodb_key . ', <hashmap>)');
-
-    # Protect metadata values that go inside quotes for tdbset
-    foreach my $k (keys %$infodb_map) {
-        my @escaped_v = ();
-        foreach my $v (@{$infodb_map->{$k}}) {
-            if ($k eq "contains") {
-                # protect quotes in ".2;".3 etc
-                $v =~ s/\"/\\\"/g;
-                push(@escaped_v, $v);
-            }
-            else {
-                my $ev = &ghtml::unescape_html($v);
-                $ev =~ s/\"/\\\"/g;
-                push(@escaped_v, $ev);
-            }
-        }
-        $infodb_map->{$k} = \@escaped_v;
-    }
-
-    # Generate the record string
-    my $serialized_infodb_map = $self->_convert_infodb_hash_to_string($infodb_map);
-
-    # Store it into GDBM
-    my $tdbset_exe = $self->_get_tdb_executable('tdbset');
-    my $cmd = '"' . $tdbset_exe . '" "' . $infodb_file_path . '" "' . $infodb_key . '" "' . $serialized_infodb_map . '"';
-    my $status = system($cmd);
-
-    return $status;
-}
-## set_infodb_entry(string, string, hashmap) => integer ##
-
 1;
Index: /gs2-extensions/tdb/trunk/perllib/dbutil.pm
===================================================================
--- /gs2-extensions/tdb/trunk/perllib/dbutil.pm	(revision 30346)
+++ /gs2-extensions/tdb/trunk/perllib/dbutil.pm	(revision 30347)
@@ -37,11 +37,9 @@
     }
     # Are we running standalone? In which case the INC won't be correct
-    #if (!caller) {
-	# Ensure the INC includes the path to FileUtils.pm
-	unshift(@INC, $ENV{'GSDLHOME'} . '/perllib');
-
-    #}
-    require DBDrivers::GDBM;
-    my $driver = DBDrivers::GDBM->new(1);
+    my $perllib_path = $ENV{'GSDLHOME'} . '/perllib';
+    my $all_inc = join(':', @INC);
+    if ($all_inc !~ /$perllib_path/) {
+	unshift(@INC, $perllib_path);
+    }
 }
 
@@ -240,4 +238,7 @@
 		 'tea' => ['a drink with jam and bread'],
 		 'doh' => ['which brings us back to']};
+    $test_count = 0;
+    $pass_count = 0;
+    $skip_count = 0;
     print "===== DBUtils Testing Suite =====\n";
     print "For each driver specified, run a battery of tests\n";
@@ -259,5 +260,6 @@
 	&_addPathsToINC();
 	foreach my $driver_name (@drivers) {
-	    print "* Testing: " . $driver_name . "\n";
+	    my $t1 = [gettimeofday()];
+	    print "=== Testing: " . $driver_name . " ===\n";
 	    my $driver = _loadDBDriver($driver_name);
 	    my $db_path = $driver->get_infodb_file_path('test','/tmp/');
@@ -274,6 +276,13 @@
 	    &_printTest('writing raw entry', 1);
 	    # 3. Close handle
-	    $driver->close_infodb_handle($db_handle);
-	    &_printTest('closing handle', (tell($db_handle) < 1));
+	    $driver->close_infodb_write_handle($db_handle);
+	    if ($driver->supportsPersistentConnection()) {
+		$test_count += 1;
+		$skip_count += 1;
+		print " - Skipping test as persistent drivers delay 'close'.\n";
+	    }
+	    else {
+		&_printTest('closing handle', (tell($db_handle) < 1));
+	    }
 	    # 4a. Read entry
 	    my $data3 = $driver->read_infodb_entry($db_path, 'Alpha');
@@ -302,5 +311,5 @@
 	    my $db_handle2 = $driver->open_infodb_write_handle($db_path, 'append');
 	    $driver->delete_infodb_entry($db_handle2, 'Alpha');
-	    $driver->close_infodb_handle($db_handle2);
+	    $driver->close_infodb_write_handle($db_handle2);
 	    my $keys2 = {};
 	    $driver->read_infodb_keys($db_path, $keys2);
@@ -308,4 +317,7 @@
 	    # 8. Remove test db
 	    #unlink($db_path);
+	    my $t2 = [gettimeofday()];
+	    my $elapsed1 = tv_interval($t1, $t2);
+	    print " - Testing took " . $elapsed1 . " seconds\n";
 	}
 	print "===== Results =====\n";
@@ -320,7 +332,7 @@
 	print "Warning! No drivers specified - expected as arguments to call\n";
     }
-    my $t1 = [gettimeofday()];
-    my $elapsed = tv_interval($t0, $t1);
-    print "===== Complete in " . $elapsed . " seconds =====\n";
+    my $t3 = [gettimeofday()];
+    my $elapsed2 = tv_interval($t0, $t3);
+    print "===== Complete in " . $elapsed2 . " seconds =====\n";
     print "\n";
     exit(0);
