File Coverage

blib/lib/Mir/FileHandler.pm
Criterion Covered Total %
statement 97 126 76.9
branch 28 68 41.1
condition 13 48 27.0
subroutine 22 27 81.4
pod 7 9 77.7
total 167 278 60.0


line stmt bran cond sub pod time code
1             # ABSTRACT: A module to handle files and folders
2             package Mir::FileHandler;
3              
4 4     4   560261 use 5.018;
  4         42  
5 4     4   2300 use Moose;
  4         1923494  
  4         28  
6 4     4   30522 no warnings 'experimental::smartmatch';
  4         10  
  4         207  
7 4     4   2708 use DirHandle;
  4         7451  
  4         139  
8 4     4   30 use File::Find;
  4         9  
  4         234  
9 4     4   25 use File::Basename qw( dirname );
  4         8  
  4         258  
10 4     4   1696 use Log::Log4perl;
  4         60785  
  4         41  
11 4     4   230 use Digest::MD5 'md5_base64';
  4         18  
  4         224  
12 4     4   2588 use Cache::FileCache;
  4         166233  
  4         7375  
13              
14             =head1 NAME
15              
16             Mir::FileHandler - An Mir module to handle files and folders...
17              
18             =head1 VERSION
19              
20             Version 0.05
21              
22             =cut
23              
24             our $VERSION = '0.05';
25             our @rec_files = ();
26             our $files_as_hash = {};
27             our @types = ();
28              
29             =head1 SYNOPSIS
30              
31             use Mir::FileHandler;
32              
33             # the oo way...
34             # get a new FileHandler obj for the passed root directory
35             my $o = Mir::FileHandler->new(
36             path => $path, # root folder to start with
37             );
38              
39             # get plain files list inside the root directory
40             my $list = $o->plainfiles(); # or pass a path in input
41              
42             # get plain files list from folder and sub-folders
43             my $list = $o->plainfiles_recursive( $path, $suffix, \&found );
44              
45             # Traverses a directory tree and exec code for each file
46             $o->dir_walk(
47             path => $path,
48             code => $code,
49             );
50              
51             # Traverses a directory tree and exec code for each file
52             # Stops after max success code execution
53             # the sub pointed by $code has to return 1 in
54             # case of success
55             # if cached_dir is set, at each iteration starts from
56             # what stored in cache, if something has been stored,
57             # otherwise starts from path
58             $o->clear_cache(); # to clear current dir stored in cache
59             $o->dir_walk_max(
60             code => $code, # not mandatory, code to exec for each file found
61             max => $max # not mandatory, max files successfully processed
62             );
63              
64             =head1 EXPORT
65              
66             =over
67              
68             =item plainfiles
69              
70             =item plainfiles_recursive
71              
72             =back
73              
74             =head1 SUBROUTINES/METHODS
75              
76             =cut
77              
78             #=============================================================
79             has 'path' => (
80             is => 'rw',
81             isa => 'Str',
82             required => 1
83             );
84              
85             has 'cache_key' => (
86             is => 'rw',
87             isa => 'Str',
88             );
89              
90             has 'cache' => (
91             is => 'ro',
92             isa => 'Cache::FileCache',
93             lazy => 1,
94             default => sub {
95             my $self = shift;
96             my $o = Cache::FileCache->new();
97             return $o;
98             }
99             );
100              
101             has 'log' => (
102             is => 'rw',
103             isa => 'Log::Log4perl::Logger',
104             lazy => 1,
105             default => sub {
106             return Log::Log4perl->get_logger( __PACKAGE__ );
107             }
108             );
109              
110             sub BUILD {
111 3     3 0 6 my $self = shift;
112 3         93 $self->cache_key( md5_base64( $self->path ) );
113             }
114              
115             #=============================================================
116              
117             =head2 plainfiles
118              
119             =head3 INPUT
120              
121             $path: path to look for (not mandatory, eventually takes the
122             one passed at construction time)
123              
124             =head3 OUTPUT
125              
126             An ArrayRef
127              
128             =head3 DESCRIPTION
129              
130             Returns the list of folder plain files.
131              
132             =cut
133              
134             #=============================================================
135             sub plainfiles {
136 1     1 1 3 my $self = shift;
137              
138 1 50       41 return undef unless ( -e $self->path );
139 1         43 return [ _pf( $self->path ) ];
140             }
141              
142             #=============================================================
143              
144             =head2 _pf
145              
146             =head3 INPUT
147              
148             $path: the path to look for docs
149              
150             =head3 OUTPUT
151              
152             A sorted list of docs.
153              
154             =head3 DESCRIPTION
155              
156             Private function. Returns the sorted list of regular
157             files in current folder.
158              
159             =cut
160              
161             #=============================================================
162             sub _pf {
163 1 50   1   4 my $dir = shift or return undef;
164 1 50       21 my $dh = DirHandle->new($dir) or die "can't opendir $dir: $!";
165             return sort
166 1         22 grep { -f }
167 1         18 map { "$dir/$_" }
168 1         107 grep { !/^\./ }
  3         65  
169             $dh->read();
170             }
171              
172             #=============================================================
173              
174             =head2 _get_path_from_cache
175              
176             =head3 INPUT
177              
178             None
179              
180             =head3 OUTPUT
181              
182             The cache path or undef
183              
184             =head3 DESCRIPTION
185              
186             Tries to retrieve a current path cached for the root one or
187             returns undef
188              
189             =cut
190              
191             #=============================================================
192             sub _get_path_from_cache {
193 3     3   5 my $self = shift;
194 3         99 return $self->cache->get( $self->cache_key );
195             }
196              
197             sub _set_path_in_cache {
198 3     3   6 my ( $self, $path ) = @_;
199 3 50       10 return $self->clear_cache() unless ( $path );
200 0         0 return $self->cache->set( $self->cache_key, $path );
201             }
202              
203             sub clear_cache {
204 6     6 0 11 my $self = shift;
205 6         175 $self->cache->remove( $self->cache_key );
206 6         880 return 1;
207             }
208              
209             #=============================================================
210              
211             =head2 dir_walk - traverses recursively a folder
212              
213             =head3 INPUT
214              
215             An hash with keys:
216             code: a coderef to apply to each file
217              
218             =head3 OUTPUT
219              
220             -1 if root file or folder are not existent, else
221             the number of valid files.
222              
223             =head3 DESCRIPTION
224              
225             Traverse a directory, triggering a sub for each file found.
226             The sub should return 1 if file is good, 0 otherwise.
227             It is meant to recursively process a folder in one shot.
228             This is the minimal logic to traverse a tree, for added,
229             features look at dir_walk_max.
230              
231             =cut
232              
233             #=============================================================
234             sub dir_walk {
235 1     1 1 4 my ( $self, %params ) = @_;
236 1         3 my $code = $params{code};
237 1 50       5 return undef unless $code;
238 1         34 return $self->_walk( $self->path, $code, 0);
239             }
240              
241             #=============================================================
242              
243             =head2 _walk
244              
245             =head3 INPUT
246              
247             $top: subtree root
248             $code: code to execute against each valid file
249             $count: number of valid files till now...
250              
251             =head3 OUTPUT
252              
253             The number of valid files.
254              
255             =head3 DESCRIPTION
256              
257             Traverse the subtree.
258              
259             =cut
260              
261             #=============================================================
262             sub _walk {
263 2     2   5 my ( $self, $top, $code, $count ) = @_;
264 2         4 my $DIR;
265 2 50       78 unless (opendir $DIR, $top) {
266 0         0 $self->log->error( "Couldn’t open directory $top: $!" );
267 0         0 return;
268             }
269 2         7 my $file;
270 2         47 while ($file = readdir $DIR) {
271 6         757 my $item = "$top/$file";
272 6 100       115 if ( -f $item ) {
    50          
273 1         7 $count += $code->($item);
274             } elsif (-d $item ) {
275 5 100 100     48 next if $file eq '.' || $file eq '..';
276 1         7 $count = $self->_walk( $item, $code, $count );
277             }
278             }
279 2         27 closedir $DIR;
280 2         20 return $count;
281             }
282              
283             #=============================================================
284              
285             =head2 dir_walk_max - traverses recursively a folder, stops after
286             max valid files found or all tree has been
287             traversed
288              
289             =head3 INPUT
290              
291             An hash with keys:
292             code: a coderef to apply to
293             max: max number of items to evaluate
294              
295             =head3 OUTPUT
296              
297             -1 if root file or folder are not existent, else
298             the number of valid files.
299              
300             =head3 DESCRIPTION
301              
302             Traverse a directory, triggering a sub for each file found.
303             The sub should return 1 if file is good, 0 otherwise.
304             The method stops when all files are consumed or max number
305             of good files is reached.
306              
307              
308             =cut
309              
310             #=============================================================
311             sub dir_walk_max {
312 3     3 1 13 my ( $self, %params ) = @_;
313 3         7 my $code = $params{code};
314 3         6 my $max = $params{max};
315 3 50 33     18 return undef unless ( $code && $max );
316 3         8 my $rel_path = $self->_get_path_from_cache();
317 3         526 return $self->_walk_max( $self->path, 0, $max, $code, $rel_path );
318             }
319              
320              
321             #=============================================================
322              
323             =head2 _walk_max
324              
325             =head3 INPUT
326              
327             root : the root folder to start with
328             count : number of valid items processed
329             rel_path : the relative path from root
330             max : max number of valid items
331             code : code to run against each item
332              
333             =head3 OUTPUT
334              
335             The number of valid items processed
336              
337             =head3 DESCRIPTION
338              
339             Inner loop to analize each item of the tree.
340             it calls recursively itself for each sub-tree.
341             Ends when 1 of these conditions is met:
342             - no more files found
343             - max number of valid items reached
344             this number is set to be 10 times the max number of valid items.
345              
346              
347             =cut
348              
349             #=============================================================
350             sub _walk_max {
351 3     3   9 my ( $self, $root, $count, $max, $code, $rel_path ) = @_;
352              
353 3 50       8 $self->log->info(sprintf("[%06d]:Scanning %s", $count, $rel_path)) if( $rel_path );
354 3 50       7 my $path = ( $rel_path ) ? "$root/$rel_path" : $root;
355 3         19 $self->_set_path_in_cache( $rel_path );
356              
357 3         7 my $DIR;
358 3 50       120 unless (opendir $DIR, $path) {
359 0         0 $self->log->error( "Couldn’t open directory $path $!" );
360 0         0 return undef;
361             }
362              
363 3         10 my $file;
364 3   100     75 while ( defined($file = readdir $DIR) && ( $count < $max ) ) {
365 11         76 my $item = "$path/$file";
366 11 100       154 if ( -f $item ) {
    50          
367 9         35 $count += $code->($item);
368             } elsif (-d $item ) {
369 2 50 66     19 next if $file eq '.' || $file eq '..';
370 0 0       0 $count = $self->_walk_max( $root, $count, $max, $code, ( $rel_path ) ? $rel_path.'/'.$file : $file );
371             }
372             }
373 3         74 closedir $DIR;
374 3         15 undef $DIR;
375 3 100       9 unless ( $file ) {
376 1 50       7 ( $rel_path ) ? $self->_set_path_in_cache( join('/', splice(@{[split(m|/|, $rel_path)]},0,-1))) :
  0         0  
377             $self->clear_cache();
378             }
379 3         5 undef $file;
380 3         25 return $count;
381             }
382              
383             #=============================================================
384              
385             =head2 plainfiles_recursive
386              
387             =head3 INPUT
388              
389             $path: a path to start from.
390             $avoid: arrayref con lista risorse da evitare
391             $suffix: arrayref con lista suffissi da processare
392             $found: ref sub callback per ogni risorsa
393              
394             =head3 OUTPUT
395              
396             An arrayref.
397              
398             =head3 DESCRIPTION
399              
400             Returns recursively the list of all files from passed folder.
401              
402             =cut
403              
404             #=============================================================
405             sub plainfiles_recursive {
406 1     1 1 9713 my ( $self, $path, $found ) = @_;
407              
408 1 50 33     15 my $dir = ( $self && ref $self ) ? $path || $self->{path} : $path;
      33        
409 1 50       5 return undef unless $dir;
410              
411 1 50       6 $found = \&_found unless $found;
412              
413 1         3 @rec_files = ();
414 1         111 find ( $found, ( $dir ) );
415 1         14 return \@rec_files;
416             }
417              
418             sub _found {
419 3 100   3   234 return if ( -d );
420 1 50 33     19 if ( -f && !/^\./ && !/\~$/ ) {
      33        
421 1         36 push @rec_files, $File::Find::name;
422             }
423             }
424              
425             sub _collect_as_hash {
426 0 0 0 0     return if ( -d && /\.svn/ );
427 0 0 0       if ( -f && !/^\./ && !/\~$/ ) {
      0        
428 0 0         if ( m|(\w+)\.(\w+)$| ) {
429 0           push @{ $files_as_hash->{ $2 } }, $File::Find::name;
  0            
430             }
431             }
432             }
433              
434             sub _collect_of_type {
435 0 0 0 0     return if ( -d && /\.\w+/ );
436 0 0 0       if ( -f && !/^\./ && !/\~$/ ) {
      0        
437 0 0         if ( m|(\w+)\.(\w+)$| ) {
438 0 0         if ( $2 ~~ @types ) {
439 0           push @rec_files, $File::Find::name;
440             }
441             }
442             }
443             }
444              
445             #=============================================================
446              
447             =head2 plainfiles_recursive_as_hash
448              
449             =head3 INPUT
450              
451             $path : root path (not mandatory if already passed at
452             construction time)
453              
454             =head3 OUTPUT
455              
456             An HashRef.
457              
458             =head3 DESCRIPTION
459              
460             Recursively collects files in an hash indexed by file suffix.
461              
462             =cut
463              
464             #=============================================================
465             sub plainfiles_recursive_as_hash {
466 0     0 1   my ( $self, $path ) = @_;
467              
468 0 0 0       my $dir = ( $self && ref $self ) ? $path || $self->{path} : $path;
      0        
469 0 0         return undef unless $dir;
470              
471 0           $files_as_hash = {};
472 0           $self->plainfiles_recursive( $dir, \&_collect_as_hash );
473              
474             # foreach my $file ( @$rec_files ) {
475             # if ( $file =~ m|(\w+)\.(\w+)$| ) {
476             # push @{ $files_as_hash->{ $2 } }, $file;
477             # }
478             # }
479              
480 0           return $files_as_hash;
481             }
482              
483             #=============================================================
484              
485             =head2 plainfiles_recursive_of_type
486              
487             =head3 INPUT
488              
489             @types: list of valid file suffixes
490              
491             =head3 OUTPUT
492              
493             An ArrayRef or undef in case of errors.
494              
495             =head3 DESCRIPTION
496              
497             Recursively collects all files with valid suffixes
498              
499             =cut
500              
501             #=============================================================
502             sub plainfiles_recursive_of_type {
503 0     0 1   my ( $self, @ptypes ) = @_;
504              
505 0 0         return undef unless $self->{path};
506              
507 0           @types = @ptypes;
508 0           @rec_files = ();
509 0           $self->plainfiles_recursive( $self->{path}, \&_collect_of_type );
510 0           return \@rec_files;
511             }
512              
513             #=============================================================
514              
515             =head2 process_dir
516              
517             =head3 INPUT
518              
519             $dir : dir to start with
520             $suffix : arrayref of valid file suffixes
521             $depth : depth in processing subdirs
522              
523             =head3 OUTPUT
524              
525             An arrayref
526              
527             =head3 DESCRIPTION
528              
529             Workflow:
530             get a single dir as input and the level of recursions
531             get the list of valid dir files and process them
532             get the list of dir direct subdirs
533             depth--
534             if depth > 0
535             call process_dir foreach subdir
536              
537             =cut
538              
539             #=============================================================
540       0 1   sub process_dir ($$) {
541              
542             }
543              
544             =head1 AUTHOR
545              
546             Marco Masetti, C<< <marco.masetti at softeco.it> >>
547              
548             =head1 BUGS
549              
550             Please report any bugs or feature requests to C<bug-ishare-filehandler at rt.cpan.org>, or through
551             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Mir-FileHandler>. I will be notified, and then you'll
552             automatically be notified of progress on your bug as I make changes.
553              
554             =head1 SUPPORT
555              
556             You can find documentation for this module with the perldoc command.
557              
558             perldoc Mir::FileHandler
559              
560             =head1 LICENSE AND COPYRIGHT
561              
562             Copyright 2013 Marco Masetti.
563              
564             This program is free software; you can redistribute it and/or modify it
565             under the terms of either: the GNU General Public License as published
566             by the Free Software Foundation; or the Artistic License.
567              
568             See http://dev.perl.org/licenses/ for more information.
569              
570             =cut
571              
572 4     4   55 no Moose;
  4         10  
  4         40  
573             __PACKAGE__->meta->make_immutable;