diff options
| author | fukachan <fukachan> | 2003-07-19 10:23:33 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2003-07-19 10:23:33 +0000 |
| commit | 593b7adf58b58378c1bca22a3d37026976429255 (patch) | |
| tree | e3af803f44413f3d479aad746adbd0b16e06b332 | |
| parent | b65d1718ac550eb4dbdef82e10fe2ac814233ad9 (diff) | |
| parent | 52449fa0a4c31c0196d083b85db1c53edb2f6fb1 (diff) | |
| download | fml8-593b7adf58b58378c1bca22a3d37026976429255.tar.gz fml8-593b7adf58b58378c1bca22a3d37026976429255.tar.bz2 fml8-593b7adf58b58378c1bca22a3d37026976429255.zip | |
merge udb branch
| -rw-r--r-- | fml/lib/Mail/Message/DB.pm | 1267 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/HISTORY | 3 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/IMPLEMENTATION | 33 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/MH.pm | 2 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/THREAD.euc-jp | 102 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/TODO | 12 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/ToHTML.pm | 973 | ||||
| -rw-r--r-- | fml/lib/Mail/Message/Utils.pm | 40 |
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. |
