File Coverage

blib/lib/POE/Component/DebugShell/Jabber.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package POE::Component::DebugShell::Jabber;
2              
3 1     1   45048 use warnings;
  1         3  
  1         36  
4 1     1   6 use strict;
  1         2  
  1         39  
5              
6 1     1   29 use 5.006;
  1         8  
  1         54  
7              
8 1     1   6 use Carp;
  1         2  
  1         118  
9              
10 1     1   654 use POE qw( Component::Jabber Component::Jabber::Error);
  0            
  0            
11             use POE::API::Peek;
12             use POE::Filter::XML::Node;
13             use POE::Filter::XML::NS qw/ :JABBER :IQ /;
14              
15             our $VERSION = '0.04';
16             our $RUNNING = 0;
17             our %COMMANDS = ( #{{{
18              
19             'reload' => {
20             help => "Reload the shell to catch updates.",
21             short_help => "Reload the shell to catch updates.",
22             cmd => \&cmd_reload,
23             },
24              
25             show_sessions => {
26             help => 'Show a list of all sessions in the system. The output format is in the form of loggable session ids.',
27             short_help => 'Show a list of all sessions',
28             cmd => \&cmd_show_sessions,
29             },
30              
31             'list_aliases' => {
32             help => 'List aliases for a given session id. Provide one session id as a parameter.',
33             short_help => 'List aliases for a given session id.',
34             cmd => \&cmd_list_aliases,
35             },
36              
37             'session_stats' => {
38             help => 'Display various statistics for a given session id. Provide one session id as a parameter.',
39             short_help => 'Display various statistics for a given session id.',
40             cmd => \&cmd_session_stats,
41             },
42              
43             'queue_dump' => {
44             help => 'Dump the contents of the event queue.',
45             short_help => 'Dump the contents of the event queue.',
46             cmd => \&cmd_queue_dump,
47             },
48              
49             'status' => {
50             help => 'General shell status.',
51             short_help => 'General shell status.',
52             cmd => \&cmd_status,
53             },
54              
55             # 'shutdown' => {
56             # help => 'Shutdown the jabber component',
57             # short_help => 'Shutdown this component',
58             # cmd => \&cmd_shutdown,
59             # },
60             ); #}}}
61             our $SPAWN_TIME;
62              
63             sub spawn { #{{{
64             my $class = shift;
65             my %opts = @_;
66              
67             carp "".__PACKAGE__."::spawn() : 'jabber' must be a hash ref, read the docs"
68             unless ($opts{jabber} && ref($opts{jabber}) eq 'HASH');
69              
70             carp "".__PACKAGE__."::spawn() : 'jabber_client' must be a package name like POE::Component::Jabber::Client::XMPP"
71             unless ($opts{jabber_package} && $opts{jabber_package} =~ m/^POE::Component::Jabber/);
72              
73             # optional
74             $opts{users} ||= {};
75            
76             # Singleton check {{{
77             if($RUNNING) {
78             carp "A ".__PACKAGE__." session is already running. Will not start a second.";
79             return undef;
80             } else {
81             $RUNNING = 1;
82             }
83             # }}}
84              
85             my $api = POE::API::Peek->new() or croak "Unable to create POE::API::Peek object";
86              
87             if ($opts{cmds} && ref($opts{cmds}) eq 'HASH') {
88             foreach (keys %{$opts{cmds}}) {
89             $COMMANDS{$_} = $opts{cmds}->{$_};
90             }
91             delete $opts{cmds};
92             }
93              
94             # Session creation {{{
95             my $sess = POE::Session->create(
96             inline_states => {
97             _start => \&_start,
98             _stop => \&_stop,
99              
100             jabber_input => \&jabber_input,
101             error_event => \&error_event,
102             init_finished => \&init_finished,
103             },
104             heap => {
105             %opts,
106             api => $api,
107             },
108             );
109             # }}}
110              
111             if($sess) {
112             $SPAWN_TIME = time();
113             return $sess;
114             } else {
115             return undef;
116             }
117             } #}}}
118              
119              
120              
121             sub _start { #{{{
122             $_[KERNEL]->alias_set(__PACKAGE__." controller");
123              
124             my $pkg = $_[HEAP]->{jabber_package};
125             eval "use $pkg;";
126             carp $@ if ($@);
127            
128             $pkg->new(
129             ALIAS => __PACKAGE__." jabber",
130             DEBUG => '0',
131             STATE_PARENT => $_[SESSION]->ID,
132             STATES => {
133             InitFinish => 'init_finished',
134             InputEvent => 'jabber_input',
135             ErrorEvent => 'error_event',
136             },
137             %{$_[HEAP]->{jabber}},
138             );
139              
140             unless ($_[HEAP]->{no_std_tie}) {
141             tie *STDOUT, __PACKAGE__."::Output", 'stdout', \&_output;
142             tie *STDERR, __PACKAGE__."::Output", 'stderr', \&_output;
143             }
144              
145             if ($_[HEAP]->{ties}) {
146             foreach (@{$_[HEAP]->{ties}}) {
147             tie *$_, __PACKAGE__."::Output", $_, \&_output;
148             }
149             }
150              
151             } #}}}
152              
153              
154              
155             sub _stop { #{{{
156             # Shut things down
157             $_[HEAP]->{vt} && $_[HEAP]->{vt}->delete_window($_[HEAP]->{main_window});
158             } #}}}
159              
160              
161              
162             sub old_input { #{{{
163             my ($input, $exception) = @_[ARG0, ARG1];
164              
165             unless (defined $input) {
166             croak("Received exception from UI: $exception");
167             }
168              
169             if($input =~ /^help (.*?)$/) {
170             my $cmd = $1;
171             if($COMMANDS{$cmd}) {
172             if($COMMANDS{$cmd}{help}) {
173             _output("Help for $cmd:");
174             _output($COMMANDS{$cmd}{help});
175             } else {
176             _output("Error: '$cmd' has no help.");
177             }
178             } else {
179             _output("Error: '$cmd' is not a known command");
180             }
181             } elsif ( ($input eq 'help') or ($input eq '?') ) {
182             my $text;
183             _output(' ');
184             _output("General help for ".__PACKAGE__." v$VERSION");
185             _output("The following commands are available:");
186             foreach my $cmd (sort keys %COMMANDS) {
187             no warnings;
188             my $short_help = $COMMANDS{$cmd}{short_help} || '[ No short help provided ]';
189             _output("\t* $cmd - $short_help");
190             }
191             _output(' ');
192              
193             } else {
194             my ($cmd, @args);
195             if($input =~ /^(.+?)\s+(.*)$/) {
196             $cmd = $1;
197             my $args = $2;
198             @args = split('\s+',$args) if $args;
199             } else {
200             $cmd = $input;
201             }
202              
203             if($COMMANDS{$cmd}) {
204             my $txt = eval { $COMMANDS{$cmd}{cmd}->( api => $_[HEAP]->{api}, args => \@args); };
205             if($@) {
206             _output("Error running $cmd: $@");
207             } else {
208             my @lines = split(/\n/, $txt);
209             _output($_) for @lines;
210             }
211             } else {
212             _output("Error: '$cmd' is not a known command");
213             }
214             }
215              
216             } #}}}
217              
218              
219              
220             sub _output { #{{{
221             my $msg = shift || ' ';
222             my $heap = $poe_kernel->alias_resolve(__PACKAGE__." controller")->get_heap();
223             return unless ($heap->{sid});
224             my $hash = ($heap->{to}) ? {$heap->{to} => 1} : $heap->{users};
225             foreach (keys %{$hash}) {
226             my $n = POE::Filter::XML::Node->new('message');
227             $n->attr('from',$heap->{jid});
228             $n->attr('to',$_);
229             $n->attr('xmlns','jabber:client');
230             $n->insert_tag('body')->data($msg);
231             $poe_kernel->post($heap->{sid} => output_handler => $n);
232             }
233             } #}}}
234              
235             sub _raw_commands { #{{{
236             return \%COMMANDS;
237             } #}}}
238              
239             # {{{
240              
241              
242             ###############
243              
244             sub cmd_shutdown {
245            
246             }
247              
248             sub cmd_reload { #{{{
249             my $ret;
250             $ret .= "Reloading....\n";
251             eval q|
252             no warnings qw(redefine);
253             $SIG{__WARN__} = sub { };
254              
255             my $p = __PACKAGE__;
256             $p =~ s/\:\:/\//g;
257             foreach my $key (keys %INC) {
258             if($key =~ m#$p#) {
259             delete $INC{$key};
260             } elsif ($key =~ m#POE/API/Peek#) {
261             delete $INC{$key};
262             }
263             }
264             require __PACKAGE__;
265             |;
266             $ret .= "Error: $@\n" if $@;
267              
268             return $ret;
269             } #}}}
270              
271             sub cmd_show_sessions { #{{{
272             my %args = @_;
273             my $api = $args{api};
274              
275             my $ret;
276             $ret .= "Session List:\n";
277             my @sessions = $api->session_list;
278             foreach my $sess (@sessions) {
279             my $id = $sess->ID. " [ ".$api->session_id_loggable($sess)." ]";
280             $ret .= "\t* $id\n";
281             }
282              
283             return $ret;
284             } #}}}
285              
286             sub cmd_list_aliases { #{{{
287             my %args = @_;
288             my $user_args = $args{args};
289             my $api = $args{api};
290              
291             my $ret;
292              
293             if(my $id = shift @$user_args) {
294             if(my $sess = $api->resolve_session_to_ref($id)) {
295             my @aliases = $api->session_alias_list($sess);
296             if(@aliases) {
297             $ret .= "Alias list for session $id\n";
298             foreach my $alias (sort @aliases) {
299             $ret .= "\t* $alias\n";
300             }
301             } else {
302             $ret .= "No aliases found for session $id\n";
303             }
304             } else {
305             $ret .= "** Error: ID $id does not resolve to a session. Sorry.\n";
306             }
307              
308             } else {
309             $ret .= "** Error: Please provide a session id\n";
310             }
311             return $ret;
312             }
313              
314             # }}}
315              
316             sub cmd_session_stats { #{{{
317             my %args = @_;
318             my $user_args = $args{args};
319             my $api = $args{api};
320              
321             my $ret;
322              
323             if(my $id = shift @$user_args) {
324             if(my $sess = $api->resolve_session_to_ref($id)) {
325             my $to = $api->event_count_to($sess);
326             my $from = $api->event_count_from($sess);
327             $ret .= "Statistics for Session $id\n";
328             $ret .= "\tEvents coming from: $from\n";
329             $ret .= "\tEvents going to: $to\n";
330              
331             } else {
332             $ret .= "** Error: ID $id does not resolve to a session. Sorry.\n";
333             }
334              
335              
336             } else {
337             $ret .= "** Error: Please provide a session id\n";
338             }
339              
340             return $ret;
341             } #}}}
342              
343             sub cmd_queue_dump { #{{{
344             my %args = @_;
345             my $api = $args{api};
346             my $verbose;
347              
348             my $ret;
349              
350             if($args{args} && defined $args{args}) {
351             if(ref $args{args} eq 'ARRAY') {
352             if(@{$args{args}}[0] eq '-v') {
353             $verbose = 1;
354             }
355             }
356             }
357              
358             my @queue = $api->event_queue_dump();
359              
360             $ret .= "Event Queue:\n";
361              
362             foreach my $item (@queue) {
363             $ret .= "\t* ID: ". $item->{ID}." - Index: ".$item->{index}."\n";
364             $ret .= "\t\tPriority: ".$item->{priority}."\n";
365             $ret .= "\t\tEvent: ".$item->{event}."\n";
366              
367             if($verbose) {
368             $ret .= "\t\tSource: ".
369             $api->session_id_loggable($item->{source}).
370             "\n";
371             $ret .= "\t\tDestination: ".
372             $api->session_id_loggable($item->{destination}).
373             "\n";
374             $ret .= "\t\tType: ".$item->{type}."\n";
375             $ret .= "\n";
376             }
377             }
378             return $ret;
379             } #}}}
380              
381             sub cmd_status { #{{{
382             my %args = @_;
383             my $api = $args{api};
384             my $sess_count = $api->session_count;
385             my $ret = "\n";
386             $ret .= "This is ".__PACKAGE__." v".$VERSION."\n";
387             $ret .= "running inside $0."."\n";
388             $ret .= "This console was spawned at ".localtime($SPAWN_TIME).".\n";
389             $ret .= "There are $sess_count known sessions (including the kernel).\n";
390             $ret .= "\n";
391             return $ret;
392             } # }}}
393              
394             # }}}
395              
396             sub init_finished() {
397             my ($kernel, $sender, $heap, $jid) = @_[KERNEL, SENDER, HEAP, ARG0];
398            
399             $heap->{'jid'} = $jid;
400             $heap->{'sid'} = $sender->ID();
401             my $node = POE::Filter::XML::Node->new('presence');
402             $node->insert_tag('status')->data('Online');
403             $node->insert_tag('priority')->data('8');
404             $node->attr('xmlns','jabber:client');
405             $node->attr('from',$jid);
406            
407             $kernel->post($heap->{sid} => output_handler => $node);
408            
409             _output("Welcome to POE Debug Shell v$VERSION");
410             }
411              
412             sub jabber_input() {
413             my ($kernel, $heap, $self, $node) = @_[KERNEL, HEAP, OBJECT, ARG0];
414            
415             my $at = $node->get_attrs();
416             my $from = $at->{from};
417             $at->{from} =~ s/\/.+//;
418            
419             if ($node->name() eq 'presence') {
420            
421             if ($at->{from} && $at->{from} =~ m/\@/ && $at->{from} ne $at->{to}) {
422             my $n = POE::Filter::XML::Node->new('presence');
423             $n->insert_tag('status')->data('Online');
424             $n->insert_tag('priority')->data('8');
425             $n->attr('from',$heap->{'jid'});
426             $n->attr('to',$from);
427             $n->attr('xmlns','jabber:client');
428            
429             $kernel->post($heap->{sid} => output_handler => $n);
430             return;
431             }
432             }
433              
434             if($node->name() eq 'message') {
435             my $input = $node->get_tag('body')->data;
436             return if ($at->{from} eq $at->{to});
437            
438             $heap->{users}->{$at->{from}} = 1;
439             $heap->{to} = $from;
440              
441             if($input =~ /^help (.*?)$/) {
442             my $cmd = $1;
443             if($COMMANDS{$cmd}) {
444             if($COMMANDS{$cmd}{help}) {
445             _output("Help for $cmd:");
446             _output($COMMANDS{$cmd}{help});
447             } else {
448             _output("Error: '$cmd' has no help.");
449             }
450             } else {
451             _output("Error: '$cmd' is not a known command");
452             }
453             } elsif ( ($input eq 'help') or ($input eq '?') ) {
454             my $text;
455             _output(' ');
456             _output("General help for ".__PACKAGE__." v$VERSION");
457             _output("The following commands are available:");
458             foreach my $cmd (sort keys %COMMANDS) {
459             no warnings;
460             my $short_help = $COMMANDS{$cmd}{short_help} || '[ No short help provided ]';
461             _output("\t* $cmd - $short_help");
462             }
463             _output(' ');
464              
465             } else {
466             my ($cmd, @args);
467             if($input =~ /^(.+?)\s+(.*)$/) {
468             $cmd = $1;
469             my $args = $2;
470             @args = split('\s+',$args) if $args;
471             } else {
472             $cmd = $input;
473             }
474              
475             if($COMMANDS{$cmd}) {
476             my $txt = eval { $COMMANDS{$cmd}{cmd}->( api => $_[HEAP]->{api}, args => \@args); };
477             if($@) {
478             _output("Error running $cmd: $@");
479             } else {
480             if ($txt) {
481             my @lines = split(/\n/, $txt);
482             _output($_) for @lines;
483             }
484             }
485             } else {
486             _output("Error: '$cmd' is not a known command");
487             }
488             }
489             delete $heap->{to};
490             }
491             }
492              
493             sub error_event() {
494             my ($kernel, $sender, $heap, $error) = @_[KERNEL, SENDER, HEAP, ARG0];
495              
496             if($error == +PCJ_SOCKFAIL)
497             {
498             my ($call, $code, $err) = @_[ARG1..ARG3];
499             print "Socket error: $call, $code, $err\n";
500            
501             } elsif($error == +PCJ_SOCKDISC) {
502            
503             print "We got disconneted\n";
504             print "Reconnecting!\n";
505             $kernel->post($sender, 'reconnect_to_server');
506              
507             } elsif ($error == +PCJ_AUTHFAIL) {
508              
509             print "Failed to authenticate\n";
510              
511             } elsif ($error == +PCJ_BINDFAIL) {
512              
513             print "Failed to bind a resource\n";
514            
515             } elsif ($error == +PCJ_SESSFAIL) {
516              
517             print "Failed to establish a session\n";
518             }
519             }
520              
521             1;
522              
523             package POE::Component::DebugShell::Jabber::Output;
524              
525             use strict;
526             #use warnings FATAL => "all";
527              
528             sub PRINT {
529             my $self = shift;
530             my $txt = join('',@_);
531             # $txt =~ s/\r?\n$//;
532             $self->{print}->($self->{type}."> $txt");
533             }
534              
535             sub TIEHANDLE {
536             my $class = shift;
537             bless({
538             type => shift,
539             print => shift,
540             }, $class);
541             }
542              
543             1;
544             __END__
545              
546             =pod
547              
548             =head1 NAME
549              
550             POE::Component::DebugShell::Jabber - Component to allow interactive peeking into a
551             running POE application via Jabber
552              
553             =head1 SYNOPSIS
554              
555             use POE::Component::DebugShell::Jabber;
556              
557             POE::Component::DebugShell::Jabber->spawn(
558             jabber_package => 'POE::Component::Jabber::Client::XMPP',
559             jabber => {
560             IP => 'localhost',
561             HOSTNAME => 'localhost',
562             PORT => '5222',
563             USERNAME => 'bot',
564             PASSWORD => 'test',
565             },
566             users => {
567             'blah@somehost.com' => 1,
568             },
569             );
570              
571             =head1 DESCRIPTION
572              
573             This component allows for interactive peeking into a running POE
574             application.
575              
576             C<spawn()> creates a Jabber client shell equipped with various debug
577             commands. After it connects, stdout and stderr is redirected to all
578             users in the user hash, AND anyone who speaks to the bot. Everyone
579             can issue commands.
580              
581             The following commands are available.
582              
583             =head1 COMMANDS
584              
585             =head2 show_sessions
586              
587             debug> show_sessions
588             * 3 [ session 3 (POE::Component::DebugShell::Jabber controller) ]
589             * 2 [ session 2 (PIE, PIE2) ]
590              
591             Show a list of all sessions in the system. The output format is in the
592             form of loggable session ids.
593              
594             =head2 session_stats
595              
596             debug> session_stats 2
597             Statistics for Session 2
598             Events coming from: 1
599             Events going to: 1
600              
601             Display various statistics for a given session. Provide one session id
602             as a parameter.
603              
604             =head2 list_aliases
605              
606             debug> list_aliases 2
607             Alias list for session 2
608             * PIE
609             * PIE2
610              
611             List aliases for a given session id. Provide one session id as a
612             parameter.
613              
614             =head2 queue_dump
615              
616             debug> queue_dump
617             Event Queue:
618             * ID: 738 - Index: 0
619             Priority: 1078459009.06715
620             Event: _sigchld_poll
621             * ID: 704 - Index: 1
622             Priority: 1078459012.42691
623             Event: ping
624              
625             Dump the contents of the event queue. Add a C<-v> parameter to get
626             verbose output.
627              
628             =head2 help
629              
630             debug> help
631             The following commands are available:
632             ...
633              
634             Display help about available commands.
635              
636             =head2 status
637              
638             debug> status
639             This is POE::Component::DebugShell::Jabber v0.01
640             running inside examples/foo.perl.
641             This console spawned at Fri Apr 29 11:00:34 2004.
642             There are 3 known sessions (including the kernel).
643              
644             General shell status.
645              
646             =head2 reload
647              
648             debug> reload
649             Reloading...
650              
651             Reload the shell
652              
653             =head2 exit
654              
655             debug> exit
656             Exiting...
657              
658             Exit the shell
659              
660             =head1 DEVELOPERS
661              
662             Note from Matt:
663             For you wacky developers, I've provided access to the raw command data
664             via the C<_raw_commands> method. The underbar at the beginning should
665             let you know that this is an experimental interface for developers only.
666              
667             C<_raw_commands> returns a hash reference. The keys of this hash are the
668             command names. The values are a hash of data about the command. This
669             hash contains the following data:
670              
671             =over 4
672              
673             =item * short_help
674              
675             Short help text
676              
677             =item * help
678              
679             Long help text
680              
681             =item * cmd
682              
683             Code reference for the command. This command requires that a hash be
684             passed to it containing an C<api> parameter, which is a
685             C<POE::API::Peek> object, and an C<args> parameter, which is an array
686             reference of arguments (think C<@ARGV>).
687              
688             =head1 AUTHOR
689              
690             David Davis (xantus@cpan.org)
691              
692             =head2 THANKS
693              
694             Matt Cashner (cpan@eekeek.org)
695              
696             =head1 LICENSE
697              
698             Copyright (c) 2005, David Davis
699              
700             Permission is hereby granted, free of charge, to any person obtaining
701             a copy of this software and associated documentation files (the
702             "Software"), to deal in the Software without restriction, including
703             without limitation the rights to use, copy, modify, merge, publish,
704             distribute, sublicense, and/or sell copies of the Software, and to
705             permit persons to whom the Software is furnished to do so, subject
706             to the following conditions:
707              
708             The above copyright notice and this permission notice shall be included
709             in all copies or substantial portions of the Software.
710              
711             THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
712             WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
713             MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
714             EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
715             SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
716             PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
717             OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
718             WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
719             OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
720             ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
721              
722             =cut
723              
724             # sungo // vim: ts=4 sw=4 expandtab