
package Apache::ASP;

# quickly decomped out of Apache::ASP just to optionally load
# it at runtime for CGI programs ( which shouldn't need it anyway )
# will still precompile this for mod_perl

use strict;
use vars qw( $StatINCReady $StatINCInit %Stat $StatStartTime );

$StatStartTime = time();

# Apache::StatINC didn't quite work right, so writing own
sub StatINCRun {
    my $self = shift;
    my $stats = 0;

    # include necessary libs, without nice error message...
    # we only do this once if successful, to speed up code a bit,
    # and load success bool into global. otherwise keep trying
    # to generate consistent error messages
    unless($StatINCReady) {
	my $ready = 1;
	for('Devel::Symdump') {
	    eval "use $_";
	    if($@) {
		$ready = 0;
		$self->Error("You need $_ to use StatINC: $@ ... ".
			     "Please download it from your nearest CPAN");
	    }
	}
	$StatINCReady = $ready;
    }
    return unless $StatINCReady;
    
    # make sure that we have pre-registered all the modules before
    # this only happens on the first request of a new process
    unless($StatINCInit) {
	$StatINCInit = 1;
	$self->Debug("statinc init");
	$self->StatRegisterAll();	
    }

    while(my($key,$file) = each %INC) {
	if($self->{stat_inc_match} && defined $Stat{$file}) {
	    # we skip only if we have already registered this file
	    # we need to register the codes so we don't undef imported symbols
	    next unless ($key =~ /$self->{stat_inc_match}/);
	}

	next unless (-e $file); # sometimes there is a bad file in the %INC
	my $mtime = (stat($file))[9];

	# its ok if this block is CPU intensive, since it should only happen
	# when modules get changed, and that should be infrequent on a production site
	if(! defined $Stat{$file}) {
	    $self->{dbg} && $self->Debug("loading symbols first time", { $key => $file});
	    $self->StatRegister($key, $file, $mtime);	    
	} elsif($mtime > $Stat{$file}) {
	    $self->{dbg} && $self->Debug("reloading", {$key => $file});
	    $stats++; # count files we have reloaded
	    $self->StatRegisterAll();
	    
	    # we need to explicitly re-register a namespace that 
	    # we are about to undef, in case any imports happened there
	    # since last we checked, so we don't delete duplicate symbols
	    $self->StatRegister($key, $file, $mtime);

	    my $class = &File2Class($key);
	    my $sym = Devel::Symdump->new($class);

	    my $function;
	    my $is_global_package = $class eq $self->{GlobalASA}{'package'} ? 1 : 0;
	    for $function ($sym->functions()) { 
		my $code = \&{$function};

		if($function =~ /::O_[^:]+$/) {
		    $self->{dbg} && $self->Debug("skipping undef of troublesome $function");
		    next;
		}

		if($Apache::ASP::Codes{$code}{count} > 1) {
		    $self->{dbg} && $self->Debug("skipping undef of multiply defined $function: $code");
		    next;
		}	       

		if($is_global_package) {
		    # skip undef if id is an include or script 
		    if($function =~ /::__ASP_/) {
			$self->{dbg} && $self->Debug("skipping undef compiled ASP sub $function");
			next;
		    }

		    if($Apache::ASP::ScriptSubs{$function}) {
			$self->{dbg} && $self->Debug("$function is a script sub");
			next;
		    }

		}

		$self->{dbg} && $self->Debug("undef code $function: $code");

		undef(&$code); # method for perl 5.6.1
		delete $Apache::ASP::Codes{$code};
		undef($code);  # older perls
	    }

	    # extract the lib, just incase our @INC went away
	    (my $lib = $file) =~ s/$key$//g;
	    push(@INC, $lib);

	    # don't use "use", since we don't want symbols imported into ASP
	    delete $INC{$key};
	    $self->Debug("loading $key with require");
	    eval { require($key); }; 
	    if($@) {
		$INC{$key} = $file; # make sure we keep trying to reload it
		$self->Error("can't require/reload $key: $@");
		next;
	    }

	    # if this was the same module as the global.asa package,
	    # then we need to reload the global.asa, since we just 
	    # undef'd the subs
	    if($is_global_package) {
		# we just undef'd the global.asa routines, so these too 
		# must be recompiled
		$self->Debug("reloading global.asa file after clearing package namespace");
		delete $Apache::ASP::Compiled{$self->{GlobalASA}{'id'}};
		&Apache::ASP::GlobalASA::new($self);
	    }

	    $self->StatRegister($key, $file, $mtime);

	    # we want to register INC now in case any new libs were
	    # added when this module was reloaded
	    $self->StatRegisterAll();
	}
    }

    $stats;
}

sub StatRegister {
    my($self, $key, $file, $mtime) = @_;

    # keep track of times
    $Stat{$file} = $mtime; 
    
    # keep track of codes, don't undef on codes
    # with multiple refs, since these are exported
    my $class = &File2Class($key);

    # we skip Apache stuff as on some platforms (RedHat 6.0)
    # Apache::OK seems to error when getting its code ref
    # these shouldn't be reloaded anyway, as they are internal to 
    # modperl and should require a full server restart
    if($class eq 'Apache' or $class eq 'Apache::Constants') {
	$self->Debug("skipping StatINC register of $class");
	return;
    }

    $self->{dbg} && $self->Debug("stat register of $key $file $class");
    if($class eq 'CGI') {
	# must compensate for its autoloading behavior, and 
	# precompile all the routines, so we can register them
	# and not delete them later
	CGI->compile(':all');
    }

    my $sym = Devel::Symdump->new($class);
    my $function;
    for $function ($sym->functions()) {
	my $code = \&{$function};
	unless($code =~ /CODE/) {
	    $self->Debug("no code ref for function $function");
	    next;
	}

	# don't update if we already have this code defined for this func.
	next if $Apache::ASP::Codes{$code}{funcs}{$function}; 

#	$self->Debug("code $code for $function");
	$Apache::ASP::Codes{$code}{count}++;
	$Apache::ASP::Codes{$code}{libs}{$key}++;
	$Apache::ASP::Codes{$code}{funcs}{$function}++;
    }

    1;
}

sub StatRegisterAll {
    my $self = shift;
    # we make sure that all modules that are loaded are registered
    # so we don't undef exported subroutines, when we reload 
    my($key, $file);
    while(($key,$file) = each %INC) {
	next if defined $Stat{$file};
	next unless -e $file;
	# we use the module load time to init, in case it was
	# pulled in with PerlModule, and has changed since,
	# so it won't break with a graceful restart
	$self->StatRegister($key, $file, $StatStartTime - 1);
    }

    1;
}

sub File2Class {
    my $file = shift;
    return $file unless $file =~ s,\.pm$,,;
    $file =~ s,/,::,g;
    $file;
}

1;
