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 84 2020-05-31 06:29:34Z stro $
2              
3             package CPAN::SQLite::META;
4 3     3   23 use strict;
  3         7  
  3         103  
5 3     3   18 use warnings;
  3         7  
  3         160  
6             our $VERSION = '0.219';
7              
8 3     3   28 use English qw/-no_match_vars/;
  3         9  
  3         26  
9              
10             require CPAN::SQLite;
11 3     3   4440 use DBI;
  3         37891  
  3         188  
12 3     3   141 use File::Spec;
  3         8  
  3         116  
13              
14 3     3   19 use parent 'Exporter';
  3         7  
  3         50  
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 723 my ($class, $cpan_meta) = @_;
24 2         16 my $cpan_sqlite = CPAN::SQLite->new();
25 2         11 return bless { cpan_meta => $cpan_meta, cpan_sqlite => $cpan_sqlite }, $class;
26             }
27              
28             sub set {
29 921     921 1 1926956 my ($self, $class, $id) = @_;
30 921         2532 my $sqlite_obj = $self->make_obj(class => $class, id => $id);
31 921         2332 return $sqlite_obj->set_one();
32             }
33              
34             sub search {
35 26     26 0 336157 my ($self, $class, $regex) = @_;
36 26         128 my $sqlite_obj = $self->make_obj(class => $class, regex => $regex);
37 26         160 return $sqlite_obj->set_many();
38             }
39              
40             sub make_obj {
41 947     947 0 3081 my ($self, %args) = @_;
42 947         1832 my $class = $args{class};
43 947 50 33     6301 die qq{Must supply a CPAN::* class string}
44             unless ($class and $class =~ /^CPAN::/);
45 947         3480 (my $type = $class) =~ s/^CPAN//;
46 947         2084 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         5396 }, $package;
54             }
55              
56             package CPAN::SQLite::META::Author;
57 3     3   1364 use parent 'CPAN::SQLite::META';
  3         7  
  3         12  
58 3     3   240 use CPAN::SQLite::Util qw(has_hash_data);
  3         7  
  3         990  
59              
60             sub set_one {
61 10     10   20 my $self = shift;
62 10         33 my $cpan_sqlite = $self->{cpan_sqlite};
63 10         20 my $id = $self->{id};
64 10         21 my $class = $self->{class};
65 10         24 $cpan_sqlite->{results} = {};
66 10         49 $cpan_sqlite->query(mode => 'author', name => $id, meta_obj => $self);
67 10         33 my $cpan_meta = $self->{cpan_meta};
68 10         63 return $cpan_meta->{readonly}{$class}{$id};
69             }
70              
71             sub set_many {
72 6     6   16 my $self = shift;
73 6         16 my $cpan_sqlite = $self->{cpan_sqlite};
74 6         16 my $regex = $self->{regex};
75 6         16 $cpan_sqlite->{results} = [];
76 6         42 return $cpan_sqlite->query(mode => 'author', query => $regex, meta_obj => $self);
77             }
78              
79             sub set_data {
80 16     16   38 my ($self, $results) = @_;
81 16         73 return $self->set_author($results->{cpanid}, $results);
82             }
83              
84             package CPAN::SQLite::META::Distribution;
85 3     3   24 use parent 'CPAN::SQLite::META';
  3         14  
  3         15  
86 3     3   205 use CPAN::SQLite::Util qw(has_hash_data download);
  3         17  
  3         200  
87 3     3   39 use CPAN::DistnameInfo;
  3         7  
  3         1223  
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   25 my $self = shift;
108 8         35 my $cpan_sqlite = $self->{cpan_sqlite};
109 8         21 my $regex = $self->{regex};
110 8         22 $cpan_sqlite->{results} = [];
111 8         47 return $cpan_sqlite->query(mode => 'dist', query => $regex, meta_obj => $self);
112             }
113              
114             sub set_data {
115 52     52   100 my ($self, $results) = @_;
116 52         88 $global_id = $results->{download};
117 52         129 return $self->set_dist($results->{download}, $results);
118             }
119              
120             sub set_list_data {
121 52     52   130 my ($self, $results, $download) = @_;
122 52         103 $global_id = $download;
123 52         204 $self->set_containsmods($results);
124 52         91 $global_id = undef;
125 52         106 return;
126             }
127              
128             package CPAN::SQLite::META::Module;
129 3     3   31 use parent 'CPAN::SQLite::META';
  3         6  
  3         15  
130 3     3   192 use CPAN::SQLite::Util qw(has_hash_data);
  3         6  
  3         1118  
131              
132             sub set_one {
133 901     901   1555 my $self = shift;
134 901         1448 my $cpan_sqlite = $self->{cpan_sqlite};
135 901         1446 my $id = $self->{id};
136 901 50       2165 return if ($id =~ /^Bundle::/);
137 901         1431 my $class = $self->{class};
138 901         1726 $cpan_sqlite->{results} = {};
139 901         3138 $cpan_sqlite->query(mode => 'module', name => $id, meta_obj => $self);
140 901         1582 my $cpan_meta = $self->{cpan_meta};
141 901         4364 return $cpan_meta->{readonly}{$class}{$id};
142             }
143              
144             sub set_many {
145 8     8   28 my $self = shift;
146 8         62 my $cpan_sqlite = $self->{cpan_sqlite};
147 8         27 my $regex = $self->{regex};
148 8         68 $cpan_sqlite->{results} = [];
149 8         82 return $cpan_sqlite->query(mode => 'module', query => $regex, meta_obj => $self);
150             }
151              
152             sub set_data {
153 1135     1135   2076 my ($self, $results) = @_;
154 1135         3053 $self->set_module($results->{mod_name}, $results);
155 1135         34997 $global_id = $results->{download};
156 1135         2580 return $self->set_dist($results->{download}, $results);
157             }
158              
159             sub set_list_data {
160 927     927   2098 my ($self, $results, $download) = @_;
161 927         1762 $global_id = $download;
162 927         2634 $self->set_containsmods($results);
163 927         1576 $global_id = undef;
164 927         1955 return;
165             }
166              
167             package CPAN::SQLite::META::Bundle;
168 3     3   25 use parent 'CPAN::SQLite::META';
  3         7  
  3         13  
