File Coverage

blib/lib/Date/LastModified.pm
Criterion Covered Total %
statement 24 140 17.1
branch 0 34 0.0
condition 0 9 0.0
subroutine 8 19 42.1
pod 4 8 50.0
total 36 210 17.1


line stmt bran cond sub pod time code
1             package Date::LastModified;
2             # ------ Return last-modified date from set of files, dirs, DBIs, etc.
3              
4              
5             # ------ pragmas
6 1     1   87145 use 5.006;
  1         4  
  1         48  
7 1     1   6 use strict;
  1         2  
  1         37  
8 1     1   15 use warnings;
  1         12  
  1         40  
9 1     1   2963 use AppConfig qw(:argcount);
  1         12345  
  1         186  
10 1     1   1950 use Date::Parse;
  1         3619  
  1         141  
11 1     1   7 use File::Find;
  1         2  
  1         79  
12 1     1   7204 use File::stat;
  1         31765  
  1         8  
13              
14              
15             # ------ set up exported names
16             require Exporter;
17             our @ISA = qw(Exporter); # we are an Exporter
18             our %EXPORT_TAGS # but we export nothing
19             = ( 'all' => [ qw() ] );
20             our @EXPORT_OK # but we export nothing
21             = ( @{ $EXPORT_TAGS{'all'} } );
22             our @EXPORT # but we export nothing
23             = qw();
24              
25              
26             # ------ version information
27             our $VERSION = '0.60'; # our version number
28              
29              
30             # ------ define functions
31              
32              
33             # ------ return last-modified date for a file
34             sub dlm_file {
35 0     0 1   my $file = shift; # file to examine
36 0           my $st = stat($file); # file status info
37              
38 0           return $st->mtime;
39             }
40              
41              
42             # ------ package for directory handling
43             {
44             package Date::LastModified::Directory;
45 1     1   218 use File::stat;
  1         2  
  1         4  
46              
47             my $last = 0; # last modification date
48              
49             # ------ reset last modification date
50             sub reset_last {
51 0     0     $last = 0;
52             }
53              
54             # ------ return last modification date
55             sub get_last {
56 0     0     return $last;
57             }
58              
59             # ------ File::Find "wanted" function
60             sub wanted {
61 0     0     my $st = stat($_); # file status info
62              
63 0 0 0       return if (m#/..$/# || m#^..$#);
64 0 0         if ($st->mtime > $last) {
65 0           $last = $st->mtime;
66             }
67             }
68             }
69              
70              
71             # ------ return last-modified date for a directory
72             sub dlm_dir {
73 0     0 1   my $dir = shift; # directory to examine
74              
75             # NOTE: "no_chdir" is friendly to Win32
76 0           Date::LastModified::Directory::reset_last();
77 0           find(
78             { wanted => \&Date::LastModified::Directory::wanted,
79             no_chdir => 1 },
80             $dir);
81 0           return Date::LastModified::Directory::get_last;
82             }
83              
84              
85             # ------ phrasebook for extracting Unix time from database
86             my $unix_time =
87             { "Oracle" => # Oracle database
88             { "time" # time extraction phrase
89             => "TO_CHAR(..., 'YYYY-MM-DD HH24:MI:SS')",
90             "parse_date" # have to parse date to get Unix time
91             => 1
92             },
93             "mysql" => # MySQL database
94             { "time" # time extraction phrase
95             => "UNIX_TIMESTAMP(...)",
96             "parse_date" # have to parse date to get Unix time
97             => 0
98             },
99             "SQLite" => # SQLite database
100             { "time" # time extraction phrase
101             => "...", # SQLite is typeless
102             "parse_date" # have to parse date to get Unix time
103             => 1
104             },
105             "SQL92" => # pseudo-entry for SQL92 databases
106             { "time" # time extraction phrase
107             => "CAST(... AS CHAR)",
108             "parse_date" # have to parse date to get Unix time
109             => 1
110             }
111             };
112              
113              
114             # ------ DBI error handler
115             sub dbi_error {
116 0     0 0   my $err = ""; # error string
117              
118 0 0 0       if (defined($err) && $err !~ m/^\s*$/) {
119 0           die "Database fatal error: $err\n";
120             }
121             }
122              
123              
124             # ------ return last-modified date from a database table via DBI
125             sub dlm_dbi {
126 0     0 1   my $dbi = shift; # DBI database connection info
127 0           my $cfg = ""; # DB username/password config object
128 0           my $column = ""; # column name for date
129 0           my $dbh = ""; # database handle
130 0           my $dbd = ""; # database driver
131 0           my $connect = ""; # DBI database connect string
132 0           my $last = ""; # last-modified date
133 0           my $passfile = ""; # password filename
134 0           my $password = ""; # database password
135 0           my $sql = ""; # SQL template for extracting date
136 0           my $sth = ""; # database statement handle
137 0           my $table = ""; # table with last-modified date
138 0           my $time_phrase = ""; # time extraction SQL phrase
139 0           my @tokens = (); # tokens from DB extract-date string
140 0           my $username = ""; # database username
141              
142             # ------ extract database connection information
143 0           @tokens = split(',', $dbi);
144 0 0         if (scalar(@tokens) >= 5) {
    0          
145 0           ($connect,$username,$password,$table,$column) = @tokens;
146             } elsif (scalar(@tokens) == 4) {
147 0           ($connect,$passfile,$table,$column) = @tokens;
148             } else {
149 0           die "Sorry, I can't find my database connection info in '$dbi'\n";
150             }
151 0 0         if ($passfile !~ m/^\s*$/) {
152 0           $cfg
153             = new AppConfig( {
154             CREATE => 1,
155             ERROR =>
156             \&AppConfig_err,
157             } );
158 0           $cfg->define("DbUsername",
159             { ARGCOUNT => ARGCOUNT_ONE } );
160 0           $cfg->define("DbPassword",
161             { ARGCOUNT => ARGCOUNT_ONE } );
162 0 0         if (!$cfg->file($passfile)) {
163 0           die "can't read '$passfile'\n";
164             }
165 0           $username = $cfg->get("DbUsername");
166 0           $password = $cfg->get("DbPassword");
167             }
168              
169             # ------ connect to specified database
170 0           $dbh = DBI->connect($connect,$username,$password);
171 0 0         if (!defined($dbh)) {
172 0           die "cannot connect to '$connect' for $table/$column because: $DBI::errstr\n";
173             }
174              
175             # ------ extract last-modified date from specified table and column
176 0           (undef,$dbd,undef) = split(':', $connect, 3);
177 0           $time_phrase = $unix_time->{$dbd}->{"time"};
178 0           $time_phrase =~ s/\.\.\./$column/;
179 0           $sql =<
180             SELECT
181             $time_phrase
182             FROM
183             $table
184             ORDER BY
185             $column
186             DESC
187             endSQL
188 0           $sth = $dbh->prepare($sql);
189 0           dbi_error($DBI::errstr);
190 0           $sth->execute();
191 0           dbi_error($DBI::errstr);
192 0           ($last) = $sth->fetchrow_array();
193 0           dbi_error($sth->errstr);
194              
195             # ------ return last-modified data as a Unix time
196 0 0         if ($unix_time->{$dbd}->{"parse_date"}) {
197 0           $last = str2time($last);
198             }
199 0           return $last;
200             }
201              
202              
203              
204             # ------ define private package variables
205             my $strategy # date resources strategy
206             = [
207             { "name" => "file", # file handler
208             "last_mod" => \&dlm_file, # return last-modified date
209             },
210             { "name" => "dir", # directory handler
211             "last_mod" => \&dlm_dir, # return last-modified date
212             },
213             { "name" => "dbi", # DBI handler
214             "last_mod" => \&dlm_dbi, # return last-modified date
215             },
216             ];
217              
218              
219             # ------ empty error function for AppConfig
220 0     0 0   sub AppConfig_err {
221             }
222              
223              
224             # ------ constructor
225             sub new {
226 0     0 0   my $class = shift; # our classname
227 0           my $resources = shift; # hashref of date resources
228             # OR scalar with config filename
229 0           my $cfg # configuration object
230             = new AppConfig( {
231             CREATE => 1, # create variables without predefinitions
232             ERROR =>
233             \&AppConfig_err, # error handler (empty)
234             } );
235 0           my $self = {}; # my blessed self
236 0           my $tactic = ""; # tactic in resource handler strategy
237 0           my $tactic_cnt = 0; # total # of tactics we use
238 0           my $tactic_name = ""; # name of tactic in strategy
239              
240             # ------ bless ourself into our class
241 0           bless $self, $class;
242              
243             # ------ setup where last-modified came from
244 0           $self->{"From"} = undef;
245              
246             # ------ use passed-in date resources
247 0 0         if (ref($resources) eq "HASH") {
248 0           foreach $tactic (@$strategy) {
249 0           $self->{"Resources"}->{$tactic->{"name"}}
250             = $resources->{$tactic->{"name"}};
251             }
252              
253             # ------ use date resources from config file
254             } else {
255              
256             # ------ set up variables we know about
257 0           foreach $tactic (@$strategy) {
258 0           $cfg->define("dlm_$tactic->{name}",
259             { ARGCOUNT => ARGCOUNT_LIST } );
260             }
261              
262             # ------ read configuration file
263 0 0         if (!$cfg->file($resources)) {
264 0           die "can't read '$resources'\n";
265             }
266 0           foreach $tactic (@$strategy) {
267 0           $self->{"Resources"}->{$tactic->{"name"}}
268             = $cfg->get("dlm_$tactic->{name}");
269             }
270             }
271              
272             # ------ ensure we got something to work with
273 0           foreach $tactic_name (keys(%{$self->{"Resources"}})) {
  0            
274 0           $tactic = $self->{"Resources"}->{$tactic_name};
275 0 0         if (ref($tactic) eq "ARRAY") {
276 0           $tactic_cnt += scalar(@{$tactic});
  0            
277             }
278             }
279 0 0         if ($tactic_cnt < 1) {
280 0           die "no resources to use by Date::LastModified\n";
281             }
282              
283             # ------ everything OK so return my blessed self
284 0           return $self;
285             }
286              
287              
288             # ------ return last-modified date of one or more resources
289             sub last {
290 0     0 0   my $self = shift; # my blessed self
291 0           my $current = 0; # current date from a resource
292 0           my $func = undef; # function to find last-modified date
293 0           my $latest = 0; # last-modified date of all resources
294 0           my $resource = ""; # resource to examine
295 0           my $tactic = ""; # tactic in resource handler strategy
296 0           my $tactic_name = ""; # name of tactic in strategy
297              
298 0           $current = $latest = 0;
299 0           $self->{"From"} = undef;
300 0           foreach $tactic_name (keys(%{$self->{"Resources"}})) {
  0            
301 0           $tactic = $self->{"Resources"}->{$tactic_name};
302 0           $func = undef;
303 0 0 0       if (ref($tactic) eq "ARRAY" && scalar(@$tactic) > 0) {
304 0           foreach $tactic (@$strategy) {
305 0 0         if ($tactic->{"name"} eq $tactic_name) {
306 0           $func = $tactic->{"last_mod"};
307             }
308             }
309 0 0         if (!defined($func)) {
310 0           die "missing last_mod function for '$tactic_name'\n";
311             }
312 0           foreach $resource (@{$tactic}) {
  0            
313 0           $current = &$func($resource);
314 0 0         if ($current > $latest) {
315 0           $self->{"From"} = "$tactic_name: $resource";
316 0           $latest = $current;
317             }
318             }
319             }
320             }
321              
322 0           return $latest;
323             }
324              
325             # ------ return where last-modified date came from
326             sub from {
327 0     0 1   my $self = shift; # our blessed self
328              
329 0           return $self->{"From"};
330             }
331              
332             1;
333             __END__