]> git.donarmstrong.com Git - debbugs.git/blob - Debbugs/Package.pm
661242768cc53542f6e637932d98682eeae48007
[debbugs.git] / 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
233     my @bin_ver_search;
234     for my $sp (keys %bin_ver_packages) {
235         push @bin_ver_search,
236             (-and => {'bin_pkg.pkg' => $sp,
237                       'me.ver' => [keys %{$bin_ver_packages{$sp}}],
238                      },
239              );
240     }
241     my $packages = {};
242     sub _default_pkg_info {
243         return {name => $_[0],
244                 type => $_[1]//'source',
245                 valid => $_[2]//1,
246                 valid_version_info => [],
247                 invalid_versions => {},
248                };
249     }
250     sub add_result_to_package {
251         my ($pkgs,$rs,$svp,$bvp,$sp,$bp) = @_;
252         while (my $pkg = $rs->next) {
253             my $n = 'src:'.$pkg->{src_pkg};
254             if (not exists $pkgs->{$n}) {
255                 $pkgs->{$n} =
256                     _default_pkg_info($pkg->{src_pkg});
257             }
258             push @{$pkgs->{$n}{valid_version_info}},
259                {%$pkg};
260             $n = $pkg->{bin_pkg};
261             if (not exists $pkgs->{$n}) {
262                 $pkgs->{$n} =
263                     _default_pkg_info($pkg->{bin_pkg},'binary');
264             }
265             push @{$pkgs->{$n}{valid_version_info}},
266                    {%$pkg};
267             # this is a package with a valid src_ver
268             $svp->{$pkg->{src_pkg}}{$pkg->{src_ver}}++;
269             $sp->{$pkg->{src_pkg}}++;
270             # this is a package with a valid bin_ver
271             $bvp->{$pkg->{bin_pkg}}{$pkg->{bin_ver}}++;
272             $bp->{$pkg->{bin_pkg}}++;
273         }
274     }
275     if (keys %src_packages) {
276         my $src_rs = $s->resultset('SrcVer')->
277             search({-or => [-and => {'src_pkg.pkg' => [keys %src_packages],
278                                      -or => {'suite.codename' => $common_dists,
279                                              'suite.suite_name' => $common_dists,
280                                             },
281                                     },
282                             @src_ver_search,
283                            ],
284                    },
285                   {join => ['src_pkg',
286                            {
287                             'src_associations' => 'suite'},
288                            {
289                             'bin_vers' => ['bin_pkg','arch']},
290                             'maintainer',
291                            ],
292                    'select' => [qw(src_pkg.pkg),
293                                 qw(suite.codename),
294                                 qw(suite.suite_name),
295                                 qw(src_associations.modified),
296                                 qw(me.ver),
297                                 q(CONCAT(src_pkg.pkg,'/',me.ver)),
298                                 qw(bin_vers.ver bin_pkg.pkg arch.arch),
299                                 qw(maintainer.name),
300                                ],
301                    'as' => [qw(src_pkg codename suite_name),
302                             qw(modified_time src_ver src_pkg_ver),
303                             qw(bin_ver bin_pkg arch maintainer),
304                            ],
305                    result_class => 'DBIx::Class::ResultClass::HashRefInflator',
306                   },
307                   );
308         add_result_to_package($packages,$src_rs,
309                               \%src_ver_packages,
310                               \%bin_ver_packages,
311                               \%src_packages,
312                               \%bin_packages,
313                              );
314     }
315     if (keys %bin_packages) {
316         my $bin_assoc_rs =
317             $s->resultset('BinAssociation')->
318             search({-and => {'bin_pkg.pkg' => [keys %bin_packages],
319                              -or => {'suite.codename' => $common_dists,
320                                      'suite.suite_name' => $common_dists,
321                                     },
322                             }},
323                   {join => [{'bin' =>
324                              [{'src_ver' => ['src_pkg',
325                                              'maintainer',
326                                             ]},
327                               'bin_pkg',
328                               'arch']},
329                             'suite',
330                            ],
331                    'select' => [qw(src_pkg.pkg),
332                                 qw(suite.codename),
333                                 qw(suite.suite_name),
334                                 qw(me.modified),
335                                 qw(src_ver.ver),
336                                 q(CONCAT(src_pkg.pkg,'/',src_ver.ver)),
337                                 qw(bin.ver bin_pkg.pkg arch.arch),
338                                 qw(maintainer.name),
339                                ],
340                    'as' => [qw(src_pkg codename suite_name),
341                             qw(modified_time src_ver src_pkg_ver),
342                             qw(bin_ver bin_pkg arch maintainer),
343                            ],
344                    result_class => 'DBIx::Class::ResultClass::HashRefInflator',
345                   },
346                   );
347         add_result_to_package($packages,$bin_assoc_rs,
348                               \%src_ver_packages,
349                               \%bin_ver_packages,
350                               \%src_packages,
351                               \%bin_packages,
352                              );
353     }
354     if (@bin_ver_search) {
355         my $bin_rs = $s->resultset('BinVer')->
356             search({-or => [@bin_ver_search,
357                            ],
358                    },
359                   {join => ['bin_pkg',
360                            {
361                             'bin_associations' => 'suite'},
362                            {'src_ver' => ['src_pkg',
363                                           'maintainer',
364                                          ]},
365                             'arch',
366                            ],
367                    'select' => [qw(src_pkg.pkg),
368                                 qw(suite.codename),
369                                 qw(suite.suite_name),
370                                 qw(bin_associations.modified),
371                                 qw(src_ver.ver),
372                                 q(CONCAT(src_pkg.pkg,'/',src_ver.ver)),
373                                 qw(me.ver bin_pkg.pkg arch.arch),
374                                 qw(maintainer.name),
375                                ],
376                    'as' => [qw(src_pkg codename suite_name),
377                             qw(modified_time src_ver src_pkg_ver),
378                             qw(bin_ver bin_pkg arch maintainer),
379                            ],
380                    result_class => 'DBIx::Class::ResultClass::HashRefInflator',
381                   },
382                   );
383         add_result_to_package($packages,$bin_rs,
384                               \%src_ver_packages,
385                               \%bin_ver_packages,
386                               \%src_packages,
387                               \%bin_packages,
388                              );
389     }
390     for my $sp (keys %src_ver_packages) {
391         if (not exists $packages->{'src:'.$sp}) {
392             $packages->{'src:'.$sp} =
393                 _default_pkg_info($sp,'source',0);
394         }
395         for my $sv (keys %{$src_ver_packages{$sp}}) {
396             next if $src_ver_packages{$sp}{$sv} > 0;
397             $packages->{'src:'.$sp}{invalid_versions}{$sv} = 1;
398         }
399     }
400     for my $bp (keys %bin_ver_packages) {
401         if (not exists $packages->{$bp}) {
402             $packages->{$bp} =
403                 _default_pkg_info($bp,'binary',0);
404         }
405         for my $bv (keys %{$bin_ver_packages{$bp}}) {
406             next if $bin_ver_packages{$bp}{$bv} > 0;
407             $packages->{$bp}{invalid_versions}{$bv} = 1;
408         }
409     }
410     for my $sp (keys %src_packages) {
411         next if $src_packages{$sp} > 0;
412         $packages->{'src:'.$sp} =
413             _default_pkg_info($sp,'source',0);
414     }
415     for my $bp (keys %bin_packages) {
416         next if $bin_packages{$bp} > 0;
417         $packages->{$bp} =
418             _default_pkg_info($bp,'binary',0);
419     }
420     return $packages;
421 }
422
423 has 'source_version_to_info' =>
424     (is => 'bare', isa => 'HashRef',
425      traits => ['Hash'],
426      lazy => 1,
427      builder => '_build_source_version_to_info',
428      handles => {_get_source_version_to_info => 'get',
429                 },
430     );
431
432 sub _build_source_version_to_info {
433     my $self = shift;
434     my $info = {};
435     my $i = 0;
436     for my $v ($self->_valid_version_info) {
437         push @{$info->{$v->{src_ver}}}, $i;
438         $i++;
439     }
440     return $info;
441 }
442
443 has 'binary_version_to_info' =>
444     (is => 'bare', isa => 'HashRef',
445      traits => ['Hash'],
446      lazy => 1,
447      builder => '_build_binary_version_to_info',
448      handles => {_get_binary_version_to_info => 'get',
449                 },
450     );
451
452 sub _build_binary_version_to_info {
453     my $self = shift;
454     my $info = {};
455     my $i = 0;
456     for my $v ($self->_valid_version_info) {
457         push @{$info->{$v->{bin_ver}}}, $i;
458         $i++;
459     }
460     return $info;
461 }
462
463 has 'dist_to_info' =>
464     (is => 'bare', isa => 'HashRef',
465      traits => ['Hash'],
466      lazy => 1,
467      builder => '_build_dist_to_info',
468      handles => {_get_dist_to_info => 'get',
469                 },
470     );
471 sub _build_dist_to_info {
472     my $self = shift;
473     my $info = {};
474     my $i = 0;
475     for my $v ($self->_valid_version_info) {
476         next unless defined $v->{suite_name} and length($v->{suite_name});
477         push @{$info->{$v->{suite_name}}}, $i;
478         $i++;
479     }
480     return $info;
481 }
482
483 # this is a hashref of versions that we know are invalid
484 has 'invalid_versions' =>
485     (is => 'bare',isa => 'HashRef[Bool]',
486      lazy => 1,
487      default => sub {{}},
488      clearer => '_clear_invalid_versions',
489      traits => ['Hash'],
490      handles => {_invalid_version => 'exists',
491                  _mark_invalid_version => 'set',
492                 },
493     );
494
495 has 'binaries' => (is => 'ro',
496                    isa => 'Debbugs::Collection::Package',
497                    lazy => 1,
498                    builder => '_build_binaries',
499                    predicate => '_has_binaries',
500                   );
501
502 sub _build_binaries {
503     my $self = shift;
504     if ($self->is_binary) {
505         return $self->package_collection->limit($self->name);
506     }
507     # OK, walk through the valid_versions for this package
508     my @binaries =
509         uniq map {$_->{bin_pkg}} $self->_valid_version_info;
510     return $self->package_collection->limit(@binaries);
511 }
512
513 has 'sources' => (is => 'ro',
514                   isa => 'Debbugs::Collection::Package',
515                   lazy => 1,
516                   builder => '_build_sources',
517                   predicate => '_has_sources',
518                  );
519
520 sub _build_sources {
521     my $self = shift;
522     return $self->package_collection->limit($self->source_names);
523 }
524
525 sub source_names {
526     my $self = shift;
527
528     if ($self->is_source) {
529         return $self->name
530     }
531     return uniq map {'src:'.$_->{src_pkg}} $self->_valid_version_info;
532 }
533
534 =head2 maintainers 
535
536 L<Debbugs::Collection::Correspondent> of the maintainer(s) of the current package
537
538 =cut
539
540 has maintainers => (is => 'ro',
541                     isa => 'Debbugs::Collection::Correspondent',
542                     lazy => 1,
543                     builder => '_build_maintainers',
544                     predicate => '_has_maintainers',
545                    );
546
547 sub _build_maintainers {
548     my $self = shift;
549     my @maintainers;
550     for my $v ($self->_valid_version_info) {
551         next unless length($v->{suite_name}) and length($v->{maintainer});
552         push @maintainers,$v->{maintainer};
553     }
554     @maintainers =
555         uniq @maintainers;
556     return $self->correspondent_collection->limit(@maintainers);
557 }
558
559 has 'versions' => (is => 'bare',
560                    isa => 'HashRef[Debbugs::Version]',
561                    traits => ['Hash'],
562                    handles => {_exists_version => 'exists',
563                                _get_version => 'get',
564                                _set_version => 'set',
565                               },
566                    lazy => 1,
567                    builder => '_build_versions',
568                   );
569
570 sub _build_versions {
571     my $self = shift;
572     return {};
573 }
574
575 sub _add_version {
576     my $self = shift;
577     my @set;
578     for my $v (@_) {
579         push @set,
580             $v->version,$v;
581     }
582     $self->_set_version(@set);
583 }
584
585 sub get_source_version_distribution {
586     my $self = shift;
587
588     my %src_pkg_vers = @_;
589     for my $dist (@_) {
590         my @ver_loc =
591             grep {defined $_}
592             $self->_get_dist_to_info($dist);
593         for my $v ($self->
594                    _get_valid_version_info(@ver_loc)) {
595             $src_pkg_vers{$v->{src_pkg_ver}} = 1;
596         }
597     }
598     return $self->package_collection->
599         get_source_versions(keys %src_pkg_vers)->members;
600 }
601
602 # returns the source version(s) corresponding to the version of *this* package; the
603 # version passed may be binary or source, depending.
604 sub get_source_version {
605     my $self = shift;
606     if ($self->is_source) {
607         return $self->get_version(@_);
608     }
609     my %src_pkg_vers;
610     for my $ver (@_) {
611         my %archs;
612         if (ref $ver) {
613             my @archs;
614             ($ver,@archs) = @{$ver};
615             @archs{@archs} = (1) x @archs;
616         }
617         my @ver_loc =
618             @{$self->_get_binary_version_to_info($ver)//[]};
619         next unless @ver_loc;
620         my @vers = map {$self->
621                             _get_valid_version_info($_)}
622             @ver_loc;
623         for my $v (@vers) {
624             if (keys %archs) {
625                 next unless exists $archs{$v->{arch}};
626             }
627             $src_pkg_vers{$v->{src_pkg_ver}} = 1;
628         }
629     }
630     return $self->package_collection->
631         get_source_versions(keys %src_pkg_vers)->members;
632 }
633
634 sub get_version {
635     my $self = shift;
636     my @ret;
637     for my $v (@_) {
638         if ($self->_exists_version($v)) {
639             push @ret,$self->_get_version($v);
640         } else {
641             push @ret,
642                 $self->_create_version($v);
643         }
644     }
645     return @ret;
646 }
647
648 sub _create_version {
649     my $self = shift;
650     my @versions;
651     if ($self->is_source) {
652         for my $v (@_) {
653             push @versions,
654                 $v,
655                 Debbugs::Version::Source->
656                     new(package => $self,
657                         version => $v,
658                         package_collection => $self->package_collection,
659                         $self->schema_argument,
660                        );
661         }
662     } else {
663         for my $v (@_) {
664             push @versions,
665                 $v,
666                 Debbugs::Version::Binary->
667                     new(package => $self,
668                         version => $v,
669                         package_collection => $self->package_collection,
670                         $self->schema_argument,
671                        );
672         }
673     }
674     $self->_set_version(@versions);
675 }
676
677 =head2 package_collection
678
679 L<Debbugs::Collection::Package> to get additional packages required
680
681 =cut
682
683 # gets used to retrieve packages
684 has 'package_collection' => (is => 'ro',
685                              isa => 'Debbugs::Collection::Package',
686                              builder => '_build_package_collection',
687                              lazy => 1,
688                             );
689
690 sub _build_package_collection {
691     my $self = shift;
692     return Debbugs::Collection::Package->new($self->schema_argument)
693 }
694
695 =head2 correspondent_collection
696
697 L<Debbugs::Collection::Correspondent> to get additional maintainers required
698
699 =cut
700
701 has 'correspondent_collection' => (is => 'ro',
702                                    isa => 'Debbugs::Collection::Correspondent',
703                                    builder => '_build_correspondent_collection',
704                                    lazy => 1,
705                                   );
706
707 sub _build_correspondent_collection {
708     my $self = shift;
709     return Debbugs::Collection::Correspondent->new($self->schema_argument)
710 }
711
712 sub CARP_TRACE {
713     my $self = shift;
714     return 'Debbugs::Package={package='.$self->qualified_name.'}';
715 }
716
717 __PACKAGE__->meta->make_immutable;
718 no Mouse;
719
720 1;
721
722
723 __END__
724 # Local Variables:
725 # indent-tabs-mode: nil
726 # cperl-indent-level: 4
727 # End: