/[mws]/trunk/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

Contents of /trunk/MWS.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 51 - (show annotations)
Tue May 25 13:44:47 2004 UTC (19 years, 11 months ago) by dpavlin
File size: 8357 byte(s)
don't croak on empty parametar

1 #!/usr/bin/perl -w
2
3 package MWS;
4
5 use strict;
6 use warnings;
7
8 use lib '.';
9
10 use Carp;
11 use Mail::Box::Manager;
12 use Config::IniFiles;
13 use POSIX qw(strftime);
14 use Text::Autoformat;
15 use Text::Iconv;
16 use Text::Unaccent;
17 use Date::Parse;
18 use POSIX qw(strftime);
19 use MIME::Base64;
20
21 our $VERSION = '1.00';
22
23 my $debug = 1;
24
25 my @counters = qw(from to cc bcc folder);
26
27 sub new {
28 my $class = shift;
29 my $self = {@_};
30 bless($self, $class);
31
32
33 my $config_name = $self->{config_file} || croak "need config_file";
34 $config_name =~ s/\.conf.*$//;
35 $self->{config_name} = $config_name;
36
37 $self->{config} = new Config::IniFiles( -file => $self->{config_file} );
38
39 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(access => 'r');
42 $self->{index_dir} = $index_dir;
43
44 # placeholder for opened folders
45 $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;
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 {
186 my $self = shift;
187
188 my $mbox_id = shift || die "need mbox_id!";
189 my ($mbox,$id) = split(/ /,$mbox_id);
190
191 # return message with ID
192 print STDERR "fetch $id from $mbox\n" if ($debug);
193
194 if ($self->{fetch_count}++ > 100) {
195 $self->close_folder($mbox);
196 print STDERR "close_folder($mbox) forced on ",$self->{fetch_count},"iteration\n";
197 }
198
199 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 {
210 my $self = shift;
211
212 carp "search called without argument!" if (! @_);
213
214 $self->reset_counters;
215
216 print STDERR "search(",join(" ",@_),")\n" if ($debug == 2);
217 my @index_ids = $self->search_index(@_);
218
219 $self->{'index_ids'} = \@index_ids;
220
221 #my $results = $#index_ids + 1;
222 #$self->{'results'} = $results;
223
224 my $results = $self->{'total_hits'} || ($#index_ids + 1);
225
226 $self->{'curr_result'} = 0;
227
228 $self->reset_counters;
229
230 print STDERR "$results results\n" if ($debug == 2);
231
232 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($$$) {
273 my $self = shift;
274
275 my ($message,$part,$sub) = @_;
276
277 my @arr;
278
279 return if (! $message->$part);
280
281 foreach my $from ($message->$part) {
282 my $tmp = $from->$sub || next;
283
284 $tmp = $self->decode_qp($tmp);
285 push @arr, $tmp;
286 }
287
288 return @arr;
289 }
290
291 sub fetch_all_results {
292 my $self = shift;
293
294 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;
299
300 foreach my $id (@{$self->{'index_ids'}}) {
301 push @arr, $self->fetch_result_by_id($id);
302 }
303
304
305 return @arr;
306 }
307
308 sub fetch_result {
309 my $self = shift;
310
311 my $args = {@_};
312
313 croak "results called before search!" if (! $self->{'index_ids'});
314
315 my $curr = $self->{'curr_result'}++;
316
317 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);
322 }
323
324 sub plain_text_body {
325 my $self = shift;
326 my $message = shift || croak "plain_text_body needs message!";
327
328 my $body;
329
330 if (! $message->isMultipart) {
331 $body = $message->decoded->string;
332 } else {
333 foreach my $part ($message->parts) {
334 if ($part->body->mimeType eq 'text/plain') {
335 $body = $part->decoded->string;
336 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;
408
409 }

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26