File Coverage

blib/lib/File/Set/Writer.pm
Criterion Covered Total %
statement 54 54 100.0
branch 20 26 76.9
condition 1 3 33.3
subroutine 12 12 100.0
pod 1 2 50.0
total 88 97 90.7


line stmt bran cond sub pod time code
1             package File::Set::Writer;
2              
3 7     7   120934 use Moo;
  7         109005  
  7         43  
4 7     7   17883 use MooX::Types::MooseLike::Base qw( Str );
  7         51201  
  7         571  
5 7     7   5564 use MooX::Types::MooseLike::Numeric qw( PositiveInt );
  7         19204  
  7         11266  
6              
7             our $VERSION = '0.000002'; # 0.0.2
8             $VERSION = eval $VERSION;
9              
10             has max_lines => ( is => 'rw', default => sub { 500 }, isa => PositiveInt );
11              
12             has max_files => ( is => 'rw', default => sub { 100 }, isa => PositiveInt );
13              
14             has max_handles => ( is => 'rw', required => 1, isa => PositiveInt );
15              
16             has line_join => ( is => 'rw', default => sub { "\n" }, isa => Str );
17              
18             has expire_files_batch_size => ( is => 'rw', isa => PositiveInt );
19              
20             has expire_handles_batch_size => ( is => 'rw', isa => PositiveInt );
21              
22             # If the user doesn't set a batch_size for files or handles
23             # we will use 20% of max_(files|handles). This will be updated
24             # if max_files or max_handles is updated _unless_ the user explictly
25             # sets the batch_size, at which point it becomes their responsiblity
26             # to manage the values.
27              
28             around expire_files_batch_size => sub {
29             my ( $orig, $self ) = ( shift, shift );
30              
31             return $self->$orig( @_ ) || int( $self->max_files / 5 );
32             };
33              
34             around expire_handles_batch_size => sub {
35             my ( $orig, $self ) = ( shift, shift );
36              
37             return $self->$orig( @_ ) || int( $self->max_handles / 5 );
38             };
39              
40             sub print {
41 31000     31000 1 9803772 my ( $self, $file, @lines ) = @_;
42            
43 31000         42983 push @{$self->{queue}->{$file}}, @lines;
  31000         85028  
44              
45             $self->_write_files( $file )
46 31000 100       37101 if @{$self->{queue}->{$file}} >= $self->max_lines;
  31000         727041  
47              
48 31000 100       210279 $self->_write_pending_files
49             if $self->_files >= $self->max_files;
50            
51 31000         224999 return $self;
52             }
53              
54             # Write $self->expire_files_batch_size amount of files to disk,
55             # in the order of files with the most lines of content. This
56             # is used when ->_files >= ->max_files in ->print.
57              
58             sub _write_pending_files {
59 1000     1000   7562 my ( $self ) = @_;
60            
61             my @files = sort {
62 19000 50       37749 scalar @{$self->{queue}->{$b} || []} <=> scalar @{$self->{queue}->{$a} || []}
  19000 50       44353  
  19000         48514  
63 1000         1257 } keys %{$self->{queue}};
  1000         6797  
64              
65 1000         24979 $self->_write_files( splice @files, 0, $self->expire_files_batch_size );
66             }
67              
68             # Given names of files with queued lines, write the lines to the
69             # file handle with $self->_write(), joining the lines together with
70             # $self->line_join.
71              
72             sub _write_files {
73 4012     4012   38230 my ( $self, @files ) = @_;
74              
75 4012         7398 foreach my $file ( @files ) {
76             die "Error _write_files called with invalid argument \"$file\""
77 13000 50 33     45840 unless defined $file and exists $self->{queue}->{$file};
78              
79             $self->_write(
80             $file,
81 13000         278362 join( $self->line_join, @{$self->{queue}->{$file}}, '' )
  13000         112448  
82             );
83 13000         35695 delete $self->{queue}->{$file};
84             }
85             }
86              
87              
88             # Given a filename and a message, write the message to the file.
89             #
90             # This function implements a Least Recently Used (LRU) algorithm to cache file
91             # handles for repeated use.
92             # $self->max_handles is the limit of open file descriptors at any given time,
93             # while $self->expires_handles_batch_size handles will be closed when max_handles
94             # has been reached.
95              
96             sub _write {
97 13000     13000   24263 my ( $self, $file, @contents ) = @_;
98            
99 13000 100       22683 if ( $self->_handles >= $self->max_handles ) {
100             my @files = sort {
101             $self->{fcache}->{$a}->{stamp} <=> $self->{fcache}->{$b}->{stamp}
102 1369         7967 } keys %{$self->{fcache}};
  26028         47222  
  1369         7444  
103            
104 1369         31986 foreach my $i ( 0 .. $self->expire_handles_batch_size ) {
105 4899 100       58586 last unless $files[$i];
106 4800         115446 delete $self->{fcache}->{$files[$i]};
107             }
108             }
109              
110 13000 100       100832 if ( ! exists $self->{fcache}->{$file} ) {
111 4820 50       143682 open my $new_fh, ">>", $file
112             or die "Failed to open $file for writing: $!";
113 4820         23368 $self->{fcache}->{$file} = {
114             fh => $new_fh,
115             name => $file,
116             stamp => time(),
117             };
118             }
119              
120 13000         29888 my $wfh = $self->{fcache}->{$file}->{fh};
121 13000         29506 my $content = join ("", @contents);
122 13000 50       64352 print $wfh $content
123             or die "Failed to write $file: $!";
124 13000         32931 $self->{fcache}->{$file}->{stamp} = time;
125             }
126              
127             # Write all staged data to disk and closes all currently-open
128             # file handles. This happens automatically at the objects
129             # destruction.
130              
131             sub _sync {
132 12     12   56 my ( $self ) = @_;
133              
134 12         26 $self->_write_files( keys %{$self->{queue}} );
  12         129  
135             }
136              
137             # Return the count of open file handles currently in the cache.
138              
139             sub _handles {
140 14000 100   14000   14602 return scalar keys %{ shift->{fcache} || {} };
  14000         296748  
141             }
142              
143             # Return the count of files currently staged for being written.
144              
145             sub _files {
146 41000 50   41000   45745 return scalar keys %{ shift->{queue} || {} };
  41000         716762  
147             }
148              
149             # $self->_lines( "filename" );
150             #
151             # Return the count of lines staged for the given filename.
152              
153             sub _lines {
154 10000 100   10000   13551 return scalar @{ shift->{queue}->{ shift() } || [] };
  10000         57733  
155             }
156              
157             # Push our buffered arrays into the file handles before
158             # we close the file handles.
159             sub DEMOLISH {
160 12     12 0 1363225 shift->_sync;
161             }
162              
163             1;
164              
165              
166             __END__