/[mws]/trunk/lib/MWS.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/lib/MWS.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

trunk/MWS.pm revision 4 by dpavlin, Tue May 4 15:47:14 2004 UTC trunk/lib/MWS.pm revision 52 by dpavlin, Tue May 25 18:55:46 2004 UTC
# Line 4  package MWS; Line 4  package MWS;
4    
5  use strict;  use strict;
6  use warnings;  use warnings;
 use Carp;  
7    
8  use Plucene::Simple;  use lib '.';
9    
10    use Carp;
11  use Mail::Box::Manager;  use Mail::Box::Manager;
12  use Config::IniFiles;  use Config::IniFiles;
13    use POSIX qw(strftime);
14  require Exporter;  use Text::Autoformat;
15    use Text::Iconv;
16  our @ISA = qw(Exporter);  use Text::Unaccent;
17    use Date::Parse;
18  our %EXPORT_TAGS = ();  use POSIX qw(strftime);
19  our @EXPORT_OK;  use MIME::Base64;
 our @EXPORT;  
20    
21  our $VERSION = '1.00';  our $VERSION = '1.00';
22    
   
 my $folder;     # placeholder for folders  
   
23  my $debug = 1;  my $debug = 1;
24    
25    my @counters = qw(from to cc bcc folder);
26    
27  sub new {  sub new {
28          my $class = shift;          my $class = shift;
29          my $self = {};          my $self = {@_};
30          bless($self, $class);          bless($self, $class);
31    
         my $config_file = shift || die "need index file";  
32    
33          $self->{config} = new Config::IniFiles( -file => $config_file );          my $config_name = $self->{config_file} || croak "need config_file";
34            $config_name =~ s/\.conf.*$//;
35            $self->{config_name} = $config_name;
36    
37          my $index_file = $self->{config}->val('global', 'index') || croak "can't find [index] section in config file with path of index";          $self->{config} = new Config::IniFiles( -file => $self->{config_file} );
38    
39          $self->{index} = Plucene::Simple->open($index_file) || croak "can't open index '$index_file': $!";          my $index_dir = $self->{config}->val('global', 'index') || croak "can't find [index] section in config file with path of index";
40    
41          $self->{mgr} = Mail::Box::Manager->new;          $self->{mgr} = Mail::Box::Manager->new(access => 'r');
42            $self->{index_dir} = $index_dir;
43    
44          # placeholder for opened folders          # placeholder for opened folders
45          $self->{folder} = {};          $self->{folder} = {};
46    
47            $self->{wrap_margin} = $self->{config}->val('global', 'wrap_margin');
48            $self->{max_results} = $self->{config}->val('global', 'max_results') || 100;
49            $self->reset_counters;
50    
51          return $self;          return $self;
52  }  }
53    
54    sub normalize_string {
55            my $self = shift;
56    
57            my $v = shift || return;
58    
59            $v = unac_string('ISO-8859-2', $v);
60            $v = join('',sort split(/\s+/,$v));
61            $v =~ s/\W+//g;
62    
63            return lc($v);
64    }
65    
66    # reset tables for search results
67    sub reset_counters {
68            my $self = shift;
69    
70            $self->{counter} = {};
71    
72    #       foreach my $c (qw(thread from to cc bcc lists links att)) {
73    #               $self->{counter}->{$c} = {};
74    #       }
75    
76    }
77    
78    sub add_counter($$) {
79            my $self = shift;
80    
81            my ($c,$v) = @_;
82            my $k = $self->normalize_string($v);
83    
84            $self->{counter}->{$c}->{$k}->{name} = $v || return;
85            return $self->{counter}->{$c}->{$k}->{usage}++;
86    }
87    
88    sub yyyymmdd {
89            my $self = shift;
90    
91            my $t = shift || time;
92    
93            my (undef,undef,undef,$dd,$mm,$yyyy) = localtime($t);
94            $mm++;
95            $yyyy+=1900;
96            return ($yyyy,$mm,$dd);
97    }
98    
99    sub fmtdate {
100            my $self = shift;
101    
102            my @out;
103            my @formats = qw(%04d %02d %02d);
104            while (my $v = shift) {
105                    my $f = shift @formats;
106                    push @out, sprintf($f, $v);
107            }
108    
109            print STDERR "fmtdate: ",join('|',@out),"\n" if ($debug == 2);
110    
111            return (wantarray ? @out : join("-",@out));
112    }
113    
114    sub add_counter_calendar($) {
115            my $self = shift;
116    
117            my $t = shift || return;
118            
119            my ($yyyy,$mm,$dd) = $self->fmtdate($self->yyyymmdd($t));
120    
121            return $self->{counter}->{calendar}->{"$yyyy-$mm"}->{$dd}++;
122    }
123    
124    
125    sub counter {
126            my $self = shift;
127    
128            my $c = shift || return;
129    
130            return if (! $self->{counter}->{$c});
131    
132            return $self->{counter}->{$c};
133    }
134    
135    sub mbox_name2path {
136            my $self = shift;
137    
138            my $mbox = shift || croak "folder_name2path needs mbox name";
139    
140            return $self->{config}->val('folders', $mbox) || croak "comeone removed folder $mbox from config?";
141    }
142    
143    sub open_folder {
144            my $self = shift;
145    
146            my $mbox = shift || croak "open_folder needs mbox name";
147    
148            if (! $self->{folder}->{$mbox}) {
149                    my $mbox_path = $self->mbox_name2path($mbox);
150    
151                    print STDERR "about to open_folder($mbox)\n" if ($debug == 2);
152    
153                    my $folder;
154    
155                    # is this maildir (no locking anyway)
156                    if (-d $mbox_path) {
157                            $folder = $self->{mgr}->open($mbox_path);
158                    } else {
159                            $folder = $self->{mgr}->open($mbox_path, lock_type=>'none' );
160                    }
161    
162                    $self->{folder}->{$mbox} = $folder || croak "can't open folder $mbox at '$mbox_path': $!";
163    
164                    print STDERR "open_folder($mbox) ok\n" if ($debug);
165            }
166    
167            $self->{fetch_count} = 0;
168    
169            return $self->{folder}->{$mbox};
170    
171    }
172    
173    sub close_folder {
174            my $self = shift;
175    
176            my $mbox = shift || croak "open_folder needs mbox name";
177    
178            $self->{folder}->{$mbox}->close(write => 'NEVER') || croak "can't close folder $mbox";
179    
180            # XXX this is rather agressive!!!
181            $self->{folder} = {};
182            return
183    }
184    
185  sub fetch_message {  sub fetch_message {
186          my $self = shift;          my $self = shift;
# Line 53  sub fetch_message { Line 188  sub fetch_message {
188          my $mbox_id = shift || die "need mbox_id!";          my $mbox_id = shift || die "need mbox_id!";
189          my ($mbox,$id) = split(/ /,$mbox_id);          my ($mbox,$id) = split(/ /,$mbox_id);
190    
191          if (! $self->{folder}->{$mbox}) {          # return message with ID
192                  my $mbox_path = $self->{config}->val('folders', $mbox) || croak "comeone removed folder $mbox from config?";          print STDERR "fetch $id from $mbox\n" if ($debug);
                 $self->{folder}->{$mbox} = $self->{mgr}->open($mbox_path) || croak "can't open folder $mbox at '$mbox_path': $!";  
                 print STDERR "## open($mbox)\n" if ($debug);  
         }  
193    
194          my $message = $self->{folder}->{$mbox}->find($id) ||          if ($self->{fetch_count}++ > 100) {
195                  print STDERR "can't find message $id in $mbox. Time to re-index?\n";                  $self->close_folder($mbox);
196                    print STDERR "close_folder($mbox) forced on ",$self->{fetch_count},"iteration\n";
197            }
198                    
199          return $message;          my $msg = $self->open_folder($mbox)->find($id);
200            if ($msg) {
201                    return $msg;
202            } else {
203                    print STDERR "can't find message $id in $mbox. Time to re-index?\n";
204                    return;
205            }
206  }  }
207    
208    
209  sub search {  sub search {
210          my $self = shift;          my $self = shift;
211    
212          my $s = shift || carp "search called without argument!";          carp "search called without argument!" if (! @_);
213    
214            $self->reset_counters;
215    
216          my @index_ids = $self->{index}->search($s);          print STDERR "search(",join(" ",@_),")\n" if ($debug == 2);
217            my @index_ids = $self->search_index(@_);
218    
219          $self->{'index_ids'} = \@index_ids;          $self->{'index_ids'} = \@index_ids;
220    
221          my $results = $#index_ids + 1;          #my $results = $#index_ids + 1;
222          $self->{'results'} = $results;          #$self->{'results'} = $results;
223            
224            my $results = $self->{'total_hits'} || ($#index_ids + 1);
225    
226          $self->{'curr_result'} = 0;          $self->{'curr_result'} = 0;
227    
228            $self->reset_counters;
229    
230            print STDERR "$results results\n" if ($debug == 2);
231    
232          return $results || 'error';          return $results || 'error';
233  }  }
234    
235    sub decode_qp($) {
236            my $self = shift;
237    
238            my $tmp = shift || return;
239    
240            sub decode($$$) {
241                    my ($cp,$enc,$qp) = @_;
242    
243                    print STDERR "decode($cp,$qp) -> " if ($debug == 2);
244    
245                    if (uc($enc) eq "Q") {
246                            $qp =~ s/=([a-f0-9][a-f0-9])/chr(hex($1))/ieg;
247                            $qp =~ s/_/ /g;
248                    } elsif (uc($enc) eq "B") {
249                            $qp = decode_base64($qp);
250                    } else {
251                            croak "unsupported encoding '$enc' in decode_qp\n";
252                            return $qp;
253                    }
254    
255                    print STDERR "$qp\n" if ($debug == 2);
256    
257                    my $iconv;
258                    eval '$iconv = Text::Iconv->new($cp,"ISO-8859-2")';
259                    if ($iconv) {
260                            return $iconv->convert($qp) || '';
261                    } else {
262                            return $qp;
263                    }
264            }
265    
266            $tmp =~ s/=\?([^\?]+)\?([QB])\?(.+?)\?=/decode($1,$2,$3)/ige;
267            $tmp =~ s/^\s*["']+(.*?)["']+\s*$/$1/g;
268            #print STDERR "$tmp\n" if ($debug == 2);
269            return $tmp;
270    }
271    
272  sub unroll($$$) {  sub unroll($$$) {
273            my $self = shift;
274    
275          my ($message,$part,$sub) = @_;          my ($message,$part,$sub) = @_;
276    
277          my @arr;          my @arr;
278    
279            return if (! $message->$part);
280    
281          foreach my $from ($message->$part) {          foreach my $from ($message->$part) {
282                  push @arr, $from->$sub;                  my $tmp = $from->$sub || next;
283    
284                    $tmp = $self->decode_qp($tmp);
285                    push @arr, $tmp;
286          }          }
287          return \@arr;  
288            return @arr;
289  }  }
290    
291  sub fetch_all_results {  sub fetch_all_results {
# Line 99  sub fetch_all_results { Line 293  sub fetch_all_results {
293    
294          croak "results called before search!" if (! $self->{'index_ids'});          croak "results called before search!" if (! $self->{'index_ids'});
295    
296            print STDERR "fetch_all_results [",scalar @{$self->{'index_ids'}},"]\n" if ($debug == 2);
297    
298          my @arr;          my @arr;
299    
300          foreach my $id (@{$self->{'index_ids'}}) {          foreach my $id (@{$self->{'index_ids'}}) {
301                  push @arr, $self->fetch_result_by_id($id);                  push @arr, $self->fetch_result_by_id($id);
302          }          }
303    
304    
305          return @arr;          return @arr;
306  }  }
307    
# Line 118  sub fetch_result { Line 315  sub fetch_result {
315          my $curr = $self->{'curr_result'}++;          my $curr = $self->{'curr_result'}++;
316    
317          my $id = $self->{'index_ids'}->[$curr];          my $id = $self->{'index_ids'}->[$curr];
318            
319            print STDERR "fetch_result: $curr = $id\n" if ($debug == 2);
320    
321          return $self->fetch_result_by_id($id);          return $self->fetch_result_by_id($id);
322  }  }
323    
324  sub fetch_result_by_id {  sub plain_text_body {
325          my $self = shift;          my $self = shift;
326            my $message = shift || croak "plain_text_body needs message!";
327    
328          my $id = shift || return;          my $body;
   
         my $message = $self->fetch_message($id);  
   
         my $row;  
329    
         $row->{'from'} = unroll($message,'from','phrase');  
         $row->{'subject'} = $message->get('Subject');  
330          if (! $message->isMultipart) {          if (! $message->isMultipart) {
331                  $row->{'body'} = $message->decoded->string;                  $body = $message->decoded->string;
332          } else {          } else {
333                  foreach my $part ($message->parts) {                  foreach my $part ($message->parts) {
334                          if ($part->body->mimeType eq 'text/plain') {                          if ($part->body->mimeType eq 'text/plain') {
335                                  $row->{'body'} = $part->decoded->string;                                  $body = $part->decoded->string;
336                                  last;                                  last;
337                          }                          }
338                  }                  }
339          }          }
340    
341            if (! $body) {
342                    $body = "[plain/text body not found]" if ($debug == 2);
343                    print STDERR "plain/text body not found\n" if ($debug);
344                    return;
345            }
346    
347            # reformat with Text::Autoformat
348            my $wrap = $self->{wrap_margin};
349            if ($wrap && $body && $body =~ m/^.{$wrap}..*$/m) {
350                    eval '$body = autoformat($body, {right=>$wrap, all=>1});';
351                    $body .="\n[reformated using autoformat, margin at $wrap]" if ($debug == 2);
352            }
353    
354            return $body;
355    }
356    
357    
358    sub fetch_result_by_id {
359            my $self = shift;
360    
361            my $id = shift || return;
362    
363            my $row = $self->{cache}->{$id};
364    
365            if (! $row) {
366    
367                    print STDERR "fetch_result_by_id($id) not in cache, hitting disk\n" if ($debug == 2);
368    
369                    my $message = $self->fetch_message($id) || return;
370    
371                    $row->{'id'} = $id;
372    
373                    foreach my $p (@counters) {
374                            foreach my $v ($self->unroll($message,$p,'phrase')) {
375                                    push @{$row->{$p}},$v;
376                                    $self->add_counter($p,$v);
377                            }
378                    }
379    
380                    $self->add_counter('folder', $1) if ($id =~ m/^(\S+)\s/);
381    
382                    $row->{'subject'} = $self->decode_qp($message->subject);
383                    $row->{'body'} = $self->plain_text_body($message);
384                    my $utime = str2time($message->date);
385    
386                    $row->{'date_utime'} = $utime;
387    
388                    $row->{'date'} = strftime("%Y-%m-%d %H:%M:%S", localtime($utime));
389                    $self->add_counter_calendar($utime);
390    
391                    # XXX store in cache?
392                    $self->{cache}->{$id} = $row;
393                    print STDERR "$id stored in cache\n" if ($debug == 2);
394            } else {
395                    print STDERR "fetch_result_by_id($id) in cache\n" if ($debug == 2);
396                    foreach my $p (@counters) {
397                            foreach my $v (@{$row->{$p}}) {
398                                    $self->add_counter($p,$v);
399                            }
400                    }
401    
402                    $self->add_counter('folder', $1) if ($id =~ m/^(\S+)\s/);
403    
404                    $self->add_counter_calendar($row->{date_utime});
405            }
406    
407          return $row;          return $row;
408                    
409  }  }

Legend:
Removed from v.4  
changed lines
  Added in v.52

  ViewVC Help
Powered by ViewVC 1.1.26