File Coverage

blib/lib/Email/Sender/Transport/Mbox.pm
Criterion Covered Total %
statement 62 64 96.8
branch 11 20 55.0
condition 1 6 16.6
subroutine 15 15 100.0
pod 0 1 0.0
total 89 106 83.9


line stmt bran cond sub pod time code
1             package Email::Sender::Transport::Mbox 2.600;
2             # ABSTRACT: deliver mail to an mbox on disk
3              
4 2     2   102372 use Moo;
  2         10249  
  2         10  
5             with 'Email::Sender::Transport';
6              
7 2     2   1428 use Carp;
  2         3  
  2         98  
8 2     2   10 use File::Path;
  2         4  
  2         91  
9 2     2   17 use File::Basename;
  2         4  
  2         142  
10 2     2   856 use IO::File 1.11; # binmode
  2         7908  
  2         206  
11 2     2   371 use Email::Simple 1.998; # needed for ->header_obj
  2         4191  
  2         44  
12 2     2   9 use Fcntl ':flock';
  2         5  
  2         1489  
13              
14             #pod =head1 DESCRIPTION
15             #pod
16             #pod This transport delivers into an mbox. The mbox file may be given by the
17             #pod F argument to the constructor, and defaults to F.
18             #pod
19             #pod The transport I assumes that the mbox is in F format, but
20             #pod this may change or be configurable in the future.
21             #pod
22             #pod =cut
23              
24             has 'filename' => (is => 'ro', default => sub { 'mbox' }, required => 1);
25              
26             sub send_email {
27 2     2 0 4 my ($self, $email, $env) = @_;
28              
29 2         7 my $filename = $self->filename;
30 2         6 my $fh = $self->_open_fh($filename);
31              
32 2         4 my $ok = eval {
33 2 100       9 if ($fh->tell > 0) {
34 1 50       33 $fh->print("\n") or Carp::confess("couldn't write to $filename: $!");
35             }
36              
37 2 50       32 $fh->print($self->_from_line($email, $env))
38             or Carp::confess("couldn't write to $filename: $!");
39              
40 2 50       32 $fh->print($self->_escape_from_body($email))
41             or Carp::confess("couldn't write to $filename: $!");
42              
43             # This will make streaming a bit more annoying. -- rjbs, 2007-05-25
44 2 50 0     88 $fh->print("\n")
45             or Carp::confess("couldn't write to $filename: $!")
46             unless $email->as_string =~ /\n$/;
47              
48 2 50       125 $self->_close_fh($fh)
49             or Carp::confess "couldn't close file $filename: $!";
50              
51 2         37 1;
52             };
53              
54 2 50       22 die unless $ok;
55             # Email::Sender::Failure->throw($@ || 'unknown error') unless $ok;
56              
57 2         8 return $self->success;
58             }
59              
60             sub _open_fh {
61 2     2   3 my ($class, $file) = @_;
62 2         76 my $dir = dirname($file);
63 2 50 33     87 Carp::confess "couldn't make path $dir: $!" if not -d $dir or mkpath($dir);
64              
65 2 50       15 my $fh = IO::File->new($file, '>>')
66             or Carp::confess "couldn't open $file for appending: $!";
67              
68 2         211 $fh->binmode(':raw');
69              
70 2         24 $class->_getlock($fh, $file);
71              
72 2         14 $fh->seek(0, 2);
73 2         23 return $fh;
74             }
75              
76             sub _close_fh {
77 2     2   4 my ($class, $fh, $file) = @_;
78 2         5 $class->_unlock($fh);
79 2         13 return $fh->close;
80             }
81              
82             sub _escape_from_body {
83 2     2   5 my ($class, $email) = @_;
84              
85 2         6 my $body = $email->get_body;
86 2         90 $body =~ s/^(From )/>$1/gm;
87              
88 2         6 my $simple = $email->cast('Email::Simple');
89 2         368 return $simple->header_obj->as_string . $simple->crlf . $body;
90             }
91              
92             sub _from_line {
93 2     2   5 my ($class, $email, $envelope) = @_;
94              
95 2         70 my $fromtime = localtime;
96 2         13 $fromtime =~ s/(:\d\d) \S+ (\d{4})$/$1 $2/; # strip timezone.
97 2         17 return "From $envelope->{from} $fromtime\n";
98             }
99              
100             sub _getlock {
101 2     2   6 my ($class, $fh, $fn) = @_;
102 2         5 for (1 .. 10) {
103 2 50       23 return 1 if flock($fh, LOCK_EX | LOCK_NB);
104 0         0 sleep $_;
105             }
106 0         0 Carp::confess "couldn't lock file $fn";
107             }
108              
109             sub _unlock {
110 2     2   4 my ($class, $fh) = @_;
111 2         63 flock($fh, LOCK_UN);
112             }
113              
114 2     2   14 no Moo;
  2         4  
  2         18  
115             1;
116              
117             __END__