I have now a module that I think would be nice to have in many situations and thus I'm considering a release on the CPAN. The problem is, I don't know if the name I have (until now) is a good name, or if I reinvented the wheel because I didn't find a module that scratched this itch.
The module provides a simple interface to determine if a set of files changed since the last time they were checked. This is mostly intended for long running programs (in my case, a daemon process) that need to reinitialize if their configuration files, their program code or the code of any module they depend on (hence the name) changes.
The code itself is written (and even has 6 tests, which I haven't included as not to bloat this node even further), but I'd like some feedback on it, things that could be much easier, ideas for other methods of file signatures etc. - also, documentation stuff would be very appreciated, including correction of tyops.
As you most likely won't bother to delve into the module without something to whet your appetite, here are the two examples out of the synopsis. The first example is a long running process that checks from time to time if its configuration files have changed and then takes "appropriate action" :
use strict;
use File::Dependencies;
my $d = File::Dependencies->new(Files=>['Import.cfg','Export.cfg']);
while (1) {
my (@changes) = $d->changed;
if (@changes) {
print "$_ was changed\n" for @changes;
$d->update();
};
sleep 60;
};
The second example is an example of another long running process that restarts if any file of its source code (minus required files and done files) changed. What I want to know here is, if the idea of restarting a process like this is a good one to have in the documentation ...
use strict;
use File::Dependencies;
my $files = File::Dependencies->new(Files=>[values %INC, $0]);
# We want to restart when any module was changed
exec $0, @ARGV if $files->changed();
As you've now read this far, you most likely also want to have a look at the module itself - here it comes :
package File::Dependencies;
#use 5.006; # shouldn't be necessary
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our $VERSION = '0.01';
sub new {
my ($class, %args) = @_;
my $method = $args{Method} || "MD5";
my $files = $args{Files} || [];
my $self = {
Defaultmethod => $method,
Files => {},
};
bless $self, $class;
$self->addfile($_) for @$files;
return $self;
};
sub adddependency {
my ($self,$filename,$method) = @_;
$method ||= $self->{Defaultmethod};
my $signatureclass = "Dependency::Signature::$method";
$self->{Files}->{$filename} = $signatureclass->new($filename);
};
sub addfile {
my ($self,@files) = @_;
$self->adddependency($_) for @files;
};
sub update {
my ($self) = @_;
$_->initialize() for values %{$self->{Files}};
};
sub changed {
my ($self) = @_;
return map {$_->{Filename}} grep {$_->changed()} (values %{$self->{F
+iles}});
};
1;
{
package Dependency::Signature;
# This is a case where Python would be nicer. With Python, we could
+have (paraphrased)
# class Dependency::Signature;
# def initialize(self):
# self.hash = self.identificate()
# return self
# def signature(self):
# return MD5(self.filename)
# def changed(self):
# return self.hash != self.signature()
# and it would work as expected, (almost) regardless of the structur
+e that is returned
# by self.signature(). This is some DWIMmery that I sometimes miss i
+n Perl.
# For now, only string comparisions are allowed.
sub new {
my ($class,$filename) = @_;
my $self = {
Filename => $filename,
};
bless $self, $class;
$self->initialize();
return $self;
};
sub initialize {
my ($self) = @_;
$self->{Signature} = $self->signature();
return $self;
};
sub changed {
my ($self) = @_;
my $currsig = $self->signature();
# FIXME: Deep comparision of the two signatures instead of equalit
+y !
# And what's this about string comparisions anyway ?
if ((ref $currsig) or (ref $self->{Signature})) {
die "Implementation error in $self : changed() can't handle refe
+rences (yet) !\n";
#return $currsig != $self->{Signature};
} else {
return $currsig ne $self->{Signature};
};
};
1;
};
{
package Dependency::Signature::mtime;
use base 'Dependency::Signature';
sub signature {
my ($self) = @_;
my @stat = stat $self->{Filename} or die "Couldn't stat '$self->{F
+ilename}' : $!";
return $stat[9];
};
1;
};
{
package Dependency::Signature::MD5;
use base 'Dependency::Signature';
use vars qw( $fallback );
BEGIN {
eval "use Digest::MD5;";
if ($@) {
#print "Falling back on Dependency::Signature::mtime\n";
$fallback = 1;
};
};
# Fall back on simple mtime check unless MD5 is available :
sub new {
my ($class,$filename) = @_;
if ($fallback) {
return Dependency::Signature::mtime->new($filename);
} else {
return $class->SUPER::new($filename);
};
};
sub signature {
my ($self) = @_;
my $result;
if (-e $self->{Filename} and -r $self->{Filename}) {
local *F;
open F, $self->{Filename} or die "Couldn't read from file '$self
+->{Filename}' : $!";
$result = Digest::MD5->new()->addfile(*F)->b64digest();
close F;
};
return $result;
};
1;
};
1;
__END__
=head1 NAME
File::Dependencies - Perl extension for detection of changed files.
=head1 SYNOPSIS
use strict;
use File::Dependencies;
my $d = File::Dependencies->new(Files=>['Import.cfg','Export.cfg']);
while (1) {
my (@changes) = $d->changed;
if (@changes) {
print "$_ was changed\n" for @changes;
$d->update();
};
sleep 60;
};
Second example - a script that knows when any of its modules have chan
+ged :
use File::Dependencies;
my $files = File::Dependencies->new(Files=>[values %INC, $0]);
# We want to restart when any module was changed
exec $0, @ARGV if $files->changed();
=head1 DESCRIPTION
The Dependencies module is intended as a simple method for programs to
+ detect
whether configuration files (or modules they rely on) have changed. Th
+ere are
currently two methods of change detection implemented, C<mtime> and C<
+MD5>.
The C<MD5> method will fall back to use timestamps if the C<Digest::MD
+5> module
cannot be loaded.
=over 4
=item new %ARGS
Creates a new instance. The C<%ARGS> hash has two possible keys,
C<Method>, which denotes the method used for checking as default,
and C<Files>, which takes an array reference to the filenames to
watch.
=item adddependency filename, method
Adds a new file to watch. C<method> is the method (or rather, the
subclass of C<Dependency::Signature>) to use to determine whether
a file has changed or not.
=item addfile LIST
Adds a list of files to watch. The method used for watching is t1he
default method as set in the constructor.
=item update
Updates all signatures to the current state. All pending changes
are discarded.
=item changed
Returns a list of the filenames whose files did change since
the construction or the last call to C<update> (whichever last
occurred).
=back
=head2 Adding new methods for signatures
Adding a new signature method is as simple as creating a new subclass
of C<Dependency::Signature>. See C<Dependency::Signature::MD5> for a s
+imple
example. There is one point of lazyness in the implementation of C<Dep
+endency::Signature>,
the C<check> method can only compare strings instead of arbitrary stru
+ctures (yes,
there ARE things that are easier in Python than in Perl).
=head2 EXPORT
None by default.
=head1 AUTHOR
Max Maischein, E<lt>corion@informatik.uni-frankfurt.deE<gt>
=head1 SEE ALSO
L<perl>,L<Digest::MD5>.
=cut
perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The
$d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider
($c = $d->accept())->get_request(); $c->send_response( new #in the
HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web