#-*- perl -*- # # Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl itself. # # $FML: Summary.pm,v 1.28 2006/10/14 02:35:57 fukachan Exp $ # package FML::Article::Summary; use strict; use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); use Carp; use Mail::Message::Date; =head1 NAME FML::Article::Summary - generate article summary. =head1 SYNOPSIS =head1 DESCRIPTION =head1 METHODS =head2 new() constructor. =cut # Descriptions: constructor. # Arguments: OBJ($self) OBJ($curproc) # Side Effects: $self->{ _curproc } = $curproc; # Return Value: OBJ sub new { my ($self, $curproc) = @_; my ($type) = ref($self) || $self; my $me = {}; $me->{ _curproc } = $curproc; return bless $me, $type; } # Descriptions: append summary to $article_summary_file. # Arguments: OBJ($self) HANDLE($wh) NUM($id) # Side Effects: update $article_summary_file file. # Return Value: none sub print { my ($self, $wh, $id) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); # XXX last resort == STDOUT. $wh ||= \*STDOUT; if (defined $wh) { my $info = $self->_prepare_info($id); if (defined $info) { $self->print_one_line_summary($wh, $info); } } } # Descriptions: prepare information on article $id for later use. # such as $id, $address, $subject et.al. # Arguments: OBJ($self) NUM($id) # Side Effects: none # Return Value: HASH_REF sub _prepare_info { my ($self, $id) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); my $tag = $config->{ article_subject_tag }; my $addrlen = $config->{ article_summary_file_format_address_length }; my $article = undef; if (defined $self->{ _article }) { $article = $self->{ _article }; } else { # XXX we need article object to use $article->filepath() method. eval q{ use FML::Article;}; $article = new FML::Article $curproc; } my $file = $article->filepath($id); if (-f $file) { use Mail::Message; my $msg = new Mail::Message->parse( { file => $file } ); my $header = $msg->whole_message_header(); my $address = $header->get('from') || ''; my $date_str = $header->get('date') || ''; my $subject = $header->get('subject') || ''; # data -> unix time. use Mail::Message::Date; my $date = new Mail::Message::Date $date_str; my $unixtime = $date->as_unixtime(); # log the first 15 bytes of user@domain in From: header field. if ($address) { use Mail::Message::Address; my $addr = new Mail::Message::Address $address; $addr->cleanup(); $address = $addr->substr(0, $addrlen) || ''; } # de-tag, unfold, and charset conversion. if ($subject) { # XXX as side effect, converted to internal code e.g. euc-jp. use Mail::Message::Subject; my $sbj = new Mail::Message::Subject $subject; $sbj->mime_decode(); $sbj->unfold(); $sbj->delete_tag($tag); $sbj->unfold(); $sbj->charcode_convert_to_external_charset(); # e.g. iso-2022-jp $subject = $sbj->as_str(); } my $info = { id => $id, address => $address, subject => $subject, unixtime => $unixtime, }; return $info; } else { return undef; } } # Descriptions: one line version of print(). # Arguments: OBJ($self) HANDLE($wh) HASH_REF($info) # Side Effects: none # Return Value: none sub print_one_line_summary { my ($self, $wh, $info) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); my $style = $config->{ 'article_summary_file_format_style' }; if ($style eq 'fml4_compatible') { $self->_fml4_compatible_style_one_line_summary($wh, $info); } else { $curproc->logerror("unknown \$article_summary_file_style: $style"); } } # Descriptions: write out formatted string into $article_summary_file. # Arguments: OBJ($self) HANDLE($wh) HASH_REF($info) # Side Effects: update $article_summary_file. # Return Value: none sub _fml4_compatible_style_one_line_summary { my ($self, $wh, $info) = @_; my $curproc = $self->{ _curproc }; my $time = $info->{ unixtime } || undef; my $rdate = undef; if ($time) { use Mail::Message::Date; $rdate = new Mail::Message::Date $time; } else { $curproc->logerror("unix time undefined"); } if (defined $rdate) { my $date = $rdate->{ 'log_file_style' }; my $format = "%s [%d:%s] %s\n"; my $id = $info->{ id }; my $addr = $info->{ address }; my $subj = $info->{ subject }; printf $wh $format, $date, $id, $addr, $subj; } else { $curproc->logerror("date object undefined."); } } =head1 UTILITIES =head2 append($article, $id) append summary information for article $id into the article summary file. =cut # Descriptions: append summary information for article $id. # Arguments: OBJ($self) OBJ($article) NUM($id) # Side Effects: update summary # Return Value: none sub append { my ($self, $article, $id) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); my $file = $config->{ 'article_summary_file' }; if (defined $article) { $self->{ _article } = $article; } use FileHandle; my $wh = new FileHandle ">> $file"; if (defined $wh) { $wh->autoflush(1); $self->print($wh, $id); $wh->close(); } } # Descriptions: re-genearete summary from $min to $max. # Arguments: OBJ($self) NUM($min) NUM($max) # Side Effects: re-create $summary file. # Return Value: none sub rebuild { my ($self, $min, $max) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); my $file = $config->{ 'article_summary_file' }; my $tmp = "$file.new.$$"; use FileHandle; my $wh = new FileHandle ">> $tmp"; if (defined $wh) { $wh->autoflush(1); # speculate boundary if not specified. $min ||= 1; $max ||= $curproc->article_get_max_id(); for my $id ($min .. $max) { $self->print($wh, $id); } $wh->close(); } if (-s $tmp) { rename($tmp, $file); } else { $curproc->logerror("fail to write $tmp"); } } # Descriptions: print all lines in summary file into file handle $wh. # Arguments: OBJ($self) HANDLE($wh) # Side Effects: none # Return Value: none sub dump { my ($self, $wh) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); my $article_summary_file = $config->{ "article_summary_file" }; if (-f $article_summary_file) { my $rh = new FileHandle $article_summary_file; if (defined $rh && defined $wh) { my $buf; while ($buf = <$rh>) { print $wh $buf;} $rh->close(); } } } # Descriptions: remove content corresponding with expired articles. # Arguments: OBJ($self) NUM($first_seq) NUM($last_seq) # Side Effects: summary rebuilt. # Return Value: none sub expire { my ($self, $first_seq, $last_seq) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); my $summary_style = $config->{ article_summary_file_format_style }; if ($summary_style eq 'fml4_compatible') { $self->_expire_fml4_compatible_style_summary($first_seq, $last_seq); } else { my $errmsg = "unknown \$article_summary_file_style: $summary_style"; $curproc->logerror($errmsg); } } # Descriptions: remove content corresponding with expired articles. # Arguments: OBJ($self) NUM($first_seq) NUM($last_seq) # Side Effects: summary rebuilt. # Return Value: none sub _expire_fml4_compatible_style_summary { my ($self, $first_seq, $last_seq) = @_; my $curproc = $self->{ _curproc }; my $config = $curproc->config(); # 06/05/03 16:31:16 [1268:fukachan@home.f] SUMMARY my $summary_file = $config->{ article_summary_file }; my $tmp_file = $curproc->tmp_file_path(); if (-f $summary_file) { use FileHandle; my $rh = new FileHandle $summary_file; my $wh = new FileHandle "> $tmp_file"; if (defined $rh && defined $wh) { my $buf; LINE: while ($buf = <$rh>) { if ($buf =~ /\[(\d+):/) { my $seq = $1; next LINE if $seq <= $last_seq; } print $wh $buf; } $wh->close(); $rh->close(); } else { $curproc->logerror("cannot open summary file") unless defined $rh; $curproc->logerror("cannot open tmp file") unless defined $wh; } unless (-s $tmp_file) { $curproc->logerror("expire: tmporary file creation fail"); return; } if (rename($tmp_file, $summary_file)) { $curproc->logdebug("expire: summary file rebuilt"); } else { $curproc->logerror("expire: summary file rebuilding fail"); } } } =head1 CODING STYLE See C on fml coding style guide. =head1 AUTHOR Ken'ichi Fukamachi =head1 COPYRIGHT Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 HISTORY FML::Article::Summary appeared in fml8 mailing list driver package. See C for more details. =cut 1;