File Coverage

blib/lib/Digest/Directory/BASE.pm
Criterion Covered Total %
statement 217 228 95.1
branch 64 92 69.5
condition 5 8 62.5
subroutine 29 29 100.0
pod 15 22 68.1
total 330 379 87.0


line stmt bran cond sub pod time code
1              
2             ######################################################################
3             #
4             # Directory Digest -- Digest::Directory::BASE.pm
5             # Matthew Gream (MGREAM)
6             # Copyright 2002 Matthew Gream. All Rights Reserved.
7             # $Id: BASE.pm,v 0.90 2002/10/21 20:24:06 matt Exp matt $
8             #
9             ######################################################################
10              
11             =head1 NAME
12              
13             Digest::Directory::BASE - base class for Directory Digests
14              
15             =head1 SYNOPSIS
16              
17             use Digest::Directory::BASE;
18            
19             my($d) = Digest::Directory::BASE->new;
20            
21             $d->quiet(1);
22             $d->include("/etc");
23             $d->include("/usr");
24             $d->exclude("/usr/local");
25             $d->compute();
26             $d->save("/var/dirgests/root.dirgests");
27              
28             =head1 REQUIRES
29              
30             Perl 5.004, Digest::MD5, File::Find, LWP::UserAgent.
31              
32             =head1 EXPORTS
33              
34             Nothing.
35              
36             =head1 DESCRIPTION
37              
38             B is a base class for creating digests of
39             file and directory sets. Clients can specify include and exclude
40             file and directory specifications, and then compute digests over
41             these sets, with optional prefix trimming. Clients can also fetch,
42             load, save, print, compare or export these sets.
43              
44             =cut
45              
46             ######################################################################
47              
48             package Digest::Directory::BASE;
49              
50             require 5.004;
51              
52 2     2   7990 use strict;
  2         3  
  2         68  
53 2     2   11 use warnings;
  2         3  
  2         75  
54 2     2   10 use vars qw( @ISA $PROGRAM $VERSION $AUTHOR $RIGHTS $USAGE );
  2         7  
  2         311  
55             @ISA = qw(Exporter);
56              
57             $PROGRAM = "Digest::Directory::BASE";
58             $VERSION = sprintf("%d.%02d", q$Revision: 0.90 $ =~ /(\d+)\.(\d+)/);
59             $AUTHOR = "Matthew Gream ";
60             $RIGHTS = "Copyright 2002 Matthew Gream. All Rights Reserved.";
61             $USAGE = "see pod documentation";
62              
63             ######################################################################
64              
65 2     2   10 use Digest::MD5;
  2         5  
  2         115  
66 2     2   10 use File::Find;
  2         3  
  2         161  
67 2     2   10 use Fcntl ':mode';
  2         3  
  2         623  
68 2     2   2762 use LWP::UserAgent;
  2         117197  
  2         6222  
