The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mail::Decency::LogParser::GeoSource;

use Moose;
extends qw/
    Mail::Decency::LogParser::Core
/;

use version 0.74; our $VERSION = qv( "v0.1.4" );

use Geo::IP;
use Data::Dumper;

=head1 NAME

Mail::Decency::LogParser::GeoSource


=head1 DESCRIPTION

Log statistics about geographical sender sources.

=head1 CLASS ATTRIBUTES

=head2 interval_formats : ArrayRef[Str]

Intervals in strftime format for L<DateTime>

=cut

has interval_formats => ( is => 'rw', isa => 'ArrayRef[Str]', default => sub { [] } );

=head2 geo_ip : Geo::IP

Instance of Geo::IP

=cut

has geo_ip => ( is => 'ro', isa => 'Geo::IP', default => sub { Geo::IP->new( GEOIP_STANDARD ) } );

=head2 enable_per_sender : Bool

Wheter source stats should be enabled per sender domain.. CAN BECOME VERY HUGE!!

Default: 0

=cut

has enable_per_sender => ( is => 'rw', isa => 'Bool', default => 0 );

=head2 enable_per_recipient : Bool

Wheter source stats should be enabled per recipient domain..

Default: 0

=cut

has enable_per_recipient => ( is => 'rw', isa => 'Bool', default => 0 );

=head2 schema_definition : HashRef[HashRef]

Schema for database

=cut

has schema_definition   => ( is => 'ro', isa => 'HashRef[HashRef]', default => sub {
    {
        geo => {
            source => {
                from_domain => [ varchar => 255 ],
                to_domain   => [ varchar => 255 ],
                country     => [ varchar => 2 ],
                type        => [ varchar => 25 ],
                interval    => [ varchar => 25 ],
                counter     => 'integer',
                last_update => 'integer',
                -unique     => [ 'from_domain', 'to_domain', 'country', 'type', 'interval' ]
            },
        }
    };
} );

=head1 METHODS

=head2 init

=cut

sub setup {
    my ( $self ) = @_;
    
    $self->config->{ interval_formats } ||= [ '%Y-%m-%d', '%Y-%m', '%Y' ];
    $self->interval_formats( $self->config->{ interval_formats } );
    
    $self->enable_per_recipient( 1 )
        if $self->config->{ enable_per_recipient };
    
    $self->enable_per_sender( 1 )
        if $self->config->{ enable_per_sender };
    
    
}


=head2 handle

Checks wheter incoming mail is whilist for final recipient

=cut

sub handle_data {
    my ( $self, $parsed_ref ) = @_;
    
    # no relevant
    return 
        if ! $parsed_ref->{ ip } || ! $parsed_ref->{ final } || ! ( $parsed_ref->{ reject } || $parsed_ref->{ bounced } || $parsed_ref->{ sent } );
    
    # determine country
    my $country = $self->geo_ip->country_code_by_addr( $parsed_ref->{ ip } ) || "";
    #return unless $country;
    
    # setup save pairs
    my @pairs = ( [ qw/ total total / ] );
    push @pairs, [ $parsed_ref->{ from_domain }, 'total' ]
        if $self->enable_per_sender && $parsed_ref->{ from_domain };
    push @pairs, [ 'total', $parsed_ref->{ to_domain } ]
        if $self->enable_per_recipient && $parsed_ref->{ to_domain };
    push @pairs, [ $parsed_ref->{ from_domain }, $parsed_ref->{ to_domain } ]
        if $self->enable_per_sender && $self->enable_per_recipient
        && $parsed_ref->{ from_domain } && $parsed_ref->{ to_domain };
    
    # determin types
    my @types = qw/ total /;
    push @types, 'reject' if $parsed_ref->{ reject };
    push @types, 'bounce' if $parsed_ref->{ bounced };
    push @types, 'sent'   if $parsed_ref->{ sent };
    
    
    # determine intervals
    my $dt = DateTime->now( time_zone => 'local' );
    my @intervals = ( 'total' );
    push @intervals, map { $dt->strftime( $_ ) } @{ $self->interval_formats };
    
    foreach my $interval( @intervals ) {
        
        foreach my $type( @types ) {
            
            foreach my $ref( @pairs ) {
                my ( $from, $to ) = @$ref;
                
                $self->database->usr_lock;
                my $entry_ref = $self->database->get( geo => source => my $search_ref = {
                    from_domain => $from,
                    to_domain   => $to,
                    type        => $type,
                    interval    => $interval,
                    country     => $country,
                } ) || { counter => 0 };
                $entry_ref->{ last_update } = time();
                $entry_ref->{ counter } ++;
                $self->database->set( geo => source => $search_ref, $entry_ref );
                $self->database->usr_unlock;
            }
        }
    }
}



=head2 print_stats

=cut

sub print_stats {
    my ( $self ) = @_;
    
    foreach my $type( qw/ sent bounced reject / ) {
        print "# FROM $type\n";
        
        my ( $handle, $meth ) = $self->database->search_read( geo => source => {
            type        => $type,
            from_domain => { "!=" => 'total' },
        } );
        while ( my $ref = $handle->$meth ) {
            print "$ref->{ type };$ref->{ interval };$ref->{ from_domain };$ref->{ to_domain };$ref->{ country };$ref->{ counter }\n";
        }
        
        print "\n# TO $type\n";
        ( $handle, $meth ) = $self->database->search_read( geo => source => {
            type      => $type,
            to_domain => { "!=" => 'total' }
        } );
        while ( my $ref = $handle->$meth ) {
            print "$ref->{ type };$ref->{ interval };$ref->{ from_domain };$ref->{ to_domain };$ref->{ country };$ref->{ counter }\n";
        }
        
        print "\n# TOTAL $type\n";
        ( $handle, $meth ) = $self->database->search_read( geo => source => {
            type        => $type,
            from_domain => 'total',
            to_domain   => 'total'
        } );
        while ( my $ref = $handle->$meth ) {
            print "$ref->{ type };$ref->{ interval };$ref->{ from_domain };$ref->{ to_domain };$ref->{ country };$ref->{ counter }\n";
        }
        print "\n\n";
    }
    
}


=head2 maintenance

Remove old cumulated entries. See maintenance_ttl

=cut

sub maintenance {
    my ( $self ) = @_;
    my $obsolete_time = time() - $self->maintenance_ttl;
    
    $self->logger->debug0( "Clear obsolete entries (TTL ". $self->maintenance_ttl. ")" );
    
    $self->database->remove( geo => source => {
        last_update => {
            '<' => $obsolete_time
        }
    } );
    
}







=head1 AUTHOR

Ulrich Kautz <uk@fortrabbit.de>

=head1 COPYRIGHT

Copyright (c) 2010 the L</AUTHOR> as listed above

=head1 LICENCSE

This library is free software and may be distributed under the same terms as perl itself.

=cut

1;