#!/usr/bin/perl -w

# "harte" Perl-Regeln
use strict;

# für gettimeofday() (gettimeofday() ist mikrosekunden-genau)
use Time::HiRes qw(gettimeofday);

# für die Kommunikations mit dem HTTP-Server
use LWP::UserAgent;
use HTTP::Request;
use HTML::Entities;

# für GetOptions() (Kommandozeilenverarbeitung)
use Getopt::Long;

my $opts = {
  'base-url' => 'http://rain/artikel/site/', # Basis-URL
  'send-if-modified-since' => 0,             # Switch, ob wir If-Last-Modified verwenden sollen
  'send-if-none-match' => 0,                 # Switch, ob wir If-None-Match verwenden sollen
  'help' => \&help
};

# Optionen einlesen
GetOptions(
  $opts,
  'base-url=s',
  'send-if-modified-since',
  'send-if-none-match',
  'help'
);

my $time = 0;    # Zeit, die wir für den Request gebraucht haben
my $down = {};   # Verzeichnis von URLs, die wir bereits geladen haben

# Das UserAgent-Objekt, dass unsere Anfragen an den HTTP-Server stellt
my $ua = new LWP::UserAgent;

# Nun führe bitte auch den Request durch... die Rückgabe brauchen wir
# zum filtern der URLs (wir wollen schliesslich alles zum Rendern des
# Dokumentes Notwendige herunterladen)
my $str = do_request($ua,$opts->{'base-url'},$down,$opts,\$time);

# ausserdem brauchen wir die Basis-URL, in der *nur* der
# Verzeichnisname ist
my $baseurl = $opts->{'base-url'};
$baseurl =~ s!/[^/]+$!/!g;

# durchsuche alle src-Attribute (Bilder, Scripte, etc)
while($str =~ m!src=(?:(?:(['"])(.+?))|(\S+))\1!ig) {
  my $uri = $2||$3;
  $uri    = $baseurl.$uri unless $uri =~ m!^http://!;
  $uri    =~ s/#.+$//;
  $uri    = decode_entities($uri);
  $uri    =~ s!([^:])//!$1/!g;

  do_request($ua,$uri,$down,$opts,\$time);
}

# durchsuche alle <link>-Attribute, die ein Stylesheet referenzieren
while($str =~ m!<link[^>]+rel=["']stylesheet["'][^>]+href=(?:(?:(["'])(.+?)\1)|(\S+))!ig) {
  my $uri = $2||$3;
  $uri    = $baseurl.$uri unless $uri =~ m!^http://!;
  $uri    =~ s/#.+$//;
  $uri    = decode_entities($uri);
  $uri    =~ s!([^:])//!$1/!g;

  do_request($ua,$uri,$down,$opts,\$time);
}

# gib eine Meldung an den User
print sprintf("\n".'Time elapsed: %5.6f seconds'."\n",$time/(10**6));

# Hilfetext
sub help {
  print <<USAGE;
Usage:
  $0 [Options]

Available options are:
  --base-url url           Define which URL will be requested
  --send-if-modified-since If set, a If-Last-Modified header will be send
  --send-if-none-match     If set, a If-None-Match header will be send

USAGE
  exit;
}

sub do_request {
  my $ua   = shift; # User-Agent-Objekt
  my $uri  = shift; # URI, die angefordert werden soll
  my $down = shift; # Download-Verzeichnis
  my $opts = shift; # Kommandozeilen-Optionen
  my $time = shift; # Referenz auf die Zeit-Variable

  my $re_send = 0;  # Status-Variable

  my (@td1,@td2);   # Zum speichern der Zeit vor und nach dem Request

  # ist die Datei bereits heruntergeladen worden, brauchen wir
  # sie natürlich kein zweites mal.
  return if $down->{$uri};

  # Datei im Katalog registrieren
  $down->{$uri}++;

  # Status-Meldung für den User
  print "Getting $uri...\n";

  my $rq = new HTTP::Request('GET',$uri);

  # So, wir brauchen die Zeit von vor dem Request (möglichst genau;
  # gettimeofday liefert die Sekunden seit dem 1.1. 0:00 1970 plus
  # die Mikrosekunden der angebrochenen Sekunde) und die Zeit von
  # nach dem Request. Zeit vorher - Zeit nachher gibt dann die
  # Uebertragunszeit plus einen vernachlässigbaren Analyse-Overhead.
  @td1 = gettimeofday();
  my $rsp = $ua->request($rq);
  @td2 = gettimeofday();

  # Wenn wir If-Modified-Since schicken sollten, so brauchten wir den
  # ersten Request, um das Änderungsdatum zu bekommen. Das heisst, dergleiche
  # Request muss noch einmal getätigt werden, nur mit If-Modified-Since-Header.
  if($opts->{'send-if-modified-since'}) {
    if($rsp->header('Last-Modified')) {
      $rq->header('If-Last-Modified',$rsp->header('Last-Modified'));
      $re_send = 1;
    }
  }

  # Dasselbe gilt für If-None-Match. Soll dieser Header geschickt werden,
  # muss der aktuelle Request wiederholt werden, mit einem ausgefüllten
  # If-None-Match-Header
  if($opts->{'send-if-none-match'}) {
    if($rsp->header('ETags')) {
      $rq->header('If-None-Match',$rsp->header('ETags'));
      $re_send = 1;
    }
  }

  # Siehe oben...
  if($re_send) {
    @td1 = gettimeofday();
    $rsp = $ua->request($rq);
    @td2 = gettimeofday();
  }

  # Verbrauchte Übertragunszeit = alte Zeit + neue Zeit
  $$time += timediff(@td2,@td1);

  # uU brauchen wir den Inhalt des Dokumentes -- aber bitte ohne
  # JavaScript!
  my $txt = $rsp->content;
  $txt =~ s!<script([^>]*)>.*?</script>!<script$1></script>!sig;

  return $txt;
}

# Zum errechnen der Differenz zwischen zwei
# Zeiten
sub timediff {
  my $s1 = shift;
  my $m1 = shift;
  my $s2 = shift;
  my $m2 = shift;

  my $diff = ($s1 * 1000000 + $m1) - ($s2 * 1000000 + $m2);
  die 'Sorry? Time diff is '.$diff if $diff < 0;
  $diff;
}

# eof

