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