File Coverage

blib/lib/File/Set/Writer.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package File::Set::Writer;
2              
3 7     7   179765 use Moo;
  7         195938  
  7         49  
4 7     7   32664 use MooX::Types::MooseLike::Base qw( Str );
  7         62215  
  7         825  
5 7     7   14179 use MooX::Types::MooseLike::Numeric qw( PositiveInt );
  0            
  0            
6              
7             our $VERSION = '0.000001'; # 0.0.1
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             my ( $self, $file, @lines ) = @_;
42            
43             push @{$self->{queue}->{$file}}, @lines;
44              
45             $self->_write_files( $file )
46             if @{$self->{queue}->{$file}} >= $self->max_lines;
47              
48             $self->_write_pending_files
49             if $self->_files >= $self->max_files;
50            
51             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             my ( $self ) = @_;
60            
61             my @files = sort {
62             scalar @{$self->{queue}->{$b} || []} <=> scalar @{$self->{queue}->{$a} || []}
63             } keys %{$self->{queue}};
64              
65             $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             my ( $self, @files ) = @_;
74              
75             foreach my $file ( @files ) {
76             die "Error _write_files called with invalid argument \"$file\""
77             unless defined $file and exists $self->{queue}->{$file};
78              
79             $self->_write(
80             $file,
81             join( $self->line_join, @{$self->{queue}->{$file}}, '' )
82             );
83             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             my ( $self, $file, @contents ) = @_;
98            
99             if ( $self->_handles >= $self->max_handles ) {
100             my @files = sort {
101             $self->{fcache}->{$a}->{stamp} <=> $self->{fcache}->{$b}->{stamp}
102             } keys %{$self->{fcache}};
103            
104             foreach my $i ( 0 .. $self->expire_handles_batch_size ) {
105             last unless $files[$i];
106             delete $self->{fcache}->{$files[$i]};
107             }
108             }
109              
110             if ( ! exists $self->{fcache}->{$file} ) {
111             open my $new_fh, ">>", $file
112             or die "Failed to open $file for writing: $!";
113             $self->{fcache}->{$file} = {
114             fh => $new_fh,
115             name => $file,
116             stamp => time(),
117             };
118             }
119              
120             my $wfh = $self->{fcache}->{$file}->{fh};
121             my $content = join ("", @contents);
122             print $wfh $content
123             or die "Failed to write $file: $!";
124             $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             my ( $self ) = @_;
133              
134             $self->_write_files( keys %{$self->{queue}} );
135             }
136              
137             # Return the count of open file handles currently in the cache.
138              
139             sub _handles {
140             return scalar keys %{ shift->{fcache} || {} };
141             }
142              
143             # Return the count of files currently staged for being written.
144              
145             sub _files {
146             return scalar keys %{ shift->{queue} || {} };
147             }
148              
149             # $self->_lines( "filename" );
150             #
151             # Return the count of lines staged for the given filename.
152              
153             sub _lines {
154             return scalar @{ shift->{queue}->{ shift() } || [] };
155             }
156              
157             # Push our buffered arrays into the file handles before
158             # we close the file handles.
159             sub DESTROY { shift->_sync; }
160              
161             1;
162              
163              
164             __END__