summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfukachan <fukachan>2003-07-19 10:23:33 +0000
committerfukachan <fukachan>2003-07-19 10:23:33 +0000
commit593b7adf58b58378c1bca22a3d37026976429255 (patch)
treee3af803f44413f3d479aad746adbd0b16e06b332
parentb65d1718ac550eb4dbdef82e10fe2ac814233ad9 (diff)
parent52449fa0a4c31c0196d083b85db1c53edb2f6fb1 (diff)
downloadfml8-593b7adf58b58378c1bca22a3d37026976429255.tar.gz
fml8-593b7adf58b58378c1bca22a3d37026976429255.tar.bz2
fml8-593b7adf58b58378c1bca22a3d37026976429255.zip
merge udb branch
-rw-r--r--fml/lib/Mail/Message/DB.pm1267
-rw-r--r--fml/lib/Mail/Message/HISTORY3
-rw-r--r--fml/lib/Mail/Message/IMPLEMENTATION33
-rw-r--r--fml/lib/Mail/Message/MH.pm2
-rw-r--r--fml/lib/Mail/Message/THREAD.euc-jp102
-rw-r--r--fml/lib/Mail/Message/TODO12
-rw-r--r--fml/lib/Mail/Message/ToHTML.pm973
-rw-r--r--fml/lib/Mail/Message/Utils.pm40
8 files changed, 1715 insertions, 717 deletions
diff --git a/fml/lib/Mail/Message/DB.pm b/fml/lib/Mail/Message/DB.pm
new file mode 100644
index 00000000..a1617ce9
--- /dev/null
+++ b/fml/lib/Mail/Message/DB.pm
@@ -0,0 +1,1267 @@
+#-*- perl -*-
+#
+# Copyright (C) 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.1.2.15 2003/06/24 16:26:53 fukachan Exp $
+#
+
+package Mail::Message::DB;
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD
+ $NULL_VALUE
+ @table_list
+ @orig_header_fields @header_fields
+ %old_db_to_udb_map
+ %udb_to_old_db_map
+ %header_field_type
+ );
+use Carp;
+use File::Spec;
+
+use lib qw(../../../../fml/lib
+ ../../../../cpan/lib
+ ../../../../img/lib
+ );
+
+my $version = q$FML: DB.pm,v 1.1.2.15 2003/06/24 16:26:53 fukachan Exp $;
+if ($version =~ /,v\s+([\d\.]+)\s+/) { $version = $1;}
+
+# special value
+$NULL_VALUE = '___NULL___';
+
+my $debug = 0;
+my $is_keepalive = 1;
+my $is_demand_copying = 1;
+
+# map = { key => value } (normal order hash)
+# inv_map = { value => key } (inverted hash)
+# or
+# { value => "key key2 key3 ..." } (inverted hash)
+
+
+%header_field_type = (
+ from => 'ADDR',
+ date => 'STR',
+ subject => 'STR,MIME_DECODE',
+ to => 'ADDR_LIST',
+ cc => 'ADDR_LIST',
+ reply_to => 'ADDR_LIST',
+ message_id => 'ADDR,INVERSE_MAP',
+ references => 'ADDR_LIST',
+
+ # save info for filter system.
+ return_path => 'ADDR',
+ posted => 'STR',
+ x_posted => 'STR',
+ sender => 'ADDR',
+ x_sender => 'ADDR',
+ received => 'STR',
+ x_received => 'STR',
+ );
+
+@table_list = qw(who
+
+ inv_message_id
+
+ ref_key_list
+ next_key
+ prev_key
+
+ html_filename
+ html_filepath
+ subdir
+
+ month
+ inv_month
+
+ hint
+ );
+
+
+# db name is same unless specified.
+# OLD NEW
+# OLD .htdb_${db_name}
+# NEW ${db_name}
+%old_db_to_udb_map = qw(
+ msgidref inv_message_id
+ idref ref_key_list
+ next_id next_key
+ prev_id prev_key
+ filename html_filename
+ filepath html_filepath
+ monthly_idlist inv_month
+ thread_list undef
+ subdir subdir
+ info hint
+ );
+# reverse map
+{
+ my ($k, $v);
+ while (($k, $v) = each %old_db_to_udb_map) {
+ $udb_to_old_db_map{ $v } = $k;
+ }
+}
+
+
+=head1 NAME
+
+Mail::Message::DB - DB interface
+
+=head1 SYNOPSIS
+
+ ... lock by something ...
+
+ ... unlock by something ...
+
+This module itself provides no lock function.
+please use flock() built in perl or CPAN lock modules for it.
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 C<new($args)>
+
+ my $args = {
+ db_module => 'AnyDBM_File',
+ db_base_dir => '/var/spool/ml/@udb@/thread',
+ db_name => 'elena', # mailing list identifier
+ key => 100, # article sequence number
+ };
+
+In fml 8 case, C<table> not needs the full mail address such as
+C<elena@fml.org> since fml uses different $db_base_dir for each
+domain.
+
+For example, this module creates/updates the following databases (e.g.
+/$db_base_dir/$db_name/$table.db where $table is 'article', '
+message_id', 'sender', et.al.).
+
+ /var/spool/ml/@udb@/thread/elena/articles.db
+ /var/spool/ml/@udb@/thread/elena/date.db
+ /var/spool/ml/@udb@/thread/elena/message_id.db
+ /var/spool/ml/@udb@/thread/elena/sender.db
+ /var/spool/ml/@udb@/thread/elena/status.db
+ /var/spool/ml/@udb@/thread/elena/thread_id.db
+
+Almost all tables use $key (article sequence number) as primary key
+since it is unique in the mailing list articles.
+
+ # key => filepath
+ $article = {
+ 100 => /var/spool/ml/elena/spool/100,
+ 101 => /var/spool/ml/elena/spool/101,
+ };
+
+=cut
+
+
+# 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 = {};
+
+ # initialize DB module backend
+ set_db_module_name($me, $args->{ db_module } || 'AnyDBM_File');
+
+ # db_base_dir = /var/spool/ml/@udb@/elena
+ set_db_base_dir($me,
+ $args->{ db_base_dir } || croak("specify db_base_dir"));
+
+ # XXX in old ToHTML module (before UDB), db_base_dir == html_base_dir.
+ if (defined $args->{ old_db_base_dir }) {
+ $me->{ _old_db_base_dir } = $args->{ old_db_base_dir };
+ }
+
+ # $db_name/$table uses $key as primary key.
+ set_db_name($me, $args->{ db_name }) if defined $args->{ db_name };
+ set_key($me, $args->{ key }) if defined $args->{ key };
+
+ # genearete @orig_header_fields based on @header_fields
+ @header_fields = sort keys %header_field_type;
+ for my $hdr (@header_fields) {
+ push(@orig_header_fields, "orig_$hdr");
+ }
+
+ return bless $me, $type;
+}
+
+
+# Descriptions: destructor.
+# Arguments: OBJ($self)
+# Side Effects: close db
+# Return Value: none
+sub DESTROY
+{
+ my ($self) = @_;
+
+ _PRINT_DEBUG("DB::DESTROY");
+ if (defined $self->{ _db }) {
+ $self->db_close();
+ }
+}
+
+
+=head1 PARSE and ANALYZE
+
+=cut
+
+
+# Descriptions: update database on message header, thread relation
+# et. al.
+# Arguments: OBJ($self) OBJ($msg) HASH_REF($args)
+# Side Effects: update database
+# Return Value: none
+sub analyze
+{
+ my ($self, $msg) = @_;
+ my $hdr = $msg->whole_message_header;
+ my $id = $self->get_key();
+ my $month = $self->msg_time($hdr, 'yyyy/mm');
+ my $subdir = $self->msg_time($hdr, 'yyyymm');
+
+ _PRINT_DEBUG("analyze start");
+
+ my $db = $self->db_open();
+
+ $self->_update_max_id($db, $id);
+ $self->_save_header_info($db, $id, $hdr);
+
+ $self->_db_set($db, 'id', $id, $id); # 100 => 100
+ $self->_db_set($db, 'month', $id, $month); # 100 => 2003/06
+ $self->_db_set($db, 'subdir', $id, $subdir); # 100 => 200306
+
+ # HASH { YYYY/MM => (id1 id2 id3 ..) }
+ $self->_db_array_add($db, 'inv_month', $month, $id);
+
+ $self->_analyze_thread($db, $id, $msg, $hdr);
+
+ unless ($is_keepalive) {
+ $self->db_close();
+ }
+
+ _PRINT_DEBUG("analyze end");
+}
+
+
+# Descriptions: update $max_id in hint.
+# Arguments: OBJ($self) HASH_REF($db) NUM($id)
+# Side Effects: update hint in $db
+# Return Value: none
+sub _update_max_id
+{
+ my ($self, $db, $id) = @_;
+
+ # we should not update max_id when our target is an attachment.
+ # update max_id only under the top level operation
+ unless ($self->{ _is_attachment }) {
+ _PRINT_DEBUG("mode = parent");
+
+ my $max_id = $self->_db_get($db, 'hint', 'max_id') || 0;
+ if (defined $max_id && $max_id) {
+ my $value = $max_id < $id ? $id : $max_id;
+ $self->_db_set($db, 'hint', 'max_id', $value);
+ }
+ else {
+ $self->_db_set($db, 'hint', 'max_id', $id);
+ }
+ }
+ else {
+ _PRINT_DEBUG("mode = child");
+ }
+}
+
+
+# Descriptions: extract and format if needed header infomation
+# and save them into db.
+# Arguments: OBJ($self) HASH_REF($db) NUM($id) OBJ($hdr)
+# Side Effects: update hint in $db
+# Return Value: none
+sub _save_header_info
+{
+ my ($self, $db, $id, $hdr) = @_;
+ my ($fld, $val, @val);
+
+ # @header_fields may be overwritten. For example,
+ # key = message_id
+ # fld = message-id
+ # val = xxx@yyy.domain
+ for my $key (@header_fields) {
+ $fld = $key;
+ $fld =~ s/_/-/g;
+ $fld =~ tr/A-Z/a-z/;
+ @val = $hdr->get($fld); $val = join("", @val);
+ $val =~ s/\s*$//;
+
+ $self->_db_set($db, "orig_$key", $id, $val);
+
+ # ADDR type: save the first element of address list.
+ if ($header_field_type{ $key } =~ /ADDR/) { # ADDR or ADDR_LIST
+ my $ra_val = $self->_address_clean_up( $val );
+ $val = $ra_val->[0] || '';
+ $self->_db_set($db, $key, $id, $val);
+ }
+ elsif ($header_field_type{ $key } =~ /MIME_DECODE/) {
+ $val = $self->_decode_mime_string($val);
+ $self->_db_set($db, $key, $id, $val);
+ }
+ else {
+ $self->_db_set($db, $key, $id, $val);
+ }
+
+ # reverse map { $key => $id }
+ if ($header_field_type{ $key } =~ /INVERSE_MAP/) {
+ $self->_db_set($db, "inv_$key", $val, $id);
+ }
+ }
+
+ # what is called, "Gecos field"
+ my $who = $self->_who_of_address( $hdr->get('from') );
+ $self->_db_set($db, 'who', $id, $who); # Rudolf Shumidt
+}
+
+
+# Descriptions: analyze thread information based on In-Reply-To:
+# and References. It updates HASH_REF(ref_key_list).
+#
+# For example, "article 101" is a reply to "article 100"
+# and references: shows 101 refers 90 and 91.
+# "article 102" is a reply to 101 but without references.
+# "article 91" is a reply to 90.
+# ref_key_list = {
+# 101 => "102"
+# 100 => "101",
+# 90 => "91 101",
+# 91 => "101",
+# };
+#
+# Arguments: OBJ($self) HASH_REF($db) NUM($id) OBJ($msg) OBJ($hdr)
+# Side Effects: update db
+# Return Value: none
+sub _analyze_thread
+{
+ my ($self, $db, $id, $msg, $hdr) = @_;
+ my $current_key = $self->get_key();
+ my $ra_ref = $self->_address_clean_up($hdr->get('references'));
+ my $ra_inreplyto = $self->_address_clean_up($hdr->get('in-reply-to'));
+ my $in_reply_to = $ra_inreplyto->[0] || '';
+ my %uniq = ();
+ my $count = 0;
+
+ # ref_key_list = ( id(myself), id(in-reply-to), id's(references) );
+ $self->_db_array_add($db, 'ref_key_list', $id, $id);
+
+ # search order is artibrary (see comments above).
+ MSGID_SEARCH:
+ for my $mid (@$ra_inreplyto, @$ra_ref) {
+ next MSGID_SEARCH unless defined $mid;
+
+ # ensure uniqueness
+ next MSGID_SEARCH if $uniq{$mid};
+ $uniq{$mid} = 1;
+
+ $count++;
+
+ my $head_id = $self->_db_get($db, 'inv_message_id', $mid) || 0;
+ if ($head_id && $head_id != $current_key) {
+ $self->_db_array_add($db, 'ref_key_list', $head_id, $current_key);
+ _PRINT_DEBUG("THREAD SEARCH: $head_id => $current_key");
+ }
+ else {
+ _PRINT_DEBUG("THREAD SEARCH: NOT FOUND");
+ }
+ }
+
+ unless ($count) {
+ _PRINT_DEBUG("THREAD SEARCH: NOT TRY");
+ }
+
+ # II. ok. go to speculate prev/next links
+ # 1. If In-Reply-To: is found, use it as "pointer to previous id"
+ my $idp = 0;
+ if (defined $in_reply_to) {
+ # XXX idp (id pointer) = id1 by _head_of_list_str( (id1 id2 id3 ...)
+ $idp = $self->_db_get($db, 'inv_message_id', $in_reply_to);
+ }
+ # 2. if not found, try to use References: "in reverse order"
+ elsif (@$ra_ref) {
+ my (@rra) = reverse(@$ra_ref);
+ $idp = $rra[0] || 0;
+ }
+ # 3. no link to previous one found
+ else {
+ $idp = 0;
+ }
+
+ # 4. if $idp (link to previous message) found,
+ if (defined($idp) && $idp && $idp =~ /^\d+$/) {
+ if ($idp != $current_key) {
+ $self->_db_set($db, 'prev_key', $current_key, $idp);
+ }
+
+ # We should not overwrite "id => next_key" assinged already.
+ # We should preserve the first "id => next_key" value.
+ # but we may overwride it if "id => id (itself)", wrong link.
+ my $nid = $self->_db_get($db, 'next_key', $idp) || 0;
+ unless ($nid && $nid != $idp && $current_key != $idp) {
+ $self->_db_set($db, 'next_key', $idp, $current_key);
+ }
+ }
+ else {
+ _PRINT_DEBUG("no prev thread link (key=$current_key)");
+ }
+}
+
+
+=head1 SUMMARY
+
+retrieve summary on thread et.al.
+
+=head2 thread_summary($id).
+
+return the following thread summary around the primary key $id.
+
+ my $summary = {
+ prev_id => $prev_id,
+ next_id => $next_id,
+ prev_thread_id => $prev_thread_id,
+ next_thread_id => $next_thread_id,
+ };
+
+For example, supporse $id 5 and the thread link is (3 5 6):
+
+ my $summary = {
+ prev_id => 4,
+ next_id => 6,
+ prev_thread_id => 3,
+ next_thread_id => 6,
+ };
+
+=cut
+
+
+# Descriptions: return thread summary around key $id.
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: HASH_REF
+sub thread_summary
+{
+ my ($self, $id) = @_;
+ my ($fn_prev_id, $fn_next_id, $fn_prev_thread_id,
+ $fn_next_thread_id, $fp_prev_id, $fp_next_id,
+ $fp_prev_thread_id, $fp_next_thread_id);
+ my $db = $self->db_open();
+ my $prev_id = $id > 1 ? $id - 1 : undef;
+ my $next_id = $id + 1;
+ my $prev_thread_id = $self->_db_get($db, 'prev_key', $id) || $prev_id;
+ my $next_thread_id = $self->_db_get($db, 'next_key', $id) || $next_id;
+
+ # diagnostic
+ if (defined $prev_thread_id && $prev_thread_id) {
+ undef $prev_thread_id if $prev_thread_id == $id;
+ }
+ if (defined $next_thread_id && $next_thread_id) {
+ undef $next_thread_id if $next_thread_id == $id;
+ }
+
+ # file name (fn_*) and file path (fp_*)
+ if (defined $prev_id) {
+ $fn_prev_id = $self->_db_get($db, 'html_filename', $prev_id);
+ $fp_prev_id = $self->_db_get($db, 'html_filepath', $prev_id);
+ }
+
+ if (defined $next_id) {
+ $fn_next_id = $self->_db_get($db, 'html_filename', $next_id);
+ $fp_next_id = $self->_db_get($db, 'html_filepath', $next_id);
+
+ unless (-f $fp_next_id) {
+ undef $next_id;
+ $fp_next_id = '';
+ $fn_next_id = '';
+ }
+ }
+
+ if (defined $prev_thread_id) {
+ $fn_prev_thread_id =
+ $self->_db_get($db, 'html_filename', $prev_thread_id);
+ $fp_prev_thread_id =
+ $self->_db_get($db, 'html_filepath', $prev_thread_id);
+ }
+
+ if (defined $next_thread_id) {
+ $fn_next_thread_id =
+ $self->_db_get($db, 'html_filename', $next_thread_id);
+ $fp_next_thread_id =
+ $self->_db_get($db, 'html_filepath', $next_thread_id);
+
+ unless(-f $fp_next_thread_id) {
+ undef $next_thread_id;
+ $fn_next_thread_id = '';
+ $fp_next_thread_id = '';
+ }
+ }
+
+ # XXX this routine returns information expected straight forwardly, so
+ # XXX $summary may be invalid since $next*id not yet exists.
+ # XXX we expect the program calling this method validates this info.
+ # XXX For examle, check the existence of msg${next_id}.html before use.
+ my $summary = {
+ id => $id,
+
+ prev_id => $prev_id,
+ next_id => $next_id,
+ prev_thread_id => $prev_thread_id,
+ next_thread_id => $next_thread_id,
+
+ # file relative path info
+ html_filename_prev_id => $fn_prev_id,
+ html_filename_next_id => $fn_next_id,
+ html_filename_prev_thread_id => $fn_prev_thread_id,
+ html_filename_next_thread_id => $fn_next_thread_id,
+
+ # file (full)path info
+ html_filepath_prev_id => $fp_prev_id,
+ html_filepath_next_id => $fp_next_id,
+ html_filepath_prev_thread_id => $fp_prev_thread_id,
+ html_filepath_next_thread_id => $fp_next_thread_id,
+ };
+
+ return $summary;
+}
+
+
+# Descriptions: return thread summary around key $id.
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: HASH_REF
+sub tohtml_thread_summary
+{
+ my ($self, $id) = @_;
+ my $db = $self->db_open();
+ my $summary = $self->thread_summary($id);
+ my $prev_id = $summary->{ prev_id };
+ my $next_id = $summary->{ next_id };
+ my $prev_thread_id = $summary->{ prev_thread_id };
+ my $next_thread_id = $summary->{ next_thread_id };
+
+ #
+ # XXX-TODO: we should get back this method to Mail::Message::ToHTML ?
+ #
+
+ unless (defined $next_thread_id || $next_thread_id) {
+ my $xid = $self->_search_default_next_thread_id($db, $id);
+ if ($xid && ($xid != $id)) {
+ $next_thread_id = $xid;
+ _PRINT_DEBUG("override next_thread_id = $next_thread_id");
+ }
+ }
+
+ my $subject = {};
+ if (defined $prev_id && $prev_id) {
+ $subject->{ prev_id } = $self->_db_get($db, 'subject', $prev_id);
+ }
+ if (defined $next_id && $next_id) {
+ $subject->{ next_id } = $self->_db_get($db, 'subject', $next_id);
+ }
+ if (defined $prev_thread_id && $prev_thread_id) {
+ $subject->{ prev_thread_id } =
+ $self->_db_get($db, 'subject', $prev_thread_id);
+ }
+ if (defined $next_thread_id && $next_thread_id) {
+ $subject->{ next_thread_id } =
+ $self->_db_get($db, 'subject', $next_thread_id);
+ }
+
+ # filename (relative file path)
+ my $fn_prev_id = $summary->{ html_filename_prev_id };
+ my $fn_next_id = $summary->{ html_filename_next_id };
+ my $fn_prev_thread_id = $summary->{ html_filename_prev_thread_id };
+ my $fn_next_thread_id = $summary->{ html_filename_next_thread_id };
+ my $path = $self->_db_get($db, 'html_filepath', $id);
+ my $tohtml_thread_summary = {
+ # myself
+ id => $id,
+ filepath => $path,
+
+ # other links
+ prev_id => $prev_id,
+ next_id => $next_id,
+ prev_thread_id => $prev_thread_id,
+ next_thread_id => $next_thread_id,
+
+ link_prev_id => $fn_prev_id,
+ link_next_id => $fn_next_id,
+ link_prev_thread_id => $fn_prev_thread_id,
+ link_next_thread_id => $fn_next_thread_id,
+
+ subject => $subject,
+ };
+
+ _PRINT_DEBUG("$id link relation");
+ _PRINT_DEBUG_DUMP_HASH( $tohtml_thread_summary );
+ return $tohtml_thread_summary;
+}
+
+
+# Descriptions: speculate head of the next thread list.
+# Arguments: HASH_REF($db) STR($id)
+# Side Effects: none
+# Return Value: STR
+sub _search_default_next_thread_id
+{
+ my ($self, $db, $id) = @_;
+ my (@ra, @c0) = ();
+ my $list = $self->get_as_array_ref('ref_key_list', $id);
+
+ # 1. @ra (id list for $id thread relations)
+ @ra = reverse @$list if defined $list;
+
+ # 2. @c0 (gamble :-)
+ for my $_id (1 .. 10) { push(@c0, $id + $_id);}
+
+ # prepare thread list to search
+ # 1. thread includes $id
+ # 2. thread(s) begining at each id in thread 1.
+ # 3. last resort: thread includes ($id+1),
+ # thread includes ($id+2), ...
+ for my $xid ($id, @ra, @c0) {
+ my $default = $self->__search_default_next_id_in_thread($db, $xid);
+ return $default if defined $default;
+ }
+
+ return 0;
+}
+
+
+# Descriptions: speculate the next id of $id.
+# Arguments: HASH_REF($db) STR($id)
+# Side Effects: none
+# Return Value: STR
+sub __search_default_next_id_in_thread
+{
+ my ($self, $db, $id) = @_;
+ my $list = [];
+ my $prev = 0;
+
+ _PRINT_DEBUG("__search_default_next_id_in_thread($id)");
+
+ # thread_list HASH { $id => $id1 $id2 $id3 ... }
+ $list = $self->get_as_array_ref('ref_key_list', $id);
+ if (@$list) {
+ return undef unless $#$list > 1;
+
+ # thread_list HASH { $id => $id1 $id2 $id3 ... $id $prev ... }
+ # <---- search ---
+ SEARCH:
+ for my $xid (reverse @$list) {
+ last SEARCH if $xid == $id;
+ $prev = $xid;
+ }
+ }
+
+ # found
+ # XXX we use $prev in reverse order, so this $prev means "next"
+ if ($prev > 0) {
+ _PRINT_DEBUG("default thread: $id => $prev (@$list)");
+ return $prev;
+ }
+ else {
+ _PRINT_DEBUG("default thread: $id => none (@$list)");
+ return undef;
+ }
+
+ return undef;
+}
+
+
+=head1 UTILITY FUNCTIONS
+
+All methods are module internal.
+
+=cut
+
+
+# Descriptions: convert space-separeted string to array
+# Arguments: STR($str)
+# Side Effects: none
+# Return Value: ARRAY_REF
+sub _str_to_array_ref
+{
+ my ($str) = @_;
+
+ return undef unless defined $str;
+
+ $str =~ s/^\s*//;
+ $str =~ s/\s*$//;
+ my (@a) = split(/\s+/, $str);
+ return \@a;
+}
+
+
+# Descriptions: add { key => value } into $table with converting
+# where value is "x y z ..." form, space separated string.
+# Arguments: HASH_REF($db) STR($dbname) STR($key) STR($value)
+# Side Effects: update database
+# Return Value: none
+sub _db_array_add
+{
+ my ($self, $db, $table, $key, $value) = @_;
+ my $found = 0;
+ my $ra = $self->get_as_array_ref($table, $key) || [];
+
+ _PRINT_DEBUG("ARRAY: table=$table key=$key, add '$value' into (@$ra)");
+
+ if (defined($key) && $key && defined($value) && $value) {
+ # check duplication to ensure uniqueness within this array.
+ for my $v (@$ra) {
+ $found = 1 if ($value =~ /^\d+$/o) && ($v == $value);
+ $found = 1 if ($value !~ /^\d+$/o) && ($v eq $value);
+ }
+
+ # add if the value is a new comer.
+ unless ($found) {
+ my $v = $self->_db_get($db, $table, $key) || '';
+ $v .= $v ? " $value" : $value;
+ $self->_db_set($db, $table, $key, $v);
+ }
+ }
+ else {
+ _PRINT_DEBUG("ARRAY: fail to add");
+ }
+}
+
+
+# Descriptions: head of array (space separeted string)
+# Arguments: STR($buf)
+# Side Effects: none
+# Return Value: STR
+sub _head_of_list_str
+{
+ my ($buf) = @_;
+ $buf =~ s/^\s*//;
+ $buf =~ s/\s*$//;
+
+ return (split(/\s+/, $buf))[0];
+}
+
+
+# Descriptions: decode mime string.
+# Arguments: OBJ($self) STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+sub _decode_mime_string
+{
+ my ($self, $str, $out_code, $in_code) = @_;
+
+ use Mail::Message::Encode;
+ my $encode = new Mail::Message::Encode;
+ return $encode->decode_mime_string($str, $out_code, $in_code);
+}
+
+
+# Descriptions: return formated time of message Date:
+# Arguments: OBJ($self) STR($type)
+# Side Effects: none
+# Return Value: STR
+sub msg_time
+{
+ my ($self, $hdr, $type) = @_;
+
+ if (defined($hdr) && $hdr->get('date')) {
+ use Time::ParseDate;
+ my $unixtime = parsedate( $hdr->get('date') );
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime( $unixtime );
+
+ if ($type eq 'yyyymm') {
+ return sprintf("%04d%02d", 1900 + $year, $mon + 1);
+ }
+ elsif ($type eq 'yyyy/mm') {
+ return sprintf("%04d/%02d", 1900 + $year, $mon + 1);
+ }
+ }
+ else {
+ warn("cannot pick up Date: field");
+ return '';
+ }
+}
+
+
+# Descriptions: clean up email address by Mail::Address.
+# return clean-up'ed address list.
+# Arguments: STR($addr)
+# Side Effects: none
+# Return Value: ARRAY_REF
+sub _address_clean_up
+{
+ my ($self, $addr) = @_;
+ my (@r);
+
+ use Mail::Address;
+ my (@addrs) = Mail::Address->parse($addr);
+
+ my $i = 0;
+ LIST:
+ for my $addr (@addrs) {
+ my $xaddr = $addr->address();
+ next LIST unless $xaddr =~ /\@/;
+ push(@r, $xaddr);
+ }
+
+ return \@r;
+}
+
+
+# Descriptions: extrace gecos field in $address
+# Arguments: OBJ($self) STR($address)
+# Side Effects: none
+# Return Value: STR
+sub _who_of_address
+{
+ my ($self, $address) = @_;
+
+ use Mail::Message::Utils;
+ return Mail::Message::Utils::from_to_name($address);
+}
+
+
+=head1 DATABASE PARAMETERS MANIPULATION
+
+=cut
+
+
+# Descriptions: open database
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: tied with $self->{ _db }
+# Todo: we should use IO::Adapter ?
+# Return Value: none
+sub db_open
+{
+ my ($self, $args) = @_;
+
+ return $self->{ _db } if defined $self->{ _db };
+
+ my $db_type = $self->get_db_module_name();
+ my $db_dir = $self->get_db_base_dir();
+ my $file_mode = $self->{ _file_mode } || 0644;
+
+ _PRINT_DEBUG("db_open( type = $db_type )");
+
+ eval qq{ use $db_type; use Fcntl;};
+ unless ($@) {
+ my ($file, $str);
+ for my $db (@orig_header_fields, @header_fields, @table_list) {
+ $file = File::Spec->catfile($db_dir, $db);
+ $str = qq{
+ my \%$db = ();
+ tie \%$db, \$db_type, \$file, O_RDWR|O_CREAT, $file_mode;
+ \$self->{ _db }->{ '_$db' } = \\\%$db;
+ };
+ eval $str;
+ croak($@) if $@;
+ }
+ }
+ else {
+ croak("cannot use $db_type");
+ }
+
+ $self->{ _db } || undef;
+}
+
+
+# Descriptions: close database
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: untie $self->{ _db }
+# Todo: we should use IO::Adapter ?
+# Return Value: none
+sub db_close
+{
+ my ($self, $args) = @_;
+ my $db_type = $args->{ db_type } || $self->{ _db_type } || 'AnyDBM_File';
+ my $db_dir = $self->{ _html_base_directory };
+
+ _PRINT_DEBUG("db_close()");
+
+ for my $db (@orig_header_fields, @header_fields, @table_list) {
+ my $str = qq{
+ my \$${db} = \$self->{ _db }->{ '_$db' };
+ untie \%\$${db};
+ };
+ eval $str;
+ croak($@) if $@;
+ }
+
+ delete $self->{ _db } if defined $self->{ _db };
+}
+
+
+# Descriptions: set $key = $value of $table
+# Arguments: OBJ($self) STR($table) STR($key) STR($value)
+# Side Effects: one
+# Return Value: STR
+sub set
+{
+ my ($self, $table, $key, $value) = @_;
+ my $db = $self->db_open();
+
+ _PRINT_DEBUG("_db_set: table=$table { $key => $value }");
+
+ $self->_db_set($db, $table, $key, $value);
+}
+
+
+# Descriptions: get $key of $table
+# Arguments: OBJ($self) STR($table) STR($key)
+# Side Effects: one
+# Return Value: STR
+sub get
+{
+ my ($self, $table, $key) = @_;
+ my $db = $self->db_open();
+
+ return $self->_db_get($db, $table, $key);
+}
+
+
+# Descriptions: get $key (list) of $table as array (ARRAY_REF)
+# Arguments: OBJ($self) STR($table) STR($key)
+# Side Effects: one
+# Return Value: ARRAY_REF
+sub get_as_array_ref
+{
+ my ($self, $table, $key) = @_;
+
+ _PRINT_DEBUG("get_as_array_ref($table, $key)");
+
+ my $db = $self->db_open();
+ my $val = $self->_db_get($db, $table, $key);
+ $val =~ s/^\s*//;
+ $val =~ s/\s*$//;
+
+ _PRINT_DEBUG("get_as_array_ref($table, $key, '$val')");
+
+ my (@x) = split(/\s+/, $val);
+
+ _PRINT_DEBUG("return(@x)");
+ return( \@x );
+}
+
+
+# Descriptions: set $key = $value of $table
+# Arguments: OBJ($self) HASH_REF($db) STR($table) STR($key) STR($value)
+# Side Effects: one
+# Return Value: STR
+sub _db_set
+{
+ my ($self, $db, $table, $key, $value) = @_;
+
+ if (defined $value && $value) {
+ _PRINT_DEBUG("_db_set: table=$table { $key => $value }");
+ $db->{ "_$table" }->{ $key } = $value;
+ }
+}
+
+
+# Descriptions: get $key of $table
+# Arguments: OBJ($self) HASH_REF($db) STR($table) STR($key)
+# Side Effects: one
+# Return Value: STR
+sub _db_get
+{
+ my ($self, $db, $table, $key) = @_;
+ my $v = $db->{ "_$table" }->{ $key } || '';
+
+ if ($v eq $NULL_VALUE) {
+ return '';
+ }
+
+ unless ($v) {
+ if ($is_demand_copying) {
+ _PRINT_DEBUG("_old_db_copyin(\$db, $table, $key)");
+ $self->_old_db_copyin($db, $table, $key);
+ }
+ $v = $db->{ "_$table" }->{ $key } || '';
+
+ unless ($v) {
+ $db->{ "_$table" }->{ $key } = $NULL_VALUE;
+ }
+ }
+
+ if ($v eq $NULL_VALUE) {
+ return '';
+ }
+
+ return $v;
+}
+
+
+# Descriptions: set module name
+# Arguments: OBJ($self) STR($module)
+# Side Effects: none
+# Return Value: none
+sub set_db_module_name
+{
+ my ($self, $module) = @_;
+
+ $self->{ _db_module } = $module if defined $module;
+}
+
+
+# Descriptions: get module name
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: STR
+sub get_db_module_name
+{
+ my ($self) = @_;
+
+ return( $self->{ _db_module } || undef );
+}
+
+
+# Descriptions: set db_base_dir
+# Arguments: OBJ($self) STR($dir)
+# Side Effects: none
+# Return Value: none
+sub set_db_base_dir
+{
+ my ($self, $dir) = @_;
+
+ $self->{ _db_base_dir } = $dir if defined $dir;
+}
+
+
+# Descriptions: get db_base_dir
+# Arguments: OBJ($self) STR($dir)
+# Side Effects: none
+# Return Value: none
+sub get_db_base_dir
+{
+ my ($self) = @_;
+
+ return( $self->{ _db_base_dir } || undef );
+}
+
+
+# Descriptions: set db_name
+# Arguments: OBJ($self) STR($name)
+# Side Effects: none
+# Return Value: none
+sub set_db_name
+{
+ my ($self, $name) = @_;
+
+ $self->{ _db_name } = $name if defined $name;
+}
+
+
+# Descriptions: get db_name
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: STR
+sub get_db_name
+{
+ my ($self) = @_;
+
+ return( $self->{ _db_name } || undef );
+}
+
+
+# Descriptions: set the curent key
+# Arguments: OBJ($self) STR($name)
+# Side Effects: none
+# Return Value: none
+sub set_key
+{
+ my ($self, $key) = @_;
+
+ $self->{ _key } = $key if defined $key;
+}
+
+
+# Descriptions: get the curent key
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: none
+sub get_key
+{
+ my ($self) = @_;
+
+ return( $self->{ _key } || undef );
+}
+
+
+=head1 HANDLE TABLE
+
+=cut
+
+
+# Descriptions: get table as hash ref to handle it as HASH_REF
+# Arguments: OBJ($self) STR($table)
+# Side Effects: none
+# Return Value: HASH_REF
+sub get_table_as_hash_ref
+{
+ my ($self, $table) = @_;
+ my $db = $self->db_open();
+
+ return( $db->{ "_$table" } || {} );
+}
+
+
+=head1 HANDLE OLD DB by DEMAND COPYING
+
+=cut
+
+
+# Descriptions: copy a part of $table from old db.
+# Arguments: OBJ($self) HASH_REF($db) STR($table) STR($key)
+# Side Effects: one
+# Return Value: STR
+sub _old_db_copyin
+{
+ my ($self, $db, $table, $key) = @_;
+ my (%old_db);
+ my $db_type = $self->get_db_module_name();
+ my $db_dir = $self->get_db_base_dir();
+ my $file_mode = $self->{ _file_mode } || 0644;
+ my $old_db_dir = $self->{ _old_db_base_dir };
+ my $_table = $udb_to_old_db_map{ $table } || $table;
+ my $file = File::Spec->catfile($old_db_dir, ".htdb_${_table}");
+ my $file_db = File::Spec->catfile($old_db_dir, ".htdb_${_table}.db");
+ my $file_pag = File::Spec->catfile($old_db_dir, ".htdb_${_table}.pag");
+
+ if (-f $file_db || -f $file_pag) {
+ eval qq{ use $db_type; use Fcntl;};
+ unless ($@) {
+ eval q{
+ tie %old_db, $db_type, $file, O_RDWR|O_CREAT, $file_mode;
+ };
+ croak($@) if $@;
+ }
+ else {
+ croak("cannot use $db_type");
+ }
+
+ if ($key =~ /^\d+$/o) {
+ my $value = $old_db{ $key } || $NULL_VALUE;
+ $self->_db_set($db, $table, $key, $value);
+
+ my $start = $key - 25 > 0 ? $key - 25 : 1;
+ my $end = $key + 25;
+ _PRINT_DEBUG("copy ($start .. $end) into $table from $file");
+ for my $i ($start .. $end) {
+ $self->_db_set($db, $table, $i, $old_db{ $i } || $NULL_VALUE);
+ }
+ }
+ # we may need to copy all contents
+ else {
+ my $value = $old_db{ $key } || $NULL_VALUE;
+ $self->_db_set($db, $table, $key, $value);
+
+ _PRINT_DEBUG("all copy into $table from $file");
+ my ($k, $v);
+ while (($k, $v) = each %old_db) {
+ $self->_db_set($db, $table, $k, $v || $NULL_VALUE);
+ }
+ }
+
+ eval q{ untie %old_db; };
+ }
+ else {
+ _PRINT_DEBUG("$file not found");
+ }
+}
+
+
+=head1 DEBUG
+
+=cut
+
+# Descriptions: debug
+# Arguments: STR($str)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG
+{
+ my ($str) = @_;
+ print STDERR "(debug) $str\n" if $debug;
+}
+
+
+# Descriptions: debug, print out hash
+# Arguments: HASH_REF($hash)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG_DUMP_HASH
+{
+ my ($hash) = @_;
+ my ($k,$v);
+
+ if ($debug) {
+ while (($k, $v) = each %$hash) {
+ printf STDERR "(debug) %-30s => %s\n", $k, $v;
+ }
+ }
+}
+
+
+#
+# DEBUG
+#
+if ($0 eq __FILE__) {
+ my $args = {
+ db_module => 'AnyDBM_File',
+ db_base_dir => '/tmp',
+ db_name => 'elena', # mailing list identifier
+ key => 100, # article sequence number
+ };
+
+ my $obj = new Mail::Message::DB $args;
+
+ for my $file (@ARGV) {
+ use File::Basename;
+ my $id = basename($file);
+
+ use Mail::Message;
+ my $msg = Mail::Message->parse( { file => $file } );
+ $obj->set_key($id);
+ $obj->analyze($msg);
+ }
+}
+
+
+=head1 TODO
+
+=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) 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::Message::DB first appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+This class is renamed from C<Mail::HTML::Lite> 1.40 (2001-2002).
+
+=cut
+
+
+1;
diff --git a/fml/lib/Mail/Message/HISTORY b/fml/lib/Mail/Message/HISTORY
new file mode 100644
index 00000000..f8153124
--- /dev/null
+++ b/fml/lib/Mail/Message/HISTORY
@@ -0,0 +1,3 @@
+Sat Jul 19 19:21:18 JST 2003
+
+ merge udb branch into -current
diff --git a/fml/lib/Mail/Message/IMPLEMENTATION b/fml/lib/Mail/Message/IMPLEMENTATION
new file mode 100644
index 00000000..cbec2ce7
--- /dev/null
+++ b/fml/lib/Mail/Message/IMPLEMENTATION
@@ -0,0 +1,33 @@
+ Mail::Message::DB
+ new Mail::Message::DB { ml_name => , seq => }
+ add( $msg )
+ get($table, $key)
+ set($table, $key, 4value)
+
+ thread_summary($key)
+
+ $msg ---> Mail::Message::ToHTML
+ | A
+ | |
+ new() | |
+ add($msg) | | thread_summary()
+ set() | | get()
+ | |
+ V |
+ Mail::Message::DB =========== tie() ===> AnyDBM
+ A + update .db files via tie()
+ add($msg) | + thread analyze
+ |
+ | +===============================> cache with time limit
+ | ||
+ | ||
+ FML::UDB ? << unified cache database interface >>
+ A
+ |
+ FML::*::* modules
+
+
+
+ $msg Mail::Message OBJECT
+ ===> method using tie()
+ ---> method not Tie::*
diff --git a/fml/lib/Mail/Message/MH.pm b/fml/lib/Mail/Message/MH.pm
index a4cb3f82..6f4e6a46 100644
--- a/fml/lib/Mail/Message/MH.pm
+++ b/fml/lib/Mail/Message/MH.pm
@@ -2,7 +2,7 @@
#
# Copyright (C) 2001,2002,2003 Ken'ichi Fukamachi
#
-# $FML: MH.pm,v 1.13 2003/01/26 03:23:10 fukachan Exp $
+# $FML$
#
package Mail::Message::MH;
diff --git a/fml/lib/Mail/Message/THREAD.euc-jp b/fml/lib/Mail/Message/THREAD.euc-jp
new file mode 100644
index 00000000..9fde8432
--- /dev/null
+++ b/fml/lib/Mail/Message/THREAD.euc-jp
@@ -0,0 +1,102 @@
+ ¥¹¥ì¥Ã¥É¤Ë¤Ä¤¤¤Æ
+
+
+¡û Mail::Message::DB::_analyze_thread()
+
+¥¹¥ì¥Ã¥É¤Î°Í¸´Ø·¸¤òÄ´¤Ù¤ë½èÍý¤Ï _thread_analyze() ¤¬¿¿¿ñ¤Î°ì¤Ä¤Ç¤¹¡£
+¤³¤Î´Ø¿ô¤Ï
+
+ HASH_REF(ref_key_list)
+
+ doubly linked list
+ HASH_REF(prev_key)
+ HASH_REF(next_key)
+
+¤ò¹¹¿·¤·¤Æ¤¤¤Þ¤¹¡£prev_key ¤È next_key ¤ÏºÇ¤â»²¾È´Ø·¸¤Î¶á¤¤µ­»öƱ»Î¤Î
+Áê¸ß¥ê¥ó¥¯¤òɽ¤·¤Æ¤¤¤Þ¤¹¡£
+
+¤µ¤Æ¡¢HASH_REF(ref_key_list) ¤Ï¡¢¼¡¤Î¤è¤¦¤Ë¹¹¿·¤µ¤ì¤Þ¤¹¡£
+
+ref_key_list ¤Ë¼«Ê¬¤Î ID ¤òÀßÄꤹ¤ë( { id => id } )¡£
+
+for $message_id (In-Reply-To ¤È References ¤Ë¤Õ¤¯¤Þ¤ì¤ëID) {
+
+ inv_message_id hash
+ $message_id ¤«¤é»²¾È¤µ¤ì¤Æ¤¤¤ë ID ¤ò³ä½Ð¤¹
+
+ ref_key_list->{ »²¾È¤µ¤ì¤Æ¤¤¤ëID } ¤Ë¸½µ­»ö¤Î ID ¤òÄɲÃ
+
+}
+
+¤¿¤È¤¨¤Ð¡¢µ­»ö101¤ò½èÍý¤·¤Æ¤¤¤ë¤È¤·¡¢µ­»ö101¤¬100¤Ø¤Î¥Õ¥©¥í¡¼¤À¤È¤·¤Þ
+¤·¤ç¤¦¡£¤³¤Î¾ì¹ç¡¢next_key ¤Ï { 100 => 101 } ¤Ç¤¢¤ê¡¢prev_key ¤Ï { 101
+=> 100 } ¤È¤Ê¤ê¤Þ¤¹¡£
+
+¤µ¤é¤Ë¡¢101¤Ï90¤È91¤â»²¾È¤»¤è¤È»Ø¼¨¤·¤Æ¤¤¤ë¤È¤·¤Þ¤¹¡£¤Þ¤¿¡¢90¤¬¥¹¥ì¥Ã
+¥É¤ÎÀèÆ¬¤Ç¡¢91¤Ï90¤Ø¤Î¥Õ¥©¥í¡¼¤À¤È¤·¤Þ¤·¤ç¤¦¡£¤³¤Î¾ì¹ç¡¢½é´ü¾õÂ֤ϼ¡¤Î
+¤è¤¦¤Ë¤Ê¤Ã¤Æ¤¤¤Þ¤¹¡£
+
+ref_key_list = {
+ 90 => 91,
+};
+
+101¤Î½èÍý¤Ï¼¡¤Î¤è¤¦¤Ê¥ë¡¼¥×¤Ç¤¹¡£
+
+for $message_id (100¤Îmid, 90¤Îmid, 91¤Îmid) {
+ $inv_message_id{ 100¤Îmid } => 100
+ ref_key_list ¤ØÄɲÃ
+}
+
+·ë²Ì¤È¤·¤Æ
+
+ref_key_list = {
+ 100 => 101,
+ 90 => "91 101",
+ 91 => 101,
+};
+
+¼¡¤Ë¡¢µ­»ö102¤¬101¤Ø¤Î¥Õ¥©¥í¡¼¤Ê¤é¡¢ref_key_list ¤Ï¼¡¤Î¤è¤¦¤Ë¤Ê¤ë¤Ç¤·¤ç
+¤¦¡£
+
+ref_key_list = {
+ 101 => 102,
+ 100 => "101 102",
+ 90 => "91 101 102",
+ 91 => "101 102",
+};
+
+¤Ê¤ª¡¢°Ê¾å¤Î¤è¤¦¤Ê½èÍý¤Î¤¿¤á¡¢
+
+ for $message_id (In-Reply-To ¤È References ¤Ë¤Õ¤¯¤Þ¤ì¤ëID) {
+
+¤Î½èÍý¤Î½çÈ֤Ϥɤ¦¤Ç¤â¤¤¤¤¤Ï¤º¤Ç¤¹¡£
+
+
+
+
+¡û Mail::Message::ToTHML::_print_thread()
+
+¤³¤ì¤¬¼ÂºÝ¤Ë¥¹¥ì¥Ã¥É¤Î°Í¸´Ø·¸¤ò HTML ²½¤¹¤ë´Ø¿ô¤Ç¤¹¡£
+
+³µÇ°Åª¤Ë¤Ï
+
+ _print_thread() {
+
+ for (ref_key_list id ¤Î¥ê¥¹¥È) {
+ if (¥ê¥¹¥È¤ÎÃæ¿È¤¬Ê£¿ô) {
+ # ¤¢¤È¤Ç»È¤¦¤¿¤á¤Ë¥­¥ã¥Ã¥·¥å¤·¤Æ¤ª¤¯
+ $self->{ _thread_list }->{ $id } = $buf;
+ }
+
+ print_ul()
+ print_li()
+ _print_thread() (recursive call)
+ }
+
+ print_ul_end() # recursive ¤Î²ó¿ô¤À¤± </UL> ¤ò½ÐÎϤ¹¤ë
+ }
+
+
+ [TODO]
+ $self->{ _thread_list } ¤Ï¥ª¥ó¥á¥â¥ê¤À¤±¤É¡¢
+ ¼¡²ó¤Î½èÍý¤Î¤¿¤á¤Ë db ¤Ë½ñ¤¤¤Æ¤ª¤¯¤Ù¤­¤¸¤ã¤Ê¤¤¤Î¤«¡©
diff --git a/fml/lib/Mail/Message/TODO b/fml/lib/Mail/Message/TODO
new file mode 100644
index 00000000..3edafc5d
--- /dev/null
+++ b/fml/lib/Mail/Message/TODO
@@ -0,0 +1,12 @@
+o msg_time() ¤Ï¡¢Utils ¤Ø¡©
+
+
+o set_article_file_path() / get_article_file_path() ?
+
+
+o set_article_html_file_path() / get_article_html_file_path() ?
+
+
+ # need month database to determine subdir for the html file
+ $db->{ _filename }->{ $id } = $self->html_filename($id);
+ $db->{ _filepath }->{ $id } = $dst;
diff --git a/fml/lib/Mail/Message/ToHTML.pm b/fml/lib/Mail/Message/ToHTML.pm
index c95641be..05ef7fd8 100644
--- a/fml/lib/Mail/Message/ToHTML.pm
+++ b/fml/lib/Mail/Message/ToHTML.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: ToHTML.pm,v 1.42 2003/06/23 10:09:14 tmu Exp $
+# $FML$
#
package Mail::Message::ToHTML;
@@ -17,7 +17,7 @@ my $debug = 0;
my $URL =
"<A HREF=\"http://www.fml.org/software/\">Mail::Message::ToHTML</A>";
-my $version = q$FML: ToHTML.pm,v 1.42 2003/06/23 10:09:14 tmu Exp $;
+my $version = q$FML$;
if ($version =~ /,v\s+([\d\.]+)\s+/) {
$version = "$URL $1";
}
@@ -92,23 +92,49 @@ stored.
sub new
{
my ($self, $args) = @_;
- my ($type) = ref($self) || $self;
- my $me = {};
+ my ($type) = ref($self) || $self;
+ my $me = {};
$me->{ _html_base_directory } = $args->{ directory };
$me->{ _charset } = $args->{ charset } || 'us-ascii';
$me->{ _is_attachment } = defined($args->{ attachment }) ? 1 : 0;
$me->{ _db_type } = $args->{ db_type };
+ $me->{ _db_name } = $args->{ db_name };
+ $me->{ _db_base_dir } = $args->{ db_base_dir };
$me->{ _args } = $args;
$me->{ _num_attachment } = 0; # for child process
$me->{ _use_subdir } = 'yes';
$me->{ _subdir_style } = 'yyyymm';
$me->{ _html_id_order } = $args->{ index_order } || 'normal';
+ my $db_type = $me->{ _db_type };
+ my $db_base = $me->{ _db_base_dir } || croak("specify db_base_dir\n");
+ my $db_name = $me->{ _db_name } || croak("specify db_name\n");
+ my $_args = {
+ db_module => $db_type,
+ db_base_dir => $db_base,
+ db_name => $db_name, # mailing list identifier
+
+ # db non UDB
+ old_db_base_dir => $args->{ directory },
+ };
+
+ # Firstly, prepare db object.
+ use Mail::Message::DB;
+ my $ndb = new Mail::Message::DB $_args;
+ $me->{ _ndb } = $ndb;
+
return bless $me, $type;
}
+sub DESTROY
+{
+ _PRINT_DEBUG("ToHTML::DESTROY");
+ 1;
+}
+
+
=head2 C<htmlfy_rfc822_message($args)>
convert mail to html.
@@ -149,8 +175,8 @@ sub htmlfy_rfc822_message
# $id = article id
# $src = source file
# $dst = destination file (target html)
- my ($id, $src, $dst) = $self->_init_htmlfy_rfc822_message($args);
- $self->{ _current_id } = $id;
+ my ($id, $src, $dst) = $self->_init_htmlfy_rfc822_message($args);
+ $self->{ _debug_id } = $id;
# target html exists already.
if (-f $dst) {
@@ -159,11 +185,8 @@ sub htmlfy_rfc822_message
return undef;
}
- # hints
- $self->{ _hints }->{ src }->{ filepath } = $src;
-
# save information for index.html and thread.html
- $self->cache_message_info($msg, { id => $id,
+ $self->cache_message_info($msg, { id => $id,
src => $src,
dst => $dst,
} );
@@ -337,11 +360,10 @@ sub html_filename
my ($self, $id) = @_;
my $use_subdir = $self->{ _use_subdir };
+ # relative path under html_base_dir
if (defined($id) && ($id > 0)) {
if ($use_subdir eq 'yes') {
- my $r = $self->_html_file_subdir_name($id);
- # print STDERR "xdebug: $id => $r\n";
- return $r;
+ return $self->_html_file_subdir_name($id);
}
else {
return "msg${id}.html";
@@ -360,43 +382,35 @@ sub html_filename
sub _html_file_subdir_name
{
my ($self, $id) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
+ my $ndb = $self->ndb();
my $subdir = '';
+ my $html_base_dir = $self->{ _html_base_directory };
my $subdir_style = $self->{ _subdir_style };
- my $month_db = $self->{ _db }->{ _month };
- my $subdir_db = $self->{ _db }->{ _subdir };
- my $curid = $self->{ _current_id };
my $dir_mode = $self->{ _dir_mode } || 0755;
if ($subdir_style eq 'yyyymm') {
- if (defined $subdir_db->{ $id } && $subdir_db->{ $id }) {
- $subdir = $subdir_db->{ $id };
- }
- else {
- $subdir = $self->_msg_time('yyyymm');
+ my $hdr = $self->{ _current_hdr };
+ $subdir = $ndb->msg_time($hdr, 'yyyymm');
- # XXX why we need validate $curid here ? (sholed be true always ?)
- if (defined($curid) && $curid == $id) {
- $subdir_db->{ $id } = $subdir; # cache subdir info into DB.
- # print STDERR "xdebug: \$subdir_db->{ $id } = $subdir\n";
- }
-
- use File::Spec;
- my $xsubdir = File::Spec->catfile($html_base_dir, $subdir);
- unless (-d $xsubdir) {
- my $mask = umask();
- umask(022);
- mkdir($xsubdir, $dir_mode);
- umask($mask);
- }
+ use File::Spec;
+ my $xsubdir = File::Spec->catfile($html_base_dir, $subdir);
+ unless (-d $xsubdir) {
+ my $mask = umask();
+ umask(022);
+ mkdir($xsubdir, $dir_mode);
+ umask($mask);
}
}
+ else {
+ croak("unknown \$subdir_style");
+ }
if ($subdir) {
use File::Spec;
return File::Spec->catfile($subdir, "msg$id.html");
}
else {
+ warn("not create msg$id.html");
return undef;
}
}
@@ -460,8 +474,6 @@ sub _init_htmlfy_rfc822_message
croak("htmlfy_rfc822_message: specify \$id or \$dst\n");
}
- $self->{ _id } = $id;
-
return ($id, $src, $dst);
}
@@ -607,9 +619,10 @@ sub _set_output_channel
sub _create_temporary_filename
{
my ($self) = @_;
- my $db_dir = $self->{ _html_base_directory };
+ my $html_base_dir = $self->{ _html_base_directory };
- return "$db_dir/tmp$$";
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, "tmp.$$");
}
@@ -917,307 +930,32 @@ See section C<Internal Data Presentation> for more detail.
sub cache_message_info
{
my ($self, $msg, $args) = @_;
- my $hdr = $msg->whole_message_header;
- my $id = $args-> { id };
- my $dst = $args-> { dst };
-
- $self->_db_open();
- my $db = $self->{ _db };
-
- # XXX we should not update max_id when our target is an attachment.
- # XXX update max_id only under the top level operation
- unless ($self->{ _is_attachment }) {
- if (defined $db->{ _info }->{ id_max }) {
- $db->{_info}->{id_max} =
- $db->{_info}->{id_max} < $id ? $id : $db->{_info}->{id_max};
- }
- else {
- $db->{_info}->{id_max} = $id;
- }
- _PRINT_DEBUG(" parent");
- _PRINT_DEBUG(" update id_max = $db->{_info }->{id_max}");
- }
- else {
- _PRINT_DEBUG(" child");
- }
-
- _PRINT_DEBUG(" cache_message_info( id=$id ) running");
-
- # HASH { $id => Date: }
- $db->{ _date }->{ $id } = $hdr->get('date');
-
- # HASH { $id => YYYY/MM }
- my $month = $self->_msg_time('yyyy/mm');
- $db->{ _month }->{ $id } = $month;
-
- # HASH { YYYY/MM => (id1 id2 id3 ..) }
- __add_value_to_array($db, '_monthly_idlist', $month, $id);
-
- # need month database to determine subdir for the html file
- $db->{ _filename }->{ $id } = $self->html_filename($id);
- $db->{ _filepath }->{ $id } = $dst;
-
- # HASH { $id => Subject: }
- $db->{ _subject }->{ $id } =
- $self->_decode_mime_string( $hdr->get('subject') );
-
- # HASH { $id => From: }
- my $ra = _address_clean_up( $hdr->get('from') );
- $db->{ _from }->{ $id } = $ra->[0];
- $db->{ _who }->{ $id } = $self->_who_of_address( $hdr->get('from') );
-
- # HASH { $id => Message-Id: }
- # HASH { Message-Id: => $id }
- # HASH { $id => list of $id ... }
- $ra = _address_clean_up( $hdr->get('message-id') );
- my $mid = $ra->[0];
- if ($mid) {
- $db->{ _message_id }->{ $id } = $mid;
- $db->{ _msgidref }->{ $mid } = $id;
- $db->{ _idref }->{ $id } = $id;
- }
-
- # Thread Information by In-Reply-To: and References
- {
- my $irt_ra = _address_clean_up( $hdr->get('in-reply-to') );
- my $in_reply_to = $irt_ra->[0];
-
- _PRINT_DEBUG("In-Reply-To: $in_reply_to") if defined $in_reply_to;
-
- # save message-id(s) within In-Reply-To: field into database
- for my $mid (@$irt_ra) {
- # { message-id => (id1 id2 id3 ...)
- __add_value_to_array($db, '_msgidref', $mid, $id);
-
- # idp (pointer to id) by { message-id => id }
- my $idp = _list_head($db->{ _msgidref }->{ $mid });
-
- # { idp => (id1 id2 id3 ...) }
- __add_value_to_array($db, '_idref', $idp, $id) if defined $idp;
- }
-
- # apply the same logic as above for all message-id's in References:
- my $ref_ra = _address_clean_up( $hdr->get('references') );
- my %uniq = ();
- MSGID_SEARCH:
- for my $mid (@$ref_ra) {
- next MSGID_SEARCH unless defined $mid;
- next MSGID_SEARCH if $uniq{$mid};
- $uniq{$mid} = 1; # ensure uniqueness
-
- _PRINT_DEBUG("References: $mid");
- __add_value_to_array($db, '_msgidref', $mid, $id);
- my $idp = _list_head($db->{ _msgidref }->{ $mid });
- __add_value_to_array($db, '_idref', $idp, $id) if defined $idp;
- }
-
- # 0. ok. go to speculate prev/next links
- # 1. If In-Reply-To: is found, use it as "pointer to previous id"
- my $idp = 0;
- if (defined $in_reply_to) {
- # XXX idp (id pointer) = id1 by _list_head( (id1 id2 id3 ...)
- $idp = _list_head( $db->{ _msgidref }->{ $in_reply_to } );
- }
- # 2. if not found, try to use References: "in reverse order"
- elsif (@$ref_ra) {
- my (@rra) = reverse(@$ref_ra);
- $idp = $rra[0];
- }
- # 3. no prev/next link
- else {
- $idp = 0;
- }
-
- if (defined($idp) && $idp && $idp =~ /^\d+$/) {
- if ($idp != $id) {
- $db->{ _prev_id }->{ $id } = $idp;
- _PRINT_DEBUG("\$db->{ _prev_id }->{ $id } = $idp");
- }
- else {
- _PRINT_DEBUG("no \$db->{ _prev_id }");
- }
-
- # XXX we should not overwrite " id => next_id " assinged already.
- # XXX we preserve the first " id => next_id " value.
- # XXX but we overwride it if "id => id (itself)", wrong link.
- unless ((defined $db->{ _next_id }->{ $idp }) &&
- ($db->{ _next_id }->{ $idp } != $idp)) {
- $db->{ _next_id }->{ $idp } = $id;
- _PRINT_DEBUG("override \$db->{ _next_id }->{ $idp } = $id");
- }
- else {
- my $thread_head_id = _thread_head( $db, $id );
- _PRINT_DEBUG("no \$db->{ _next_id }->{ $idp } override");
- _PRINT_DEBUG(" = $db->{ _next_id }->{ $idp }");
- }
- }
- else {
- _PRINT_DEBUG("no prev/next thread link (id=$id)");
- warn("no prev/next thread link (id=$id)\n") if $debug;
- }
- }
-
- $self->_db_close();
-}
-
-
-# Descriptions: return
-# Arguments: OBJ($self) STR($type)
-# Side Effects: none
-# Return Value: STR
-sub _msg_time
-{
- my ($self, $type) = @_;
- my $hdr = $self->{ _current_hdr };
-
- if (defined($hdr) && $hdr->get('date')) {
- use Time::ParseDate;
- my $unixtime = parsedate( $hdr->get('date') );
- my ($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime( $unixtime );
-
- if ($type eq 'yyyymm') {
- return sprintf("%04d%02d", 1900 + $year, $mon + 1);
- }
- elsif ($type eq 'yyyy/mm') {
- return sprintf("%04d/%02d", 1900 + $year, $mon + 1);
- }
- }
- else {
- my $id = $self->{ _current_id };
- warn("cannot pick up Date: field id=$id");
- return '';
- }
-}
-
-
-# Descriptions: convert space-separeted string to array
-# Arguments: STR($str)
-# Side Effects: none
-# Return Value: ARRAY_REF
-sub __str2array
-{
- my ($str) = @_;
-
- return undef unless defined $str;
-
- $str =~ s/^\s*//;
- $str =~ s/\s*$//;
- my (@a) = split(/\s+/, $str);
- return \@a;
-}
-
-
-# Descriptions: add { key => value } of database $dbname.
-# value is "x y z ..." form, space separated string.
-# Arguments: HASH_REF($db) STR($dbname) STR($key) STR($value)
-# Side Effects: update database
-# Return Value: none
-sub __add_value_to_array
-{
- my ($db, $dbname, $key, $value) = @_;
- my $found = 0;
- my $ra = __str2array($db->{ $dbname }->{ $key }) || [];
-
- if (defined($key) && $key && defined($value) && $value) {
- # check dup to ensure uniqueness within this array.
- for my $v (@$ra) {
- $found = 1 if ($value =~ /^\d+$/o) && ($v == $value);
- $found = 1 if ($value !~ /^\d+$/o) && ($v eq $value);
- }
-
- # add if the value is a new comer.
- unless ($found) {
- $db->{ $dbname }->{ $key } .= " $value";
- }
- }
-}
-
-
-# Descriptions: speculate head of thread list,
-# traced back from $id.
-# Arguments: HASH_REF($db) STR($id)
-# Side Effects: none
-# Return Value: NUM
-sub _thread_head
-{
- my ($db, $id) = @_;
- my $max = 128;
- my $head_id = $id;
-
- # track back id list to search the thread head
- while ($max-- > 0) {
- my $prev_id = $db->{ _prev_id }->{ $head_id };
- last unless $prev_id;
- $head_id = $prev_id;
- }
+ my $ndb = $self->ndb();
+ my $id = $args->{ id };
+ my $src = $args->{ src };
+ my $dst = $args->{ dst };
- return $head_id;
-}
+ $ndb->set_key($id);
+ $ndb->set('html_filename', $id, $self->html_filename($id));
+ $ndb->set('html_filepath', $id, $dst);
-# Descriptions: speculate head of the next thread list.
-# Arguments: HASH_REF($db) STR($id)
-# Side Effects: none
-# Return Value: STR
-sub _search_default_next_thread_id
-{
- my ($db, $id) = @_;
- my $list = __str2array( $db->{ _thread_list }->{ $id } );
- my (@ra, @c0, @c1) = ();
- @ra = reverse @$list if defined $list;
-
- for my $_id (1 .. 10) { push(@c0, $id + $_id);}
-
- # prepare thread list to search
- # 1. thread includes $id
- # 2. thread(s) begining at each id in thread 1.
- # 3. last resort: thread includes ($id+1),
- # thread includes ($id+2), ...
- for my $xid ($id, @ra, @c0) {
- my $default = __search_default_next_id_in_thread($db, $xid);
- return $default if defined $default;
- }
+ $ndb->analyze($msg);
}
-# Descriptions: speculate the next id of $id.
-# Arguments: HASH_REF($db) STR($id)
+# Descriptions: return Mail::Message::DB object.
+# Arguments: OBJ($self)
# Side Effects: none
-# Return Value: STR
-sub __search_default_next_id_in_thread
+# Return Value: OBJ
+sub ndb
{
- my ($db, $id) = @_;
- my $list = [];
- my $prev = 0;
-
- # thread_list HASH { $id => $id1 $id2 $id3 ... }
- if (defined $db->{ _thread_list }->{ $id }) {
- $list = __str2array( $db->{ _thread_list }->{ $id } );
- return undef unless $#$list > 1;
-
- # thread_list HASH { $id => $id1 $id2 $id3 ... $id $prev ... }
- # <---- search ---
- SEARCH:
- for my $xid (reverse @$list) {
- last SEARCH if $xid == $id;
- $prev = $xid;
- }
- }
-
- # found
- # XXX we use $prev in reverse order, so this $prev means "next"
- if ($prev > 0) {
- _PRINT_DEBUG("default thread: $id => $prev (@$list)");
- return $prev;
- }
- else {
- _PRINT_DEBUG("default thread: $id => none (@$list)");
- return undef;
- }
+ my ($self) = @_;
+ return $self->{ _ndb };
}
-=head2 C<update_relation($id)>
+=head2 C<update_msg_html_links($id)>
update link relation around C<$id>.
@@ -1225,14 +963,14 @@ update link relation around C<$id>.
# Descriptions: top level dispatcher to update database.
-# _update_relation() has real function for updating.
+# _msg_file_rewrite_links() has real function for updating.
# Arguments: OBJ($self) STR($id)
# Side Effects: update databse
# Return Value: none
-sub update_relation
+sub update_msg_html_links
{
my ($self, $id) = @_;
- my $args = $self->evaluate_relation($id);
+ my $info = $self->evaluate_links_relation($id);
my $list = $self->{ _affected_idlist } = [];
if ($self->is_ignore($id)) {
@@ -1240,33 +978,48 @@ sub update_relation
return undef;
}
+ # sanity
+ return unless defined $id;
+ return unless $id;
+
# update target itself, of course
- $self->_update_relation($id);
+ $self->_msg_file_rewrite_links($id);
push(@$list, $id);
- # rewrite links of files for
- # prev/next id (article id) and
- # prev/next by thread
- my $db = $self->{ _db };
+ # no rewriting for myself
my %uniq = ( $id => 1 );
- UPDATE:
- for my $id (qw(prev_id next_id prev_thread_id next_thread_id)) {
- if (defined $args->{ $id }) {
- next UPDATE if $uniq{ $args->{$id} }; $uniq{ $args->{$id} } = 1;
+ KEY:
+ for my $_link (qw(prev_id next_id prev_thread_id next_thread_id)) {
+ if (defined $info->{ $_link }) {
+ my $_id = $info->{ $_link };
- $self->_update_relation( $args->{ $id });
- push(@$list, $args->{ $id });
+ next KEY if $uniq{ $_id };
+ $uniq{ $_id } = 1;
+
+ _PRINT_DEBUG("try: rewrite $_link links in msg $_id");
+
+ if (defined $_id && $_id) {
+ $self->_msg_file_rewrite_links($_id);
+ push(@$list, $_id);
+ }
+ }
+ else {
+ _PRINT_DEBUG("error: fail to rewrite msg $_link");
}
}
- if (defined $db->{ _thread_list }->{ $id } ) {
- my $thread_list = __str2array( $db->{ _thread_list }->{ $id } );
+ # hint cached on memory, provided by _print_thread().
+ if (defined $self->{ _hint_ref_key_list }->{ $id }) {
+ my $thread_list = $self->{ _hint_ref_key_list }->{ $id } || [];
# update link relation for all articles in this thread.
+ KEY:
for my $id (@$thread_list) {
- next UPDATE if $uniq{ $id}; $uniq{ $id } = 1;
- $self->_update_relation( $id );
+ next KEY if $uniq{ $id};
+ $uniq{ $id } = 1;
+
+ $self->_msg_file_rewrite_links( $id );
push(@$list, $id);
}
}
@@ -1277,12 +1030,12 @@ sub update_relation
# Arguments: OBJ($self) STR($id)
# Side Effects: rewrite index file
# Return Value: none
-sub _update_relation
+sub _msg_file_rewrite_links
{
my ($self, $id) = @_;
- my $args = $self->evaluate_relation($id);
- my $preamble = $self->evaluate_safe_preamble($args);
- my $footer = $self->evaluate_safe_footer($args);
+ my $info = $self->evaluate_links_relation($id);
+ my $preamble = $self->evaluate_safe_preamble($info);
+ my $footer = $self->evaluate_safe_footer($info);
my $code = _charset_to_code($self->{ _charset });
my $pat_preamble_begin = quotemeta($preamble_begin);
@@ -1294,10 +1047,10 @@ sub _update_relation
umask(022);
- _PRINT_DEBUG("_update_relation $id");
+ _PRINT_DEBUG("try _msg_file_rewrite_links($id)");
use FileHandle;
- my $file = $args->{ file };
+ my $file = $info->{ filepath };
if (defined $file && $file && -f $file) {
my ($old, $new) = ($file, "$file.new.$$");
my $rh = new FileHandle $old;
@@ -1306,6 +1059,8 @@ sub _update_relation
if (defined $rh && defined $wh) {
my $buf;
+ _PRINT_DEBUG("rewrite: open msg $id");
+
LINE:
while ($buf = <$rh>) {
if ($buf =~ /^$pat_preamble_begin/
@@ -1335,6 +1090,9 @@ sub _update_relation
unless (rename($new, $old)) {
croak("rename($new, $old) fail (id=$id)\n");
}
+ else {
+ _PRINT_DEBUG("done: rewritten links in msg $id");
+ }
}
else {
unless (defined $file) {
@@ -1356,72 +1114,12 @@ sub _update_relation
# Arguments: OBJ($self) NUM($id)
# Side Effects: none
# Return Value: HASH_REF
-sub evaluate_relation
+sub evaluate_links_relation
{
my ($self, $id) = @_;
+ my $ndb = $self->ndb();
- $self->_db_open();
- my $db = $self->{ _db };
- my $file = $db->{ _filepath }->{ $id };
-
- my $next_file = $self->html_filepath( $id + 1 );
- my $prev_id = $id > 1 ? $id - 1 : undef;
- my $next_id = $id + 1 if -f $next_file;
- my $prev_thread_id = $db->{ _prev_id }->{ $id } || undef;
- my $next_thread_id = $db->{ _next_id }->{ $id } || undef;
-
- # diagnostic
- if ($prev_thread_id) {
- undef $prev_thread_id if $prev_thread_id == $id;
- }
- if ($next_thread_id) {
- undef $next_thread_id if $next_thread_id == $id;
- }
- else {
- my $xid = _search_default_next_thread_id($db, $id);
- if ($xid && ($xid != $id)) {
- $next_thread_id = $xid;
- _PRINT_DEBUG("override next_thread_id = $next_thread_id");
- }
- }
-
- my $link_prev_id = $self->html_filename($prev_id);
- my $link_next_id = $self->html_filename($next_id);
- my $link_prev_thread_id = $self->html_filename($prev_thread_id);
- my $link_next_thread_id = $self->html_filename($next_thread_id);
-
- my $subject = {};
- if (defined $prev_id) {
- $subject->{ prev_id } = $db->{ _subject }->{ $prev_id };
- }
- if (defined $next_id) {
- $subject->{ next_id } = $db->{ _subject }->{ $next_id };
- }
- if (defined $prev_thread_id) {
- $subject->{ prev_thread_id } = $db->{ _subject }->{ $prev_thread_id };
- }
- if (defined $next_thread_id) {
- $subject->{ next_thread_id } = $db->{ _subject }->{ $next_thread_id };
- }
-
- my $args = {
- id => $id,
- file => $file,
- prev_id => $prev_id,
- next_id => $next_id,
- prev_thread_id => $prev_thread_id,
- next_thread_id => $next_thread_id,
- link_prev_id => $link_prev_id,
- link_next_id => $link_next_id,
- link_prev_thread_id => $link_prev_thread_id,
- link_next_thread_id => $link_next_thread_id,
- subject => $subject,
- };
- _PRINT_DEBUG_DUMP_HASH( $args );
-
- $self->_db_close();
-
- return $args;
+ return $ndb->tohtml_thread_summary($id);
}
@@ -1445,26 +1143,29 @@ sub evaluate_safe_preamble
umask(022);
- if (defined($link_prev_id)) {
+ # for debug
+ $preamble .= "<!-- rewritten for id=$self->{ _debug_id } -->\n";
+
+ if (defined($link_prev_id) && $link_prev_id) {
$preamble .= "<A HREF=\"${prefix}$link_prev_id\">[Prev by ID]</A>\n";
}
else {
$preamble .= "[No Prev ID]\n";
}
- if (defined($link_next_id)) {
+ if (defined($link_next_id) && $link_next_id) {
$preamble .= "<A HREF=\"${prefix}$link_next_id\">[Next by ID]</A>\n";
}
else {
$preamble .= "[No Next ID]\n";
}
- if (defined $link_prev_thread_id) {
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
$preamble .=
"<A HREF=\"${prefix}$link_prev_thread_id\">[Prev by Thread]</A>\n";
}
else {
- if (defined $link_prev_id) {
+ if (defined $link_prev_id && $link_prev_id) {
$preamble .=
"<A HREF=\"${prefix}$link_prev_id\">[Prev by Thread]</A>\n";
}
@@ -1473,12 +1174,12 @@ sub evaluate_safe_preamble
}
}
- if (defined $link_next_thread_id) {
+ if (defined $link_next_thread_id && $link_next_thread_id) {
$preamble .=
"<A HREF=\"${prefix}$link_next_thread_id\">[Next by Thread]</A>\n";
}
else {
- if (defined $link_next_id) {
+ if (defined $link_next_id && $link_next_id) {
$preamble .=
"<A HREF=\"${prefix}$link_next_id\">[Next by Thread]</A>\n";
}
@@ -1513,7 +1214,7 @@ sub evaluate_safe_footer
my $prefix = $use_subdir ? '../' : '';
my $footer = $footer_begin. "\n";;
- if (defined($link_prev_id)) {
+ if (defined($link_prev_id) && $link_prev_id) {
$footer .= "<BR>\n";
$footer .= "<A HREF=\"${prefix}$link_prev_id\">Prev by ID: ";
if (defined $subject->{ prev_id } ) {
@@ -1522,7 +1223,7 @@ sub evaluate_safe_footer
$footer .= "</A>\n";
}
- if (defined($link_next_id)) {
+ if (defined($link_next_id) && $link_next_id) {
$footer .= "<BR>\n";
$footer .= "<A HREF=\"${prefix}$link_next_id\">Next by ID: ";
if (defined $subject->{ next_id } ) {
@@ -1531,7 +1232,7 @@ sub evaluate_safe_footer
$footer .= "</A>\n";
}
- if (defined $link_prev_thread_id) {
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
$footer .= "<BR>\n";
$footer .=
"<A HREF=\"${prefix}$link_prev_thread_id\">Prev by Thread: ";
@@ -1540,8 +1241,17 @@ sub evaluate_safe_footer
}
$footer .= "</A>\n";
}
+ elsif (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_id });
+ }
+ $footer .= "</A>\n";
+ }
- if (defined $link_next_thread_id) {
+ if (defined $link_next_thread_id && $link_next_thread_id) {
$footer .= "<BR>\n";
$footer .=
"<A HREF=\"${prefix}$link_next_thread_id\">Next by Thread: ";
@@ -1550,6 +1260,15 @@ sub evaluate_safe_footer
}
$footer .= "</A>\n";
}
+ elsif (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_id\">Next by Thread: ";
+ if (defined $subject->{ next_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_id });
+ }
+ $footer .= "</A>\n";
+ }
$footer .= qq{<BR>\n};
$footer .= _format_index_navigator( { use_subdir => $use_subdir } );
@@ -1559,138 +1278,6 @@ sub evaluate_safe_footer
}
-=head1 Internal Data Presentation
-
-=head2 Hashes for Database
-
- name hash content
- ----------------------------
- from id => From: header field
- date id => Date: header field
- subject id => Subject: header field
- message_id id => Message-Id: header field
- references id => References: header field
- filepath id => file location ( /some/where/YYYY/MM/DD/xxx.html )
- idref id => id(myself) refered-by-id1 refered-by-id2 ...
- msgidref message-id => id(myself) refered-by-id1 refered-by-id2 ...
-
-We need several information to speculate thread relation rapidly.
-At least we need two relations:
-
-1. to speculate [Next by Thread]
-
- message-id => ( id1 id2 id3 ... )
-
-where C<id1> is the message itself.
-
-2. to speculate [Prev by Thread]
-
- id => message-id of replied message (e.g. In-Reply-To:)
-
-hashes.
-
-BTW, the end message of the thread has no next message,
-and the top of the thread has no previous message.
-We arrange apporopviate link to another thread.
-Also we need this relation for C<thread.html>.
-
-To resolve this problem, we need ID or Date ordered thread (top id of
-th thread) list ?
-
- thread followup relation in the thread
- -----------------------------
- id1 id1 - id2 - id4
- id3 id3 - id5 - id6
- |
- - id7 - id10
- id8 id8 - id9 - id11
- id12 id12 ...
-
-=head2 Usage
-
-For example, you can set { $key => $value } for C<from> data in this way:
-
- $self->{ _db }->{ _from }->{ $key } = $value;
-
-=cut
-
-my @kind_of_databases = qw(from date subject message_id references
- msgidref idref next_id prev_id
- filename filepath
- unixtime month monthly_idlist
- thread_list
- subdir
- who info);
-
-
-# 1. Hmm, what database is needed for
-# {Prev,Next} by Article ID
-# {Prev,Next} by Thread
-#
-# 2. each message needs ?
-#
-# Subject:
-# From:
-#
-
-
-# Descriptions: open database
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: tied with $self->{ _db }
-# Todo: we should use IO::Adapter ?
-# Return Value: none
-sub _db_open
-{
- my ($self, $args) = @_;
- my $db_type = $args->{ db_type } || $self->{ _db_type } || 'AnyDBM_File';
- my $db_dir = $self->{ _html_base_directory };
- my $file_mode = $self->{ _file_mode } || 0644;
-
- _PRINT_DEBUG("_db_open( type = $db_type )");
-
- eval qq{ use $db_type; use Fcntl;};
- unless ($@) {
- for my $db (@kind_of_databases) {
- my $file = "$db_dir/.htdb_${db}";
- my $str = qq{
- my \%$db = ();
- tie \%$db, \$db_type, \$file, O_RDWR|O_CREAT, $file_mode;
- \$self->{ _db }->{ _$db } = \\\%$db;
- };
- eval $str;
- croak($@) if $@;
- }
- }
- else {
- croak("cannot use $db_type");
- }
-}
-
-
-# Descriptions: close database
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: untie $self->{ _db }
-# Todo: we should use IO::Adapter ?
-# Return Value: none
-sub _db_close
-{
- my ($self, $args) = @_;
- my $db_type = $args->{ db_type } || $self->{ _db_type } || 'AnyDBM_File';
- my $db_dir = $self->{ _html_base_directory };
-
- _PRINT_DEBUG("_db_close()");
-
- for my $db (@kind_of_databases) {
- my $str = qq{
- my \$${db} = \$self->{ _db }->{ _$db };
- untie \%\$${db};
- };
- eval $str;
- croak($@) if $@;
- }
-}
-
-
=head2 C<update_id_index($args)>
update index.html.
@@ -1802,31 +1389,28 @@ sub update_id_index
}
$self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $id_max = $db->{ _info }->{ id_max };
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
$self->_print_ul($wh, $db, $code);
if ($order eq 'reverse') {
- for my $id ( reverse (1 .. $id_max )) {
+ for my $id (reverse (1 .. $max_id)) {
$self->_print_li_filename($wh, $db, $id, $code);
}
}
else {
- for my $id ( 1 .. $id_max ) {
+ for my $id (1 .. $max_id) {
$self->_print_li_filename($wh, $db, $id, $code);
}
}
$self->_print_end_of_ul($wh, $db, $code);
- $self->_db_close();
$self->_print_index_end( $htmlinfo );
}
-=head2 C<update_id_monthly_index($args)>
+=head2 C<update_monthly_id_index($args)>
=cut
@@ -1835,7 +1419,7 @@ sub update_id_index
# Arguments: OBJ($self) HASH_REF($args)
# Side Effects: rewrite monthly index
# Return Value: none
-sub update_id_monthly_index
+sub update_monthly_id_index
{
my ($self, $args) = @_;
my $affected_list = $self->{ _affected_idlist };
@@ -1846,26 +1430,26 @@ sub update_id_monthly_index
}
# open databaes
- $self->_db_open();
- my $db = $self->{ _db };
-
+ my $db = $self->ndb();
my %month_update = ();
IDLIST:
for my $id (@$affected_list) {
- next IDLIST unless $id =~ /^\d+$/;
- my $month = $db->{ _month }->{ $id };
- if (defined $month) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
$month_update{ $month } = 1;
}
}
# todo list
for my $month (sort keys %month_update) {
- my $this_month = $month; # yyyy/mm
- my $suffix = $month; $suffix =~ s@/@@g; # yyyymm
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
- $self->_update_id_monthly_index($args, {
+ $self->_update_monthly_id_index($args, {
this_month => $this_month,
suffix => $suffix,
});
@@ -1885,19 +1469,21 @@ sub _update_id_montly_index_master
my ($self, $args) = @_;
my $html_base_dir = $self->{ _html_base_directory };
my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_index.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_index.html.new.$$");
my $htmlinfo = {
title => defined($args->{ title }) ? $args->{ title } : "ID Index",
- old => "$html_base_dir/monthly_index.html",
- new => "$html_base_dir/monthly_index.html.new.$$",
+ old => $old,
+ new => $new,
code => $code,
};
$self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $mlist = $db->{ _monthly_idlist };
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
my (@list) = sort __sort_yyyymm keys %$mlist;
my ($years) = _yyyy_range(\@list);
@@ -1910,7 +1496,7 @@ sub _update_id_montly_index_master
_print_raw_str($wh, "<TR>", $code) if $month == 7;
my $id = sprintf("%04d/%02d", $year, $month); # YYYY/MM
- my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
my $fn = "month.$xx.html";
use File::Spec;
@@ -1925,7 +1511,6 @@ sub _update_id_montly_index_master
}
_print_raw_str($wh, "</TABLE>", $code);
- $self->_db_close();
$self->_print_index_end( $htmlinfo );
}
@@ -1940,7 +1525,7 @@ sub _yyyy_range
my ($yyyy) = {};
for my $y (@$list) {
- if ($y =~ /^(\d{4})\/(\d{2})/) {
+ if ($y =~ /^(\d{4})\/(\d{2})/o) {
$yyyy->{ $1 } = $1;
}
}
@@ -1970,7 +1555,7 @@ sub __sort_yyyymm
# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
# Side Effects: update month.YYYYMM.html
# Return Value: none
-sub _update_id_monthly_index
+sub _update_monthly_id_index
{
my ($self, $args, $monthlyinfo) = @_;
my $html_base_dir = $self->{ _html_base_directory };
@@ -1986,33 +1571,31 @@ sub _update_id_monthly_index
};
$self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $id_max = $db->{ _info }->{ id_max };
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
- # oops, this list may be " a b c d e " string, nuke \s* to avoid warning.
- $db->{ _monthly_idlist }->{ $this_month } =~ s/^\s*//;
- $db->{ _monthly_idlist }->{ $this_month } =~ s/\s*$//;
- my (@list) = split(/\s+/, $db->{ _monthly_idlist }->{ $this_month });
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
$self->_print_ul($wh, $db, $code);
if ($order eq 'reverse') {
- for my $id (reverse sort {$a <=> $b} @list) {
- next unless $id =~ /^\d+$/;
+ ID:
+ for my $id (reverse sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
$self->_print_li_filename($wh, $db, $id, $code);
}
}
else {
- for my $id (sort {$a <=> $b} @list) {
- next unless $id =~ /^\d+$/;
+ ID:
+ for my $id (sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
$self->_print_li_filename($wh, $db, $id, $code);
}
}
$self->_print_end_of_ul($wh, $db, $code);
- $self->_db_close();
$self->_print_index_end( $htmlinfo );
}
@@ -2047,17 +1630,15 @@ sub update_thread_index
}
$self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $id_max = $db->{ _info }->{ id_max };
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
# initialize negagtive cache to ensure uniquness
delete $self->{ _uniq };
$self->_print_ul($wh, $db, $code);
- for my $id ( 1 .. $id_max ) {
+ for my $id ( 1 .. $max_id ) {
# head of the thread (not referenced yet)
unless (defined $self->{ _uniq }->{ $id }) {
$self->_print_thread($wh, $db, $id, $code);
@@ -2065,7 +1646,6 @@ sub update_thread_index
}
$self->_print_end_of_ul($wh, $db, $code);
- $self->_db_close();
$self->_print_index_end( $htmlinfo );
}
@@ -2078,8 +1658,7 @@ sub _has_link
{
my ($self, $db, $id) = @_;
- if (defined( $db->{ _next_id }->{ $id } ) ||
- defined( $db->{ _prev_id }->{ $id } )) {
+ if ($db->get('next_key', $id) || $db->get('prev_key', $id)) {
return 1;
}
else {
@@ -2096,29 +1675,28 @@ sub _print_thread
{
my ($self, $wh, $db, $head_id, $code) = @_;
my $saved_stack_level = $self->{ _stack };
- my $uniq = $self->{ _uniq };
-
- # debug information (it is useful not to remove this ?)
- _print_raw_str($wh, "<!-- thread head=$head_id -->\n", $code);
+ my $uniq = $self->{ _uniq };
# get id list: @idlist = ( $head_id id2 id3 ... )
- my $buf = $db->{ _idref }->{ $head_id };
+ my $ndb = $self->ndb();
+ my $buf = $ndb->get('ref_key_list', $head_id);
- if (defined $buf) {
- my $ra = __str2array($buf);
- my (@idlist) = @$ra;
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- thread head=$head_id ($buf) -->\n", $code);
+ my $idlist = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ if (@$idlist) {
IDLIST:
- for my $id (@idlist) {
- # save $id => " @idlist " for further use
- # XXX override occurs but select latest information (no reason;)
- if ($#idlist > 1) {
- $db->{ _thread_list }->{ $id } = $buf;
- _PRINT_DEBUG("\$db->{ _thread_list }->{ $id } = $buf");
+ for my $id (@$idlist) {
+ # save $head_id => "id1 id2 id3 ..." on memory for further use.
+ # "> 1" implies idlist contains others than myself.
+ if ($#$idlist > 1) {
+ my $ra = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ $self->{ _hint_ref_key_list }->{ $id } = $ra;
}
- # @idlist = (number's)
- _print_raw_str($wh, "<!-- thread (@idlist) -->\n", $code);
+ # @$idlist = (number's)
+ _print_raw_str($wh, "<!-- thread (@$idlist) -->\n", $code);
next IDLIST if $uniq->{ $id };
$uniq->{ $id } = 1;
@@ -2127,6 +1705,7 @@ sub _print_thread
# oops, we should ignore head of the thread ( myself ;-)
if (($id != $head_id) && $self->_has_link($db, $id)) {
+ _print_raw_str($wh, "<!-- thread $id has link -->\n", $code);
$self->_print_li_filename($wh, $db, $id, $code);
$self->_print_thread($wh, $db, $id, $code);
}
@@ -2380,9 +1959,11 @@ sub _print_end_of_ul
sub _print_li_filename
{
my ($self, $wh, $db, $id, $code) = @_;
- my $filename = $db->{ _filename }->{ $id };
- my $subject = $db->{ _subject }->{ $id };
- my $who = $db->{ _who }->{ $id };
+ my $filename = $db->get('html_filename', $id);
+ my $subject = $db->get('subject', $id);
+ my $who = $db->get('who', $id);
+
+ _PRINT_DEBUG("-- print_li_filename id=$id file=$filename");
if (defined $filename && $filename) {
_print_raw_str($wh, "<!-- LI id=$id -->\n", $code);
@@ -2402,31 +1983,6 @@ sub _print_li_filename
=cut
-# Descriptions: clean up email address by Mail::Address.
-# return clean-up'ed address list.
-# Arguments: STR($addr)
-# Side Effects: none
-# Return Value: ARRAY_REF
-sub _address_clean_up
-{
- my ($addr) = @_;
- my (@r);
-
- use Mail::Address;
- my (@addrs) = Mail::Address->parse($addr);
-
- my $i = 0;
- LIST:
- for my $addr (@addrs) {
- my $xaddr = $addr->address();
- next LIST unless $xaddr =~ /\@/;
- push(@r, $xaddr);
- }
-
- return \@r;
-}
-
-
# Descriptions: extrace gecos field in $address
# Arguments: OBJ($self) STR($address)
# Side Effects: none
@@ -2434,37 +1990,9 @@ sub _address_clean_up
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() )) {
- my $phrase = $self->_decode_mime_string( $addr->phrase() );
-
- if ($phrase) {
- return($phrase);
- }
- }
- $user = $addr->user();
- }
-
- return( $user ? "$user\@xxx.xxx.xxx.xxx" : $address );
-}
-
-
-# Descriptions: head of array (space separeted string)
-# Arguments: STR($buf)
-# Side Effects: none
-# Return Value: STR
-sub _list_head
-{
- my ($buf) = @_;
- $buf =~ s/^\s*//;
- $buf =~ s/\s*$//;
- return (split(/\s+/, $buf))[0];
+ use Mail::Message::Utils;
+ return Mail::Message::Utils::from_to_name($address);
}
@@ -2478,27 +2006,10 @@ sub _decode_mime_string
my $charset = $options->{ 'charset' } || $self->{ _charset };
my $code = _charset_to_code($charset) || 'euc';
- # If looks Japanese and $code is specified as Japanese, decode !
- if (defined($str) &&
- ($str =~ /=\?ISO\-2022\-JP\?[BQ]\?/i) &&
- ($code eq 'euc' || $code eq 'jis')) {
- 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;
- }
-
- if (defined($str) && $str) {
- eval q{ use Jcode;};
- my $icode = &Jcode::getcode(\$str);
- warn("code not specified") unless defined $code;
- warn("icode not specified") unless defined $icode;
- &Jcode::convert(\$str, $code, $icode);
- }
+ if (defined($str) && $str) {
+ use Mail::Message::Encode;
+ my $encode = new Mail::Message::Encode;
+ return $encode->decode_mime_string($str, $code);
}
return $str;
@@ -2553,18 +2064,30 @@ sub htmlify_file
printf STDERR "htmlify_file( id=%-6s src=%s )\n", $id, $file;
}
+ _PRINT_DEBUG("htmlfy_rfc822_message begin");
$html->htmlfy_rfc822_message({
id => $id,
src => $file,
});
+ _PRINT_DEBUG("htmlfy_rfc822_message end");
if ($debug) {
printf STDERR "htmlify_file( id=%-6s ) update relation\n", $id;
}
- $html->update_relation( $id );
- $html->update_id_monthly_index({ id => $id });
+
+ _PRINT_DEBUG("-- msg_html_links");
+ $html->update_msg_html_links( $id );
+
+ _PRINT_DEBUG("-- monthly id index");
+ $html->update_monthly_id_index({ id => $id });
+
+ _PRINT_DEBUG("-- id index");
$html->update_id_index({ id => $id });
+
+ _PRINT_DEBUG("-- thread index");
$html->update_thread_index({ id => $id });
+
+ _PRINT_DEBUG("-- top index");
$html->create_top_index();
# no more action for old files
@@ -2648,28 +2171,48 @@ if ($0 eq __FILE__) {
my $has_fork = defined $ENV{'HAS_FORK'} ? 1 : 0;
my $max = defined $ENV{'MAX'} ? $ENV{'MAX'} : 1000;
my $charset = 'euc-jp';
+ my $opts = {
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ };
eval q{
- my $obj = new Mail::Message::ToHTML;
-
+ my ($t, $time_b, $time_e);
for my $x (@ARGV) {
+ $time_b = time;
+ print STDERR "debug.main processing $x ...";
+
if (-f $x) {
- $obj->htmlify_file($x, {
- directory => $dir,
- charset => $charset,
+ eval q{
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_file($x, {
+ directory => $dir,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
});
+ };
+ print STDERR $@ if $@;
}
elsif (-d $x) {
+ my $obj = new Mail::Message::ToHTML $opts;
$obj->htmlify_dir($x, {
directory => $dir,
has_fork => $has_fork,
max => $max,
charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
});
}
+
+ $t = time - $time_b;
+ print STDERR "\t$t sec.\n";
}
+ print STDERR "done.\n";
};
- croak($@) if $@;
+
+ if ($@) { croak($@);}
}
diff --git a/fml/lib/Mail/Message/Utils.pm b/fml/lib/Mail/Message/Utils.pm
index 7c97d60c..7bcbe93f 100644
--- a/fml/lib/Mail/Message/Utils.pm
+++ b/fml/lib/Mail/Message/Utils.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: Utils.pm,v 1.4 2002/09/22 14:57:05 fukachan Exp $
+# $FML: Utils.pm,v 1.5.2.2 2003/06/14 10:41:05 fukachan Exp $
#
package Mail::Message::Utils;
@@ -51,6 +51,44 @@ sub remove_subject_tag_like_string
}
+=head2 from_to_name($address)
+
+extract gecos field in $address with shielding the real address.
+
+=cut
+
+
+# Descriptions: extract gecos field in $address
+# Arguments: OBJ($self) STR($address)
+# Side Effects: none
+# Return Value: STR
+sub from_to_name
+{
+ my ($address) = @_;
+ my ($user);
+
+ use Mail::Address;
+ my (@addrs) = Mail::Address->parse($address);
+
+ use Mail::Message::Encode;
+ my $encode = new Mail::Message::Encode;
+
+ for my $addr (@addrs) {
+ if (defined( $addr->phrase() )) {
+ my $phrase = $encode->decode_mime_string( $addr->phrase() );
+
+ if ($phrase) {
+ return($phrase);
+ }
+ }
+
+ $user = $addr->user();
+ }
+
+ return( $user ? "$user\@xxx.xxx.xxx.xxx" : $address );
+}
+
+
=head1 CODING STYLE
See C<http://www.fml.org/software/FNF/> on fml coding style guide.