]> git.donarmstrong.com Git - debbugs.git/blob - scripts/gen-indices
reimplement _add_member_hash for speed; add _shallow_clone for limit
[debbugs.git] / scripts / gen-indices
1 #!/usr/bin/perl
2 # gen-indices generates bug index files, and is released
3 # under the terms of the GPL version 2, or any later version, at your
4 # option. See the file README and COPYING for more information.
5
6 # Copyright (c) 2005/08/03 Anthony Towns
7 # Copyright 2007, 2008 by Don Armstrong <don@donarmstrong.com>.
8
9 use warnings;
10 use strict;
11
12 use DB_File;
13 use MLDBM qw(DB_FILE Storable);
14 use Fcntl qw/O_RDWR O_CREAT O_TRUNC/;
15 use File::Copy;
16
17 use Getopt::Long;
18 use Pod::Usage;
19
20 use File::stat;
21 use List::AllUtils qw(min);
22
23 use Debbugs::Common qw(make_list);
24
25 =head1 NAME
26
27 gen-indices - Generates index files for the cgi scripts
28
29 =head1 SYNOPSIS
30
31  gen-indices [options]
32
33  Options:
34   --index-path path to index location
35   --quick update changed bugs
36   --debug, -d debugging level (Default 0)
37   --help, -h display this help
38   --man, -m display manual
39
40 =head1 OPTIONS
41
42 =over
43
44 =item B<--quick>
45
46 Only update changed bugs
47
48 =item B<--debug, -d>
49
50 Debug verbosity. (Default 0)
51
52 =item B<--help, -h>
53
54 Display brief useage information.
55
56 =item B<--man, -m>
57
58 Display this manual.
59
60 =back
61
62 =head1 EXAMPLES
63
64
65 =cut
66
67 # Use portable Storable images
68 $MLDBM::DumpMeth=q(portable);
69
70
71 my %options = (debug           => 0,
72                help            => 0,
73                man             => 0,
74                quick           => 0,
75                index_path      => undef,
76                );
77
78 GetOptions(\%options,'quick!','index_path|index-path=s','debug|d+','help|h|?','man|m') or pod2usage(2);
79 pod2usage(1) if $options{help};
80 pod2usage(-verbose=>2) if $options{man};
81
82 use Debbugs::Config qw(:config);
83 use Debbugs::Common qw(getparsedaddrs getbugcomponent lockpid);
84 use Debbugs::Status qw(readbug split_status_fields);
85 use Debbugs::Log;
86 use Debbugs::UTF8 qw(encode_utf8_structure);
87
88 chdir($config{spool_dir}) or die "chdir $config{spool_dir} failed: $!";
89
90 my $verbose = $options{debug};
91 my $indexdest = $options{index_path} || $config{spool_dir};
92
93 my $initialdir = "db-h";
94 my $suffix = "";
95
96 if (defined $ARGV[0] and $ARGV[0] eq "archive") {
97     $initialdir = "archive";
98     $suffix = "-arc";
99 }
100
101 if (not lockpid($config{spool_dir}.'/lock/gen-indices')) {
102      if ($options{quick}) {
103           # If this is a quick run, just exit
104           print STDERR "Another gen-indices is running; stopping\n" if $verbose;
105           exit 0;
106      }
107      print STDERR "Another gen-indices is running; stopping\n";
108      exit 1;
109 }
110
111 # NB: The reverse index is special; it's used to clean up during updates to bugs
112 my @indexes = ('package', 'tag', 'severity','owner','submitter-email','status','correspondent','affects','reverse');
113 my $indexes;
114 my %slow_index = ();
115 my %fast_index = ();
116 if (not $options{quick}) {
117      # We'll trade memory for speed here if we're not doing a quick rebuild
118      for my $indexes (@indexes) {
119           $fast_index{$indexes} = {};
120      }
121      $indexes = \%fast_index;
122 }
123 else {
124      $indexes = \%slow_index;
125 }
126 my $time = undef;
127 my $start_time = time;
128 for my $i (@indexes) {
129         $slow_index{$i} = {};
130         if ($options{quick}) {
131              if (-e "$indexdest/by-$i${suffix}.idx") {
132                   system('cp','-a',"$indexdest/by-$i${suffix}.idx","$indexdest/by-$i${suffix}.idx.new") == 0
133                        or die "Error creating the new index";
134                   my $stat = stat("$indexdest/by-$i${suffix}.idx") or die "Unable to stat $indexdest/by-$i${suffix}.idx";
135                   $time = defined $time ? min($time,$stat->mtime) : $stat->mtime;
136              }
137              tie %{$slow_index{$i}}, MLDBM => "$indexdest/by-$i$suffix.idx.new",
138                   O_RDWR|O_CREAT, 0666
139                        or die "$0: can't create by-$i$suffix-idx.new: $!";
140         }
141         else {
142              tie %{$slow_index{$i}}, MLDBM => "$indexdest/by-$i$suffix.idx.new",
143                   O_RDWR|O_CREAT|O_TRUNC, 0666
144                        or die "$0: can't create by-$i$suffix-idx.new: $!";
145
146         }
147         $time = 0 if not defined $time;
148 }
149
150 sub addbugtoindex {
151      my ($index, $bug, @values) = @_;
152
153      if (exists $indexes->{reverse}{"$index $bug"}) {
154           # We do this insanity to work around a "feature" in MLDBM
155           for my $key (@{$indexes->{reverse}{"$index $bug"}}) {
156                my $temp = $indexes->{$index}{$key};
157                delete $temp->{$bug};
158                $indexes->{$index}{$key} = $temp;
159                $indexes->{$index}{"count $key"}--;
160           }
161           delete $indexes->{reverse}{"$index $bug"};
162      }
163      for my $key (@values) {
164           $indexes->{$index}->{"count $key"}++;
165           # We do this insanity to work around a "feature" in MLDBM
166           my $temp = $indexes->{$index}->{$key};
167           $temp->{$bug} = 1;
168           $indexes->{$index}->{$key} = $temp;
169      }
170      $indexes->{reverse}{"$index $bug"} = [@values];
171 }
172
173 sub emailfromrfc822 {
174         my $email = shift;
175         $email =~ s/\s*\(.*\)\s*//;
176         $email = $1 if ($email =~ m/<(.*)>/);
177         return $email;
178 }
179
180 my $modification_made = 0;
181 my $cnt = 0;
182
183 my @dirs = ($initialdir);
184 while (my $dir = shift @dirs) {
185         printf "Doing dir %s ...\n", $dir if $verbose;
186
187         opendir(DIR, "$dir/.") or die "opendir $dir: $!";
188         my @subdirs = readdir(DIR);
189         closedir(DIR);
190
191         my @list = map { m/^(\d+)\.summary$/?($1):() } @subdirs;
192         push @dirs, map { m/^(\d+)$/ && -d "$dir/$1"?("$dir/$1"):() } @subdirs;
193
194         for my $bug (@list) {
195                 print "Up to $cnt bugs...\n" if (++$cnt % 100 == 0 && $verbose);
196                 my $stat = stat(getbugcomponent($bug,'summary',$initialdir));
197                 if (not defined $stat) {
198                      print STDERR "Unable to stat $bug $!\n";
199                      next;
200                 }
201                 next if $stat->mtime < $time;
202                 my ($fdata) = encode_utf8_structure(split_status_fields(readbug($bug, $initialdir)));
203                 $modification_made = 1;
204                 addbugtoindex("package", $bug, make_list($fdata->{package}));
205                 addbugtoindex("tag", $bug, make_list($fdata->{keywords}));
206                 addbugtoindex("affects", $bug, make_list($fdata->{"affects"}));
207                 addbugtoindex('submitter-email', $bug,
208                               map {lc($_->address)} getparsedaddrs($fdata->{originator}));
209                 addbugtoindex("severity", $bug, $fdata->{"severity"});
210                 addbugtoindex("owner", $bug,
211                               map {lc($_->address)} getparsedaddrs($fdata->{"owner"}));
212                 # handle log entries
213                 # do this in eval to avoid exploding on jacked logs
214                 eval {
215                     my $log = Debbugs::Log->new(bug_num => $bug);
216                     my @correspondents;
217                     while (my $record = $log->read_record()) {
218                         next unless $record->{type} eq 'incoming-recv';
219                         # we use a regex here, because a full mime parse will be slow.
220                         my ($from) = $record->{text} =~ /^From:\s+(.+?)^\S/ism;
221                         push @correspondents, map {lc($_->address)} getparsedaddrs($from);
222                     }
223                     addbugtoindex('correspondent',$bug,@correspondents) if @correspondents;
224                 };
225                 if ($@) {
226                      print STDERR "Problem dealing with log of $bug: $@";
227                 }
228            }
229 }
230
231 if (not $options{quick}) {
232      # put the fast index into the slow index
233      for my $key1 (keys %fast_index) {
234           for my $key2 (keys %{$fast_index{$key1}}) {
235                $slow_index{$key1}{$key2} = $fast_index{$key1}{$key2};
236           }
237           print "Dealt with index $key1\n" if $verbose;
238      }
239 }
240
241 for my $i (@indexes) {
242     untie %{$slow_index{$i}};
243     # Only move if we've made changes, otherwise unlink
244     if ($modification_made) {
245         move("$indexdest/by-$i$suffix.idx.new", "$indexdest/by-$i$suffix.idx");
246         # We do this, because old versions of touch don't support -d '@epoch'
247         system('touch','-d',"1/1/1970 UTC + ${start_time}secs","$indexdest/by-$i$suffix.idx");
248     }
249     else {
250         unlink("$indexdest/by-$i$suffix.idx.new");
251     }
252 }
253
254 unlink($config{spool_dir}.'/lock/gen-indices')