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: v1.05-6-g44d5bc8
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   60179 use 5.005;
  3         17  
11 3     3   13 use strict;
  3         5  
  3         59  
12 3     3   12 use Cwd ();
  3         5  
  3         31  
13 3     3   20 use File::Spec ();
  3         3  
  3         40  
14 3     3   1185 use IO::File ();
  3         21094  
  3         74  
15 3     3   1286 use prefork 'File::Temp';
  3         2452  
  3         13  
16 3     3   145 use prefork 'File::Copy';
  3         5  
  3         10  
17 3     3   107 use prefork 'File::Copy::Recursive';
  3         7  
  3         9  
18 3     3   108 use prefork 'File::Remove';
  3         5  
  3         9  
19              
20             our $VERSION = '1.06';
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 108 sub exists { defined $_[1] and -e $_[1] }
43              
44             # Is a filesystem object a file.
45 5 50   5 1 84 sub isaFile { defined $_[1] and -f $_[1] }
46              
47             # Is a filesystem object a directory.
48 6 50   6 1 102 sub isaDirectory { defined $_[1] and -d $_[1] }
49              
50             # Do we have permission to read a filesystem object.
51 7 100 66 7 1 1168 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 1934 return -w $_[1] if -e $_[1];
58              
59             # Can we create it
60 18 50       107 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
61 18         50 $Object->_canCreate;
62             }
63              
64             # Can we both read and write to a filesystem object
65 5 100 66 5 1 2201 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 556 sub canExecute { defined $_[1] and -x $_[1] }
69              
70             # Could we open this as a file
71 14 100 66 14 1 1530 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 184 sub isText { defined $_[1] and -f $_[1] and -T $_[1] }
82              
83             # Is a file a binary file.
84 5 100 66 5 0 161 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 2919 my $class = shift;
90 10 100       32 my $file = shift or return undef;
91              
92             # Check the file
93 9 100       114 return $class->_error( 'File does not exist' ) unless -e $file;
94 7 50       19 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         82 (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 1572 my $class = shift;
117              
118             # One or two argument form
119 28         73 my ($file, $mode) = ();
120 28 100       68 if ( @_ == 1 ) {
    50          
121 6         8 $file = shift;
122              
123             # Read by default
124 6 50       17 $mode = $file =~ s/^([<>+]{1,2})\s*// ? $1 : '<';
125              
126             } elsif ( @_ == 2 ) {
127 22         35 $mode = shift;
128 22         29 $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       75 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         37 my $remove_on_fail = '';
141 28 100 100     338 if ( $modes{$mode} and ! -e $file ) {
142 14         64 $remove_on_fail = $class->_makePath( $file );
143 14 50       36 return undef unless defined $remove_on_fail;
144             }
145              
146             # Try to get the IO::File
147 28 100       144 IO::File->new( $file, $mode )
148             or $class->_andRemove( $remove_on_fail );
149             }
150              
151             # Provide creation mode specific methods
152 1     1 1 78 sub getReadHandle { $_[0]->open( '<', $_[1] ) }
153 10     10 1 2581 sub getWriteHandle { $_[0]->open( '>', $_[1] ) }
154 6     6 1 126 sub getAppendHandle { $_[0]->open( '>>', $_[1] ) }
155 1     1 1 76 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 1883 my $class = shift;
170 3 100       11 my $file = shift or return undef;
171              
172             # Check the file
173 2 100       6 $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       5 _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       30 CORE::open( SLURP, "<$file" ) or return undef;
186 1         17 my $source = ;
187 1 50       10 CORE::close( SLURP ) or return undef;
188 1         8 \$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 3486 my $class = shift;
196 6 100       16 my $file = shift or return;
197              
198             # Check the file
199 4 100       7 unless ( $class->canOpen( $file ) ) {
200 2         10 $class->_error( "Unable to open file '$file'" );
201 2         6 return;
202             }
203              
204             # Load the file
205 2 50       51 unless ( CORE::open(FILE, $file) ) {
206 0         0 $class->_error( "Unable to open file '$file'" );
207 0         0 return;
208             }
209 2         49 my @content = ;
210 2         7 chomp @content;
211 2         14 CORE::close(FILE);
212              
213 2 100       13 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 4082 my $class = shift;
221 21 100       52 my $file = shift or return undef;
222 20 100       45 unless ( defined $_[0] ) {
223 2         15 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         20 my $contents;
231 18 100       49 if ( ref $_[0] ) {
232 11 50 66     59 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         8 $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         22 my $dontclose = 0;
246 18 100       29 if ( ref $file ) {
247             # Don't close is someone passes us a handle.
248             # They might want to write other things.
249 12         19 $dontclose = 1;
250             } else {
251 6 50       16 $file = $class->getWriteHandle( $file ) or return undef;
252             }
253              
254             # Write the contents to the handle
255 18 100       650 if ( UNIVERSAL::isa($contents, 'SCALAR') ) {
256 14 50       171 $file->print( $$contents ) or return undef;
257             } else {
258 4         11 foreach ( @$contents ) {
259             # When printing the lines to the file,
260             # fix any possible newline problems.
261 16         93 chomp $_;
262 16 50       39 $file->print( $_ . "\n" ) or return undef;
263             }
264             }
265              
266             # Close the file if needed
267 18 100       356 $file->close unless $dontclose;
268              
269 18         200 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 2689 my $class = shift;
277 6 100       19 my $file = shift or return undef;
278 5 100       14 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         682 require File::Temp;
290 4         8757 require File::Copy;
291              
292             # Get a temp file
293 4         22 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       1283 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         11 $handle->close;
307              
308             # Now move the finished file to the final location
309 4 50       140 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         397 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 2557 my $class = shift;
322 7 100       43 my $file = shift or return undef;
323 6 100       20 return undef unless defined $_[0];
324              
325             # Get the appending handle, and write to it
326 5 50       18 my $handle = $class->getAppendHandle( $file ) or return undef;
327 5 100       680 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         190 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 7744 my $class = shift;
341 14 100 100     72 return undef unless defined($_[0]) && defined($_[1]);
342 12 50       61 my $source = File::Spec->canonpath( shift ) or return undef;
343 12 50       35 my $target = File::Spec->canonpath( shift ) or return undef;
344              
345             # Check the source and target
346 12 100       159 return $class->_error( "No such file or directory '$source'" ) unless -e $source;
347 11 50       161 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       45 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         40 my $remove_on_fail = $class->_makePath( $target );
362 11 50       22 return undef unless defined $remove_on_fail;
363              
364 11 100       94 if ( -f $source ) {
365             # Copy a file to the new location
366 10         92 require File::Copy;
367 10 50       47 return File::Copy::copy( $source, $target ) ? 1
368             : $class->_andRemove( $remove_on_fail );
369             }
370              
371             # Create the target directory
372 1 50       23 my $tocopy = File::Spec->catfile( $source, '*' ) or return undef;
373 1 50       46 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         530 require File::Copy::Recursive;
380 1         3543 my $rv = File::Copy::Recursive::dircopy( $tocopy, $target );
381 1 50       1257 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 1755 my $class = shift;
387 2 50       8 my $source = shift or return undef;
388 2 50       4 my $target = shift or return undef;
389              
390             # Check the source and target
391 2 50       27 return $class->_error( "Copy source '$source' does not exist" ) unless -e $source;
392 2 50 33     22 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         7 my $remove_on_fail = $class->_makePath( $target );
403 2 50       5 return undef unless defined $remove_on_fail;
404              
405             # Do the file move
406 2         11 require File::Copy;
407 2         8 my $rv = File::Copy::move( $source, $target );
408 2 50       147 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         6 1;
415             }
416              
417             # Remove a file or directory ( safely )
418             sub remove {
419 18     18 1 1619 my $class = shift;
420 18 100       43 my $file = shift or return undef;
421              
422             # Does the file exist
423 17 100       276 unless ( -e $file ) {
424 1         4 return $class->_error( "File or directory does not exist" );
425             }
426              
427             # Use File::Remove to remove it
428 16         162 require File::Remove;
429 16 50       91 File::Remove::remove( \1, $file ) or return undef;
430 16 100 100     43905 ($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 5389 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
436 8         56 $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 1149 my $class = shift;
443 6 100       20 my $file = shift or return undef;
444 5 100       12 my $bytes = defined $_[0] ? shift : 0; # Beginning unless otherwise specified
445              
446             # Check the file
447 5 100       75 return $class->_error( "Cannot truncate a directory" ) if -d $file;
448 4 50       15 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       437 $handle->truncate( $bytes )
456             or return $class->_error( "Failed to truncate file handle: $!" );
457 4         103 $handle->close;
458              
459 4         66 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 1054 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
476 5         12 $Object->makeDirectory;
477             }
478              
479             # Make sure that everything above our path exists
480             sub _makePath {
481 27 50   27   103 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
482 27         96 $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 900 sub errstr { $errstr }
495 11     11   1917 sub _error { $errstr = $_[1]; undef }
  11         94  
496             sub _andRemove {
497 5     5   326 my $self = shift;
498 5         6 my $to_remove = shift;
499 5 50       10 if ( length $to_remove ) {
500 0         0 require File::Remove;
501 0         0 File::Remove::remove( $to_remove );
502             }
503              
504 5 50       19 @_ ? $self->_error(@_) : undef;
505             }
506              
507             1;
508              
509              
510              
511              
512              
513              
514              
515              
516             package File::Flat::Object; # git description: v1.05-6-g44d5bc8
517             our $VERSION = '1.06';
518              
519             # Instantiatable version of File::Flat.
520             #
521             # The methods are the same as for File::Flat, where applicable.
522              
523 3     3   6535 use strict;
  3         5  
  3         86  
524 3     3   16 use File::Spec ();
  3         5  
  3         4527  
525              
526             sub new {
527 58     58   121 my $class = shift;
528 58 50       122 my $filename = shift or return undef;
529              
530 58         341 bless {
531             type => undef,
532             original => $filename,
533             absolute => undef,
534             volume => undef,
535             directories => undef,
536             file => undef,
537             }, $class;
538             }
539              
540             sub _init {
541 58     58   83 my $self = shift;
542              
543             # Get the current working directory.
544             # If we don't pass it ourselves to File::Spec->rel2abs,
545             # it might use a backtick `pwd`, which is horribly slow.
546 58         406 my $base = Cwd::getcwd();
547              
548             # Populate the other properties
549 58         1093 $self->{absolute} = File::Spec->rel2abs( $self->{original}, $base );
550 58         657 my ($v, $d, $f) = File::Spec->splitpath( $self->{absolute} );
551 58         322 my @dirs = File::Spec->splitdir( $d );
552 58         96 $self->{volume} = $v;
553 58         80 $self->{directories} = \@dirs;
554 58         81 $self->{file} = $f;
555 58 50       130 $self->{type} = $self->{file} eq '' ? 'directory' : 'file';
556              
557 58         119 1;
558             }
559              
560             # Define the basics
561 0     0   0 sub exists { -e $_[0]->{original} }
562 0     0   0 sub isaFile { -f $_[0]->{original} }
563 0     0   0 sub isaDirectory { -d $_[0]->{original} }
564 0 0   0   0 sub canRead { -e $_[0]->{original} and -r _ }
565 0 0   0   0 sub canWrite { -e $_[0]->{original} and -w _ }
566 0 0 0 0   0 sub canReadWrite { -e $_[0]->{original} and -r _ and -w _ }
567 0 0   0   0 sub canExecute { -e $_[0]->{original} and -x _ }
568 0 0   0   0 sub canOpen { -f $_[0]->{original} and -r _ }
569 0     0   0 sub fileSize { File::Flat->fileSize( $_[0]->{original} ) }
570              
571             # Can we create this file/directory, if it doesn't exist.
572             # Returns 2 if yes, but we need to create directories
573             # Returns 1 if yes, and we won't need to create any directories.
574             # Returns 0 if no.
575             sub _canCreate {
576 18     18   21 my $self = shift;
577 18 50       51 $self->_init unless defined $self->{type};
578              
579             # It it already exists, check for writable instead
580 18 50       163 return $self->canWrite if -e $self->{original};
581            
582             # Go up the directories and find the last one that exists
583 18         48 my $dir_known = '';
584 18         26 my $dir_unknown = '';
585 18         29 my @dirs = @{$self->{directories}};
  18         68  
586 18 50       49 pop @dirs if $self->{file} eq '';
587 18         44 while ( defined( my $dir = shift @dirs ) ) {
588 122         530 $dir_unknown = File::Spec->catdir( $dir_known, $dir );
589              
590             # Does the filesystem object exist.
591             # We use '' for the file part, because not specifying it at
592             # all throws a warning.
593 122         469 my $fullpath = File::Spec->catpath( $self->{volume}, $dir_unknown, '' );
594 122 100       1122 last unless -e $fullpath;
595              
596             # This should be a directory
597 113 50       1001 if ( -d $fullpath ) {
598 113         205 $dir_known = $dir_unknown;
599 113         273 next;
600             }
601              
602             # A file is where we think a directory should be
603 0         0 0;
604             }
605              
606             # $dir_known now contains the last directory that exists.
607             # Can we create filesystem objects under this?
608 18 50       192 return 0 unless -w $dir_known;
609              
610             # If @dirs is empty, we don't need to create
611             # any directories when we create the file
612 18 100       151 @dirs ? 2 : 1;
613             }
614              
615             ### FIXME - Implement this.
616             # Should check the we can delete the file.
617             # If it's a directory, should check that we can
618             # recursively delete everything in it.
619 0     0   0 sub canRemove { die "The ->canRemove method has not been implemented yet" }
620              
621             # Is the file a text file.
622 0 0 0 0   0 sub isText { -e $_[0]->{original} and -f _ and -T $_[0]->{original} }
623              
624             # Is a file a binary file.
625 0 0 0 0   0 sub isBinary { -e $_[0]->{original} and -f _ and -B $_[0]->{original} }
626              
627              
628              
629              
630              
631             #####################################################################
632             # Opening File
633              
634             # Pass these down to the static methods
635              
636             sub open {
637 0     0   0 my $self = shift;
638             defined $_[0]
639             ? File::Flat->open( $self->{original}, $_[0] )
640             : File::Flat->open( $self->{original} )
641 0 0       0 }
642              
643 0     0   0 sub getReadHandle { File::Flat->open( '<', $_[0]->{original} ) }
644 0     0   0 sub getWriteHandle { File::Flat->open( '>', $_[0]->{original} ) }
645 0     0   0 sub getAppendHandle { File::Flat->open( '>>', $_[0]->{original} ) }
646 0     0   0 sub getReadWriteHandle { File::Flat->open( '+<', $_[0]->{original} ) }
647              
648              
649              
650              
651              
652             #####################################################################
653             # Quick File Methods
654              
655 0     0   0 sub slurp { File::Flat->slurp( $_[0]->{original} ) }
656 0     0   0 sub read { File::Flat->read( $_[0]->{original} ) }
657 0     0   0 sub write { File::Flat->write( $_[0]->{original} ) }
658 0     0   0 sub overwrite { File::Flat->overwrite( $_[0]->{original} ) }
659 0     0   0 sub append { File::Flat->append( $_[0]->{original} ) }
660 0     0   0 sub copy { File::Flat->copy( $_[0]->{original}, $_[1] ) }
661              
662             sub move {
663 0     0   0 my $self = shift;
664 0         0 my $moveTo = shift;
665 0 0       0 File::Flat->move( $self->{original}, $moveTo ) or return undef;
666              
667             # Since the file is moving, once we actually
668             # move the file, update the object information so
669             # it refers to the new location.
670 0         0 $self->{original} = $moveTo;
671              
672             # Re-initialise if we have already
673 0 0       0 $self->init if $self->{type};
674              
675 0         0 1;
676             }
677              
678             sub remove {
679 0     0   0 File::Flat->remove( $_[0]->{original} );
680             }
681              
682             # For a given path, remove all empty files that were left behind
683             # by previously deleting it.
684             sub prune {
685 8     8   13 my $self = shift;
686 8 50       54 $self->_init unless defined $self->{type};
687              
688             # We don't actually delete anything that currently exists
689 8 100       145 if ( -e $self->{original} ) {
690 1         29 return $self->_error('Bad use of ->prune, to try to delete a file');
691             }
692              
693             # Get the list of directories, fully resolved
694             ### TO DO - Might be able to do this smaller or more efficiently
695             ### by using List::Util::reduce
696 7         16 my @dirs = @{$self->{directories}};
  7         29  
697             my @potential = (
698 7         86 File::Spec->catpath( $self->{volume}, shift(@dirs), '' )
699             );
700 7         51 while ( @dirs ) {
701 55         222 push @potential, File::Spec->catdir( $potential[-1], shift(@dirs), '' );
702             }
703              
704             # Go backwards though this list
705 7         14 foreach my $dir ( reverse @potential ) {
706             # Not existing is good... it fulfils the intent
707 20 100       238 next unless -e $dir;
708              
709             # This should also definately be a file
710 15 50       147 unless ( -d $dir ) {
711 0         0 return $self->_error('Found file where a directory was expected while pruning');
712             }
713              
714             # Does it contain anything, other that (possibly) curdir and updir entries
715 15 50       294 opendir( PRUNEDIR, $dir )
716             or return $self->_error("opendir failed while pruning: $!");
717 15         229 my @files = readdir PRUNEDIR;
718 15         124 closedir PRUNEDIR;
719 15         35 foreach ( @files ) {
720 24 100       166 next if $_ eq File::Spec->curdir;
721 15 100       43 next if $_ eq File::Spec->updir;
722              
723             # Found something, we don't need to prune this,
724             # or anything else for that matter.
725 7         257 return 1;
726             }
727              
728             # Nothing in the directory, we can delete it
729 8 50       48 File::Flat->remove( $dir ) or return undef;
730             }
731              
732 0         0 1;
733             }
734              
735             sub truncate {
736 0     0   0 File::Flat->truncate( $_[0]->{original} );
737             }
738              
739              
740              
741              
742              
743             #####################################################################
744             # Directory methods
745              
746             # Create a directory.
747             # Returns true on success, undef on error.
748             sub makeDirectory {
749 5     5   8 my $self = shift;
750 5   50     21 my $mode = shift || 0755;
751 5 50       93 if ( -e $self->{original} ) {
752 0 0       0 return 1 if -d $self->{original};
753 0         0 return $self->_error( "'$self->{original}' already exists, and is a file" );
754             }
755 5 50       22 $self->_init unless defined $self->{type};
756              
757             # Ensure the directory below ours exists
758 5         11 my $remove_on_fail = $self->_makePath( $mode );
759 5 50       12 return undef unless defined $remove_on_fail;
760              
761             # Create the directory
762 5 50       197 unless ( mkdir $self->{original}, $mode ) {
763 0         0 return $self->_andRemove( $remove_on_fail,
764             "Failed to create directory '$self->{original}': $!" );
765             }
766              
767 5         47 1;
768             }
769              
770             # Make sure the directory that this file/directory is in exists.
771             # Returns the root of the creation dirs if created.
772             # Returns '' if nothing required.
773             # Returns undef on error.
774             sub _makePath {
775 32     32   52 my $self = shift;
776 32   100     86 my $mode = shift || 0755;
777 32 50       246 return '' if -e $self->{original};
778 32 100       130 $self->_init unless defined $self->{type};
779              
780             # Go up the directories and find the last one that exists
781 32         42 my $dir_known = '';
782 32         36 my $dir_unknown = '';
783 32         42 my $creation_root = '';
784 32         43 my @dirs = @{$self->{directories}};
  32         87  
785 32 50       72 pop @dirs if $self->{file} eq '';
786 32         85 while ( defined( my $dir = shift @dirs ) ) {
787 234         1042 $dir_unknown = File::Spec->catdir( $dir_known, $dir );
788              
789             # Does the filesystem object exist
790             # We use '' for the file part, because not specifying it at
791             # all throws a warning.
792 234         926 my $fullpath = File::Spec->catpath( $self->{volume}, $dir_unknown, '' );
793 234 100       2423 if ( -e $fullpath ) {
794             # This should be a directory
795 213 50       1887 return undef unless -d $fullpath;
796             } else {
797             # Try to create the directory
798 21 50       796 unless ( mkdir $dir_unknown, $mode ) {
799 0         0 return $self->_error( $! );
800             }
801              
802             # Set the base of our creations to return
803 21 100       71 $creation_root = $dir_unknown unless $creation_root;
804             }
805              
806 234         779 $dir_known = $dir_unknown;
807             }
808              
809 32         136 $creation_root;
810             }
811              
812             # Legacy, kept around for CVS Monitor
813             *_ensureDirectory = *_makePath;
814              
815              
816              
817              
818              
819             #####################################################################
820             # Error handling
821              
822 0     0   0 sub errstr { $File::Flat::errstr }
823 1     1   16 sub _error { $File::Flat::errstr = $_[1]; undef }
  1         10  
824 0     0     sub _andRemove { shift; File::Flat->_andRemove(@_) }
  0            
825              
826             1;
827              
828             __END__