File Coverage

blib/lib/Email/Sender/Transport/Maildir.pm
Criterion Covered Total %
statement 83 83 100.0
branch 14 26 53.8
condition 2 6 33.3
subroutine 19 19 100.0
pod 0 1 0.0
total 118 135 87.4


line stmt bran cond sub pod time code
1             package Email::Sender::Transport::Maildir 2.500;
2             # ABSTRACT: deliver mail to a maildir on disk
3              
4 2     2   108565 use Moo;
  2         13125  
  2         13  
5             with 'Email::Sender::Transport';
6              
7 2     2   2115 use Errno ();
  2         5  
  2         34  
8 2     2   10 use Fcntl;
  2         4  
  2         612  
9 2     2   15 use File::Path 2.06;
  2         62  
  2         135  
10 2     2   14 use File::Spec;
  2         5  
  2         59  
11              
12 2     2   1161 use Sys::Hostname;
  2         2311  
  2         127  
13              
14 2     2   595 use MooX::Types::MooseLike::Base qw(Bool);
  2         7366  
  2         208  
15              
16             #pod =head1 DESCRIPTION
17             #pod
18             #pod This transport delivers into a maildir. The maildir's location may be given as
19             #pod the F argument to the constructor, and defaults to F in the
20             #pod current directory (at the time of transport initialization).
21             #pod
22             #pod If the directory does not exist, it will be created.
23             #pod
24             #pod By default, three headers will be added:
25             #pod
26             #pod * X-Email-Sender-From - the envelope sender
27             #pod * X-Email-Sender-To - the envelope recipients (one header per rcpt)
28             #pod * Lines - the number of lines in the body
29             #pod
30             #pod These can be controlled with the C and
31             #pod C constructor arguments.
32             #pod
33             #pod The L object returned on success has a C
34             #pod method that returns the filename to which the message was delivered.
35             #pod
36             #pod =cut
37              
38             {
39             package
40             Email::Sender::Success::MaildirSuccess;
41 2     2   22 use Moo;
  2         4  
  2         16  
42 2     2   784 use MooX::Types::MooseLike::Base qw(Str);
  2         5  
  2         176  
43             extends 'Email::Sender::Success';
44             has filename => (
45             is => 'ro',
46             isa => Str,
47             required => 1,
48             );
49 2     2   14 no Moo;
  2         5  
  2         12  
50             }
51              
52              
53             my $HOSTNAME;
54 2     2   683 BEGIN { ($HOSTNAME = hostname) =~ s/\..*//; }
55 2     2   5 sub _hostname { $HOSTNAME }
56              
57             my $MAILDIR_TIME = 0;
58             my $MAILDIR_COUNTER = 0;
59              
60             has [ qw(add_lines_header add_envelope_headers) ] => (
61             is => 'ro',
62             isa => Bool,
63             default => sub { 1 },
64             );
65              
66             has dir => (
67             is => 'ro',
68             required => 1,
69             default => sub { File::Spec->catdir(File::Spec->curdir, 'Maildir') },
70             );
71              
72             sub send_email {
73 2     2 0 8 my ($self, $email, $env) = @_;
74              
75 2         5 my $dupe = Email::Abstract->new(\do { $email->as_string });
  2         11  
76              
77 2 50       560 if ($self->add_envelope_headers) {
78             $dupe->set_header('X-Email-Sender-From' =>
79 2 50       16 (defined $env->{from} ? $env->{from} : '-'),
80             );
81              
82 2         201 my @to = grep {; defined } @{ $env->{to} };
  2         8  
  2         7  
83 2 50       10 $dupe->set_header('X-Email-Sender-To' => (@to ? @to : '-'));
84             }
85              
86 2         141 $self->_ensure_maildir_exists;
87              
88 2 50       21 $self->_add_lines_header($dupe) if $self->add_lines_header;
89 2         149 $self->_update_time;
90              
91 2         9 my $fn = $self->_deliver_email($dupe);
92              
93 2         57 return Email::Sender::Success::MaildirSuccess->new({
94             filename => $fn,
95             });
96             }
97              
98             sub _ensure_maildir_exists {
99 2     2   5 my ($self) = @_;
100              
101 2         7 for my $dir (qw(cur tmp new)) {
102 6         153 my $subdir = File::Spec->catdir($self->dir, $dir);
103 6 100       173 next if -d $subdir;
104              
105 3 50 33     522 Email::Sender::Failure->throw("couldn't create $subdir: $!")
106             unless File::Path::make_path($subdir) || -d $subdir;
107             }
108             }
109              
110             sub _add_lines_header {
111 2     2   7 my ($class, $email) = @_;
112 2 50       49 return if $email->get_header("Lines");
113 2         170 my $lines = $email->get_body =~ tr/\n/\n/;
114 2         77 $email->set_header("Lines", $lines);
115             }
116              
117             sub _update_time {
118 2     2   29 my $time = time;
119 2 100       11 if ($MAILDIR_TIME != $time) {
120 1         3 $MAILDIR_TIME = $time;
121 1         3 $MAILDIR_COUNTER = 0;
122             } else {
123 1         4 $MAILDIR_COUNTER++;
124             }
125             }
126              
127             sub _deliver_email {
128 2     2   8 my ($self, $email) = @_;
129              
130 2         8 my ($tmp_filename, $tmp_fh) = $self->_delivery_fh;
131              
132             # if (eval { $email->can('stream_to') }) {
133             # eval { $mail->stream_to($fh); 1 } or return;
134             #} else {
135 2         10 my $string = $email->as_string;
136 2 50       393 $string =~ s/\x0D\x0A/\x0A/g unless $^O eq 'MSWin32';
137 2 50       56 print $tmp_fh $string
138             or Email::Sender::Failure->throw("could not write to $tmp_filename: $!");
139              
140 2 50       113 close $tmp_fh
141             or Email::Sender::Failure->throw("error closing $tmp_filename: $!");
142              
143 2         43 my $target_name = File::Spec->catfile($self->dir, 'new', $tmp_filename);
144              
145 2         118 my $ok = rename(
146             File::Spec->catfile($self->dir, 'tmp', $tmp_filename),
147             $target_name,
148             );
149              
150 2 50       14 Email::Sender::Failure->throw("could not move $tmp_filename from tmp to new")
151             unless $ok;
152              
153 2         14 return $target_name;
154             }
155              
156             sub _delivery_fh {
157 2     2   6 my ($self) = @_;
158              
159 2         10 my $hostname = $self->_hostname;
160              
161 2         6 my ($filename, $fh);
162 2         8 until ($fh) {
163 2         13 $filename = join q{.}, $MAILDIR_TIME, $$, ++$MAILDIR_COUNTER, $hostname;
164 2         27 my $filespec = File::Spec->catfile($self->dir, 'tmp', $filename);
165 2         218 sysopen $fh, $filespec, O_CREAT|O_EXCL|O_WRONLY;
166 2         17 binmode $fh;
167             Email::Sender::Failure->throw("cannot create $filespec for delivery: $!")
168 2 0 33     14 unless $fh or $!{EEXIST};
169             }
170              
171 2         9 return ($filename, $fh);
172             }
173              
174 2     2   2022 no Moo;
  2         6  
  2         8  
175             1;
176              
177             __END__