File Coverage

blib/lib/App/Milter/Limit/Plugin/SQLite.pm
Criterion Covered Total %
statement 18 86 20.9
branch 0 26 0.0
condition 0 5 0.0
subroutine 6 20 30.0
pod 3 5 60.0
total 27 142 19.0


line stmt bran cond sub pod time code
1             package App::Milter::Limit::Plugin::SQLite;
2             our $VERSION = '0.51';
3              
4             # ABSTRACT: SQLite driver for App::Milter::Limit
5              
6 1     1   904 use strict;
  1         3  
  1         42  
7 1     1   7 use base qw(App::Milter::Limit::Plugin Class::Accessor);
  1         2  
  1         1044  
8 1     1   15147 use DBI;
  1         18567  
  1         83  
9 1     1   14 use File::Spec;
  1         2  
  1         31  
10 1     1   1175 use App::Milter::Limit::Log;
  1         25449  
  1         80  
11 1     1   931 use App::Milter::Limit::Util;
  1         616  
  1         1066  
12              
13             __PACKAGE__->mk_accessors(qw(_dbh table));
14              
15              
16             sub init {
17 0     0 1   my $self = shift;
18              
19 0           $self->_init_defaults;
20              
21 0           App::Milter::Limit::Util::make_path($self->config_get('driver', 'home'));
22              
23 0           $self->table( $self->config_get('driver', 'table') );
24              
25             # setup the database
26 0           $self->_init_database;
27             }
28              
29             sub _init_defaults {
30 0     0     my $self = shift;
31              
32 0           $self->config_defaults('driver',
33             home => $self->config_get('global', 'state_dir'),
34             file => 'stats.db',
35             table => 'milter');
36             }
37              
38              
39             sub db_file {
40 0     0 1   my $self = shift;
41              
42 0           my $home = $self->config_get('driver', 'home');
43 0           my $file = $self->config_get('driver', 'file');
44              
45 0           return File::Spec->catfile($home, $file);
46             }
47              
48             sub _new_dbh {
49 0     0     my $self = shift;
50              
51             # setup connection to the database.
52 0           my $db_file = $self->db_file;
53              
54 0 0         my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {
55             PrintError => 0,
56             AutoCommit => 1 })
57             or die "failed to initialize SQLite: $!";
58              
59 0           return $dbh;
60             }
61              
62             # initialize the database
63             sub _init_database {
64 0     0     my $self = shift;
65              
66             # setup connection to the database.
67 0           $self->_dbh($self->_new_dbh);
68              
69 0 0         unless ($self->_table_exists($self->table)) {
70 0           $self->_create_table($self->table);
71             }
72              
73             # make sure the db file has the right owner.
74 0           my $uid = $self->config_get('global', 'user');
75 0           my $gid = $self->config_get('global', 'group');
76              
77 0           my $db_file = $self->db_file;
78 0 0         chown $uid, $gid, $db_file or die "chown($db_file): $!";
79             }
80              
81             sub child_init {
82 0     0 0   my $self = shift;
83              
84 0           debug("reopen db handle");
85              
86 0 0         if (my $dbh = $self->_dbh) {
87 0           $dbh->disconnect;
88 0           $dbh = $self->_new_dbh;
89 0           $self->_dbh($dbh);
90             }
91             }
92              
93             sub child_exit {
94 0     0 0   my $self = shift;
95              
96 0           debug("close db handle");
97              
98 0 0         if (my $dbh = $self->_dbh) {
99 0           $dbh->disconnect;
100             }
101             }
102              
103             sub query {
104 0     0 1   my ($self, $from) = @_;
105              
106 0           $from = lc $from;
107              
108 0           my $rec = $self->_retrieve($from);
109              
110 0 0         unless (defined $rec) {
111             # initialize new record for sender
112 0 0         $rec = $self->_create($from)
113             or return 0; # I give up
114             }
115              
116 0   0       my $start = $$rec{first_seen} || time;
117 0   0       my $count = $$rec{messages} || 0;
118 0           my $expire = $self->config_get('global', 'expire');
119              
120             # reset counter if it is expired
121 0 0         if ($start < time - $expire) {
122 0           $self->_reset($from);
123 0           return 1;
124             }
125              
126             # update database for this sender.
127 0           $self->_update($from);
128              
129 0           return $count + 1;
130             }
131              
132             # return true if the given table exists in the db.
133             sub _table_exists {
134 0     0     my ($self, $table) = @_;
135              
136 0 0         $self->_dbh->do("select 1 from $table limit 0")
137             or return 0;
138              
139 0           return 1;
140             }
141              
142             # create the stats table
143             sub _create_table {
144 0     0     my ($self, $table) = @_;
145              
146 0           my $dbh = $self->_dbh;
147              
148 0 0         $dbh->do(qq{
149             create table $table (
150             sender varchar (255),
151             first_seen timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
152             messages integer NOT NULL DEFAULT 0,
153             PRIMARY KEY (sender)
154             )
155             }) or die "failed to create table $table: $DBI::errstr";
156              
157 0 0         $dbh->do(qq{
158             create index ${table}_first_seen_key on $table (first_seen)
159             }) or die "failed to create first_seen index: $DBI::errstr";
160             }
161              
162             ## CRUD methods
163             sub _create {
164 0     0     my ($self, $sender) = @_;
165              
166 0           my $table = $self->table;
167              
168 0 0         $self->_dbh->do(qq{insert or replace into $table (sender) values (?)},
169             undef, $sender)
170             or warn "failed to create sender record: $DBI::errstr";
171              
172 0           return $self->_retrieve($sender);
173             }
174              
175             sub _retrieve {
176 0     0     my ($self, $sender) = @_;
177              
178 0           my $table = $self->table;
179              
180 0           my $query = qq{
181             select
182             sender,
183             messages,
184             strftime('%s',first_seen) as first_seen
185             from
186             $table
187             where
188             sender = ?
189             };
190              
191 0           return $self->_dbh->selectrow_hashref($query, undef, $sender);
192             }
193              
194             sub _update {
195 0     0     my ($self, $sender) = @_;
196              
197 0           my $table = $self->table;
198              
199 0           my $query = qq{update $table set messages = messages + 1 where sender = ?};
200              
201 0           return $self->_dbh->do($query, undef, $sender);
202             }
203              
204             sub _reset {
205 0     0     my ($self, $sender) = @_;
206              
207 0           my $table = $self->table;
208              
209 0 0         $self->_dbh->do(qq{
210             update
211             $table
212             set
213             messages = 1,
214             first_seen = CURRENT_TIMESTAMP
215             where
216             sender = ?
217             }, undef, $sender)
218             or warn "failed to reset $sender: $DBI::errstr";
219             }
220              
221             1;
222              
223              
224              
225             =pod
226              
227             =head1 NAME
228              
229             App::Milter::Limit::Plugin::SQLite - SQLite driver for App::Milter::Limit
230              
231             =head1 VERSION
232              
233             version 0.51
234              
235             =head1 SYNOPSIS
236              
237             my $milter = App::Milter::Limit->instance('SQLite');
238              
239             =head1 DESCRIPTION
240              
241             This module implements the C backend using a SQLite data
242             store.
243              
244             =head1 METHODS
245              
246             =head2 db_file
247              
248             return the full path to the SQLite database filename
249              
250             =for Pod::Coverage child_init
251             child_exit
252              
253             =head1 CONFIGURATION
254              
255             The C<[driver]> section of the configuration file must specify the following items:
256              
257             =over 4
258              
259             =item home [optional]
260              
261             The directory where the database files should be stored.
262              
263             default: C
264              
265             =item file [optional]
266              
267             The database filename.
268              
269             default: C
270              
271             =item table [optional]
272              
273             Table name that will store the statistics.
274              
275             default: C
276              
277             =back
278              
279             =head1 SEE ALSO
280              
281             L,
282             L
283              
284             =head1 AUTHOR
285              
286             Michael Schout
287              
288             =head1 COPYRIGHT AND LICENSE
289              
290             This software is copyright (c) 2010 by Michael Schout.
291              
292             This is free software; you can redistribute it and/or modify it under
293             the same terms as the Perl 5 programming language system itself.
294              
295             =cut
296              
297              
298             __END__