File Coverage

blib/lib/AnyEvent/IMAP.pm
Criterion Covered Total %
statement 27 135 20.0
branch 0 22 0.0
condition 0 3 0.0
subroutine 9 35 25.7
pod 3 14 21.4
total 39 209 18.6


line stmt bran cond sub pod time code
1             package AnyEvent::IMAP;
2 2     2   64476 use strict;
  2         5  
  2         88  
3 2     2   12 use warnings;
  2         4  
  2         65  
4 2     2   52 use 5.010001;
  2         12  
  2         154  
5             our $VERSION = '0.04';
6              
7 2     2   2144 use parent qw(Object::Event);
  2         931  
  2         11  
8              
9 2     2   65974 use AnyEvent::Socket;
  2         33724  
  2         307  
10 2     2   2576 use AnyEvent::Handle;
  2         17718  
  2         77  
11 2     2   2244 use AnyEvent::TLS;
  2         32226  
  2         86  
12 2     2   1235 use Mail::IMAP::Util;
  2         5  
  2         149  
13              
14 2     2   1692 use Mouse;
  2         113249  
  2         12  
15              
16             has 'socket' => (is => 'ro');
17             has 'ssl' => (is => 'rw', isa => 'Bool');
18             has 'host' => (is => 'rw');
19             has 'port' => (is => 'rw');
20             has 'user' => (is => 'rw');
21             has 'pass' => (is => 'rw');
22             has id => (is => 'ro', default => sub { 1 });
23              
24             sub connect {
25 0     0 1   my ($self) = @_;
26              
27 0 0         if ($self->{socket}) {
28 0           $self->disconnect("reconnect requested");
29             }
30              
31 0           my $cv = AE::cv();
32 0           $self->{accumulator} = [];
33 0           $self->{lineparts} = [];
34             $self->{socket} = AnyEvent::Handle->new(
35             connect => [$self->host, $self->port],
36             ($self->ssl ? (tls => 'connect') : ()),
37             on_connect => sub {
38 0     0     my ($handle, $host, $port, $retry) = @_;
39             $self->{socket}->push_read(
40             line => "\r\n", sub {
41 0           my ($handle, $line) = @_;
42 0 0         if ($line =~ /^\*\s+OK/) {
43 0           $cv->send(1, $line);
44 0           $self->event('connect');
45             } else {
46 0           $cv->send(0, $line);
47 0           $self->event('connect_error');
48             }
49             },
50 0           );
51             },
52             on_starttls => sub {
53 0     0     $self->event('starttls');
54             },
55             on_eof => sub {
56 0     0     $self->disconnect("EOF from server $self->{host}: $self->{port}");
57             },
58             on_error => sub {
59 0     0     $self->disconnect("Error in connection to server $self->{host}: $self->{port}: $!");
60             },
61             on_drain => sub {
62 0     0     $self->event('buffer_empty');
63             },
64             on_read => sub {
65             $self->{socket}->push_read('regex' => qr{((?:^.+?\r\n)*)(NIC\d+)\s+([A-Z_]+)[^\r\n]+\r\n}, sub {
66 0           my ($handle, $res) = @_;
67 0           $self->event('recv', $res);
68 0           my $id = $2;
69 0           my $status = $3;
70 0 0         my $ok = $status eq 'OK' ? 1 : 0;
71 0 0         if (my $cv = delete $self->{cvmap}->{$id}) {
72 0           my @lines = split /\r\n/, $res;
73 0           pop @lines; # remove last line
74 0 0 0       if ($ok && (my $filter = delete $self->{filters}->{$id})) {
75 0           $res = $filter->(@lines)
76             } else {
77 0           $res = \@lines;
78             }
79 0           $cv->send($ok, $res);
80             }
81 0     0     });
82             },
83 0 0         );
84 0           return $cv;
85             }
86              
87             sub login {
88 0     0 0   my $self = shift;
89 0           my $user = imap_string_quote($self->user);
90 0           my $pass = imap_string_quote($self->pass);
91 0           my ($id, $cv) = $self->send_cmd("LOGIN $user $pass");
92 0           return $cv;
93             }
94              
95             sub disconnect {
96 0     0 1   my ($self, $reason) = @_;
97 0           delete $self->{con_guard};
98 0           delete $self->{socket};
99 0           $self->event (disconnect => $reason);
100             }
101              
102             sub is_connected {
103 0     0 0   my ($self) = @_;
104 0 0         $self->{socket} && $self->{connected}
105             }
106              
107             sub send_cmd {
108 0     0 1   my ($self, $cmd, $filter) = @_;
109 0           my $id = "NIC" . $self->{id}++;
110 0 0         return unless $self->{socket};
111              
112 0           my $cv = AE::cv();
113 0           my $msg = "$id $cmd\r\n";
114 0           $self->event('send', $msg);
115 0           $self->{socket}->push_write($msg);
116 0           $self->{cvmap}->{$id} = $cv;
117 0 0         if ($filter) {
118 0           $self->{filters}->{$id} = $filter;
119             }
120 0           return ($id, $cv);
121             }
122              
123             sub capability {
124 0     0 0   my ($self) = @_;
125             my ($id, $cv) = $self->send_cmd('CAPABILITY', sub {
126 0 0   0     if ($_[0] =~ /^\*\s+CAPABILITY\s+(.*?)\s*$/) {
127 0           return [ split(/\s+/, $1) ];
128             }
129 0           return;
130 0           });
131 0           return $cv;
132             }
133              
134             sub folders {
135 0     0 0   my ($self) = @_;
136             my ($id, $cv) = $self->send_cmd('LIST "" "*"', sub {
137 0     0     [map { imap_parse_tokens([$_])->[4] } @_];
  0            
138 0           });
139 0           return $cv;
140             }
141              
142             sub status {
143 0     0 0   my ($self, $folder) = @_;
144 0           my $all_cv = AE::cv();
145 0           my $cmd = sprintf("%s (MESSAGES RECENT UNSEEN UIDNEXT UIDVALIDITY)",
146             imap_string_quote($folder));
147             my ($id, $cv) = $self->send_cmd("STATUS $cmd", sub {
148 0     0     +{ @{imap_parse_tokens([$_[0]])->[3]} };
  0            
149 0           });
150 0           return $cv;
151             }
152              
153             sub status_multi {
154 0     0 0   my ($self, $folders) = @_;
155 0           my $all_cv = AE::cv();
156 0           my %ret;
157 0     0     $all_cv->begin(sub { shift->send(1, \%ret); });
  0            
158 0           for my $folder (@$folders) {
159 0           $all_cv->begin();
160             $self->status($folder)->cb(sub {
161 0     0     my ($ok, $ret) = shift->recv;
162 0 0         $ret{$folder} = $ret if $ok;
163 0           $all_cv->end();
164 0           });
165             }
166 0           $all_cv->end;
167 0           return $all_cv;
168             }
169              
170             sub select {
171 0     0 0   my ($self, $folder) = @_;
172 0           $folder = imap_string_quote($folder);
173 0           my ($id, $cv) = $self->send_cmd("SELECT $folder");
174 0           return $cv;
175             }
176              
177             sub fetch {
178 0     0 0   my ($self, $query) = @_;
179             my ($id, $cv) = $self->send_cmd("FETCH $query", sub {
180             # in form: [ '*', ID, 'FETCH', [ tokens ]]
181 0     0     [map { +{@{$_->[3]}} } grep { $_->[2] eq 'FETCH' } map {imap_parse_tokens([$_])} @_]
  0            
  0            
  0            
  0            
182 0           });
183 0           return $cv;
184             }
185              
186             sub expunge {
187 0     0 0   my ($self) = @_;
188 0           my ($id, $cv) = $self->send_cmd('EXPUNGE');
189 0           return $cv;
190             }
191              
192             sub create_folder {
193 0     0 0   my ($self, $folder) = @_;
194 0           $folder = imap_string_quote($folder);
195 0           my ($id, $cv) = $self->send_cmd("CREATE $folder");
196 0           return $cv;
197             }
198              
199             sub noop {
200 0     0 0   my ($self) = @_;
201 0           my ($id, $cv) = $self->send_cmd('NOOP');
202 0           return $cv;
203             }
204              
205             # TODO:
206             # add_flags
207             # copy
208             # search('ALL')
209             # get_part_body
210              
211             1;
212             __END__