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.06-2-g7f3dc2c
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   71142 use 5.005;
  3         19  
11 3     3   15 use strict;
  3         5  
  3         71  
12 3     3   14 use Cwd ();
  3         5  
  3         37  
13 3     3   23 use File::Spec ();
  3         6  
  3         45  
14 3     3   1382 use IO::File ();
  3         26445  
  3         81  
15 3     3   1539 use prefork 'File::Temp';
  3         2933  
  3         18  
16 3     3   177 use prefork 'File::Copy';
  3         6  
  3         11  
17 3     3   131 use prefork 'File::Copy::Recursive';
  3         5  
  3         9  
18 3     3   128 use prefork 'File::Remove';
  3         6  
  3         46  
19              
20             our $VERSION = '1.07';
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 141 sub exists { defined $_[1] and -e $_[1] }
43              
44             # Is a filesystem object a file.
45 5 50   5 1 104 sub isaFile { defined $_[1] and -f $_[1] }
46              
47             # Is a filesystem object a directory.
48 6 50   6 1 124 sub isaDirectory { defined $_[1] and -d $_[1] }
49              
50             # Do we have permission to read a filesystem object.
51 7 100 66 7 1 1361 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 2377 return -w $_[1] if -e $_[1];
58              
59             # Can we create it
60 18 50       112 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
61 18         68 $Object->_canCreate;
62             }
63              
64             # Can we both read and write to a filesystem object
65 5 100 66 5 1 2640 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 633 sub canExecute { defined $_[1] and -x $_[1] }
69              
70             # Could we open this as a file
71 14 100 66 14 1 1843 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 206 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 3969 my $class = shift;
90 10 100       36 my $file = shift or return undef;
91              
92             # Check the file
93 9 100       135 return $class->_error( 'File does not exist' ) unless -e $file;
94 7 50       21 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         106 (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 1928 my $class = shift;
117              
118             # One or two argument form
119 28         69 my ($file, $mode) = ();
120 28 100       80 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         32 $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       74 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         47 my $remove_on_fail = '';
141 28 100 100     419 if ( $modes{$mode} and ! -e $file ) {
142 14         65 $remove_on_fail = $class->_makePath( $file );
143 14 50       35 return undef unless defined $remove_on_fail;
144             }
145              
146             # Try to get the IO::File
147 28 100       163 IO::File->new( $file, $mode )
148             or $class->_andRemove( $remove_on_fail );
149             }
150              
151             # Provide creation mode specific methods
152 1     1 1 97 sub getReadHandle { $_[0]->open( '<', $_[1] ) }
153 10     10 1 3140 sub getWriteHandle { $_[0]->open( '>', $_[1] ) }
154 6     6 1 150 sub getAppendHandle { $_[0]->open( '>>', $_[1] ) }
155 1     1 1 96 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 2302 my $class = shift;
170 3 100       10 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       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         4 local $/ = undef;
184 1         3 local *SLURP;
185 1 50       37 CORE::open( SLURP, "<$file" ) or return undef;
186 1         22 my $source = ;
187 1 50       13 CORE::close( SLURP ) or return undef;
188 1         9 \$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 4208 my $class = shift;
196 6 100       22 my $file = shift or return;
197              
198             # Check the file
199 4 100       12 unless ( $class->canOpen( $file ) ) {
200 2         14 $class->_error( "Unable to open file '$file'" );
201 2         8 return;
202             }
203              
204             # Load the file
205 2 50       65 unless ( CORE::open(FILE, $file) ) {
206 0         0 $class->_error( "Unable to open file '$file'" );
207 0         0 return;
208             }
209 2         62 my @content = ;
210 2         9 chomp @content;
211 2         18 CORE::close(FILE);
212              
213 2 100       15 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 5009 my $class = shift;
221 21 100       55 my $file = shift or return undef;
222 20 100       45 unless ( defined $_[0] ) {
223 2         6 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         23 my $contents;
231 18 100       42 if ( ref $_[0] ) {
232 11 50 66     77 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         22 $contents = $_[0];
237             } else {
238 7         14 $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         21 my $dontclose = 0;
246 18 100       41 if ( ref $file ) {
247             # Don't close is someone passes us a handle.
248             # They might want to write other things.
249 12         20 $dontclose = 1;
250             } else {
251 6 50       17 $file = $class->getWriteHandle( $file ) or return undef;
252             }
253              
254             # Write the contents to the handle
255 18 100       775 if ( UNIVERSAL::isa($contents, 'SCALAR') ) {
256 14 50       56 $file->print( $$contents ) or return undef;
257             } else {
258 4         15 foreach ( @$contents ) {
259             # When printing the lines to the file,
260             # fix any possible newline problems.
261 16         118 chomp $_;
262 16 50       41 $file->print( $_ . "\n" ) or return undef;
263             }
264             }
265              
266             # Close the file if needed
267 18 100       443 $file->close unless $dontclose;
268              
269 18         256 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 3136 my $class = shift;
277 6 100       20 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         933 require File::Temp;
290 4         10280 require File::Copy;
291              
292             # Get a temp file
293 4         14 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       1502 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         15 $handle->close;
307              
308             # Now move the finished file to the final location
309 4 50       169 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         454 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 3142 my $class = shift;
322 7 100       41 my $file = shift or return undef;
323 6 100       32 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       732 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         15 $handle->close;
333              
334 5         196 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 9512 my $class = shift;
341 14 100 100     118 return undef unless defined($_[0]) && defined($_[1]);
342 12 50       73 my $source = File::Spec->canonpath( shift ) or return undef;
343 12 50       42 my $target = File::Spec->canonpath( shift ) or return undef;
344              
345             # Check the source and target
346 12 100       202 return $class->_error( "No such file or directory '$source'" ) unless -e $source;
347 11 50       201 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       52 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         42 my $remove_on_fail = $class->_makePath( $target );
362 11 50       27 return undef unless defined $remove_on_fail;
363              
364 11 100       122 if ( -f $source ) {
365             # Copy a file to the new location
366 10         85 require File::Copy;
367 10 50       59 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       44 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         670 require File::Copy::Recursive;
380 1         4269 my $rv = File::Copy::Recursive::dircopy( $tocopy, $target );
381 1 50       1484 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 1950 my $class = shift;
387 2 50       8 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       45 return $class->_error( "Copy source '$source' does not exist" ) unless -e $source;
392 2 50 33     26 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       9 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       7 return undef unless defined $remove_on_fail;
404              
405             # Do the file move
406 2         14 require File::Copy;
407 2         8 my $rv = File::Copy::move( $source, $target );
408 2 50       218 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         10 1;
415             }
416              
417             # Remove a file or directory ( safely )
418             sub remove {
419 18     18 1 1987 my $class = shift;
420 18 100       57 my $file = shift or return undef;
421              
422             # Does the file exist
423 17 100       268 unless ( -e $file ) {
424 1         19 return $class->_error( "File or directory does not exist" );
425             }
426              
427             # Use File::Remove to remove it
428 16         187 require File::Remove;
429 16 50       68 File::Remove::remove( \1, $file ) or return undef;
430 16 100 100     40000 ($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 6636 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
436 8         90 $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 1316 my $class = shift;
443 6 100       30 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       88 return $class->_error( "Cannot truncate a directory" ) if -d $file;
448 4 50       19 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       15 my $handle = $class->open( '>', $file )
454             or return $class->_error( 'Failed to open write file handle' );
455 4 50       486 $handle->truncate( $bytes )
456             or return $class->_error( "Failed to truncate file handle: $!" );
457 4         117 $handle->close;
458              
459 4         73 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 1234 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
476 5         18 $Object->makeDirectory;
477             }
478              
479             # Make sure that everything above our path exists
480             sub _makePath {
481 27 50   27   126 my $Object = File::Flat::Object->new( $_[1] ) or return undef;
482 27         100 $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 1122 sub errstr { $errstr }
495 11     11   5722 sub _error { $errstr = $_[1]; undef }
  11         99  
496             sub _andRemove {
497 5     5   362 my $self = shift;
498 5         6 my $to_remove = shift;
499 5 50       11 if ( length $to_remove ) {
500 0         0 require File::Remove;
501 0         0 File::Remove::remove( $to_remove );
502             }
503              
504 5 50       27 @_ ? $self->_error(@_) : undef;
505             }
506              
507             1;
508              
509              
510              
511              
512              
513              
514              
515              
516             package File::Flat::Object; # git description: v1.06-2-g7f3dc2c
517             our $VERSION = '1.07';
518              
519             # Instantiatable version of File::Flat.
520             #
521             # The methods are the same as for File::Flat, where applicable.
522              
523 3     3   8157 use strict;
  3         6  
  3         97  
524 3     3   17 use File::Spec ();
  3         5  
  3         5496  
525              
526             sub new {
527 58     58   138 my $class = shift;
528 58 50       134 my $filename = shift or return undef;
529              
530 58         361 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   106 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         489 my $base = Cwd::getcwd();
547              
548             # Populate the other properties
549 58         1294 $self->{absolute} = File::Spec->rel2abs( $self->{original}, $base );
550 58         732 my ($v, $d, $f) = File::Spec->splitpath( $self->{absolute} );
551 58         346 my @dirs = File::Spec->splitdir( $d );
552 58         106 $self->{volume} = $v;
553 58         96 $self->{directories} = \@dirs;
554 58         87 $self->{file} = $f;
555 58 50       150 $self->{type} = $self->{file} eq '' ? 'directory' : 'file';
556              
557 58         143 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   29 my $self = shift;
577 18 50       57 $self->_init unless defined $self->{type};
578              
579             # It it already exists, check for writable instead
580 18 50       225 return $self->canWrite if -e $self->{original};
581            
582             # Go up the directories and find the last one that exists
583 18         44 my $dir_known = '';
584 18         32 my $dir_unknown = '';
585 18         21 my @dirs = @{$self->{directories}};
  18         77  
586 18 50       52 pop @dirs if $self->{file} eq '';
587 18         56 while ( defined( my $dir = shift @dirs ) ) {
588 122         652 $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         587 my $fullpath = File::Spec->catpath( $self->{volume}, $dir_unknown, '' );
594 122 100       1454 last unless -e $fullpath;
595              
596             # This should be a directory
597 113 50       1232 if ( -d $fullpath ) {
598 113         265 $dir_known = $dir_unknown;
599 113         340 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       242 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       163 @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   17 my $self = shift;
686 8 50       51 $self->_init unless defined $self->{type};
687              
688             # We don't actually delete anything that currently exists
689 8 100       157 if ( -e $self->{original} ) {
690 1         34 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         19 my @dirs = @{$self->{directories}};
  7         35  
697             my @potential = (
698 7         106 File::Spec->catpath( $self->{volume}, shift(@dirs), '' )
699             );
700 7         42 while ( @dirs ) {
701 55         284 push @potential, File::Spec->catdir( $potential[-1], shift(@dirs), '' );
702             }
703              
704             # Go backwards though this list
705 7         18 foreach my $dir ( reverse @potential ) {
706             # Not existing is good... it fulfils the intent
707 20 100       291 next unless -e $dir;
708              
709             # This should also definately be a file
710 15 50       186 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       359 opendir( PRUNEDIR, $dir )
716             or return $self->_error("opendir failed while pruning: $!");
717 15         273 my @files = readdir PRUNEDIR;
718 15         150 closedir PRUNEDIR;
719 15         45 foreach ( @files ) {
720 24 100       186 next if $_ eq File::Spec->curdir;
721 15 100       59 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         336 return 1;
726             }
727              
728             # Nothing in the directory, we can delete it
729 8 50       51 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     27 my $mode = shift || 0755;
751 5 50       105 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       29 $self->_init unless defined $self->{type};
756              
757             # Ensure the directory below ours exists
758 5         14 my $remove_on_fail = $self->_makePath( $mode );
759 5 50       15 return undef unless defined $remove_on_fail;
760              
761             # Create the directory
762 5 50       210 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         55 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   51 my $self = shift;
776 32   100     112 my $mode = shift || 0755;
777 32 50       281 return '' if -e $self->{original};
778 32 100       144 $self->_init unless defined $self->{type};
779              
780             # Go up the directories and find the last one that exists
781 32         60 my $dir_known = '';
782 32         42 my $dir_unknown = '';
783 32         49 my $creation_root = '';
784 32         40 my @dirs = @{$self->{directories}};
  32         116  
785 32 50       82 pop @dirs if $self->{file} eq '';
786 32         100 while ( defined( my $dir = shift @dirs ) ) {
787 234         1249 $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         1151 my $fullpath = File::Spec->catpath( $self->{volume}, $dir_unknown, '' );
793 234 100       2889 if ( -e $fullpath ) {
794             # This should be a directory
795 213 50       2381 return undef unless -d $fullpath;
796             } else {
797             # Try to create the directory
798 21 50       914 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       80 $creation_root = $dir_unknown unless $creation_root;
804             }
805              
806 234         905 $dir_known = $dir_unknown;
807             }
808              
809 32         165 $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   13 sub _error { $File::Flat::errstr = $_[1]; undef }
  1         16  
824 0     0     sub _andRemove { shift; File::Flat->_andRemove(@_) }
  0            
825              
826             1;
827              
828             __END__