File Coverage

blib/lib/Algorithm/FloodControl.pm
Criterion Covered Total %
statement 79 106 74.5
branch 2 12 16.6
condition 3 8 37.5
subroutine 15 17 88.2
pod 7 7 100.0
total 106 150 70.6


line stmt bran cond sub pod time code
1             package Algorithm::FloodControl;
2              
3 2     2   90272 use strict;
  2         6  
  2         87  
4 2     2   12 use warnings;
  2         6  
  2         61  
5 2     2   2409 use utf8;
  2         28  
  2         11  
6 2     2   142 use 5.008000;
  2         7  
  2         76  
7              
8 2     2   12 use Carp;
  2         3  
  2         195  
9 2     2   2066 use Params::Validate qw/:all/;
  2         29503  
  2         549  
10 2     2   19 use base 'Class::Accessor::Fast';
  2         5  
  2         2245  
11 2     2   7771 use Exporter 'import';
  2         6  
  2         67  
12 2     2   1983 use Module::Load;
  2         2522  
  2         16  
13              
14 2     2   1933 use version; our $VERSION = qv("2.01")->numify;
  2         5046  
  2         12  
15             our @EXPORT = qw(
16             flood_check
17             flood_storage
18             );
19              
20             # $Id: FloodControl.pm 7 2008-11-06 12:51:33Z gugu $
21             # $Source$
22             # $HeadURL: file:///var/svn/Algorithm-FloodControl/lib/Algorithm/FloodControl.pm $
23              
24             __PACKAGE__->mk_accessors(qw/backend_name storage limits/);
25              
26             my %FLOOD = ();
27              
28             sub flood_check {
29 0     0 1 0 my $fc = shift; # max flood events count
30 0         0 my $fp = shift; # max flood time period for $fc events
31 0         0 my $en = shift; # event name (key) which identifies flood check data
32              
33 0 0       0 if ( !$en ) {
34 0         0 my ( $p, $f, $l ) = caller; # construct event name by:
35 0         0 $en = "$p:$f:$l"; # package + filename + line
36             # print STDERR "EN: $en\n";
37             }
38              
39 0   0     0 $FLOOD{$en} ||= []; # make empty flood array for this event name
40 0         0 my $ar = $FLOOD{$en}; # get array ref for event's flood array
41 0         0 my $ec = @{$ar}; # events count in the flood array
  0         0  
42              
43 0 0       0 if ( $ec >= $fc ) {
44              
45             # flood array has enough events to do real flood check
46 0         0 my $ot = $ar->[0]; # oldest event timestamp in the flood array
47 0         0 my $tp = time() - $ot; # time period between current and oldest event
48              
49             # now calculate time in seconds until next allowed event
50 0         0 my $wait = int( ( $ot + ( $ec * $fp / $fc ) ) - time() );
51 0 0       0 if ( $wait > 0 ) {
52              
53             # positive number of seconds means flood in progress
54             # event should be rejected or postponed
55             # print "WARNING: next event will be allowed in $wait seconds\n";
56 0         0 return $wait;
57             }
58              
59             # negative or 0 seconds means that event should be accepted
60             # oldest event is removed from the flood array
61 0         0 shift @{$ar};
  0         0  
62             }
63              
64             # flood array is not full or oldest event is already removed
65             # so current event has to be added
66 0         0 push @{$ar}, time();
  0         0  
67              
68             # event is ok
69 0         0 return 0;
70             }
71              
72             sub flood_storage {
73 0 0   0 1 0 if (@_) {
74 0 0       0 if ( ref( $_[0] ) ne 'HASH' ) {
75 0         0 croak "flood_storage sub requires hash reference as single argument"
76             }
77 0         0 %FLOOD = %{ $_[0] };
  0         0  
78             }
79 0         0 return \%FLOOD;
80             }
81              
82             ################# OOP ###########################
83              
84             sub new {
85 1     1 1 33982 my $class = shift;
86 1         78 my $params = validate @_,
87             {
88             storage => { type => OBJECT },
89             backend_name => { type => SCALAR, optional => 1 },
90             limits => { type => HASHREF }
91             };
92 1         30 my $self = $class->SUPER::new($params);
93              
94             # be default backend will be selected by storage classname. but you can override it
95 1   33     40 my $backend_name = __PACKAGE__ . '::Backend::' . ( $self->{backend_name} || ref $self->storage );
96 1         19 load $backend_name;
97 1         17 $self->backend_name($backend_name);
98 1         14 return $self;
99             }
100              
101             sub is_user_overrated {
102 9     9 1 1287 my ( $self, @params ) = @_;
103 9         134 my ( $limit, $identifier ) = validate_pos @params, { type => SCALAR }, { type => SCALAR };
104 9         32 my @configs = @{ $self->{limits}{$limit} };
  9         30  
105 9         13 my $max_timeout = 0;
106 9         17 foreach my $config (@configs) {
107 18         53 my $prefix = __PACKAGE__ . '_rc_' . "$identifier|$limit|$config->{period}";
108 18         52 my $backend = $self->backend_name->new(
109             {
110             storage => $self->storage,
111             expires => $config->{period},
112             prefix => $prefix
113             }
114             );
115 18         419 my $info = $backend->get_info( $config->{attempts} );
116 18 100 66     126 if ( $info->{size} >= $config->{attempts} && $info->{timeout} > $max_timeout ) {
117 2         10 $max_timeout = $info->{timeout};
118             }
119             }
120 9         39 return $max_timeout;
121             }
122              
123             sub get_attempt_count {
124 2     2 1 9 my $self = shift;
125 2         36 my ( $limit, $identifier ) = validate_pos @_, { type => SCALAR }, { type => SCALAR };
126 2         6 my %attempts;
127 2         3 my @configs = @{ $self->{limits}{$limit} };
  2         8  
128 2         4 foreach my $config (@configs) {
129 4         16 my $prefix = __PACKAGE__ . '_rc_' . "$identifier|$limit|$config->{period}";
130 4         13 my $queue = $self->backend_name->new(
131             {
132             storage => $self->storage,
133             expires => $config->{period},
134             prefix => $prefix
135             }
136             );
137 4         90 $attempts{ $config->{period} } = $queue->get_info( $config->{attempts} )->{size};
138             }
139 2         10 return \%attempts;
140             }
141              
142             sub register_attempt {
143 5     5 1 1271 my $self = shift;
144 5         89 my ( $limit, $identifier ) = validate_pos @_, { type => SCALAR }, { type => SCALAR };
145 5         17 my @configs = @{ $self->{limits}{$limit} };
  5         16  
146 5         15 my $is_overrated = $self->is_user_overrated(@_);
147 5         10 foreach my $config (@configs) {
148 10         29 my $prefix = __PACKAGE__ . '_rc_' . "$identifier|$limit|$config->{period}";
149 10         29 my $queue = $self->backend_name->new(
150             {
151             storage => $self->storage,
152             expires => $config->{period},
153             prefix => $prefix
154             }
155             );
156 10         216 $queue->increment;
157             }
158 5         19 return $is_overrated;
159             }
160              
161             sub forget_attempts {
162 1     1 1 4 my $self = shift;
163 1         30 my ( $limit, $identifier ) = validate_pos @_, { type => SCALAR }, { type => SCALAR };
164 1         5 my @configs = @{ $self->{limits}{$limit} };
  1         6  
165 1         4 my $is_overrated = $self->is_user_overrated(@_);
166 1         3 foreach my $config (@configs) {
167 2         53 my $prefix = __PACKAGE__ . '_rc_' . "$identifier|$limit|$config->{period}";
168 2         6 my $queue = $self->backend_name->new(
169             {
170             storage => $self->storage,
171             expires => $config->{period},
172             prefix => $prefix
173             }
174             );
175 2         42 $queue->clear;
176             }
177 1         42 return $is_overrated;
178             }
179              
180             1;
181              
182             __END__