File Coverage

blib/lib/File/Flat.pm
Criterion Covered Total %
statement 262 330 79.3
branch 159 258 61.6
condition 25 46 54.3
subroutine 50 79 63.2
pod 26 29 89.6
total 522 742 70.3


line stmt bran cond sub pod time code
1             package File::Flat; # git description: fabfdb3
2             # ABSTRACT: Implements a flat filesystem
3              
4             # The File::Flat is a static class that provides a unified interface
5             # to the filesystem in a way such that directories are abstracted away.
6              
7             # This should work on non-Unix platforms, but there may be some
8             # minor remaining bugs.
9              
10 3     3   72007 use 5.005;
  3         20  
11 3     3   15 use strict;
  3         6  
  3         71  
12 3     3   17 use Cwd ();
  3         5  
  3         37  
13 3     3   21 use File::Spec ();
  3         12  
  3         42  
14 3     3   1459 use IO::File ();
  3         26258  
  3         87  
15 3     3   1546 use prefork 'File::Temp';
  3         2883  
  3         16  
16 3     3   184 use prefork 'File::Copy';
  3         6  
  3         10  
17 3     3   130 use prefork 'File::Copy::Recursive';
  3         4  
  3         10  
18 3     3   130 use prefork 'File::Remove';
  3         5  
  3         9  
