###
###  Copyright 2000-2007 University of Illinois Board of Trustees
###  All rights reserved. 
###
###  PSGConf::Action::PackageManager::FTPArchive - General tools to use to get package info from an FTP Archive.
###
###
###  Campus Information Technologies and Educational Services
###  University of Illinois at Urbana-Champaign
###


package PSGConf::Action::PackageManager::FTPArchive;

use strict;

use PSGConf::Action::PackageManager;
use PSGConf::Data::Boolean;
use PSGConf::Data::Integer;
use PSGConf::Data::String;

use PSGConf::Util;
use File::Basename;
use Net::FTP;

our @ISA = qw(PSGConf::Action::PackageManager);

###############################################################################
###  configuration constructor
###############################################################################

sub conf
{
     my ($class, $psgconf) = @_;
     my ($self);

     $self = {};
     bless($self, $class);

     $psgconf->register_data(
          pkg_ftp_passive_enable     => PSGConf::Data::Boolean->new(
                              value => 0
                         ),
          pkg_ftp_timeout     => PSGConf::Data::Integer->new(
                              value => 120
                         ),
          pkg_ftp_list_method     => PSGConf::Data::String->new(
                              value => "ls"
                         )
	);

     return $self;
}

sub get_latest_version 
{
     my ($self, $psgconf, $pkg, $path, $regex) = @_;
     my (@files);
     my ($version) = '';

	# Return right away if we do not have all the information we need.
	return undef
		if ( ! length ($path) ||
			! length ($pkg) ||
			! length ($regex) );

     @files = $self->list_files_from_url($psgconf, $path, $regex);

	grep (/$regex/ &&
			($version=($self->ComparePkgs($version, $1)<0)? $1: $version),
		@files);

	### Now trim off the pkg name from the version.
	$version =~ s/^\Q${pkg}\E$self->{seperator}->[0]//;

     warn "\t* no updates found for package $pkg\n"
          if ( $psgconf->{verbose} && $version eq '' );

     return $version;
}

sub list_files_from_url
{
	my ($self, $psgconf, $path, $regex) = @_;
	my (@files, $url);

	# Return right away if we do not have all the information we need.
	return @files if ( ! length ($path) );

	foreach $url (split (/[\\\s]+/o, $path)) {

		grep (/^($regex)/ && (push @files, $url . $1),
			$self->_get_directory_listing($psgconf, $url));

		last if ( scalar @files );
	}

	return @files;
}

sub _parse_url {
	my ($url) = @_;
	my ($host, $dir, $file);

	grep (/ftp:\/\/([^\/]+)(.*\/)([^\/]+)?$/o 
		&& ($host=$1, $dir=$2, $file=$3),
		$url);

	return ($host, $dir, $file);
}

sub _get_directory_listing {
	my ($self, $psgconf, $url) = @_;
	my ($ftp, $cache, $method);
	local *FP;

	$cache = $url;
	$cache =~ s,/,::,g;

	$method = $psgconf->data_obj('pkg_ftp_list_method')->get();

	if ( ! defined $self->{$cache} ) {
		if (($ftp = &_go_2_ftpdir ($psgconf, $url))) {
			if ( ! ( @{$self->{$cache}} = $ftp->$method ) ) {
				warn "\tCould not read dir ($url)\n\t\t" . $ftp->message;
				return undef;
			}

			$ftp->quit;

			# parse out the last element in the ftp listing
			# (should be the filename).
			map { $_ =~ s/^.* //; } @{$self->{$cache}};
		}
	}

	return @{$self->{$cache}};
}

sub get_file_from_url {
	my ($self, $psgconf, $url) = @_;
	my ($file, $ftp);

	return undef if ( ! length ( $url ));

	$file = &basename($url);

	if (($ftp = &_go_2_ftpdir ($psgconf, $url))) {

		# Fetch the file.
		if ( ! $ftp->get($file, $psgconf->{tmpdir} . '/' . $file) ) {
			warn "Failed to get ($file) ", $ftp->message;
			$file = undef;
		}

		$ftp->quit;
	}

	return $file;
}

sub _go_2_ftpdir {
	my ($psgconf, $url) = @_;
	my ($host, $dir, $ftp);

	($host, $dir) = &_parse_url($url);

	if (($ftp=Net::FTP->new($host,
						Passive => $psgconf->data_obj('pkg_ftp_passive_enable')->get(),
						Timeout => $psgconf->data_obj('pkg_ftp_timeout')->get()))) {
		if ( $ftp->login( "ftp", "psgconf@" . $psgconf->data_obj('hostname')->get() )) {
			if ( $ftp->cwd($dir) ) {
				if ( ! $ftp->binary() ) {
					warn "\tCould not covert to binary mode\n\t\t" . $ftp->message;
					return undef;
				} 
			} else {
				warn "\tCould not cd\n\t\t" . $ftp->message;
				return undef;
			}
		} else {
			warn "\tCould not login\n\t\t" . $ftp->message;
			return undef;
		}
	} else {
		warn "\tCould not open ftp session to ($url) $@\n";
		return undef;
	}

	return $ftp;
}

###############################################################################
###  documentation
###############################################################################

1;

__END__

=head1 NAME

PSGConf::Action::PackageManager::FTPArchive - General tools to use to get package info from an FTP Archive.

=head1 SYNOPSIS

  use PSGConf::Action::PackageManager::FTPArchive;

  $psgconf->register_actions(
		PSGConf::Action::PackageManager::FTPArchive->new(
			'quiet'		=> 1
		),
		...
	);

=head1 DESCRIPTION

The B<PSGConf::Action::PackageManager::FTPArchive> module provides a B<PSGConf>
action class for updating packages.

The B<PSGConf::Action::PackageManager::FTPArchive> class is derived from the
B<PSGConf::Action::PackageManager> class, but it defines/overrides the 
following methods:

=over 4

=item I<get_latest_version($self, $psgconf, $pkg, $path, $regex)>

Find the latest version of a package from FTP Archive(s).

=item I<list_files_from_url($self, $psgconf, $path, $regex)>

Given an ftp list of ftp URLs, list all files that match the given regex.

=item I<get_file_from_url($self, $url)>

Download the URL given, file will be stored in C<psgconf>'s tmpdir.

=back

The constructor also registers the following data objects:

=over 4

=item I<pkg_ftp_passive_enable>

A B<PSGConf::Data::Boolean> object that determines if L<Net::FTP> will
use a Passive FTP connection.  Defaults to C<false>.

=item I<pkg_ftp_timeout>

A B<PSGConf::Data::Integer> object that determines how many seconds
L<Net::FTP> will use FTP connection timeout.  Defaults to C<120>.

=item I<pkg_ftp_list_method>

A B<PSGConf::Data::String> object that determines what Net::FTP method
to call to list the contents of a directory.  The default is C<ls>.  The
assumption is that the last element of each line is the filename.

=back

=head1 SEE ALSO

L<perl>

L<PSGConf>

L<PSGConf::Action>

L<PSGConf::Action::PackageManager>

L<PSGConf::Data::Boolean>

L<PSGConf::Data::Integer>

L<PSGConf::Data::String>

L<PSGConf::Util>

L<Net::FTP>

L<File::Basename>

=cut



syntax highlighted by Code2HTML, v. 0.9.1