root/bin/pl-thread

Revision 326:6fe792e6c8f1, 7.2 KB (checked in by Anselm Lingnau <anselm@…>, 9 months ago)

Big get_config() cleanup to get rid of hard_coded defaults.
We're now getting parameter defaults from a central list instead of putting
them near the get_config() calls themselves, to avoid possible inconsistencies.
If for whatever reasons parameters end up with obviously invalid values, we
still try to do the Right Thing(TM).

Line 
1#!/usr/bin/perl -w
2
3=head1 NAME
4
5pl-thread - add message to threaded archive, or rebuild archive thread index
6
7=head1 SYNOPSIS
8
9pl-thread [--rebuild] LIST@DOMAIN [MESSAGE ...]
10
11=head1 AUTHOR
12
13Anselm Lingnau <anselm@anselms.net>
14
15=head1 COPYRIGHT AND LICENSE
16
17Copyright 2011 by Anselm Lingnau. This program is free software; you
18may redistribute it and/or modify it under the terms of the GNU
19General Public License as published by the Free Software Foundation;
20either version 2 of the License, or (at your option) any later version.
21
22This program is distributed in the hope that it will be useful, but
23WITHOUT ANY WARRANTY; without even the implied warranty of
24MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25General Public License for more details.
26
27You should have received a copy of the GNU General Public License
28along with this program; if not, refer to
29<URL:http://www.gnu.org/copyleft/gpl.html>. You may also obtain it by
30writing to the Free Software Foundation, Inc., 59 Temple Place - Suite
31330, Boston, MA 02111-1307, USA.
32
33=cut
34
35use strict;
36
37use IO::File;
38use Email::Address;
39use Email::Date;
40use Time::Piece;
41use DBI;
42use Encode qw/encode decode/;
43
44use Lancelot::GetOpt qw/GetOptions/;
45
46use Lancelot::Message;
47use Lancelot::DB;
48
49use Lancelot::Log qw/log/;
50
51use Lancelot::Module::archive_store;
52
53my $ddate = 0;
54my $rebuild = 0;
55my %opts;
56
57GetOptions(\%opts, -argvmin => 1, "no list address specified",
58                   "rebuild!");
59
60my $listaddr = shift;
61
62my ($list, $domain) = split /\@/, $listaddr, 2;
63
64my $msgid = "<$list-%d\@msgid.$domain>";
65
66my (@head, %threads, %last);
67my (%subjects, %senders, %sender_info, %message, %last_subject);
68my ($subject_count, $sender_count);
69my $prev_date = 0;
70
71my (@month, %month_index, $m_idx);
72
73my $db = new Lancelot::DB $listaddr, { user => $opts{user} }
74    or die "couldn't find mailing list $listaddr\n";
75
76if ($opts{rebuild}) {
77    rebuild_index($db);
78} elsif (@ARGV) {
79    foreach my $fn (@ARGV) {
80        my $f = IO::File->new($fn);
81        add_message(join "", <$f>);
82        $f->close();
83    }
84} else {
85    add_message(join "", <STDIN>);
86}
87
88sub rebuild_index {
89    my $dir = $db->get_config("archive.directory");
90    unless ($dir) {
91        log "warn", "this list is not archived (no archive.directory)";
92        return;
93    }
94    $dir = File::Spec->join($db->get_listdir(), $dir) if $dir !~ m!^/!;
95    my $dbh = Lancelot::Module::archive_store::init_database($dir);
96
97    print STDERR "Reading messages\n";
98    my $min = 0;
99    my $max = $db->get_parameter("message");
100    # my $max = $min+200;
101    foreach my $i ($min .. $max) {
102        print STDERR "\n$i" if $i % 1000 == 0;
103        read_message_bulk($dir, $i);
104    }
105    print STDERR "\n";
106    print STDERR "Identifying threads\n";
107    identify_threads(0, $max);
108    print STDERR scalar(@head) . " threads found\n";
109    output_threads($dbh);
110    print STDERR "Done.\n";
111}
112
113sub read_message_bulk {
114    my ($dir, $n) = @_;
115    my $fn = sprintf("%s/%d/%02d", $dir, int($n/100), $n % 100);
116    if (-e $fn) {
117        my $f = IO::File->new($fn);
118        my $msg = Lancelot::Message->new(join "", <$f>);
119        $f->close;
120
121        process_message_bulk($n, $msg);
122    }
123}
124
125my @m;
126
127sub process_message_bulk {
128    my ($n, $msg) = @_;
129
130    $m[$n]->{date} = Lancelot::Module::archive_store::determine_date($msg) || gmtime;
131    if ($prev_date != 0 && (abs($m[$n]->{date}->epoch - $prev_date) > 15*24*3600)) {
132        print STDERR "Unusual date: $n: $m[$n]->{date} (vs. $prev_date)\n";
133    }
134    $prev_date = $m[$n]->{date}->epoch;
135
136    $m[$n]->{msgid} = $msg->header("Message-ID") || sprintf $msgid, $n;
137    $message{$m[$n]->{msgid}} = $n;
138
139    my $subject = Lancelot::Module::archive_store::simplify_subject($msg->header("Subject"));
140    $m[$n]->{subject} = store_subject($subject);
141    my $sender = $msg->header("From") || "Anonymous <anonymous\@$domain>";
142    $m[$n]->{sender} = store_sender($sender);
143
144    $m[$n]->{parent} = Lancelot::Module::archive_store::find_parent(undef, $n, $msg, $m[$n]->{subject},
145                                   \%last_subject, \%message);
146    $m[$n]->{root} = find_root($n);
147    my ($year, $month) = ($m[$n]->{date}->year, $m[$n]->{date}->mon);
148    my $m_id;
149    unless ($m_id = $month_index{"$year-$month"}) {
150        push @month, [ ++$m_idx, $year, $month ];
151        $m_id = $month_index{"$year-$month"} = $month[$#month]->[0];
152    }
153    $m[$n]->{month_id} = $m_id;
154    $m[$m[$n]->{root}]->{lastmsg_id} = $n;
155}
156
157sub find_root {
158    my ($n) = @_;
159
160    my $parent = $n;
161    while ($n != -1) {
162        $parent = $n;
163        $n = $m[$n]->{parent};
164    }
165    return $parent;
166}
167
168sub store_subject {
169    my ($s) = @_;
170    return $subjects{$s} if defined $subjects{$s};
171    return $subjects{$s} = $subject_count++;
172}
173
174sub store_sender {
175    my @a = Email::Address->parse($_[0]);
176    my $s = $a[0] ? $a[0]->name : "Anonymous";
177    my $a = $a[0] ? $a[0]->address : "anonymous\@$domain";
178    my $f = $a[0] ? $a[0]->format : qq|"$s" <$a>|;
179    return $senders{$a} if defined $senders{$a};
180    $sender_info{$a} = [$s, $a];
181    return $senders{$a} = $sender_count++;
182}
183
184sub identify_threads {
185    my ($from, $to) = @_;
186    my %kids;
187
188    # print STDERR "identify_threads: from=$from to=$to\n";
189    for (my $i = $from; $i <= $to; $i++) {
190        next unless $m[$i];
191        push @head, $i if $i == $m[$i]->{root};
192        push @{$kids{$m[$i]->{parent}}}, $i if $m[$i]->{parent} >= 0;
193    }
194
195    foreach my $h (@head) {
196        my $tt = {};
197        $threads{$h} = [];
198        Lancelot::Module::archive_store::visit_kids(\%kids, $h, 0, 0,
199                                                    $threads{$h}, $tt);
200        $last{$h} = ${$threads{$h}}[-1];
201        $m[$_]->{level} = $tt->{$_}->{level} foreach (keys %$tt);
202    }
203}
204
205sub output_threads {
206    my ($dbh) = @_;
207    my $prev = -1;
208
209    $dbh->begin_work;
210
211    print STDERR "Writing senders ($sender_count) ...\n";
212    my $sth = $dbh->prepare("INSERT INTO sender VALUES(?,?,?,?)");
213    foreach my $a (keys %senders) {
214        my $s = $sender_info{$a}->[0];
215        $s = decode("MIME-Header", $s) if $s =~ /=\?.*\?[qb]\?/i;
216        $s = encode("utf-8", $s);
217        my $s_id = $db->get_subscription_id($a);
218        $sth->execute($senders{$a}, $s, $a, $s_id);
219    }
220
221    print STDERR "Writing subjects ($subject_count) ...\n";
222    $sth = $dbh->prepare("INSERT INTO subject VALUES(?,?)");
223    foreach my $k (keys %subjects) {
224        my $s = $k;
225        $s = encode("utf-8", $s);
226        $sth->execute($subjects{$k}, $s) or print STDERR "$k: " . $dbh->error;
227    }
228
229    print STDERR "Writing month records ...\n";
230    $sth = $dbh->prepare("INSERT INTO month VALUES(?,?,?)");
231    foreach my $m (@month) {
232        $sth->execute(@$m);
233    }
234
235    print STDERR "\nWriting messages and threads ...\n";
236    $sth = $dbh->prepare("INSERT INTO message VALUES(?,?,?,?,?,?,?,?,?,?,?)");
237    my $tth = $dbh->prepare("INSERT INTO thread VALUES(?,?,?,?,?,?,?)");
238
239    foreach my $hh (0 .. $#head) {
240        my $h = $head[$hh];
241        my $count = 0;
242        my @t = @{$threads{$h}};
243        unshift @t, $hh ? $last{$head[$hh-1]} : -1;
244        push @t, $hh < $#head ? $head[$hh+1] : -1;
245        foreach my $i (1 .. $#t-1) {
246            my $k = $t[$i];
247            my $m = $m[$k];
248            $sth->execute($k, $m->{root}, $count, $m->{parent}, $m->{level},
249                          $t[$i-1], $t[$i+1], $m->{sender}, $m->{date}->epoch,
250                          $m->{subject}, $m->{msgid});
251            $count++;
252        }
253        $tth->execute($h, $m[$t[1]]->{month_id}, $m[$t[1]]->{subject},
254                      $m[$t[1]]->{date}->epoch, $m[$t[1]]->{lastmsg_id},
255                      $hh ? $head[$hh-1] : -1, $hh < $#head ? $head[$hh+1] : -1);
256    }
257
258    print STDERR "Committing database ...\n";
259    $dbh->commit;
260}
Note: See TracBrowser for help on using the browser.