File Coverage

blib/lib/App/Hashl.pm
Criterion Covered Total %
statement 82 85 96.4
branch 14 22 63.6
condition 4 8 50.0
subroutine 20 20 100.0
pod 15 15 100.0
total 135 150 90.0


line stmt bran cond sub pod time code
1             package App::Hashl;
2              
3 1     1   1006 use strict;
  1         3  
  1         44  
4 1     1   5 use warnings;
  1         2  
  1         50  
5 1     1   30 use 5.010;
  1         6  
  1         74  
6              
7 1     1   1267 use Digest::SHA qw(sha1_hex);
  1         5553  
  1         163  
8 1     1   1338 use Storable qw(nstore retrieve);
  1         4454  
  1         1183  
9              
10             our $VERSION = '1.00';
11              
12             sub new {
13 6     6 1 951 my ( $obj, %conf ) = @_;
14              
15 6         26 my $ref = {
16             files => {},
17             ignored => {},
18             };
19              
20 6         15 $ref->{config} = \%conf;
21 6   100     32 $ref->{config}->{read_size} //= ( 2**20 ) * 4; # 4 MiB
22              
23 6         26 return bless( $ref, $obj );
24             }
25              
26             sub new_from_file {
27 1     1 1 875 my ( $obj, $file ) = @_;
28              
29 1         5 my $ref = retrieve($file);
30              
31 1         123 return bless( $ref, $obj );
32             }
33              
34             sub si_size {
35 3     3 1 7 my ( $self, $bytes ) = @_;
36 3         10 my @post = ( q{ }, qw(k M G T) );
37              
38 3         8 while ( $bytes >= 1024 ) {
39 2         5 $bytes /= 1024;
40 2         7 shift @post;
41             }
42              
43 3         60 return sprintf( '%6.1f%s', $bytes, $post[0] );
44             }
45              
46             sub hash_file {
47 21     21 1 38 my ( $self, $file ) = @_;
48 21         26 my $data;
49              
50             # read() fails for empty files
51 21 50       465 if ( ( stat($file) )[7] == 0 ) {
52 0         0 return sha1_hex();
53             }
54              
55             #<<< perltidy has problems indenting 'or die' with tabs
56              
57 21 50       865 open( my $fh, '<', $file )
58             or die("Can't open ${file} for reading: $!\n");
59 21 50       70 binmode($fh)
60             or die("Can't set binmode on ${file}: $!\n");
61 21 50       440 read( $fh, $data, $self->{config}->{read_size} )
62             or die("Can't read ${file}: $!\n");
63 21 50       310 close($fh)
64             or die("Can't close ${file}: $!\n");
65              
66             #>>>
67 21         332 return sha1_hex($data);
68             }
69              
70             sub hash_in_db {
71 10     10 1 27 my ( $self, $hash ) = @_;
72              
73 10 100       37 if ( $self->{ignored}->{$hash} ) {
74 3         20 return '// ignored';
75             }
76              
77 7         21 for my $name ( $self->files() ) {
78 6         14 my $file = $self->file($name);
79              
80 6 100       23 if ( $file->{hash} eq $hash ) {
81 5         31 return $name;
82             }
83             }
84 2         10 return;
85             }
86              
87             sub file_in_db {
88 8     8 1 704 my ( $self, $file ) = @_;
89              
90 8         26 return $self->hash_in_db( $self->hash_file($file) );
91             }
92              
93             sub read_size {
94 2     2 1 568 my ($self) = @_;
95              
96 2         18 return $self->{config}->{read_size};
97             }
98              
99             sub file {
100 12     12 1 24 my ( $self, $name ) = @_;
101              
102 12         52 return $self->{files}->{$name};
103             }
104              
105             sub delete_file {
106 2     2 1 4 my ( $self, $name ) = @_;
107              
108 2         10 delete $self->{files}->{$name};
109              
110 2         3 return 1;
111             }
112              
113             sub files {
114 12     12 1 23 my ($self) = @_;
115              
116 12         17 return keys %{ $self->{files} };
  12         69  
117             }
118              
119             sub add_file {
120 5     5 1 48 my ( $self, %opt ) = @_;
121 5         11 my $file = $opt{file};
122 5         11 my $path = $opt{path};
123 5         120 my ( $size, $mtime ) = ( stat($path) )[ 7, 9 ];
124              
125 5 0 33     21 if ( $self->file($file)
      33        
126             and $self->file($file)->{mtime} == $mtime
127             and $self->file($file)->{size} == $size )
128             {
129 0         0 return;
130             }
131              
132 5         16 my $hash = $self->hash_file($path);
133              
134 5 100       21 if ( $self->{ignored}->{$hash} ) {
135 2 100       9 if ( $opt{unignore} ) {
136 1         79 $self->unignore($hash);
137             }
138             else {
139 1         5 return;
140             }
141             }
142              
143 4         24 $self->{files}->{$file} = {
144             hash => $hash,
145             mtime => $mtime,
146             size => $size,
147             };
148              
149 4         27 return 1;
150             }
151              
152             sub ignored {
153 4     4 1 12 my ($self) = @_;
154              
155 4 50       18 if ( exists $self->{ignored} ) {
156 4         6 return keys %{ $self->{ignored} };
  4         59  
157             }
158              
159 0         0 return ();
160             }
161              
162             sub ignore {
163 2     2 1 6 my ( $self, $file, $path ) = @_;
164              
165 2         8 $self->delete_file($file);
166 2         8 $self->{ignored}->{ $self->hash_file($path) } = 1;
167              
168 2         11 return 1;
169             }
170              
171             sub unignore {
172 2     2 1 6 my ( $self, $hash ) = @_;
173              
174 2         7 delete $self->{ignored}->{$hash};
175              
176 2         5 return 1;
177             }
178              
179             sub save {
180 1     1 1 3 my ( $self, $file ) = @_;
181              
182 1         7 return nstore( $self, $file );
183             }
184              
185             1;
186              
187             __END__