File Coverage

blib/lib/CPAN/Testers/Data/Release.pm
Criterion Covered Total %
statement 177 177 100.0
branch 51 62 82.2
condition 16 18 88.8
subroutine 19 19 100.0
pod 6 6 100.0
total 269 282 95.3


line stmt bran cond sub pod time code
1             package CPAN::Testers::Data::Release;
2              
3 9     9   263986 use strict;
  9         22  
  9         335  
4 9     9   46 use warnings;
  9         16  
  9         317  
5              
6 9     9   48 use vars qw($VERSION);
  9         20  
  9         589  
7             $VERSION = '0.05';
8              
9             #----------------------------------------------------------------------------
10             # Library Modules
11              
12 9     9   49 use base qw(Class::Accessor::Fast);
  9         18  
  9         9791  
13              
14 9     9   54853 use CPAN::Testers::Common::DBUtils;
  9         369084  
  9         98  
15 9     9   14635 use Config::IniFiles;
  9         422447  
  9         371  
16 9     9   112 use File::Basename;
  9         19  
  9         797  
17 9     9   56 use File::Path;
  9         18  
  9         512  
18 9     9   12861 use Getopt::Long;
  9         123143  
  9         66  
19 9     9   11886 use IO::File;
  9         11446  
  9         25365  
