File Coverage

blib/lib/App/Milter/Limit/Plugin/SQLite.pm
Criterion Covered Total %
statement 49 90 54.4
branch 6 28 21.4
condition 1 8 12.5
subroutine 14 21 66.6
pod 3 5 60.0
total 73 152 48.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of App-Milter-Limit-Plugin-SQLite
3             #
4             # This software is copyright (c) 2010 by Michael Schout.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9              
10             package App::Milter::Limit::Plugin::SQLite;
11             $App::Milter::Limit::Plugin::SQLite::VERSION = '0.52';
12             # ABSTRACT: SQLite driver for App::Milter::Limit
13              
14 3     3   86408 use strict;
  3         8  
  3         83  
15 3     3   15 use warnings;
  3         6  
  3         90  
16 3     3   14 use base qw(App::Milter::Limit::Plugin Class::Accessor);
  3         6  
  3         849  
17 3     3   8080 use DBI;
  3         37625  
  3         176  
18 3     3   29 use File::Spec;
  3         6  
  3         40  
19 3     3   386 use App::Milter::Limit::Log;
  3         15286  
  3         147  
20 3     3   285 use App::Milter::Limit::Util;
  3         468  
  3         711  
21              
22             __PACKAGE__->mk_accessors(qw(_dbh table));
23              
24              
25             sub init {
26 2     2 1 492 my $self = shift;
27              
28 2         8 $self->_init_defaults;
29              
30 2         158 App::Milter::Limit::Util::make_path($self->config_get('driver', 'home'));
31              
32 2         118 $self->table( $self->config_get('driver', 'table') );
33              
34             # setup the database
35 2         76 $self->_init_database;
36             }
37              
38             sub _init_defaults {
39 2     2   2 my $self = shift;
40              
41 2         14 $self->config_defaults('driver',
42             home => $self->config_get('global', 'state_dir'),
43             file => 'stats.db',
44             table => 'milter');
45             }
46              
47              
48             sub db_file {
49 2     2 1 6 my $self = shift;
50              
51 2         4 my $home = $self->config_get('driver', 'home');
52 2         40 my $file = $self->config_get('driver', 'file');
53              
54 2         68 return File::Spec->catfile($home, $file);
55             }
56              
57             sub _new_dbh {
58 2     2   4 my $self = shift;
59              
60             # setup connection to the database.
61 2         4 my $db_file = $self->db_file;
62              
63 2 50       20 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {
64             PrintError => 0,
65             AutoCommit => 1 })
66             or die "failed to initialize SQLite: $!";
67              
68 2         21036 return $dbh;
69             }
70              
71             # initialize the database
72             sub _init_database {
73 2     2   4 my $self = shift;
74              
75             # setup connection to the database.
76 2         4 $self->_dbh($self->_new_dbh);
77              
78 2 50       36 unless ($self->_table_exists($self->table)) {
79 2         718 $self->_create_table($self->table);
80             }
81              
82             # make sure the db file has the right owner.
83 2         26120 my $uid = $self->config_get('global', 'user');
84 2         146 my $gid = $self->config_get('global', 'group');
85              
86 2 50 33     52 if (defined $uid and defined $gid) {
87 0         0 my $db_file = $self->db_file;
88 0 0       0 chown $uid, $gid, $db_file or die "chown($db_file): $!";
89             }
90             }
91              
92             sub child_init {
93 0     0 0 0 my $self = shift;
94              
95 0         0 debug("reopen db handle");
96              
97 0 0       0 if (my $dbh = $self->_dbh) {
98 0         0 $dbh->disconnect;
99 0         0 $dbh = $self->_new_dbh;
100 0         0 $self->_dbh($dbh);
101             }
102             }
103              
104             sub child_exit {
105 0     0 0 0 my $self = shift;
106              
107 0         0 debug("close db handle");
108              
109 0 0       0 if (my $dbh = $self->_dbh) {
110 0         0 $dbh->disconnect;
111             }
112             }
113              
114             sub query {
115 0     0 1 0 my ($self, $from) = @_;
116              
117 0         0 $from = lc $from;
118              
119 0         0 my $rec = $self->_retrieve($from);
120              
121 0 0       0 unless (defined $rec) {
122             # initialize new record for sender
123 0 0       0 $rec = $self->_create($from)
124             or return 0; # I give up
125             }
126              
127 0   0     0 my $start = $$rec{first_seen} || time;
128 0   0     0 my $count = $$rec{messages} || 0;
129 0         0 my $expire = $self->config_get('global', 'expire');
130              
131             # reset counter if it is expired
132 0 0       0 if ($start < time - $expire) {
133 0         0 $self->_reset($from);
134 0         0 return 1;
135             }
136              
137             # update database for this sender.
138 0         0 $self->_update($from);
139              
140 0         0 return $count + 1;
141             }
142              
143             # return true if the given table exists in the db.
144             sub _table_exists {
145 2     2   46 my ($self, $table) = @_;
146              
147 2 50       8 $self->_dbh->do("select 1 from $table limit 0")
148             or return 0;
149              
150 0         0 return 1;
151             }
152              
153             # create the stats table
154             sub _create_table {
155 2     2   24 my ($self, $table) = @_;
156              
157 2         6 my $dbh = $self->_dbh;
158              
159 2 50       26 $dbh->do(qq{
160             create table $table (
161             sender varchar (255),
162             first_seen timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
163             messages integer NOT NULL DEFAULT 0,
164             PRIMARY KEY (sender)
165             )
166             }) or die "failed to create table $table: $DBI::errstr";
167              
168 2 50       296660 $dbh->do(qq{
169             create index ${table}_first_seen_key on $table (first_seen)
170             }) or die "failed to create first_seen index: $DBI::errstr";
171             }
172              
173             ## CRUD methods
174             sub _create {
175 0     0     my ($self, $sender) = @_;
176              
177 0           my $table = $self->table;
178              
179 0 0         $self->_dbh->do(qq{insert or replace into $table (sender) values (?)},
180             undef, $sender)
181             or warn "failed to create sender record: $DBI::errstr";
182              
183 0           return $self->_retrieve($sender);
184             }
185              
186             sub _retrieve {
187 0     0     my ($self, $sender) = @_;
188              
189 0           my $table = $self->table;
190              
191 0           my $query = qq{
192             select
193             sender,
194             messages,
195             strftime('%s',first_seen) as first_seen
196             from
197             $table
198             where
199             sender = ?
200             };
201              
202 0           return $self->_dbh->selectrow_hashref($query, undef, $sender);
203             }
204              
205             sub _update {
206 0     0     my ($self, $sender) = @_;
207              
208 0           my $table = $self->table;
209              
210 0           my $query = qq{update $table set messages = messages + 1 where sender = ?};
211              
212 0           return $self->_dbh->do($query, undef, $sender);
213             }
214              
215             sub _reset {
216 0     0     my ($self, $sender) = @_;
217              
218 0           my $table = $self->table;
219              
220 0 0         $self->_dbh->do(qq{
221             update
222             $table
223             set
224             messages = 1,
225             first_seen = CURRENT_TIMESTAMP
226             where
227             sender = ?
228             }, undef, $sender)
229             or warn "failed to reset $sender: $DBI::errstr";
230             }
231              
232             1;
233              
234             __END__