diff options
| author | fukachan <fukachan> | 2004-03-31 12:53:50 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2004-03-31 12:53:50 +0000 |
| commit | 5432a7011fcb4386b56fc044a54ec4c4dd2da0e5 (patch) | |
| tree | 0d73b11db0e7a903d7b020323b97f6aaaa8cd10d /fml/lib | |
| parent | 408f950159c3aae27154eaaceb15d6d91095d9d9 (diff) | |
| download | fml8-5432a7011fcb4386b56fc044a54ec4c4dd2da0e5.tar.gz fml8-5432a7011fcb4386b56fc044a54ec4c4dd2da0e5.tar.bz2 fml8-5432a7011fcb4386b56fc044a54ec4c4dd2da0e5.zip | |
Mail::ThreadTrack is obsoleted. remove related codes.
Diffstat (limited to 'fml/lib')
| -rw-r--r-- | fml/lib/FML/CGI/ThreadTrack.pm | 247 | ||||
| -rw-r--r-- | fml/lib/FML/Command/Admin/thread.pm | 116 | ||||
| -rw-r--r-- | fml/lib/FML/Process/Distribute.pm | 41 | ||||
| -rw-r--r-- | fml/lib/FML/Process/Switch.pm | 3 | ||||
| -rwxr-xr-x | fml/lib/Mail/ThreadTrack.pm | 591 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Analyze.pm | 663 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/DB.pm | 321 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/HeaderRewrite.pm | 133 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Print.pm | 419 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Print/HTML.pm | 291 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Print/Message.pm | 253 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Print/Sort.pm | 124 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Print/Text.pm | 247 | ||||
| -rw-r--r-- | fml/lib/Mail/ThreadTrack/Print/Utils.pm | 115 |
14 files changed, 3 insertions, 3561 deletions
diff --git a/fml/lib/FML/CGI/ThreadTrack.pm b/fml/lib/FML/CGI/ThreadTrack.pm deleted file mode 100644 index 9e729338..00000000 --- a/fml/lib/FML/CGI/ThreadTrack.pm +++ /dev/null @@ -1,247 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003,2004 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: ThreadTrack.pm,v 1.28 2004/01/01 23:52:10 fukachan Exp $ -# - -package FML::CGI::ThreadTrack; -use strict; -use Carp; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use CGI qw/:standard/; # load standard CGI routines - -use FML::Process::CGI; -@ISA = qw(FML::Process::CGI); - - -=head1 NAME - -FML::CGI::ThreadTrack - CGI details to control thread system - -=head1 SYNOPSIS - - $obj = new FML::CGI::ThreadTrack; - $obj->prepare(); - $obj->verify_request(); - $obj->run(); - $obj->finish(); - -run() executes html_start(), run_cgi() and html_end() described below. - -See L<FML::Process::Flow> for flow details. - -=head1 DESCRIPTION - -=head2 CLASS HIERARCHY - -C<FML::CGI::ThreadTrack> is a subclass of C<FML::Process::CGI>. - -=head1 METHODS - -Almost methods common for CGI or HTML are forwarded to -C<FML::Process::CGI> base class. - -=cut - - -# Descriptions: print out HTML header + body former part -# Arguments: OBJ($curproc) -# Side Effects: none -# Return Value: none -sub html_start -{ - my ($curproc) = @_; - my $config = $curproc->config(); - my $title = $config->{ thread_cgi_title } || 'thread system interface'; - my $color = $config->{ thread_cgi_bgcolor } || '#E6E6FA'; - my $myname = $curproc->myname(); - my $charset = $curproc->get_charset("cgi"); - - # o.k start html - print start_html(-title => $title, - -lang => $charset, - -BGCOLOR => $color); - print "\n"; -} - - -# Descriptions: print out body latter part -# Arguments: OBJ($curproc) -# Side Effects: none -# Return Value: none -sub html_end -{ - my ($curproc) = @_; - - # o.k. end of html - print end_html; - print "\n"; -} - - -# Descriptions: main routine for thread control. -# run_cgi() can process request: list, show, change_status -# Arguments: OBJ($curproc) -# Side Effects: none -# Return Value: none -sub run_cgi_main -{ - my ($curproc) = @_; - my $config = $curproc->config(); - my $myname = $config->{ program_name }; # XXX-TODO: valid ? - my $ttargs = $curproc->_build_threadtrack_param(); - my $action = $curproc->safe_param_action() || ''; - - use Mail::ThreadTrack; - my $thread = new Mail::ThreadTrack $ttargs; - - if (defined $thread) { - $thread->set_mode('html'); - - if ($action eq 'list') { - $thread->summary(); - } - elsif ($action eq 'show') { - my $id = $curproc->safe_param_article_id(); - # XXX-TODO: do not call _XXX() internal method from outside. - my $tid = $thread->_create_thread_id_strings($id); - $thread->show($tid); - } - elsif ($action eq 'change_status') { - # fmlthread.cgi is for administorator, so you can change status. - if ($myname eq 'fmlthread.cgi') { - my $list = $curproc->safe_paramlist2_threadcgi_change_status(); - for my $param (@$list) { - my ($ml, $id, $value) = @$param; - if ($value eq 'closed') { - my $tid = $thread->_create_thread_id_strings($id); - print "closed $tid", br, "\n"; - $thread->close($tid); - } - } - } - # XXX-TODO: ? clarify this message more. - else { - print "Warning: only administrator change status\n"; - } - - $thread->summary(); - } - else { - $thread->summary(); - } - } - else { - croak("fail to create thread object"); - } -} - - -# Descriptions: prepare basic parameters for Mail::ThreadTrack module -# Arguments: OBJ($curproc) -# Side Effects: none -# Return Value: HASH_REF -sub _build_threadtrack_param -{ - my ($curproc) = @_; - my $config = $curproc->config(); - my $myname = $config->{ program_name }; - my $option = $curproc->command_line_options(); - - # prepare arguments for thread track module - # XXX-TODO: hmm, we should provide $curproc->util->article_max_id() ? - my $ml_name = $curproc->safe_param_ml_name(); - my $thread_db_dir = $config->{ thread_db_dir }; - my $spool_dir = $config->{ spool_dir }; - my $max_id = $curproc->article_max_id(); - my $ttargs = { - myname => $myname, - logfp => \&Log, - fd => \*STDOUT, - db_base_dir => $thread_db_dir, - ml_name => $ml_name, - spool_dir => $spool_dir, - reverse_order => 0, - }; - - # import some variables - for my $varname (qw(base_url msg_base_url)) { - if (defined $option->{ $varname }) { - $ttargs->{ $varname } = $option->{ $varname }; - } - elsif (defined $config->{ $varname }) { - $ttargs->{ $varname } = $config->{ $varname }; - } - } - - return $ttargs; -} - - -# Descriptions: print navigation bar -# Arguments: OBJ($curproc) -# Side Effects: none -# Return Value: none -sub run_cgi_navigator -{ - my ($curproc) = @_; - my $config = $curproc->config(); - my $action = $curproc->safe_cgi_action_name(); - my $target = $config->{ thread_cgi_target_window } || '_top'; - # XXX-TODO: we should provide $curproc->util->get_ml_list() method ? - my $ml_list = $curproc->get_ml_list(); - my $ml_name = $config->{ ml_name }; - - print start_form(-action=>$action, -target=>$target); - - print "ML: "; - print popup_menu(-name => 'ml_name', -values => $ml_list); - print "<BR>\n"; - - print "orderd by: "; - my $order = [ 'cost', 'date', 'reverse date' ]; - print popup_menu(-name => 'order', -values => $order ); - print "<BR>\n"; - - print submit(-name => 'change target'); - print reset(-name => 'reset'); - - print end_form; - print "<HR>\n"; -} - - -=head1 SEE ALSO - -L<CGI>, -L<FML::Process::CGI> -and -L<FML::Process::Flow> - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003,2004 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::CGI::ThreadTrack first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/FML/Command/Admin/thread.pm b/fml/lib/FML/Command/Admin/thread.pm index b6038ee8..f3876af0 100644 --- a/fml/lib/FML/Command/Admin/thread.pm +++ b/fml/lib/FML/Command/Admin/thread.pm @@ -4,7 +4,7 @@ # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl itself. # -# $FML: thread.pm,v 1.5 2004/03/28 13:05:55 fukachan Exp $ +# $FML: thread.pm,v 1.6 2004/03/29 14:13:20 fukachan Exp $ # package FML::Command::Admin::thread; @@ -126,120 +126,6 @@ sub _new_switch } -=head1 OLD VERSION - -=cut - - -# Descriptions: thread manipulation interface. -# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) -# Side Effects: update $recipient_map -# Return Value: none -sub old_process -{ - my ($self, $curproc, $command_args) = @_; - my $config = $curproc->config(); - my $myname = $curproc->myname(); - - # prepare argumente for thread track module (Mail::ThreadTrack). - my $ml_name = $config->{ ml_name }; - my $thread_db_dir = $config->{ thread_db_dir }; - my $spool_dir = $config->{ spool_dir }; - my $max_id = $curproc->article_max_id(); - my $ttargs = { - myname => $myname, - logfp => \&Log, - fd => \*STDOUT, - db_base_dir => $thread_db_dir, - ml_name => $ml_name, - spool_dir => $spool_dir, - max_id => $max_id, - reverse_order => 1, - }; - - # if (defined $options->{ f }) { - # _read_filter_list($thread, $options->{ f }); - # } - - $self->_old_switch($curproc, $command_args, $ttargs); -} - - -# Descriptions: switch thread library command -# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) -# HASH_REF($ttargs) -# Side Effects: thread db may be updated -# Return Value: none -sub _old_switch -{ - my ($self, $curproc, $command_args, $ttargs) = @_; - my $options = $command_args->{ options }; - my $command = $options->[ 0 ] || 'list'; - my $max_id = $ttargs->{ max_id }; - - # utility functions for spool in fml side. - use FML::Article::Thread; - my $a_thread = new FML::Article::Thread $curproc; - - # functions to manipulate thread db. - use Mail::ThreadTrack; - my $thread = new Mail::ThreadTrack $ttargs; - $thread->set_mode('text'); - - if ($command eq 'list' || $command eq 'summary') { - $thread->$command(); - } - elsif ($command eq 'review') { - my $str = $options->[ 1 ] || 'last:100'; - $thread->review( $str , 1, $max_id ); - } - elsif ($command eq 'db_dump') { - my $type = $options->[ 1 ] || 'status'; - $thread->db_open(); - $thread->db_dump( $type ); - $thread->db_close(); - } - elsif ($command eq 'db_update') { - my $last_id = $a_thread->speculate_last_id($curproc, $thread); - print STDERR "db_update: $last_id -> $max_id\n"; - $thread->db_mkdb($last_id, $max_id); - } - elsif ($command eq 'db_rebuild') { - print STDERR "\$thread->db_mkdb(1, $max_id);\n"; - $thread->db_mkdb(1, $max_id); - } - elsif ($command eq 'db_clear') { - $thread->db_open(); - $thread->db_clear(); - $thread->db_close(); - } - elsif ($command eq 'close') { - my $thread_id = $options->[ 1 ]; - if (defined $thread_id) { - $a_thread->close($thread, $thread_id, 1, $max_id); - } - else { - croak("specify \$thread_id"); - } - } - elsif ($command eq 'cui') { - # XXX-TODO: hmm, run interactive session unless @ARGV ? - # XXX-TODO: showing help is appropriate ? - if ($options->[ 1 ]) { - push(@ISA, 'FML::Article::Thread::CUI'); - # $ttargs->{ ml_name } = $argv->[ 0 ]; - $a_thread->interactive($thread, $ttargs); - } - else { - help(); - } - } - else { - croak("subcommand not specified"); - } -} - - =head1 CODING STYLE See C<http://www.fml.org/software/FNF/> on fml coding style guide. diff --git a/fml/lib/FML/Process/Distribute.pm b/fml/lib/FML/Process/Distribute.pm index 717e45be..a756acd8 100644 --- a/fml/lib/FML/Process/Distribute.pm +++ b/fml/lib/FML/Process/Distribute.pm @@ -3,7 +3,7 @@ # Copyright (C) 2000,2001,2002,2003,2004 Ken'ichi Fukamachi # All rights reserved. # -# $FML: Distribute.pm,v 1.140 2004/03/17 10:27:35 fukachan Exp $ +# $FML: Distribute.pm,v 1.141 2004/03/28 10:33:35 fukachan Exp $ # package FML::Process::Distribute; @@ -514,45 +514,6 @@ sub _deliver_article # Arguments: OBJ($curproc) # Side Effects: update thread information # Return Value: none -sub _old_thread_check -{ - my ($curproc) = @_; - my $config = $curproc->config(); - my $pcb = $curproc->pcb(); - my $myname = $curproc->myname(); - - my $ml_name = $config->{ ml_name }; - my $thread_db_dir = $config->{ thread_db_dir }; - my $spool_dir = $config->{ spool_dir }; - my $article_id = $pcb->get('article', 'id'); - my $is_rewrite_hdr = $config->yes('use_thread_subject_tag') ? 1 : 0; - my $ttargs = { - myname => $myname, - logfp => \&Log, - fd => \*STDOUT, - db_base_dir => $thread_db_dir, - ml_name => $ml_name, - spool_dir => $spool_dir, - article_id => $article_id, - rewrite_header => $is_rewrite_hdr, - }; - - my $msg = $curproc->article_message(); - - # old thread engine - eval q{ - use Mail::ThreadTrack; - my $thread = new Mail::ThreadTrack $ttargs; - $thread->analyze($msg); - }; - $curproc->log($@) if $@; -} - - -# Descriptions: the top level interface to drive thread tracking system. -# Arguments: OBJ($curproc) -# Side Effects: update thread information -# Return Value: none sub _new_thread_check { my ($curproc) = @_; diff --git a/fml/lib/FML/Process/Switch.pm b/fml/lib/FML/Process/Switch.pm index bd1f6fd1..4ed2859b 100644 --- a/fml/lib/FML/Process/Switch.pm +++ b/fml/lib/FML/Process/Switch.pm @@ -4,7 +4,7 @@ # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl itself. # -# $FML: Switch.pm,v 1.103 2003/12/30 03:56:00 fukachan Exp $ +# $FML: Switch.pm,v 1.104 2004/01/02 14:42:44 fukachan Exp $ # package FML::Process::Switch; @@ -547,7 +547,6 @@ L<FML::Process::Distribute>, L<FML::Process::Command>, L<FML::Process::ListServer>, L<FML::Process::Configure>, -L<FML::Process::ThreadTrack>, L<FML::Process::MailErrorAnalyzer> =head1 CODING STYLE diff --git a/fml/lib/Mail/ThreadTrack.pm b/fml/lib/Mail/ThreadTrack.pm deleted file mode 100755 index f57154b1..00000000 --- a/fml/lib/Mail/ThreadTrack.pm +++ /dev/null @@ -1,591 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: ThreadTrack.pm,v 1.34 2003/01/11 15:16:34 fukachan Exp $ -# - -package Mail::ThreadTrack; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - -use Mail::ThreadTrack::Analyze; -use Mail::ThreadTrack::DB; -use Mail::ThreadTrack::Print; - -@ISA = qw(Mail::ThreadTrack::Analyze - Mail::ThreadTrack::DB - Mail::ThreadTrack::Print - ); - - -=head1 NAME - -Mail::ThreadTrack - analyze message thread - -=head1 SYNOPSIS - - ... lock mailing list ... - - my $args = { - fd => \*STDOUT, - db_base_dir => "/var/spool/ml/\@db\@/thread", - ml_name => 'elena', - spool_dir => "/var/spool/ml/elena/spool", - article_id => 100, - }; - - use Mail::ThreadTrack; - my $thread = new Mail::ThreadTrack $args; - $thread->analyze($msg); - $thread->show_summary(); - - ... unlock mailing list ... - -where C<$msg> is Mail::Message object for the article 100. - -=head1 DESCRIPTION - -=head1 METHODS - -=head2 new($args) - - $args = { - fd => \*STDOUT, - db_base_dir => "/var/spool/ml/\@db\@/thread", - ml_name => 'elena', - spool_dir => "/var/spool/ml/elena/spool", - article_id => $id, - }; - -C<db_base_dir>, C<spool_dir> and C<ml_name> in C<config> are mandatory. -C<$id> is the sequential number for input data (article). - -Available variables in $args follows: - - variables type example - ------------------------------------------------------------ - myname STR ? - ml_name STR elena - spool_dir STR /var/spool/ml/elena - article_id STR 100 - db_base_dir STR /var/spool/ml/@db@/thread - reverse_order STR 1 or 0 - rewrite_header STR 1 or 0 - base_url STR "" or URL - msg_base_url STR "" or URL - dir_mode NUM 0755 - thread_id_syntax STR elena/%d - thread_subject_tag STR [elena/%d] - fd HANDLE \*STDOUT - logfp CODE \&Log() - -=cut - - -my $dir_mode = 0755; - - -# Descriptions: constructor -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: OBJ -sub new -{ - my ($self, $args) = @_; - my ($type) = ref($self) || $self; - my $me = {}; - my $config = $me->{ _config } = { - db_type => 'AnyDBM_File', - }; - - if (defined $args->{ dir_mode }) { - $dir_mode = $args->{ dir_mode }; - } - - my @keys = qw(myname ml_name spool_dir article_id db_base_dir - reverse_order - rewrite_header - base_url msg_base_url - ); - - my %must = ('ml_name' => 1, 'spool_dir' => 1, 'db_base_dir' => 1); - - for my $key (@keys) { - if (defined $args->{ $key }) { - $config->{ $key } = $args->{ $key }; - } - elsif (defined $must{ $key }) { - croak("specify $key"); - } - - } - my $ml_name = $config->{ ml_name }; - - unless (defined $args->{ thread_id_syntax }) { - $config->{ thread_id_syntax } = "$ml_name/\%d"; - } - - unless (defined $args->{ thread_subject_tag }) { - my $id_syntax = $config->{ thread_id_syntax }; - $config->{ thread_subject_tag } = "[$id_syntax]"; - } - - # database directory used to store thread information et. al. - use File::Spec; - my $base_dir = $config->{ db_base_dir }; - $me->{ _db_base_dir } = $base_dir; - $me->{ _index_db } = File::Spec->catfile($base_dir, "index"); - $me->{ _db_dir } = File::Spec->catfile($base_dir, $ml_name); - $me->{ _fd } = $args->{ fd } || \*STDOUT; - $me->{ _saved_args } = $args; - - if (defined $config->{ rewrite_header } && $config->{ rewrite_header }) { - $me->{ _is_rewrite_header } = 1; - eval q{ use Mail::ThreadTrack::HeaderRewrite; }; - croak($@) if $@; - push(@ISA, 'Mail::ThreadTrack::HeaderRewrite'); - } - - # XXX-TODO: $article_summary_lines hard-coded. - # ::Print parameters - $me->{ _article_summary_lines } = 5; - - # log function pointer - if (defined $args->{ logfp }) { - $me->{ _logfp } = $args->{ logfp }; - } - - # initialize directory - _init_dir($me); - - return bless $me, $type; -} - - -# Descriptions: dummy -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: none -sub DESTROY {} - - -# Descriptions: "mkdir -p" or "mkdirhier" -# Arguments: STR($dir) STR($mode) -# Side Effects: set $ErrorString -# Return Value: 1 or UNDEF -sub _mkdirhier -{ - my ($dir, $mode) = @_; - - # XXX $mode (e.g. 0755) should be a numeric not a string - eval q{ - use File::Path; - mkpath($dir, 0, $dir_mode); - }; - - return ($@ ? undef : 1); -} - - -# Descriptions: create the directory taken from $self->{ _db_dir }. -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: create a "_db_dir" directory if needed -# Return Value: 1 (success) or undef (fail) -sub _init_dir -{ - my ($self, $args) = @_; - - if (defined $self->{ _db_dir }) { - my $db_dir = $self->{ _db_dir }; - unless (-d $db_dir) { - _mkdirhier($db_dir) || do { - croak("cannot make \$db_dir=$db_dir\n"); - }; - } - } - else { - croak("no \$db_dir\n"); - } - - return 1; -} - - -=head2 increment_id(file) - -increment thread number which is taken up from C<file> -and save the new number to C<file>. - -=cut - - -# Descriptions: increment thread number $id holded in DB. -# Arguments: OBJ($self) STR($seq_file) -# Side Effects: increment id holded in $seq_file -# Return Value: NUM -sub increment_id -{ - my ($self, $seq_file) = @_; - my $seq = 0; - - # XXX-TODO: $seq_file is not used. fix this method. - - $self->db_open(); - - # prepare hash table tied to db_dir/*db's - # XXX-TODO: we prepare $self->db_base(); ? - my $rh = $self->{ _hash_table }; - - if (defined $rh->{ _info }->{ sequence }) { - $rh->{ _info }->{ sequence }++; - return $rh->{ _info }->{ sequence }; - } - else { - $seq = $rh->{ _info }->{ sequence } = 1; - } - - $self->db_close(); - - return $seq; -} - - -=head2 list_up_thread_id() - -return not closed ticket id(s) as ARRRAY_REF. - -=cut - - -# Descriptions: return not closed ticket id(s) as ARRRAY_REF. -# Arguments: OBJ($self) -# Side Effects: none -# Return Value: ARRAY_HASH -sub list_up_thread_id -{ - my ($self) = @_; - my ($tid, $status); - my $rh_status = $self->{ _hash_table }->{ _status }; - my $mode = 'default'; - my $stat = {}; - my @thread_id = (); - - TICEKT_LIST: - while (($tid, $status) = each %$rh_status) { - if ($status =~ /close/o) { - $stat->{ 'closed' }++; - } - else { - $stat->{ $status }++; - } - - if ($mode eq 'default') { - next TICEKT_LIST if $status =~ /close/o; - } - - push(@thread_id, $tid); - } - - # save statistics - $self->{ _ticket_id_stat } = $stat; - - \@thread_id; -} - - -=head2 set_mode($mode) - -specify output format by $mode string. -"text" and "html" are available. -"text" by default. - -=head2 get_mode() - -get output format. - -=cut - - -# Descriptions: set output format -# Arguments: OBJ($self) STR($mode) -# Side Effects: update info in object -# Return Value: STR -sub set_mode -{ - my ($self, $mode) = @_; - $self->{ _mode } = $mode || 'text'; -} - - -# Descriptions: set output format -# Arguments: OBJ($self) -# Side Effects: update info in object -# Return Value: STR -sub get_mode -{ - my ($self) = @_; - return(defined $self->{ _mode } ? $self->{ _mode } : undef); -} - - -=head2 set_fd( $fd ) - -=head2 get_fd() - -=cut - - -# Descriptions: set output channel handle. -# Arguments: OBJ($self) HADNLE($fd) -# Side Effects: update info in object. -# Return Value: HANDLE -sub set_fd -{ - my ($self, $fd) = @_; - $self->{ _fd } = $fd; -} - - -# Descriptions: get output channel handle. -# Arguments: OBJ($self) -# Side Effects: update info in object. -# Return Value: HANDLE -sub get_fd -{ - my ($self) = @_; - return $self->{ _mode }; -} - - -=head2 set_order( $order ) - -set thread listing order where $order is 'normal' or 'reverse'. - -=cut - - -# Descriptions: set thread listing order -# Arguments: OBJ($self) STR($order) -# Side Effects: update info in object. -# Return Value: none -sub set_order -{ - my ($self, $order) = @_; - - if ((defined $order) && $order eq 'normal') { - $self->{ _config }->{ reverse_order } = 0; - } - elsif ((defined $order) && $order eq 'reverse') { - $self->{ _config }->{ reverse_order } = 1; - } - else { - warn("set_order: unknown order $order"); - } -} - - -=head2 exist($thread_id) - -$thread_id exists or not in database? -return 1 (exist) or 0. - -=cut - - -# Descriptions: $thread_id exists or not in database? -# Arguments: OBJ($self) STR($id) -# Side Effects: none -# Return Value: 1 or 0 -sub exist -{ - my ($self, $id) = @_; - my $r = 0; - - $self->db_open(); - - my $rh = $self->{ _hash_table }; - - if (defined $rh->{ _articles }) { - my $a = $rh->{ _articles }; - $r = (defined $a->{ $id } ? 1 : 0); - } - - $self->db_close(); - - return $r; -} - - -=head2 close($thread_id) - -close specified $thread_id. - -=cut - - -# Descriptions: close specified $thread_id. -# Arguments: OBJ($self) STR($thread_id) -# Side Effects: update status -# Return Value: none -sub close -{ - my ($self, $thread_id) = @_; - - $self->db_open(); - $self->_set_status($thread_id, "close"); - $self->db_close(); -} - - -=head2 set_status($args) - -set $status for $thread_id. It rewrites DB (file). -C<$args>, HASH reference, must have two keys. - - $args = { - thread_id => $thread_id, - status => $status, - } - -C<set_status()> calls db_open() an db_close() automatically within it. - -=cut - - -# Descriptions: set status. -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: update status in db. -# Return Value: none -sub set_status -{ - my ($self, $args) = @_; - my $thread_id = $args->{ thread_id }; - my $status = $args->{ status }; - - # XXX-TODO: validate input. - $self->db_open(); - $self->_set_status($thread_id, $status); - $self->db_close(); -} - - -# Descriptions: set status. -# Arguments: OBJ($self) STR($thread_id) STR($value) -# Side Effects: update status in db. -# Return Value: none -sub _set_status -{ - my ($self, $thread_id, $value) = @_; - $self->{ _hash_table }->{ _status }->{ $thread_id } = $value; -} - - -=head2 add_filter( { key => value } ) - -add filter rule to ignore in thread database. - - my $thread = new Mail::ThreadTrack; - $thread->set_filter( { 'subject' => 'fml cvs weekly changes' } ); - -=cut - - -# Descriptions: add filter rule(s) to ignore in thread database. -# Arguments: OBJ($self) HASH_REF($hash) -# Side Effects: none -# Return Value: none -sub add_filter -{ - my ($self, $hash) = @_; - - # update filter list - my ($k, $v); - while (($k, $v) = each %$hash) { - $self->{ _filterlist }->{ $k } = $v; - } -} - - -=head2 log( $str ) - -log $str using the specified log function or into STDERR if logfp -unspecified. - -=cut - - -# Descriptions: log -# Arguments: OBJ($self) STR($str) -# Side Effects: none -# Return Value: none -sub log -{ - my ($self, $str) = @_; - - if (defined $self->{ _logfp }) { - my $fp = $self->{ _logfp }; - &$fp( $str ); - } - else { - print STDERR "Log> $str\n"; - } -} - - -=head2 filepath($args) - -return the article file path. - - $args = { - base_dir => DIR, - id => NUM, - use_subdir => 1 or 0, - }; - -=cut - - -# Descriptions: return the article file path. -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: STR(file path) -sub filepath -{ - my ($self, $args) = @_; - - use Mail::Message::Spool; - my $spool = new Mail::Message::Spool; - my $file = $spool->filepath($args); - - return $file; -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Analyze.pm b/fml/lib/Mail/ThreadTrack/Analyze.pm deleted file mode 100644 index 96b01a6b..00000000 --- a/fml/lib/Mail/ThreadTrack/Analyze.pm +++ /dev/null @@ -1,663 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: Analyze.pm,v 1.32 2003/07/21 11:29:15 fukachan Exp $ -# - -package Mail::ThreadTrack::Analyze; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - -# XXX-TODO: we should remove not used function ? - -my $debug = 0; - -=head1 NAME - -Mail::ThreadTrack::Analyze - analyze mail thread relation - -=head1 SYNOPSIS - -See C<Mail::ThreadTrack> perl module for more detail. - -=head1 DESCRIPTION - -=head1 METHODS - -=head2 analyze($mesg) - -C<$mesg> is Mail::Message object. - -This is top level entrance for - -1) assign a new thread or extract the existing thread-id from the subject. - -2) update thread status if needed. - -3) update database. - -=cut - - -# Descriptions: top level entrance -# Arguments: OBJ($self) OBJ($msg) -# $msg = "Mail::Messge object" -# Side Effects: none -# Return Value: none -sub analyze -{ - my ($self, $msg) = @_; - - $self->assign($msg); - $self->rewrite_header($msg) if $self->{ _is_rewrite_header }; - $self->update_thread_status($msg); - $self->update_db($msg); -} - - -=head2 assign($msg) - -analyze message given by $msg and assign thread id if needed. - -=cut - - -# Descriptions: given string looks like subject or not -# Arguments: STR($subject) -# Side Effects: none -# Return Value: 1 or 0 -sub _is_reply -{ - my ($subject) = @_; - - use Mail::Message::Language::Japanese::Subject; - return Mail::Message::Language::Japanese::Subject::is_reply($subject); -} - - -# Descriptions: assign a new thread id or -# extract the existing thread-id from the subject -# Arguments: OBJ($self) OBJ($msg) -# $msg = Mail::Message object -# Side Effects: a new thread_id may be assigned -# Return Value: none -sub assign -{ - my ($self, $msg) = @_; - my $header = $msg->whole_message_header(); - my $subject = $header->get('subject'); - my $is_reply = _is_reply($subject); - - # XXX-TODO: who validate $thread_id regexp - # XXX-TODO: since $thread_id is given from outside. - # 1. try to extract $thread_id from header - my $thread_id = $self->_extract_thread_id_in_subject($header); - unless ($thread_id) { - # If we fail to pick up thread id from subject, - # we try to speculate id from other fields in header. - $thread_id = $self->_speculate_thread_id_from_header($header); - if ($thread_id) { - $is_reply = 1; # message already have thread_id, so replied one? - $self->set_thread_id($thread_id); - $self->log("speculated id=$thread_id"); - } - else { - $self->log("(debug) fail to spelucate thread_id") if $debug; - } - } - - # 2. check "X-Thread-Pragma:" field, - # we ignore this mail if the pragma is specified as "ignore". - if (defined $header->get('x-thread-pragma')) { - my $pragma = $header->get('x-thread-pragma') || ''; - if ($pragma =~ /ignore/io) { - $self->{ _pragma } = 'ignore'; - $self->_append_thread_status_info("ignored"); - return undef; - } - } - - # 3. if the header has some thread_id, - # we do not rewrite the subject but save the extracted $thread_id. - if ($is_reply && $thread_id) { - $self->log("reply message with thread_id=$thread_id"); - $self->set_thread_id($thread_id); - $self->set_thread_status('analyzed'); - $self->_append_thread_status_info('analyzed'); - } - elsif ($thread_id) { - $self->log("message with thread_id=$thread_id but not reply"); - $self->set_thread_id($thread_id); - $self->_append_thread_status_info("found"); - } - else { - $self->log("message without thread_id") if $debug; - - my $id = $self->_assign_new_thread_id_number(); - - # side effect: - # define $self->{ _thread_subject_tag } and $self->{ _thread_id } - my $thread_id = $self->_create_thread_id_strings($id); - $self->set_thread_id($thread_id); - $self->_append_thread_status_info("newly assigned"); - } -} - - -# Descriptions: assign new thread_id -# Arguments: OBJ($self) -# Side Effects: increment id -# Return Value: NUM -sub _assign_new_thread_id_number -{ - my ($self) = @_; - my $id = 0; - - # assign a new thread number for a new message - if (1) { - # unique but non sequential number - $id = $self->{ _config }->{ article_id }; - } - else { - # incremental number - $id = $self->increment_id(); - } - - $self->log("assign thread_id=$id") if $debug; - return $id; -} - - -# Descriptions: update $self->{ _status_info } -# Arguments: OBJ($self) STR($s) -# Side Effects: update $self->{ _status_info } -# Return Value: STR -sub _append_thread_status_info -{ - my ($self, $s) = @_; - $self->{ _status_info } .= $self->{ _status_info } ? " -> ".$s : $s; -} - - -=head2 get_thread_status() - -get thread status. - -=head2 set_thread_status($status) - -set thread status. - -=cut - - -# Descriptions: get thread status -# Arguments: OBJ($self) -# Side Effects: none -# Return Value: STR -sub get_thread_status -{ - my ($self) = @_; - return(defined $self->{ _status } ? $self->{ _status } : undef); -} - - -# Descriptions: set thread status -# Arguments: OBJ($self) STR($thread_status) -# Side Effects: none -# Return Value: STR -sub set_thread_status -{ - my ($self, $thread_status) = @_; - $self->{ _status } = $thread_status; - return $thread_status; -} - - -=head2 update_thread_status($msg) - -ignore this procedure if "ignore" pragma is specified. - -update status to "close" if "close" pragma or "close" commmand in the -message is found. - -=cut - - -# Descriptions: update thread status -# Arguments: OBJ($self) OBJ($msg) -# Side Effects: update database -# Return Value: none -sub update_thread_status -{ - my ($self, $msg) = @_; - my $content = ''; - my $subject = ''; - my $pragma = ''; - - if (defined $self->{ _pragma }) { - return if $self->{ _pragma } eq 'ignore'; - } - - unless (ref($msg) eq 'Mail::Message') { - croak("invalid object"); - } - - # filter messages of some class - if (defined $self->{ _filterlist }) { - if ($self->_is_ignore($msg)) { - $self->log("this thread should be ignored"); - $pragma = "close"; - } - } - - my $header = $msg->whole_message_header(); - my $textmsg = $msg->find_first_plaintext_message(); - $content = $textmsg->message_text() if defined $textmsg; - $subject = $header->get('subject') || ''; - $pragma = $header->get('x-thread-pragma') || $pragma || ''; - - if ($pragma =~ /close/ || - $content =~ /^\s*close/ || - $subject =~ /^\s*close/) { - $self->set_thread_status("close"); - $self->_append_thread_status_info("closed"); - $self->log("thread is closed"); - } - else { - $self->log("thread status not changed"); - } -} - - -# Descriptions: check filter whether this $msg should be ignored or not. -# Arguments: OBJ($self) OBJ($msg) -# Side Effects: none -# Return Value: 1 or 0 -sub _is_ignore -{ - my ($self, $msg) = @_; - my ($header, $textmsg, $content); - my ($field, $rule); - my $filterlist = $self->{ _filterlist }; - - $header = $msg->whole_message_header(); - $textmsg = $msg->find_first_plaintext_message(); - $content = $textmsg->message_text() if defined $textmsg; - - # check header - while (($field, $rule) = each %$filterlist) { - if (defined $header->get($field)) { - my $value = $header->get($field); - if ($debug) { - print STDERR "ignore $field: $value\n" if $value =~ /$rule/m; - } - return 1 if $value =~ /$rule/; - } - } - - return 0; -} - - -=head2 get_thread_id() - -get thread_id in object ($self->{ _thread_id }). - -=head2 set_thread_id($thread_id) - -set thread_id in object ($self->{ _thread_id }). - -=cut - - -# Descriptions: get thread_id in object ($self->{ _thread_id }). -# Arguments: OBJ($self) -# Side Effects: none. -# Return Value: STR or UNDEF -sub get_thread_id -{ - my ($self) = @_; - return(defined $self->{ _thread_id } ? $self->{ _thread_id } : undef); -} - - -# Descriptions: set thread_id -# Arguments: OBJ($self) STR($thread_id) -# Side Effects: update $self->{ _thread_id }. -# Return Value: STR -sub set_thread_id -{ - my ($self, $thread_id) = @_; - $self->{ _thread_id } = $thread_id; - return $thread_id; -} - - -# Descriptions: create regexp for a subject tag, for example -# "[%s %05d]" => "\[\S+ \d+\]" -# Arguments: STR($s) -# XXX non OO type function -# Side Effects: none -# Return Value: STR(a regexp for the given tag) -sub _regexp_compile -{ - my ($s) = @_; - - $s = quotemeta( $s ); - $s =~ s@\\\%@\%@g; - $s =~ s@\%s@\\S+@g; - $s =~ s@\%d@\\d+@g; - $s =~ s@\%0\d+d@\\d+@g; - $s =~ s@\%\d+d@\\d+@g; - $s =~ s@\%\-\d+d@\\d+@g; - - # quote for regexp substitute: [ something ] -> \[ something \] - # $s =~ s/^(.)/quotemeta($1)/e; - # $s =~ s/(.)$/quotemeta($1)/e; - - return $s; -} - - -# Descriptions: extract message-id list and return it. -# Arguments: OBJ($header) -# function not OO -# Side Effects: none -# Return Value: ARRAY_HASH -sub _extract_message_id_references -{ - my ($header) = @_; - my (@addrs, @r, %uniq) = (); - - # XXX-TODO: Mail::Message should provide this function ? - - use Mail::Address; - - if (defined $header->get('in-reply-to')) { - my $buf = $header->get('in-reply-to'); - push(@addrs, Mail::Address->parse($buf)); - } - - if (defined $header->get('references')) { - my $buf = $header->get('references'); - push(@addrs, Mail::Address->parse($buf)); - } - - for my $addr (@addrs) { - my $a = $addr->address; - unless (defined $uniq{ $a } && $uniq{ $a }) { - # RFC822 says msg-id = "<" addr-spec ">" ; Unique message id - push(@r, "<".$addr->address.">"); - $uniq{ $a } = 1; - } - } - - return \@r; -} - - -# Descriptions: extract thread_id in Subject: -# Arguments: OBJ($self) OBJ($header) -# Side Effects: none -# Return Value: STR or 0 -sub _extract_thread_id_in_subject -{ - my ($self, $header) = @_; - my $config = $self->{ _config }; - my $tag = $config->{ thread_subject_tag } || ''; - my $loctype = $config->{ thread_subject_tag_location } || 'appended'; - my $subject = $header->get('subject') || ''; - my $regexp = _regexp_compile($tag); - - # Subject: ... [thread_id] - if (($loctype eq 'appended') && ($subject =~ /($regexp)\s*$/)) { - my $id = $1; - $id =~ s/^(\[|\(|\{)//; - $id =~ s/(\]|\)|\})$//; - return $id; - } - # XXX incomplete, we check subject after cutting off "Re:" et. al. - # Subject: [thread_id] ... - # Subject: Re: [thread_id] ... - elsif (($loctype eq 'prepended') && ($subject =~ /^\s*($regexp)/)) { - my $id = $1; - $id =~ s/^(\[|\(|\{)//; - $id =~ s/(\]|\)|\})$//; - return $id; - } - else { - $self->log("no thread id /$regexp/ in subject") if $debug; - return 0; - } -} - - -# Descriptions: -# For example, consider a posting to both elena ML and -# rudo (DM) from kenken. -# -# From: kenken -# To: elena-ml -# Cc: rudo -# -# The reply to this DM (direct message) from rudo is -# -# From: rudo -# To: elena-ml -# -# This reply message has no thread_id since the -# message from kenken to rudo comes directly from -# kenken not through fml driver. In this case, we -# try to speculdate the reply relation and the -# thread_id of this thread by using _ -# speculate_thread_id_from_header(). -# -# Arguments: OBJ($self) OBJ($header) -# Side Effects: none -# Return Value: STR(message id) -sub _speculate_thread_id_from_header -{ - my ($self, $header) = @_; - my $midlist = _extract_message_id_references( $header ); - my $result = ''; - - if (defined $midlist) { - $self->db_open(); - - # prepare hash table tied to db_dir/*db's - my $rh = $self->{ _hash_table }; - - MSGID_LIST: - for my $mid (@$midlist) { - $result = $rh->{ _message_id }->{ $mid }; - last MSGID_LIST if $result; - } - - $self->db_close(); - } - - if ($debug) { - $self->log("(debug) not speculated") unless $result; - } - $result; -} - - -# Descriptions: create thread_id -# Arguments: OBJ($self) STR($id) -# Side Effects: update $self->{ _thread_subject_tag } -# Return Value: STR(thread_id string) -sub _create_thread_id_strings -{ - my ($self, $id) = @_; - my $config = $self->{ _config }; - - # thread_id appeared in subject: field - my $subject_tag = $config->{ thread_subject_tag }; - $self->{ _thread_subject_tag } = sprintf($subject_tag, $id); - - # thread_id used as primary key - my $id_syntax = $config->{ thread_id_syntax }; - return sprintf($id_syntax, $id); -} - - -=head2 update_db($msg) - -update database. - -=cut - - -# Descriptions: top level dispatcher to drive database update -# Arguments: OBJ($self) OBJ($msg) -# Side Effects: update databases -# Return Value: none -sub update_db -{ - my ($self, $msg) = @_; - my $config = $self->{ _config }; - my $ml_name = $config->{ ml_name }; - - if (defined $self->{ _pragma }) { - return if $self->{ _pragma } eq 'ignore'; - } - - $self->db_open(); - - # save $ticke_id et.al. in db_dir/$ml_name - $self->_update_db($msg); - - $self->prepare_history_info($msg) if $self->{ _is_rewrite_header }; - - # save cross reference pointers among $ml_name - $self->_update_index_db(); - - $self->db_close(); -} - - -# Descriptions: speculate unixtime from header -# Arguments: OBJ($msg) -# Side Effects: none -# Return Value: NUM(unix time) -sub _speculate_time -{ - my ($msg) = @_; - my $header = $msg->whole_message_header; - - if (defined $header->get('date')) { - use Mail::Message::Date; - my $obj = new Mail::Message::Date; - return $obj->date_to_unixtime($header->get('date')); - } - else { - return time; - } -} - - -# Descriptions: update database -# Arguments: OBJ($self) OBJ($msg) -# Side Effects: update database -# Return Value: STR -sub _update_db -{ - my ($self, $msg) = @_; - my $config = $self->{ _config }; - my $article_id = $config->{ article_id }; - my $thread_id = $self->get_thread_id(); - - # 0. logging - $self->log("article_id=$article_id thread_id=$thread_id") if $debug; - - # prepare hash table tied to db_dir/*db's - my $rh = $self->{ _hash_table }; - - # check - if (defined $rh->{ _thread_id }->{ $article_id }) { - print STDERR "_update_db.ignore id=$article_id\n" if $debug; - return; - } - - # 1. - $rh->{ _thread_id }->{ $article_id } = $thread_id; - $rh->{ _date }->{ $article_id } = _speculate_time($msg); - $rh->{ _articles }->{ $thread_id } .= $article_id . " "; - - # 2. record the sender information - my $header = $msg->whole_message_header; - $rh->{ _sender }->{ $article_id } = $header->get('from'); - - # 3. update status information - if (defined $self->get_thread_status()) { - my $status = $self->get_thread_status(); - $self->_set_status($thread_id, $status); - } - else { - # set the default status value for the first time. - unless (defined $rh->{ _status }->{ $thread_id }) { - $self->_set_status($thread_id, 'open'); - } - } - - # 4. save optional/additional information - # message_id hash is { message_id => thread_id }; - # RFC822 says msg-id = "<" addr-spec ">" ; Unique message id - my $mid = $header->get('message-id'); $mid =~ s/[\n\s]*$//; - if ($mid eq "") { - print STDERR "missing message-id $thread_id\n" if $debug; - return; - } - $rh->{ _message_id }->{ $mid } = $thread_id; -} - - -# Descriptions: register myself to index_db for further reference -# among mailing lists -# Arguments: OBJ($self) -# Side Effects: update object -# Return Value: STR -sub _update_index_db -{ - my ($self) = @_; - my $config = $self->{ _config }; - my $thread_id = $self->get_thread_id(); - my $rh = $self->{ _hash_table }; - my $ml_name = $config->{ ml_name }; - - my $ref = $rh->{ _index }->{ $thread_id } || ''; - if ($ref !~ /^$ml_name|\s$ml_name\s|$ml_name$/) { - $rh->{ _index }->{ $thread_id } .= $ml_name." "; - } -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack::Analyze first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/DB.pm b/fml/lib/Mail/ThreadTrack/DB.pm deleted file mode 100644 index a7831861..00000000 --- a/fml/lib/Mail/ThreadTrack/DB.pm +++ /dev/null @@ -1,321 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: DB.pm,v 1.33 2003/08/23 04:35:49 fukachan Exp $ -# - -package Mail::ThreadTrack::DB; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - -my $debug = 0; - -=head1 NAME - -Mail::ThreadTrack::DB - database access. - -=head1 SYNOPSIS - -=head1 DESCRIPTION - -=head1 METHODS - -=cut - -=head2 db_open() - -open database. -It uses tie() to bind a hash to a DB file. -Our thread model uses several DB files such as -C<%thread_id>, -C<%date>, -C<%status>, -C<%sender>, -C<%articles>, -C<%message_id> -and -C<%index>. - -=head2 db_close() - -untie() the corresponding hashes opened by C<db_open()>. - -=cut - -my @kind_of_databases = qw(thread_id date status sender articles - message_id); - - -# Descriptions: open database by tie() -# Arguments: OBJ($self) -# Side Effects: $self->{ _hash_table } initialized. -# Return Value: none -sub db_open -{ - my ($self) = @_; - my $db_type = $self->{ config }->{ db_type } || 'AnyDBM_File'; - my $db_dir = $self->{ _db_dir }; - my $file_mode = $self->{ _file_mode } || 0644; - - use File::Spec; - eval qq{ use $db_type; use Fcntl;}; - unless ($@) { - for my $db (@kind_of_databases) { - my $file = File::Spec->catfile($db_dir, $db); - my $str = qq{ - my \%$db = (); - tie \%$db, \$db_type, \$file, O_RDWR|O_CREAT, $file_mode; - \$self->{ _hash_table }->{ _$db } = \\\%$db; - }; - eval $str; - croak($@) if $@; - } - - my %index = (); - my $index_file = $self->{ _index_db }; - eval q{ - tie %index, $db_type, $index_file, O_RDWR|O_CREAT, $file_mode; - $self->{ _hash_table }->{ _index } = \%index; - }; - croak($@) if $@; - } - else { - croak("failed to \"use $db_type\""); - } - - 1; -} - - -# Descriptions: clear database -# Arguments: OBJ($self) -# Side Effects: update database -# Return Value: none -sub db_clear -{ - my ($self) = @_; - my $db_dir = ''; - - $db_dir = $self->{ _db_dir }; - _db_clear($db_dir) if -d $db_dir; - - $db_dir = $self->{ _db_base_dir }; - _db_clear($db_dir) if -d $db_dir; -} - - -# Descriptions: clear database -# Arguments: STR($db_dir) -# Side Effects: clear database, remove file if needed -# Return Value: none -sub _db_clear -{ - my ($db_dir) = @_; - - eval q{ - use DirHandle; - use File::Spec; - my $dh = new DirHandle $db_dir; - - if (defined $dh) { - my $f = ''; - while (defined($f = $dh->read)) { - next if $f =~ /^\./; - my $file = File::Spec->catfile($db_dir, $f); - if (-f $file) { - unlink $file; - print STDERR "removed $file\n" unless -f $file; - } - } - $dh->close; - } - }; - croak($@) if $@; -} - - -# Descriptions: close database by untie() -# Arguments: OBJ($self) -# Side Effects: none -# Return Value: none -sub db_close -{ - my ($self) = @_; - - for my $db (@kind_of_databases) { - my $str = qq{ - my \$${db} = \$self->{ _hash_table }->{ _$db }; - untie \%\$${db}; - }; - eval $str; - croak($@) if $@; - } - - my $index = $self->{ _hash_table }->{ _index }; - untie %$index; -} - - -=head2 db_mkdb($min, $max) - -remake database. - -=cut - - -# Descriptions: remake database for messages from $min_id to $max_id -# Arguments: OBJ($self) NUM($min_id) NUM($max_id) -# Side Effects: remake database -# Return Value: none -sub db_mkdb -{ - my ($self, $min_id, $max_id) = @_; - my $config = $self->{ _config }; - my $spool_dir = $config->{ spool_dir }; - my $saved_args = $self->{ _saved_args }; # original $args - - return undef unless (defined $min_id && defined $max_id); - - use Mail::Message; - use File::Spec; - - my $count = 0; - my ($fh, $file, $msg); - print STDERR "db_mkdb: $min_id -> $max_id\n" if $debug; - - ID: - for my $id ( $min_id .. $max_id ) { - print STDERR "." if $count++ % 10 == 0; - print STDERR "process $id\n" if $debug; - - # XXX-TODO: this code is workaround, we should create more clever way. - # XXX-TODO: overwrite (tricky) - $self->{ _config }->{ article_id } = $id; - - # parse article and analyze it. - $file = $self->filepath({ - base_dir => $spool_dir, - id => $id, - }); - - $fh = new FileHandle $file; - if (defined $fh) { - my $msg = Mail::Message->parse({ fd => $fh }); - $self->analyze($msg); - - # XXX-TODO: workaround, we should create more clever way. - # XXX-TODO: remove current status (tricky ;) - delete $self->{ _status }; - } - } - print STDERR "\n" if $count > 0; -} - - -=head2 db_dump([$type]) - -dump hash as text. -dump status database if $type is not specified. - -=cut - - -# Descriptions: dump data for database $type -# Arguments: OBJ($self) STR($type) -# Side Effects: none -# Return Value: none -sub db_dump -{ - my ($self, $type) = @_; - my $db_type = "_" . ( defined $type ? $type : 'status' ); - my $rh = $self->{ _hash_table }->{ $db_type }; - - my ($k, $v); - while (($k, $v) = each %$rh) { - printf "%-20s %s\n", $k, $v; - } -} - - -=head2 db_hash( $type ) - -return HASH REFERENCE for specified database $type. - -=cut - - -# Descriptions: get HASH REFERENCE for specified $type. -# Arguments: OBJ($self) STR($db_type) -# Side Effects: none -# Return Value: HASH_REF or UNDEF -sub db_hash -{ - my ($self, $db_type) = @_; - my $type = "_" . $db_type; - - if (defined $self->{ _hash_table }->{ $type }) { - return $self->{ _hash_table }->{ $type }; - } - else { - return undef; - } -} - - -=head2 db_last_modified() - -return the last modified time of our dateabase as unix time. -This time is the latest modified time among all database files. - -=cut - - -# Descriptions: return the last modified time (unix time) of database -# Arguments: OBJ($self) STR($db_type) -# Side Effects: none -# Return Value: STR or UNDEF -sub db_last_modified -{ - my ($self, $db_type) = @_; - my $db_dir = $self->{ _db_dir }; - my $last_modified = 0; - - # XXX-TODO: we supporse Berkeley DB. fix it. - use File::Spec; - my $file = File::Spec->catfile($db_dir, "date.db"); - if (-f $file) { - $last_modified = (stat($file))[8]; - } - - return $last_modified; -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack::DB first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/HeaderRewrite.pm b/fml/lib/Mail/ThreadTrack/HeaderRewrite.pm deleted file mode 100644 index 5b47445c..00000000 --- a/fml/lib/Mail/ThreadTrack/HeaderRewrite.pm +++ /dev/null @@ -1,133 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: HeaderRewrite.pm,v 1.14 2003/08/23 04:35:49 fukachan Exp $ -# - -package Mail::ThreadTrack::HeaderRewrite; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - -=head1 NAME - -Mail::ThreadTrack::HeaderRewrite - header manipulation - -=head1 SYNOPSIS - -=head1 DESCRIPTION - -=head1 METHODS - -=head2 rewrite_header($msg) - -C<$msg> is Mail::Message object. - -=cut - - -# Descriptions: add thread track info into $msg where -# $msg is Mail::Message object. -# Arguments: OBJ($self) OBJ($msg) -# Side Effects: modify $msg header -# Return Value: none -sub rewrite_header -{ - my ($self, $msg) = @_; - my $config = $self->{ _config }; - my $loctype = $config->{ thread_subject_tag_location } || 'appended'; - my $header = $msg->whole_message_header(); - my $tag = $self->{ _thread_subject_tag } || ''; - - # append the thread tag to the subject - if (defined $header->get('subject')) { - my $subject = $header->get('subject'); - - if ($loctype eq 'appended' && $tag) { - $header->replace('subject', $subject ." ". $tag); - } - elsif ($loctype eq 'prepended' && $tag) { - $header->replace('subject', $tag ." ". $subject); - } - else { - $self->log("unknown thread_subject_tag_location type"); - } - - if (defined $self->{ _status_info }) { - $header->add('X-Thread-Status', $self->{ _status_info }); - } - - if (defined $self->{ _thread_id }) { - $header->add('X-Thread-ID', $self->{ _thread_id }); - } - - if (defined $self->{ _status_history }) { - $header->add('X-Thread-History', $self->{ _status_history }); - } - } -} - - -# Descriptions: prepare history infomation for further header rewriting. -# Arguments: OBJ($self) OBJ($msg) -# Side Effects: update $self->{ _status_history } -# Return Value: none -sub prepare_history_info -{ - my ($self, $msg) = @_; - my $thread_id = $self->get_thread_id(); - - # prepare hash table tied to db_dir/*db's - my $rh = $self->{ _hash_table }; - - if (defined $rh->{ _articles }->{ $thread_id }) { - my $buf = ''; - my (@aid) = split(/\s+/, $rh->{ _articles }->{ $thread_id }); - my $sender = $rh->{ _sender }->{ $aid[0] }; - my $when = $rh->{ _date }->{ $aid[0] }; - - # clean up - $sender =~ s/[\s\n]*$//; - $when =~ s/[\s\n]*$//; - - use Mail::Message::Date; - $when = Mail::Message::Date->new($when)->mail_header_style(); - - # XXX-TODO: validate $aid[0], $sender, $when, @aid. - $buf .= "\t\n"; - $buf .= "\tthis thread is opended at article $aid[0]\n"; - $buf .= "\tby $sender\n"; - $buf .= "\ton $when\n"; - $buf .= "\tarticle references: @aid\n"; - $self->{ _status_history } = $buf; - } -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack::HeaderRewrite first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Print.pm b/fml/lib/Mail/ThreadTrack/Print.pm deleted file mode 100644 index c8b08bfc..00000000 --- a/fml/lib/Mail/ThreadTrack/Print.pm +++ /dev/null @@ -1,419 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2004 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: Print.pm,v 1.27 2002/12/22 03:21:33 fukachan Exp $ -# - -package Mail::ThreadTrack::Print; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; -use Mail::ThreadTrack::Print::Utils qw(decode_mime_string STR2EUC); - -=head1 NAME - -Mail::ThreadTrack::Print - dispatcher to print out thread - -=head1 SYNOPSIS - -=head1 DESCRIPTION - -=head1 METHODS - -=head2 list([ @opts ]) - -show todo list without article summary. - -=cut - - -# Descriptions: show todo list without article summary. -# Arguments: OBJ($self) VARARGS(@opts) -# Side Effects: none -# Return Value: none -sub list -{ - my ($self, @opts) = @_; - - $self->_load_library(); - $self->db_open(); - $self->_do_list(@opts); - $self->db_close(); -} - - -=head2 summary([ @opts ]) - -show the thread summary, which is todo list C<with> article summary. - -Each row that C<show_summary()> returns has a set of -C<date>, C<age>, C<status>, C<thread-id> and -C<articles>, which is a list of articles with the thread-id. - -list() shows entries by the thread_id order. For example, - - date age status thread id articles - ------------------------------------------------------------ - 2001/02/07 3.6 going elena_#00000450 807 808 809 - 2001/02/07 3.1 open elena_#00000451 810 - 2001/02/07 3.0 open elena_#00000452 812 - 2001/02/07 3.0 open elena_#00000453 813 - 2001/02/07 3.0 going elena_#00000454 814 815 - 2001/02/10 0.1 open elena_#00000456 821 - -summary() shows the todo list above and article summaries. - -=cut - - -# Descriptions: show todo list with article summary. -# Arguments: OBJ($self) VARARGS(@opts) -# Side Effects: none -# Return Value: none -sub summary -{ - my ($self, @opts) = @_; - - $self->_load_library(); - $self->db_open(); - $self->_do_summary(@opts); - $self->db_close(); -} - - -=head2 review([ @opts ]) - -show a chain of a few lines summary for articles in each thread. -This summary is a collection of short summary of articles in one thread. - -=cut - - -# Descriptions: show a chain of summaries for each thread. -# Arguments: OBJ($self) VARARGS(@opts) -# Side Effects: none -# Return Value: none -sub review -{ - my ($self, @opts) = @_; - - $self->_load_library(); - $self->db_open(); - $self->_do_review(@opts); - $self->db_close(); -} - - - -# Descriptions: load subclasses, change @INC. -# Arguments: OBJ($self) -# Side Effects: @INC modified -# Return Value: none -sub _load_library -{ - my ($self) = @_; - my $mode = $self->get_mode || 'text'; - - require Mail::ThreadTrack::Print::Message; - require Mail::ThreadTrack::Print::Sort; - my @list = - qw(Mail::ThreadTrack::Print::Message Mail::ThreadTrack::Print::Sort); - - if ($mode eq 'text') { - require Mail::ThreadTrack::Print::Text; - push(@list, 'Mail::ThreadTrack::Print::Text'); - } - elsif ($mode eq 'html') { - require Mail::ThreadTrack::Print::HTML; - push(@list, 'Mail::ThreadTrack::Print::HTML'); - } - - unshift(@ISA, @list); -} - - -# -# SUMMARY MODE -# - - -# Descriptions: dispatcher to show todo list with article summary. -# Arguments: OBJ($self) -# Side Effects: none -# Return Value: none -sub _do_summary -{ - my ($self) = @_; - $self->__do_summary( { mode => 'summary' }); -} - - -# Descriptions: dispatcher to show todo list without article summary. -# Arguments: OBJ($self) -# Side Effects: none -# Return Value: none -sub _do_list -{ - my ($self) = @_; - $self->__do_summary( { mode => 'list' }); -} - - -# Descriptions: get thread id list with status != 'close' and -# show summary for the list if needed. -# Arguments: OBJ($thread) HASH_REF($option) -# Side Effects: none -# Return Value: none -sub __do_summary -{ - my ($thread, $option) = @_; - my $mode = $thread->get_mode || 'text'; - my $config = $thread->{ _config }; - - # rh: thread id list picked from status.db - my $thread_id_list = $thread->list_up_thread_id(); - - # 1. sort the thread output order by cost - # 2. print the thread brief summary in that order. - # 3. show short summary for each message if needed (mode dependent) - if (@$thread_id_list) { - # XXX-TODO: $thread->sort() method should accept the order ? - $thread->sort_thread_id($thread_id_list); - - # reverse order (first thread is the latest one) if reverse mode - if ($config->{ reverse_order }) { - @$thread_id_list = reverse @$thread_id_list; - } - - # XXX thread summary == todo list - $thread->_print_thread_summary($thread_id_list); - - # XXX message summary == brief summary of articles. - if ($option->{ mode } eq 'summary') { - $thread->_print_message_summary($thread_id_list); - } - } -} - - -# Descriptions: show thread summary (without article summary). -# Arguments: OBJ($self) ARRAY_REF($thread_id_list) -# Side Effects: none -# Return Value: none -sub _print_thread_summary -{ - my ($self, $thread_id_list) = @_; - my $mode = $self->get_mode || 'text'; - my $db = $self->{ _hash_table }; - - # guide of presentation - $self->__start_thread_summary(); # XXX dynamic binding - - # show brief summary along thread_id list - my ($thread_id, @article_id, $article_id, $date, $age, $status) = (); - my $date_h = new Mail::Message::Date; - for $thread_id (@$thread_id_list) { - next unless defined $db->{ _articles }->{ $thread_id }; - - # get the first $article_id from the article_id list - (@article_id) = split(/\s+/, $db->{ _articles }->{ $thread_id }); - $article_id = $article_id[0]; - - # format $date for the $article_id - $date = $date_h->YYYYxMMxDD( $db->{ _date }->{ $article_id } , '/'); - $age = $self->{ _age }->{ $thread_id }; - $status = $db->{ _status }->{ $thread_id }; - - # XXX-TODO: who care for output mode ? (e.g. against CSS). - $self->__print_thread_summary( { - date => $date, - age => $age, - status => $status, - thread_id => $thread_id, - articles => $db->{ _articles }->{ $thread_id }, - }); # XXX dynamic binding - } - - $self->__end_thread_summary(); # XXX dynamic binding -} - - -# Descriptions: show the first few lines of the first message in the thread -# Arguments: OBJ($self) STR($thread_id) -# Side Effects: none -# Return Value: none -sub _print_message_summary -{ - my ($self, $thread_id) = @_; - $self->__print_message_summary($thread_id); -} - - -# -# REVIEW MODE -# - -# Descriptions: show brief summary chain of messages in the thread. -# Arguments: OBJ($self) STR($str) NUM($min) NUM($max) -# Side Effects: none -# Return Value: none -sub _do_review -{ - my ($self, $str, $min, $max) = @_; - my $config = $self->{ _config }; - my $spool_dir = $config->{ spool_dir }; - my $fd = $self->{ _fd } || \*STDOUT; - my $db = $self->{ _hash_table }; - my %uniq = (); - my $is_first = 0; - - # translate the given parameter (MH style) - # get ARRAY_REF of specified range - use Mail::Message::MH; - my $range = Mail::Message::MH->expand($str, $min, $max); - - # reverse order (first thread is the latest one) if reverse mode - if ($config->{ reverse_order }) { @$range = reverse @$range;} - - ID_LIST: - for my $id (@$range) { - next ID_LIST unless defined $id; - - if ($id =~ /^\d+$/) { - # create thread identifier string: e.g. 100 -> elena/100 - my $tid = $self->_create_thread_id_strings($id); - - # check thread id $tid exists really ? - if (defined $db->{ _articles }->{ $tid }) { - # XXX-TODO: validate $tid. - printf $fd "\n>Thread-Id: %-10s %s\n", $tid; - - # different treatment for the fisrt article in this thread - $is_first = 1; - - # show all articles in this thread - ARTICLE: - for my $aid (split(/\s+/, $db->{ _articles }->{ $tid })) { - # ensure uniquness - next ARTICLE if $uniq{ $aid }; - $uniq{ $aid } = 1; - - # show header only for the first message in this thread - if ($is_first) { - undef $self->{ _no_header_summary }; - $is_first = 0; - } - else { - $self->{ _no_header_summary } = 1; - } - - my $file = $self->filepath({ - base_dir => $spool_dir, - id => $aid, - }); - if (-f $file) { - $self->print( $self->message_summary($file) ); - print $fd "\n"; - } - } - } - } - } -} - - -=head2 show($tid) - -show all articles in specified thread. - -=cut - - -# Descriptions: show all articles in specified thread. -# Arguments: OBJ($self) STR($tid) -# Side Effects: none -# Return Value: none -sub show -{ - my ($self, $tid) = @_; - - $self->_load_library(); - $self->db_open(); - $self->show_articles_in_thread($tid); - $self->db_close(); -} - - -=head2 print(str) - -print str with special effect e.g. quoting if needed. -The function depends the mode, 'text' or 'html'. - -=cut - - -# Descriptions: wrapper of print() -# Arguments: OBJ($self) STR($str) -# Side Effects: quote if needed -# Return Value: none -sub print -{ - my ($self, $str) = @_; - my $mode = $self->get_mode || 'text'; - my $fd = $self->{ _fd } || \*STDOUT; - - if ($mode eq 'text') { - print $fd $str; - } - elsif ($mode eq 'html') { - $str = &_quote($str); - $str =~ s/\n/<BR>\n/g; - print $fd $str; - } -} - - -# Descriptions: quote for html metachars -# Arguments: STR($str) -# Side Effects: none -# Return Value: STR -sub _quote -{ - my ($str) = @_; - - $str =~ s/&/&/g; - $str =~ s/</</g; - $str =~ s/>/>/g; - $str =~ s/\"/"/g; - - return $str; -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2004 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 - -Mail::ThreadTrack::Print first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Print/HTML.pm b/fml/lib/Mail/ThreadTrack/Print/HTML.pm deleted file mode 100644 index 220c6e09..00000000 --- a/fml/lib/Mail/ThreadTrack/Print/HTML.pm +++ /dev/null @@ -1,291 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: HTML.pm,v 1.17 2003/02/11 11:22:56 fukachan Exp $ -# - -package Mail::ThreadTrack::Print::HTML; - -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - - -=head1 NAME - -Mail::ThreadTrack::Print::HTML - print thread summary as HTML - -=head1 SYNOPSIS - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 DESCRIPTION - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 METHODS - -=head2 show_articles_in_thread(thread_id) - -show articles as HTML in this thread. - -=cut - - -use CGI qw/:standard/; -use Mail::ThreadTrack::Print::Utils qw(decode_mime_string STR2EUC); - - -# Descriptions: show articles as HTML in this thread -# Arguments: OBJ($self) STR($thread_id) -# Side Effects: none -# Return Value: none -sub show_articles_in_thread -{ - my ($self, $thread_id) = @_; - my $mode = $self->get_mode || 'text'; - my $config = $self->{ _config }; - my $spool_dir = $config->{ spool_dir }; - - my $articles = $self->{ _hash_table }->{ _articles }->{ $thread_id }; - - # XXX-TODO: who validates $thread_id ? - print "<B>"; - print "show contents related with thread_id=$thread_id\n"; - print "</B>"; - print "<HR>"; - print "<PRE>\n"; - - if (defined($articles) && defined($spool_dir) && -d $spool_dir) { - use FileHandle; - - my $s = ''; - for my $article (split(/\s+/, $articles)) { - my $file = $self->filepath({ - base_dir => $spool_dir, - id => $article, - }); - - # XXX-TODO: care for non Japanese char(s). - # XXX-TODO: to avoid CSS bug, convert all special char(s). - # XXX-TODO: create method safe_html_string() in Mail::Message ? - if (-f $file) { - my $fh = new FileHandle $file; - - if (defined $fh) { - my $buf; - - while (defined($buf = $fh->getline())) { - # ignore header part. - next if 1 .. $buf =~ /^$/o; - - $s = STR2EUC($buf); - $s =~ s/&/&/g; - $s =~ s/</</g; - $s =~ s/>/>/g; - $s =~ s/\"/"/g; - print $s; - } - $fh->close; - } - } - } - } - - print "</PRE>"; -} - - -# Descriptions: show guide -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: none -sub __start_thread_summary -{ - my ($self, $args) = @_; - my $config = $self->{ _config }; - my $ml_name = $config->{ ml_name }; - my $fd = $self->{ _fd } || \*STDOUT; - my $action = $curproc->safe_cgi_action_name(); - my $target = '_top'; - - # statistics - if (defined $self->{ _ticket_id_stat }) { - my $stat = $self->{ _ticket_id_stat }; - for my $key ('open', 'analyzed', 'closed') { - print $fd "$key: "; - print $fd defined $stat->{ $key } ? $stat->{ $key } : 0; - print $fd ", "; - } - print $fd br, "\n"; - } - - # XXX-TODO: validate $action ? - print $fd start_form(-action=>$action, -target=>$target); - print $fd submit(-name => 'submit'); - print $fd reset(-name => 'reset'); - print $fd "\n"; - - # XXX-TODO: validate $ml_name ? - print $fd hidden(-name => 'ml_name', - -default => [ $ml_name ], - ), "\n"; - - param('action', 'change_status'); # we need to override - print $fd hidden(-name => 'action', - -default => [ 'change_status ' ], - ), "\n"; - - print $fd "<TABLE BORDER=4>\n"; - print $fd "<TD>id\n"; - print $fd "<TD>change\n"; - print $fd "<TD>summary\n"; - print $fd "<TD>age\n"; - print $fd "<TD>status\n"; -} - - -# Descriptions: finalize thread list. -# close TABLE tag. -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: none -sub __end_thread_summary -{ - my ($self, $args) = @_; - my $fd = $self->{ _fd } || \*STDOUT; - - print $fd "</TABLE>\n"; - - print submit(-name => 'submit'); - print reset(-name => 'reset'); - print $fd end_form; -} - - -# Descriptions: This shows summary on C<$thread_id> in HTML language. -# It is used in C<FML::CGI::ThreadSystem>. -# Arguments: OBJ($self) HASH_REF($optargs) -# Side Effects: none -# Return Value: none -sub __print_thread_summary -{ - my ($self, $optargs) = @_; - my $config = $self->{ _config }; - my $ml_name = $config->{ ml_name }; - my $spool_dir = $config->{ spool_dir }; - my $action = $curproc->safe_cgi_action_name(); - my $target = $config->{ thread_cgi_target_window } || '_top'; - - my $date = $optargs->{ date }; - my $age = $optargs->{ age }; - my $status = $optargs->{ status }; - my $tid = $optargs->{ thread_id }; - my $articles = $optargs->{ articles }; - my $aid = (split(/\s+/, $articles))[0]; - - # do nothing if the $thread_id is unknown. - return unless $tid; - - # XXX-TODO: validate $action, $ml_name, $aid ... - # <FORM ACTION=> ..> - my $xtid = CGI::escape($tid); - $action = "${action}?ml_name=${ml_name}&article_id=$aid"; - - $self->{ _table_count } = 1 unless defined $self->{ _table_count }; - if (($self->{ _table_count }++ % 5) == 0) { - print "<TR>\n<TD>\n"; - print submit(-name => 'submit'); - print reset(-name => 'reset'); - } - - print "<TR>\n"; - - # XXX-TODO: validate $msg_base_url ? - # show articles in this thread id - print "<TD>"; - if (defined $config->{ msg_base_url }) { - my $msg_base_url = $config->{ msg_base_url }; - my $url = "$msg_base_url/msg$aid.html"; - print "<A HREF=\"$url\" TARGET=\"article\">\n"; - print $tid; - print "\n</A>\n"; - } - else { - # XXX-TODO: validate $action ? - print "<A HREF=\"$action&action=show\" TARGET=\"article\">\n"; - print $tid; - print "\n</A>\n"; - } - - # action - print "<TD>"; - my $name = "change_status.$tid"; - my $values = ["open", "analyzed", "closed"]; - my $default = $status; - print radio_group(-name => $name, - -values => $values, - -default => $default, - -linebreak => 'true', - ); - - # message (article) brief summary - print "<TD>"; - if (defined $articles) { - $aid = (split(/\s+/, $articles))[0]; - my $f = $self->filepath({ - base_dir => $spool_dir, - id => $aid, - }); - if (-f $f) { - # XXX-TODO: care for non Japanese. - my $buf = $self->message_summary($f); - $self->print( STR2EUC($buf) ); - } - } - - # addional information: age, status - print "<TD>$age\n"; - print "<TD>$status\n"; - - print "\n\n"; -} - - -# Descriptions: dummy, defined for symmetry -# Arguments: none -# Side Effects: none -# Return Value: none -sub __print_message_summary -{ - ; -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack::Print::HTML first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Print/Message.pm b/fml/lib/Mail/ThreadTrack/Print/Message.pm deleted file mode 100644 index 7c61a63a..00000000 --- a/fml/lib/Mail/ThreadTrack/Print/Message.pm +++ /dev/null @@ -1,253 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: Message.pm,v 1.9 2002/12/22 03:19:15 fukachan Exp $ -# - -package Mail::ThreadTrack::Print::Message; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; -use Mail::ThreadTrack::Print::Utils qw(decode_mime_string STR2EUC); - - -=head1 NAME - -Mail::ThreadTrack::Print::Message - summarize message et.al. - -=head1 SYNOPSIS - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 DESCRIPTION - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 METHODS - -=head2 message_summary($file) - -make message summary for specified $file (article). - -=cut - - -# Descriptions: make summary of the specified $file (article). -# Arguments: OBJ($self) STR($file) -# Side Effects: none -# Return Value: STR -sub message_summary -{ - my ($self, $file) = @_; - my (@header) = (); - my $msgbuf = ''; - my $line = $self->{ _article_summary_lines } || 3; - my $mode = $self->get_mode || 'text'; - my $padding = $mode eq 'text' ? ' ' : ''; - - use FileHandle; - my $fh = new FileHandle $file; - - if (defined $fh) { - my $buf; - - LINE: - while ($buf = <$fh>) { - # remove useless lines - next LINE if $buf =~ /^\>/o; - next LINE if $buf =~ /^\-/o; - - # header part - if (1 .. $buf =~ /^$/o) { - push(@header, $buf); - } - # body part - else { - next LINE if $buf =~ /^\s*$/o; - - # ignore mail header like patterns. - next LINE if $buf =~ /^X-[-A-Za-z0-9]+:/io; - next LINE if $buf =~ /^Return-[-A-Za-z0-9]+:/io; - next LINE if $buf =~ /^Mime-[-A-Za-z0-9]+:/io; - next LINE if $buf =~ /^Content-[-A-Za-z0-9]+:/io; - next LINE if $buf =~ /^(To|From|Subject|Reply-To|Received):/io; - next LINE if $buf =~ /^(Message-ID|Date):/io; - - # pick up effetive the first $line lines - if (_is_valid_buf($buf)) { - $line--; - $msgbuf .= $padding . $buf; - } - - last LINE if $line < 0; - } - } - - $fh->close(); - - # XXX-TODO: WHO CARE FOR CSS ? return raw messages from here. - # XXX-TODO: care for non Japanese. - if (defined $self->{ _no_header_summary }) { - return STR2EUC( $msgbuf ); - } - else { - use Mail::Header; - my $header = new Mail::Header \@header; - my $header_info = $self->header_summary({ - header => $header, - padding => $padding, - }); - return STR2EUC( $header_info ."\n". $msgbuf ); - } - } - else { - return undef; - } -} - - -# Descriptions: check if $str looks effective, not quotation et.al. ? -# Arguments: STR($str) -# Side Effects: none -# Return Value: 1 or 0 -sub _is_valid_buf -{ - my ($str) = @_; - $str = STR2EUC( $str ); - - if ($str =~ /^[\>\#\|\*\:\;\=]/o) { - return 0; - } - elsif ($str =~ /^in /o) { # quotation ? - return 0; - } - elsif ($str =~ /\w+\@\w+/o) { # mail address ? - return 0; - } - elsif ($str =~ /^\S+\>/o) { # quotation ? - return 0; - } - - return 1; -} - - -# Descriptions: remove subject tag like string in $str e.g. [elena 100]. -# Arguments: STR($str) -# Side Effects: none -# Return Value: STR -sub _delete_subject_tag_like_string -{ - my ($str) = @_; - - if (defined $str) { - # XXX-TODO: hmm, method-ify Mail::Message::Utils ? - use Mail::Message::Utils; - return Mail::Message::Utils::remove_subject_tag_like_string($str); - } - else { - return undef; - } -} - - -# Descriptions: make summary of header $args->{ header }. -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: STR -sub header_summary -{ - my ($self, $args) = @_; - my $date = $args->{ header }->get('date'); - my $from = $args->{ header }->get('from'); - my $subject = $args->{ header }->get('subject'); - my $padding = $args->{ padding } || ' '; - - # XXX-TODO: care for non Japanese. - if (defined $subject) { - $subject = decode_mime_string($subject, { charset => 'euc-japan' }); - $subject =~ s/\n/ /g; - $subject = _delete_subject_tag_like_string($subject); - $subject =~ s/[\s\n]*$//g; - } - - if (defined $from) { - $from = $self->_who_of_address( $from ); - $from =~ s/\n/ /g; - $from =~ s/[\s\n]*$//g; - } - - # XXX-TODO: WHO CARE FOR CSS ? return raw messages from here. - # XXX-TODO: care for non Japanese. - # return buffer - my $r = $padding. $date; - $r .= $padding. "$subject, $from\n"; - return STR2EUC( $r ); -} - - -# Descriptions: get gecos field in $address. -# return $address itself if the extraction failed. -# Arguments: OBJ($self) STR($address) -# Side Effects: none -# Return Value: STR -sub _who_of_address -{ - my ($self, $address) = @_; - my ($user); - - use Mail::Address; - my (@addrs) = Mail::Address->parse($address); - - for my $addr (@addrs) { - if (defined( $addr->phrase() )) { - # XXX-TODO: care for non Japanese. - my $phrase = decode_mime_string( $addr->phrase(), { - charset => 'euc-japan', - }); - - if ($phrase) { - return($phrase); - } - } - - $user = $addr->user(); - } - - # XXX-TODO: hmm, CROSS SITE SCRIPTING may cause ? - if ($self->get_mode() eq 'html') { - return( $user ? "$user\@xxx.xxx.xxx.xxx" : $address ); - } - else { - return $address; - } -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack::Print::Message first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Print/Sort.pm b/fml/lib/Mail/ThreadTrack/Print/Sort.pm deleted file mode 100644 index 6b0fa5bf..00000000 --- a/fml/lib/Mail/ThreadTrack/Print/Sort.pm +++ /dev/null @@ -1,124 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2004 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: Sort.pm,v 1.9 2002/12/22 03:21:33 fukachan Exp $ -# - -package Mail::ThreadTrack::Print::Sort; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - - -=head1 NAME - -Mail::ThreadTrack::Print::Sort - sort function for printing - -=head1 SYNOPSIS - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 DESCRIPTION - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 METHODS - -=head2 sort_thread_id($thread_id_list) - -=cut - - -# Descriptions: sort ARRAY REFERENCE $thread_id_list -# Arguments: OBJ($self) ARRAY_REF($thread_id_list) -# Side Effects: initialize $self->{ _age } and $self->{ _cost } -# Return Value: ARRAY_REF -sub sort_thread_id -{ - my ($self, $thread_id_list) = @_; - - # get age HASH TABLE - my ($age, $cost) = $self->_calculate_age($thread_id_list); - $self->{ _age } = $age; - $self->{ _cost } = $cost; - - @$thread_id_list = sort { - $cost->{$b} <=> $cost->{$a} - } @$thread_id_list; - - return $thread_id_list; -} - - -my $status_cost = { - open => ( 1 << 10 ), - analyzed => ( 1 << 9 ), -}; - - -# Descriptions: evaluate how old and status each thread is -# Arguments: OBJ($self) ARRAY_REF($thread_id_list) -# Side Effects: none -# Return Value: ARRAY( HASH_REF, HASH_REF ) -sub _calculate_age -{ - my ($self, $thread_id_list) = @_; - my (%age, %cost) = (); - my $now = time; # save the current UTC for convenience - my $rh = $self->{ _hash_table } || {}; - my $day = 24*3600; - - # $age hash referehence = { $thread_id => $age }; - my (@aid, $last, $age, $date, $status, $tid) = (); - for $tid (sort @$thread_id_list) { - next unless defined $rh->{ _articles }->{ $tid }; - - # $last: get the latest one of article_id's - (@aid) = split(/\s+/, $rh->{ _articles }->{ $tid }); - $last = $aid[ $#aid ] || 0; - - # how long this thread is not concerned ? - $age = sprintf("%2.1f%s", ($now - $rh->{ _date }->{ $last })/$day); - $age{ $tid } = $age; - - # evaluate cost hash table which is { $thread_id => $cost } - my $status = $rh->{ _status }->{ $tid }; - $cost{ $tid } = $status_cost->{ $status } + $age; - } - - return (\%age, \%cost); -} - - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2004 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 - -Mail::ThreadTrack::Print::Sort first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Print/Text.pm b/fml/lib/Mail/ThreadTrack/Print/Text.pm deleted file mode 100644 index c6d5a6a1..00000000 --- a/fml/lib/Mail/ThreadTrack/Print/Text.pm +++ /dev/null @@ -1,247 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2003 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: Text.pm,v 1.14 2003/01/11 15:16:37 fukachan Exp $ -# - -package Mail::ThreadTrack::Print::Text; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; -use Mail::ThreadTrack::Print::Utils qw(decode_mime_string STR2EUC); - -# -# XXX-TODO: insert more examples on format in each function. -# - -=head1 NAME - -Mail::ThreadTrack::Print::Text - printing suitable for text - -=head1 SYNOPSIS - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 DESCRIPTION - -See C<Mail::ThreadTrack::Print> for usage of this subclass. - -=head1 METHODS - -=head2 show_articles_in_thread(thread_id) - -show articles as text in this thread. - -=cut - -# XXX-TODO: $is_show_cost_indicate hard-coded. -my $is_show_cost_indicate = 0; - -# XXX-TODO: $format hard-coded. -my $format = "%-20s %10s %5s %8s %s\n"; - - -# Descriptions: show articles as text in this thread -# Arguments: OBJ($self) STR($thread_id) -# Side Effects: none -# Return Value: none -sub show_articles_in_thread -{ - my ($self, $thread_id) = @_; - my $mode = $self->get_mode || 'text'; - my $config = $self->{ _config }; - my $spool_dir = $config->{ spool_dir }; - my $articles = $self->{ _hash_table }->{ _articles }->{ $thread_id }; - my $wh = $self->{ _fd } || \*STDOUT; - - use FileHandle; - if (defined($articles) && defined($spool_dir) && -d $spool_dir) { - my $s = ''; - # $articles = "1 2 3 4 5"; - for my $id (split(/\s+/, $articles)) { - my $file = $self->filepath({ - base_dir => $spool_dir, - id => $id, - }); - - my $fh = new FileHandle $file; - if (defined $fh) { - my $buf; - - LINE: - while (defined($buf = $fh->getline())) { - next LINE if 1 .. $buf =~ /^$/o; - - # XXX-TODO: we suppose Japanese only here. - $s = STR2EUC($buf); - print $wh $s; - } - $fh->close; - } - } - } -} - - -# Descriptions: show guide line -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: none -sub __start_thread_summary -{ - my ($self, $args) = @_; - my $fd = $self->{ _fd } || \*STDOUT; - - # XXX-TODO: guide line is hard-coded. o.k.? - printf($fd $format, 'id', 'date', 'age', 'status', 'articles'); - print $fd "-" x60; - print $fd "\n"; -} - - -# Descriptions: print formatted brief summary -# Arguments: OBJ($self) HASH_REF($optargs) -# Side Effects: none -# Return Value: none -sub __print_thread_summary -{ - my ($self, $optargs) = @_; - my $fd = $self->{ _fd } || \*STDOUT; - my $date = $optargs->{ date }; - my $age = $optargs->{ age }; - my $status = $optargs->{ status }; - my $thread_id = $optargs->{ thread_id }; - my $articles = $optargs->{ articles }; - my $aid = (split(/\s+/, $articles))[0]; # the head of this thread - - printf($fd $format, $thread_id, $date, $age, $status, - _format_list(25, $articles)); -} - - -# Descriptions: print closing string, empty now (dummy). -# Arguments: OBJ($self) HASH_REF($args) -# Side Effects: none -# Return Value: none -sub __end_thread_summary -{ - my ($self, $args) = @_; - my $fd = $self->{ _fd } || \*STDOUT; -} - - -# Descriptions: create a string of "a b c .." style up to $num bytes -# Arguments: NUM($max) STR($str) -# Side Effects: none -# Return Value: STR -sub _format_list -{ - my ($max, $str) = @_; - my (@idlist) = split(/\s+/, $str); - my $r = ''; - - ID: - for my $id (@idlist) { - $r .= $id . " "; - if (length($r) > $max) { - $r .= "..."; - last ID; - } - } - - return $r; -} - - -# Descriptions: print message summary -# Arguments: OBJ($self) STR($thread_id) -# Side Effects: none -# Return Value: none -sub __print_message_summary -{ - my ($self, $thread_id) = @_; - my $config = $self->{ _config }; - my $age = $self->{ _age } || {}; - my $cost = $self->{ _cost } || {}; - my $fd = $self->{ _fd } || \*STDOUT; - my $rh = $self->{ _hash_table }; - - if (defined $config->{ spool_dir }) { - my ($aid, @aid, $file); - my $spool_dir = $config->{ spool_dir }; - - THREAD_ID_LIST: - for my $thread_id (@$thread_id) { - if ($is_show_cost_indicate) { - my $how_bad = _cost_to_indicator( $cost->{ $thread_id } ); - printf $fd "\n%6s %-10s %s\n", $how_bad, $thread_id; - } - else { - printf $fd "\n>Thread-Id: %-10s %s\n", $thread_id; - } - - # show only the first article of this thread $thread_id - if (defined $rh->{ _articles }->{ $thread_id }) { - (@aid) = split(/\s+/, $rh->{ _articles }->{ $thread_id }); - $aid = $aid[0]; - $file = $self->filepath({ - base_dir => $spool_dir, - id => $aid, - }); - if (-f $file) { - $self->print( $self->message_summary($file) ); - } - } - } - } -} - - -# Descriptions: for example, cost -> '!!!' -# broken now ;-) -# Arguments: STR($cost) -# Side Effects: none -# Return Value: STR -sub _cost_to_indicator -{ - my ($cost) = @_; - my $how_bad = 0; - - # XXX-TODO: cost indicator is broken ? - if ($cost =~ /(\w+)\-(\d+)/) { - $how_bad += $2; - $how_bad += 2 if $1 =~ /open/; - $how_bad = "!" x ($how_bad > 6 ? 6 : $how_bad); - } - - $how_bad; -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2003 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 - -Mail::ThreadTrack::Print::Text first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; diff --git a/fml/lib/Mail/ThreadTrack/Print/Utils.pm b/fml/lib/Mail/ThreadTrack/Print/Utils.pm deleted file mode 100644 index 45bfcbcf..00000000 --- a/fml/lib/Mail/ThreadTrack/Print/Utils.pm +++ /dev/null @@ -1,115 +0,0 @@ -#-*- perl -*- -# -# Copyright (C) 2001,2002,2004 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: Utils.pm,v 1.6 2002/12/22 03:21:33 fukachan Exp $ -# - -package Mail::ThreadTrack::Print::Utils; -use strict; -use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); -use Carp; - -require Exporter; -@ISA = qw(Exporter); -@EXPORT_OK = qw(decode_mime_string STR2EUC); - - -=head1 NAME - -Mail::ThreadTrack::Print::Utils - utility functions - -=head1 SYNOPSIS - - use Mail::ThreadTrack::Print::Utils qw(decode_mime_string STR2EUC); - -=head1 DESCRIPTION - -utility functions to manipulate Japanese string. - -=head1 METHODS - -=head2 decode_mime_string(string, [$options]) - -decode a base64/quoted-printable encoded string to a plain message. -The encoding method is automatically detected. - -C<$options> is a HASH REFERENCE. -You can specify the charset of the string to return -by $options->{ charset }. - -=head2 STR2EUC(str) - -convert str to Japanese EUC. - -=cut - - -# Descriptions: decode $str -# Arguments: STR($str) HASH_REF($options) -# Side Effects: none -# Return Value: STR -sub decode_mime_string -{ - my ($str, $options) = @_; - my $charset = $options->{ 'charset' } || 'euc-japan'; - - # XXX-TODO: care for non Japanese. - if ($charset eq 'euc-japan') { - if ($str =~ /=\?ISO\-2022\-JP\?B\?(\S+\=*)\?=/i) { - eval q{ use MIME::Base64; }; - $str =~ s/=\?ISO\-2022\-JP\?B\?(\S+\=*)\?=/decode_base64($1)/gie; - } - - if ($str =~ /=\?ISO\-2022\-JP\?Q\?(\S+\=*)\?=/i) { - eval q{ use MIME::QuotedPrint;}; - $str =~ s/=\?ISO\-2022\-JP\?Q\?(\S+\=*)\?=/decode_qp($1)/gie; - } - } - - use Jcode; - &Jcode::convert(\$str, 'euc'); - $str; -} - - -# Descriptions: convert $str to Japanese EUC -# Arguments: STR($str) -# Side Effects: none -# Return Value: STR -sub STR2EUC -{ - my ($str) = @_; - - use Jcode; - &Jcode::convert(\$str, 'euc'); - return $str; -} - - -=head1 CODING STYLE - -See C<http://www.fml.org/software/FNF/> on fml coding style guide. - -=head1 AUTHOR - -Ken'ichi Fukamachi - -=head1 COPYRIGHT - -Copyright (C) 2001,2002,2004 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 - -Mail::ThreadTrack::Print::Utils first appeared in fml8 mailing list driver package. -See C<http://www.fml.org/> for more details. - -=cut - - -1; |