19              
20             our $VERSION = '1.05';
21              
22             # The main error string
23             our $errstr = '';
24              
25             # Create a map of all file open modes we support,
26             # and which ones will create a new file if needed.
27             our %modes = (
28             '<' => 0, 'r' => 0, # Read
29             '+<' => 1, 'r+' => 1, # ReadWrite
30             '>' => 1, 'w' => 1, # Write
31             '+>' => 1, 'w+' => 1, # ReadWrite
32             '>>' => 1, 'a' => 1 # Append
33             );
34              
35             our $AUTO_PRUNE = '';
36              
37              
38             #####################################################################
39             # Examining the file system
40              
41             # Does a filesystem entity exist.
42 6 50   6 1 133 sub exists { defined $_[1] and -e $_[1] }
43              
44             # Is a filesystem object a file.
45 5 50   5 1 102 sub isaFile { defined $_[1] and -f $_[1] }
46              
47             # Is a filesystem object a directory.
48 6 50   6 1 442 sub isaDirectory { defined $_[1] and -d $_[1] }
49              
50             # Do we have permission to read a filesystem object.
51 7 100 66 7 1 1418 sub canRead { defined $_[1] and -e $_[1] and -r _ }
52              
53             # Do we have permission to write to a filesystem object.
54             # If it doesn't exist, can we create it.
55             sub canWrite {
56             # If it already exists, check normally
57 27 100   27 1 2307 return -w $_[1] if -e $_[1];
58              
59             # Can we create it
60 18 50       172 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
61 18         74 $Object->_canCreate;
62             }
63              
64             # Can we both read and write to a filesystem object
65 5 100 66 5 1 2642 sub canReadWrite { defined $_[1] and -r $_[1] and -w _ }
66              
67             # Do we have permission to execute a filesystem object
68 10 50   10 1 659 sub canExecute { defined $_[1] and -x $_[1] }
69              
70             # Could we open this as a file
71 14 100 66 14 1 1736 sub canOpen { defined $_[1] and -f $_[1] and -r _ }
72              
73             # Could a file or directory be removed, were we to try
74             sub canRemove {
75             # Pass through to the object class
76 0 0   0 1 0 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
77 0         0 $Object->canRemove;
78             }
79              
80             # Is the file a text file
81 5 100 66 5 0 222 sub isText { defined $_[1] and -f $_[1] and -T $_[1] }
82              
83             # Is a file a binary file.
84 5 100 66 5 0 194 sub isBinary { defined $_[1] and -f $_[1] and -B $_[1] }
85              
86             # Stat based methods.
87             # I've included only the most usefull one I can think of.
88             sub fileSize {
89 10     10 1 3553 my $class = shift;
90 10 100       37 my $file = shift or return undef;
91              
92             # Check the file
93 9 100       134 return $class->_error( 'File does not exist' ) unless -e $file;
94 7 50       23 return $class->_error( 'Cannot get the file size for a directory' ) unless -f _;
95              
96             # A file's size is contained in element 7
97 7         93 (stat $file)[7];
98             }
99              
100              
101              
102              
103              
104             #####################################################################
105             # Opening Files.
106              
107             # Note: Files are closed conventionally using the IO::Handle's methods.
108              
109             # Open a file.
110             # Takes as arguments either a ">filepath" style file name, or the two argument
111             # form of "mode", "filename". Supports perl '<' type modes, and fopen 'rw'
112             # type modes. Pipes and more advanced things are not supported.
113             # Both the 1 and 2 argument modes are supported.
114             # Returns an IO::File for the filesystem object.
115             sub open {
116 28     28 1 1858 my $class = shift;
117              
118             # One or two argument form
119 28         61 my ($file, $mode) = ();
120 28 100       150 if ( @_ == 1 ) {
    50          
121 6         9 $file = shift;
122              
123             # Read by default
124 6 50       24 $mode = $file =~ s/^([<>+]{1,2})\s*// ? $1 : '<';
125              
126             } elsif ( @_ == 2 ) {
127 22         47 $mode = shift;
128 22         31 $file = shift;
129              
130             } else {
131 0         0 return $class->_error( "Invalid argument count to ->open" );
132             }
133              
134             # Check the mode
135 28 50       76 unless ( exists $modes{$mode} ) {
136 0         0 return $class->_error( "Unknown or unsupported mode '$mode'" );
137             }
138              
139             # Ensure the directory exists for those that need it
140 28         44 my $remove_on_fail = '';
141 28 100 100     388 if ( $modes{$mode} and ! -e $file ) {
142 14         61 $remove_on_fail = $class->_makePath( $file );
143 14 50       42 return undef unless defined $remove_on_fail;
144             }
145              
146             # Try to get the IO::File
147 28 100       170 IO::File->new( $file, $mode )
148             or $class->_andRemove( $remove_on_fail );
149             }
150              
151             # Provide creation mode specific methods
152 1     1 1 94 sub getReadHandle { $_[0]->open( '<', $_[1] ) }
153 10     10 1 3210 sub getWriteHandle { $_[0]->open( '>', $_[1] ) }
154 6     6 1 150 sub getAppendHandle { $_[0]->open( '>>', $_[1] ) }
155 1     1 1 94 sub getReadWriteHandle { $_[0]->open( '+<', $_[1] ) }
156              
157              
158              
159              
160              
161             #####################################################################
162             # Quick File Methods
163              
164             # Slurp quickly reads in an entire file in a memory efficient manner.
165             # Reads and file and returns a reference to a scalar containing the file.
166             # Returns 0 if the file does not exist.
167             # Returns undef on error.
168             sub slurp {
169 3     3 1 2307 my $class = shift;
170 3 100       12 my $file = shift or return undef;
171              
172             # Check the file
173 2 100       8 $class->canOpen( $file )
174             or return $class->_error( "Unable to open file '$file'" );
175              
176             # Use idiomatic slurp instead of File::Slurp
177 1 50       6 _slurp($file) or $class->_error( "Error opening file '$file'", $! );
178             }
179              
180             # Provide a simple _slurp implementation
181             sub _slurp {
182 1     1   2 my $file = shift;
183 1         5 local $/ = undef;
184 1         4 local *SLURP;
185 1 50       35 CORE::open( SLURP, "<$file" ) or return undef;
186 1         20 my $source = ;
187 1 50       13 CORE::close( SLURP ) or return undef;
188 1         10 \$source;
189             }
190              
191             # read reads in an entire file, returning it as an array or a reference to it.
192             # depending on the calling context. Returns undef or () on error, depending on
193             # the calling context.
194             sub read {
195 6     6 1 4281 my $class = shift;
196 6 100       18 my $file = shift or return;
197              
198             # Check the file
199 4 100       11 unless ( $class->canOpen( $file ) ) {
200 2         13 $class->_error( "Unable to open file '$file'" );
201 2         6 return;
202             }
203              
204             # Load the file
205 2 50       62 unless ( CORE::open(FILE, $file) ) {
206 0         0 $class->_error( "Unable to open file '$file'" );
207 0         0 return;
208             }
209 2         57 my @content = ;
210 2         8 chomp @content;
211 2         18 CORE::close(FILE);
212              
213 2 100       14 wantarray ? @content : \@content;
214             }
215              
216             # writeFile writes a file to the filesystem, replacing the existing file
217             # if needed. Existing files will be clobbered before starting to write to
218             # the file, as per a typical write file handle.
219             sub write {
220 21     21 1 4868 my $class = shift;
221 21 100       57 my $file = shift or return undef;
222 20 100       44 unless ( defined $_[0] ) {
223 2         5 return $class->_error( "Did not pass anything to write to file" );
224             }
225              
226             # Get a ref to the contents.
227             # This looks messy, but it avoids copying potentially large amounts
228             # of data in memory, bloating the RAM usage.
229             # This also makes sure the stuff we are going to write is ok.
230 18         25 my $contents;
231 18 100       42 if ( ref $_[0] ) {
232 11 50 66     69 unless ( UNIVERSAL::isa($_[0], 'SCALAR') or UNIVERSAL::isa($_[0], 'ARRAY') ) {
233 0         0 return $class->_error( "Unknown or invalid argument to ->write" );
234             }
235              
236 11         21 $contents = $_[0];
237             } else {
238 7         12 $contents = \$_[0];
239             }
240              
241             # Get an opened write file handle if we weren't passed a handle already.
242             # When this falls out of context, it will close itself.
243             # Since there are many things that act like file handles, don't check
244             # specifically for IO::Handle or anything, just for a reference.
245 18         31 my $dontclose = 0;
246 18 100       35 if ( ref $file ) {
247             # Don't close is someone passes us a handle.
248             # They might want to write other things.
249 12         18 $dontclose = 1;
250             } else {
251 6 50       23 $file = $class->getWriteHandle( $file ) or return undef;
252             }
253              
254             # Write the contents to the handle
255 18 100       751 if ( UNIVERSAL::isa($contents, 'SCALAR') ) {
256 14 50       65 $file->print( $$contents ) or return undef;
257             } else {
258 4         12 foreach ( @$contents ) {
259             # When printing the lines to the file,
260             # fix any possible newline problems.
261 16         114 chomp $_;
262 16 50       43 $file->print( $_ . "\n" ) or return undef;
263             }
264             }
265              
266             # Close the file if needed
267 18 100       432 $file->close unless $dontclose;
268              
269 18         247 1;
270             }
271              
272             # overwrite() writes a file to the filesystem, replacing the existing file
273             # if needed. Existing files will be clobbered at the end of writing the file,
274             # essentially allowing you to write the file to disk atomically.
275             sub overwrite {
276 6     6 1 3074 my $class = shift;
277 6 100       19 my $file = shift or return undef;
278 5 100       17 return undef unless defined $_[0];
279              
280             # Make sure we will be able to write over the file
281 4 50       12 unless ( $class->canWrite($file) ) {
282 0         0 return $class->_error( "Will not be able to create the file '$file'" );
283             }
284              
285             # Load in the two libraries we need.
286             # It's a fair chunk of overhead, so we do it here instead of up
287             # the top so it only loads in if we need to do overwriting.
288             # Not as good as Class::Autouse, but these arn't OO modules.
289 4         882 require File::Temp;
290 4         9575 require File::Copy;
291              
292             # Get a temp file
293 4         15 my ($handle, $tempfile) = File::Temp::tempfile( SUFFIX => '.tmp', UNLINK => 0 );
294              
295             # Write the content to it.
296             # Pass the argument by reference if it isn't already,
297             # to avoid copying large scalars.
298 4 100       1419 unless ( $class->write( $handle, ref $_[0] ? $_[0] : \$_[0] ) ) {
    50          
299             # Clean up and return an error
300 0         0 $handle->close;
301 0         0 unlink $tempfile;
302 0         0 return $class->_error( "Error while writing file" );
303             }
304              
305             # We are finished with the handle
306 4         14 $handle->close;
307              
308             # Now move the finished file to the final location
309 4 50       159 unless ( File::Copy::move( $tempfile, $file ) ) {
310             # Clean up the tempfile and return an error
311 0         0 unlink $tempfile;
312 0         0 return $class->_error( "Failed to copy file into final location" );
313             }
314              
315 4         409 1;
316             }
317              
318             # appendFile writes content to the end of an existing file, or creating the
319             # file if needed.
320             sub append {
321 7     7 1 3100 my $class = shift;
322 7 100       53 my $file = shift or return undef;
323 6 100       25 return undef unless defined $_[0];
324              
325             # Get the appending handle, and write to it
326 5 50       27 my $handle = $class->getAppendHandle( $file ) or return undef;
327 5 100       720 unless ( $class->write( $handle, ref $_[0] ? $_[0] : \$_[0] ) ) {
    50          
328             # Clean up and return an error
329 0         0 $handle->close;
330 0         0 return $class->_error( "Error while writing file" );
331             }
332 5         22 $handle->close;
333              
334 5         209 1;
335             }
336              
337             # Copy a file or directory from one place to another.
338             # We apply our own copy semantics.
339             sub copy {
340 14     14 1 9379 my $class = shift;
341 14 100 100     88 return undef unless defined($_[0]) && defined($_[1]);
342 12 50       82 my $source = File::Spec->canonpath( shift ) or return undef;
343 12 50       55 my $target = File::Spec->canonpath( shift ) or return undef;
344              
345             # Check the source and target
346 12 100       196 return $class->_error( "No such file or directory '$source'" ) unless -e $source;
347 11 50       180 if ( -e $target ) {
348 0 0 0     0 unless ( -f $source and -f $target ) {
349 0 0       0 return $class->_error( "Won't overwrite "
    0          
350             . (-f $target ? 'file' : 'directory')
351             . " '$target' with "
352             . (-f $source ? 'file' : 'directory')
353             . " '$source'" );
354             }
355             }
356 11 50       55 unless ( $class->canWrite( $target ) ) {
357 0         0 return $class->_error( "Insufficient permissions to create '$target'" );
358             }
359              
360             # Make sure the directory for the target exists
361 11         41 my $remove_on_fail = $class->_makePath( $target );
362 11 50       26 return undef unless defined $remove_on_fail;
363              
364 11 100       118 if ( -f $source ) {
365             # Copy a file to the new location
366 10         85 require File::Copy;
367 10 50       50 return File::Copy::copy( $source, $target ) ? 1
368             : $class->_andRemove( $remove_on_fail );
369             }
370              
371             # Create the target directory
372 1 50       17 my $tocopy = File::Spec->catfile( $source, '*' ) or return undef;
373 1 50       47 unless ( mkdir $target, 0755 ) {
374 0         0 return $class->_andRemove( $remove_on_fail,
375             "Failed to create directory '$target'" );
376             }
377              
378             # Hand off to File::Copy::Recursive
379 1         653 require File::Copy::Recursive;
380 1         4206 my $rv = File::Copy::Recursive::dircopy( $tocopy, $target );
381 1 50       1584 defined $rv ? $rv : $class->_andRemove( $remove_on_fail );
382             }
383              
384             # Move a file from one place to another.
385             sub move {
386 2     2 1 2137 my $class = shift;
387 2 50       9 my $source = shift or return undef;
388 2 50       5 my $target = shift or return undef;
389              
390             # Check the source and target
391 2 50       33 return $class->_error( "Copy source '$source' does not exist" ) unless -e $source;
392 2 50 33     25 if ( -d $source and -f $target ) {
393 0         0 return $class->_error( "Cannot overwrite non-directory '$source' with directory '$target'" );
394             }
395              
396             # Check permissions
397 2 50       8 unless ( $class->canWrite( $target ) ) {
398 0         0 return $class->_error( "Insufficient permissions to write to '$target'" );
399             }
400              
401             # Make sure the directory for the target exists
402 2         9 my $remove_on_fail = $class->_makePath( $target );
403 2 50       6 return undef unless defined $remove_on_fail;
404              
405             # Do the file move
406 2         16 require File::Copy;
407 2         11 my $rv = File::Copy::move( $source, $target );
408 2 50       167 unless ( $rv ) {
409             # Clean up after ourselves
410 0 0       0 File::Flat->remove( $remove_on_fail ) if $remove_on_fail;
411 0         0 return $class->_error( "Error moveing '$source' to '$target'" );
412             }
413              
414 2         8 1;
415             }
416              
417             # Remove a file or directory ( safely )
418             sub remove {
419 18     18 1 2038 my $class = shift;
420 18 100       67 my $file = shift or return undef;
421              
422             # Does the file exist
423 17 100       255 unless ( -e $file ) {
424 1         6 return $class->_error( "File or directory does not exist" );
425             }
426              
427             # Use File::Remove to remove it
428 16         185 require File::Remove;
429 16 50       110 File::Remove::remove( \1, $file ) or return undef;
430 16 100 100     35965 ($AUTO_PRUNE or $_[0]) ? $class->prune( $file ) : 1; # Optionally prune
431             }
432              
433             # For a given path, remove any empty directories left behind
434             sub prune {
435 8 50   8 1 7063 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
436 8         68 $Object->prune;
437             }
438              
439             # Truncate a file. That is, leave the file in place,
440             # but reduce its size to a certain size, default 0.
441             sub truncate {
442 6     6 1 1286 my $class = shift;
443 6 100       25 my $file = shift or return undef;
444 5 100       16 my $bytes = defined $_[0] ? shift : 0; # Beginning unless otherwise specified
445              
446             # Check the file
447 5 100       81 return $class->_error( "Cannot truncate a directory" ) if -d $file;
448 4 50       16 unless ( $class->canWrite( $file ) ) {
449 0         0 return $class->_error( "Insufficient permissions to truncate file" );
450             }
451              
452             # Get a handle to the file and truncate it
453 4 50       16 my $handle = $class->open( '>', $file )
454             or return $class->_error( 'Failed to open write file handle' );
455 4 50       481 $handle->truncate( $bytes )
456             or return $class->_error( "Failed to truncate file handle: $!" );
457 4         122 $handle->close;
458              
459 4         71 1;
460             }
461              
462              
463              
464              
465              
466             #####################################################################
467             # Directory Methods
468              
469             # Pass these through to the object version. It should be
470             # better at this sort of thing.
471              
472             # Create a directory.
473             # Returns true on success, undef on error.
474             sub makeDirectory {
475 5 50   5 1 1218 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
476 5         21 $Object->makeDirectory;
477             }
478              
479             # Make sure that everything above our path exists
480             sub _makePath {
481 27 50   27   118 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
482 27         93 $Object->_makePath;
483             }
484              
485             # Legacy, kept around for CVS Monitor
486             *_ensureDirectory = *_makePath;
487              
488              
489              
490              
491             #####################################################################
492             # Error handling
493              
494 2     2 0 1146 sub errstr { $errstr }
495 11     11   4708 sub _error { $errstr = $_[1]; undef }
  11         132  
