The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Logfile::EPrints::Filter::Session;

use strict;

use vars qw( %SESSIONS $AUTOLOAD $TIDY_ON $TIDY_COUNT );

$TIDY_ON = 10000;
$TIDY_COUNT = 0;

sub new
{
	my ($class,%self) = @_;
	$self{session} ||= 'Logfile::EPrints::Session';
	$TIDY_COUNT = 0;
	bless \%self, ref($class) || $class;
}

sub AUTOLOAD
{
	return if $AUTOLOAD =~ /[A-Z]$/;
	$AUTOLOAD =~ s/^.*:://;
	my( $self, $hit ) = @_;
	my $address = $hit->address;
	if( exists $SESSIONS{$address} and
		$SESSIONS{$address}->expired_by( $hit ) )
	{
		delete($SESSIONS{$address})->end_session;
	}
	my $session = $SESSIONS{$address} ||=
		$self->{session}->new(
			filter => $self,
			address => $address,
		);
	$session->$AUTOLOAD( $hit );
	
	$self->_tidyup( $hit ) if ++$TIDY_COUNT > $TIDY_ON;
	
	$hit->{session} = $session;
	return $self->{handler}->$AUTOLOAD($hit);
}

sub _tidyup
{
	my( $self, $hit ) = @_;
	$TIDY_COUNT = 0;
	for(keys %SESSIONS)
	{
		if( $SESSIONS{$_}->expired_by( $hit ) )
		{
			delete($SESSIONS{$_})->end_session;
		};
	}
}

package Logfile::EPrints::Session;

=head1 NAME

Logfile::EPrints::Session - Simple session class

=head1 METHODS

=over 4

=cut

use strict;
use warnings;

use vars qw( $AUTOLOAD $MAX_SESSION_GAP );

$MAX_SESSION_GAP = 60*10; # 10 minutes

sub new
{
	my( $class, %self ) = @_;
	bless \%self, $class;
}

=item $session->expired_by( $hit )

Returns true if this session would be expired before $hit occurred. NOTE for the purposes of tidyup $hit may not be from the same address as the session.

=cut

sub expired_by { ($_[1]->utime - $_[0]->{last_seen}) > $MAX_SESSION_GAP }

=item $session->start_session( $hit )

A new session has started with $hit.

=cut

sub start_session {}

=item $session->end_session

The session has expired/finished.

=cut

sub end_session { delete $_[0]->{last_abstract} }

=item $session->total( [ $type ] )

Return the total number of requests in this session or, if $type is given, total unique requests (by identifier) for $type.

=cut

sub total
{
	my( $self, $type ) = @_;

	return @_ == 2 ?
		scalar keys %{$self->{requests}->{$type}} :
		$self->{requests}->{total};
}

sub AUTOLOAD
{
	return if $AUTOLOAD =~ /[A-Z]$/;
	$AUTOLOAD =~ s/^.*:://;
	my( $self, $hit ) = @_;

	if( !defined $self->{last_seen} )
	{
		$self->{first_seen} = $hit->utime;
		$self->start_session( $hit );
	}

	if( $AUTOLOAD eq 'abstract' )
	{
		$self->{last_abstract} = $hit; # creates a loop in this hit
	}
	elsif( $AUTOLOAD eq 'fulltext' and exists $self->{last_abstract} )
	{
		if( $self->{last_abstract}->identifier eq $hit->identifier )
		{
			$hit->{abstract_referrer} = $self->{last_abstract};
		}
		else
		{
			delete $self->{last_abstract};
		}
	}
	
	$self->{last_seen} = $hit->utime;
	
	if( $AUTOLOAD eq 'abstract' or $AUTOLOAD eq 'fulltext' )
	{
		$self->{requests}->{total}++;
		$self->{requests}->{$AUTOLOAD}->{$hit->identifier}++;
	}
}

package Logfile::EPrints::Filter::MaxPerSession;

use strict;
use warnings;

our @ISA = qw( Logfile::EPrints::Filter );

use vars qw( $AUTOLOAD );

sub AUTOLOAD
{
	return if $AUTOLOAD =~ /[A-Z]$/;
	$AUTOLOAD =~ s/^.*:://;
	my( $self, $hit ) = @_;
	if( defined($self->{$AUTOLOAD}) and
		$hit->{session}->total($AUTOLOAD) > $self->{$AUTOLOAD} )
	{
		return undef if $hit->{session}->{__PACKAGE__ . '_removed'};
		$hit->{session}->{__PACKAGE__ . '_removed'} = 1;
		return Logfile::EPrints::Hit::Negate->new(
			address => $hit->address,
			start_utime => $hit->{session}->{first_seen},
			end_utime => $hit->{session}->{last_seen},
		);
	}
	else
	{
		return $self->{handler}->$AUTOLOAD( $hit );
	}
}

1;

__END__

=back