目次

背景

Mac OS XにバンドルされているメーラのMailは10.3版と10.4版では メールを格納するファイルの形式が異なっている。10.3はmboxだったが、10.4ではemlxという 拡張子になってバラバラでディレクトリに格納されている。
googleで探ってみると情報がたくさんあり、既にtoolが存在している。

単なる興味本位と暇つぶしにperlでemlx 2 mboxを書いてみた。

解説

TBD
  • 解説が欲しい、ソースが欲しい、直して欲しい等あればコメントしてください。 -- 2006-01-30 (Mon) 16:06:07

src

#!/usr/bin/perl
use Carp;
use strict;

# grobal varliables
my $targetDir;
my $mboxFilename;
# grobal constant valiables 
my $MBOX_DEFAULT_NAME = "mbox";

if ($#ARGV < 0) {
	&usage();
} elsif ( !(-e $ARGV[0]) ) {
	&usage("$ARGV[0] is not exist\n");
} elsif ( $ARGV[1] ) {
	$mboxFilename = $ARGV[1];
}


$targetDir = $ARGV[0];
&mainloop($targetDir, $mboxFilename);

exit(0);
########################################
### main routines ###
sub mainloop {
	my ($i);
	my $tdir = $_[0];
	my $tname = $_[1];
	
	if ( $tname =~ /^\s{0,}$/ || length($tname) < 1 ) {
		$tname = $MBOX_DEFAULT_NAME;
	}

	# ファイル一覧の取得
	opendir(DIR, "$targetDir") or die "$!\n";
	my $file;
	my @files;
	while ( ($file = readdir(DIR)) ) {
		push(@files, $file) unless ( -d $file );
	}
	closedir(DIR);

	# mboxのオープン。既存があればとりあえずここで終了する。
	my $wr = \*WR;
	if ( -e $tname ) {
		printf("%s is exist, so aborting.\n", $tname); 
		exit(1);
	} else {
		open($wr, ">$tname") or die "$!\n";
	}

	# mboxへのコピー
	my $flen;
	my $fpath;
	my $mboxlen = 0;
	for ( $i = 0; $i < $#files +1; $i++ ) {
		$fpath = "$tdir/$files[$i]";
		$flen = -s $fpath;
		if ( $flen > 0 ) {
			$mboxlen += &writeMbox($wr, $fpath, $mboxlen);
			printf("do %s(%d) | %s(%d)\n", 
				$fpath, $flen, $tname, $mboxlen);
		} else {
			printf("%s filesize is %d, so ignore\n", 
				$fpath, $flen);
		}
		&reportProgress($#files+1, $i+1);
	}
}

### sub routines ###
sub writeMbox {
	my $wr = $_[0];
	my $fpath = $_[1];
	my $mboxlen = $_[2];
	my $endmark = "\r\n\r\n";
	my $wlen;

	# emlxからデータ取得
	my $rbuf;
	open(FH, $fpath) or die "$!\n";
	# 1行目の謎の数字はmbox用先頭From行に差し替える必要がある。
	# 先頭のFrom行の形式は後述
	$rbuf = <FH>;
	undef($/);
	$rbuf = <FH>;
	$/ = "\n";
	close(FH);

	# 先頭From行の形式は
	# 	^From: <sender> <date>\r\n$
	# の形。時間は最後のReceivedヘッダの時刻にする。
	# senderはFrom行のaddress。
	$rbuf = &add1stFrom($rbuf);
	# mboxに書き出し
	$wlen = syswrite($wr, $rbuf, length($rbuf)) or die "$!\n";
	# mboxの最後は改行2個
	syswrite($wr, $endmark, length($endmark)) or die "$!\n";

	return ($wlen+length($endmark));
}

sub debugPrint {
	my $ref = $_[0];
	my $refarry = $ref->{"lines"};
	my @lines = @$refarry;
	my $isPrint = 0;

	if ( $isPrint ) {
		print "######\n";
		printf("[name](%s)\n", $ref->{"name"});
		printf("[linenum] %d\n", $ref->{"line"});
		printf("[id] %s\n", $ref->{"id"});
		print "-----\n";
		foreach ( @lines ) {
			print "$_";
		}
	}
}

sub add1stFrom {
	my $body = $_[0];
	my @body = split(/\n/, $body);
	# 落とした \nを加える
	foreach ( @body ) {
		$_ = "$_\n";
	}
	my ( $line, $ref);
	my @hdr= &extractHeader(@body);
	my $rcvIsNotOccured = 1;

	my $fromAddr = "";
	my $receivedTime = "";
	my $inProgress = 2;

	my $i;
	for ( $i = 0; $i < $#hdr+1; $i++ ) {
		&debugPrint($hdr[$i]);
	}

	foreach $ref (@hdr) {
		if ( $ref->{"name"} =~ /From/ ) {
			$fromAddr = &extractFromAddress($ref);
			$inProgress -= 1;
		} elsif ( $ref->{"name"} =~ /Received/ ){
			if ( $rcvIsNotOccured ) {
				$receivedTime = &extractReceivedTime($ref);
				$inProgress -= 1;
				$rcvIsNotOccured = 0;
			}
		}

		if ( $inProgress == 0 ) { last; }
	}
	#printf("inProgress: %s\n", $inProgress);
	#printf("fromAddr : %s\n", $fromAddr);
	#printf("receivedTime : %s\n", $receivedTime);
	return "From $fromAddr $receivedTime\r\n$body";
}

sub extractFromAddress {
	my $ref = $_[0];
	my $rar = $ref->{"lines"};
	my @ar = @$rar;

	my $line = $ar[0];
	$_ = $line;
	# mail address regex
	# [\w.-]+\@([\w-[\w-]+\.)+\w+
	/.*[^\w.-]([\w.-]+\@([\w-[\w-]+\.)+\w+)[^\w].*/;

	my $ret = $1;

	return $ret;
}

sub extractReceivedTime {
	my $ref = $_[0];
	my $rar = $ref->{"lines"};
	my @ar = @$rar;
	my $line;

	foreach (@ar) {
		s/[\r\n]//g;
		$line .= $_;
	}
	
	# 日付の形式は  曜日 mm dd HH:MM:SS yyyy
	# asctime formatである
	my @tmp = split(/;/, $line);
	my $date = $tmp[$#tmp];
	if ( $date =~ /^\s{1,}(.*)/ ) {
		$date = $1;
	}
	
	return &myAscTime($date);
}

sub myAscTime {
	my $time = $_[0];
	# 取得するのはRFC2822にあるFMT
	# 曜日, dd mm yyyy HH:MM:SS +TZ(場所)
	$_ = $time;
	s/,//;
	my @tmp = split(/ /,$_);
	my $w_day = 0;
	my $day = 1;
	my $mon = 2;
	my $year = 3;
	my $time = 4;

	$time = "$tmp[$w_day] $tmp[$mon] $tmp[$day] $tmp[$time] $tmp[$year]";
	# 日付の形式は  曜日 mm dd HH:MM:SS yyyy
	# asctime formatである
	return $time;
}

sub mkHeaderLine {
	my @hl = @_;
	my $ret = {name => "", linenum => 0, lines => "", 
		id=>""};

	# set header lines
	$ret->{"lines"} = \@hl;
	$ret->{"linenum"} = $#hl;
	# parse header name
	$ret->{"name"} = (split(/:/, $hl[0]))[0];

	return $ret;
}

sub extractHeader {
	my @msg = @_;
	my @ret = ();
	my @hdr;
	my ($i, $j, $ref);
	my $runInLoop = 0;
	my $blockCount = 0;

	for ( $i = 0; $i < $#msg +1; $i++ ) {
		@hdr = ($msg[$i]);
		$j = $i+1;
		$runInLoop = 0;
		SW_EXTRACT_HDR_BLOCK:{
		while ( $msg[$j] =~ /^\s{1,}/ ) {
			# headerのデリミタなら脱出
			if ( $msg[$j] =~ /^\r\n$/ ) {
				last SW_EXTRACT_HDR_BLOCK;
			}
			push(@hdr, $msg[$j]);
			$j += 1;
			$runInLoop = 1;
		}
		};# end of SW_EXTRACT_HDR_BLOCK
		$ref = &mkHeaderLine(@hdr);
		$ref->{"id"} = $blockCount;
		$blockCount += 1;
		push(@ret, $ref);
		if ( $runInLoop ) {
		# 次のループに移るときに $i+=1となるので、1少なく
		# 足してみた。びみょー
			$i = $j -1;
		}
		last if ( $msg[$i] =~ /^[\r\n]{1,}$/ );
	}

	return @ret;
}

sub reportProgress {
	my $total = $_[0];
	my $finished = $_[1];
	my $percent = ($finished/$total)*100;

	printf("%3.2f\%(%d/%d) is over\n",
		$percent, $finished, $total);
}

sub usage {
	printf("usage: $0 targetdir\n");
	print <<"EOF";
useage : $0 targetdir [tofile]
	targetdir : the driectory that convert to mbox.
	tofile    : mbox filename converted from emlx dir.
EOF
	if ( $#_ > 0 ) {
		foreach (@_) {
			print "\t$_";
		}
	}

	exit(2);
}

Last-modified: 2006-01-31 13:14:20