File Coverage

blib/lib/SPOPS/HashFile.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             package SPOPS::HashFile;
2              
3             # $Id: HashFile.pm,v 3.4 2004/06/02 00:48:21 lachoy Exp $
4              
5 1     1   78428 use strict;
  1         2  
  1         43  
6 1     1   5 use base qw( SPOPS );
  1         2  
  1         905  
7             use Data::Dumper;
8              
9             $SPOPS::HashFile::VERSION = sprintf("%d.%02d", q$Revision: 3.4 $ =~ /(\d+)\.(\d+)/);
10              
11             # Just grab the tied hash from the SPOPS::TieFileHash
12              
13             sub new {
14             my ( $pkg, $p ) = @_;
15             my $class = ref $pkg || $pkg;
16             my ( %data );
17             my $int = tie %data, 'SPOPS::TieFileHash', $p->{filename}, $p->{perm};
18             my $object = bless( \%data, $class );
19             $object->initialize( $p );
20             return $object;
21             }
22              
23             # Subclasses can override
24              
25             sub initialize { return 1 }
26              
27             sub class_initialize {
28             my $class = shift;
29             return $class->_class_initialize( @_ );
30             }
31              
32              
33             # Just pass on the parameters to 'new'
34              
35             sub fetch {
36             my ( $class, $filename, $p ) = @_;
37             $p ||= {};
38             return undef unless ( $class->pre_fetch_action( $filename, $p ) );
39             my $object = $class->new( { filename => $filename, %{ $p } } );
40             return undef unless( $object->post_fetch_action( $filename, $p ) );
41             return $object;
42             }
43              
44              
45             # Ensure we can write and that the filename is kosher, then
46             # dump out the data to the file.
47              
48             sub save {
49             my ( $self, $p ) = @_;
50             my $obj = tied %{ $self };
51              
52             unless ( $obj->{perm} eq 'write' ) {
53             SPOPS::Exception->throw( "Cannot save [$obj->{filename}]; opened as read-only" );
54             }
55              
56             unless ( $obj->{filename} ) {
57             SPOPS::Exception->throw( "Cannot save data: the filename has been " .
58             "erased. Did you assign an empty hash to the object?" );
59             }
60              
61             my $temp_filename = "$obj->{filename}.tmp";
62             if ( -f $temp_filename ) {
63             unlink( $temp_filename ); # just to be sure...
64             }
65             if ( -f $obj->{filename} ) {
66             rename( $obj->{filename}, $temp_filename ) ||
67             SPOPS::Exception->throw( "Cannot rename old file to make room for new one. Error: $!" );
68             }
69              
70             return undef unless ( $self->pre_save_action( $p ) );
71              
72             my %data = %{ $obj->{data} };
73             $p->{dumper_level} ||= 2;
74             local $Data::Dumper::Indent = $p->{dumper_level};
75              
76             eval { open( INFO, "> $obj->{filename}" ) || die $! };
77             if ( $@ ) {
78             rename( $temp_filename, $obj->{filename} ) ||
79             SPOPS::Exception->throw( "Cannot open file for writing [$@] and " .
80             "cannot move backup file to original place [$!]" );
81             SPOPS::Exception->throw( "Cannot open file for writing [$@]. Backup file restored ok." );
82             }
83             print INFO Data::Dumper->Dump( [ \%data ], [ 'data' ] );
84             close( INFO );
85             if ( -f $temp_filename ) {
86             unlink( $temp_filename )
87             || warn "Cannot remove the old data file. It still lingers in ($temp_filename)\n";
88             }
89             return undef unless ( $self->post_save_action( $p ) );
90             return $self;
91             }
92              
93              
94             sub remove {
95             my ( $self, $p ) = @_;
96             my $obj = tied %{ $self };
97             unless ( $obj->{perm} eq 'write' ) {
98             SPOPS::Exception->throw( "Cannot save [$obj->{filename}]; opened as read-only" );
99             }
100             unless ( $obj->{filename} ) {
101             SPOPS::Exception->throw( "Cannot remove data: the filename has been " .
102             "erased. Did you assign an empty hash to the object?" );
103             }
104             return undef unless ( $self->pre_remove_action( $p ) );
105             my $rv = %{ $self } = ();
106             return undef unless ( $self->post_remove_action( $p ) );
107             return $rv;
108             }
109              
110              
111             # Create a new object from an old one, allowing any passed-in
112             # values to override the ones from the old object
113              
114             sub clone {
115             my ( $self, $p ) = @_;
116             unless ( $p->{filename} ) {
117             $p->{filename} = (tied %{ $self })->{filename};
118             }
119             my $new = $self->new({ filename => $p->{filename}, perm => $p->{perm} });
120             while ( my ( $k, $v ) = each %{ $self } ) {
121             $new->{ $k } = $p->{ $k } || $v;
122             }
123             return $new;
124             }
125              
126              
127              
128             package SPOPS::TieFileHash;
129              
130             use strict;
131             use File::Copy qw( cp );
132              
133             $SPOPS::TieFileHash::VERSION = sprintf("%d.%02d", q$Revision: 3.4 $ =~ /(\d+)\.(\d+)/);
134              
135             # These are all very standard routines for a tied hash; more info: see
136             # 'perldoc Tie::Hash'
137              
138             # Ensure that the file exists and can be read (unless they pass in
139             # 'new' for the permissions, which means it's ok to start out with
140             # blank data); store the meta info (permission and filename) in the
141             # object, and the 'data' key holds the actual information
142              
143             sub TIEHASH {
144             my ( $class, $filename, $perm ) = @_;
145             $perm ||= 'read';
146             if ( $perm !~ /^(read|write|new|write\-new)$/ ) {
147             SPOPS::Exception->throw( "Invalid permissions [$perm]; valid: [read|write|new|write-new]" );
148             }
149             unless ( $filename ) {
150             SPOPS::Exception->throw( "You must pass a filename to use for reading and writing." );
151             }
152             my $file_exists = ( -f $filename );
153             unless ( $file_exists ) {
154             if ( $perm eq 'write-new' or $perm eq 'new' ) {
155             $perm = 'new';
156             }
157             else {
158             SPOPS::Exception->throw( "Cannot create object without existing file " .
159             "or 'new' permission [$filename] [$perm]" );
160             }
161             }
162             $perm = 'write' if ( $perm eq 'write-new' );
163              
164             my $data = undef;
165             if ( $file_exists ) {
166              
167             # First create a backup...
168              
169             cp( $filename, "${filename}.backup" );
170              
171             # Then open up the file
172              
173             open( PD, $filename ) ||
174             SPOPS::Exception->throw( "Cannot open [$filename]: $!" );
175             local $/ = undef;
176             my $info = ;
177             close( PD );
178              
179             # Note that we create the SIG{__WARN__} handler here to trap any
180             # messages that might be sent to STDERR; we want to capture the
181             # message and send it along in a 'die' instead
182              
183             {
184             local $SIG{__WARN__} = sub { return undef };
185             no strict 'vars';
186             $data = eval $info;
187             }
188             if ( $@ ) {
189             SPOPS::Exception->throw( "Error reading in perl code: $@" );
190             }
191             }
192             else {
193             $data = {};
194             $perm = 'write';
195             }
196             return bless({ data => $data,
197             filename => $filename,
198             perm => $perm }, $class );
199             }
200              
201              
202             sub FETCH {
203             my ( $self, $key ) = @_;
204             return undef unless $key;
205             return $self->{data}->{ $key };
206             }
207              
208              
209             sub STORE {
210             my ( $self, $key, $value ) = @_;
211             return undef unless $key;
212             return $self->{data}->{ $key } = $value;
213             }
214              
215              
216             sub EXISTS {
217             my ( $self, $key ) = @_;
218             return undef unless $key;
219             return exists $self->{data}->{ $key };
220             }
221              
222              
223             sub DELETE {
224             my ( $self, $key ) = @_;
225             return undef unless $key;
226             return delete $self->{data}->{ $key };
227             }
228              
229              
230             # This allows people to do '%{ $obj } = ();' and remove the object; is
231             # this too easy to mistakenly do? I don't think so.
232              
233             sub CLEAR {
234             my ( $self ) = @_;
235             if ( $self->{perm} ne 'write' ) {
236             SPOPS::Exception->throw( "Cannot remove [$self->{filename}]; " .
237             "permission set to read-only" );
238             }
239             unlink( $self->{filename} ) ||
240             SPOPS::Exception->throw( "Cannot remove [$self->{filename}]: $!" );
241             $self->{data} = undef;
242             $self->{perm} = undef;
243             }
244              
245              
246             sub FIRSTKEY {
247             my ( $self ) = @_;
248             keys %{ $self->{data} };
249             my $first_key = each %{ $self->{data} };
250             return undef unless ( $first_key );
251             return $first_key;
252             }
253              
254              
255             sub NEXTKEY {
256             my ( $self ) = @_;
257             my $next_key = each %{ $self->{data} };
258             return undef unless ( $next_key );
259             return $next_key;
260             }
261              
262              
263             1;
264              
265             __END__