blob: c8c11beab91be1ffe0314c9be49c932a7ce326a4 [file] [log] [blame]
package WebDep;
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use strict;
use warnings;
use WWW::Curl::Easy;
use Digest::MD5 qw{md5 md5_hex md5_base64};
use File::Path;
use File::Basename qw{dirname};
use IO::Uncompress::Unzip qw{$UnzipError};
use Data::Dumper;
use Cwd qw{getcwd abs_path};
# columns from web_deps.txt in order
my @columns = qw{name version cdn_location compression filename source_dir final_dir};
# class method returns list of WebDep objects loaded from file
sub getDeps {
my $class = shift;
my $webdeps_file = shift;
my @deps;
open my $fh, '<', $webdeps_file or die "Can't open $webdeps_file\n";
# final_dir within webdeps_file are absolute or relative to directory that file is in
my $oldcwd = getcwd();
my $webdeps_dir = dirname($webdeps_file);
chdir($webdeps_dir);
while (<$fh>) {
# comments only if line starts with #
next if /^#/;
chomp;
my @parts = split( /\s*,\s*/, $_ );
my $obj = bless {}, $class;
for my $attrib (@columns) {
$obj->{$attrib} = shift @parts;
}
$obj->{filename} =~ s{^/}{};
$obj->{source_dir} =~ s{/$}{};
$obj->{source_dir} =~ s{/$}{};
my $final_dir = $obj->{final_dir};
if ( !-d $final_dir ) {
print "Making dir: $final_dir\n";
mkpath($final_dir);
}
$obj->{final_dir} = abs_path($final_dir);
push( @deps, $obj );
}
chdir($oldcwd);
return @deps;
}
sub getSrcFileName {
my $self = shift;
my @parts;
push @parts, $self->{source_dir} if $self->{source_dir} ne '';
push @parts, $self->{filename};
return join( '/', @parts );
}
sub getDestFileName {
my $self = shift;
return join( '/', $self->{final_dir}, $self->{filename} );
}
sub getDownloadContent {
my $self = shift;
my ( $response_body, $err ) = _getDownloadContent( $self->{cdn_location} );
return ( $response_body, $err );
}
sub getContent {
my $self = shift;
my ( $content, $err ) = $self->getDownloadContent();
if ( defined $err ) {
die "$err\n";
}
my $srcfn = $self->getSrcFileName();
if ( !exists $self->{content} ) {
if ( $self->{compression} eq 'zip' ) {
#print "Unzipping $srcfn\n";
my $u = IO::Uncompress::Unzip->new( \$content ) or die "IO::Uncompress::Unzip failed: $UnzipError\n";
my $found;
while ( $u->nextStream() > 0 && !$u->eof() ) {
my $name = $u->getHeaderInfo()->{Name};
if ( $name eq $srcfn ) {
$found = $name;
last;
}
}
if ( !defined $found ) {
die "$srcfn not found in " . $self->{cdn_location} . "\n";
}
undef $/; # slurp mode
$content = <$u>;
$u->close();
}
$self->{content} = $content;
}
return $self->{content};
}
sub needsUpdating {
my $self = shift;
my $fn = $self->getDestFileName();
# checksum and compare
open my $fh, '<', $fn or die "Can't open existing file: $fn\n";
my $md5_existing = md5_hex(<$fh>);
close $fh;
my $md5_new = md5_hex( $self->getContent() );
my $needsUpdating = ( $md5_new ne $md5_existing );
return $needsUpdating;
}
sub update {
my $self = shift;
my $err;
my $srcfn = $self->getSrcFileName();
my $destfn = $self->getDestFileName();
my $action = "";
# download archive
if ( -f $destfn ) {
if ( !$self->needsUpdating() ) {
$action = "Kept";
return ( $action, $err );
}
# exists but needs to be replaced
$action = "Replaced";
}
else {
$action = "Created";
}
my $content = $self->getContent();
open my $ofh, '>', $destfn or $err = "Can't write to $destfn";
if ( !defined $err ) {
print $ofh $content;
close $ofh;
}
return ( $action, $err );
}
####################################################################
# Utilities
sub execCommand {
my ( $command, @args ) = @_;
my $pid = fork();
my $result = 0;
if ( $pid == 0 ) {
exec( $command, @args );
exit 0;
}
else {
wait;
$result = $?;
if ( $result != 0 ) {
print "ERROR executing: $command, args: " . join( ' ', @args ) . "\n";
}
}
return $result;
}
sub curlMe {
my $url = shift;
my $curl = WWW::Curl::Easy->new;
my $response_body;
my $err; # undef if no error
$curl->setopt( CURLOPT_VERBOSE, 0 );
if ( $url =~ m/https/ ) {
$curl->setopt( CURLOPT_SSL_VERIFYHOST, 0 );
$curl->setopt( CURLOPT_SSL_VERIFYPEER, 0 );
}
$curl->setopt( CURLOPT_IPRESOLVE, 1 );
$curl->setopt( CURLOPT_FOLLOWLOCATION, 1 );
$curl->setopt( CURLOPT_CONNECTTIMEOUT, 5 );
$curl->setopt( CURLOPT_TIMEOUT, 15 );
$curl->setopt( CURLOPT_HEADER, 0 );
$curl->setopt( CURLOPT_FOLLOWLOCATION, 1 );
$curl->setopt( CURLOPT_URL, $url );
$curl->setopt( CURLOPT_WRITEDATA, \$response_body );
my $retcode = $curl->perform;
my $response_code = $curl->getinfo(CURLINFO_HTTP_CODE);
if ( $response_code != 200 ) {
$err = "Got HTTP $response_code response for '$url'";
}
elsif ( length($response_body) == 0 ) {
$err = "URL: $url returned empty!!";
}
return ( $response_body, $err );
}
{
# cache cdn locs -- some files extracted from same downloaded archive
my %content_for;
sub _getDownloadContent {
my $cdnloc = shift;
my $err;
if ( !exists $content_for{$cdnloc} ) {
my $response_body;
( $response_body, $err ) = curlMe($cdnloc);
$content_for{$cdnloc} = $response_body;
}
# could be undef indicating previous error
return ( $content_for{$cdnloc}, $err );
}
}
1;