File Coverage

blib/lib/CPAN/SQLite/META.pm
Criterion Covered Total %
statement 195 246 79.2
branch 16 48 33.3
condition 5 18 27.7
subroutine 41 46 89.1
pod 1 15 6.6
total 258 373 69.1


line stmt bran cond sub pod time code
1             # $Id: META.pm 85 2022-10-29 05:44:36Z stro $
2              
3             package CPAN::SQLite::META;
4 3     3   27 use strict;
  3         7  
  3         96  
5 3     3   35 use warnings;
  3         11  
  3         163  
6             our $VERSION = '0.220';
7              
8 3     3   19 use English qw/-no_match_vars/;
  3         6  
  3         18  
9              
10             require CPAN::SQLite;
11 3     3   4382 use DBI;
  3         38487  
  3         169  
12 3     3   121 use File::Spec;
  3         10  
  3         112  
13              
14 3     3   17 use parent 'Exporter';
  3         7  
  3         44  
15             our @EXPORT_OK;
16             @EXPORT_OK = qw(setup update check);
17             our $global_id;
18              
19             # This is usually already defined in real life, but tests need it to be set
20             $CPAN::FrontEnd ||= "CPAN::Shell";
21              
22             sub new {
23 2     2 0 720 my ($class, $cpan_meta) = @_;
24 2         13 my $cpan_sqlite = CPAN::SQLite->new();
25 2         16 return bless { cpan_meta => $cpan_meta, cpan_sqlite => $cpan_sqlite }, $class;
26             }
27              
28             sub set {
29 921     921 1 1880058 my ($self, $class, $id) = @_;
30 921         2605 my $sqlite_obj = $self->make_obj(class => $class, id => $id);
31 921         2300 return $sqlite_obj->set_one();
32             }
33              
34             sub search {
35 26     26 0 332570 my ($self, $class, $regex) = @_;
36 26         118 my $sqlite_obj = $self->make_obj(class => $class, regex => $regex);
37 26         141 return $sqlite_obj->set_many();
38             }
39              
40             sub make_obj {
41 947     947 0 3163 my ($self, %args) = @_;
42 947         1721 my $class = $args{class};
43 947 50 33     6263 die qq{Must supply a CPAN::* class string}
44             unless ($class and $class =~ /^CPAN::/);
45 947         3587 (my $type = $class) =~ s/^CPAN//;
46 947         2195 my $package = __PACKAGE__ . $type;
47             return bless {
48             cpan_meta => $self->{cpan_meta},
49             cpan_sqlite => $self->{cpan_sqlite},
50             class => $class,
51             id => $args{id},
52             regex => $args{regex},
53 947         5523 }, $package;
54             }
55              
56             package CPAN::SQLite::META::Author;
57 3     3   1361 use parent 'CPAN::SQLite::META';
  3         9  
  3         26  
58 3     3   239 use CPAN::SQLite::Util qw(has_hash_data);
  3         6  
  3         945  
59              
60             sub set_one {
61 10     10   22 my $self = shift;
62 10         35 my $cpan_sqlite = $self->{cpan_sqlite};
63 10         25 my $id = $self->{id};
64 10         21 my $class = $self->{class};
65 10         26 $cpan_sqlite->{results} = {};
66 10         50 $cpan_sqlite->query(mode => 'author', name => $id, meta_obj => $self);
67 10         23 my $cpan_meta = $self->{cpan_meta};
68 10         68 return $cpan_meta->{readonly}{$class}{$id};
69             }
70              
71             sub set_many {
72 6     6   17 my $self = shift;
73 6         17 my $cpan_sqlite = $self->{cpan_sqlite};
74 6         18 my $regex = $self->{regex};
75 6         17 $cpan_sqlite->{results} = [];
76 6         34 return $cpan_sqlite->query(mode => 'author', query => $regex, meta_obj => $self);
77             }
78              
79             sub set_data {
80 16     16   48 my ($self, $results) = @_;
81 16         76 return $self->set_author($results->{cpanid}, $results);
82             }
83              
84             package CPAN::SQLite::META::Distribution;
85 3     3   27 use parent 'CPAN::SQLite::META';
  3         15  
  3         23  
