Added version to the file header
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine.pm
index 3f6a99b..19ac1fc 100644 (file)
@@ -102,6 +102,44 @@ sub new {
     return $self;
 }
 
+sub write_file_signature {
+    my $self = shift;
+    my ($obj) = @_;
+
+    my $fh = $obj->_fh;
+
+    my $loc = $self->_request_space(
+        $obj, length( SIG_FILE ) + $self->{data_size},
+    );
+    seek($fh, $loc + $obj->_root->{file_offset}, SEEK_SET);
+    print( $fh SIG_FILE, pack($self->{data_pack}, 0) );
+
+    return;
+}
+
+sub read_file_signature {
+    my $self = shift;
+    my ($obj) = @_;
+
+    my $fh = $obj->_fh;
+
+    seek($fh, 0 + $obj->_root->{file_offset}, SEEK_SET);
+    my $buffer;
+    my $bytes_read = read(
+        $fh, $buffer, length(SIG_FILE) + $self->{data_size},
+    );
+
+    if ( $bytes_read ) {
+        my ($signature, $version) = unpack( "A4 $self->{data_pack}", $buffer );
+        unless ($signature eq SIG_FILE) {
+            $self->close_fh( $obj );
+            $obj->_throw_error("Signature not found -- file is not a Deep DB");
+        }
+    }
+
+    return $bytes_read;
+}
+
 sub setup_fh {
     my $self = shift;
     my ($obj) = @_;
@@ -109,22 +147,16 @@ sub setup_fh {
     $self->open( $obj ) if !defined $obj->_fh;
 
     my $fh = $obj->_fh;
-    print "1\n";
     flock $fh, LOCK_EX;
-    print "2\n";
 
     unless ( $obj->{base_offset} ) {
-        seek($fh, 0 + $obj->_root->{file_offset}, SEEK_SET);
-        my $signature;
-        my $bytes_read = read( $fh, $signature, length(SIG_FILE));
+        my $bytes_read = $self->read_file_signature( $obj );
 
         ##
         # File is empty -- write signature and master index
         ##
         if (!$bytes_read) {
-            my $loc = $self->_request_space( $obj, length( SIG_FILE ) );
-            seek($fh, $loc + $obj->_root->{file_offset}, SEEK_SET);
-            print( $fh SIG_FILE);
+            $self->write_file_signature( $obj );
 
             $obj->{base_offset} = $self->_request_space(
                 $obj, $self->tag_size( $self->{index_size} ),
@@ -144,14 +176,6 @@ sub setup_fh {
             $obj->{base_offset} = $bytes_read;
 
             ##
-            # Check signature was valid
-            ##
-            unless ($signature eq SIG_FILE) {
-                $self->close_fh( $obj );
-                $obj->_throw_error("Signature not found -- file is not a Deep DB");
-            }
-
-            ##
             # Get our type from master index signature
             ##
             my $tag = $self->load_tag($obj, $obj->_base_offset)
@@ -193,9 +217,8 @@ sub open {
         or $obj->_throw_error("Cannot sysopen file '$filename': $!");
     $obj->_root->{fh} = $fh;
 
-    #XXX Can we remove this by using the right sysopen() flags?
-    # Maybe ... q.v. above
-    binmode $fh; # for win32
+    # Even though we use O_BINARY, better be safe than sorry.
+    binmode $fh;
 
     if ($obj->_root->{autoflush}) {
         my $old = select $fh;
@@ -395,6 +418,59 @@ sub add_bucket {
     return $result;
 }
 
+sub _get_tied {
+    my $item = shift;
+    my $r = Scalar::Util::reftype( $item ) || return;
+    if ( $r eq 'HASH' ) {
+        return tied(%$item);
+    }
+    elsif ( $r eq 'ARRAY' ) {
+        return tied(@$item);
+    }
+    else {
+        return;
+    };
+}
+
+sub _get_dbm_object {
+    my $item = shift;
+
+    my $obj = eval {
+        local $SIG{__DIE__};
+        if ($item->isa( 'DBM::Deep' )) {
+            return $item;
+        }
+        return;
+    };
+    return $obj if $obj;
+
+    my $r = Scalar::Util::reftype( $item ) || '';
+    if ( $r eq 'HASH' ) {
+        my $obj = eval {
+            local $SIG{__DIE__};
+            my $obj = tied(%$item);
+            if ($obj->isa( 'DBM::Deep' )) {
+                return $obj;
+            }
+            return;
+        };
+        return $obj if $obj;
+    }
+    elsif ( $r eq 'ARRAY' ) {
+        my $obj = eval {
+            local $SIG{__DIE__};
+            my $obj = tied(@$item);
+            if ($obj->isa( 'DBM::Deep' )) {
+                return $obj;
+            }
+            return;
+        };
+        return $obj if $obj;
+    }
+
+    return;
+}
+
 sub write_value {
     my $self = shift;
     my ($obj, $location, $key, $value) = @_;
@@ -402,12 +478,10 @@ sub write_value {
     my $fh = $obj->_fh;
     my $root = $obj->_root;
 
-    my $is_dbm_deep = eval {
-        local $SIG{'__DIE__'};
-        $value->isa( 'DBM::Deep' );
-    };
-
-    my $is_internal_ref = $is_dbm_deep && ($value->_root eq $root);
+    my $dbm_deep_obj = _get_dbm_object( $value );
+    if ( $dbm_deep_obj && $dbm_deep_obj->_root ne $obj->_root ) {
+        $obj->_throw_error( "Cannot cross-reference. Use export() instead" );
+    }
 
     seek($fh, $location + $root->{file_offset}, SEEK_SET);
 
@@ -415,14 +489,20 @@ sub write_value {
     # Write signature based on content type, set content length and write
     # actual value.
     ##
-    my $r = Scalar::Util::reftype($value) || '';
-    if ( $is_internal_ref ) {
-        $self->write_tag( $obj, undef, SIG_INTERNAL,pack($self->{long_pack}, $value->_base_offset) );
+    my $r = Scalar::Util::reftype( $value ) || '';
+    if ( $dbm_deep_obj ) {
+        $self->write_tag( $obj, undef, SIG_INTERNAL,pack($self->{long_pack}, $dbm_deep_obj->_base_offset) );
     }
     elsif ($r eq 'HASH') {
+        if ( !$dbm_deep_obj && tied %{$value} ) {
+            $obj->_throw_error( "Cannot store something that is tied" );
+        }
         $self->write_tag( $obj, undef, SIG_HASH, chr(0)x$self->{index_size} );
     }
     elsif ($r eq 'ARRAY') {
+        if ( !$dbm_deep_obj && tied @{$value} ) {
+            $obj->_throw_error( "Cannot store something that is tied" );
+        }
         $self->write_tag( $obj, undef, SIG_ARRAY, chr(0)x$self->{index_size} );
     }
     elsif (!defined($value)) {
@@ -438,14 +518,14 @@ sub write_value {
     print( $fh pack($self->{data_pack}, length($key)) . $key );
 
     # Internal references don't care about autobless
-    return 1 if $is_internal_ref;
+    return 1 if $dbm_deep_obj;
 
     ##
     # If value is blessed, preserve class name
     ##
     if ( $root->{autobless} ) {
         my $value_class = Scalar::Util::blessed($value);
-        if ( defined $value_class && !$is_dbm_deep ) {
+        if ( defined $value_class && !$dbm_deep_obj ) {
             print( $fh chr(1) );
             print( $fh pack($self->{data_pack}, length($value_class)) . $value_class );
         }
@@ -458,29 +538,21 @@ sub write_value {
     # If content is a hash or array, create new child DBM::Deep object and
     # pass each key or element to it.
     ##
-    if ( !$is_internal_ref ) {
-        if ($r eq 'HASH') {
-            my $branch = DBM::Deep->new(
-                type => DBM::Deep->TYPE_HASH,
-                base_offset => $location,
-                root => $root,
-            );
-            foreach my $key (keys %{$value}) {
-                $branch->STORE( $key, $value->{$key} );
-            }
-        }
-        elsif ($r eq 'ARRAY') {
-            my $branch = DBM::Deep->new(
-                type => DBM::Deep->TYPE_ARRAY,
-                base_offset => $location,
-                root => $root,
-            );
-            my $index = 0;
-            foreach my $element (@{$value}) {
-                $branch->STORE( $index, $element );
-                $index++;
-            }
-        }
+    if ($r eq 'HASH') {
+        my %x = %$value;
+        tie %$value, 'DBM::Deep', {
+            base_offset => $location,
+            root => $root,
+        };
+        %$value = %x;
+    }
+    elsif ($r eq 'ARRAY') {
+        my @x = @$value;
+        tie @$value, 'DBM::Deep', {
+            base_offset => $location,
+            root => $root,
+        };
+        @$value = @x;
     }
 
     return 1;
@@ -583,13 +655,13 @@ sub read_from_loc {
     # If value is a hash or array, return new DBM::Deep object with correct offset
     ##
     if (($signature eq SIG_HASH) || ($signature eq SIG_ARRAY)) {
-        my $obj = DBM::Deep->new(
+        my $new_obj = DBM::Deep->new({
             type => $signature,
             base_offset => $subloc,
             root => $obj->_root,
-        );
+        });
 
-        if ($obj->_root->{autobless}) {
+        if ($new_obj->_root->{autobless}) {
             ##
             # Skip over value and plain key to see if object needs
             # to be re-blessed
@@ -597,7 +669,8 @@ sub read_from_loc {
             seek($fh, $self->{data_size} + $self->{index_size}, SEEK_CUR);
 
             my $size;
-            read( $fh, $size, $self->{data_size}); $size = unpack($self->{data_pack}, $size);
+            read( $fh, $size, $self->{data_size});
+            $size = unpack($self->{data_pack}, $size);
             if ($size) { seek($fh, $size, SEEK_CUR); }
 
             my $bless_bit;
@@ -607,13 +680,14 @@ sub read_from_loc {
                 # Yes, object needs to be re-blessed
                 ##
                 my $class_name;
-                read( $fh, $size, $self->{data_size}); $size = unpack($self->{data_pack}, $size);
+                read( $fh, $size, $self->{data_size});
+                $size = unpack($self->{data_pack}, $size);
                 if ($size) { read( $fh, $class_name, $size); }
-                if ($class_name) { $obj = bless( $obj, $class_name ); }
+                if ($class_name) { $new_obj = bless( $new_obj, $class_name ); }
             }
         }
 
-        return $obj;
+        return $new_obj;
     }
     elsif ( $signature eq SIG_INTERNAL ) {
         my $size;