]> git.donarmstrong.com Git - debbugs.git/blob - lib/Debbugs/Package.pm
70f0e35832d828bc290816d039c663746cc4692f
[debbugs.git] / lib / Debbugs / Package.pm
1 # This module is part of debbugs, and
2 # is released under the terms of the GPL version 3, or any later
3 # version (at your option). See the file README and COPYING for more
4 # information.
5 # Copyright 2018 by Don Armstrong <don@donarmstrong.com>.
6
7 package Debbugs::Package;
8
9 =head1 NAME
10
11 Debbugs::Package -- OO interface to packages
12
13 =head1 SYNOPSIS
14
15    use Debbugs::Package;
16    Debbugs::Package->new(schema => $s,binaries => [qw(foo)],sources => [qw(bar)]);
17
18 =head1 DESCRIPTION
19
20
21
22 =cut
23
24 use Mouse;
25 use strictures 2;
26 use v5.10; # for state
27 use namespace::autoclean;
28
29 use List::AllUtils  qw(uniq pairmap);
30 use Debbugs::Config qw(:config);
31 use Debbugs::Version::Source;
32 use Debbugs::Version::Binary;
33
34 extends 'Debbugs::OOBase';
35
36 =head2 name
37
38 Name of the Package
39
40 =head2 qualified_name
41
42 name if binary, name prefixed with C<src:> if source
43
44 =cut
45
46 has name => (is => 'ro', isa => 'Str',
47              required => 1,
48             );
49
50 sub qualified_name {
51     my $self = shift;
52     return
53         # src: if source, nothing if binary
54         ($self->_type eq 'source' ? 'src:':'') .
55         $self->name;
56 }
57
58
59 =head2 type
60
61 Type of the package; either C<binary> or C<source>
62
63 =cut
64
65 has type => (is => 'bare', isa => 'Str',
66              lazy => 1,
67              builder => '_build_type',
68              clearer => '_clear_type',
69              reader => '_type',
70              writer => '_set_type',
71             );
72
73 sub _build_type {
74     my $self = shift;
75     if ($self->name !~ /^src:/) {
76         return 'binary';
77     }
78 }
79
80 =head2 url
81
82 url to the package
83
84 =cut
85
86 sub url {
87     my $self = shift;
88     return $config{web_domain}.'/'.$self->qualified_name;
89 }
90
91 around BUILDARGS => sub {
92     my $orig = shift;
93     my $class = shift;
94     my %args;
95     if (@_==1 and ref($_[0]) eq 'HASH') {
96         %args = %{$_[0]};
97     } else {
98         %args = @_;
99     }
100     $args{name} //= '(unknown)';
101     if ($args{name} =~ /src:(.+)/) {
102         $args{name} = $1;
103         $args{type} = 'source';
104     } else {
105         $args{type} = 'binary' unless
106             defined $args{type};
107     }
108     return $class->$orig(%args);
109 };
110
111 =head2 is_source
112
113 true if the package is a source package
114
115 =head2 is_binary
116
117 true if the package is a binary package
118
119 =cut
120
121 sub is_source {
122     return $_[0]->_type eq 'source'
123 }
124
125 sub is_binary {
126     return $_[0]->_type eq 'binary'
127 }
128
129 =head2 valid -- true if the package has any valid versions
130
131 =cut
132
133 has valid => (is => 'ro', isa => 'Bool',
134               lazy => 1,
135               builder => '_build_valid',
136               writer => '_set_valid',
137              );
138
139 sub _build_valid {
140     my $self = shift;
141     if ($self->valid_version_info_count> 0) {
142         return 1;
143     }
144     return 0;
145 }
146
147 # this contains source name, source version, binary name, binary version, arch,
148 # and dist which have been selected from the database. It is used to build
149 # versions and anything else which are known as required.
150 has 'valid_version_info' =>
151     (is => 'bare', isa => 'ArrayRef',
152      traits => ['Array'],
153      lazy => 1,
154      builder => '_build_valid_version_info',
155      predicate => '_has_valid_version_info',
156      clearer => '_clear_valid_version_info',
157      handles => {'_get_valid_version_info' => 'get',
158                  'valid_version_info_grep' => 'grep',
159                  '_valid_version_info' => 'elements',
160                  'valid_version_info_count' => 'count',
161                 },
162     );
163
164 sub _build_valid_version_info {
165     my $self = shift;
166     my $pkgs = $self->_get_valid_version_info_from_db;
167     for my $invalid_version (@{$pkgs->{$self->qualified_name}->{invalid_versions}}) {
168         $self->_mark_invalid_version($invalid_version,1);
169     }
170     return $pkgs->{$self->qualified_name}->{valid_version_info} // [];
171 }
172
173 state $common_dists = [@{$config{distributions}}];
174 sub _get_valid_version_info_from_db {
175     my $self;
176     if ((@_ % 2) == 1 and
177         blessed($_[0])) {
178         $self = shift;
179     }
180     my %args = @_;
181     my @packages;
182     my $s; # schema
183     if (defined $self) {
184         if ($self->has_schema) {
185             $s = $self->schema;
186         } else {
187             $s = $args{schema};
188         }
189         @packages = $self->qualified_name;
190     } else {
191         $s = $args{schema};
192         @packages = @{$args{packages}};
193     }
194     if (not defined $s) {
195         confess("get_info_from_db not implemented without schema");
196     }
197     my %src_packages;
198     my %src_ver_packages;
199     my %bin_packages;
200     my %bin_ver_packages;
201     # split packages into src/ver, bin/ver, src, and bin so we can select them
202     # from the database
203     local $_;
204     for my $pkg (@packages) {
205         if (ref($pkg)) {
206             if ($pkg->[0] =~ /^src:(.+)$/) {
207                 for my $ver (@{$pkg}[1..$#{$pkg}]) {
208                     $src_ver_packages{$1}{$ver} = 0;
209                 }
210             } else {
211                 for my $ver (@{$pkg}[1..$#{$pkg}]) {
212                     $bin_ver_packages{$pkg->[0]}{$ver} = 0;
213                 }
214             }
215         } elsif ($pkg =~ /^src:(.+)$/) {
216             $src_packages{$1} = 0;
217         } else {
218             $bin_packages{$pkg} = 0;
219         }
220     }
221     # calculate searches for packages where we want specific versions. We
222     # calculate this here so add_result_to_package can stomp over
223     # %src_ver_packages and %bin_ver_packages
224     my @src_ver_search;
225     for my $sp (keys %src_ver_packages) {
226         push @src_ver_search,
227             (-and => {'src_pkg.pkg' => $sp,
228                       'me.ver' => [keys %{$src_ver_packages{$sp}}],
229                      },
230              );
231     }
232     my @src_packages = keys %src_packages;
233
234     my @bin_ver_search;
235     for my $sp (keys %bin_ver_packages) {
236         push @bin_ver_search,
237             (-and => {'bin_pkg.pkg' => $sp,
238                       'me.ver' => [keys %{$bin_ver_packages{$sp}}],
239                      },
240              );
241     }
242     my @bin_packages = keys %bin_packages;
243     my $packages = {};
244     sub _default_pkg_info {
245         return {name => $_[0],
246                 type => $_[1]//'source',
247                 valid => $_[2]//1,
248                 valid_version_info => [],
249                 invalid_versions => {},
250                };
251     }
252     sub add_result_to_package {
253         my ($pkgs,$rs,$svp,$bvp,$sp,$bp) = @_;
254         while (my $pkg = $rs->next) {
255             my $n = 'src:'.$pkg->{src_pkg};
256             if (not exists $pkgs->{$n}) {
257                 $pkgs->{$n} =
258                     _default_pkg_info($pkg->{src_pkg});
259             }
260             push @{$pkgs->{$n}{valid_version_info}},
261                {%$pkg};
262             $n = $pkg->{bin_pkg};
263             if (not exists $pkgs->{$n}) {
264                 $pkgs->{$n} =
265                     _default_pkg_info($pkg->{bin_pkg},'binary');
266             }
267             push @{$pkgs->{$n}{valid_version_info}},
268                    {%$pkg};
269             # this is a package with a valid src_ver
270             $svp->{$pkg->{src_pkg}}{$pkg->{src_ver}}++;
271             $sp->{$pkg->{src_pkg}}++;
272             # this is a package with a valid bin_ver
273             $bvp->{$pkg->{bin_pkg}}{$pkg->{bin_ver}}++;
274             $bp->{$pkg->{bin_pkg}}++;
275         }
276     }
277     if (@src_packages) {
278         my $src_rs = $s->resultset('SrcVer')->
279             search({-or => [-and => {'src_pkg.pkg' => [@src_packages],
280                                      -or => {'suite.codename' => $common_dists,
281                                              'suite.suite_name' => $common_dists,
282                                             },
283                                     },
284                             @src_ver_search,
285                            ],
286                    },
287                   {join => ['src_pkg',
288                            {
289                             'src_associations' => 'suite'},
290                            {
291                             'bin_vers' => ['bin_pkg','arch']},
292                             'maintainer',
293                            ],
294                    'select' => [qw(src_pkg.pkg),
295                                 qw(suite.codename),
296                                 qw(suite.suite_name),
297                                 qw(src_associations.modified),
298                                 qw(me.ver),
299                                 q(CONCAT(src_pkg.pkg,'/',me.ver)),
300                                 qw(bin_vers.ver bin_pkg.pkg arch.arch),
301                                 qw(maintainer.name),
302                                ],
303                    'as' => [qw(src_pkg codename suite_name),
304                             qw(modified_time src_ver src_pkg_ver),
305                             qw(bin_ver bin_pkg arch maintainer),
306                            ],
307                    result_class => 'DBIx::Class::ResultClass::HashRefInflator',
308                   },
309                   );
310         add_result_to_package($packages,$src_rs,
311                               \%src_ver_packages,
312                               \%bin_ver_packages,
313                               \%src_packages,
314                               \%bin_packages,
315                              );
316     }
317     if (@bin_packages) {
318         my $bin_assoc_rs =
319             $s->resultset('BinAssociation')->
320             search({-and => {'bin_pkg.pkg' => [@bin_packages],
321                              -or => {'suite.codename' => $common_dists,
322                                      'suite.suite_name' => $common_dists,
323                                     },
324                             }},
325                   {join => [{'bin' =>
326                              [{'src_ver' => ['src_pkg',
327                                              'maintainer',
328                                             ]},
329                               'bin_pkg',
330                               'arch']},
331                             'suite',
332                            ],
333                    'select' => [qw(src_pkg.pkg),
334                                 qw(suite.codename),
335                                 qw(suite.suite_name),
336                                 qw(me.modified),
337                                 qw(src_ver.ver),
338                                 q(CONCAT(src_pkg.pkg,'/',src_ver.ver)),
339                                 qw(bin.ver bin_pkg.pkg arch.arch),
340                                 qw(maintainer.name),
341                                ],
342                    'as' => [qw(src_pkg codename suite_name),
343                             qw(modified_time src_ver src_pkg_ver),
344                             qw(bin_ver bin_pkg arch maintainer),
345                            ],
346                    result_class => 'DBIx::Class::ResultClass::HashRefInflator',
347                   },
348                   );
349         add_result_to_package($packages,$bin_assoc_rs,
350                               \%src_ver_packages,
351                               \%bin_ver_packages,
352                               \%src_packages,
353                               \%bin_packages,
354                              );
355     }
356     if (@bin_ver_search) {
357         my $bin_rs = $s->resultset('BinVer')->
358             search({-or => [@bin_ver_search,
359                            ],
360                    },
361                   {join => ['bin_pkg',
362                            {
363                             'bin_associations' => 'suite'},
364                            {'src_ver' => ['src_pkg',
365                                           'maintainer',
366                                          ]},
367                             'arch',
368                            ],
369                    'select' => [qw(src_pkg.pkg),
370                                 qw(suite.codename),
371                                 qw(suite.suite_name),
372                                 qw(bin_associations.modified),
373                                 qw(src_ver.ver),
374                                 q(CONCAT(src_pkg.pkg,'/',src_ver.ver)),
375                                 qw(me.ver bin_pkg.pkg arch.arch),
376                                 qw(maintainer.name),
377                                ],
378                    'as' => [qw(src_pkg codename suite_name),
379                             qw(modified_time src_ver src_pkg_ver),
380                             qw(bin_ver bin_pkg arch maintainer),
381                            ],
382                    result_class => 'DBIx::Class::ResultClass::HashRefInflator',
383                   },
384                   );
385         add_result_to_package($packages,$bin_rs,
386                               \%src_ver_packages,
387                               \%bin_ver_packages,
388                               \%src_packages,
389                               \%bin_packages,
390                              );
391     }
392     for my $sp (keys %src_ver_packages) {
393         if (not exists $packages->{'src:'.$sp}) {
394             $packages->{'src:'.$sp} =
395                 _default_pkg_info($sp,'source',0);
396         }
397         for my $sv (keys %{$src_ver_packages{$sp}}) {
398             next if $src_ver_packages{$sp}{$sv} > 0;
399             $packages->{'src:'.$sp}{invalid_versions}{$sv} = 1;
400         }
401     }
402     for my $bp (keys %bin_ver_packages) {
403         if (not exists $packages->{$bp}) {
404             $packages->{$bp} =
405                 _default_pkg_info($bp,'binary',0);
406         }
407         for my $bv (keys %{$bin_ver_packages{$bp}}) {
408             next if $bin_ver_packages{$bp}{$bv} > 0;
409             $packages->{$bp}{invalid_versions}{$bv} = 1;
410         }
411     }
412     for my $sp (keys %src_packages) {
413         next if $src_packages{$sp} > 0;
414         $packages->{'src:'.$sp} =
415             _default_pkg_info($sp,'source',0);
416     }
417     for my $bp (keys %bin_packages) {
418         next if $bin_packages{$bp} > 0;
419         $packages->{$bp} =
420             _default_pkg_info($bp,'binary',0);
421     }
422     return $packages;
423 }
424
425 has 'source_version_to_info' =>
426     (is => 'bare', isa => 'HashRef',
427      traits => ['Hash'],
428      lazy => 1,
429      builder => '_build_source_version_to_info',
430      handles => {_get_source_version_to_info => 'get',
431                 },
432     );
433
434 sub _build_source_version_to_info {
435     my $self = shift;
436     my $info = {};
437     my $i = 0;
438     for my $v ($self->_valid_version_info) {
439         push @{$info->{$v->{src_ver}}}, $i;
440         $i++;
441     }
442     return $info;
443 }
444
445 has 'binary_version_to_info' =>
446     (is => 'bare', isa => 'HashRef',
447      traits => ['Hash'],
448      lazy => 1,
449      builder => '_build_binary_version_to_info',
450      handles => {_get_binary_version_to_info => 'get',
451                 },
452     );
453
454 sub _build_binary_version_to_info {
455     my $self = shift;
456     my $info = {};
457     my $i = 0;
458     for my $v ($self->_valid_version_info) {
459         push @{$info->{$v->{bin_ver}}}, $i;
460         $i++;
461     }
462     return $info;
463 }
464
465 has 'dist_to_info' =>
466     (is => 'bare', isa => 'HashRef',
467      traits => ['Hash'],
468      lazy => 1,
469      builder => '_build_dist_to_info',
470      handles => {_get_dist_to_info => 'get',
471                 },
472     );
473 sub _build_dist_to_info {
474     my $self = shift;
475     my $info = {};
476     my $i = 0;
477     for my $v ($self->_valid_version_info) {
478         next unless defined $v->{suite_name} and length($v->{suite_name});
479         push @{$info->{$v->{suite_name}}}, $i;
480         $i++;
481     }
482     return $info;
483 }
484
485 # this is a hashref of versions that we know are invalid
486 has 'invalid_versions' =>
487     (is => 'bare',isa => 'HashRef[Bool]',
488      lazy => 1,
489      default => sub {{}},
490      clearer => '_clear_invalid_versions',
491      traits => ['Hash'],
492      handles => {_invalid_version => 'exists',
493                  _mark_invalid_version => 'set',
494                 },
495     );
496
497 has 'binaries' => (is => 'ro',
498                    isa => 'Debbugs::Collection::Package',
499                    lazy => 1,
500                    builder => '_build_binaries',
501                    predicate => '_has_binaries',
502                   );
503
504 sub _build_binaries {
505     my $self = shift;
506     if ($self->is_binary) {
507         return $self->package_collection->limit($self->name);
508     }
509     # OK, walk through the valid_versions for this package
510     my @binaries =
511         uniq map {$_->{bin_pkg}} $self->_valid_version_info;
512     return $self->package_collection->limit(@binaries);
513 }
514
515 has 'sources' => (is => 'ro',
516                   isa => 'Debbugs::Collection::Package',
517                   lazy => 1,
518                   builder => '_build_sources',
519                   predicate => '_has_sources',
520                  );
521
522 sub _build_sources {
523     my $self = shift;
524     return $self->package_collection->limit($self->source_names);
525 }
526
527 sub source_names {
528     my $self = shift;
529
530     if ($self->is_source) {
531         return $self->name
532     }
533     return uniq map {'src:'.$_->{src_pkg}} $self->_valid_version_info;
534 }
535
536 =head2 maintainers 
537
538 L<Debbugs::Collection::Correspondent> of the maintainer(s) of the current package
539
540 =cut
541
542 has maintainers => (is => 'ro',
543                     isa => 'Debbugs::Collection::Correspondent',
544                     lazy => 1,
545                     builder => '_build_maintainers',
546                     predicate => '_has_maintainers',
547                    );
548
549 sub _build_maintainers {
550     my $self = shift;
551     my @maintainers;
552     for my $v ($self->_valid_version_info) {
553         next unless length($v->{suite_name}) and length($v->{maintainer});
554         push @maintainers,$v->{maintainer};
555     }
556     @maintainers =
557         uniq @maintainers;
558     return $self->correspondent_collection->limit(@maintainers);
559 }
560
561 has 'versions' => (is => 'bare',
562                    isa => 'HashRef[Debbugs::Version]',
563                    traits => ['Hash'],
564                    handles => {_exists_version => 'exists',
565                                _get_version => 'get',
566                                _set_version => 'set',
567                               },
568                    lazy => 1,
569                    builder => '_build_versions',
570                   );
571
572 sub _build_versions {
573     my $self = shift;
574     return {};
575 }
576
577 sub _add_version {
578     my $self = shift;
579     my @set;
580     for my $v (@_) {
581         push @set,
582             $v->version,$v;
583     }
584     $self->_set_version(@set);
585 }
586
587 sub get_source_version_distribution {
588     my $self = shift;
589
590     my %src_pkg_vers = @_;
591     for my $dist (@_) {
592         my @ver_loc =
593             grep {defined $_}
594             $self->_get_dist_to_info($dist);
595         for my $v ($self->
596                    _get_valid_version_info(@ver_loc)) {
597             $src_pkg_vers{$v->{src_pkg_ver}} = 1;
598         }
599     }
600     return $self->package_collection->
601         get_source_versions(keys %src_pkg_vers)->members;
602 }
603
604 # returns the source version(s) corresponding to the version of *this* package; the
605 # version passed may be binary or source, depending.
606 sub get_source_version {
607     my $self = shift;
608     if ($self->is_source) {
609         return $self->get_version(@_);
610     }
611     my %src_pkg_vers;
612     for my $ver (@_) {
613         my %archs;
614         if (ref $ver) {
615             my @archs;
616             ($ver,@archs) = @{$ver};
617             @archs{@archs} = (1) x @archs;
618         }
619         my @ver_loc =
620             @{$self->_get_binary_version_to_info($ver)//[]};
621         next unless @ver_loc;
622         my @vers = map {$self->
623                             _get_valid_version_info($_)}
624             @ver_loc;
625         for my $v (@vers) {
626             if (keys %archs) {
627                 next unless exists $archs{$v->{arch}};
628             }
629             $src_pkg_vers{$v->{src_pkg_ver}} = 1;
630         }
631     }
632     return $self->package_collection->
633         get_source_versions(keys %src_pkg_vers)->members;
634 }
635
636 sub get_version {
637     my $self = shift;
638     my @ret;
639     for my $v (@_) {
640         if ($self->_exists_version($v)) {
641             push @ret,$self->_get_version($v);
642         } else {
643             push @ret,
644                 $self->_create_version($v);
645         }
646     }
647     return @ret;
648 }
649
650 sub _create_version {
651     my $self = shift;
652     my @versions;
653     if ($self->is_source) {
654         for my $v (@_) {
655             push @versions,
656                 $v,
657                 Debbugs::Version::Source->
658                     new(pkg => $self,
659                         version => $v,
660                         package_collection => $self->package_collection,
661                         $self->schema_argument,
662                        );
663         }
664     } else {
665         for my $v (@_) {
666             push @versions,
667                 $v,
668                 Debbugs::Version::Binary->
669                     new(pkg => $self,
670                         version => $v,
671                         package_collection => $self->package_collection,
672                         $self->schema_argument,
673                        );
674         }
675     }
676     $self->_set_version(@versions);
677 }
678
679 =head2 package_collection
680
681 L<Debbugs::Collection::Package> to get additional packages required
682
683 =cut
684
685 # gets used to retrieve packages
686 has 'package_collection' => (is => 'ro',
687                              isa => 'Debbugs::Collection::Package',
688                              builder => '_build_package_collection',
689                              lazy => 1,
690                             );
691
692 sub _build_package_collection {
693     my $self = shift;
694     return Debbugs::Collection::Package->new($self->schema_argument)
695 }
696
697 =head2 correspondent_collection
698
699 L<Debbugs::Collection::Correspondent> to get additional maintainers required
700
701 =cut
702
703 has 'correspondent_collection' => (is => 'ro',
704                                    isa => 'Debbugs::Collection::Correspondent',
705                                    builder => '_build_correspondent_collection',
706                                    lazy => 1,
707                                   );
708
709 sub _build_correspondent_collection {
710     my $self = shift;
711     return Debbugs::Collection::Correspondent->new($self->schema_argument)
712 }
713
714 sub CARP_TRACE {
715     my $self = shift;
716     return 'Debbugs::Package={package='.$self->qualified_name.'}';
717 }
718
719 __PACKAGE__->meta->make_immutable;
720 no Mouse;
721
722 1;
723
724
725 __END__
726 # Local Variables:
727 # indent-tabs-mode: nil
728 # cperl-indent-level: 4
729 # End: