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__ |