69              
70             ######################################################################
71              
72             =head1 METHODS
73              
74             The following methods are provided:
75              
76             =over 4
77              
78             =cut
79              
80              
81             ######################################################################
82              
83             =item B<$dirgest = Digest::Directory::BASE-Enew( )>
84              
85             Create a dirgest instance; sets up default options, no quiet,
86             no includes, no excludes, zero digest and zero summary.
87              
88             =cut
89              
90             ######################################################################
91              
92             sub new
93             {
94 53     53 1 13602 my($class) = @_;
95              
96 53         403 my $self = {
97             quiet => 0,
98             trim => 0,
99             include => {},
100             exclude => {},
101             digests => {},
102             summary => ""
103             };
104              
105 53         246 return bless $self, $class;
106             }
107              
108              
109             ######################################################################
110              
111             =item B<$dirgest-Equiet( $enabled )>
112              
113             Enable quiet operating mode for a dirgest; ensures that no debug
114             trace output is provided during operation.
115              
116             $enabled => '0' or '1' for whether operation to be quiet or not;
117              
118             =cut
119              
120             ######################################################################
121              
122             sub quiet
123             {
124 56     56 1 233 my($self, $q) = @_;
125              
126 56         108 $self->{'quiet'} = $q;
127 56         115 return 1;
128             }
129              
130              
131             ######################################################################
132              
133             =item B<$dirgest-Etrim( $count )>
134              
135             Enable trimming of file/directory names;
136              
137             $count => 'n' where 'n' > 0 && 'n' specifies number of leading
138             elements to trim, e.g. '/a/b/c' trim 2 == 'b/c';
139              
140             =cut
141              
142             ######################################################################
143              
144             sub trim
145             {
146 6     6 1 27 my($self, $t) = @_;
147              
148 6 50       20 ( $t >= 0 ) || return 0;
149 6         11 $self->{'trim'} = $t;
150 6         15 return 1;
151             }
152              
153              
154             ######################################################################
155              
156             =item B<$result = $dirgest-Econfigure( $file )>
157              
158             Read a configuration file into a dirgest;
159              
160             $file => filename to read configuration from;
161              
162             return => '1' on success, or '0' on failure;
163              
164             File should contain lines with '+name' or '-name' that are turned
165             into include or exclude file/directory sets. All other names are
166             ignored. Whitespace may be present: ' + name', ' +name', '+ name',
167             etc. Also, '!trim=n' will set trim level, and '!quiet=n' will set
168             quiet level.
169              
170             =cut
171              
172             ######################################################################
173              
174             sub configure
175             {
176 3     3 1 403 my($self, $file) = @_;
177              
178 3 50       40 print "configuring from $file\n"
179             if (!$self->{'quiet'});
180              
181 3 50       132 if (open(FILE, "<$file"))
182             {
183 3         119 while ()
184             {
185 31 100       220 if (/^[ \t]*\-[ \t]*(.*)[ \t]*$/)
    100          
    100          
    100          
186             {
187 7         21 $self->exclude($1);
188             }
189             elsif (/^[ \t]*\+[ \t]*(.*)[ \t]*$/)
190             {
191 9         27 $self->include($1);
192             }
193             elsif (/^[ \t]*\![ \t]*trim[ \t]*=[ \t]*([\d]+)/i)
194             {
195 1         5 $self->trim($1);
196             }
197             elsif (/^[ \t]*\![ \t]*quiet[ \t]*=[ \t]*([\d]+)/i)
198             {
199 1         5 $self->quiet($1);
200             }
201             }
202              
203 3         32 close(FILE);
204              
205 3         14 return 1;
206             }
207             else
208             {
209 0         0 return 0;
210             }
211             }
212              
213              
214             ######################################################################
215              
216             =item B<$dirgest-Einclude( $name )>
217              
218             Include a name in the compute set for a dirgest;
219              
220             $name => particular name of file/directory set to include into
221             compute operation.
222              
223             =cut
224              
225             ######################################################################
226              
227             sub include
228             {
229 41     41 1 166 my($self, $name) = @_;
230              
231 41 50       146 print "including ", $name, "\n"
232             if (!$self->{'quiet'});
233              
234 41         104 $self->{'include'}{$name} = 1;
235 41         121 return 1;
236             }
237              
238              
239             ######################################################################
240              
241             =item B<$dirgest-Eexclude( $name )>
242              
243             Exclude a name from the compute set for a dirgest;
244              
245             $name => particular name of file/directory set to exclude from
246             compute operation.
247              
248             =cut
249              
250             ######################################################################
251              
252             sub exclude
253             {
254 10     10 1 209 my($self, $name) = @_;
255              
256 10 50       111 print "excluding ", $name, "\n"
257             if (!$self->{'quiet'});
258              
259 10         26 $self->{'exclude'}{$name} = 1;
260 10         49 return 1;
261             }
262              
263             sub digests
264             {
265 12     12 0 15 my($self) = @_;
266              
267 12         15 return %{$self->{'digests'}};
  12         67  
268             }
269             sub summary
270             {
271 29     29 0 41 my($self) = @_;
272              
273 29         546 return $self->{'summary'};
274             }
275              
276              
277             ######################################################################
278              
279             =item B<%stats = $dirgest-Estatistics( )>
280              
281             Return a hash with statistics about the dirgest; the hash
282             contains the following elements:
283              
284             'include' => number of includes specified;
285              
286             'exclude' => number of excludes specified;
287              
288             'digests' => number of digests;
289              
290             'quiet' => quiet enable or not;
291              
292             'trim' => trim level in operation;
293              
294             return => the hash;
295            
296             =cut
297              
298             ######################################################################
299              
300             sub statistics
301             {
302 21     21 1 298 my($self) = @_;
303              
304 21         25 my(%stats);
305 21         25 $stats{'include'} = scalar( keys %{$self->{'include'}} );
  21         79  
306 21         26 $stats{'exclude'} = scalar( keys %{$self->{'exclude'}} );
  21         47  
307 21         26 $stats{'digests'} = scalar( keys %{$self->{'digests'}} );
  21         45  
308 21         44 $stats{'quiet'} = $self->{'quiet'};
309 21         37 $stats{'trim'} = $self->{'trim'};
310              
311 21         144 return %stats;
312             }
313              
314              
315             ######################################################################
316              
317             =item B<$dirgest-Eclear( )>
318              
319             Clear a dirgest;
320              
321             'clear' out all of the dirgests, and reset the summary.
322              
323             =cut
324              
325             ######################################################################
326              
327             sub clear
328             {
329 1     1 1 9 my($self) = @_;
330              
331 1         3 $self->{'digests'} = {};
332 1         5 $self->{'summary'} = "";
333             }
334              
335             sub parse
336             {
337 55     55 0 83 my($self, $l) = @_;
338              
339 55         62 $_ = $l;
340 55         91 my($t) = $self->{'trim'};
341 55 100       277 if (/^= ([^=]*==[ ]*[\d]*)[ ]*([^\r\n]*).*$/)
    50          
342             {
343 42         82 my $d = $1;
344 42         60 my $f = $2; $f =~ s|^([^/]*/){$t}||;
  42         220  
345 42         197 $self->{'digests'}{$f} = $d;
346             }
347             elsif (/^# ([^=]*==).*$/)
348             {
349 13         69 my $s = $1;
350 13         81 $self->{'summary'} = $s;
351             }
352             }
353              
354              
355             ######################################################################
356              
357             =item B<$result = $dirgest-Efetch( $link, $user, $pass )>
358              
359             Fetch dirgests from a url;
360              
361             $link => the link to fetch from, should have protocol specifier, e.g.
362             'http://matthewgream.net', 'file://source.dirgest.org';
363              
364             $user => the http username for basic authorisation (if desired);
365              
366             $pass => the http password for basic authorisation (if desired);
367              
368             return => '1' on success, or '0' on failure;
369              
370             =cut
371              
372             ######################################################################
373              
374             sub fetch
375             {
376 6     6 1 19 my($self, $url, $user, $pass) = @_;
377              
378 6 50       21 print "fetching from $url\n"
379             if (!$self->{'quiet'});
380              
381 6         54 my $ua = LWP::UserAgent->new;
382 6         8303 $ua->agent("Mozilla/5.5 compatible: Dirgest/$VERSION");
383              
384 6 50       336 $_ = $url; if (/^http/ig) { $url .= "\?o=show"; }
  6         29  
  0         0  
385 6         45 my $req = HTTP::Request->new(GET => $url);
386 6 50 33     17570 if (defined $user && defined $pass)
387             {
388 6         69 $req->authorization_basic($user, $pass);
389             }
390              
391 6         4347 my $res = $ua->request($req);
392 6 50       51476 if ( $res->is_success() )
393             {
394 6         85 foreach (split(/\n/, $res->content))
395             {
396 24         127 $self->parse($_);
397             }
398            
399 6         26 $self->summarise();
400              
401 6         179 return 1;
402             }
403             else
404             {
405 0         0 return 0;
406             }
407             }
408              
409              
410             ######################################################################
411              
412             =item B<$result = $dirgest-Eload( $file )>
413              
414             Load dirgests from a file;
415              
416             $file => the name of the file to load from;
417              
418             return => '1' on success, or '0' on failure;
419              
420             =cut
421              
422             ######################################################################
423              
424             sub load
425             {
426 7     7 1 22 my($self, $file) = @_;
427              
428 7 50       23 print "reading from $file\n"
429             if (!$self->{'quiet'});
430              
431 7 50       231 if (open(FILE, "<$file"))
432             {
433 7         101 while ()
434             {
435 31         95 $self->parse($_);
436             }
437              
438 7         69 close(FILE);
439              
440 7         16 $self->summarise();
441              
442 7         33 return 1;
443             }
444             else
445             {
446 0         0 return 0;
447             }
448             }
449              
450              
451             ######################################################################
452              
453             =item B<$result = $dirgest-Esave( $file )>
454              
455             Save dirgests to a file;
456              
457             $file => the name of the file to save to;
458              
459             return => '1' on success, or '0' on failure;
460              
461             =cut
462              
463             ######################################################################
464              
465             sub save
466             {
467 15     15 1 39 my($self, $file) = @_;
468              
469 15 50       48 print "writing to $file\n"
470             if (!$self->{'quiet'});
471              
472 15 50       86517 if (open(FILE, ">$file"))
473             {
474 15         24 foreach my $f (sort(keys %{$self->{'digests'}}))
  15         86  
475             {
476 48         197 print FILE "= ", $self->{'digests'}{$f}, " ", $f, "\n";
477             }
478            
479 15         49 $self->summarise();
480              
481 15 50       40 if (length($self->{'summary'}))
482             {
483 15         33 print FILE "# ", $self->{'summary'}, "\n";
484             }
485              
486 15         699 close(FILE);
487              
488 15         93 return 1;
489             }
490             else
491             {
492 0         0 return 0;
493             }
494             }
495              
496              
497             ######################################################################
498              
499             =item B<$result = $dirgest-Ecompute( )>
500              
501             Compute dirgests from given include/exclude sets;
502              
503             return => 'n' where 'n' is the number of dirgests computed;
504              
505             =cut
506              
507             ######################################################################
508              
509             my(%digests_temp) = ();
510             my($digests_trim) = 0;
511             my(%digests_excl) = ();
512             sub compute
513             {
514 30     30 1 108 my($self) = @_;
515 30         41 my($result) = 0;
516              
517 30         57 %digests_temp = ();
518 30         43 $digests_trim = $self->{'trim'};
519 30         32 %digests_excl = %{$self->{'exclude'}};
  30         90  
520              
521 30         39 foreach my $d (keys %{$self->{'include'}})
  30         106  
522             {
523 32 50       78 print "computing from $d\n"
524             if (!$self->{'quiet'});
525              
526 32         3890 find( { wanted => \&compute_impl, follow => 1, no_chdir => 1 }, $d);
527 32         114 ++$result;
528             }
529              
530 30         72 %{$self->{'digests'}} = %digests_temp;
  30         113  
531 30         77 %digests_temp = ();
532 30         38 %digests_excl = ();
533 30         35 $digests_trim = 0;
534              
535 30         77 $self->summarise();
536              
537 30         116 return $result;
538             }
539             sub compute_impl
540             {
541 149     149 0 223 my $file = $File::Find::name;
542 149         2206 my @stat = (stat($file));
543            
544 149 50       388 if (!@stat)
545             {
546 0         0 $file =~ s|^([^/]*/){$digests_trim}||;
547 0         0 $digests_temp{$file} = "======================== ============";
548 0         0 return;
549             }
550              
551 149         193 my $exclude = 0;
552 149         475 foreach my $e (keys %digests_excl)
553             {
554 11 100 66     93 if (!$exclude && $file =~ /$e/)
555             {
556 4         11 $exclude = 1;
557             }
558             }
559              
560 149 100       563 if (!$exclude)
561             {
562 145         188 my $mode = (@stat)[2];
563 145         180 my $size = (@stat)[7];
564 145 100       5289 if (! S_ISDIR($mode) )
565             {
566 91 50       2797 if (open (FILE, $file))
567             {
568 91         162 binmode(FILE);
569 91         488 my $digest = Digest::MD5->new;
570 91         1281 $digest->addfile(*FILE);
571 91         835 close(FILE);
572              
573 91         870 $file =~ s|^([^/]*/){$digests_trim}||;
574 91         4386 $digests_temp{$file} =
575             $digest->b64digest . "== " . sprintf("%012d", $size);
576             }
577             else
578             {
579 0         0 $file =~ s|^([^/]*/){$digests_trim}||;
580 0         0 $digests_temp{$file} =
581             "======================== ============";
582             }
583             }
584             }
585             }
586              
587              
588             ######################################################################
589              
590             =item B<$result = $dirgest-Eprint( $nodetails, $nosummary )>
591              
592             Print a dirgest;
593              
594             $nodetails => don't print detailed dirgests;
595              
596             $nosummary => don't print summary dirgests;
597              
598             return => 'n' where 'n' is the number of dirgests printed;
599              
600             =cut
601              
602             ######################################################################
603              
604             sub print
605             {
606 12     12 1 70 my($self, $nodetails, $nosummary) = @_;
607 12         33 my($result, $string) = $self->results_impl($nodetails, $nosummary);
608 12 50       1326 print $string if ($string);
609 12         45 return $result;
610             }
611              
612              
613             ######################################################################
614              
615             =item B<$string = $dirgest-Estring( $nodetails, $nosummary )>
616              
617             Export a dirgest;
618              
619             $nodetails => don't stringify detailed dirgests;
620              
621             $nosummary => don't stringify summary dirgests;
622              
623             return => 'n' where 'n' is the number of dirgests printed;
624              
625             =cut
626              
627             ######################################################################
628              
629             sub string
630             {
631 5     5 1 76 my($self, $nodetails, $nosummary) = @_;
632 5         230 my($result, $string) = $self->results_impl($nodetails, $nosummary);
633 5         16 return $string;
634             }
635              
636             sub results_impl
637             {
638 17     17 0 25 my($self, $nodetails, $nosummary) = @_;
639              
640 17 50       45 $nodetails = 0 if (not defined $nodetails);
641 17 50       31 $nosummary = 0 if (not defined $nosummary);
642              
643 17         21 my($result) = 0;
644 17         19 my($string) = "";
645              
646 17 50       34 if (!$nodetails)
647             {
648 17         18 foreach my $f (sort(keys %{$self->{'digests'}}))
  17         68  
649             {
650 42         51 $string .= "= ";
651 42         75 $string .= $self->{'digests'}{$f};
652 42         54 $string .= " ";
653 42         109 $string .= $f;
654 42         42 $string .= "\n";
655 42         64 ++$result;
656             }
657             }
658 17 50       48 if (!$nosummary)
659             {
660 17         33 $self->summarise();
661              
662 17 100       36 if (length($self->{'summary'}))
663             {
664 16         19 $string .= "# ";
665 16         22 $string .= $self->{'summary'};
666 16         19 $string .= "\n";
667             }
668             }
669 17         45 return ($result, $string);
670             }
671              
672             sub summarise
673             {
674 75     75 0 106 my($self) = @_;
675              
676 75 100 100     262 if (!length($self->{'summary'}) && scalar(keys %{$self->{'digests'}}))
  31         105  
677             {
678 30         37 $self->{'summary'} = $self->summarise_impl( \%{$self->{'digests'}} );
  30         96  
679             }
680             }
681              
682             sub summarise_impl
683             {
684 30     30 0 46 my($self, $digests) = @_;
685              
686 30         128 my($digest) = Digest::MD5->new;
687              
688 30         149 foreach my $f (sort(keys %$digests))
689             {
690 91         397 $digest->add( join('', $$digests{$f}, " ", $f) );
691             }
692              
693 30         237 return join ('', $digest->b64digest, "==");
694             }
695              
696              
697             ######################################################################
698              
699             =item B<$result = $dirgest-Ecompare( $peer, $nodetails, $nosummary, $showequals )>
700              
701             Compare dirgest with another with options;
702              
703             $peer => the peer dirgest;
704              
705             $nodetails => don't compare detailed dirgests;
706              
707             $nosummary => don't compare summary dirgests;
708              
709             $showequals => show equal dirgests during activity;
710              
711             return => 'n' where 'n' is the number of differences found;
712              
713             =cut
714              
715             ######################################################################
716              
717             sub compare
718             {
719 15     15 1 169 my($self, $peer, $nodetails, $nosummary, $showequal) = @_;
720 15         17 my($result) = 0;
721              
722 15 100       34 $nodetails = 0 if (not defined $nodetails);
723 15 100       63 $nosummary = 0 if (not defined $nosummary);
724 15 100       33 $showequal = 0 if (not defined $showequal);
725              
726 15 100       27 if (!$nodetails)
727             {
728 12 50       28 print "comparing digests\n"
729             if (!$self->{'quiet'});
730              
731 12         31 my(%digests_l) = $peer->digests();
732 12         24 foreach my $f (sort(keys %{$self->{'digests'}}))
  12         51  
733             {
734 43         69 my ($c) = $self->{'digests'}{$f};
735            
736 43 100       71 if (!defined $digests_l{$f})
737             {
738 4         291 print "< ", $c, " ", $f, "\n";
739 4         13 ++$result;
740             }
741             else
742             {
743 39 100       62 if ($c ne $digests_l{$f})
744             {
745 2         41 print "! ", $c, " ", $f, "\n";
746 2         3 ++$result;
747             }
748             else
749             {
750 37 50       74 print "= ", $c, " ", $f, "\n"
751             if ($showequal);
752             }
753 39         73 delete $digests_l{$f}
754             }
755             }
756 12         42 foreach my $f (sort(keys %digests_l))
757             {
758 4         9 my ($c) = $digests_l{$f};
759 4         343 print "> ", $c, " ", $f, "\n";
760 4         20 ++$result;
761             }
762             }
763              
764 15 100       40 if (!$nosummary)
765             {
766 9 50       23 print "comparing summaries\n"
767             if (!$self->{'quiet'});
768              
769 9 100       25 if ($peer->summary() ne $self->summary())
    50          
770             {
771 7         18 print "? ", $peer->summary(), "\n";
772 7         17 ++$result;
773             }
774             elsif ($showequal)
775             {
776 0         0 print "# ", $peer->summary(), "\n";
777             }
778             }
779              
780 15 50       48 print "comparing differences: $result\n"
781             if (!$self->{'quiet'});
782              
783 15         45 return $result;
784             }
785              
786              
787             ######################################################################
788              
789             =back
790              
791             =head1 AUTHOR
792              
793             Matthew Gream (MGREAM)
794              
795             =head1 VERSION
796              
797             Version 0.90
798              
799             =head1 RIGHTS
800              
801             Copyright 2002 Matthew Gream. All Rights Reserved.
802              
803             This program is free software; you can redistribute it and/or modify
804             it under the same terms as Perl itself.
805              
806             =cut
807              
808             ######################################################################
809              
810             1;
811