File Coverage

blib/lib/CPAN/Testers/Data/Uploads.pm
Criterion Covered Total %
statement 288 316 91.1
branch 86 124 69.3
condition 32 53 60.3
subroutine 40 40 100.0
pod 7 7 100.0
total 453 540 83.8


line stmt bran cond sub pod time code
1             package CPAN::Testers::Data::Uploads;
2              
3 13     13   784626 use strict;
  13         35  
  13         503  
4 13     13   75 use warnings;
  13         24  
  13         621  
5              
6 13     13   74 use vars qw($VERSION);
  13         26  
  13         958  
7             $VERSION = '0.20';
8             $|++;
9              
10             #----------------------------------------------------------------------------
11             # Library Modules
12              
13 13     13   81 use base qw(Class::Accessor::Fast);
  13         32  
  13         14753  
14              
15 13     13   93915 use CPAN::DistnameInfo;
  13         15598  
  13         434  
16 13     13   13977 use CPAN::Testers::Common::DBUtils;
  13         376074  
  13         141  
17 13     13   16116 use CPAN::Testers::Common::Article;
  13         284388  
  13         1457  
18 13     13   19484 use Config::IniFiles;
  13         598271  
  13         558  
19 13     13   160 use DBI;
  13         31  
  13         602  
20 13     13   204 use File::Basename;
  13         31  
  13         1084  
21 13     13   24591 use File::Find::Rule;
  13         145509  
  13         151  
22 13     13   935 use File::Path;
  13         28  
  13         949  
23 13     13   16868 use File::Slurp;
  13         199342  
  13         1384  
24 13     13   18604 use Getopt::Long;
  13         182579  
  13         113  
25 13     13   15049 use IO::AtomicFile;
  13         34395  
  13         680  
26 13     13   117 use IO::File;
  13         28  
  13         2407  
27 13     13   17115 use Net::NNTP;
  13         485470  
  13         1010  
28              
29             #----------------------------------------------------------------------------
30             # Variables
31              
32             my (%backups);
33 13     13   174 use constant LASTMAIL => '_lastmail';
  13         37  
  13         1040  
34 13     13   77 use constant LOGFILE => '_uploads.log';
  13         27  
  13         615  
35 13     13   76 use constant JOURNAL => '_journal.sql';
  13         25  
  13         61318  