86 3     3   226 use CPAN::SQLite::Util qw(has_hash_data download);
  3         7  
  3         193  
87 3     3   20 use CPAN::DistnameInfo;
  3         20  
  3         1211  
88             my $ext = qr{\.(tar\.gz|tar\.Z|tgz|zip)$};
89              
90             sub set_one {
91 0     0   0 my $self = shift;
92 0         0 my $cpan_sqlite = $self->{cpan_sqlite};
93 0         0 my $id = $self->{id};
94 0         0 my ($dist_name, $dist_id);
95 0 0       0 if ($id =~ /$ext/) {
96 0         0 ($dist_name, $dist_id) = $self->extract_distinfo($id);
97             }
98 0 0 0     0 return unless ($dist_name and $dist_id);
99 0         0 my $class = $self->{class};
100 0         0 $cpan_sqlite->{results} = {};
101 0         0 $cpan_sqlite->query(mode => 'dist', name => $dist_name, meta_obj => $self);
102 0         0 my $cpan_meta = $self->{cpan_meta};
103 0         0 return $cpan_meta->{readonly}{$class}{$dist_id};
104             }
105              
106             sub set_many {
107 8     8   22 my $self = shift;
108 8         33 my $cpan_sqlite = $self->{cpan_sqlite};
109 8         24 my $regex = $self->{regex};
110 8         24 $cpan_sqlite->{results} = [];
111 8         43 return $cpan_sqlite->query(mode => 'dist', query => $regex, meta_obj => $self);
112             }
113              
114             sub set_data {
115 52     52   98 my ($self, $results) = @_;
116 52         90 $global_id = $results->{download};
117 52         123 return $self->set_dist($results->{download}, $results);
118             }
119              
120             sub set_list_data {
121 52     52   123 my ($self, $results, $download) = @_;
122 52         96 $global_id = $download;
123 52         143 $self->set_containsmods($results);
124 52         104 $global_id = undef;
125 52         132 return;
126             }
127              
128             package CPAN::SQLite::META::Module;
129 3     3   23 use parent 'CPAN::SQLite::META';
  3         5  
  3         14  
130 3     3   222 use CPAN::SQLite::Util qw(has_hash_data);
  3         7  
  3         1078  
131              
132             sub set_one {
133 901     901   1395 my $self = shift;
134 901         1431 my $cpan_sqlite = $self->{cpan_sqlite};
135 901         1425 my $id = $self->{id};
136 901 50       2239 return if ($id =~ /^Bundle::/);
137 901         1462 my $class = $self->{class};
138 901         1773 $cpan_sqlite->{results} = {};
139 901         3003 $cpan_sqlite->query(mode => 'module', name => $id, meta_obj => $self);
140 901         1563 my $cpan_meta = $self->{cpan_meta};
141 901         4121 return $cpan_meta->{readonly}{$class}{$id};
142             }
143              
144             sub set_many {
145 8     8   22 my $self = shift;
146 8         51 my $cpan_sqlite = $self->{cpan_sqlite};
147 8         24 my $regex = $self->{regex};
148 8         50 $cpan_sqlite->{results} = [];
149 8         61 return $cpan_sqlite->query(mode => 'module', query => $regex, meta_obj => $self);
150             }
151              
152             sub set_data {
153 1135     1135   2141 my ($self, $results) = @_;
154 1135         3076 $self->set_module($results->{mod_name}, $results);
155 1135         35021 $global_id = $results->{download};
156 1135         2477 return $self->set_dist($results->{download}, $results);
157             }
158              
159             sub set_list_data {
160 927     927   2061 my ($self, $results, $download) = @_;
161 927         1601 $global_id = $download;
162 927         2381 $self->set_containsmods($results);
163 927         1644 $global_id = undef;
164 927         1952 return;
165             }
166              
167             package CPAN::SQLite::META::Bundle;
168 3     3   22 use parent 'CPAN::SQLite::META';
  3         16  
  3         17  
169 3     3   203 use CPAN::SQLite::Util qw(has_hash_data);
  3         7  
  3         1252  
