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.600;
2             # ABSTRACT: deliver mail to a maildir on disk
3              
4 2     2   81981 use Moo;
  2         9231  
  2         9  
5             with 'Email::Sender::Transport';
6              
7 2     2   1446 use Errno ();
  2         4  
  2         28  
8 2     2   7 use Fcntl;
  2         3  
  2         448  
9 2     2   12 use File::Path 2.06;
  2         37  
  2         100  
10 2     2   10 use File::Spec;
  2         11  
  2         48  
11              
12 2     2   791 use Sys::Hostname;
  2         1736  
  2         98  
13              
14 2     2   402 use MooX::Types::MooseLike::Base qw(Bool);
  2         5436  
  2         163  
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   13 use Moo;
  2         3  
  2         9  
42 2     2   515 use MooX::Types::MooseLike::Base qw(Str);
  2         4  
  2         132  
43             extends 'Email::Sender::Success';
44             has filename => (
45             is => 'ro',
46             isa => Str,
47             required => 1,
48             );
49 2     2   12 no Moo;
  2         3  
  2         8  
50             }
51              
52              
53             my $HOSTNAME;
54 2     2   478 BEGIN { ($HOSTNAME = hostname) =~ s/\..*//; }
55 2     2   4 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 5 my ($self, $email, $env) = @_;
74              
75 2         3 my $dupe = Email::Abstract->new(\do { $email->as_string });
  2         7  
76              
77 2 50       395 if ($self->add_envelope_headers) {
78             $dupe->set_header('X-Email-Sender-From' =>
79 2 50       10 (defined $env->{from} ? $env->{from} : '-'),
80             );
81              
82 2         151 my @to = grep {; defined } @{ $env->{to} };
  2         7  
  2         4  
83 2 50       19 $dupe->set_header('X-Email-Sender-To' => (@to ? @to : '-'));
84             }
85              
86 2         113 $self->_ensure_maildir_exists;
87              
88 2 50       14 $self->_add_lines_header($dupe) if $self->add_lines_header;
89 2         117 $self->_update_time;
90              
91 2         4 my $fn = $self->_deliver_email($dupe);
92              
93 2         37 return Email::Sender::Success::MaildirSuccess->new({
94             filename => $fn,
95             });
96             }
97              
98             sub _ensure_maildir_exists {
99 2     2   4 my ($self) = @_;
100              
101 2         5 for my $dir (qw(cur tmp new)) {
102 6         77 my $subdir = File::Spec->catdir($self->dir, $dir);
103 6 100       81 next if -d $subdir;
104              
105 3 50 33     371 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   6 my ($class, $email) = @_;
112 2 50       34 return if $email->get_header("Lines");
113 2         123 my $lines = $email->get_body =~ tr/\n/\n/;
114 2         57 $email->set_header("Lines", $lines);
115             }
116              
117             sub _update_time {
118 2     2   21 my $time = time;
119 2 100       8 if ($MAILDIR_TIME != $time) {
120 1         1 $MAILDIR_TIME = $time;
121 1         3 $MAILDIR_COUNTER = 0;
122             } else {
123 1         3 $MAILDIR_COUNTER++;
124             }
125             }
126              
127             sub _deliver_email {
128 2     2   4 my ($self, $email) = @_;
129              
130 2         6 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         8 my $string = $email->as_string;
136 2 50       261 $string =~ s/\x0D\x0A/\x0A/g unless $^O eq 'MSWin32';
137 2 50       28 print $tmp_fh $string
138             or Email::Sender::Failure->throw("could not write to $tmp_filename: $!");
139              
140 2 50       69 close $tmp_fh
141             or Email::Sender::Failure->throw("error closing $tmp_filename: $!");
142              
143 2         30 my $target_name = File::Spec->catfile($self->dir, 'new', $tmp_filename);
144              
145 2         74 my $ok = rename(
146             File::Spec->catfile($self->dir, 'tmp', $tmp_filename),
147             $target_name,
148             );
149              
150 2 50       10 Email::Sender::Failure->throw("could not move $tmp_filename from tmp to new")
151             unless $ok;
152              
153 2         9 return $target_name;
154             }
155              
156             sub _delivery_fh {
157 2     2   3 my ($self) = @_;
158              
159 2         5 my $hostname = $self->_hostname;
160              
161 2         4 my ($filename, $fh);
162 2         5 until ($fh) {
163 2         8 $filename = join q{.}, $MAILDIR_TIME, $$, ++$MAILDIR_COUNTER, $hostname;
164 2         19 my $filespec = File::Spec->catfile($self->dir, 'tmp', $filename);
165 2         143 sysopen $fh, $filespec, O_CREAT|O_EXCL|O_WRONLY;
166 2         10 binmode $fh;
167             Email::Sender::Failure->throw("cannot create $filespec for delivery: $!")
168 2 0 33     8 unless $fh or $!{EEXIST};
169             }
170              
171 2         8 return ($filename, $fh);
172             }
173              
174 2     2   1509 no Moo;
  2         10  
  2         7  
175             1;
176              
177             __END__