# $Id$ # TeXLive::TLDownload.pm - module for abstracting the download modes # Copyright 2009 Norbert Preining # # This file is licensed under the GNU General Public License version 2 # or any later version. # # TODO: # - retry: currently we do not retry if one get did not succeed, but # return immediately 0. TLUtils::download_file will then try wget mode. # we should have retrials here, too # - re-opening of a dropped connection, detection of dropped connection? # - timeout for ftp: default is AFAIR 120sec, that is probably not enough # for a normal user to select all options and go press/enter "i"nstall # either extend timeout or care for reopening of closed connections package TeXLive::TLDownload; use TeXLive::TLUtils; my $svnrev = '$Revision$'; my $_modulerevision; if ($svnrev =~ m/: ([0-9]+) /) { $_modulerevision = $1; } else { $_modulerevision = "unknown"; } sub module_revision { return $_modulerevision; } # since Net::HTTP and Net::FTP are shipped by the same packages # we only test for Net::HTTP, if that fails, let us know ;-) my $net_lib_avail = 0; eval { require Net::HTTP; }; if ($@) { debug("Net::HTTP is not available, falling back to wget mode.\n"); $net_lib_avail = 0; } else { require Net::HTTP; require Net::FTP; require HTTP::Status; $net_lib_avail = 1; debug("Net::HTTP available!\n"); } sub new { my $class = shift; my %params = @_; my $self = { server => $params{'server'}, mode => defined($params{'mode'}) ? $params{'mode'} : "wget", }; if ($self->{'mode'} eq "http" || $self->{'mode'} eq "ftp") { die "Net::HTTP and Net::FTP is not available, cannot use http/ftp mode." if (!$net_lib_avail); } elsif ($self->{'mode'} eq "wget") { # noting to be done } else { die "Unknown download mode: ", $self->{'mode'}; } bless $self, $class; return $self; } sub start_connection { my $self = shift; if ($self->mode eq "wget") { debug("TLDownload::start_connection: no need to call start_connection in wget mode\n"); return 1; } if (!defined($self->server)) { die "TLDownload::start_connection: No server defined, cannot start connection."; } if ($self->mode eq "http") { my $handle = Net::HTTP->new(Host => $self->server, KeepAlive => 1); if (!$handle) { tlwarn("TLDownload::start_connection: Could not connect to server " . $self->server . "\n"); return 0; } # now we got a handle, save it simply into $self for later reuse $self->{'handle'} = $handle; return 1; } if ($self->mode eq "ftp") { my $handle = Net::FTP->new($self->server, Debug => 0); if (!$handle) { tlwarn("TLDownload::start_connection: Could not connect to server " . $self->server . "\n"); return 0; } if (!$handle->login("anonymous",'-texlive@')) { tlwarn("TLDownload::start_connection: Cannot login to server: " . $handle->message . "\n"); return 0; } if (!$handle->binary) { tlwarn("TLDownload::start_connection: Cannot set transfer mode to binary: " . $handle->message . "\n"); return 0; } # now we got a handle, save it simply into $self for later reuse $self->{'handle'} = $handle; return 1; } # we are still here, that cannot be return 0; } sub get_file { my ($self, $url, $out, $size) = @_; # # first split the $url into components. we might get a full url # or some relative path my $protocol; my $server; my $path; if ($url =~ m!^(http|ftp)://([^/]*)(/.*)$!i) { $protocol = lc($1); $server = lc($2); $path = $3; } elsif ($url =~ m!^/.*$!) { $path = $url; } else { tlwarn("TLDownload::get_file: do not understand that url: $url\n"); return 0; } # # wget case # simple fall back to TLUtils::_download_file # if ($self->mode eq "wget") { if (!defined($protocol)) { tlwarn("TLDownload::get_file: cannot download relative url $url in wget mode.\n"); return 0; } my $wget; if (defined($::progs{'wget'})) { $wget = $progs->{'wget'}; } else { tlwarn ("TLDownload::get_file: programs not set up, trying literal wget\n"); $wget = "wget"; } return TeXLive::TLUtils::_download_file($url, $out, $wget); } # # check for incompatible server names or protocols # if (defined($protocol) && ($protocol ne $self->mode)) { tlwarn("TLDownload::get_file: protocols mismatch, url uses $protocol, server " . $self->mode . ".\n"); return 0; } if (defined($server) && ($server ne lc($self->server))) { tlwarn("TLDownload::get_file: server mismatch, url uses $server, but current download server is " . $self->server . ".\n"); return 0; } # # ftp mode # if ($self->mode eq "ftp") { if (!defined($self->{'handle'})) { if (!$self->start_connection) { # start_connection already warns ... return 0; } } # now we should have a $handle my $handle = $self->{'handle'}; if (!$handle->get($path, $out)) { tlwarn("TLDownload::get_file: Cannot get $path: " . $handle->message . "\n"); return 0; } return 1; } # # http mode # if ($self->mode eq "http") { if (!defined($self->{'handle'})) { if (!$self->start_connection) { # start_connection already warns ... return 0; } } # now we should have a $handle my $handle = $self->{'handle'}; if (! $handle->write_request(GET => $path, 'User-Agent' => 'texlive (net::http)')) { tlwarn("TLDownload::get_file: Cannot send GET request.\n"); return 0; } my ($code, $msg, %h) = $handle->read_response_headers; if (HTTP::Status::is_error($code)) { tlwarn("Problem downloading from server: $msg\n"); return 0; } my $buffer = ""; my $bytes_read = 0; while (1) { my $buf; my $n = $handle->read_entity_body($buf, (defined($size) ? $size : 4096)); if (!defined($n)) { tlwarn("TLDownload::get_file: Cannot read file from server: $!\n"); return 0; } last unless $n; dddebug("- http read got $n bytes\n"); $bytes_read += $n; $buffer .= $buf; } if (defined($size) && ($bytes_read != $size)) { tlwarn("TLDownload::get_file: didn't get $size bytes, expect breakage\n"); } if (!open (OUT, ">$out")) { tlwarn("TLDownload::get_file: cannot open $out for writing: $!\n"); return 0; } binmode(OUT); print OUT $buffer; if (!close(OUT)) { tlwarn("TLDownload::get_file: strange, cannot close file handle ...\n"); } return 1; } # # we are still here, strange # tlwarn("TLDownload::get_file: still around, strange, mode = " . $self->mode . "\n"); return 0; } sub mode { my $self = shift; if (@_) { $self->{'mode'} = shift } return $self->{'mode'}; } sub server { my $self = shift; if (@_) { $self->{'server'} = shift } return $self->{'server'}; } 1; __END__ =head1 NAME C -- TeX Live Download abstraction module =head1 SYNOPSIS use TeXLive::TLDownload; $TeXLive::TLDownload::net_lib_avail my $dl = TeXLive::TLDownload->new(server => "www.foo.com", mode => "http"); my $dl = TeXLive::TLDownload->new(server => "www.foo.com", mode => "ftp"); my $dl = TeXLive::TLDownload->new(server => "http://www.foo.com", mode => "wget"); $dl->start_connection; $dl->get_file($relpath, $output [, $expected_size ]); =head1 DESCRIPTION The C module handles TeX Live Download abstractions to make it possible to use persistent network connections instead of calling wget for each single file. It depends on the modules Net::HTTP and Net::FTP, and will check for these at loading time. If they are not present only "wget" mode will be available. =head1 SEE ALSO =head1 AUTHORS AND COPYRIGHT This script and its documentation were written for the TeX Live distribution (L) and both are licensed under the GNU General Public License Version 2 or later. =cut ### Local Variables: ### perl-indent-level: 2 ### tab-width: 2 ### indent-tabs-mode: nil ### End: # vim:set tabstop=2 expandtab: #