170              
171             sub set_one {
172 10     10   36 my $self = shift;
173 10         22 my $cpan_sqlite = $self->{cpan_sqlite};
174 10         20 my $id = $self->{id};
175 10 50       46 unless ($id =~ /^Bundle::/) {
176 0         0 $id = 'Bundle::' . $id;
177             }
178 10         20 my $class = $self->{class};
179 10         24 $cpan_sqlite->{results} = {};
180 10         42 $cpan_sqlite->query(mode => 'module', name => $id, meta_obj => $self);
181 10         34 my $cpan_meta = $self->{cpan_meta};
182 10         60 return $cpan_meta->{readonly}{$class}{$id};
183             }
184              
185             sub set_many {
186 4     4   11 my $self = shift;
187 4         15 my $cpan_sqlite = $self->{cpan_sqlite};
188 4         13 my $regex = $self->{regex};
189 4 50       44 unless ($regex =~ /(^Bundle::|[\^\$\*\+\?\|])/i) {
190 4         17 $regex = '^Bundle::' . $regex;
191             }
192 4 50       15 $regex = '^Bundle::' if $regex eq '^';
193 4         13 $cpan_sqlite->{results} = [];
194 4         20 return $cpan_sqlite->query(mode => 'module', query => $regex, meta_obj => $self);
195             }
196              
197             sub set_data {
198 18     18   45 my ($self, $results) = @_;
199 18         105 $self->set_bundle($results->{mod_name}, $results);
200 18         564 $global_id = $results->{download};
201 18         51 return $self->set_dist($results->{download}, $results);
202             }
203              
204             sub set_list_data {
205 16     16   49 my ($self, $results, $download) = @_;
206 16         37 $global_id = $download;
207 16         66 $self->set_containsmods($results);
208 16         77 $global_id = undef;
209 16         41 return;
210             }
211              
212             package CPAN::SQLite::META;
213 3     3   24 use CPAN::SQLite::Util qw(download);
  3         5  
  3         4744  