496             sub _andRemove {
497 5     5   382 my $self = shift;
498 5         9 my $to_remove = shift;
499 5 50       12 if ( length $to_remove ) {
500 0         0 require File::Remove;
501 0         0 File::Remove::remove( $to_remove );
502             }
503              
504 5 50       24 @_ ? $self->_error(@_) : undef;
505             }
506              
507             1;
508              
509              
510              
511              
512              
513              
514              
515              
516             package File::Flat::Object; # git description: fabfdb3
517              
518             # Instantiatable version of File::Flat.
519             #
520             # The methods are the same as for File::Flat, where applicable.
521              
522 3     3   7871 use strict;
  3         6  
  3         92  
523 3     3   19 use File::Spec ();
  3         4  
  3         5278  
524              
525             sub new {
526 58     58   167 my $class = shift;
527 58 50       150 my $filename = shift or return undef;
528              
529 58         406 bless {
530             type => undef,
531             original => $filename,
532             absolute => undef,
533             volume => undef,
534             directories => undef,
535             file => undef,
536             }, $class;
537             }
538              
539             sub _init {
540 58     58   92 my $self = shift;
541              
542             # Get the current working directory.
543             # If we don't pass it ourselves to File::Spec->rel2abs,
544             # it might use a backtick `pwd`, which is horribly slow.
545 58         464 my $base = Cwd::getcwd();
546              
547             # Populate the other properties
548 58         1282 $self->{absolute} = File::Spec->rel2abs( $self->{original}, $base );
549 58         741 my ($v, $d, $f) = File::Spec->splitpath( $self->{absolute} );
550 58         361 my @dirs = File::Spec->splitdir( $d );
551 58         114 $self->{volume} = $v;
552 58         96 $self->{directories} = \@dirs;
553 58         105 $self->{file} = $f;
554 58 50       194 $self->{type} = $self->{file} eq '' ? 'directory' : 'file';
555              
556 58         109 1;
557             }
558              
559             # Define the basics
560 0     0   0 sub exists { -e $_[0]->{original} }
561 0     0   0 sub isaFile { -f $_[0]->{original} }
562 0     0   0 sub isaDirectory { -d $_[0]->{original} }
563 0 0   0   0 sub canRead { -e $_[0]->{original} and -r _ }
564 0 0   0   0 sub canWrite { -e $_[0]->{original} and -w _ }
565 0 0 0 0   0 sub canReadWrite { -e $_[0]->{original} and -r _ and -w _ }
566 0 0   0   0 sub canExecute { -e $_[0]->{original} and -x _ }
567 0 0   0   0 sub canOpen { -f $_[0]->{original} and -r _ }
568 0     0   0 sub fileSize { File::Flat->fileSize( $_[0]->{original} ) }
569              
570             # Can we create this file/directory, if it doesn't exist.
571             # Returns 2 if yes, but we need to create directories
572             # Returns 1 if yes, and we won't need to create any directories.
573             # Returns 0 if no.
574             sub _canCreate {
575 18     18   33 my $self = shift;
576 18 50       70 $self->_init unless defined $self->{type};
577              
578             # It it already exists, check for writable instead
579 18 50       168 return $self->canWrite if -e $self->{original};
580            
581             # Go up the directories and find the last one that exists
582 18         59 my $dir_known = '';
583 18         35 my $dir_unknown = '';
584 18         33 my @dirs = @{$self->{directories}};
  18         72  
585 18 50       49 pop @dirs if $self->{file} eq '';
586 18         59 while ( defined( my $dir = shift @dirs ) ) {
587 122         653 $dir_unknown = File::Spec->catdir( $dir_known, $dir );
588              
589             # Does the filesystem object exist.
590             # We use '' for the file part, because not specifying it at
591             # all throws a warning.
592 122         592 my $fullpath = File::Spec->catpath( $self->{volume}, $dir_unknown, '' );
593 122 100       1516 last unless -e $fullpath;
594              
595             # This should be a directory
596 113 50       1170 if ( -d $fullpath ) {
597 113         279 $dir_known = $dir_unknown;
598 113         341 next;
599             }
600              
601             # A file is where we think a directory should be
602 0         0 0;
603             }
604              
605             # $dir_known now contains the last directory that exists.
606             # Can we create filesystem objects under this?
607 18 50       232 return 0 unless -w $dir_known;
608              
609             # If @dirs is empty, we don't need to create
610             # any directories when we create the file
611 18 100       163 @dirs ? 2 : 1;
612             }
613              
614             ### FIXME - Implement this.
615             # Should check the we can delete the file.
616             # If it's a directory, should check that we can
617             # recursively delete everything in it.
618 0     0   0 sub canRemove { die "The ->canRemove method has not been implemented yet" }
619              
620             # Is the file a text file.
621 0 0 0 0   0 sub isText { -e $_[0]->{original} and -f _ and -T $_[0]->{original} }
622              
623             # Is a file a binary file.
624 0 0 0 0   0 sub isBinary { -e $_[0]->{original} and -f _ and -B $_[0]->{original} }
625              
626              
627              
628              
629              
630             #####################################################################
631             # Opening File
632              
633             # Pass these down to the static methods
634              
635             sub open {
636 0     0   0 my $self = shift;
637             defined $_[0]
638             ? File::Flat->open( $self->{original}, $_[0] )
639             : File::Flat->open( $self->{original} )
640 0 0       0 }
641              
642 0     0   0 sub getReadHandle { File::Flat->open( '<', $_[0]->{original} ) }
643 0     0   0 sub getWriteHandle { File::Flat->open( '>', $_[0]->{original} ) }
644 0     0   0 sub getAppendHandle { File::Flat->open( '>>', $_[0]->{original} ) }
645 0     0   0 sub getReadWriteHandle { File::Flat->open( '+<', $_[0]->{original} ) }
646              
647              
648              
649              
650              
651             #####################################################################
652             # Quick File Methods
653              
654 0     0   0 sub slurp { File::Flat->slurp( $_[0]->{original} ) }
655 0     0   0 sub read { File::Flat->read( $_[0]->{original} ) }
656 0     0   0 sub write { File::Flat->write( $_[0]->{original} ) }
657 0     0   0 sub overwrite { File::Flat->overwrite( $_[0]->{original} ) }
658 0     0   0 sub append { File::Flat->append( $_[0]->{original} ) }
659 0     0   0 sub copy { File::Flat->copy( $_[0]->{original}, $_[1] ) }
660              
661             sub move {
662 0     0   0 my $self = shift;
663 0         0 my $moveTo = shift;
664 0 0       0 File::Flat->move( $self->{original}, $moveTo ) or return undef;
665              
666             # Since the file is moving, once we actually
667             # move the file, update the object information so
668             # it refers to the new location.
669 0         0 $self->{original} = $moveTo;
670              
671             # Re-initialise if we have already
672 0 0       0 $self->init if $self->{type};
673              
674 0         0 1;
675             }
676              
677             sub remove {
678 0     0   0 File::Flat->remove( $_[0]->{original} );
679             }
680              
681             # For a given path, remove all empty files that were left behind
682             # by previously deleting it.
683             sub prune {
684 8     8   16 my $self = shift;
685 8 50       77 $self->_init unless defined $self->{type};
686              
687             # We don't actually delete anything that currently exists
688 8 100       131 if ( -e $self->{original} ) {
689 1         28 return $self->_error('Bad use of ->prune, to try to delete a file');
690             }
691              
692             # Get the list of directories, fully resolved
693             ### TO DO - Might be able to do this smaller or more efficiently
694             ### by using List::Util::reduce
695 7         18 my @dirs = @{$self->{directories}};
  7         43  
696             my @potential = (
697 7         105 File::Spec->catpath( $self->{volume}, shift(@dirs), '' )
698             );
699 7         35 while ( @dirs ) {
700 55         281 push @potential, File::Spec->catdir( $potential[-1], shift(@dirs), '' );
701             }
702              
703             # Go backwards though this list
704 7         19 foreach my $dir ( reverse @potential ) {
705             # Not existing is good... it fulfils the intent
706 20 100       333 next unless -e $dir;
707              
708             # This should also definately be a file
709 15 50       234 unless ( -d $dir ) {
710 0         0 return $self->_error('Found file where a directory was expected while pruning');
711             }
712              
713             # Does it contain anything, other that (possibly) curdir and updir entries
714 15 50       372 opendir( PRUNEDIR, $dir )
715             or return $self->_error("opendir failed while pruning: $!");
716 15         276 my @files = readdir PRUNEDIR;
717 15         147 closedir PRUNEDIR;
718 15         44 foreach ( @files ) {
719 24 100       245 next if $_ eq File::Spec->curdir;
720 15 100       59 next if $_ eq File::Spec->updir;
721              
722             # Found something, we don't need to prune this,
723             # or anything else for that matter.
724 7         290 return 1;
725             }
726              
727             # Nothing in the directory, we can delete it
728 8 50       52 File::Flat->remove( $dir ) or return undef;
729             }
730              
731 0         0 1;
732             }
733              
734             sub truncate {
735 0     0   0 File::Flat->truncate( $_[0]->{original} );
736             }
737              
738              
739              
740              
741              
742             #####################################################################
743             # Directory methods
744              
745             # Create a directory.
746             # Returns true on success, undef on error.
747             sub makeDirectory {
748 5     5   8 my $self = shift;
749 5   50     24 my $mode = shift || 0755;
750 5 50       90 if ( -e $self->{original} ) {
751 0 0       0 return 1 if -d $self->{original};
752 0         0 return $self->_error( "'$self->{original}' already exists, and is a file" );
753             }
754 5 50       23 $self->_init unless defined $self->{type};
755              
756             # Ensure the directory below ours exists
757 5         12 my $remove_on_fail = $self->_makePath( $mode );
758 5 50       13 return undef unless defined $remove_on_fail;
759              
760             # Create the directory
761 5 50       192 unless ( mkdir $self->{original}, $mode ) {
762 0         0 return $self->_andRemove( $remove_on_fail,
763             "Failed to create directory '$self->{original}': $!" );
764             }
765              
766 5         54 1;
767             }
768              
769             # Make sure the directory that this file/directory is in exists.
770             # Returns the root of the creation dirs if created.
771             # Returns '' if nothing required.
772             # Returns undef on error.
773             sub _makePath {
774 32     32   52 my $self = shift;
775 32   100     119 my $mode = shift || 0755;
776 32 50       266 return '' if -e $self->{original};
777 32 100       164 $self->_init unless defined $self->{type};
778              
779             # Go up the directories and find the last one that exists
780 32         48 my $dir_known = '';
781 32         51 my $dir_unknown = '';
782 32         52 my $creation_root = '';
783 32         41 my @dirs = @{$self->{directories}};
  32         105  
784 32 50       82 pop @dirs if $self->{file} eq '';
785 32         93 while ( defined( my $dir = shift @dirs ) ) {
786 234         1410 $dir_unknown = File::Spec->catdir( $dir_known, $dir );
787              
788             # Does the filesystem object exist
789             # We use '' for the file part, because not specifying it at
790             # all throws a warning.
791 234         1190 my $fullpath = File::Spec->catpath( $self->{volume}, $dir_unknown, '' );
792 234 100       2792 if ( -e $fullpath ) {
793             # This should be a directory
794 213 50       2154 return undef unless -d $fullpath;
795             } else {
796             # Try to create the directory
797 21 50       974 unless ( mkdir $dir_unknown, $mode ) {
798 0         0 return $self->_error( $! );
799             }
800              
801             # Set the base of our creations to return
802 21 100       84 $creation_root = $dir_unknown unless $creation_root;
803             }
804              
805 234         905 $dir_known = $dir_unknown;
806             }
807              
808 32         165 $creation_root;
809             }
810              
811             # Legacy, kept around for CVS Monitor
812             *_ensureDirectory = *_makePath;
813              
814              
815              
816              
817              
818             #####################################################################
819             # Error handling
820              
821 0     0   0 sub errstr { $File::Flat::errstr }
822 1     1   23 sub _error { $File::Flat::errstr = $_[1]; undef }
  1         12  
823 0     0     sub _andRemove { shift; File::Flat->_andRemove(@_) }
  0            
824              
825             1;
826              
827             __END__