File Coverage

blib/lib/CPAN/Testers/Data/Release.pm
Criterion Covered Total %
statement 178 178 100.0
branch 51 62 82.2
condition 18 18 100.0
subroutine 19 19 100.0
pod 6 6 100.0
total 272 283 96.1


line stmt bran cond sub pod time code
1             package CPAN::Testers::Data::Release;
2              
3 9     9   189352 use strict;
  9         21  
  9         316  
4 9     9   79 use warnings;
  9         16  
  9         300  
5              
6 9     9   48 use vars qw($VERSION);
  9         17  
  9         592  
7             $VERSION = '0.06';
8              
9             #----------------------------------------------------------------------------
10             # Library Modules
11              
12 9     9   51 use base qw(Class::Accessor::Fast);
  9         13  
  9         8392  
13              
14 9     9   98967 use CPAN::Testers::Common::DBUtils;
  9         1017299  
  9         85  
15 9     9   12261 use Config::IniFiles;
  9         405793  
  9         374  
16 9     9   106 use File::Basename;
  9         19  
  9         791  
17 9     9   54 use File::Path;
  9         18  
  9         466  
18 9     9   14203 use Getopt::Long;
  9         118201  
  9         66  
19 9     9   15833 use IO::File;
  9         8326  
  9         21687  
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 8     8 1 15340 my $class = shift;
52              
53 8         21 my $self = {};
54 8         25 bless $self, $class;
55              
56 8         37 $self->_init_options(@_);
57 3         12 return $self;
58             }
59              
60             sub DESTROY {
61 8     8   14115 my $self = shift;
62             }
63              
64             __PACKAGE__->mk_accessors(qw( idfile logfile logclean ));
65              
66             sub process {
67 4     4 1 146169 my $self = shift;
68 4 100       26 if($self->{clean}) { $self->clean() }
  1 100       5  
69 2         7 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 2589 my $self = shift;
75              
76 3         14 $self->_log("Find new start");
77              
78 3         73 my $lastid = 0;
79 3         18 my $idfile = $self->idfile();
80 3 100 100     62 if($idfile && -f $idfile) {
81 1 50       7 if(my $fh = IO::File->new($idfile,'r')) {
82 1         84 my @lines = <$fh>;
83 1         5 ($lastid) = $lines[0] =~ /(\d+)/;
84 1         4 $fh->close;
85             }
86             }
87              
88 3   100     34 $lastid ||= 0;
89 3         14 $self->_log("Starting from $lastid");
90              
91             # retrieve data from master database
92 3         74 my $rows = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectDists'},$lastid);
93 3         747 while(my $row = $rows->()) {
94 43         1077985 $self->_log("... dist=$row->{dist}, version=$row->{version}");
95 43         1915 my $next = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectDist'},$row->{dist},$row->{version});
96 43         13399 my ($pass,$fail,$na,$unknown) = (0,0,0,0);
97 43         218 while(my $rs = $next->()) {
98 97         4362 $pass += $rs->{pass};
99 97         169 $fail += $rs->{fail};
100 97         193 $na += $rs->{na};
101 97         256 $unknown += $rs->{unknown};
102 97 100       1036 $lastid = $rs->{id} if($lastid < $rs->{id});
103             }
104              
105 43         1552 $self->{RELEASE}{dbh}->do_query($phrasebook{'DeleteRelease'},$row->{dist},$row->{version});
106 43         1087907 $self->{RELEASE}{dbh}->do_query($phrasebook{'InsertRelease'},$row->{dist},$row->{version},$pass,$fail,$na,$unknown);
107             }
108              
109 3         50989 $self->_log("Writing lastid=$lastid");
110              
111 3 100       80 if($idfile) {
112 2 50       24 if(my $fh = IO::File->new($idfile,'w+')) {
113 2         298 print $fh "$lastid\n";
114 2         10 $fh->close;
115             }
116             }
117              
118 3         86 $self->_log("Backup completed");
119             }
120              
121             sub backup_from_start {
122 2     2 1 1117924 my $self = shift;
123 2         6 my $lastid = 0;
124              
125 2         12 $self->_log("Create backup database");
126              
127             # start with a clean slate
128 2         116 $self->{RELEASE}{dbh}->do_query($phrasebook{'DeleteTable'});
129 2         58944 $self->{RELEASE}{dbh}->do_query($phrasebook{'CreateTable'});
130 2         45002 $self->{RELEASE}{dbh}->do_query($phrasebook{'CreateDistIndex'});
131 2         55450 $self->{RELEASE}{dbh}->do_query($phrasebook{'CreateVersIndex'});
132              
133 2         26658 $self->_log("Generate backup data");
134              
135             # store data from master database
136 2         74 my %data;
137 2         9 my $dist = '';
138 2         28 my $rows = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectAll'});
139 2         1465 while(my $row = $rows->()) {
140 39 100 100     1904 if($dist && $dist ne $row->{dist}) {
141 4         29 $self->_log("... dist=$dist");
142 4         131 for my $vers (keys %data) {
143 12         146624 $self->{RELEASE}{dbh}->do_query($phrasebook{'InsertRelease'},@{ $data{$vers} });
  12         99  
144             }
145              
146 4         115402 %data = ();
147             }
148              
149 39         96 $dist = $row->{dist};
150              
151 39 100       245 if($data{$row->{version}}) {
152 21         57 $data{$row->{version}}->[2] += $row->{pass};
153 21         55 $data{$row->{version}}->[3] += $row->{fail};
154 21         97 $data{$row->{version}}->[4] += $row->{na};
155 21         49 $data{$row->{version}}->[5] += $row->{unknown};
156             } else {
157 18         47 $data{$row->{version}} = [ map { $row->{$_} } qw(dist version pass fail na unknown) ];
  108         309  
158             }
159              
160 39 100       362 $lastid = $row->{id} if($lastid < $row->{id});
161             }
162              
163 2 50       59 if($dist) {
164 2         21 $self->_log("... dist=$dist");
165 2         56 for my $vers (keys %data) {
166 6         57776 $self->{RELEASE}{dbh}->do_query($phrasebook{'InsertRelease'},@{ $data{$vers} });
  6         48  
167             }
168             }
169              
170 2         17927 $self->{RELEASE}{exists} = 1;
171              
172 2         25 my $idfile = $self->idfile();
173 2 100       33 if($idfile) {
174 1 50       17 if(my $fh = IO::File->new($idfile,'w+')) {
175 1         249 print $fh "$lastid\n";
176 1         17 $fh->close;
177             }
178             }
179              
180 2         102 $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         5 $self->_log("Clean master database");
188              
189 1         8 my %data;
190 1         3 my $dist = '';
191 1         8 my $rows = $self->{CPANSTATS}{dbh}->iterator('hash',$phrasebook{'SelectRows'});
192 1         194 while(my $row = $rows->()) {
193 11 100 100     420 if($dist && $dist ne $row->{dist}) {
194 2         22 $self->{CPANSTATS}{dbh}->do_query($phrasebook{'DelRows'},$dist);
195 2         319557 $self->_log("DelRows: $dist");
196 2         30 for my $vers (keys %data) {
197 6         73 for my $code (keys %{$data{$vers}}) {
  6         38  
198 6         16 my $rowx = $data{$vers}{$code};
199 6         76 $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         85936 $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         21 $dist = $row->{dist};
215 11         62 my $code = join(':',$row->{oncpan},$row->{distmat},$row->{perlmat},$row->{patched});
216 11         7934 $data{$row->{version}}{$code} = $row;
217             }
218              
219 1 50       23 if($dist) {
220 1         7 $self->{CPANSTATS}{dbh}->do_query($phrasebook{'DelRows'},$dist);
221 1         29307 $self->_log("DelRows: $dist");
222 1         26 for my $vers (keys %data) {
223 3         36 for my $code (keys %{$data{$vers}}) {
  3         335  
224 4         24 my $rowx = $data{$vers}{$code};
225 4         57 $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         317072 $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         20 $self->_log("Clean completed");
239             }
240              
241             sub help {
242 5     5 1 8 my ($self,$full,$mess) = @_;
243              
244 5 100       115 print "\n$mess\n\n" if($mess);
245              
246 5 100       14 if($full) {
247 4         200 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 5         98 print "$0 v$VERSION\n\n";
261 5         20 exit(0);
262             }
263              
264              
265             #----------------------------------------------------------------------------
266             # Internal Methods
267              
268             sub _init_options {
269 8     8   16 my $self = shift;
270 8         33 my %hash = @_;
271 8         13 my %options;
272              
273 8 50       46 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 8         2335 for(qw(config help version)) {
282 24 100 100     151 next unless(!defined $options{$_} && defined $hash{$_});
283 9         25 $options{$_} = $hash{$_};
284             }
285              
286 8 100       37 $self->help(1) if($options{help});
287 6 100       33 $self->help(0) if($options{version});
288              
289 5 100       25 $self->help(1,"Must specific the configuration file") unless( $options{config});
290 4 100       77 $self->help(1,"Configuration file [$options{config}] not found") unless(-f $options{config});
291              
292             # load configuration
293 3         45 my $cfg = Config::IniFiles->new( -file => $options{config} );
294              
295 3         7331 $self->idfile( $cfg->val('MASTER','idfile' ) );
296 3         136 $self->logfile( $cfg->val('MASTER','logfile' ) );
297 3   100     83 $self->logclean( $cfg->val('MASTER','logclean' ) || 0 );
298              
299             # configure upload DB
300 3         88 for my $dbname (qw(CPANSTATS RELEASE)) {
301 6 50       36 $self->help(1,"No configuration for $dbname database") unless($cfg->SectionExists($dbname));
302 6   100     195 my %opts = map {$_ => ($cfg->val($dbname,$_) || undef);} qw(driver database dbfile dbhost dbport dbuser dbpass);
  42         843  
303 6 50       355 $self->{$dbname}{exists} = $opts{driver} =~ /SQLite/i ? -f $opts{database} : 1;
304 6         64 $self->{$dbname}{dbh} = CPAN::Testers::Common::DBUtils->new(%opts);
305 6 50       225 $self->help(1,"Cannot configure $dbname database") unless($self->{$dbname}{dbh});
306             }
307              
308 3 50       59 $self->{clean} = 1 if($options{clean});
309             }
310              
311             sub _log {
312 82     82   314 my $self = shift;
313 82 100       2055 my $log = $self->logfile or return;
314 32 100       2000 mkpath(dirname($log)) unless(-f $log);
315              
316 32 100       301 my $mode = $self->logclean ? 'w+' : 'a+';
317 32         412 $self->logclean(0);
318              
319 32         1708 my @dt = localtime(time);
320 32         420 my $dt = sprintf "%04d/%02d/%02d %02d:%02d:%02d", $dt[5]+1900,$dt[4]+1,$dt[3],$dt[2],$dt[1],$dt[0];
321              
322 32 50       502 my $fh = IO::File->new($log,$mode) or die "Cannot write to log file [$log]: $!\n";
323 32         18783 print $fh "$dt ", @_, "\n";
324 32         404 $fh->close;
325             }
326              
327             q{Written to the tune of Release by Pearl Jam :)};
328              
329             __END__