214              
215             my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
216             my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
217              
218             sub set_author {
219 16     16 0 40 my ($self, $id, $results) = @_;
220 16         29 my $class = 'CPAN::Author';
221 16         26 my $cpan_meta = $self->{cpan_meta};
222             return $cpan_meta->instance($class => $id)->set(
223             'FULLNAME' => $results->{fullname},
224             'EMAIL' => $results->{email},
225 16         60 );
226             }
227              
228             sub set_module {
229 1135     1135 0 2112 my ($self, $id, $results) = @_;
230 1135         1637 my $class = 'CPAN::Module';
231 1135         1843 my $cpan_meta = $self->{cpan_meta};
232 1135         3347 my $d = $cpan_meta->instance($class => $id);
233             return $d->set(
234             'description' => $results->{mod_abs},
235             'userid' => $results->{cpanid},
236             'CPAN_VERSION' => $results->{mod_vers},
237             'CPAN_FILE' => $results->{download},
238             'CPAN_USERID' => $results->{cpanid},
239 1135         58288 );
240             }
241              
242             sub set_bundle {
243 18     18 0 42 my ($self, $id, $results) = @_;
244 18         38 my $class = 'CPAN::Bundle';
245 18         36 my $cpan_meta = $self->{cpan_meta};
246 18         65 my $d = $cpan_meta->instance($class => $id);
247             return $d->set(
248             'description' => $results->{mod_abs},
249             'userid' => $results->{cpanid},
250             'CPAN_VERSION' => $results->{mod_vers},
251             'CPAN_FILE' => $results->{download},
252             'CPAN_USERID' => $results->{cpanid},
253 18         981 );
254             }
255              
256             sub set_dist {
257 1205     1205 0 2139 my ($self, $id, $results) = @_;
258 1205         1723 my $class = 'CPAN::Distribution';
259 1205         1882 my $cpan_meta = $self->{cpan_meta};
260 1205         2628 my $d = $cpan_meta->instance($class => $id);
261             return $d->set(
262             'DESCRIPTION' => $results->{dist_abs},
263             'CPAN_USERID' => $results->{cpanid},
264             'CPAN_VERSION' => $results->{dist_vers},
265 1205         45872 );
266             }
267              
268             sub set_containsmods {
269 995     995 0 1725 my ($self, $mods) = @_;
270 995         1523 my $class = 'CPAN::Distribution';
271 995         1895 my $cpan_meta = $self->{cpan_meta};
272 995         1328 my %containsmods;
273 995 50 33     4363 if ($mods and (ref($mods) eq 'ARRAY')) {
274 995         2141 %containsmods = map { $_->{mod_name} => 1 } @$mods;
  22772         47789  
275             }
276 995         4785 my $d = $cpan_meta->instance($class => $global_id);
277 995         45353 return $d->{CONTAINSMODS} = \%containsmods;
278             }
279              
280             sub reload {
281 2     2 0 145494 my ($self, %args) = @_;
282              
283 2   33     20 my $time = $args{'time'} || time;
284 2         6 my $force = $args{force};
285 2         5 my $db_name = $CPAN::SQLite::db_name;
286 2         36 my $db = File::Spec->catfile($CPAN::Config->{cpan_home}, $db_name);
287 2         8 my $journal_file = $db . '-journal';
288 2 50       35 if (-e $journal_file) {
289 0         0 $CPAN::FrontEnd->mywarn('Database locked - cannot update.');
290 0         0 return;
291             }
292 2         10 my @args = ($^X, '-MCPAN::SQLite::META=setup,update,check', '-e');
293 2 100 66     47 if (-e $db && -s _) {
294 1         5 my $mtime_db = (stat(_))[9];
295 1         5 my $time_string = gmtime_string($mtime_db);
296 1         12 $CPAN::FrontEnd->myprint("Database was generated on $time_string\n");
297              
298             # Check for status, force update if it fails
299 1 50       379366 if (system(@args, 'check')) {
300 0         0 $force = 1;
301 0         0 $CPAN::FrontEnd->myprint("Database file requires reindexing\n");
302             }
303              
304 1 50       234 unless ($force) {
305 1 50       120 return if (($time - $mtime_db) < $CPAN::Config->{index_expire} * 86400);
306             }
307 0         0 $CPAN::FrontEnd->myprint('Updating database file ... ');
308 0         0 push @args, q{update};
309             } else {
310 1 50       5 unlink($db) if -e _;
311 1         20 $CPAN::FrontEnd->myprint('Creating database file ... ');
312 1         5 push @args, q{setup};
313             }
314 1 50       4 if ($CPAN::SQLite::DBI::dbh) {
315 0         0 $CPAN::SQLite::DBI::dbh->disconnect();
316 0         0 $CPAN::SQLite::DBI::dbh = undef;
317             }
318 1 50       614709 system(@args) == 0 or die qq{system @args failed: $?};
319 1         84 $CPAN::FrontEnd->myprint("Done!\n");
320 1         73 return 1;
321             }
322              
323             sub setup {
324 0     0 0 0 my $obj = CPAN::SQLite->new(setup => 1);
325 0 0       0 $obj->index() or die qq{CPAN::SQLite setup failed};
326 0         0 return;
327             }
328              
329             sub update {
330 0     0 0 0 my $obj = CPAN::SQLite->new();
331 0 0       0 $obj->index() or die qq{CPAN::SQLite update failed};
332 0         0 return;
333             }
334              
335             sub check {
336 0     0 0 0 my $obj = CPAN::SQLite->new();
337 0         0 my $db = File::Spec->catfile($obj->{'db_dir'}, $obj->{'db_name'});
338 0         0 my $dbh = DBI->connect("DBI:SQLite:$db", '', '', { 'RaiseError' => 0, 'PrintError' => 0, 'AutoCommit' => 1 });
339 0 0       0 if (my $sth = $dbh->prepare('SELECT status FROM info WHERE status = 1')) {
340 0 0       0 if ($sth->execute()) {
341 0 0       0 if ($sth->fetchrow_arrayref()) {
342 0         0 exit 0; # status = 1
343             } else {
344 0         0 exit 1; # status <> 1, need reindexing
345             }
346             } else {
347              
348             # Something's wrong, will be safer to reinitialize
349 0         0 $dbh->disconnect();
350 0         0 undef $dbh;
351 0         0 setup();
352 0         0 update();
353             }
354             } else {
355              
356             # Probably old version of DB or no DB at all, run setup and update
357 0         0 $dbh->disconnect();
358 0         0 undef $dbh;
359 0         0 setup();
360 0         0 update();
361             }
362 0         0 return;
363             }
364              
365             sub gmtime_string {
366 1     1 0 3 my $time = shift;
367 1 50       3 return unless $time;
368 1         14 my @a = gmtime($time);
369 1         11 my $string =
370             sprintf("%s, %02d %s %d %02d:%02d:%02d GMT", $days[$a[6]], $a[3], $months[$a[4]], $a[5] + 1900, $a[2], $a[1], $a[0]);
371 1         4 return $string;
372             }
373              
374             sub extract_distinfo {
375 0     0 0   my ($self, $pathname) = @_;
376 0 0         unless ($pathname =~ m{^\w/\w\w/}) {
377 0           $pathname =~ s{^(\w)(\w)(.*)}{$1/$1$2/$1$2$3};
378             }
379 0           my $d = CPAN::DistnameInfo->new($pathname);
380 0           my $dist = $d->dist;
381 0           my $download = download($d->cpanid, $d->filename);
382 0 0 0       return ($dist and $download) ? ($dist, $download) : undef;
383             }
384              
385             1;
386              
387             =head1 NAME
388              
389             CPAN::SQLite::META - helper module for CPAN.pm integration
390              
391             =head1 VERSION
392              
393             version 0.220
394              
395             =head1 DESCRIPTION
396              
397             This module has no direct public interface, but is intended
398             as a helper module for use of CPAN::SQLite within the CPAN.pm
399             module. A new object is created as
400              
401             my $obj = CPAN::SQLite::META->new($CPAN::META);
402              
403             where C<$CPAN::META> comes from CPAN.pm. There are then
404             two main methods available.
405              
406             =over 4
407              
408             =item C
409              
410             This is used as
411              
412             $obj->set($class, $id);
413              
414             where C<$class> is one of C, C, or
415             C, and C<$id> is the id CPAN.pm uses to
416             identify the class. The method searches the C
417             database by name using the appropriate C, C,
418             or C mode, and if a result is found, calls
419              
420             $CPAN::META->instance(
421             $class => $id
422             )->set(
423             %attributes
424             );
425              
426             to register an instance of this class within C.
427              
428             =item C
429              
430             This is used as
431              
432             $obj->search($class, $id);
433              
434             where C<$class> is one of C, C, or
435             C, and C<$id> is the id CPAN.pm uses to
436             identify the class. The method searches the C
437             database by C using the appropriate C, C,
438             or C mode, and if results are found, calls
439              
440             $CPAN::META->instance(
441             $class => $id
442             )->set(
443             %attributes
444             );
445              
446             for each match to register an instance of this class
447             within C.
448              
449             =back
450              
451             The attributes set within C<$CPAN::META->instance> depend
452             on the particular class.
453              
454             =over
455              
456             =item author
457              
458             The attributes are
459              
460             'FULLNAME' => $results->{fullname},
461             'EMAIL' => $results->{email},
462              
463             where C<$results> are the results returned from C.
464              
465             =item module
466              
467             The attributes are
468              
469             'description' => $results->{mod_abs},
470             'userid' => $results->{cpanid},
471             'CPAN_VERSION' => $results->{mod_vers},
472             'CPAN_FILE' => $results->{download},
473             'CPAN_USERID' => $results->{cpanid},
474              
475             where C<$results> are the results returned from C.
476              
477             =item dist
478              
479             The attributes are
480              
481             'DESCRIPTION' => $results->{dist_abs},
482             'CPAN_USERID' => $results->{cpanid},
483             'CPAN_VERSION' => $results->{dist_vers},
484              
485             As well, a C key to C<$CPAN::META> is added, this
486             being a hash reference whose keys are the modules contained
487             within the distribution.
488              
489             =back
490              
491             There is also a method available C, which rebuilds
492             the database. It can be used as
493              
494             $obj->reload(force => 1, time => $time);
495              
496             The C
497             current time) will be used to compare the current time to
498             the mtime of the database file; if they differ by more than
499             one day, the database will be rebuilt. The option, if
500             given, will force a rebuilding of the database regardless
501             of the time difference.
502              
503             =cut