File Coverage

blib/lib/File/Dir/Dumper/Scanner.pm
Criterion Covered Total %
statement 146 158 92.4
branch 30 40 75.0
condition 5 7 71.4
subroutine 26 26 100.0
pod 1 1 100.0
total 208 232 89.6


line stmt bran cond sub pod time code
1             package File::Dir::Dumper::Scanner;
2             $File::Dir::Dumper::Scanner::VERSION = '0.6.6';
3 1     1   137310 use warnings;
  1         10  
  1         33  
4 1     1   5 use strict;
  1         2  
  1         19  
5 1     1   4 use autodie;
  1         2  
  1         5  
6              
7 1     1   5303 use 5.012;
  1         7  
8              
9 1     1   6 use parent 'File::Dir::Dumper::Base';
  1         2  
  1         7  
10              
11 1     1   43 use Carp ();
  1         2  
  1         14  
12              
13 1     1   505 use File::Find::Object ();
  1         13463  
  1         34  
14 1     1   9 use Devel::CheckOS qw( os_is );
  1         2  
  1         58  
15              
16 1     1   7 use POSIX qw(strftime);
  1         2  
  1         10  
17 1     1   77 use List::Util qw(min);
  1         2  
  1         120  
18              
19 1         10 use Class::XSAccessor accessors => {
20             _digest_cache => '_digest_cache',
21             _digests => '_digests',
22             _file_find => '_file_find',
23             _group_cache => '_group_cache',
24             _last_result => '_last_result',
25             _queue => '_queue',
26             _reached_end => '_reached_end',
27             _result => '_result',
28             _user_cache => '_user_cache',
29 1     1   6 };
  1         3  
30              
31 1     1   993 use Digest ();
  1         614  
  1         960  
