File Coverage

blib/lib/Mail/IMAPQueue.pm
Criterion Covered Total %
statement 92 105 87.6
branch 31 54 57.4
condition 1 8 12.5
subroutine 14 14 100.0
pod 9 9 100.0
total 147 190 77.3


line stmt bran cond sub pod time code
1             =head1 LICENSE AND COPYRIGHT
2              
3             THIS PROGRAM IS SUBJECT TO THE TERMS OF THE ARTISTIC LICENSE, VERSION 2.0.
4              
5             THE FOLLOWING DISCLAIMER APPLIES TO ALL SOFTWARE CODE AND OTHER MATERIALS
6             CONTRIBUTED IN CONNECTION WITH THIS PROGRAM:
7              
8             THIS SOFTWARE IS LICENSED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
9             AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
10             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND
11             ANY WARRANTY OF NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE
12             COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13             SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
14             PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
15             BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
16             IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
17             ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
18             POSSIBILITY OF SUCH DAMAGE. THIS SOFTWARE MAY BE REDISTRIBUTED TO OTHERS ONLY
19             BY EFFECTIVELY USING THIS OR ANOTHER EQUIVALENT DISCLAIMER IN ADDITION TO ANY
20             OTHER REQUIRED LICENSE TERMS.
21              
22             ONLY THE SOFTWARE CODE AND OTHER MATERIALS CONTRIBUTED IN CONNECTION WITH THIS
23             SOFTWARE, IF ANY, THAT ARE ATTACHED TO (OR OTHERWISE ACCOMPANY) THIS SUBMISSION
24             (AND ORDINARY COURSE CONTRIBUTIONS OF FUTURES PATCHES THERETO) ARE TO BE
25             CONSIDERED A CONTRIBUTION. NO OTHER SOFTWARE CODE OR MATERIALS ARE A
26             CONTRIBUTION.
27              
28             Copyright (c) 2012 Contributor
29             All rights reserved.
30             =cut
31              
32 3     3   245612 use 5.008_001;
  3         12  
  3         129  
33 3     3   17 use strict;
  3         6  
  3         111  
34 3     3   20 use warnings;
  3         7  
  3         212  
35              
36             package Mail::IMAPQueue;
37              
38             =head1 NAME
39              
40             Mail::IMAPQueue - IMAP client extension to watch and process a mailbox as a queue
41              
42             =head1 VERSION
43              
44             Version 0.04
45              
46             =cut
47              
48             our $VERSION = '0.04';
49              
50 3     3   17 use List::Util qw(max);
  3         6  
  3         302  
51 3     3   18 use Scalar::Util qw(blessed);
  3         5  
  3         3633  
