File Coverage

blib/lib/POE/Component/Client/NNTP/Tail.pm
Criterion Covered Total %
statement 117 154 75.9
branch 24 56 42.8
condition 4 4 100.0
subroutine 22 28 78.5
pod 5 5 100.0
total 172 247 69.6


line stmt bran cond sub pod time code
1             package POE::Component::Client::NNTP::Tail;
2              
3 2     2   298107 use 5.006;
  2         19  
4 2     2   10 use strict;
  2         4  
  2         50  
5 2     2   10 use warnings;
  2         4  
  2         91  
6             # ABSTRACT: Sends events for new articles posted to an NNTP newsgroup
7             # VERSION
8              
9             our $VERSION = '0.04';
10              
11 2     2   25 use Carp::POE;
  2         4  
  2         130  
12 2     2   1073 use Params::Validate;
  2         18832  
  2         147  
13 2     2   15 use POE qw(Component::Client::NNTP);
  2         5  
  2         12  
14              
15             my %spawn_args = (
16             # required
17             Group => 1,
18             NNTPServer => 1,
19             # optional with defaults
20             Interval => { default => 60 },
21             TimeOut => { default => 30 },
22             # purely optional
23             Port => 0,
24             LocalAddr => 0,
25             Alias => 0,
26             Debug => 0,
27             );
28              
29             sub spawn {
30 2     2 1 10554 my $class = shift;
31 2         78 my %opts = validate( @_, \%spawn_args );
32              
33 2         96 POE::Session->create(
34             heap => \%opts,
35             package_states => [
36             # nntp component events
37             $class => {
38             nntp_connected => '_nntp_connected',
39             nntp_registered => '_nntp_registered',
40             nntp_socketerr => '_nntp_socketerr',
41             nntp_disconnected => '_nntp_disconnected',
42             nntp_200 => '_nntp_server_ready',
43             nntp_201 => '_nntp_server_ready',
44             nntp_211 => '_nntp_group_selected',
45             nntp_220 => '_nntp_got_article',
46             nntp_221 => '_nntp_got_head',
47             nntp_411 => '_nntp_no_group',
48             nntp_423 => '_nntp_no_article',
49             nntp_430 => '_nntp_no_article',
50             nntp_503 => '_nntp_503_error',
51             },
52             # session events
53             $class => [ qw( _start _stop _child ) ],
54             # internal events
55             $class => [ qw( _poll _reconnect ) ],
56             # API events
57             $class => [ qw( register unregister get_article shutdown ) ],
58             ],
59             );
60             }
61              
62             sub _debug {
63 0     0   0 my $where = (caller(1))[3];
64 0         0 $where =~ s{.*::}{P::C::C::N::T::};
65 0         0 my @args = @_[ARG0 .. $#_];
66 0         0 for ( @args ) {
67 0 0       0 $_ = 'undef' if not defined $_;
68             }
69 0 0       0 my $args = @args ? join( " " => "", (map { "'$_'" } @args), "" ) : "";
  0         0  
70 0         0 warn "$where->($args)\n";
71 0         0 return;
72             }
73              
74             #--------------------------------------------------------------------------#
75             # session events
76             #--------------------------------------------------------------------------#
77              
78             sub _start {
79 2     2   939 my ( $kernel, $session, $heap ) = @_[KERNEL, SESSION, HEAP];
80 2 50       10 &_debug if $heap->{Debug};
81              
82             # alias defaults to group name if not otherwise set
83 2 100       8 $heap->{Alias} = $heap->{Group} unless exists $heap->{Alias};
84 2         12 $kernel->alias_set($heap->{Alias});
85              
86             # setup NNTP including optional args if defined;
87 2         70 my %nntp_args;
88 2         8 for my $k ( qw/NNTPServer Port LocalAddr TimeOut/ ) {
89 8 100       34 $nntp_args{$k} = $heap->{$k} if exists $heap->{$k};
90             }
91 2         16 my $alias = "NNTP-Client-" . $session->ID;
92 2         26 $heap->{nntp} = POE::Component::Client::NNTP->spawn($alias,\%nntp_args);
93 2         179 $heap->{nntp_id} = $heap->{nntp}->session_id;
94              
95             # start NNTP connection
96 2         15 $kernel->yield( '_reconnect' );
97 2         141 return;
98             }
99              
100             # ignore these
101             sub _child {
102 4     4   4319 my ( $kernel, $heap ) = @_[KERNEL, HEAP];
103 4 50       22 &_debug if $heap->{Debug};
104             }
105              
106             sub _stop {
107 2     2   738 my ( $kernel, $session, $heap ) = @_[KERNEL, SESSION, HEAP];
108 2 50       12 &_debug if $heap->{Debug};
109 2 50       12 $kernel->call( $session, 'shutdown' ) if $heap->{nntp};
110 2         8 return;
111             }
112              
113             #--------------------------------------------------------------------------#
114             # events from our clients
115             #--------------------------------------------------------------------------#
116              
117             #--------------------------------------------------------------------------#
118             # register -- [EVENT]
119             #
120             # EVENT - event to dispatch to the registered session on receipt of new
121             # headers; defaults to "new_header"
122             #--------------------------------------------------------------------------#
123              
124             sub register {
125 2     2 1 257 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
126 2 50       11 &_debug if $heap->{Debug};
127 2   100     13 my ($event) = $_[ARG0] || 'new_header';
128 2         6 $kernel->refcount_increment( $sender->ID, __PACKAGE__ );
129 2         95 $heap->{listeners}{$sender->ID} = $event;
130 2         14 return;
131             }
132              
133             #--------------------------------------------------------------------------#
134             # unregister --
135             #
136             # removes sender registration
137             #--------------------------------------------------------------------------#
138              
139             sub unregister {
140 2     2 1 1776861 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
141 2 50       14 &_debug if $heap->{Debug};
142 2         13 $kernel->refcount_decrement( $sender->ID, __PACKAGE__ );
143 2         176 delete $heap->{listeners}{$sender->ID};
144 2         23 return;
145             }
146              
147             #--------------------------------------------------------------------------#
148             # get_article -- ARTICLE_ID, EVENT
149             #
150             # request ARTICLE_ID be retrieved and returned via EVENT or 'got_article
151             # if not specified
152             #--------------------------------------------------------------------------#
153              
154             sub get_article {
155 8     8 1 9120 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
156 8 50       31 &_debug if $heap->{Debug};
157 8         24 my ($article_id, $return_event) = @_[ARG0, ARG1];
158 8   100     35 $return_event ||= 'got_article';
159             # store requesting session and desired return event
160 8         15 push @{$heap->{requests}{$article_id}}, [$sender, $return_event];
  8         38  
161 8         35 $kernel->post( $heap->{nntp_id} => article => $article_id );
162 8         1013 return;
163             }
164              
165             #--------------------------------------------------------------------------#
166             # shudown
167             #--------------------------------------------------------------------------#
168              
169             sub shutdown {
170 2     2 1 231 my ( $kernel, $heap ) = @_[KERNEL, HEAP];
171 2 50       14 &_debug if $heap->{Debug};
172             # unregister anyone that didn't do it themselves
173 2         8 for my $listener ( keys %{ $heap->{listeners} } ) {
  2         13  
174 0         0 $kernel->refcount_decrement( $listener, __PACKAGE__ );
175 0         0 delete $heap->{listeners}{$listener};
176             }
177 2         17 $kernel->alarm_remove_all();
178 2         314 $kernel->call( $heap->{nntp_id} => 'unregister' => 'all' );
179 2         385 $kernel->call( $heap->{nntp_id} => 'shutdown' );
180 2         1812 delete $heap->{nntp};
181 2         12 $kernel->alias_remove($_) for $kernel->alias_list;
182 2         172 return;
183             }
184              
185             #--------------------------------------------------------------------------#
186             # our internal events
187             #--------------------------------------------------------------------------#
188              
189             # if connected, check for new messages, otherwise reconnect
190             sub _poll {
191 10     10   7825619 my ( $kernel, $heap ) = @_[KERNEL, HEAP];
192 10 50       64 &_debug if $heap->{Debug};
193 10 50       50 if ( $heap->{connected} ) {
194 10         69 $kernel->post( $heap->{nntp_id} => group => $heap->{Group} );
195             }
196             else {
197 0         0 $kernel->yield( '_reconnect' );
198             }
199 10         1680 $kernel->delay( '_poll' => $heap->{Interval} );
200 10         1747 return;
201             }
202              
203             # connect to NNTP server
204             sub _reconnect {
205 2     2   231 my ( $kernel, $heap ) = @_[KERNEL, HEAP];
206 2 50       8 &_debug if $heap->{Debug};
207 2         40 $kernel->post( $heap->{nntp_id} => 'connect' );
208 2         280 return;
209             }
210              
211             #--------------------------------------------------------------------------#
212             # events from NNTP client
213             #--------------------------------------------------------------------------#
214              
215             # ignore event
216             sub _nntp_registered {
217 2     2   1771 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
218 2 50       10 &_debug if $heap->{Debug};
219 2         5 return;
220             }
221              
222             # ignore event
223             sub _nntp_connected {
224 2     2   6923 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
225 2 50       10 &_debug if $heap->{Debug};
226 2         6 return;
227             }
228              
229             # if connection can't be made, wait for next poll period to try again
230             sub _nntp_socketerr {
231 0     0   0 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
232 0 0       0 &_debug if $heap->{Debug};
233 0         0 my ($error) = $_[ARG0];
234 0         0 warn "Socket error: $error\n";
235 0         0 $heap->{connected} = 0;
236 0         0 $kernel->delay( '_reconnect' => $heap->{Interval} );
237 0         0 return;
238             }
239              
240             # if we time-out, just note it and wait for next poll to reconnect
241             sub _nntp_disconnected {
242 0     0   0 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
243 0 0       0 &_debug if $heap->{Debug};
244 0         0 $heap->{connected} = 0;
245 0         0 return;
246             }
247              
248             # once connected, start polling loop
249             sub _nntp_server_ready {
250 2     2   2379 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
251 2 50       11 &_debug if $heap->{Debug};
252 2         5 $heap->{connected} = 1;
253 2         8 $kernel->yield( '_poll' );
254 2         141 undef;
255             }
256              
257             # if the group doesn't exist, then we shut ourselves down
258             sub _nntp_no_group {
259 0     0   0 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
260 0 0       0 &_debug if $heap->{Debug};
261 0         0 warn "No such newsgroup $heap->{Group}";
262 0         0 $kernel->yield( 'shutdown' );
263 0         0 return;
264             }
265              
266             # if the article doesn't exist, warn about it
267             sub _nntp_no_article {
268 0     0   0 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
269 0 0       0 &_debug if $heap->{Debug};
270 0         0 warn "Couldnt find article in $heap->{Group}\n";
271 0         0 return;
272             }
273              
274             # 503 error in response to group query reconnect
275             sub _nntp_503_error {
276 0     0   0 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
277 0 0       0 &_debug if $heap->{Debug};
278 0         0 $heap->{connected} = 0;
279 0         0 $kernel->delay( '_reconnect' => $heap->{Interval} );
280 0         0 return;
281             }
282              
283             # if there are new articles, request their headers
284             # also schedules the next check
285             sub _nntp_group_selected {
286 10     10   36397 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
287 10 50       52 &_debug if $heap->{Debug};
288 10         94 my ($estimate,$first,$last,$group) = split( /\s+/, $_[ARG0] );
289              
290             # first time, we won't know last_article, so skip to the end
291 10 100       52 if ( exists $heap->{last_article} ) {
292             # fetch new headers or articles only if people are listening
293 8         69 for my $article_id ( $heap->{last_article} + 1 .. $last ) {
294 8 50       517 if ( scalar keys %{ $heap->{listeners} } ) {
  8         37  
295 8         28 $kernel->post( $sender => head => $article_id );
296             }
297             }
298             }
299 10         464 $heap->{last_article} = $last;
300 10         47 $kernel->delay( '_poll' => $heap->{Interval} );
301 10         1778 return;
302             }
303              
304             # notify listeners of new header
305             sub _nntp_got_head {
306 8     8   177757 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
307 8 50       42 &_debug if $heap->{Debug};
308 8         27 my ($response, $lines) = @_[ARG0, ARG1];
309 8         40 my ($article_id) = split " ", $response;
310 8         23 for my $who ( keys %{ $heap->{listeners} } ) {
  8         34  
311 8         32 $kernel->post( $who => $heap->{listeners}{$who} => $article_id, $lines );
312             }
313 8         1432 return;
314             }
315              
316             # return article to request queue
317             sub _nntp_got_article {
318 8     8   170295 my ($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
319 8 50       34 &_debug if $heap->{Debug};
320 8         25 my ($response, $lines) = @_[ARG0, ARG1];
321 8         32 my ($article_id) = split " ", $response;
322             # dispatch for all entries in the request queue for this article
323 8         19 for my $request ( @{$heap->{requests}{$article_id}} ) {
  8         29  
324 8         21 my ($who, $event) = @$request;
325 8         38 $kernel->post( $who, $event, $article_id, $lines );
326             }
327             # clear the request queue
328 8         911 delete $heap->{requests}{$article_id};
329 8         26 return;
330             }
331              
332             1;
333              
334             __END__