20              
21             #----------------------------------------------------------------------------
22             # Variables
23              
24             my %phrasebook = (
25             # MySQL database
26             'SelectAll' => 'SELECT dist,version,pass,fail,na,unknown,id FROM release_summary WHERE perlmat=1 ORDER BY dist',
27             'SelectRows' => 'SELECT * FROM release_summary ORDER BY dist',
28             'DelRows' => 'DELETE FROM release_summary WHERE dist=?',
29             'AddRow' => 'INSERT INTO release_summary (dist,version,id,guid,oncpan,distmat,perlmat,patched,pass,fail,na,unknown) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)',
30              
31             'SelectDists' => 'SELECT dist,version FROM release_summary WHERE id > ?',
32             'SelectDist' => 'SELECT dist,version,id,pass,fail,na,unknown FROM release_summary WHERE perlmat=1 AND dist=? AND version=?',
33              
34             # SQLite database
35             'DeleteTable' => 'DROP TABLE IF EXISTS release',
36             'CreateTable' => 'CREATE TABLE release (dist text not null, version text not null, pass integer not null, fail integer not null, na integer not null, unknown integer not null)',
37             'CreateDistIndex' => 'CREATE INDEX release__dist ON release ( dist )',
38             'CreateVersIndex' => 'CREATE INDEX release__version ON release ( version )',
39              
40             'DeleteAll' => 'DELETE FROM release',
41             'InsertRelease' => 'INSERT INTO release (dist,version,pass,fail,na,unknown) VALUES (?,?,?,?,?,?)',
42             'UpdateRelease' => 'UPDATE release SET pass=?,fail=?,na=?,unknown=? WHERE dist=? AND version=?',
43             'SelectRelease' => 'SELECT * FROM release WHERE dist=? AND version=?',
44             'DeleteRelease' => 'DELETE FROM release WHERE dist=? AND version=?',
45             );
46              
47             #----------------------------------------------------------------------------
48             # The Application Programming Interface
49              
50             sub new {
51 7     7 1 17659 my $class = shift;
52              
53 7         22 my $self = {};
54 7         23 bless $self, $class;
55              
56 7         40 $self->_init_options(@_);
57 3         14 return $self;
58             }
59              
60             sub DESTROY {
61 7     7   18956 my $self = shift;
62             }
63              
64             __PACKAGE__->mk_accessors(qw( idfile logfile logclean ));
65              
66             sub process {
67 4     4 1 352234 my $self = shift;
68 4 100       107 if($self->{clean}) { $self->clean() }
  1 100       6  
69 2         10 elsif($self->{RELEASE}{exists}) { $self->backup_from_last() }
70 1         6 else { $self->backup_from_start() }
71             }
72              
73             sub backup_from_last {
74 3     3 1 1135 my $self = shift;
75              
76 3         14 $self->_log("Find new start");
77              
78 3         42 my $lastid = 0;
79 3         13 my $idfile = $self->idfile();
80 3 100 100     180 if($idfile && -f $idfile) {
81 1 50       9 if(my $fh = IO::File->new($idfile,'r')) {
82 1         115 my @lines = <$fh>;
83 1         7 ($lastid) = $lines[0] =~ /(\d+)/;
84 1         5 $fh->close;
85             }
86             }
87              
88 3   100     86 $lastid ||= 0;
89 3         15 $self->_log("Starting from $lastid");
90              
91             # retrieve data from master database
92 3         297 my $rows = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectDists'},$lastid);
93 3         1066 while(my $row = $rows->()) {
94 43         1133566 $self->_log("... dist=$row->{dist}, version=$row->{version}");
95 43         1927 my $next = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectDist'},$row->{dist},$row->{version});
96 43         12832 my ($pass,$fail,$na,$unknown) = (0,0,0,0);
97 43         183 while(my $rs = $next->()) {
98 97         3540 $pass += $rs->{pass};
99 97         158 $fail += $rs->{fail};
100 97         417 $na += $rs->{na};
101 97         1461 $unknown += $rs->{unknown};
102 97 100       737 $lastid = $rs->{id} if($lastid < $rs->{id});
103             }
104              
105 43         13926 $self->{RELEASE}{dbh}->do_query($phrasebook{'DeleteRelease'},$row->{dist},$row->{version});
106 43         1134124 $self->{RELEASE}{dbh}->do_query($phrasebook{'InsertRelease'},$row->{dist},$row->{version},$pass,$fail,$na,$unknown);
107             }
108              
109 3         165738 $self->_log("Writing lastid=$lastid");
110              
111 3 100       54 if($idfile) {
112 2 50       22 if(my $fh = IO::File->new($idfile,'w+')) {
113 2         371 print $fh "$lastid\n";
114 2         12 $fh->close;
115             }
116             }
117              
118 3         186 $self->_log("Backup completed");
119             }
120              
121             sub backup_from_start {
122 2     2 1 907017 my $self = shift;
123 2         5 my $lastid = 0;
124              
125 2         11 $self->_log("Create backup database");
126              
127             # start with a clean slate
128 2         78 $self->{RELEASE}{dbh}->do_query($phrasebook{'DeleteTable'});
129 2         37190 $self->{RELEASE}{dbh}->do_query($phrasebook{'CreateTable'});
130 2         63923 $self->{RELEASE}{dbh}->do_query($phrasebook{'CreateDistIndex'});
131 2         41893 $self->{RELEASE}{dbh}->do_query($phrasebook{'CreateVersIndex'});
132              
133 2         31287 $self->_log("Generate backup data");
134              
135             # store data from master database
136 2         476 my %data;
137 2         7 my $dist = '';
138 2         50 my $rows = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectAll'});
139 2         776 while(my $row = $rows->()) {
140 39 100 100     1853 if($dist && $dist ne $row->{dist}) {
141 4         30 $self->_log("... dist=$dist");
142 4         114 for my $vers (keys %data) {
143 12         297257 $self->{RELEASE}{dbh}->do_query($phrasebook{'InsertRelease'},@{ $data{$vers} });
  12         99  
144             }
145              
146 4         97546 %data = ();
147             }
148              
149 39         87 $dist = $row->{dist};
150              
151 39 100       106 if($data{$row->{version}}) {
152 21         49 $data{$row->{version}}->[2] += $row->{pass};
153 21         42 $data{$row->{version}}->[3] += $row->{fail};
154 21         38 $data{$row->{version}}->[4] += $row->{na};
155 21         44 $data{$row->{version}}->[5] += $row->{unknown};
156             } else {
157 18         40 $data{$row->{version}} = [ map { $row->{$_} } qw(dist version pass fail na unknown) ];
  108         370  
158             }
159              
160 39 100       394 $lastid = $row->{id} if($lastid < $row->{id});
161             }
162              
163 2 50       52 if($dist) {
164 2         20 $self->_log("... dist=$dist");
165 2         51 for my $vers (keys %data) {
166 6         93225 $self->{RELEASE}{dbh}->do_query($phrasebook{'InsertRelease'},@{ $data{$vers} });
  6         49  
167             }
168             }
169              
170 2         37665 $self->{RELEASE}{exists} = 1;
171              
172 2         23 my $idfile = $self->idfile();
173 2 100       242 if($idfile) {
174 1 50       19 if(my $fh = IO::File->new($idfile,'w+')) {
175 1         312 print $fh "$lastid\n";
176 1         20 $fh->close;
177             }
178             }
179              
180 2         82 $self->_log("Backup completed");
181             }
182              
183             # sub to remove duplicates in the matser database.
184             sub clean {
185 1     1 1 2 my $self = shift;
186              
187 1         6 $self->_log("Clean master database");
188              
189 1         10 my %data;
190 1         3 my $dist = '';
191 1         9 my $rows = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectRows'});
192 1         275 while(my $row = $rows->()) {
193 11 100 100     496 if($dist && $dist ne $row->{dist}) {
194 2         18 $self->{CPANSTATS}{dbh}->do_query($phrasebook{'DelRows'},$dist);
195 2         127784 $self->_log("DelRows: $dist");
196 2         32 for my $vers (keys %data) {
197 6         69 for my $code (keys %{$data{$vers}}) {
  6         35  
198 6         18 my $rowx = $data{$vers}{$code};
199 6         83 $self->{CPANSTATS}{dbh}->do_query($phrasebook{'AddRow'},$dist,$vers,
200             $rowx->{id},$rowx->{guid},
201             $rowx->{oncpan},$rowx->{distmat},$rowx->{perlmat},$rowx->{patched},
202             $rowx->{pass},$rowx->{fail},$rowx->{na},$rowx->{unknown});
203 6         99991 $self->_log('AddRow: ' . join(', ',
204             $dist,$vers,
205             $rowx->{id},$rowx->{guid},
206             $rowx->{oncpan},$rowx->{distmat},$rowx->{perlmat},$rowx->{patched},
207             $rowx->{pass},$rowx->{fail},$rowx->{na},$rowx->{unknown}) );
208             }
209             }
210              
211 2         63 %data = ();
212             }
213              
214 11         23 $dist = $row->{dist};
215 11         51 my $code = join(':',$row->{oncpan},$row->{distmat},$row->{perlmat},$row->{patched});
216 11         98 $data{$row->{version}}{$code} = $row;
217             }
218              
219 1 50       28 if($dist) {
220 1         10 $self->{CPANSTATS}{dbh}->do_query($phrasebook{'DelRows'},$dist);
221 1         13863 $self->_log("DelRows: $dist");
222 1         16 for my $vers (keys %data) {
223 3         39 for my $code (keys %{$data{$vers}}) {
  3         18  
224 4         19 my $rowx = $data{$vers}{$code};
225 4         47 $self->{CPANSTATS}{dbh}->do_query($phrasebook{'AddRow'},$dist,$vers,
226             $rowx->{id},$rowx->{guid},
227             $rowx->{oncpan},$rowx->{distmat},$rowx->{perlmat},$rowx->{patched},
228             $rowx->{pass},$rowx->{fail},$rowx->{na},$rowx->{unknown});
229 4         166624 $self->_log('AddRow: ' . join(', ',
230             $dist,$vers,
231             $rowx->{id},$rowx->{guid},
232             $rowx->{oncpan},$rowx->{distmat},$rowx->{perlmat},$rowx->{patched},
233             $rowx->{pass},$rowx->{fail},$rowx->{na},$rowx->{unknown}) );
234             }
235             }
236             }
237              
238 1         21 $self->_log("Clean completed");
239             }
240              
241             sub help {
242 4     4 1 9 my ($self,$full,$mess) = @_;
243              
244 4 100       133 print "\n$mess\n\n" if($mess);
245              
246 4 100       16 if($full) {
247 3         83 print <
248              
249             Usage: $0 --config= [--clean] [-h] [-v]
250              
251             --config= database configuration file
252             --clean clean master database of duplicates
253             -h this help screen
254             -v program version
255              
256             HERE
257              
258             }
259              
260 4         107 print "$0 v$VERSION\n\n";
261 4         20 exit(0);
262             }
263              
264              
265             #----------------------------------------------------------------------------
266             # Internal Methods
267              
268             sub _init_options {
269 7     7   15 my $self = shift;
270 7         30 my %hash = @_;
271 7         13 my %options;
272              
273 7 50       49 GetOptions( \%options,
274             'clean',
275             'config=s',
276             'help|h',
277             'version|v'
278             ) or help(1);
279              
280             # default to API settings if no command line option
281 7         3057 for(qw(config help version)) {
282 21 100 33     121 $options{$_} ||= $hash{$_} if(defined $hash{$_});
283             }
284              
285 7 100       35 $self->help(1) if($options{help});
286 6 100       26 $self->help(0) if($options{version});
287              
288 5 100       23 $self->help(1,"Must specific the configuration file") unless( $options{config});
289 4 100       83 $self->help(1,"Configuration file [$options{config}] not found") unless(-f $options{config});
290              
291             # load configuration
292 3         45 my $cfg = Config::IniFiles->new( -file => $options{config} );
293              
294 3         7609 $self->idfile( $cfg->val('MASTER','idfile' ) );
295 3         136 $self->logfile( $cfg->val('MASTER','logfile' ) );
296 3   100     139 $self->logclean( $cfg->val('MASTER','logclean' ) || 0 );
297              
298             # configure upload DB
299 3         107 for my $dbname (qw(CPANSTATS RELEASE)) {
300 6 50       38 $self->help(1,"No configuration for $dbname database") unless($cfg->SectionExists($dbname));
301 6   100     136 my %opts = map {$_ => ($cfg->val($dbname,$_) || undef);} qw(driver database dbfile dbhost dbport dbuser dbpass);
  42         799  
302 6 50       368 $self->{$dbname}{exists} = $opts{driver} =~ /SQLite/i ? -f $opts{database} : 1;
303 6         60 $self->{$dbname}{dbh} = CPAN::Testers::Common::DBUtils->new(%opts);
304 6 50       190 $self->help(1,"Cannot configure $dbname database") unless($self->{$dbname}{dbh});
305             }
306              
307 3 50       123 $self->{clean} = 1 if($options{clean});
308             }
309              
310             sub _log {
311 82     82   246 my $self = shift;
312 82 100       494 my $log = $self->logfile or return;
313 32 100       1131 mkpath(dirname($log)) unless(-f $log);
314              
315 32 100       145 my $mode = $self->logclean ? 'w+' : 'a+';
316 32         300 $self->logclean(0);
317              
318 32         1479 my @dt = localtime(time);
319 32         327 my $dt = sprintf "%04d/%02d/%02d %02d:%02d:%02d", $dt[5]+1900,$dt[4]+1,$dt[3],$dt[2],$dt[1],$dt[0];
320              
321 32 50       462 my $fh = IO::File->new($log,$mode) or die "Cannot write to log file [$log]: $!\n";
322 32         5522 print $fh "$dt ", @_, "\n";
323 32         188 $fh->close;
324             }
325              
326             q{Written to the tune of Release by Pearl Jam :)};
327              
328             __END__