52              
53             =head1 SYNOPSIS
54              
55             =head2 Basic usage
56              
57             use Mail::IMAPClient;
58             use Mail::IMAPQueue;
59            
60             my $imap = Mail::IMAPClient->new(
61             ... # See Mail::IMAPClient documentation
62             ) or die $@;
63            
64             $imap->select('INBOX') or die $@;
65            
66             my $queue = Mail::IMAPQueue->new(
67             client => $imap
68             ) or die $@;
69            
70             while (defined(my $msg = $queue->dequeue_message())) {
71             # Do something with $msg (sequence number or UID)
72             }
73            
74             $imap->close();
75              
76             =head1 DESCRIPTION
77              
78             This module provides a way to access a mailbox with IMAP protocol,
79             regarding the mailbox as a FIFO queue so that the client code can
80             continuously process incoming email messages.
81              
82             The module utilizes L as an IMAP client interface.
83              
84             The instance of C maintains a buffer internally,
85             and loads the message sequence numbers (or UIDs) into the buffer as necessary.
86             When there are no messages in the mailbox while the buffer is empty,
87             it will wait until new messages are received in the mailbox.
88              
89             For the purpose of this module, one single mailbox (or a I) must be
90             selected at all times (C).
91              
92             It is assumed that the UID assigned to each message is I
93             as stated in RFC 3501 2.3.1.1. and that the order for any messages to start
94             appearing in the result of the C command is always consistent with
95             the order of UIDs.
96              
97             It is also assumed that the IMAP server provides the C extension (RFC 2177),
98             for real-time updates from the server.
99              
100             =head1 EXAMPLES
101              
102             =head2 Dumping messages into files
103              
104             while (defined(my $msg = $queue->dequeue_message())) {
105             $imap->message_to_file("/tmp/mails/$msg", $msg) or die $@;
106             $imap->delete_message($msg) or die $@;
107             $imap->expunge() or die $@ if $queue->is_empty;
108             }
109              
110             =head2 Managing messages with each buffer
111              
112             while (my $msg_list = $queue->dequeue_messages()) {
113             for my $msg (@$msg_list) {
114             # Do something with $msg
115             $imap->delete_message($msg) or die $@;
116             }
117             $imap->expunge() or die $@;
118             }
119              
120             =head2 Controlling timing of fetching and waiting
121              
122             while ($queue->reload_messages()) { # non-blocking
123             my $msg_list = $queue->peek_messages or die $@; # non-blocking
124             if (@$msg_list) {
125             for my $msg (@$msg_list) {
126             # Do something with $msg
127             }
128             } else {
129             $queue->attempt_idle or die $@;
130             # blocking wait for new messages, up to 30 sec.
131             }
132             }
133              
134             =head1 METHODS
135              
136             =head2 $class->new(client => $imap, ...)
137              
138             Instanciate a queue object, with the required field C set to a
139             L object.
140              
141             my $queue = Mail::IMAPQueue->new(
142             client => $imap,
143             uidnext => $known_next_uid, # default = undef
144             skip_initial => $true_or_false, # default = 0
145             idle_timeout => $seconds, # default = 30
146             ) or die $@;
147              
148             No IMAP requests are invoked with the C object during the initialization.
149             The buffer maintained by this object is initially empty.
150              
151             =over 4
152              
153             =item * client => $imap
154              
155             The underlying client object. It is assumed to be an instance of L,
156             although the type of the object is not enforced.
157              
158             =item * uidnext => $known_next_uid
159              
160             If the next message UID (the smallest UID to be used) is known (e.g. from a previous execution),
161             specify the value here.
162              
163             =item * skip_initial => $true_or_false
164              
165             Specify a true value to skip all the messages initially in the mailbox.
166             If C option is set, this option will be ignored effectively.
167              
168             =item * idle_timeout => $seconds
169              
170             Specify the timeout in seconds for the IDLE command (RFC 2177), which allows the IMAP client
171             to receive updates from the server in real-time.
172             It does I mean the method call will give up when there are no updates after the timeout,
173             but it means how frequently it will reset the IDLE command (with any blocking methods except
174             for C method, which is for one timeout round).
175              
176             =back
177              
178             =cut
179              
180             sub new {
181 5     5 1 44510 my $class = shift;
182            
183 5         107 my $self = bless {
184             client => undef,
185             buffer => [],
186             index => 0,
187             uidnext => undef,
188             skip_initial => 0,
189             idle_timeout => 30,
190             @_
191             }, $class;
192            
193 5         43 my $imap = $self->{client};
194            
195 5 50       66 unless (blessed($imap)) {
196 0         0 $@ = "Parameter 'client' must be given (Mail::IMAPClient)";
197 0         0 return undef;
198             }
199            
200 5         24 return $self;
201             }
202              
203             =head2 $queue->is_empty()
204              
205             Return 1 if the current buffer is empty, and 0 otherwise.
206              
207             =cut
208              
209             sub is_empty {
210 126     126 1 7968 my ($self) = @_;
211 126         213 return $self->{index} >= @{$self->{buffer}};
  126         668  
212             }
213              
214             =head2 $queue->dequeue_message()
215              
216             Dequeue the next message from the mailbox.
217             If the current buffer is non-empty, the next message will be removed from the buffer and returned.
218             Otherwise, the call will be blocked until there is at least one message found in the mailbox,
219             and then the first message will be removed from the loaded buffer and returned.
220              
221             The method returns the sequence number of the message
222             (or UID if the C option is turned on for the underlying client).
223             C is returned if the attempt to load the messages was failed.
224              
225             =cut
226              
227             sub dequeue_message {
228 23     23 1 8901 my ($self) = @_;
229 23         98 $self->ensure_messages;
230 23 50       63 return undef if $self->is_empty;
231            
232 23         46 my $index = $self->{index};
233 23         38 my $buffer = $self->{buffer};
234            
235 23         37 my $message = $buffer->[$index];
236 23         37 $self->{index}++;
237            
238 23         151 return $message;
239             }
240              
241             =head2 $queue->dequeue_messages()
242              
243             Dequeue the next list of messages.
244             If the current buffer is non-empty, all the messages will be removed from the buffer and returned.
245             Otherwise, the call will be blocked until there is at least one message found in the mailbox,
246             and then all the loaded messages will be removed and returned.
247              
248             In the list context, the method returns an array of the message sequence numbers
249             (or UIDs if the C option is turned on for the underlying client).
250             In the scalar context, a reference to the array is returned.
251             C is returned if the attempt to load the messages was failed.
252              
253             =cut
254              
255             sub dequeue_messages {
256 2     2 1 5 my ($self) = @_;
257 2         9 $self->ensure_messages;
258 2 50       4 return undef if $self->is_empty;
259            
260 2         5 my $index = $self->{index};
261 2         4 my $buffer = $self->{buffer};
262            
263 2         8 my $messages = [@$buffer[$index..$#$buffer]];
264 2         6 $self->{index} = @$buffer;
265            
266 2 50       12 return wantarray ? @$messages : $messages;
267             }
268              
269             =head2 $queue->peek_message()
270              
271             Retrieve the first message in the current buffer without removing the message.
272              
273             The method returns the sequence number of the message
274             (or UID if the C option is turned on for the underlying client).
275             C is returned if the current buffer is empty.
276              
277             =cut
278              
279             sub peek_message {
280 17     17 1 51 my ($self) = @_;
281 17 100       105 return undef if $self->is_empty;
282            
283 10         24 my $index = $self->{index};
284 10         21 my $buffer = $self->{buffer};
285            
286 10         52 return $buffer->[$index];
287             }
288              
289             =head2 $queue->peek_messages()
290              
291             Retrieve all the messages in the current buffer without removing the messages.
292              
293             In the list context, the method returns an array of the message sequence numbers
294             (or UIDs if the C option is turned on for the underlying client).
295             In the scalar context, a reference to the array is returned.
296              
297             =cut
298              
299             sub peek_messages {
300 18     18 1 12657 my ($self) = @_;
301 18 100       216 return [] if $self->is_empty;
302            
303 9         997 my $index = $self->{index};
304 9         18 my $buffer = $self->{buffer};
305            
306 9         44 my $messages = [@$buffer[$index..$#$buffer]];
307            
308 9 50       70 return wantarray ? @$messages : $messages;
309             }
310              
311             =head2 $queue->ensure_messages()
312              
313             The call is blocked until there is at least one message loaded into the buffer.
314              
315             The method returns the object itself if successful, and C otherwise.
316              
317             =cut
318              
319             sub ensure_messages {
320 30     30 1 3896 my ($self) = @_;
321            
322 30 100       70 if ($self->is_empty) {
323 11         26 while (1) {
324 15 50       74 $self->reload_messages or return undef;
325            
326 15 100       83 if ($self->is_empty) {
327 4 50       22 $self->attempt_idle() or return undef;
328             } else {
329             # success
330 11         47 return $self;
331             }
332             }
333             }
334            
335 19         41 return $self;
336             }
337              
338             =head2 $queue->attempt_idle()
339              
340             Attempt the IDLE command so that the call is blocked until there are any updates in the mailbox
341             or the timeout (default = 30 sec.) has elapsed.
342              
343             The method returns the object itself if successful, and C otherwise.
344             If the timeout has elapsed gracefully, it is considered to be a success.
345              
346             =cut
347              
348             sub attempt_idle {
349 4     4 1 26 my ($self) = @_;
350 4         12 my $imap = $self->{client};
351 4   50     19 my $idle_timeout = $self->{idle_timeout} || 30;
352            
353 4         10 eval {
354 4 50       25 my $idle_tag = $imap->idle or die $imap;
355            
356 4         4921 my $idle_data = $imap->idle_data($idle_timeout);
357             # do not die even if this fails; always send DONE anyway
358            
359 4 50       155934 $imap->done($idle_tag) or die $imap;
360             };
361            
362 4 50       2464 if ($@) {
363 0 0 0     0 if (ref $@ && $@ == $imap) {
364 0 0       0 $imap->reconnect or do {
365 0         0 $@ = "Disconnected while attempting IDLE";
366 0         0 return undef;
367             };
368             } else {
369 0         0 return undef;
370             }
371             }
372            
373 4         28 return $self;
374             }
375              
376             =head2 $queue->reload_messages()
377              
378             Discard the current buffer, and attempt to load any messages from the mailbox to the buffer.
379             The call is not blocked (except for the usual socket wait for any server response).
380              
381             The method returns the object itself if successful, and C otherwise.
382              
383             Note:
384             Even if no new messages are loaded, it is a success as long as the server has responded properly.
385             In order to test the last result of loading, the C method can be used.
386              
387             =cut
388              
389             sub reload_messages {
390 18     18 1 58 my ($self) = @_;
391            
392 18         49 my $uidnext = $self->{uidnext};
393 18         46 my $buffer = [];
394            
395 18         132 TRY: {
396 18         34 my $imap = $self->{client};
397            
398 18 50       88 unless ($imap->IsSelected) {
399 0         0 $@ = "Folder must be selected";
400 0         0 return undef;
401             }
402            
403 18         205 eval {
404 18         31 my $loaded = 0;
405            
406 18 100       54 unless (defined $uidnext) {
407             # Initially $uidnext is undef (except it was set explicitly)
408 5 100       17 if ($self->{skip_initial}) {
409 1 50       5 $uidnext = $imap->uidnext($imap->Folder) or die $imap;
410 1         38428 $self->{uidnext} = $uidnext;
411             } else {
412 4 50       47 $buffer = $imap->messages or die $imap;
413 4         159088 $loaded = 1;
414             }
415             }
416            
417 18 100       73 unless ($loaded) {
418 14 50       215 $buffer = $imap->search("UID $uidnext:*") or die $imap;
419 14         552729 $buffer = [grep {$uidnext <= $_} @$buffer];
  23         138  
420             }
421             };
422            
423 18 50       104 if ($@) {
424 0 0 0     0 if (ref $@ && $@ == $imap) {
425 0 0       0 $imap->reconnect or return undef;
426 0         0 redo TRY;
427             } else {
428 0         0 return undef;
429             }
430             }
431             }
432            
433 18 100       86 if (@$buffer > 0) {
434 12         89 $uidnext = max(@$buffer) + 1;
435 12         51 $self->{uidnext} = $uidnext;
436             }
437            
438 18         61 $self->{buffer} = $buffer;
439 18         52 $self->{index} = 0;
440            
441 18         93 return $self;
442             }
443              
444             =head1 AUTHOR
445              
446             Mahiro Ando, C<< >>
447              
448             =head1 BUGS
449              
450             Please report any bugs or feature requests to C, or through
451             the web interface at L. I will be notified, and then you'll
452             automatically be notified of progress on your bug as I make changes.
453              
454             =head1 SUPPORT
455              
456             You can find documentation for this module with the perldoc command.
457              
458             perldoc Mail::IMAPQueue
459              
460             You can also look for information at:
461              
462             =over 4
463              
464             =item * GitHub repository (report bugs here)
465              
466             L
467              
468             =item * RT: CPAN's request tracker (report bugs here, alternatively)
469              
470             L
471              
472             =item * AnnoCPAN: Annotated CPAN documentation
473              
474             L
475              
476             =item * CPAN Ratings
477              
478             L
479              
480             =item * Search CPAN
481              
482             L
483              
484             =back
485              
486             =head1 ACKNOWLEDGEMENTS
487              
488             The initial package was created by L v1.58.
489              
490             This module utilizes L as a client library interface for IMAP.
491              
492             =cut
493              
494             1; # End of Mail::IMAPQueue