diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-06-01 14:15:30 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-06-01 14:15:30 +0000 |
commit | 1425eea04dd872dc6313f5315f317b2de288037c (patch) | |
tree | f81c74f75429e829714029850f89ee4c7f13aa39 /lib/IO/Async/File.pm | |
download | IO-Async-tarball-master.tar.gz |
IO-Async-0.67HEADIO-Async-0.67master
Diffstat (limited to 'lib/IO/Async/File.pm')
-rw-r--r-- | lib/IO/Async/File.pm | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/lib/IO/Async/File.pm b/lib/IO/Async/File.pm new file mode 100644 index 0000000..cbb3604 --- /dev/null +++ b/lib/IO/Async/File.pm @@ -0,0 +1,219 @@ +# You may distribute under the terms of either the GNU General Public License +# or the Artistic License (the same terms as Perl itself) +# +# (C) Paul Evans, 2012 -- leonerd@leonerd.org.uk + +package IO::Async::File; + +use 5.010; # // +use strict; +use warnings; + +our $VERSION = '0.67'; + +use base qw( IO::Async::Timer::Periodic ); + +use Carp; +use File::stat; + +# No point watching blksize or blocks +my @STATS = qw( dev ino mode nlink uid gid rdev size atime mtime ctime ); + +=head1 NAME + +C<IO::Async::File> - watch a file for changes + +=head1 SYNOPSIS + + use IO::Async::File; + + use IO::Async::Loop; + my $loop = IO::Async::Loop->new; + + my $file = IO::Async::File->new( + filename => "config.ini", + on_mtime_changed => sub { + my ( $self ) = @_; + print STDERR "Config file has changed\n"; + reload_config( $self->handle ); + } + ); + + $loop->add( $file ); + + $loop->run; + +=head1 DESCRIPTION + +This subclass of L<IO::Async::Notifier> watches an open filehandle or named +filesystem entity for changes in its C<stat()> fields. It invokes various +events when the values of these fields change. It is most often used to watch +a file for size changes; for this task see also L<IO::Async::FileStream>. + +While called "File", it is not required that the watched filehandle be a +regular file. It is possible to watch anything that C<stat(2)> may be called +on, such as directories or other filesystem entities. + +=cut + +=head1 EVENTS + +The following events are invoked, either using subclass methods or CODE +references in parameters. + +=head2 on_dev_changed $new_dev, $old_dev + +=head2 on_ino_changed $new_ino, $old_ino + +=head2 ... + +=head2 on_ctime_changed $new_ctime, $old_ctime + +Invoked when each of the individual C<stat()> fields have changed. All the +C<stat()> fields are supported apart from C<blocks> and C<blksize>. Each is +passed the new and old values of the field. + +=head2 on_devino_changed $new_stat, $old_stat + +Invoked when either of the C<dev> or C<ino> fields have changed. It is passed +two L<File::stat> instances containing the complete old and new C<stat()> +fields. This can be used to observe when a named file is renamed; it will not +be observed to happen on opened filehandles. + +=head2 on_stat_changed $new_stat, $old_stat + +Invoked when any of the C<stat()> fields have changed. It is passed two +L<File::stat> instances containing the old and new C<stat()> fields. + +=cut + +=head1 PARAMETERS + +The following named parameters may be passed to C<new> or C<configure>. + +=head2 handle => IO + +The opened filehandle to watch for C<stat()> changes if C<filename> is not +supplied. + +=head2 filename => STRING + +Optional. If supplied, watches the named file rather than the filehandle given +in C<handle>. The file will be opened for reading and then watched for +renames. If the file is renamed, the new filename is opened and tracked +similarly after closing the previous file. + +=head2 interval => NUM + +Optional. The interval in seconds to poll the filehandle using C<stat(2)> +looking for size changes. A default of 2 seconds will be applied if not +defined. + +=cut + +sub _init +{ + my $self = shift; + my ( $params ) = @_; + + $params->{interval} ||= 2; + + $self->SUPER::_init( $params ); + + $self->start; +} + +sub configure +{ + my $self = shift; + my %params = @_; + + if( exists $params{filename} ) { + my $filename = delete $params{filename}; + $self->{filename} = $filename; + $self->_reopen_file; + } + elsif( exists $params{handle} ) { + $self->{handle} = delete $params{handle}; + $self->{last_stat} = stat $self->{handle}; + } + + foreach ( @STATS, "devino", "stat" ) { + $self->{"on_${_}_changed"} = delete $params{"on_${_}_changed"} if exists $params{"on_${_}_changed"}; + } + + $self->SUPER::configure( %params ); +} + +sub _add_to_loop +{ + my $self = shift; + + if( !defined $self->{filename} and !defined $self->{handle} ) { + croak "IO::Async::File needs either a filename or a handle"; + } + + return $self->SUPER::_add_to_loop( @_ ); +} + +sub _reopen_file +{ + my $self = shift; + + my $path = $self->{filename}; + + open $self->{handle}, "<", $path or croak "Cannot open $path for reading - $!"; + + $self->{last_stat} = stat $self->{handle}; +} + +sub on_tick +{ + my $self = shift; + + my $old = $self->{last_stat}; + my $new = stat( $self->{filename} // $self->{handle} ); + + my $any_changed; + foreach my $stat ( @STATS ) { + next if $old->$stat == $new->$stat; + + $any_changed++; + $self->maybe_invoke_event( "on_${stat}_changed", $new->$stat, $old->$stat ); + } + + if( $old->dev != $new->dev or $old->ino != $new->ino ) { + $self->maybe_invoke_event( on_devino_changed => $new, $old ); + $self->_reopen_file; + } + + if( $any_changed ) { + $self->maybe_invoke_event( on_stat_changed => $new, $old ); + $self->{last_stat} = $new; + } +} + +=head1 METHODS + +=cut + +=head2 $handle = $file->handle + +Returns the filehandle currently associated with the instance; either the one +passed to the C<handle> parameter, or opened from the C<filename> parameter. + +=cut + +sub handle +{ + my $self = shift; + return $self->{handle}; +} + +=head1 AUTHOR + +Paul Evans <leonerd@leonerd.org.uk> + +=cut + +0x55AA; |