32              
33              
34             sub _init
35             {
36 3     3   7 my $self = shift;
37 3         6 my $args = shift;
38              
39 3         9 my $dir_to_dump = $args->{dir};
40              
41 3         26 $self->_file_find(
42             File::Find::Object->new(
43             {
44             followlink => 0,
45             },
46             $dir_to_dump,
47             )
48             );
49              
50 3         663 $self->_queue( [] );
51              
52 3         21 $self->_add(
53             {
54             type => "header",
55             dir_to_dump => $dir_to_dump,
56             stream_type => "Directory Dump"
57             }
58             );
59              
60 3         9 $self->_digests( undef() );
61 3 100       13 if ( exists( $args->{digests} ) )
62             {
63 2         5 my $digests = {};
64 2         17 foreach my $d ( @{ $args->{digests} } )
  2         7  
65             {
66 4 50       11 if ( exists $digests->{$d} )
67             {
68 0         0 Carp::confess("Duplicate digest '$d'!");
69             }
70 4         12 $digests->{$d} = 1;
71             }
72 2 50       8 if ( !%$digests )
73             {
74 0         0 Carp::confess("The list of digests is empty.");
75             }
76 2         14 $self->_digests( [ sort { $a cmp $b } keys %$digests ] );
  2         15  
77             }
78 3   100     16 my $base = ( $args->{digest_cache} || 'Dummy' );
79 3 50       21 if ( $base !~ /\A[A-Za-z_][A-Za-z_0-9]*\z/ )
80             {
81 0         0 Carp::confess("Invalid digest_cache format.");
82             }
83 3         11 my $cl = "File::Dir::Dumper::DigestCache::$base";
84             ## no critic
85 3         224 eval "require $cl";
86             ## use critic
87 3 50       24 if ($@)
88             {
89 0         0 die $@;
90             }
91             $self->_digest_cache(
92             scalar $cl->new(
93             {
94 3   100     40 params => ( $args->{digest_cache_params} || +{} ),
95              
96             }
97             )
98             );
99              
100 3         13 $self->_user_cache( {} );
101 3         11 $self->_group_cache( {} );
102              
103 3         8 return;
104             }
105              
106             sub _add
107             {
108 19     19   40 my $self = shift;
109 19         30 my $token = shift;
110              
111 19         31 push @{ $self->_queue() }, $token;
  19         52  
112              
113 19         37 return;
114             }
115              
116             sub fetch
117             {
118 21     21 1 1017101 my $self = shift;
119              
120 21 100       36 if ( !@{ $self->_queue() } )
  21         89  
121             {
122 14         48 $self->_populate_queue();
123             }
124              
125 21         44 return shift( @{ $self->_queue() } );
  21         79  
126             }
127              
128             sub _up_to_level
129             {
130 9     9   16 my $self = shift;
131 9         18 my $target_level = shift;
132              
133 9 50       54 if ( my $last_result = $self->_last_result() )
134             {
135              
136 9         26 for my $level (
137 9         32 reverse( $target_level .. $#{ $last_result->dir_components() } ) )
138             {
139 4         15 $self->_add(
140             {
141             type => "updir",
142             depth => $level + 1,
143             }
144             );
145             }
146              
147             }
148 9         20 return;
149             }
150              
151             sub _find_new_common_depth
152             {
153 8     8   17 my $self = shift;
154              
155 8         55 my $result = $self->_result();
156 8         21 my $last_result = $self->_last_result();
157 8 50 33     46 return 0 if ( ( !$last_result ) or ( !$result ) );
158 8         16 my $depth = 0;
159              
160             my $upper_limit = min(
161 8         29 scalar( @{ $last_result->dir_components() } ),
162 8         15 scalar( @{ $result->dir_components() } ),
  8         36  
163             );
164              
165             FIND_I:
166 8         28 while ( $depth < $upper_limit )
167             {
168 2 100       13 if ( $last_result->dir_components()->[$depth] ne
169             $result->dir_components()->[$depth] )
170             {
171 1         4 last FIND_I;
172             }
173             }
174             continue
175             {
176 1         3 $depth++;
177             }
178              
179 8         29 return $depth;
180             }
181              
182             BEGIN
183             {
184 1 50   1   9 if ( os_is('Unix') )
185             {
186             *_my_getpwuid = sub {
187 5     5   5015 my $uid = shift;
188 5         1092 return scalar( getpwuid($uid) );
189 1         1058710 };
190             *_my_getgrgid = sub {
191 3     3   7 my $gid = shift;
192 3         230 return scalar( getgrgid($gid) );
193 1         859 };
194             }
195             else
196             {
197 0         0 *_my_getpwuid = sub { return "unknown"; };
  0         0  
198 0         0 *_my_getgrgid = sub { return "unknown"; };
  0         0  
199             }
200             }
201              
202             sub _get_user_name
203             {
204 8     8   568 my $self = shift;
205 8         20 my $uid = shift;
206              
207 8 100       47 if ( !exists( $self->_user_cache()->{$uid} ) )
208             {
209 3         12 $self->_user_cache()->{$uid} = _my_getpwuid($uid);
210             }
211              
212 8         56 return $self->_user_cache()->{$uid};
213             }
214              
215             sub _get_group_name
216             {
217 8     8   17 my $self = shift;
218 8         16 my $gid = shift;
219              
220 8 100       34 if ( !exists( $self->_group_cache()->{$gid} ) )
221             {
222 3         13 $self->_group_cache()->{$gid} = _my_getgrgid($gid);
223             }
224              
225 8         70 return $self->_group_cache()->{$gid};
226             }
227              
228             sub _calc_file_digests_key
229             {
230 5     5   15 my ( $self, $stat ) = @_;
231              
232 5         21 my $digests = $self->_digests;
233              
234 5 100       15 if ( !defined $digests )
235             {
236 1         22 return [];
237             }
238 4         11 my $result = $self->_result();
239 4         10 my $path = $result->path;
240             my $ret = $self->_digest_cache->get_digests(
241             {
242             path => $result->full_components,
243             mtime => $stat->[9],
244             digests => $digests,
245             calc_cb => sub {
246 4     4   55 my %ret;
247 4         10 foreach my $d (@$digests)
248             {
249 8         1156 my $o = Digest->new($d);
250 8         306 open my $fh, '<', $path;
251 8         2644 binmode $fh;
252 8         1398 $o->addfile($fh);
253 8         226 $ret{$d} = $o->hexdigest;
254 8         33 close($fh);
255             }
256 4         278 return \%ret;
257             },
258             }
259 4         20 );
260 4         69 return [ digests => $ret, ];
261             }
262             my $PERM_MASK = oct('07777');
263              
264             sub _calc_file_or_dir_token
265             {
266 8     8   17 my $self = shift;
267              
268 8         19 my $result = $self->_result();
269              
270 8         139 my @stat = stat( $result->path() );
271              
272 8 50       36 if ( not @stat )
273             {
274 0         0 Carp::confess(
275 0         0 "Could not successfully stat <<@{[$result->path()]}>> - $!");
276             }
277              
278             return {
279             filename => $result->full_components()->[-1],
280 8         97 depth => scalar( @{ $result->full_components() } ),
281             perms => sprintf( "%04o", ( $stat[2] & $PERM_MASK ) ),
282             mtime => strftime( "%Y-%m-%dT%H:%M:%S", localtime( $stat[9] ) ),
283             user => $self->_get_user_name( $stat[4] ),
284             group => $self->_get_group_name( $stat[5] ),
285             (
286             $result->is_dir()
287             ? ( type => "dir", )
288             : (
289             type => "file",
290             size => $stat[7],
291 8 100       46 @{ $self->_calc_file_digests_key( \@stat ) },
  5         16  
292             )
293             ),
294             };
295             }
296              
297             sub _populate_queue
298             {
299 14     14   29 my $self = shift;
300              
301 14 100       54 if ( $self->_reached_end() )
302             {
303 2         4 return;
304             }
305              
306 12         56 $self->_result( $self->_file_find->next_obj() );
307              
308 12 100       4102 if ( !$self->_last_result() )
    100          
309             {
310 3         18 $self->_add( { type => "dir", depth => 0 } );
311 3 50       18 if ( !$self->_result() )
312             {
313 0         0 $self->_add( { type => "footer" } );
314              
315 0         0 $self->_reached_end(1);
316             }
317             }
318             elsif ( !$self->_result() )
319             {
320 1 50       5 if ( $self->_last_result() )
321             {
322 1         4 $self->_up_to_level(-1);
323             }
324              
325 1         5 $self->_add( { type => "footer" } );
326              
327 1         4 $self->_reached_end(1);
328             }
329             else
330             {
331 8         26 $self->_up_to_level( $self->_find_new_common_depth() );
332              
333 8         30 $self->_add( $self->_calc_file_or_dir_token() );
334             }
335              
336 12         77 $self->_last_result( $self->_result() );
337             }
338              
339              
340             1; # End of File::Dir::Dumper
341              
342             __END__