package Mirror::URI; use 5.006; use strict; use Carp (); use File::Spec (); use Time::HiRes (); use Time::Local (); use URI (); use URI::file (); use URI::http (); use Params::Util qw{ _STRING _POSINT _ARRAY0 _INSTANCE }; use LWP::Simple (); # Time values have an extra 5 minute fudge factor use constant ONE_DAY => 86700; use constant TWO_DAYS => 172800; use constant THIRTY_DAYS => 2592000; use vars qw{$VERSION}; BEGIN { $VERSION = '0.90'; } ##################################################################### # Constructor and Accessors sub new { my $class = shift; my $self = bless { @_ }, $class; # Clean up params $self->{class} = $class; $self->{valid} = !! $self->valid; if ( $self->valid ) { if ( _STRING($self->master) ) { $self->{master} = URI->new( $self->master ); } unless ( _INSTANCE($self->master, 'URI') ) { Carp::croak("Missing or invalid 'master' value"); } if ( _STRING($self->{timestamp}) and ! _POSINT($self->{timestamp}) ) { unless ( $self->{timestamp} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/ ) { Carp::croak("Invalid timestamp format"); } $self->{timestamp} = Time::Local::timegm( $6, $5, $4, $3, $2 - 1, $1 ); } if ( $self->{filename} and $self->{filename} ne $self->filename ) { Carp::croak("Invalid or unsupported offset '$self->{filename}'"); } my $mirrors = $self->{mirrors}; unless ( _ARRAY0($mirrors) ) { croak("Invalid mirror list"); } foreach my $i ( 0 .. $#$mirrors ) { next unless _STRING($mirrors->[$i]); $mirrors->[$i] = URI->new( $mirrors->[$i] ); } } return $self; } sub filename { my $class = ref($_[0]) || $_[0]; die("$class does not implement filename"); } sub class { $_[0]->{class}; } sub version { $_[0]->{version}; } sub uri { $_[0]->{uri}; } sub name { $_[0]->{name}; } sub master { $_[0]->{master}; } sub timestamp { $_[0]->{timestamp}; } sub mirrors { return ( @{ $_[0]->{mirrors} } ); } sub valid { $_[0]->{valid}; } sub lastget { $_[0]->{lastget}; } sub lag { $_[0]->{lag}; } sub age { $_[0]->{lastget} - $_[0]->{timestamp}; } sub as_string { $_[0]->uri->as_string; } sub is_cached { $_[0]->uri->isa('URI::file'); } sub is_master { my $self = shift; return ( ! $self->valid and $self->as_string eq $self->uri->as_string ); } ##################################################################### # Load Methods sub read { my $class = shift; # Check the file to read my $root = shift; unless ( defined _STRING($root) and -d $root ) { Carp::croak("Directory '$root' does not exist"); } # Convert to a usable URI my $uri = URI::file->new( File::Spec->canonpath( File::Spec->rel2abs($root) ) )->canonical; # In a URI a directory must have an explicit trailing slash $uri->path( $uri->path . '/' ); # Hand off to the URI fetcher return $class->get( $uri, dir => $root, @_ ); } sub get { my $class = shift; # Check the URI my $base = shift; unless ( _INSTANCE($base, 'URI') ) { Carp::croak("Missing or invalid URI"); } unless ( $base->path =~ /\/$/ ) { Carp::croak("URI must have a trailing slash"); } # Find the file within the root path my %self = ( uri => URI->new($class->filename)->abs($base)->canonical, ); # Pull the file and time it $self{lastget} = Time::HiRes::time; $self{string} = LWP::Simple::get($self{uri}); $self{lag} = Time::HiRes::time - $self{lastget}; unless ( defined $self{string} ) { return $class->new( %self, valid => 0 ); } # Parse the file my $hash = $class->parse( $self{string} ); unless ( ref $hash eq 'HASH' ) { return $class->new( %self, valid => 0 ); } $class->new( %$hash, %self, valid => 1 ); } ##################################################################### # Populate Elements sub get_master { my $self = shift; if ( _INSTANCE($self->master, 'URI') ) { # Load the master my $master = $self->class->get($self->master); $self->{master} = $master; } return $self->master; } sub get_mirror { my $self = shift; my $i = shift; my $uri = $self->{mirrors}->[$i]; unless ( defined $uri ) { Carp::croak("No mirror with index $i"); } if ( _INSTANCE($uri, 'URI') ) { my $mirror = $self->class->get($uri); $self->{mirrors}->[$i] = $mirror; } return $self->{mirrors}->[$i]; } ##################################################################### # High Level Methods sub update { my $self = shift; # Handle various shortcuts unless ( $self->valid ) { Carp::croak("Cannot update invalid mirror"); } if ( $self->is_master ) { return 1; } # Pull the master and overwrite ourself with it my $master = $self->get_master; unless ( _INSTANCE($master, $self->class) ) { Carp::croak("Failed to fetch master record"); } # Overwrite the current version with the master foreach ( qw{ version uri name lastget timestamp mirrors lag valid master } ) { $self->{$_} = delete $master->{$_}; } return 1; } # Get all the mirrors sub get_mirrors { my $self = shift; my $mirrors = $self->{mirrors}; foreach ( 0 .. $#$mirrors ) { $self->get_mirror($_); } return 1; } 1; __END__ =pod =head1 NAME Mirror::URI - Mirror Configuration and Auto-Discovery =head1 DESCRIPTION B is an abstract base class for the mirror auto-discovery modules L and L. See their documentation for more details. =head1 SUPPORT Bugs should be reported via the CPAN bug tracker at L For other issues, or commercial enhancement or support, contact the author. =head1 AUTHOR Adam Kennedy Eadamk@cpan.orgE =head1 SEE ALSO L, L, L =head1 COPYRIGHT Copyright 2007 - 2009 Adam Kennedy. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The full text of the license can be found in the LICENSE file included with this module. =cut