169 3     3   186 use CPAN::SQLite::Util qw(has_hash_data);
  3         6  
  3         1232  
170              
171             sub set_one {
172 10     10   20 my $self = shift;
173 10         24 my $cpan_sqlite = $self->{cpan_sqlite};
174 10         18 my $id = $self->{id};
175 10 50       48 unless ($id =~ /^Bundle::/) {
176 0         0 $id = 'Bundle::' . $id;
177             }
178 10         20 my $class = $self->{class};
179 10         21 $cpan_sqlite->{results} = {};
180 10         42 $cpan_sqlite->query(mode => 'module', name => $id, meta_obj => $self);
181 10         25 my $cpan_meta = $self->{cpan_meta};
182 10         63 return $cpan_meta->{readonly}{$class}{$id};
183             }
184              
185             sub set_many {
186 4     4   8 my $self = shift;
187 4         14 my $cpan_sqlite = $self->{cpan_sqlite};
188 4         11 my $regex = $self->{regex};
189 4 50       30 unless ($regex =~ /(^Bundle::|[\^\$\*\+\?\|])/i) {
190 4         14 $regex = '^Bundle::' . $regex;
191             }
192 4 50       16 $regex = '^Bundle::' if $regex eq '^';
193 4         13 $cpan_sqlite->{results} = [];
194 4         22 return $cpan_sqlite->query(mode => 'module', query => $regex, meta_obj => $self);
195             }
196              
197             sub set_data {
198 18     18   48 my ($self, $results) = @_;
199 18         89 $self->set_bundle($results->{mod_name}, $results);
200 18         585 $global_id = $results->{download};
201 18         66 return $self->set_dist($results->{download}, $results);
202             }
203              
204             sub set_list_data {
205 16     16   50 my ($self, $results, $download) = @_;
206 16         35 $global_id = $download;
207 16         63 $self->set_containsmods($results);
208 16         45 $global_id = undef;
209 16         40 return;
210             }
211              
212             package CPAN::SQLite::META;
213 3     3   24 use CPAN::SQLite::Util qw(download);
  3         6  
  3         4771  
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 39 my ($self, $id, $results) = @_;
220 16         33 my $class = 'CPAN::Author';
221 16         35 my $cpan_meta = $self->{cpan_meta};
222             return $cpan_meta->instance($class => $id)->set(
223             'FULLNAME' => $results->{fullname},
224             'EMAIL' => $results->{email},
225 16         57 );
226             }
227              
228             sub set_module {
229 1135     1135 0 1988 my ($self, $id, $results) = @_;
230 1135         1697 my $class = 'CPAN::Module';
231 1135         1832 my $cpan_meta = $self->{cpan_meta};
232 1135         3635 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         60715 );
240             }
241              
242             sub set_bundle {
243 18     18 0 42 my ($self, $id, $results) = @_;
244 18         44 my $class = 'CPAN::Bundle';
245 18         35 my $cpan_meta = $self->{cpan_meta};
246 18         72 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         1019 );
254             }
255              
256             sub set_dist {
257 1205     1205 0 2147 my ($self, $id, $results) = @_;
258 1205         1771 my $class = 'CPAN::Distribution';
259 1205         1701 my $cpan_meta = $self->{cpan_meta};
260 1205         2636 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         45494 );
266             }
267              
268             sub set_containsmods {
269 995     995 0 1676 my ($self, $mods) = @_;
270 995         1512 my $class = 'CPAN::Distribution';
271 995         1741 my $cpan_meta = $self->{cpan_meta};
272 995         1469 my %containsmods;
273 995 50 33     4196 if ($mods and (ref($mods) eq 'ARRAY')) {
274 995         2173 %containsmods = map { $_->{mod_name} => 1 } @$mods;
  22772         46273  
275             }
276 995         4784 my $d = $cpan_meta->instance($class => $global_id);
277 995         46144 return $d->{CONTAINSMODS} = \%containsmods;
278             }
279              
280             sub reload {
281 2     2 0 228088 my ($self, %args) = @_;
282              
283 2   33     21 my $time = $args{'time'} || time;
284 2         5 my $force = $args{force};
285 2         5 my $db_name = $CPAN::SQLite::db_name;
286 2         32 my $db = File::Spec->catfile($CPAN::Config->{cpan_home}, $db_name);
287 2         8 my $journal_file = $db . '-journal';
288 2 50       46 if (-e $journal_file) {
289 0         0 $CPAN::FrontEnd->mywarn('Database locked - cannot update.');
290 0         0 return;
291             }
292 2         8 my @args = ($^X, '-MCPAN::SQLite::META=setup,update,check', '-e');
293 2 100 66     45 if (-e $db && -s _) {
294 1         7 my $mtime_db = (stat(_))[9];
295 1         4 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       391031 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       522 unless ($force) {
305 1 50       177 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       6 unlink($db) if -e _;
311 1         34 $CPAN::FrontEnd->myprint('Creating database file ... ');
312 1         6 push @args, q{setup};
313             }
314 1 50       7 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       614973 system(@args) == 0 or die qq{system @args failed: $?};
319 1         99 $CPAN::FrontEnd->myprint("Done!\n");
320 1         86 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 2 my $time = shift;
367 1 50       3 return unless $time;
368 1         13 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         8 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.219
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