36              
37             my %phrasebook = (
38             'FindAuthor' => 'SELECT * FROM ixlatest WHERE author=?',
39              
40             'FindDistVersion' => 'SELECT type FROM uploads WHERE author=? AND dist=? AND version=?',
41             'InsertDistVersion' => 'INSERT INTO uploads (type,author,dist,version,filename,released) VALUES (?,?,?,?,?,?)',
42             'UpdateDistVersion' => 'UPDATE uploads SET type=? WHERE author=? AND dist=? AND version=?',
43             'FindDistTypes' => 'SELECT * FROM uploads WHERE type=?',
44             'DeleteAll' => 'DELETE FROM uploads',
45             'SelectAll' => 'SELECT * FROM uploads',
46              
47             'DeleteAllIndex' => 'DELETE FROM ixlatest',
48             'DeleteIndex' => 'DELETE FROM ixlatest WHERE dist=? AND author=?',
49             'FindIndex' => 'SELECT * FROM ixlatest WHERE dist=? AND author=?',
50             'InsertIndex' => 'INSERT INTO ixlatest (oncpan,author,version,released,dist) VALUES (?,?,?,?,?)',
51             'AmendIndex' => 'UPDATE ixlatest SET oncpan=? WHERE author=? AND version=? AND dist=?',
52             'UpdateIndex' => 'UPDATE ixlatest SET oncpan=?,version=?,released=? WHERE dist=? AND author=?',
53             'BuildAuthorIndex' => 'SELECT x.author,x.version,x.released,x.dist,x.type FROM (SELECT dist, MAX(released) AS mv FROM uploads WHERE author=? GROUP BY dist) AS y INNER JOIN uploads AS x ON x.dist=y.dist AND x.released=y.mv ORDER BY released',
54             'GetAllAuthors' => 'SELECT distinct(author) FROM uploads',
55              
56             'InsertRequest' => 'INSERT INTO page_requests (type,name,weight) VALUES (?,?,5)',
57              
58             'ParseFailed' => 'REPLACE INTO uploads_failed (source,type,dist,version,file,pause,created) VALUES (?,?,?,?,?,?,?)',
59              
60             # SQLite backup
61             'CreateTable' => 'CREATE TABLE uploads (type text, author text, dist text, version text, filename text, released int)',
62             );
63              
64             my $extn = qr/\.(tar\.(gz|bz2)|tgz|zip)$/;
65              
66             my %oncpan = (
67             'backpan' => 2,
68             'cpan' => 1,
69             'upload' => 1
70             );
71              
72             #----------------------------------------------------------------------------
73             # The Application Programming Interface
74              
75             sub new {
76 14     14 1 19564 my $class = shift;
77              
78 14         35 my $self = {};
79 14         41 bless $self, $class;
80              
81 14         71 $self->_init_options(@_);
82 9         76 return $self;
83             }
84              
85             sub DESTROY {
86 14     14   64092 my $self = shift;
87             }
88              
89             __PACKAGE__->mk_accessors(
90             qw( uploads backpan cpan logfile logclean lastfile journal
91             mgenerate mupdate mbackup mreindex ));
92              
93             sub process {
94 5     5 1 133584 my $self = shift;
95 5 100       37 $self->generate() if($self->mgenerate);
96 5 100       69 $self->reindex() if($self->mreindex);
97 5 100       218 $self->update() if($self->mupdate);
98 5 100       671 $self->backup() if($self->mbackup);
99             }
100              
101             sub generate {
102 1     1 1 13 my $self = shift;
103 1         6 my $db = $self->uploads;
104              
105 1         11 $self->_log("Restarting uploads database");
106 1         61 $db->do_query($phrasebook{'DeleteAll'});
107              
108 1         30919 $self->_log("Creating BACKPAN entries");
109 1         105 my @files = File::Find::Rule->file()->name($extn)->in($self->backpan);
110 1         8560 $self->_parse_archive('backpan',$_) for(@files);
111              
112 1         9 $self->_log("Creating CPAN entries");
113 1         87 @files = File::Find::Rule->file()->name($extn)->in($self->cpan);
114 1         9399 $self->_parse_archive('cpan',$_) for(@files);
115             }
116              
117             sub reindex {
118 3     3 1 41 my $self = shift;
119 3         19 my $db = $self->uploads;
120              
121 3         33 $self->_log("Reindexing by author");
122              
123 3         185 my $next = $db->iterator('hash',$phrasebook{'GetAllAuthors'});
124 3         838 while(my $author = $next->()) {
125 45         1149442 $self->_log(".. author = $author->{author}");
126 45         2791 my @rows = $db->get_query('hash',$phrasebook{'BuildAuthorIndex'},$author->{author});
127 45         35537 for my $row (@rows) {
128 51         158874 $self->_log(".... dist = $row->{dist}, latest = $row->{version}");
129 51         2562 $db->do_query($phrasebook{'DeleteIndex'},$row->{dist},$row->{author});
130 51         498815 $db->do_query($phrasebook{'InsertIndex'},$oncpan{$row->{type}},$row->{author},$row->{version},$row->{released},$row->{dist});
131             }
132             }
133              
134 3         56145 $self->_log("Reindexing authors done");
135             }
136              
137             sub update {
138 1     1 1 12 my $self = shift;
139 1         6 my $db = $self->uploads;
140              
141 1         11 $self->_open_journal();
142              
143             # get list of db known CPAN distributions
144 1         349 my @rows = $db->get_query('hash',$phrasebook{'FindDistTypes'},'cpan');
145 1         1473 my %cpan = map {$_->{filename} => $_} @rows;
  63         155  
146              
147             # get currently mirrored CPAN entries
148 1         10 $self->_log("Updating CPAN entries");
149 1         108 my @files = File::Find::Rule->file()->name($extn)->in($self->cpan);
150 1         6579 for(@files) {
151 63 50       147 if(my $file = $self->_parse_archive('cpan',$_,1)) {
152 63         195 delete $cpan{$file};
153             } else {
154             #$self->_log(".. cannot parse: $_");
155             }
156             }
157              
158             # demote any distributions no longer on CPAN
159 1         7 $self->_log("Updating BACKPAN entries");
160 1         61 for my $file (keys %cpan) {
161             #$self->_log("backpan => $cpan{$file}->{dist} => $cpan{$file}->{version} => $cpan{$file}->{author} => $cpan{$file}->{released}");
162 0         0 $self->_write_journal('UpdateDistVersion','backpan',$cpan{$file}->{author},$cpan{$file}->{dist},$cpan{$file}->{version});
163 0         0 $db->do_query($phrasebook{'AmendIndex'},2,$cpan{$file}->{author},$cpan{$file}->{version},$cpan{$file}->{dist});
164             }
165              
166             # read NNTP
167 1         5 $self->_log("Updating NNTP entries");
168 1         31 my ($nntp,$num,$first,$last) = $self->_nntp_connect();
169 1         6 my $lastid = $self->_lastid();
170 1 50       7 return if($last <= $lastid);
171              
172 1         6 $self->_log(".. from $lastid to $last");
173 1         51 for(my $id = $lastid+1; $id <= $last; $id++) {
174             #$self->_log("NNTP ID = $id");
175 72870 50       181828 my $article = join "", @{$nntp->article($id) || []};
  72870         440463  
176 72870 100       5337674 next unless($article);
177 4         57 my $object = CPAN::Testers::Common::Article->new($article);
178 4 50       10961 next unless($object);
179 4         29 $self->_log("... [$id] subject=".($object->subject()));
180              
181 4         2383 my ($name,$version,$cpanid,$date,$filename);
182 4 50       23 if($object->parse_upload()) {
183 4         1052 $name = $object->distribution;
184 4         28 $version = $object->version;
185 4         25 $cpanid = $object->author;
186 4         30 $date = $object->epoch;
187 4         92 $filename = $object->filename;
188             }
189              
190             #$self->_log("... name=$name");
191             #$self->_log("... version=$version");
192             #$self->_log("... cpanid=$cpanid");
193             #$self->_log("... date=$date");
194              
195 4 50 33     64 next unless($name && $version && $cpanid && $date);
      33        
      33        
196             #$self->_log("upload => $name => $version => $cpanid => $date");
197              
198 4         22 $self->_update_index($cpanid,$version,$date,$name,1);
199 4         67886 my @rows = $db->get_query('array',$phrasebook{'FindDistVersion'},$cpanid,$name,$version);
200 4 100       1846 next if(@rows);
201 3         27 $self->_write_journal('InsertDistVersion','upload',$cpanid,$name,$version,$filename,$date);
202             }
203              
204 1         8 $self->_lastid($last);
205 1         6 $self->_close_journal();
206             }
207              
208             sub backup {
209 1     1 1 10 my $self = shift;
210 1         4 my $db = $self->uploads;
211              
212 1 50       9 if(my @journals = $self->_find_journals()) {
213 1         10 for my $driver (keys %backups) {
214 1 50 33     32 if($driver =~ /(CSV|SQLite)/i && !$backups{$driver}{'exists'}) {
215 1         13 $backups{$driver}{db}->do_query($phrasebook{'CreateTable'});
216 1         83188 $backups{$driver}{'exists'} = 1;
217             }
218             }
219            
220 1         6 for my $journal (@journals) {
221 1 50       9 next if($journal =~ /TMP$/); # don't process active journals
222 1         9 $self->_log("Processing journal $journal");
223 1         56 my $lines = $self->_read_journal($journal);
224 1         4 for my $line (@$lines) {
225 3         45610 my ($phrase,@args) = @$line;
226 3         14 for my $driver (keys %backups) {
227 3         26 $backups{$driver}{db}->do_query($phrasebook{$phrase},@args);
228             }
229             }
230              
231 1         15396 $self->_done_journal($journal);
232             }
233 1         82 $self->_log("Processed journals");
234             } else {
235 0         0 for my $driver (keys %backups) {
236 0 0       0 if($backups{$driver}{'exists'}) {
    0          
237 0         0 $backups{$driver}{db}->do_query($phrasebook{'DeleteAll'});
238             } elsif($driver =~ /(CSV|SQLite)/i) {
239 0         0 $backups{$driver}{db}->do_query($phrasebook{'CreateTable'});
240 0         0 $backups{$driver}{'exists'} = 1;
241             }
242             }
243              
244 0         0 $self->_log("Backup via DBD drivers");
245              
246 0         0 my $rows = $db->iterator('array',$phrasebook{'SelectAll'});
247 0         0 while(my $row = $rows->()) {
248 0         0 for my $driver (keys %backups) {
249 0         0 $backups{$driver}{db}->do_query($phrasebook{'InsertDistVersion'},@$row);
250             }
251             }
252             }
253              
254             # handle the CSV exception
255 1 50       124 if($backups{CSV}) {
256 0         0 $self->_log("Backup to CSV file");
257 0         0 $backups{CSV}{db} = undef; # close db handle
258 0 0       0 my $fh1 = IO::File->new('uploads','r') or die "Cannot read temporary database file 'uploads'\n";
259 0 0       0 my $fh2 = IO::File->new($backups{CSV}{dbfile},'w+') or die "Cannot write to CSV database file $backups{CSV}{dbfile}\n";
260 0         0 while(<$fh1>) { print $fh2 $_ }
  0         0  
261 0         0 $fh1->close;
262 0         0 $fh2->close;
263 0         0 unlink('uploads');
264             }
265             }
266              
267             sub help {
268 5     5 1 12 my ($self,$full,$mess) = @_;
269              
270 5 100       766 print "\n$mess\n\n" if($mess);
271              
272 5 100       18 if($full) {
273 4         97 print <
274              
275             Usage: $0 --config= [-g] [-r] [-u] [-b] [-h] [-v]
276             [--logfile=] [--logclean]
277             [--lastmail=] [--journal=]
278              
279             --config= database configuration file
280             -g generate new database
281             -r reindex database (*)
282             -u update existing database
283             -b backup database to portable files
284             -h this help screen
285             -v program version
286             --logfile= trace log file
287             --logclean overwrite exisiting log file
288             --lastmail= last id file
289             --journal= SQL journal file path
290              
291             Notes:
292             * A generate request automatically includes a reindex.
293              
294             HERE
295              
296             }
297              
298 5         105 print "$0 v$VERSION\n\n";
299 5         22 exit(0);
300             }
301              
302             #----------------------------------------------------------------------------
303             # Private Methods
304              
305             sub _parse_archive {
306 189     189   783 my ($self,$type,$file,$update) = @_;
307 189         1264 my $db = $self->uploads;
308 189         2701 my $dist = CPAN::DistnameInfo->new($file);
309              
310 189         26885 my $name = $dist->dist; # "CPAN-DistnameInfo"
311 189         1758 my $version = $dist->version; # "0.02"
312 189         1460 my $cpanid = $dist->cpanid; # "GBARR"
313 189         1290 my $filename = $dist->filename; # "CPAN-DistnameInfo-0.02.tar.gz"
314 189         12073 my $date = (stat($file))[9];
315              
316 189 50 33     3451 unless($name && defined $version && $cpanid && $date) {
      33        
      33        
317             #$self->_log("PARSE: FAIL file=$file, $type => $name => $version => $cpanid => $date => $filename");
318 0         0 $file =~ s!/opt/projects/CPAN/!!;
319 0         0 $db->do_query($phrasebook{'ParseFailed'},$file,$type,$name,$version,$filename,$cpanid,$date);
320 0         0 return;
321             }
322             #$self->_log("$type => $name => $version => $cpanid => $date");
323              
324 189         1446 my @rows = $db->get_query('array',$phrasebook{'FindDistVersion'},$cpanid,$name,$version);
325 189 100       91253 if(@rows) {
326 126 100       729 if($type ne $rows[0]->[0]) {
327 63         402 $db->do_query($phrasebook{'UpdateDistVersion'},$type,$cpanid,$name,$version);
328 63 50 33     1477847 $self->_update_index($cpanid,$version,$date,$name,$oncpan{$type})
329             if($update && $type ne 'backpan');
330             }
331             } else {
332 63         454 $db->do_query($phrasebook{'InsertDistVersion'},$type,$cpanid,$name,$version,$filename,$date);
333 63 50       1523056 $self->_update_index($cpanid,$version,$date,$name,$oncpan{$type}) if($update);
334             }
335              
336 189         3315 return $filename;
337             }
338              
339             sub _update_index {
340 4     4   10 my ($self,$author,$version,$date,$name,$oncpan) = @_;
341 4         22 my $db = $self->uploads;
342              
343 4         44 my @index = $db->get_query('hash',$phrasebook{'FindIndex'},$name,$author);
344 4 100       2296 if(@index) {
345 3 100       15 if($date > $index[0]->{released}) {
346 2         12 $db->do_query($phrasebook{'UpdateIndex'},$oncpan,$version,$date,$name,$author);
347 2         104945 $self->_log("... index update [$author,$version,$date,$name,$oncpan]");
348             }
349             } else {
350 1         8 $db->do_query($phrasebook{'InsertIndex'},$oncpan,$author,$version,$date,$name);
351 1         9529 $self->_log("... index insert [$author,$version,$date,$name,$oncpan]");
352             }
353              
354             # add to page_requests table to update letter index pages and individual pages
355 4         251 $db->do_query($phrasebook{'InsertRequest'},'ixauth',substr($author,0,1));
356 4         114178 $db->do_query($phrasebook{'InsertRequest'},'ixdist',substr($name,0,1));
357 4         66014 $db->do_query($phrasebook{'InsertRequest'},'author',$author);
358 4         75721 $db->do_query($phrasebook{'InsertRequest'},'distro',$name);
359             }
360              
361             sub _nntp_connect {
362             # connect to NNTP server
363 1 50   1   12 my $nntp = Net::NNTP->new("nntp.perl.org") or die "Cannot connect to nntp.perl.org";
364 1         29 my ($num,$first,$last) = $nntp->group("perl.cpan.uploads");
365              
366 1         91 return ($nntp,$num,$first,$last);
367             }
368              
369             sub _lastid {
370 6     6   1410 my ($self,$id) = @_;
371 6         27 my $f = $self->lastfile;
372              
373 6 100       150 unless( -f $f) {
374 2         202 mkpath(dirname($f));
375 2         16 overwrite_file( $f, 0 );
376 2   50     533 $id ||= 0;
377             }
378              
379 6 100       18 if($id) { overwrite_file( $f, $id ); }
  2         15  
380 4         20 else { $id = read_file($f); }
381              
382 6         732 return $id;
383             }
384              
385             # generate atomic journal file name
386             sub _open_journal {
387 1     1   3 my $self = shift;
388 1         127 my @now = localtime(time);
389 1         7 my $file = sprintf "%s.%04d%02d%02d%02d%02d%02d", $self->journal, $now[5]+1900,$now[4]+1,$now[3],$now[2],$now[1],$now[0];
390 1 50       29 $self->{current} = IO::AtomicFile->new($file,'w+') or die "Cannot write to journal file [$file]: $!\n";
391             }
392              
393             sub _write_journal {
394 3     3   13 my ($self,$phrase,@args) = @_;
395 3         12 my $fh = $self->{current};
396              
397 3         101 print $fh "$phrase," . join(',',@args) . "\n";
398              
399 3         21 my $db = $self->uploads;
400 3         38 $db->do_query($phrasebook{$phrase},@args);
401             }
402              
403             sub _close_journal {
404 1     1   124 my $self = shift;
405 1         13 $self->{current}->close;
406             }
407              
408             sub _find_journals {
409 1     1   2 my $self = shift;
410 1         12 my @files = glob($self->journal . '.*');
411 1         3238 return @files;
412             }
413              
414             sub _read_journal {
415 1     1   3 my ($self,$journal) = @_;
416 1         3 my @lines;
417              
418 1 50       8 my $fh = IO::File->new($journal,'r') or die "Cannot read journal file [$journal]: $!\n";
419 1         97 while(<$fh>) {
420 3         19 my @fields = split(/,/);
421 3         19 push @lines, \@fields;
422             }
423 1         6 $fh->close;
424 1         16 return \@lines;
425             }
426              
427             sub _done_journal {
428 1     1   5 my ($self,$journal) = @_;
429 1         226 my $cmd = "mv $journal logs";
430 1         15674 system($cmd);
431             }
432              
433             sub _init_options {
434 14     14   28 my $self = shift;
435 14         50 my %hash = @_;
436 14         25 my %options;
437              
438 14         97 GetOptions( \%options,
439             'config=s',
440             'generate|g',
441             'update|u',
442             'reindex|r',
443             'backup|b',
444             'journal|j=s',
445             'logfile|l=s',
446             'logclean=s',
447             'lastfile=s',
448             'help|h',
449             'version|v'
450             );
451              
452             # default to API settings if no command line option
453 14         9456 for(qw(config generate update reindex fast backup help version)) {
454 112 100 33     381 $options{$_} ||= $hash{$_} if(defined $hash{$_});
455             }
456              
457 14 100       65 $self->help(1) if($options{help});
458 13 100       51 $self->help(0) if($options{version});
459              
460 12 100 100     106 $self->help(1,"Must specify at least one option from 'generate' (-g), 'reindex' (-r),\n'update' (-u) and/or 'backup' (-b)")
      100        
      100        
461             unless($options{generate} || $options{update} || $options{backup} || $options{reindex});
462 11 100       40 $self->help(1,"Must specific the configuration file") unless( $options{config});
463 10 100       205 $self->help(1,"Configuration file [$options{config}] not found") unless(-f $options{config});
464              
465             # load configuration
466 9         204 my $cfg = Config::IniFiles->new( -file => $options{config} );
467              
468             # configure sources
469 9 100       33638 if($options{generate}) {
470 1         5 my $dir = $cfg->val('MASTER','BACKPAN');
471 1 50       24 $self->help(1,"No source location for 'BACKPAN' in config file") if(! $dir);
472 1 50       25 $self->help(1,"Cannot find source location for 'BACKPAN': [$dir]") if(!-d $dir);
473 1         7 $self->backpan($dir);
474 1         19 $self->mgenerate(1);
475 1         8 $self->mreindex(1);
476             }
477 9 100 100     88 if($options{generate} || $options{update}) {
478 7         37 my $dir = $cfg->val('MASTER','CPAN');
479 7 50       166 $self->help(1,"No source location for 'CPAN' in config file") if(! $dir);
480 7 50       204 $self->help(1,"Cannot find source location for 'CPAN': [$dir]") if(!-d $dir);
481 7         45 $self->cpan($dir);
482             }
483 9 100       169 if($options{reindex}) {
484 1         7 $self->mreindex(1);
485             }
486              
487 9 100       68 $self->mupdate(1) if($options{update});
488 9   50     125 $self->logfile( $hash{logfile} || $options{logfile} || $cfg->val('MASTER','logfile' ) || LOGFILE );
489 9   100     390 $self->logclean( $hash{logclean} || $options{logclean} || $cfg->val('MASTER','logclean' ) || 0 );
490 9   50     335 $self->lastfile( $hash{lastfile} || $options{lastfile} || $cfg->val('MASTER','lastfile' ) || LASTMAIL );
491 9   100     296 $self->journal( $hash{journal} || $options{journal} || $cfg->val('MASTER','journal' ) || JOURNAL );
492              
493             # configure upload DB
494 9 50       258 $self->help(1,"No configuration for UPLOADS database") unless($cfg->SectionExists('UPLOADS'));
495 9   100     187 my %opts = map {$_ => ($cfg->val('UPLOADS',$_) || undef)} qw(driver database dbfile dbhost dbport dbuser dbpass);
  63         1116  
496 9         320 my $db = CPAN::Testers::Common::DBUtils->new(%opts);
497 9 50       288 $self->help(1,"Cannot configure UPLOADS database") unless($db);
498 9         46 $self->uploads($db);
499              
500             # configure backup DBs
501 9 100       315 if($options{backup}) {
502 1 50       4 $self->help(1,"No configuration for BACKUPS with backup option") unless($cfg->SectionExists('BACKUPS'));
503              
504 1         31 my %available_drivers = map { $_ => 1 } DBI->available_drivers;
  7         556  
505 1         5 my @drivers = $cfg->val('BACKUPS','drivers');
506 1         24 for my $driver (@drivers) {
507 2 100       7 unless($available_drivers{$driver}) {
508 1         173 warn "No DBI support for '$driver', ignoring\n";
509 1         6 next;
510             }
511              
512 1 50       5 $self->help(1,"No configuration for backup option '$driver'") unless($cfg->SectionExists($driver));
513              
514 1   100     21 my %opt = map {$_ => ($cfg->val($driver,$_) || undef)} qw(driver database dbfile dbhost dbport dbuser dbpass);
  7         112  
515 1 50       64 $backups{$driver}{'exists'} = $driver =~ /SQLite/i ? -f $opt{database} : 1;
516              
517             # CSV is a bit of an oddity!
518 1 50       8 if($driver =~ /CSV/i) {
519 0         0 $backups{$driver}{'exists'} = 0;
520 0         0 $backups{$driver}{'dbfile'} = $opt{dbfile};
521 0         0 $opt{dbfile} = 'uploads';
522 0         0 unlink($opt{dbfile});
523             }
524              
525 1         8 $backups{$driver}{db} = CPAN::Testers::Common::DBUtils->new(%opt);
526 1 50       23 $self->help(1,"Cannot configure BACKUPS database for '$driver'") unless($backups{$driver}{db});
527             }
528              
529 1 50       11 $self->mbackup(1) if(keys %backups);
530             }
531             }
532              
533             sub _log {
534 122     122   6066 my $self = shift;
535 122 50       684 my $log = $self->logfile or return;
536 122 100       6187 mkpath(dirname($log)) unless(-f $log);
537              
538 122 100       1025 my $mode = $self->logclean ? 'w+' : 'a+';
539 122         1923 $self->logclean(0);
540              
541 122         5578 my @dt = localtime(time);
542 122         1284 my $dt = sprintf "%04d/%02d/%02d %02d:%02d:%02d", $dt[5]+1900,$dt[4]+1,$dt[3],$dt[2],$dt[1],$dt[0];
543              
544 122 50       7449 my $fh = IO::File->new($log,$mode) or die "Cannot write to log file [$log]: $!\n";
545 122         22112 print $fh "$dt ", @_, "\n";
546 122         602 $fh->close;
547             }
548              
549             q!Will code for a damn fine Balti!;
550              
551             __END__