File Coverage

blib/lib/POE/Component/Syndicator.pm
Criterion Covered Total %
statement 189 244 77.4
branch 49 88 55.6
condition 10 27 37.0
subroutine 31 36 86.1
pod 10 10 100.0
total 289 405 71.3


line stmt bran cond sub pod time code
1             package POE::Component::Syndicator;
2             BEGIN {
3 11     11   3479103 $POE::Component::Syndicator::AUTHORITY = 'cpan:HINRIK';
4             }
5             BEGIN {
6 11     11   218 $POE::Component::Syndicator::VERSION = '0.06';
7             }
8              
9 11     11   110 use strict;
  11         23  
  11         385  
10 11     11   58 use warnings FATAL => 'all';
  11         33  
  11         446  
11 11     11   64 use Carp qw(carp croak);
  11         19  
  11         1025  
12 11     11   4630 use Object::Pluggable::Constants qw(:ALL);
  11         1951  
  11         3028  
13 11     11   1181 use POE;
  11         799380  
  11         73  
14 11     11   137351 use base 'Object::Pluggable';
  11         31  
  11         12007  
15              
16 11     11   84483 use constant REFCOUNT_TAG => 'POE::Component::Syndicator registered';
  11         32  
  11         46566  
17              
18             sub _pluggable_event {
19 0     0   0 my ($self, $event, @args) = @_;
20 0         0 $self->send_event($event, @args);
21 0         0 return;
22             }
23              
24             sub _syndicator_init {
25 11     11   786 my ($self, %args) = @_;
26              
27 11 50       99 $args{prefix} = 'syndicator_' if !defined $args{prefix};
28 11         170 $self->{_syndicator}{prefix} = $args{prefix};
29              
30 11 50       60 if (defined $args{types}) {
31 0 0       0 croak("'types' argument must be an array") if ref $args{types} ne 'ARRAY';
32 0 0       0 if (@{ $args{types} } == 4) {
  0 0       0  
  0         0  
33 0         0 $self->{_syndicator}{server_event} = $args{types}[0];
34 0         0 $self->{_syndicator}{user_event} = $args{types}[2];
35 0         0 $args{types} = { @{ $args{types} } };
  0         0  
36             }
37             elsif (@{ $args{types} } == 2) {
38 0         0 $self->{_syndicator}{server_event} = $args{types}[0];
39 0         0 $self->{_syndicator}{user_event} = $args{types}[1];
40             }
41             else {
42 0         0 croak('Only two event types are supported');
43             }
44             }
45             else {
46 11         124 $args{types} = { SERVER => 'S', USER => 'U' };
47 11         42 $self->{_syndicator}{server_event} = 'SERVER';
48 11         35 $self->{_syndicator}{user_event} = 'USER';
49             }
50              
51             # set up the plugin system
52 11         165 $self->_pluggable_init(
53             prefix => delete $args{prefix},
54             reg_prefix => delete $args{reg_prefix},
55             debug => delete $args{debug},
56             types => delete $args{types},
57             );
58              
59 11 50       368 if (ref $args{object_states} eq 'ARRAY') {
60 11         48 my $start_stop = "Don't install handlers for _start or _stop. Use"
61             . "_syndicator_started or _syndicator_stopped instead";
62 11         30 my $reg_unreg = "Don't install handlers for register or unregister."
63             . " Those are handled by POE::Component::Syndicator";
64              
65 11         27 for (my $i = 1; $i <= $#{ $args{object_states} }; $i += 2) {
  23         134  
66 12         35 my $events = $args{object_states}[$i];
67 12 100       93 if (ref $events eq 'HASH') {
    50          
68 1 50 33     27 if (defined $events->{_start} || defined $events->{_stop}) {
    50 33        
69 0         0 croak($start_stop);
70             }
71             elsif (defined $events->{register} || defined $events->{unregister}) {
72 0         0 croak($reg_unreg);
73             }
74             }
75             elsif (ref $events eq 'ARRAY') {
76 11         45 for my $event (@$events) {
77 18 50 33     282 if ($event eq '_start' || $event eq '_stop') {
    50 33        
78 0         0 croak($start_stop);
79             }
80             elsif ($event eq 'register' || $event eq 'unregister') {
81 0         0 croak($reg_unreg);
82             }
83             }
84             }
85             }
86             }
87              
88             # set up our POE session
89             POE::Session->create(
90 11         263 object_states => [
91             $self => {
92             _start => '_syndicator_start',
93             _default => '_syndicator_default',
94             _stop => '_syndicator_stop',
95             register => '_syndicator_register',
96             unregister => '_syndicator_unregister',
97             },
98             $self => [qw(
99             _syndicator_shutdown
100             _syndicator_send_pending_events
101             _syndicator_delay
102             _syndicator_delay_remove
103             _syndicator_sig_register
104             _syndicator_sig_shutdown
105             _syndicator_sig_die
106             )],
107 11 50       162 ($args{object_states} ? @{ $args{object_states} } : ()),
    50          
108             ],
109             ($args{options} ? (options => delete $args{options}) : ()),
110             args => [%args],
111             heap => $self,
112             );
113              
114 11         1890 return;
115             }
116              
117             sub _syndicator_sig_die {
118 0     0   0 my ($kernel, $self, $ex) = @_[KERNEL, OBJECT, ARG1];
119 0         0 chomp $ex->{error_str};
120              
121 0         0 my $error = "Event $ex->{event} in session ".$ex->{dest_session}->ID
122             ." raised exception:\n $ex->{error_str}";
123              
124 0         0 warn $error, "\n";
125 0         0 $kernel->sig_handled();
126 0         0 return;
127             }
128              
129             sub _syndicator_destroy {
130 19     19   4300 my ($self, @args) = @_;
131 19         74 $self->call('_syndicator_shutdown', @args);
132 19         184 return;
133             }
134              
135             sub _syndicator_shutdown {
136 19     19   728 my ($kernel, $self, @args) = @_[KERNEL, OBJECT, ARG0..$#_];
137 19 100       90 return if $self->{_shutting_down};
138 11         57 $kernel->alarm_remove_all();
139 11         628 $self->_pluggable_destroy();
140 11         506 $self->send_event($self->{_syndicator}{prefix} . 'shutdown', @args);
141 11         32 $self->{_shutting_down} = 1;
142 11         37 return;
143             }
144              
145             sub _syndicator_start {
146 11     11   10599 my ($kernel, $sender, $session, $self, %args)
147             = @_[KERNEL, SENDER, SESSION, OBJECT, ARG0..$#_];
148              
149 11         74 $kernel->sig('DIE', '_syndicator_sig_die');
150 11         438 $self->{_syndicator}{session_id} = $session->ID();
151              
152             # set an alias to keep our session alive
153 11 50       111 if (defined $args{alias}) {
154 0         0 $kernel->alias_set($args{alias});
155 0         0 $self->{_syndicator}{session_alias} = $args{alias};
156             }
157             else {
158 11         90 $kernel->alias_set("$self");
159 11         524 $self->{_syndicator}{session_alias} = "$self";
160             }
161              
162 11 50       350 $args{register_signal} = 'SYNDICATOR_REGISTER' if !defined $args{register_signal};
163 11         62 $kernel->sig($args{register_signal}, '_syndicator_sig_register');
164 11 50       320 $args{shutdown_signal} = 'SYNDICATOR_SHUTDOWN' if !defined $args{shutdown_signal};
165 11         46 $kernel->sig($args{shutdown_signal}, '_syndicator_sig_shutdown');
166              
167             # if called from a parent session, register the parent for all events
168             # and detach our session from the parent
169 11 100       372 if ($sender != $kernel) {
170 1         4 my $sender_id = $sender->ID;
171 1         7 my $prefix = $self->{_syndicator}{prefix};
172 1         5 $self->{_syndicator}{events}{all}{$sender_id} = $sender_id;
173 1         5 $self->{_syndicator}{sessions}{$sender_id}{ref} = $sender_id;
174 1         32 $self->{_syndicator}{sessions}{$sender_id}{refcnt}++;
175 1         7 $kernel->refcount_increment($sender_id, REFCOUNT_TAG);
176 1         44 $kernel->post($sender, "${prefix}registered", $self);
177 1         109 $kernel->detach_myself();
178             }
179              
180 11         133 $kernel->call($session, 'syndicator_started');
181 11         5335 return;
182             }
183              
184             sub _syndicator_default {
185 21     21   1513 my ($self, $event, $args) = @_[OBJECT, ARG0, ARG1];
186 21 100       103 return if $event =~ /^_/;
187 20 100       173 return if $event =~ /^syndicator_(?:started|stopped)$/;
188 6         46 $self->send_user_event($event, [@$args]);
189 6         24 return;
190             }
191              
192             sub _syndicator_stop {
193 11     11   4972 my ($kernel, $self) = @_[KERNEL, OBJECT];
194 11         49 $kernel->call($self->{_syndicator}{session_id}, 'syndicator_stopped');
195 11         80 return;
196             }
197              
198             sub _syndicator_unregister_sessions {
199 11     11   27 my ($self) = @_;
200              
201 11         25 for my $session_id ( keys %{ $self->{_syndicator}{sessions} } ) {
  11         53  
202 11         34 my $refcnt = $self->{_syndicator}{sessions}{$session_id}{refcnt};
203 11         52 while ($refcnt-- > 0) {
204 19         321 $poe_kernel->refcount_decrement($session_id, REFCOUNT_TAG);
205             }
206 11         367 delete $self->{_syndicator}{sessions}{$session_id};
207             }
208              
209 11         48 return;
210             }
211              
212             sub yield {
213 36     36 1 3525 my ($self, @args) = @_;
214 36         280 $poe_kernel->post($self->{_syndicator}{session_id}, @args);
215 36         2984 return;
216             }
217              
218             sub _syndicator_sig_register {
219 3     3   1779 my ($kernel, $self, $session, $signal, $sender, @events)
220             = @_[KERNEL, OBJECT, SESSION, ARG0..$#_];
221              
222 3 50 33     31 if (!@events || !defined $sender) {
223 0         0 warn "Signal $signal: not enough arguments\n";
224 0         0 return;
225             }
226              
227 3         6 my $sender_id;
228 3 50       13 if (my $ref = $kernel->alias_resolve($sender)) {
229 3         69 $sender_id = $ref->ID();
230             }
231             else {
232 0         0 warn "Signal $signal: can't resolve sender $sender\n";
233 0         0 return;
234             }
235              
236 3         29 $self->_syndicator_reg($sender_id, @events);
237 3         10 return;
238             }
239              
240             sub _syndicator_sig_shutdown {
241 16     16   9104 my ($kernel, $self, @args) = @_[KERNEL, OBJECT, ARG2..$#_];
242 16 100       122 $kernel->yield('shutdown', @args) if !$self->{_shutdown_event_sent};
243 16         634 $self->{_shutdown_event_sent} = 1;
244 16         65 return;
245             }
246              
247             sub _syndicator_register {
248 7     7   2321 my ($kernel, $self, $session, $sender, @events)
249             = @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
250              
251 7 100       39 @events = 'all' if !@events;
252 7         25 my $sender_id = $sender->ID();
253 7         73 $self->_syndicator_reg($sender_id, @events);
254 7         23 return;
255             }
256              
257             sub _syndicator_reg {
258 10     10   58 my ($self, $sender_id, @events) = @_;
259 10         31 my $prefix = $self->{_syndicator}{prefix};
260              
261 10         29 for my $event (@events) {
262 18         93 $self->{_syndicator}{events}{$event}{$sender_id} = $sender_id;
263 18         94 $self->{_syndicator}{sessions}{$sender_id}{ref} = $sender_id;
264              
265 18 100 66     125 if (!$self->{_syndicator}{sessions}{$sender_id}{refcnt}
266             && $sender_id ne $self->{_syndicator}{session_id}) {
267 10         73 $poe_kernel->refcount_increment($sender_id, REFCOUNT_TAG);
268             }
269              
270 18         359 $self->{_syndicator}{sessions}{$sender_id}{refcnt}++;
271             }
272              
273             # BINGOS:
274             # Apocalypse is gonna hate me for this as 'registered' events will bypass
275             # the plugin system, but I can't see how this event will be relevant
276             # without some sort of reference, like what session has registered. I'm
277             # not going to start hurling session references around at this point :)
278 10         92 $poe_kernel->post($sender_id, "${prefix}registered", $self);
279 10         864 return;
280             }
281              
282             sub _syndicator_unregister {
283 0     0   0 my ($kernel, $self, $session, $sender, @events)
284             = @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
285              
286 0 0       0 @events = 'all' if !@events;
287 0         0 my $sender_id = $sender->ID();
288 0         0 my $prefix = $self->{_syndicator}{prefix};
289              
290 0         0 for my $event (@events) {
291 0         0 my $blah = delete $self->{_syndicator}{events}{$event}{$sender_id};
292 0 0       0 if (!defined $blah) {
293 0         0 warn "Sender $sender_id hasn't registered for '$event' events";
294 0         0 next;
295             }
296 0 0       0 if (!keys %{ $self->{_syndicator}{events}{$event} }) {
  0         0  
297 0         0 delete $self->{_syndicator}{events}{$event};
298             }
299              
300 0 0       0 if (--$self->{_syndicator}{sessions}{$sender_id}{refcnt} <= 0) {
301 0         0 delete $self->{_syndicator}{sessions}{$sender_id};
302 0 0       0 if ($session != $sender) {
303 0         0 $kernel->refcount_decrement($sender_id, REFCOUNT_TAG);
304             }
305             }
306             }
307              
308 0         0 return;
309             }
310              
311             sub delay {
312 1     1 1 145 my ($self, $arrayref, $time) = @_;
313              
314 1 50 33     12 if (!defined $arrayref || ref $arrayref ne 'ARRAY' || !@$arrayref) {
      33        
315 0         0 croak('First argument to delay() must be a populated ARRAYREF');
316             }
317              
318 1 50       5 croak('No time specified') if !defined $time;
319              
320 1         9 return $self->call('_syndicator_delay', [@$arrayref], $time);
321             }
322              
323             sub _syndicator_delay {
324 1     1   67 my ($kernel, $self, $arrayref, $time) = @_[KERNEL, OBJECT, ARG0, ARG1];
325              
326 1         3 my $event = shift @$arrayref;
327 1         5 my $alarm_id = $kernel->delay_set($event, $time, @$arrayref);
328 1 50       58 if ($alarm_id) {
329 1         2 my $prefix = $self->{_syndicator}{prefix};
330 1         7 $self->send_event("${prefix}delay_set", $alarm_id, $event, @$arrayref);
331             }
332 1         4 return $alarm_id;
333             }
334              
335             sub delay_remove {
336 1     1 1 441 my ($self, $alarm_id) = @_;
337 1 50       5 croak('No alarm id specified') if !defined $alarm_id;
338 1         3 return $self->call('_syndicator_delay_remove', $alarm_id);
339             }
340              
341             sub _syndicator_delay_remove {
342 1     1   63 my ($kernel, $self, $alarm_id) = @_[KERNEL, OBJECT, ARG0];
343              
344 1         6 my @old_alarm_list = $kernel->alarm_remove($alarm_id);
345 1 50       87 if (@old_alarm_list) {
346 1         2 my $args = $old_alarm_list[-1];
347 1         3 my $prefix = $self->{_syndicator}{prefix};
348 1         4 $self->send_event("${prefix}delay_removed", $alarm_id, $args);
349 1         4 return $args;
350             }
351              
352 0         0 return;
353             }
354              
355             sub call {
356 30     30 1 72 my ($self, @args) = @_;
357 30         130 return $poe_kernel->call($self->{_syndicator}{session_id}, @args);
358             }
359              
360             sub session_id {
361 0     0 1 0 my ($self) = @_;
362 0         0 return $self->{_syndicator}{session_id};
363             }
364              
365             sub session_alias {
366 0     0 1 0 my ($self) = @_;
367 0         0 return $self->{_syndicator}{session_alias};
368             }
369              
370             sub send_user_event {
371 8     8 1 1102 my ($self, $event, $args) = @_;
372              
373 8         10 push @{ $self->{_syndicator}{pending_events} }, [];
  8         39  
374 8         20 my $user_type = $self->{_syndicator}{user_event};
375 8         110 my $eat = $self->_pluggable_process($user_type, $event, $args);
376 8         324 $self->call('_syndicator_send_pending_events');
377 8         59 return $eat;
378             }
379              
380             sub send_event {
381 21     21 1 4303 my ($self, $event, @args) = @_;
382 21         73 $self->yield('_syndicator_send_pending_events', $event, @args);
383 21         38 return;
384             }
385              
386             sub send_event_now {
387 1     1 1 9 my ($self, $event, @args) = @_;
388 1         8 $self->call('_syndicator_send_pending_events', $event, @args);
389 1         7 return;
390             }
391              
392             sub send_event_next {
393 1     1 1 8 my ($self, $event, @args) = @_;
394              
395 1 50 33     12 if (!$self->{_syndicator}{pending_events}
396             || !@{ $self->{_syndicator}{pending_events} }) {
397 0         0 croak('send_event_next() can only be called from an event handler');
398             }
399             else {
400 1         13 $event =~ s/^\Q$self->{_syndicator}{prefix}//;
401 1         2 push @{ $self->{_syndicator}{pending_events}[-1] }, [$event, \@args];
  1         4  
402             }
403 1         3 return;
404             }
405              
406             sub _syndicator_send_pending_events {
407 30     30   4558 my ($kernel, $session, $self, $new_event, @args)
408             = @_[KERNEL, SESSION, OBJECT, ARG0, ARG1..$#_];
409 30         106 my $session_id = $session->ID();
410 30         117 my %sessions;
411 30         59 my $prefix = $self->{_syndicator}{prefix};
412              
413             # create new context if we were passed an event directly
414 30 100       106 if (defined $new_event) {
415 22         244 $new_event =~ s/^\Q$prefix//;
416 22         79 my @our_events = [$new_event, \@args];
417 22         40 push @{ $self->{_syndicator}{pending_events} }, \@our_events;
  22         93  
418             }
419              
420 30         53 while (my ($ev) = shift @{ $self->{_syndicator}{pending_events}[-1] }) {
  53         211  
421 53 100       150 last if !defined $ev;
422 23         48 my ($event, $args) = @$ev;
423              
424 10         52 my @ids = (
425             (exists $self->{_syndicator}{events}{all}
426 13         49 ? values %{ $self->{_syndicator}{events}{all} }
427             : ()),
428             (exists $self->{_syndicator}{events}{$event}
429 23 100       115 ? values %{ $self->{_syndicator}{events}{$event} }
    100          
430             : ()),
431             );
432              
433 23         134 $sessions{$_} = $_ for @ids;
434              
435             # Make sure our session gets notified of any requested events before
436             # any other bugger
437 23 50       117 if (delete $sessions{$session_id}) {
438 0         0 $kernel->call($session_id, "$prefix$event", @$args);
439             }
440              
441             # then let the plugin system process this
442 23         54 my $server_type = $self->{_syndicator}{server_event};
443 23 50       117 if ($self->_pluggable_process($server_type, $event, $args) != PLUGIN_EAT_ALL) {
444             # and finally, let registered sessions process it
445 23         2490 for my $session (values %sessions) {
446             # We have to use call() here to maintain consistency, for
447             # example if a subclass maintains state which needs to make
448             # sense at the time this event is delivered (e.g.
449             # POE::Component::IRC::State). But this is not good if the
450             # user decides to use $poe_kernel->run_while() (which
451             # POE::Quickie and LWP::UserAgent::POE do). But then again
452             # that's a risk for everyone using call(), and the user
453             # should know not to use such modules in event handlers
454             # which might get call()ed by foreign sessions.
455 23         153 $kernel->call($session, "$prefix$event", @$args);
456             }
457             }
458              
459             # unregister all sessions if we're shutting down
460 23 100       10485 if ($event eq 'shutdown') {
461 11         116 $self->_syndicator_unregister_sessions();
462             }
463             }
464              
465 30         45 pop @{ $self->{_syndicator}{pending_events} };
  30         56  
466 30         139 return;
467             }
468              
469             1;
470              
471             =encoding utf8
472              
473             =head1 NAME
474              
475             POE::Component::Syndicator - A POE component base class which implements the Observer pattern
476              
477             =head1 SYNOPSIS
478              
479             package POE::Component::IRC;
480              
481             use strict;
482             use warnings;
483             use POE;
484             use base 'POE::Component::Syndicator';
485              
486             # our constructor
487             sub spawn {
488             my ($package, %args) = @_;
489              
490             # process arguments...
491              
492             my $self = bless \%args, $package;
493              
494             # set up our plugin system and POE session
495             $self->_syndicator_init(
496             prefix => 'irc_',
497             reg_prefix => 'PCI_',
498             types => [SERVER => 'S', USER => 'U'],
499             object_states => [qw(
500             syndicator_started
501             shutdown
502             )],
503             );
504              
505             return $self;
506             }
507              
508             sub syndicator_started {
509             my ($kernel, $self) = @_[KERNEL, OBJECT];
510              
511             # connect to a server, etc...
512             }
513              
514             # plugin handler for SERVER event 'hlagh'
515             sub S_hlagh {
516             # ...
517             }
518              
519             sub shutdown {
520             my ($kernel, $self) = @_[KERNEL, OBJECT];
521              
522             # disconnect from a server, etc...
523              
524             # shut down the syndicator
525             $self->_syndicator_destroy();
526             }
527              
528             =head1 DESCRIPTION
529              
530             POE::Component::Syndicator is a base class for POE components which need
531             to handle a persistent resource (e.g. a connection to an IRC server) for
532             one or more sessions in an extendable way.
533              
534             This module (as well as L, which this
535             module inherits from) was born out of
536             L, the guts of which quickly
537             spread to other POE components. Now they can all inherit from this module
538             instead.
539              
540             The component provides an event queue, which can be managed with the methods
541             documented below. It handles delivery of events to the object itself, all
542             interested plugins, and all interested sessions.
543              
544             =head2 Component lifetime
545              
546             You start by calling L|/_syndicator_init>, which will
547             create a POE session with your object as its heap, and a few event handlers
548             installed. The events described in L delimit the start and
549             end of the session's lifetime. In between those, interested plugins and
550             sessions will receive various events, usually starting with
551             L|/_syndicator_registered>. In this phase, your
552             subclass and plugins can call the L and send the
553             L documented below. When the component has been shut
554             down, sessions (but not plugins) will receive a
555             L|/_syndicator_shutdown> event. After this, the
556             component will become unusable.
557              
558             =head2 A note on events
559              
560             In this document, an I (unless explicitly referred to as a I)
561             is defined as a message originating from POE::Component::Syndicator, delivered
562             to plugins (and the subclass) via plugin methods and to registered sessions as
563             POE events.
564              
565             Interested sessions are considered consumers only, so they always receive
566             copies of event arguments, whereas interested plugins and subclasses receive
567             scalar references to them. This allows them to alter, add, or remove event
568             arguments before sessions (or even other plugins) receive them. For more
569             information about plugins, see L's
570             documentation. A subclass does not have to register for plugin events.
571              
572             Two event types are supported: SERVER and USER, though their names can be
573             overriden (see L|/_syndicator_init>).
574              
575             =head3 SERVER events
576              
577             These represent data received from the network or some other outside resource
578             (usually a server, hence the default name).
579              
580             SERVER events are generated by the L|/send_event> methods.
581             These events are delivered to the subclass and plugins (method C) and
582             interested sessions (event C).
583              
584             =head3 USER events
585              
586             These represent commands about to be sent to a server or some other resource.
587              
588             USER events are generated by L|/send_user_event>. In
589             addition, all POE events sent to this component's session (e.g. with
590             L|/yield>) which do not have a handler will generate corresponding
591             USER events. USER events are considered more private, so they are only
592             delivered to the subclass and plugins, not to sessions.
593              
594             =head1 PRIVATE METHODS
595              
596             The following methods should only be called by a subclass.
597              
598             =head2 C<_syndicator_init>
599              
600             You should call this in your constructor. It initializes
601             L, creates the Syndicator's POE session,
602             and calls the L|/syndicator_started> POE events. It
603             takes the following arguments:
604              
605             B<'prefix'>, a prefix for all your event names, when sent to interested
606             sessions. If you don't supply this, L's
607             default (B<'pluggable'>) will be used.
608              
609             B<'reg_prefix'>, the prefix for the C/C
610             plugin methods If you don't supply this, L's
611             default (B<'plugin_'>) will be used.
612              
613             B<'debug'>, a boolean, if true, will cause a warning to be printed
614             every time a plugin event handler raises an exception.
615              
616             B<'types'>, a 2-element arrayref of the types of events that your
617             component will support, or a 4-element (2 pairs) arrayref where the event
618             types are keys and their abbrevations (used as plugin event method prefixes)
619             are values (see L and L
620             for more information). The two event types are fundamentally different, so
621             make sure you supply them in the right order. If you don't provide this
622             argument, C<< [ SERVER => 'S', USER => 'U' ] >> will be used.
623              
624             B<'register_signal'>, the name of the register signal (see L).
625             Defaults to B<'SYNDICATOR_REGISTER'>.
626              
627             B<'shutdown_signal'>, the name of the shutdown signal (see L).
628             Defaults to B<'SYNDICATOR_SHUTDOWN'>.
629              
630             B<'object_states'> an arrayref of additional object states to add to
631             the POE session. Same as the 'object_states' argument to
632             L's C method. You'll want to add a handler
633             for at least the L|/syndicator_started> event.
634              
635             B<'options'>, a hash of options for L's
636             constructor.
637              
638             If you call C<_syndicator_init> from inside another POE session, the
639             component will automatically register that session as wanting all events.
640             That session will first receive a
641             L|/syndicator_registered> event.
642              
643             =head2 C<_syndicator_destroy>
644              
645             Call this method when you want Syndicator to clean up (delete all plugins,
646             etc) and make sure it won't keep the POE session alive after all remaining
647             events have been processed. A L|/syndicator_shutdown>
648             event (or similar, depending on the prefix you chose) will be generated.
649             Any argument passed to C<_syndicator_destroy> will be passed along with that
650             event.
651              
652             B this method will clear all alarms for the POE session.
653              
654             =head1 PUBLIC METHODS
655              
656             =head2 C
657              
658             Returns the component's POE session id.
659              
660             =head2 C
661              
662             Returns the component's POE session alias.
663              
664             =head2 C
665              
666             This method provides an alternative, object-based means of posting events to the
667             component. First argument is the event to post, following arguments are sent as
668             arguments to the resultant post.
669              
670             =head2 C
671              
672             This method provides an alternative, object-based means of calling events to the
673             component. First argument is the event to call, following arguments are sent as
674             arguments to the resultant call.
675              
676             =head2 C
677              
678             Adds a new SERVER event onto the end of the queue. The event will be
679             processed after other pending events, if any. First argument is an event name,
680             the rest are the event arguments.
681              
682             $component->send_event('irc_public, 'foo!bar@baz.com', ['#mychan'], 'message');
683              
684             =head2 C
685              
686             Adds a new SERVER event to the start of the queue. The event will be the next
687             one to be processed. First argument is an event name, the rest are the event
688             arguments.
689              
690             =head2 C
691              
692             Sends a new SERVER event immediately. Execution of the current POE event will
693             be suspended (i.e. this call will block) until the new event has been
694             processed by the component class and all plugins. First argument is an event
695             name, the rest are the event arguments.
696              
697             =head2 C
698              
699             Sends a new USER event immediately. You should call this before every command
700             you send to your remote server/resource. Only the subclass and plugins will
701             see this event. Takes two arguments, an event name and an arrayref of
702             arguments. Returns one of the C constants listed in
703             L. After this
704             method returns, the arrayref's contents may have been modified by the
705             subclass or plugins.
706              
707             $component->send_user_event('PRIVMSG', '#mychan', 'message');
708              
709             =head2 C
710              
711             This method provides a way of posting delayed events to the component. The
712             first argument is an arrayref consisting of the delayed command to post and
713             any command arguments. The second argument is the time in seconds that one
714             wishes to delay the command being posted.
715              
716             my $alarm_id = $component->delay(['mode', $channel, '+o', $dude], 60);
717              
718             =head2 C
719              
720             This method removes a previously scheduled delayed event from the component.
721             Takes one argument, the C that was returned by a
722             L|/delay> method call. Returns an arrayref of arguments to the
723             event that was originally requested to be delayed.
724              
725             my $arrayref = $component->delay_remove($alarm_id);
726              
727             =head1 EVENTS
728              
729             =head2 Local events
730              
731             The component will send the following POE events to its session.
732              
733             =head3 C
734              
735             Called after the session has been started (like C<_start> in
736             L. This is where you should do
737             your POE-related setup work such as adding new event handlers to the session.
738              
739             =head3 C
740              
741             Called right before the session is about to die (like C<_stop> in
742             L).
743              
744             =head2 Input events
745              
746             Other POE sessions can send the following POE events to the Syndicator's
747             session.
748              
749             =head3 C
750              
751             Takes any amount of arguments: a list of event names that your session wants
752             to listen for, minus the prefix (specified in
753             L/_syndicator_init>).
754              
755             $kernel->post('my syndicator', 'register', qw(join part quit kick));
756              
757             Registering for the special event B<'all'> will cause it to send all
758             events to your session. Calling it with no event names is equivalent to
759             calling it with B<'all'> as an argumente.
760              
761             Registering will generate a L|/syndicator_registered>
762             event that your session can trap.
763              
764             Registering with multiple component sessions can be tricky, especially if
765             one wants to marry up sessions/objects, etc. Check the L
766             section for an alternative method of registering with multiple components.
767              
768             =head3 C
769              
770             Takes any amount of arguments: a list of event names which you I want
771             to receive. If you've previously done a L|/register>
772             for a particular event which you no longer care about, this event will
773             tell the component to stop sending them to you. (If you haven't, it just
774             ignores you. No big deal.) Calling it with no event names is equivalent to
775             calling it with B<'all'> as an argument.
776              
777             If you have registered for the special event B<'all'>, attempting to
778             unregister individual events will not work. This is a 'feature'.
779              
780             =head3 C
781              
782             By default, POE::Component::Syndicator sessions never go away. You can send
783             its session a C event manually to make it delete itself.
784             Terminating multiple Syndicators can be tricky. Check the L section
785             for a method of doing that.
786              
787             =head3 C<_default>
788              
789             Any POE events sent to the Syndicator's session which do not have a handler
790             will go to the Syndicator's C<_default> handler, will generate
791             L of the same name. If you install your own C<_default>
792             handler, make sure you do the same thing before you handle an event:
793              
794             use Object::Pluggable::Constants 'PLUGIN_EAT_ALL';
795              
796             $poe_kernel->state('_default', $self, '__default');
797              
798             sub __default {
799             my ($self, $event, $args) = @_[OBJECT, ARG0, ARG1];
800              
801             # do nothing if a plugin eats the event
802             return if $self->send_user_event($event, [@$args]) == PLUGIN_EAT_ALL;
803              
804             # handle the event
805             # ...
806             }
807              
808             Note that the handler for the C<_default> event must be named something other
809             than '_default', because that name is reserved for the plugin-type default
810             handler (see the L docs).
811              
812             =head2 Output events
813              
814             The Syndicator will send the following events at various times. The
815             B<'syndicator_'> prefix in these event names can be customized with a
816             B<'prefix'> argument to L/_syndicator_init>.
817              
818             =head3 C
819              
820             Sent once to the requesting session on registration (see
821             L|/register>). C is a reference to the component's object.
822              
823             =head3 C
824              
825             Sent to all interested sessions when the component has been shut down. See
826             L|/_syndicator_destroy>.
827              
828             =head3 C
829              
830             Sent to the subclass, plugins, and all interested sessions on a successful
831             addition of a delayed event using the L|/delay> method. C will
832             be the alarm_id which can be used later with L|/delay_remove>.
833             Subsequent parameters are the arguments that were passed to L|/delay>.
834              
835             =head3 C
836              
837             Sent to the subclass, plugins, and all interested sessions when a delayed
838             event is successfully removed. C will be the alarm_id that was removed.
839             Subsequent parameters are the arguments that were passed to L|/delay>.
840              
841             =head3 All other events
842              
843             All other events sent by the Syndicator are USER events (generated with
844             L|/send_user_event>) and SERVER events (generated with
845             L|/send_event>) which will be delivered normally. Your
846             subclass and plugins are responsible for generating them.
847              
848             =head1 SIGNALS
849              
850             The component will handle a number of custom signals that you may send using
851             L's C method. They allow any session to
852             communicate with every instance of the component in certain ways without
853             having references to their objects or knowing about their sessions. The names
854             of these signals can be customized with
855             L|/_syndicator_init>.
856              
857             =head2 C
858              
859             Registers for an event with the component. See L|/register>.
860              
861             =head2 C
862              
863             Causes a 'shutdown' event to be sent to your session. Any arguments to the
864             signal will be passed along to the event. That's where you should clean up
865             and call L|/_syndicator_destroy>.
866              
867             =head1 AUTHOR
868              
869             Hinrik Ern SigurEsson, L,
870             Chris C Williams L,
871             Apocalypse L, and probably others.
872              
873             =head1 LICENSE AND COPYRIGHT
874              
875             Copyright 2011 Hinrik Ern SigurEsson
876              
877             This program is free software, you can redistribute it and/or modify
878             it under the same terms as Perl itself.
879              
880             =cut