line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CPAN::Testers::Data::Uploads; |
2
|
|
|
|
|
|
|
|
3
|
13
|
|
|
13
|
|
349357
|
use strict; |
|
13
|
|
|
|
|
32
|
|
|
13
|
|
|
|
|
502
|
|
4
|
13
|
|
|
13
|
|
73
|
use warnings; |
|
13
|
|
|
|
|
22
|
|
|
13
|
|
|
|
|
466
|
|
5
|
|
|
|
|
|
|
|
6
|
13
|
|
|
13
|
|
71
|
use vars qw($VERSION); |
|
13
|
|
|
|
|
79
|
|
|
13
|
|
|
|
|
1112
|
|
7
|
|
|
|
|
|
|
$VERSION = '0.21'; |
8
|
|
|
|
|
|
|
$|++; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
#---------------------------------------------------------------------------- |
11
|
|
|
|
|
|
|
# Library Modules |
12
|
|
|
|
|
|
|
|
13
|
13
|
|
|
13
|
|
86
|
use base qw(Class::Accessor::Fast); |
|
13
|
|
|
|
|
21
|
|
|
13
|
|
|
|
|
26381
|
|
14
|
|
|
|
|
|
|
|
15
|
13
|
|
|
13
|
|
93816
|
use CPAN::DistnameInfo; |
|
13
|
|
|
|
|
22877
|
|
|
13
|
|
|
|
|
1563
|
|
16
|
13
|
|
|
13
|
|
14804
|
use CPAN::Testers::Common::DBUtils; |
|
13
|
|
|
|
|
450963
|
|
|
13
|
|
|
|
|
139
|
|
17
|
13
|
|
|
13
|
|
19424
|
use CPAN::Testers::Common::Article; |
|
13
|
|
|
|
|
306428
|
|
|
13
|
|
|
|
|
1409
|
|
18
|
13
|
|
|
13
|
|
18879
|
use Config::IniFiles; |
|
13
|
|
|
|
|
655879
|
|
|
13
|
|
|
|
|
566
|
|
19
|
13
|
|
|
13
|
|
162
|
use DBI; |
|
13
|
|
|
|
|
24
|
|
|
13
|
|
|
|
|
559
|
|
20
|
13
|
|
|
13
|
|
197
|
use File::Basename; |
|
13
|
|
|
|
|
28
|
|
|
13
|
|
|
|
|
1370
|
|
21
|
13
|
|
|
13
|
|
17563
|
use File::Find::Rule; |
|
13
|
|
|
|
|
151558
|
|
|
13
|
|
|
|
|
143
|
|
22
|
13
|
|
|
13
|
|
802
|
use File::Path; |
|
13
|
|
|
|
|
26
|
|
|
13
|
|
|
|
|
945
|
|
23
|
13
|
|
|
13
|
|
20398
|
use File::Slurp; |
|
13
|
|
|
|
|
257862
|
|
|
13
|
|
|
|
|
1287
|
|
24
|
13
|
|
|
13
|
|
29005
|
use Getopt::Long; |
|
13
|
|
|
|
|
209731
|
|
|
13
|
|
|
|
|
89
|
|
25
|
13
|
|
|
13
|
|
15495
|
use IO::AtomicFile; |
|
13
|
|
|
|
|
43334
|
|
|
13
|
|
|
|
|
794
|
|
26
|
13
|
|
|
13
|
|
254
|
use IO::File; |
|
13
|
|
|
|
|
28
|
|
|
13
|
|
|
|
|
2175
|
|
27
|
13
|
|
|
13
|
|
22193
|
use Net::NNTP; |
|
13
|
|
|
|
|
599718
|
|
|
13
|
|
|
|
|
1314
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
#---------------------------------------------------------------------------- |
30
|
|
|
|
|
|
|
# Variables |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my (%backups); |
33
|
13
|
|
|
13
|
|
180
|
use constant LASTMAIL => '_lastmail'; |
|
13
|
|
|
|
|
31
|
|
|
13
|
|
|
|
|
1081
|
|
34
|
13
|
|
|
13
|
|
79
|
use constant LOGFILE => '_uploads.log'; |
|
13
|
|
|
|
|
27
|
|
|
13
|
|
|
|
|
649
|
|
35
|
13
|
|
|
13
|
|
71
|
use constant JOURNAL => '_journal.sql'; |
|
13
|
|
|
|
|
27
|
|
|
13
|
|
|
|
|
62738
|
|
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
|
18477
|
my $class = shift; |
77
|
|
|
|
|
|
|
|
78
|
14
|
|
|
|
|
36
|
my $self = {}; |
79
|
14
|
|
|
|
|
42
|
bless $self, $class; |
80
|
|
|
|
|
|
|
|
81
|
14
|
|
|
|
|
242
|
$self->_init_options(@_); |
82
|
9
|
|
|
|
|
81
|
return $self; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub DESTROY { |
86
|
14
|
|
|
14
|
|
41475
|
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
|
1155838
|
my $self = shift; |
95
|
5
|
100
|
|
|
|
42
|
$self->generate() if($self->mgenerate); |
96
|
5
|
100
|
|
|
|
83
|
$self->reindex() if($self->mreindex); |
97
|
5
|
100
|
|
|
|
239
|
$self->update() if($self->mupdate); |
98
|
5
|
100
|
|
|
|
426
|
$self->backup() if($self->mbackup); |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub generate { |
102
|
1
|
|
|
1
|
1
|
15
|
my $self = shift; |
103
|
1
|
|
|
|
|
6
|
my $db = $self->uploads; |
104
|
|
|
|
|
|
|
|
105
|
1
|
|
|
|
|
14
|
$self->_log("Restarting uploads database"); |
106
|
1
|
|
|
|
|
84
|
$db->do_query($phrasebook{'DeleteAll'}); |
107
|
|
|
|
|
|
|
|
108
|
1
|
|
|
|
|
37212
|
$self->_log("Creating BACKPAN entries"); |
109
|
1
|
|
|
|
|
105
|
my @files = File::Find::Rule->file()->name($extn)->in($self->backpan); |
110
|
1
|
|
|
|
|
9939
|
$self->_parse_archive('backpan',$_) for(@files); |
111
|
|
|
|
|
|
|
|
112
|
1
|
|
|
|
|
11
|
$self->_log("Creating CPAN entries"); |
113
|
1
|
|
|
|
|
94
|
@files = File::Find::Rule->file()->name($extn)->in($self->cpan); |
114
|
1
|
|
|
|
|
7407
|
$self->_parse_archive('cpan',$_) for(@files); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub reindex { |
118
|
3
|
|
|
3
|
1
|
48
|
my $self = shift; |
119
|
3
|
|
|
|
|
18
|
my $db = $self->uploads; |
120
|
|
|
|
|
|
|
|
121
|
3
|
|
|
|
|
29
|
$self->_log("Reindexing by author"); |
122
|
|
|
|
|
|
|
|
123
|
3
|
|
|
|
|
184
|
my $next = $db->iterator('hash',$phrasebook{'GetAllAuthors'}); |
124
|
3
|
|
|
|
|
838
|
while(my $author = $next->()) { |
125
|
45
|
|
|
|
|
3530384
|
$self->_log(".. author = $author->{author}"); |
126
|
45
|
|
|
|
|
14364
|
my @rows = $db->get_query('hash',$phrasebook{'BuildAuthorIndex'},$author->{author}); |
127
|
45
|
|
|
|
|
23934
|
for my $row (@rows) { |
128
|
51
|
|
|
|
|
246765
|
$self->_log(".... dist = $row->{dist}, latest = $row->{version}"); |
129
|
51
|
|
|
|
|
3098
|
$db->do_query($phrasebook{'DeleteIndex'},$row->{dist},$row->{author}); |
130
|
51
|
|
|
|
|
4441745
|
$db->do_query($phrasebook{'InsertIndex'},$oncpan{$row->{type}},$row->{author},$row->{version},$row->{released},$row->{dist}); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
3
|
|
|
|
|
168907
|
$self->_log("Reindexing authors done"); |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub update { |
138
|
1
|
|
|
1
|
1
|
16
|
my $self = shift; |
139
|
1
|
|
|
|
|
5
|
my $db = $self->uploads; |
140
|
|
|
|
|
|
|
|
141
|
1
|
|
|
|
|
10
|
$self->_open_journal(); |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# get list of db known CPAN distributions |
144
|
1
|
|
|
|
|
277
|
my @rows = $db->get_query('hash',$phrasebook{'FindDistTypes'},'cpan'); |
145
|
1
|
|
|
|
|
1502
|
my %cpan = map {$_->{filename} => $_} @rows; |
|
63
|
|
|
|
|
172
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# get currently mirrored CPAN entries |
148
|
1
|
|
|
|
|
11
|
$self->_log("Updating CPAN entries"); |
149
|
1
|
|
|
|
|
108
|
my @files = File::Find::Rule->file()->name($extn)->in($self->cpan); |
150
|
1
|
|
|
|
|
7219
|
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
|
|
|
|
|
65
|
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
|
|
|
|
|
4
|
$self->_log("Updating NNTP entries"); |
168
|
1
|
|
|
|
|
36
|
my ($nntp,$num,$first,$last) = $self->_nntp_connect(); |
169
|
1
|
|
|
|
|
4
|
my $lastid = $self->_lastid(); |
170
|
1
|
50
|
|
|
|
5
|
return if($last <= $lastid); |
171
|
|
|
|
|
|
|
|
172
|
1
|
|
|
|
|
6
|
$self->_log(".. from $lastid to $last"); |
173
|
1
|
|
|
|
|
30
|
for(my $id = $lastid+1; $id <= $last; $id++) { |
174
|
|
|
|
|
|
|
#$self->_log("NNTP ID = $id"); |
175
|
72870
|
50
|
|
|
|
256457
|
my $article = join "", @{$nntp->article($id) || []}; |
|
72870
|
|
|
|
|
366559
|
|
176
|
72870
|
100
|
|
|
|
4414645
|
next unless($article); |
177
|
4
|
|
|
|
|
57
|
my $object = CPAN::Testers::Common::Article->new($article); |
178
|
4
|
50
|
|
|
|
9021
|
next unless($object); |
179
|
4
|
|
|
|
|
28
|
$self->_log("... [$id] subject=".($object->subject())); |
180
|
|
|
|
|
|
|
|
181
|
4
|
|
|
|
|
189
|
my ($name,$version,$cpanid,$date,$filename); |
182
|
4
|
50
|
|
|
|
21
|
if($object->parse_upload()) { |
183
|
4
|
|
|
|
|
771
|
$name = $object->distribution; |
184
|
4
|
|
|
|
|
26
|
$version = $object->version; |
185
|
4
|
|
|
|
|
23
|
$cpanid = $object->author; |
186
|
4
|
|
|
|
|
26
|
$date = $object->epoch; |
187
|
4
|
|
|
|
|
22
|
$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
|
|
|
65
|
next unless($name && $version && $cpanid && $date); |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
196
|
|
|
|
|
|
|
#$self->_log("upload => $name => $version => $cpanid => $date"); |
197
|
|
|
|
|
|
|
|
198
|
4
|
|
|
|
|
21
|
$self->_update_index($cpanid,$version,$date,$name,1); |
199
|
4
|
|
|
|
|
168173
|
my @rows = $db->get_query('array',$phrasebook{'FindDistVersion'},$cpanid,$name,$version); |
200
|
4
|
100
|
|
|
|
1857
|
next if(@rows); |
201
|
3
|
|
|
|
|
33
|
$self->_write_journal('InsertDistVersion','upload',$cpanid,$name,$version,$filename,$date); |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
1
|
|
|
|
|
11
|
$self->_lastid($last); |
205
|
1
|
|
|
|
|
6
|
$self->_close_journal(); |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub backup { |
209
|
1
|
|
|
1
|
1
|
9
|
my $self = shift; |
210
|
1
|
|
|
|
|
5
|
my $db = $self->uploads; |
211
|
|
|
|
|
|
|
|
212
|
1
|
50
|
|
|
|
10
|
if(my @journals = $self->_find_journals()) { |
213
|
1
|
|
|
|
|
5
|
for my $driver (keys %backups) { |
214
|
1
|
50
|
33
|
|
|
20
|
if($driver =~ /(CSV|SQLite)/i && !$backups{$driver}{'exists'}) { |
215
|
1
|
|
|
|
|
9
|
$backups{$driver}{db}->do_query($phrasebook{'CreateTable'}); |
216
|
1
|
|
|
|
|
85826
|
$backups{$driver}{'exists'} = 1; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
1
|
|
|
|
|
6
|
for my $journal (@journals) { |
221
|
1
|
50
|
|
|
|
13
|
next if($journal =~ /TMP$/); # don't process active journals |
222
|
1
|
|
|
|
|
12
|
$self->_log("Processing journal $journal"); |
223
|
1
|
|
|
|
|
68
|
my $lines = $self->_read_journal($journal); |
224
|
1
|
|
|
|
|
5
|
for my $line (@$lines) { |
225
|
3
|
|
|
|
|
55011
|
my ($phrase,@args) = @$line; |
226
|
3
|
|
|
|
|
14
|
for my $driver (keys %backups) { |
227
|
3
|
|
|
|
|
29
|
$backups{$driver}{db}->do_query($phrasebook{$phrase},@args); |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
1
|
|
|
|
|
18056
|
$self->_done_journal($journal); |
232
|
|
|
|
|
|
|
} |
233
|
1
|
|
|
|
|
58
|
$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
|
|
|
|
123
|
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
|
13
|
my ($self,$full,$mess) = @_; |
269
|
|
|
|
|
|
|
|
270
|
5
|
100
|
|
|
|
535
|
print "\n$mess\n\n" if($mess); |
271
|
|
|
|
|
|
|
|
272
|
5
|
100
|
|
|
|
15
|
if($full) { |
273
|
4
|
|
|
|
|
116
|
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
|
|
|
|
|
98
|
print "$0 v$VERSION\n\n"; |
299
|
5
|
|
|
|
|
21
|
exit(0); |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
#---------------------------------------------------------------------------- |
303
|
|
|
|
|
|
|
# Private Methods |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
sub _parse_archive { |
306
|
189
|
|
|
189
|
|
7118
|
my ($self,$type,$file,$update) = @_; |
307
|
189
|
|
|
|
|
1328
|
my $db = $self->uploads; |
308
|
189
|
|
|
|
|
2612
|
my $dist = CPAN::DistnameInfo->new($file); |
309
|
|
|
|
|
|
|
|
310
|
189
|
|
|
|
|
28896
|
my $name = $dist->dist; # "CPAN-DistnameInfo" |
311
|
189
|
|
|
|
|
1600
|
my $version = $dist->version; # "0.02" |
312
|
189
|
|
|
|
|
1338
|
my $cpanid = $dist->cpanid; # "GBARR" |
313
|
189
|
|
|
|
|
1565
|
my $filename = $dist->filename; # "CPAN-DistnameInfo-0.02.tar.gz" |
314
|
189
|
|
|
|
|
13560
|
my $date = (stat($file))[9]; |
315
|
|
|
|
|
|
|
|
316
|
189
|
50
|
33
|
|
|
4067
|
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
|
|
|
|
|
1678
|
my @rows = $db->get_query('array',$phrasebook{'FindDistVersion'},$cpanid,$name,$version); |
325
|
189
|
100
|
|
|
|
99087
|
if(@rows) { |
326
|
126
|
100
|
|
|
|
949
|
if($type ne $rows[0]->[0]) { |
327
|
63
|
|
|
|
|
399
|
$db->do_query($phrasebook{'UpdateDistVersion'},$type,$cpanid,$name,$version); |
328
|
63
|
50
|
33
|
|
|
2362521
|
$self->_update_index($cpanid,$version,$date,$name,$oncpan{$type}) |
329
|
|
|
|
|
|
|
if($update && $type ne 'backpan'); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} else { |
332
|
63
|
|
|
|
|
609
|
$db->do_query($phrasebook{'InsertDistVersion'},$type,$cpanid,$name,$version,$filename,$date); |
333
|
63
|
50
|
|
|
|
1478064
|
$self->_update_index($cpanid,$version,$date,$name,$oncpan{$type}) if($update); |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
189
|
|
|
|
|
3073
|
return $filename; |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub _update_index { |
340
|
4
|
|
|
4
|
|
10
|
my ($self,$author,$version,$date,$name,$oncpan) = @_; |
341
|
4
|
|
|
|
|
17
|
my $db = $self->uploads; |
342
|
|
|
|
|
|
|
|
343
|
4
|
|
|
|
|
43
|
my @index = $db->get_query('hash',$phrasebook{'FindIndex'},$name,$author); |
344
|
4
|
100
|
|
|
|
1529
|
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
|
|
|
|
|
256638
|
$self->_log("... index update [$author,$version,$date,$name,$oncpan]"); |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
} else { |
350
|
1
|
|
|
|
|
15
|
$db->do_query($phrasebook{'InsertIndex'},$oncpan,$author,$version,$date,$name); |
351
|
1
|
|
|
|
|
63576
|
$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
|
|
|
|
|
164
|
$db->do_query($phrasebook{'InsertRequest'},'ixauth',substr($author,0,1)); |
356
|
4
|
|
|
|
|
329138
|
$db->do_query($phrasebook{'InsertRequest'},'ixdist',substr($name,0,1)); |
357
|
4
|
|
|
|
|
276010
|
$db->do_query($phrasebook{'InsertRequest'},'author',$author); |
358
|
4
|
|
|
|
|
170305
|
$db->do_query($phrasebook{'InsertRequest'},'distro',$name); |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
sub _nntp_connect { |
362
|
|
|
|
|
|
|
# connect to NNTP server |
363
|
1
|
50
|
|
1
|
|
13
|
my $nntp = Net::NNTP->new("nntp.perl.org") or die "Cannot connect to nntp.perl.org"; |
364
|
1
|
|
|
|
|
30
|
my ($num,$first,$last) = $nntp->group("perl.cpan.uploads"); |
365
|
|
|
|
|
|
|
|
366
|
1
|
|
|
|
|
89
|
return ($nntp,$num,$first,$last); |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub _lastid { |
370
|
6
|
|
|
6
|
|
1729
|
my ($self,$id) = @_; |
371
|
6
|
|
|
|
|
32
|
my $f = $self->lastfile; |
372
|
|
|
|
|
|
|
|
373
|
6
|
100
|
|
|
|
171
|
unless( -f $f) { |
374
|
2
|
|
|
|
|
244
|
mkpath(dirname($f)); |
375
|
2
|
|
|
|
|
18
|
overwrite_file( $f, 0 ); |
376
|
2
|
|
50
|
|
|
612
|
$id ||= 0; |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
6
|
100
|
|
|
|
21
|
if($id) { overwrite_file( $f, $id ); } |
|
2
|
|
|
|
|
32
|
|
380
|
4
|
|
|
|
|
20
|
else { $id = read_file($f); } |
381
|
|
|
|
|
|
|
|
382
|
6
|
|
|
|
|
878
|
return $id; |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
# generate atomic journal file name |
386
|
|
|
|
|
|
|
sub _open_journal { |
387
|
1
|
|
|
1
|
|
3
|
my $self = shift; |
388
|
1
|
|
|
|
|
43
|
my @now = localtime(time); |
389
|
1
|
|
|
|
|
6
|
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
|
|
|
|
23
|
$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
|
|
59
|
my ($self,$phrase,@args) = @_; |
395
|
3
|
|
|
|
|
15
|
my $fh = $self->{current}; |
396
|
|
|
|
|
|
|
|
397
|
3
|
|
|
|
|
148
|
print $fh "$phrase," . join(',',@args) . "\n"; |
398
|
|
|
|
|
|
|
|
399
|
3
|
|
|
|
|
24
|
my $db = $self->uploads; |
400
|
3
|
|
|
|
|
51
|
$db->do_query($phrasebook{$phrase},@args); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub _close_journal { |
404
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
405
|
1
|
|
|
|
|
9
|
$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
|
|
|
|
|
145
|
return @files; |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
sub _read_journal { |
415
|
1
|
|
|
1
|
|
3
|
my ($self,$journal) = @_; |
416
|
1
|
|
|
|
|
183
|
my @lines; |
417
|
|
|
|
|
|
|
|
418
|
1
|
50
|
|
|
|
9
|
my $fh = IO::File->new($journal,'r') or die "Cannot read journal file [$journal]: $!\n"; |
419
|
1
|
|
|
|
|
762
|
while(<$fh>) { |
420
|
3
|
|
|
|
|
22
|
my @fields = split(/,/); |
421
|
3
|
|
|
|
|
18
|
push @lines, \@fields; |
422
|
|
|
|
|
|
|
} |
423
|
1
|
|
|
|
|
5
|
$fh->close; |
424
|
1
|
|
|
|
|
23
|
return \@lines; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
sub _done_journal { |
428
|
1
|
|
|
1
|
|
5
|
my ($self,$journal) = @_; |
429
|
1
|
|
|
|
|
5
|
my $cmd = "mv $journal logs"; |
430
|
1
|
|
|
|
|
17720
|
system($cmd); |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub _init_options { |
434
|
14
|
|
|
14
|
|
31
|
my $self = shift; |
435
|
14
|
|
|
|
|
574
|
my %hash = @_; |
436
|
14
|
|
|
|
|
188
|
my %options; |
437
|
|
|
|
|
|
|
|
438
|
14
|
|
|
|
|
307
|
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
|
|
|
|
|
10817
|
for(qw(config generate update reindex fast backup help version)) { |
454
|
112
|
100
|
33
|
|
|
447
|
$options{$_} ||= $hash{$_} if(defined $hash{$_}); |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
14
|
100
|
|
|
|
79
|
$self->help(1) if($options{help}); |
458
|
13
|
100
|
|
|
|
52
|
$self->help(0) if($options{version}); |
459
|
|
|
|
|
|
|
|
460
|
12
|
100
|
100
|
|
|
110
|
$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
|
|
|
|
42
|
$self->help(1,"Must specific the configuration file") unless( $options{config}); |
463
|
10
|
100
|
|
|
|
280
|
$self->help(1,"Configuration file [$options{config}] not found") unless(-f $options{config}); |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
# load configuration |
466
|
9
|
|
|
|
|
131
|
my $cfg = Config::IniFiles->new( -file => $options{config} ); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# configure sources |
469
|
9
|
100
|
|
|
|
35662
|
if($options{generate}) { |
470
|
1
|
|
|
|
|
8
|
my $dir = $cfg->val('MASTER','BACKPAN'); |
471
|
1
|
50
|
|
|
|
27
|
$self->help(1,"No source location for 'BACKPAN' in config file") if(! $dir); |
472
|
1
|
50
|
|
|
|
43
|
$self->help(1,"Cannot find source location for 'BACKPAN': [$dir]") if(!-d $dir); |
473
|
1
|
|
|
|
|
7
|
$self->backpan($dir); |
474
|
1
|
|
|
|
|
28
|
$self->mgenerate(1); |
475
|
1
|
|
|
|
|
9
|
$self->mreindex(1); |
476
|
|
|
|
|
|
|
} |
477
|
9
|
100
|
100
|
|
|
91
|
if($options{generate} || $options{update}) { |
478
|
7
|
|
|
|
|
34
|
my $dir = $cfg->val('MASTER','CPAN'); |
479
|
7
|
50
|
|
|
|
165
|
$self->help(1,"No source location for 'CPAN' in config file") if(! $dir); |
480
|
7
|
50
|
|
|
|
195
|
$self->help(1,"Cannot find source location for 'CPAN': [$dir]") if(!-d $dir); |
481
|
7
|
|
|
|
|
40
|
$self->cpan($dir); |
482
|
|
|
|
|
|
|
} |
483
|
9
|
100
|
|
|
|
128
|
if($options{reindex}) { |
484
|
1
|
|
|
|
|
7
|
$self->mreindex(1); |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
9
|
100
|
|
|
|
76
|
$self->mupdate(1) if($options{update}); |
488
|
9
|
|
50
|
|
|
131
|
$self->logfile( $hash{logfile} || $options{logfile} || $cfg->val('MASTER','logfile' ) || LOGFILE ); |
489
|
9
|
|
100
|
|
|
389
|
$self->logclean( $hash{logclean} || $options{logclean} || $cfg->val('MASTER','logclean' ) || 0 ); |
490
|
9
|
|
50
|
|
|
344
|
$self->lastfile( $hash{lastfile} || $options{lastfile} || $cfg->val('MASTER','lastfile' ) || LASTMAIL ); |
491
|
9
|
|
100
|
|
|
314
|
$self->journal( $hash{journal} || $options{journal} || $cfg->val('MASTER','journal' ) || JOURNAL ); |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
# configure upload DB |
494
|
9
|
50
|
|
|
|
278
|
$self->help(1,"No configuration for UPLOADS database") unless($cfg->SectionExists('UPLOADS')); |
495
|
9
|
|
100
|
|
|
211
|
my %opts = map {$_ => ($cfg->val('UPLOADS',$_) || undef)} qw(driver database dbfile dbhost dbport dbuser dbpass); |
|
63
|
|
|
|
|
1246
|
|
496
|
9
|
|
|
|
|
342
|
my $db = CPAN::Testers::Common::DBUtils->new(%opts); |
497
|
9
|
50
|
|
|
|
308
|
$self->help(1,"Cannot configure UPLOADS database") unless($db); |
498
|
9
|
|
|
|
|
132
|
$self->uploads($db); |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# configure backup DBs |
501
|
9
|
100
|
|
|
|
250
|
if($options{backup}) { |
502
|
1
|
50
|
|
|
|
5
|
$self->help(1,"No configuration for BACKUPS with backup option") unless($cfg->SectionExists('BACKUPS')); |
503
|
|
|
|
|
|
|
|
504
|
1
|
|
|
|
|
34
|
my %available_drivers = map { $_ => 1 } DBI->available_drivers; |
|
7
|
|
|
|
|
635
|
|
505
|
1
|
|
|
|
|
7
|
my @drivers = $cfg->val('BACKUPS','drivers'); |
506
|
1
|
|
|
|
|
28
|
for my $driver (@drivers) { |
507
|
2
|
100
|
|
|
|
8
|
unless($available_drivers{$driver}) { |
508
|
1
|
|
|
|
|
169
|
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
|
|
|
27
|
my %opt = map {$_ => ($cfg->val($driver,$_) || undef)} qw(driver database dbfile dbhost dbport dbuser dbpass); |
|
7
|
|
|
|
|
146
|
|
515
|
1
|
50
|
|
|
|
68
|
$backups{$driver}{'exists'} = $driver =~ /SQLite/i ? -f $opt{database} : 1; |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
# CSV is a bit of an oddity! |
518
|
1
|
50
|
|
|
|
6
|
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
|
|
|
|
|
11
|
$backups{$driver}{db} = CPAN::Testers::Common::DBUtils->new(%opt); |
526
|
1
|
50
|
|
|
|
26
|
$self->help(1,"Cannot configure BACKUPS database for '$driver'") unless($backups{$driver}{db}); |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
1
|
50
|
|
|
|
12
|
$self->mbackup(1) if(keys %backups); |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
sub _log { |
534
|
122
|
|
|
122
|
|
4355
|
my $self = shift; |
535
|
122
|
50
|
|
|
|
660
|
my $log = $self->logfile or return; |
536
|
122
|
100
|
|
|
|
4499
|
mkpath(dirname($log)) unless(-f $log); |
537
|
|
|
|
|
|
|
|
538
|
122
|
100
|
|
|
|
652
|
my $mode = $self->logclean ? 'w+' : 'a+'; |
539
|
122
|
|
|
|
|
1341
|
$self->logclean(0); |
540
|
|
|
|
|
|
|
|
541
|
122
|
|
|
|
|
4491
|
my @dt = localtime(time); |
542
|
122
|
|
|
|
|
1325
|
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
|
|
|
|
1517
|
my $fh = IO::File->new($log,$mode) or die "Cannot write to log file [$log]: $!\n"; |
545
|
122
|
|
|
|
|
20222
|
print $fh "$dt ", @_, "\n"; |
546
|
122
|
|
|
|
|
721
|
$fh->close; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
q!Will code for a damn fine Balti!; |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
__END__ |