]> git.donarmstrong.com Git - debbugs.git/blob - Debbugs/Bugs.pm
60217124dd067cb8b547731e6f14aeb234c0edd0
[debbugs.git] / Debbugs / Bugs.pm
1 # This module is part of debbugs, and is released
2 # under the terms of the GPL version 2, or any later
3 # version at your option.
4 # See the file README and COPYING for more information.
5 #
6 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
7
8 package Debbugs::Bugs;
9
10 =head1 NAME
11
12 Debbugs::Bugs -- Bug selection routines for debbugs
13
14 =head1 SYNOPSIS
15
16 use Debbugs::Bugs qw(get_bugs);
17
18
19 =head1 DESCRIPTION
20
21 This module is a replacement for all of the various methods of
22 selecting different types of bugs.
23
24 It implements a single function, get_bugs, which defines the master
25 interface for selecting bugs.
26
27 It attempts to use subsidiary functions to actually do the selection,
28 in the order specified in the configuration files. [Unless you're
29 insane, they should be in order from fastest (and often most
30 incomplete) to slowest (and most complete).]
31
32 =head1 BUGS
33
34 =head1 FUNCTIONS
35
36 =cut
37
38 use warnings;
39 use strict;
40 use feature 'state';
41 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
42 use Exporter qw(import);
43
44 BEGIN{
45      $VERSION = 1.00;
46      $DEBUG = 0 unless defined $DEBUG;
47
48      @EXPORT = ();
49      %EXPORT_TAGS = ();
50      @EXPORT_OK = (qw(get_bugs count_bugs newest_bug bug_filter));
51      $EXPORT_TAGS{all} = [@EXPORT_OK];
52 }
53
54 use Debbugs::Config qw(:config);
55 use Params::Validate qw(validate_with :types);
56 use IO::File;
57 use Debbugs::Status qw(splitpackages get_bug_status);
58 use Debbugs::Packages qw(getsrcpkgs getpkgsrc);
59 use Debbugs::Common qw(getparsedaddrs package_maintainer getmaintainers make_list hash_slice);
60 use Fcntl qw(O_RDONLY);
61 use MLDBM qw(DB_File Storable);
62 use List::AllUtils qw(first);
63 use Carp;
64
65 =head2 get_bugs
66
67      get_bugs()
68
69 =head3 Parameters
70
71 The following parameters can either be a single scalar or a reference
72 to an array. The parameters are ANDed together, and the elements of
73 arrayrefs are a parameter are ORed. Future versions of this may allow
74 for limited regular expressions, and/or more complex expressions.
75
76 =over
77
78 =item package -- name of the binary package
79
80 =item src -- name of the source package
81
82 =item maint -- address of the maintainer
83
84 =item submitter -- address of the submitter
85
86 =item severity -- severity of the bug
87
88 =item status -- status of the bug
89
90 =item tag -- bug tags
91
92 =item owner -- owner of the bug
93
94 =item correspondent -- address of someone who sent mail to the log
95
96 =item affects -- bugs which affect this package
97
98 =item dist -- distribution (I don't know about this one yet)
99
100 =item bugs -- list of bugs to search within
101
102 =item function -- see description below
103
104 =back
105
106 =head3 Special options
107
108 The following options are special options used to modulate how the
109 searches are performed.
110
111 =over
112
113 =item archive -- whether to search archived bugs or normal bugs;
114 defaults to false. As a special case, if archive is 'both', but
115 archived and unarchived bugs are returned.
116
117 =item usertags -- set of usertags and the bugs they are applied to
118
119 =back
120
121
122 =head3 Subsidiary routines
123
124 All subsidiary routines get passed exactly the same set of options as
125 get_bugs. If for some reason they are unable to handle the options
126 passed (for example, they don't have the right type of index for the
127 type of selection) they should die as early as possible. [Using
128 Params::Validate and/or die when files don't exist makes this fairly
129 trivial.]
130
131 This function will then immediately move on to the next subroutine,
132 giving it the same arguments.
133
134 =head3 function
135
136 This option allows you to provide an arbitrary function which will be
137 given the information in the index.db file. This will be super, super
138 slow, so only do this if there's no other way to write the search.
139
140 You'll be given a list (which you can turn into a hash) like the
141 following:
142
143  (pkg => ['a','b'], # may be a scalar (most common)
144   bug => 1234,
145   status => 'pending',
146   submitter => 'boo@baz.com',
147   severity => 'serious',
148   tags => ['a','b','c'], # may be an empty arrayref
149  )
150
151 The function should return 1 if the bug should be included; 0 if the
152 bug should not.
153
154 =cut
155
156 state $_non_search_key_regex = qr/^(bugs|archive|usertags|schema)$/;
157
158 my %_get_bugs_common_options =
159     (package   => {type => SCALAR|ARRAYREF,
160                    optional => 1,
161                   },
162      src       => {type => SCALAR|ARRAYREF,
163                    optional => 1,
164                   },
165      maint     => {type => SCALAR|ARRAYREF,
166                    optional => 1,
167                   },
168      submitter => {type => SCALAR|ARRAYREF,
169                    optional => 1,
170                   },
171      severity  => {type => SCALAR|ARRAYREF,
172                    optional => 1,
173                   },
174      status    => {type => SCALAR|ARRAYREF,
175                    optional => 1,
176                   },
177      tag       => {type => SCALAR|ARRAYREF,
178                    optional => 1,
179                   },
180      owner     => {type => SCALAR|ARRAYREF,
181                    optional => 1,
182                   },
183      dist      => {type => SCALAR|ARRAYREF,
184                    optional => 1,
185                   },
186      correspondent => {type => SCALAR|ARRAYREF,
187                        optional => 1,
188                       },
189      affects   => {type => SCALAR|ARRAYREF,
190                    optional => 1,
191                   },
192      function  => {type => CODEREF,
193                    optional => 1,
194                   },
195      bugs      => {type => SCALAR|ARRAYREF,
196                    optional => 1,
197                   },
198      archive   => {type => BOOLEAN|SCALAR,
199                    default => 0,
200                   },
201      usertags  => {type => HASHREF,
202                    optional => 1,
203                   },
204      schema => {type     => OBJECT,
205                 optional => 1,
206                },
207     );
208
209
210 state $_get_bugs_options = {%_get_bugs_common_options};
211 sub get_bugs{
212      my %param = validate_with(params => \@_,
213                                spec   => $_get_bugs_options,
214                               );
215
216      # Normalize options
217      my %options = %param;
218      my @bugs;
219      if ($options{archive} eq 'both') {
220           push @bugs, get_bugs(%options,archive=>0);
221           push @bugs, get_bugs(%options,archive=>1);
222           my %bugs;
223           @bugs{@bugs} = @bugs;
224           return keys %bugs;
225      }
226      # A configuration option will set an array that we'll use here instead.
227      for my $routine (qw(Debbugs::Bugs::get_bugs_by_db Debbugs::Bugs::get_bugs_by_idx Debbugs::Bugs::get_bugs_flatfile)) {
228           my ($package) = $routine =~ m/^(.+)\:\:/;
229           eval "use $package;";
230           if ($@) {
231                # We output errors here because using an invalid function
232                # in the configuration file isn't something that should
233                # be done.
234                warn "use $package failed with $@";
235                next;
236           }
237           @bugs = eval "${routine}(\%options)";
238           if ($@) {
239
240                # We don't output errors here, because failure here
241                # via die may be a perfectly normal thing.
242                print STDERR "$@" if $DEBUG;
243                next;
244           }
245           last;
246      }
247      # If no one succeeded, die
248      if ($@) {
249           die "$@";
250      }
251      return @bugs;
252 }
253
254 =head2 count_bugs
255
256      count_bugs(function => sub {...})
257
258 Uses a subroutine to classify bugs into categories and return the
259 number of bugs which fall into those categories
260
261 =cut
262
263 sub count_bugs {
264      my %param = validate_with(params => \@_,
265                                spec   => {function => {type => CODEREF,
266                                                       },
267                                           archive  => {type => BOOLEAN,
268                                                        default => 0,
269                                                       },
270                                          },
271                               );
272      my $flatfile;
273      if ($param{archive}) {
274           $flatfile = IO::File->new("$config{spool_dir}/index.archive", 'r')
275                or die "Unable to open $config{spool_dir}/index.archive for reading: $!";
276      }
277      else {
278           $flatfile = IO::File->new("$config{spool_dir}/index.db", 'r')
279                or die "Unable to open $config{spool_dir}/index.db for reading: $!";
280      }
281      my %count = ();
282      while(<$flatfile>) {
283           if (m/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+\[\s*([^]]*)\s*\]\s+(\w+)\s+(.*)$/) {
284                my @x = $param{function}->(pkg       => $1,
285                                           bug       => $2,
286                                           status    => $4,
287                                           submitter => $5,
288                                           severity  => $6,
289                                           tags      => $7,
290                                          );
291                local $_;
292                $count{$_}++ foreach @x;
293           }
294      }
295      close $flatfile;
296      return %count;
297 }
298
299 =head2 newest_bug
300
301      my $bug = newest_bug();
302
303 Returns the bug number of the newest bug, which is nextnumber-1.
304
305 =cut
306
307 sub newest_bug {
308      my $nn_fh = IO::File->new("$config{spool_dir}/nextnumber",'r')
309           or die "Unable to open $config{spool_dir}nextnumber for reading: $!";
310      local $/;
311      my $next_number = <$nn_fh>;
312      close $nn_fh;
313      chomp $next_number;
314      return $next_number-1;
315 }
316
317 =head2 bug_filter
318
319      bug_filter
320
321 Allows filtering bugs on commonly used criteria
322
323
324
325 =cut
326
327 sub bug_filter {
328      my %param = validate_with(params => \@_,
329                                spec   => {bug    => {type => ARRAYREF|SCALAR,
330                                                      optional => 1,
331                                                     },
332                                           status => {type => HASHREF|ARRAYREF,
333                                                      optional => 1,
334                                                     },
335                                           seen_merged => {type => HASHREF,
336                                                           optional => 1,
337                                                          },
338                                           repeat_merged => {type => BOOLEAN,
339                                                             default => 1,
340                                                            },
341                                           include => {type => HASHREF,
342                                                       optional => 1,
343                                                      },
344                                           exclude => {type => HASHREF,
345                                                       optional => 1,
346                                                      },
347                                           min_days => {type => SCALAR,
348                                                        optional => 1,
349                                                       },
350                                           max_days => {type => SCALAR,
351                                                        optional => 1,
352                                                       },
353                                          },
354                               );
355      if (exists $param{repeat_merged} and
356          not $param{repeat_merged} and
357          not defined $param{seen_merged}) {
358           croak "repeat_merged false requires seen_merged to be passed";
359      }
360      if (not exists $param{bug} and not exists $param{status}) {
361          croak "one of bug or status must be passed";
362      }
363
364      if (not exists $param{status}) {
365           my $location = getbuglocation($param{bug}, 'summary');
366           return 0 if not defined $location or not length $location;
367           $param{status} = readbug( $param{bug}, $location );
368           return 0 if not defined $param{status};
369      }
370
371      if (exists $param{include}) {
372           return 1 if (!__bug_matches($param{include}, $param{status}));
373      }
374      if (exists $param{exclude}) {
375           return 1 if (__bug_matches($param{exclude}, $param{status}));
376      }
377      if (exists $param{repeat_merged} and not $param{repeat_merged}) {
378           my @merged = sort {$a<=>$b} $param{bug}, split(/ /, $param{status}{mergedwith});
379           return 1 if first {defined $_} @{$param{seen_merged}}{@merged};
380           @{$param{seen_merged}}{@merged} = (1) x @merged;
381      }
382      my $daysold = int((time - $param{status}{date}) / 86400);   # seconds to days
383      if (exists $param{min_days}) {
384           return 1 unless $param{min_days} <= $daysold;
385      }
386      if (exists $param{max_days}) {
387           return 1 unless $param{max_days} == -1 or
388                $param{max_days} >= $daysold;
389      }
390      return 0;
391 }
392
393
394 =head2 get_bugs_by_idx
395
396 This routine uses the by-$index.idx indicies to try to speed up
397 searches.
398
399
400 =cut
401
402
403 state $_get_bugs_by_idx_options =
404    {hash_slice(%_get_bugs_common_options,
405                (qw(package submitter severity tag archive),
406                 qw(owner src maint bugs correspondent),
407                 qw(affects usertags))
408               )
409    };
410 sub get_bugs_by_idx{
411      my %param = validate_with(params => \@_,
412                                spec   => $_get_bugs_by_idx_options
413                               );
414      my %bugs = ();
415
416      # If we're given an empty maint (unmaintained packages), we can't
417      # handle it, so bail out here
418      for my $maint (make_list(exists $param{maint}?$param{maint}:[])) {
419           if (defined $maint and $maint eq '') {
420                die "Can't handle empty maint (unmaintained packages) in get_bugs_by_idx";
421           }
422      }
423
424      # We handle src packages, maint and maintenc by mapping to the
425      # appropriate binary packages, then removing all packages which
426      # don't match all queries
427      my @packages = __handle_pkg_src_and_maint(map {exists $param{$_}?($_,$param{$_}):()}
428                                                qw(package src maint)
429                                               );
430      if (exists $param{package} or
431          exists $param{src} or
432          exists $param{maint}) {
433           delete @param{qw(maint src)};
434           $param{package} = [@packages];
435      }
436      my $keys = grep {$_ !~ $_non_search_key_regex} keys(%param);
437      die "Need at least 1 key to search by" unless $keys;
438      my $arc = $param{archive} ? '-arc':'';
439      my %idx;
440      for my $key (grep {$_ !~ $_non_search_key_regex} keys %param) {
441           my $index = $key;
442           $index = 'submitter-email' if $key eq 'submitter';
443           $index = "$config{spool_dir}/by-${index}${arc}.idx";
444           tie(%idx, MLDBM => $index, O_RDONLY)
445                or die "Unable to open $index: $!";
446           my %bug_matching = ();
447           for my $search (make_list($param{$key})) {
448                for my $bug (keys %{$idx{$search}||{}}) {
449                     next if $bug_matching{$bug};
450                     # increment the number of searches that this bug matched
451                     $bugs{$bug}++;
452                     $bug_matching{$bug}=1;
453                }
454                if ($search ne lc($search)) {
455                     for my $bug (keys %{$idx{lc($search)}||{}}) {
456                          next if $bug_matching{$bug};
457                          # increment the number of searches that this bug matched
458                          $bugs{$bug}++;
459                          $bug_matching{$bug}=1;
460                     }
461                }
462           }
463           if ($key eq 'tag' and exists $param{usertags}) {
464                for my $bug (make_list(grep {defined $_ } @{$param{usertags}}{make_list($param{tag})})) {
465                     next if $bug_matching{$bug};
466                     $bugs{$bug}++;
467                     $bug_matching{$bug}=1;
468                }
469           }
470           untie %idx or die 'Unable to untie %idx';
471      }
472      if ($param{bugs}) {
473           $keys++;
474           for my $bug (make_list($param{bugs})) {
475                $bugs{$bug}++;
476           }
477      }
478      # Throw out results that do not match all of the search specifications
479      return map {$keys <= $bugs{$_}?($_):()} keys %bugs;
480 }
481
482
483 =head2 get_bugs_by_db
484
485 This routine uses the database to try to speed up
486 searches.
487
488
489 =cut
490
491 state $_get_bugs_by_db_options =
492    {hash_slice(%_get_bugs_common_options,
493                (qw(package submitter severity tag archive),
494                 qw(owner src maint bugs correspondent),
495                 qw(affects usertags))
496               ),
497     schema => {type     => OBJECT,
498               },
499    };
500 sub get_bugs_by_db{
501      my %param = validate_with(params => \@_,
502                                spec   => $_get_bugs_by_db_options,
503                               );
504      my %bugs = ();
505
506      my $s = $param{schema};
507      my $keys = grep {$_ !~ $_non_search_key_regex} keys(%param);
508      die "Need at least 1 key to search by" unless $keys;
509      my $rs = $s->resultset('Bug');
510      if (exists $param{severity}) {
511          $rs = $rs->search({'severity.severity' =>
512                             [make_list($param{severity})],
513                            },
514                           {join => 'severity'},
515                           );
516      }
517      for my $key (qw(owner submitter done)) {
518          if (exists $param{$key}) {
519              $rs = $rs->search({"${key}.addr" =>
520                                 [make_list($param{$key})],
521                                },
522                               {join => $key},
523                               );
524          }
525      }
526      if (exists $param{correspondent}) {
527          my $message_rs =
528              $s->resultset('Message')->
529              search({'correspondent.addr' =>
530                      [make_list($param{correspondent})],
531                     },
532                    {join => {message_correspondents => 'correspondent'},
533                     columns => ['id'],
534                     group_by => ['me.id'],
535                    },
536                    );
537          $rs = $rs->search({'bug_messages.message' =>
538                            {-in => $message_rs->get_column('id')->as_query()},
539                            },
540                           {join => 'bug_messages',
541                           },
542                           );
543      }
544      if (exists $param{affects}) {
545          my @aff_list = make_list($param{affects});
546          $rs = $rs->search({-or => {'bin_pkg.pkg' =>
547                                     [@aff_list],
548                                     'src_pkg.pkg' =>
549                                     [@aff_list],
550                                     'me.unknown_affects' =>
551                                     [@aff_list]
552                                    },
553                            },
554                           {join => [{bug_affects_binpackages => 'bin_pkg'},
555                                    {bug_affects_srcpackages => 'src_pkg'},
556                                    ],
557                           },
558                           );
559      }
560      if (exists $param{package}) {
561          $rs = $rs->search({-or => {'bin_pkg.pkg' =>
562                                     [make_list($param{package})],
563                                     'me.unknown.package' =>
564                                     [make_list($param{package})]},
565                            },
566                           {join => {bug_binpackages => 'bin_pkg'}});
567      }
568      if (exists $param{maint}) {
569          my @maint_list =
570              map {$_ eq '' ? undef : $_}
571              make_list($param{maint});
572          my $bin_pkgs_rs =
573              $s->resultset('BinPkg')->
574              search({'correspondent.addr' => [@maint_list]},
575                    {join => {bin_vers =>
576                             {src_ver =>
577                             {maintainer => 'correspondent'}}},
578                     columns => ['id'],
579                     group_by => ['me.id'],
580                    },
581                    );
582          my $src_pkgs_rs =
583              $s->resultset('SrcPkg')->
584              search({'correspondent.addr' => [@maint_list]},
585                    {join => {src_vers =>
586                             {maintainer => 'correspondent'}},
587                     columns => ['id'],
588                     group_by => ['me.id'],
589                    },
590                    );
591          $rs = $rs->search({-or => {'bug_binpackages.bin_pkg' =>
592                                    { -in => $bin_pkgs_rs->get_column('id')->as_query},
593                                     'bug_srcpackages.src_pkg' => 
594                                    { -in => $bin_pkgs_rs->get_column('id')->as_query},
595                                    },
596                            },
597                           {join => ['bug_binpackages',
598                                     'bug_srcpackages',
599                                    ]}
600                           );
601      }
602      if (exists $param{src}) {
603          # identify all of the srcpackages and binpackages that match first
604          my $src_pkgs_rs =
605          $s->resultset('SrcPkg')->
606              search({-or => [map {('me.pkg' => $_,
607                                   )}
608                              make_list($param{src})],
609                      columns => ['id'],
610                      group_by => ['me.id'],
611                     },
612                    );
613          my $bin_pkgs_rs =
614          $s->resultset('BinPkg')->
615              search({-or => [map {('src_pkg.pkg' => $_,
616                                   )}
617                              make_list($param{src})],
618                      },
619                    {join => {bin_vers => {src_ver => 'src_pkg'}},
620                     columns => ['id'],
621                     group_by => ['me.id'],
622                    });
623          $rs = $rs->search({-or => {'bug_binpackages.bin_pkg' =>
624                                    { -in => $bin_pkgs_rs->get_column('id')->as_query},
625                                     'bug_srcpackages.src_pkg' => 
626                                    { -in => $bin_pkgs_rs->get_column('id')->as_query},
627                                     'me.unknown_package' =>
628                                     [make_list($param{src})],
629                                    },
630                            },
631                           {join => ['bug_binpackages',
632                                     'bug_srcpackages',
633                                    ]}
634                           );
635      }
636      # tags are very odd, because we must handle usertags.
637      if (exists $param{tag}) {
638          # bugs from usertags which matter
639          my %bugs_matching_usertags;
640          for my $bug (make_list(grep {defined $_ }
641                                 @{$param{usertags}}{make_list($param{tag})})) {
642              $bugs_matching_usertags{$bug} = 1;
643          }
644          # we want all bugs which either match the tag name given in
645          # param, or have a usertag set which matches one of the tag
646          # names given in param.
647          $rs = $rs->search({-or => {map {('tag.tag' => $_)}
648                                     make_list($param{tag}),
649                                     map {('me.id' => $_)}
650                                     keys %bugs_matching_usertags
651                                    },
652                            },
653                           {join => {bug_tags => 'tag'}});
654      }
655      if (exists $param{bugs}) {
656          $rs = $rs->search({-or => {map {('me.id' => $_)}
657                                     make_list($param{bugs})}
658                            });
659      }
660      # handle archive
661      if (defined $param{archive} and $param{archive} ne 'both') {
662          $rs = $rs->search({'me.archived' => $param{archive}});
663      }
664      return $rs->get_column('id')->all();
665 }
666
667
668 =head2 get_bugs_flatfile
669
670 This is the fallback search routine. It should be able to complete all
671 searches. [Or at least, that's the idea.]
672
673 =cut
674
675 state $_get_bugs_flatfile_options =
676    {hash_slice(%_get_bugs_common_options,
677                map {$_ eq 'dist'?():($_)} keys %_get_bugs_common_options
678               )
679    };
680
681 sub get_bugs_flatfile{
682      my %param = validate_with(params => \@_,
683                                spec   => $_get_bugs_flatfile_options
684                               );
685      my $flatfile;
686      if ($param{archive}) {
687           $flatfile = IO::File->new("$config{spool_dir}/index.archive", 'r')
688                or die "Unable to open $config{spool_dir}/index.archive for reading: $!";
689      }
690      else {
691           $flatfile = IO::File->new("$config{spool_dir}/index.db", 'r')
692                or die "Unable to open $config{spool_dir}/index.db for reading: $!";
693      }
694      my %usertag_bugs;
695      if (exists $param{tag} and exists $param{usertags}) {
696           # This complex slice makes a hash with the bugs which have the
697           # usertags passed in $param{tag} set.
698           @usertag_bugs{make_list(@{$param{usertags}}{make_list($param{tag})})
699                         } = (1) x make_list(@{$param{usertags}}{make_list($param{tag})});
700      }
701      my $unmaintained_packages = 0;
702      # unmaintained packages is a special case
703      my @maints = make_list(exists $param{maint}?$param{maint}:[]);
704      $param{maint} = [];
705      for my $maint (@maints) {
706           if (defined $maint and $maint eq '' and not $unmaintained_packages) {
707                $unmaintained_packages = 1;
708                our %maintainers = %{getmaintainers()};
709                $param{function} = [(exists $param{function}?
710                                     (ref $param{function}?@{$param{function}}:$param{function}):()),
711                                    sub {my %d=@_;
712                                         foreach my $try (make_list($d{"pkg"})) {
713                                              next unless length $try;
714                                              ($try) = $try =~ m/^(?:src:)?(.+)/;
715                                              return 1 if not exists $maintainers{$try};
716                                         }
717                                         return 0;
718                                    }
719                                   ];
720           }
721           elsif (defined $maint and $maint ne '') {
722                push @{$param{maint}},$maint;
723           }
724      }
725      # We handle src packages, maint and maintenc by mapping to the
726      # appropriate binary packages, then removing all packages which
727      # don't match all queries
728      my @packages = __handle_pkg_src_and_maint(map {exists $param{$_}?($_,$param{$_}):()}
729                                                qw(package src maint)
730                                               );
731      if (exists $param{package} or
732          exists $param{src} or
733          exists $param{maint}) {
734           delete @param{qw(maint src)};
735           $param{package} = [@packages] if @packages;
736      }
737      my $grep_bugs = 0;
738      my %bugs;
739      if (exists $param{bugs}) {
740           $bugs{$_} = 1 for make_list($param{bugs});
741           $grep_bugs = 1;
742      }
743      # These queries have to be handled by get_bugs_by_idx
744      if (exists $param{owner}
745          or exists $param{correspondent}
746          or exists $param{affects}) {
747           $bugs{$_} = 1 for get_bugs_by_idx(map {exists $param{$_}?($_,$param{$_}):()}
748                                             qw(owner correspondent affects),
749                                            );
750           $grep_bugs = 1;
751      }
752      my @bugs;
753      BUG: while (<$flatfile>) {
754           next unless m/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+\[\s*(.*)\s*\]\s+(\w+)\s+(.*)$/;
755           my ($pkg,$bug,$time,$status,$submitter,$severity,$tags) = ($1,$2,$3,$4,$5,$6,$7);
756           next if $grep_bugs and not exists $bugs{$bug};
757           if (exists $param{package}) {
758                my @packages = splitpackages($pkg);
759                next unless grep { my $pkg_list = $_;
760                                   grep {$pkg_list eq $_} make_list($param{package})
761                              } @packages;
762           }
763           if (exists $param{src}) {
764                my @src_packages = map { getsrcpkgs($_)} make_list($param{src});
765                my @packages = splitpackages($pkg);
766                next unless grep { my $pkg_list = $_;
767                                   grep {$pkg_list eq $_} @packages
768                              } @src_packages;
769           }
770           if (exists $param{submitter}) {
771                my @p_addrs = map {lc($_->address)}
772                     map {getparsedaddrs($_)}
773                          make_list($param{submitter});
774                my @f_addrs = map {$_->address}
775                     getparsedaddrs($submitter||'');
776                next unless grep { my $f_addr = $_; 
777                                   grep {$f_addr eq $_} @p_addrs
778                              } @f_addrs;
779           }
780           next if exists $param{severity} and not grep {$severity eq $_} make_list($param{severity});
781           next if exists $param{status} and not grep {$status eq $_} make_list($param{status});
782           if (exists $param{tag}) {
783                my $bug_ok = 0;
784                # either a normal tag, or a usertag must be set
785                $bug_ok = 1 if exists $param{usertags} and $usertag_bugs{$bug};
786                my @bug_tags = split ' ', $tags;
787                $bug_ok = 1 if grep {my $bug_tag = $_;
788                                     grep {$bug_tag eq $_} make_list($param{tag});
789                                } @bug_tags;
790                next unless $bug_ok;
791           }
792           # We do this last, because a function may be slow...
793           if (exists $param{function}) {
794                my @bug_tags = split ' ', $tags;
795                my @packages = splitpackages($pkg);
796                my $package = (@packages > 1)?\@packages:$packages[0];
797                for my $function (make_list($param{function})) {
798                     next BUG unless
799                          $function->(pkg       => $package,
800                                      bug       => $bug,
801                                      status    => $status,
802                                      submitter => $submitter,
803                                      severity  => $severity,
804                                      tags      => \@bug_tags,
805                                     );
806                }
807           }
808           push @bugs, $bug;
809      }
810      return @bugs;
811 }
812
813 =head1 PRIVATE FUNCTIONS
814
815 =head2 __handle_pkg_src_and_maint
816
817      my @packages = __handle_pkg_src_and_maint(map {exists $param{$_}?($_,$param{$_}):()}
818                                                qw(package src maint)
819                                               );
820
821 Turn package/src/maint into a list of packages
822
823 =cut
824
825 sub __handle_pkg_src_and_maint{
826      my %param = validate_with(params => \@_,
827                                spec   => {package   => {type => SCALAR|ARRAYREF,
828                                                         optional => 1,
829                                                        },
830                                           src       => {type => SCALAR|ARRAYREF,
831                                                         optional => 1,
832                                                        },
833                                           maint     => {type => SCALAR|ARRAYREF,
834                                                         optional => 1,
835                                                        },
836                                          },
837                                allow_extra => 1,
838                               );
839
840      my @packages;
841      @packages = make_list($param{package}) if exists $param{package};
842      my $package_keys = @packages?1:0;
843      my %packages;
844      @packages{@packages} = (1) x @packages;
845      if (exists $param{src}) {
846           # We only want to increment the number of keys if there is
847           # something to match
848           my $key_inc = 0;
849           # in case there are binaries with the same name as the
850           # source
851           my %_temp_p = ();
852           for my $package ((map {getsrcpkgs($_)} make_list($param{src}))) {
853                $packages{$package}++ unless exists $_temp_p{$package};
854                $_temp_p{$package} = 1;
855                $key_inc=1;
856           }
857           for my $package (make_list($param{src})) {
858                $packages{"src:$package"}++ unless exists $_temp_p{"src:$package"};
859                $_temp_p{"src:$package"} = 1;
860                $key_inc=1;
861                # As a temporary hack, we will also include $param{src}
862                # in this list for packages passed which do not have a
863                # corresponding binary package
864                if (not exists getpkgsrc()->{$package}) {
865                    $packages{$package}++ unless exists $_temp_p{$package};
866                    $_temp_p{$package} = 1;
867                }
868           }
869           $package_keys += $key_inc;
870      }
871      if (exists $param{maint}) {
872           my $key_inc = 0;
873           my %_temp_p = ();
874           for my $package (package_maintainer(maintainer=>$param{maint})) {
875                $packages{$package}++ unless exists $_temp_p{$package};
876                $_temp_p{$package} = 1;
877                $key_inc = 1;
878           }
879           $package_keys += $key_inc;
880      }
881      return grep {$packages{$_} >= $package_keys} keys %packages;
882 }
883
884 state $field_match = {
885     'subject' => \&__contains_field_match,
886     'tags' => sub {
887         my ($field, $values, $status) = @_; 
888         my %values = map {$_=>1} @$values;
889         foreach my $t (split /\s+/, $status->{$field}) {
890             return 1 if (defined $values{$t});
891         }
892         return 0;
893     },
894     'severity' => \&__exact_field_match,
895     'pending' => \&__exact_field_match,
896     'package' => \&__exact_field_match,
897     'originator' => \&__contains_field_match,
898     'forwarded' => \&__contains_field_match,
899     'owner' => \&__contains_field_match,
900 };
901
902 sub __bug_matches {
903     my ($hash, $status) = @_;
904     foreach my $key( keys( %$hash ) ) {
905         my $value = $hash->{$key};
906         next unless exists $field_match->{$key};
907         my $sub = $field_match->{$key};
908         if (not defined $sub) {
909             die "No defined subroutine for key: $key";
910         }
911         return 1 if ($sub->($key, $value, $status));
912     }
913     return 0;
914 }
915
916 sub __exact_field_match {
917     my ($field, $values, $status) = @_; 
918     my @values = @$values;
919     my @ret = grep {$_ eq $status->{$field} } @values;
920     $#ret != -1;
921 }
922
923 sub __contains_field_match {
924     my ($field, $values, $status) = @_; 
925     foreach my $data (@$values) {
926         return 1 if (index($status->{$field}, $data) > -1);
927     }
928     return 0;
929 }
930
931
932
933
934
935 1;
936
937 __END__