File Coverage

blib/lib/POE/Component/Client/opentick.pm
Criterion Covered Total %
statement 136 203 67.0
branch 23 56 41.0
condition 2 9 22.2
subroutine 30 50 60.0
pod 15 15 100.0
total 206 333 61.8


line stmt bran cond sub pod time code
1             package POE::Component::Client::opentick;
2             #
3             # opentick.com POE client
4             #
5             # infi/2008
6             #
7             # $Id: opentick.pm 56 2009-01-08 16:51:14Z infidel $
8             #
9             # Full POD documentation after __END__
10             #
11              
12 2     2   299713 use strict;
  2         6  
  2         68  
13 2     2   12 use warnings;
  2         3  
  2         55  
14 2     2   971 use Socket;
  2         11271  
  2         1537  
15 2     2   19 use Carp qw( croak );
  2         4  
  2         168  
16 2     2   6859 use Data::Dumper;
  2         52273  
  2         139  
17 2         16 use POE qw( Wheel::SocketFactory Wheel::ReadWrite
18 2     2   977 Driver::SysRW Filter::Stream );
  2         125218  
19              
20             # Ours
21 2     2   219850 use POE::Component::Client::opentick::Constants;
  2         8  
  2         370  
22 2     2   1511 use POE::Component::Client::opentick::Util;
  2         7  
  2         175  
23 2     2   1477 use POE::Component::Client::opentick::Output;
  2         7  
  2         147  
24 2     2   11 use POE::Component::Client::opentick::Error;
  2         5  
  2         92  
25 2     2   1587 use POE::Component::Client::opentick::Protocol;
  2         6  
  2         69  
26 2     2   1644 use POE::Component::Client::opentick::Socket;
  2         5  
  2         87  
27              
28             ###
29             ### Variables
30             ###
31              
32 2     2   18 use vars qw( $VERSION $TRUE $FALSE $KEEP $DELETE $poe_kernel );
  2         6  
  2         4447  
33              
34             $VERSION = '0.21';
35             *TRUE = \1;
36             *FALSE = \0;
37             *KEEP = \0;
38             *DELETE = \1;
39              
40             # These arguments are for this object; pass the rest on.
41             my %our_args = (
42             autologin => $KEEP,
43             alias => $KEEP,
44             events => $DELETE,
45             notifyee => $DELETE,
46             realtime => $KEEP,
47             debug => $KEEP,
48             quiet => $KEEP,
49             );
50              
51             ########################################################################
52             ### Public methods ###
53             ########################################################################
54              
55             # Create the object and POE session instance
56             sub spawn
57             {
58 1     1 1 4611 my( $class, @args ) = @_;
59 1 50       11 croak( "$class requires an even number of parameters" ) if( @args & 1 );
60              
61             # Set our default variables
62 1         8 my $self = {
63             READY => OTConstant( 'OT_STATUS_INACTIVE' ),
64             # ready to accept requests?
65             debug => $FALSE, # in debug mode
66             quiet => $FALSE, # in silent mode
67             alias => OTDefault( 'alias' ), # our POE alias
68             realtime => OTDefault( 'realtime' ), # RealTime quote mode
69             autologin => OTDefault( 'autologin' ), # Auto login?
70             # event callbacks
71             # events => { event_id => { $sess_id => 1, ... }, ... }
72             events => {}, # event notification map
73             notifyee => undef,
74             # object containers
75             sock => undef, # Socket object
76             protocol => undef, # Protocol object
77             # Statistical information
78             start_time => time,
79             };
80              
81             # Set up event hashref with all event names
82 1         10 $self->{events}->{$_} = {} for( OTEventList() );
83              
84             # Create and init the object
85 1         5 bless( $self, $class );
86 1         7 my @leftovers = $self->initialize( @args );
87              
88             # Create the protocol handling object
89 1         14 $self->{protocol} =
90             POE::Component::Client::opentick::Protocol->new( @leftovers );
91              
92             # Create the socket handling object
93 1         13 $self->{sock} =
94             POE::Component::Client::opentick::Socket->new( @leftovers );
95              
96             # GO!
97 1         6 $self->_POE_startup();
98              
99 1         11 return( $self );
100             }
101              
102             # Initialize this object instance
103             sub initialize
104             {
105 1     1 1 10 my( $self, %args ) = @_;
106              
107             # Make sure we have mandatory arguments. I don't really like this.
108 1 50       6 my ($key) = grep { /^notifyee$/i } keys( %args )
  9         25  
109             or croak( 'Notifyee is a mandatory argument' );
110 1         4 my $notifyee = delete( $args{$key} );
111 1 50       4 grep { /^events$/i } keys( %args )
  8         30  
112             or croak( 'Events is a mandatory argument' );
113              
114             # Stash our args...
115 1         6 for( keys( %args ) )
116             {
117 8 100       35 if( $_ =~ /^events$/i )
118             {
119 1 50       7 croak( "Events must be an arrayref" )
120             unless( ref( $args{$_} ) eq 'ARRAY' );
121 1         7 $self->_reg_event( $notifyee, $args{$_} );
122 1         4 delete( $args{ $_ } );
123             }
124             else
125             {
126             # grab our args
127 7 100       24 if( exists( $our_args{lc $_} ) )
128             {
129 3         14 $self->{lc $_} = $args{$_};
130             # delete them if appropriate
131 3 50       14 delete( $args{ $_ } ) if( $our_args{lc $_} == $DELETE );
132             }
133             }
134             }
135              
136             # Set the debug output flag.
137 1 50       6 POE::Component::Client::opentick::Output->set_debug( $TRUE )
138             if( $self->{debug} );
139 1 50       17 POE::Component::Client::opentick::Output->set_quiet( $TRUE )
140             if( $self->{quiet} );
141              
142             # ... and return the rest.
143 1         9 return( %args );
144             }
145              
146             sub new
147             {
148 0     0 1 0 croak( 'Please use spawn() to create a session.' );
149             }
150              
151             # Shut down the OT connection and POE session
152             sub shutdown
153             {
154 0     0 1 0 my( $self ) = @_;
155              
156 0         0 $poe_kernel->post( $self->{alias}, 'shutdown' );
157              
158 0         0 return;
159             }
160              
161             sub login
162             {
163 0     0 1 0 my( $self ) = @_;
164              
165 0         0 $poe_kernel->call( $self->{alias}, '_ot_proto_issue_command',
166             OTConstant( 'OT_LOGIN' ) );
167              
168 0         0 return;
169             }
170              
171             sub logout
172             {
173 0     0 1 0 my( $self ) = @_;
174              
175 0         0 $poe_kernel->call( $self->{alias}, '_ot_proto_issue_command',
176             OTConstant( 'OT_LOGOUT' ) );
177              
178 0         0 return;
179             }
180              
181             # Send an event to OT via object method
182             sub yield
183             {
184 2     2 1 759 my $self = shift;
185              
186 2         17 $poe_kernel->post( $self->{alias} => @_ );
187              
188 2         211 return;
189             }
190              
191             # Call a synchronous event in OT via object method
192             sub call
193             {
194 0     0 1 0 my $self = shift;
195              
196 0         0 return( $poe_kernel->call( $self->{alias} => @_ ) );
197             }
198              
199             # Are we ready for action?
200             sub ready
201             {
202 0     0 1 0 my $self = shift;
203              
204 0 0       0 return( $self->{READY} == OTConstant( 'OT_STATUS_LOGGED_IN' )
205             ? $TRUE
206             : $FALSE );
207             }
208              
209             # The next 3 functions are lame, added to be compatible with the otFeed API
210             sub set_hosts
211             {
212 0     0 1 0 my( $self, @hosts ) = @_;
213 0 0       0 @hosts = @{ $hosts[0] } if( ref( $hosts[0] ) eq 'ARRAY' );
  0         0  
214              
215 0         0 $self->{'socket'}->_set_servers( \@hosts );
216              
217 0         0 return;
218             }
219              
220             sub set_port
221             {
222 0     0 1 0 my( $self, $port ) = @_;
223              
224 0         0 $self->{'socket'}->_set_port( $port );
225              
226 0         0 return;
227             }
228              
229             sub set_platform_id
230             {
231 0     0 1 0 my( $self, $platform_id, $platform_pass ) = @_;
232              
233 0         0 $self->{protocol}->{state_obj}->_set_platform_id( $platform_id );
234 0         0 $self->{protocol}->{state_obj}->_set_platform_pass( $platform_pass );
235              
236 0         0 return;
237             }
238              
239             # return our actual state
240             sub get_status
241             {
242 0     0 1 0 my( $self ) = @_;
243              
244 0         0 return( $self->{READY} );
245             }
246              
247             # Return some statistics
248             # HYBRID METHOD/POE EVENT HANDLER
249             sub statistics
250             {
251 0     0 1 0 my( $self ) = shift;
252              
253 0         0 my @fields = (
254             $self->{sock}->get_packets_sent(),
255             $self->{sock}->get_packets_recv(),
256             $self->{sock}->get_bytes_sent(),
257             $self->{sock}->get_bytes_recv(),
258             $self->{protocol}->get_messages_sent(),
259             $self->{protocol}->get_messages_recv(),
260             $self->{protocol}->get_records_recv(),
261             $self->{protocol}->get_errors_recv(),
262             $self->get_uptime(),
263             $self->{sock}->get_connect_time(),
264             );
265              
266 0         0 return( @fields );
267             }
268              
269             # Admit our age
270             sub get_uptime
271             {
272 0     0 1 0 my( $self ) = shift;
273              
274 0         0 return( time - $self->{start_time} );
275             }
276              
277             ########################################################################
278             ### POE State handlers ###
279             ########################################################################
280              
281             # Called at session start
282             # First callback upon POE start of this session.
283             sub _ot_start
284             {
285 1     1   1067 my( $self, $kernel ) = @_[OBJECT, KERNEL];
286              
287 1         8 O_DEBUG( sprintf 'Starting POE session (ID=%s)',
288             $kernel->get_active_session()->ID() );
289              
290 1         5 $kernel->alias_set( $self->{alias} );
291 1 50       42 $kernel->yield( 'connect' ) if( $self->_auto_login() );
292              
293 1         86 return;
294             }
295              
296             # Called at session shutdown
297             # Final callback before shutdown
298             sub _ot_stop
299             {
300 1     1   87 my( $self, $kernel ) = @_[OBJECT, KERNEL];
301              
302 1         5 O_DEBUG( "Final shutdown called. Bye!" );
303              
304 1         4 return;
305             }
306              
307             # Called on receipt of 'register' event
308             # Register a client for particular events
309             sub _register
310             {
311 0     0   0 my( $self, $kernel, $sender, $events ) = @_[OBJECT, KERNEL, SENDER, ARG0];
312              
313 0         0 return( $self->_reg_event( $sender->ID(), $events ) );
314             }
315              
316             # Called on receipt of 'unregister' event
317             # Unregister a client for particular events
318             sub _unregister
319             {
320 0     0   0 my( $self, $kernel, $sender, $events ) = @_[OBJECT, KERNEL, SENDER, ARG0];
321              
322 0         0 return( $self->_unreg_event( $sender->ID(), $events ) );
323             }
324              
325             # Maximum Reconnect Attempts reached. Complain to someone.
326             sub _reconn_giveup
327             {
328 0     0   0 my( $self, $kernel ) = @_[OBJECT, KERNEL];
329              
330 0         0 O_WARN( "Connection retry limit reached." );
331 0         0 $kernel->yield( '_notify_of_event', OTEvent( 'OT_CONNECT_FAILED' ) );
332              
333 0         0 return;
334             }
335              
336             # Server sent us a redirect request in OT_LOGIN response packet
337             sub _server_redirect
338             {
339 1     1   44 my( $self, $host, $port ) = @_[OBJECT, ARG0, ARG1];
340              
341 1         9 O_NOTICE( "Server redirected us to $host:$port." );
342 1         7 $self->{sock}->redirect( $host, $port );
343              
344 1         3 return;
345             }
346              
347             # Logged in; set up heartbeat and say we're ready to go!
348             sub _ot_on_login
349             {
350 1     1   135 my( $self, $kernel ) = @_[OBJECT, KERNEL];
351              
352             # Remove the connection timeout alarm.
353 1 50       13 $kernel->alarm_remove( delete( $self->{sock}->{timeout_id} ) )
354             if( $self->{sock}->{timeout_id} );
355              
356 1         90 $self->{READY} = OTConstant( 'OT_STATUS_LOGGED_IN' );
357 1         4 $self->{sock}->_set_state( OTConstant( 'OT_STATUS_LOGGED_IN' ) );
358 1         4 $kernel->yield( '_ot_proto_heartbeat_send' );
359              
360 1         67 return;
361             }
362              
363             # Logged out; stop heartbeat and disable ready flag
364             sub _ot_on_logout
365             {
366 0     0   0 my( $self, $kernel ) = @_[OBJECT, KERNEL];
367              
368 0         0 O_DEBUG( "We are logged out" );
369              
370 0         0 $kernel->yield( '_ot_proto_heartbeat_stop' );
371 0         0 $self->{READY} = OTConstant( 'OT_STATUS_INACTIVE' );
372 0         0 $self->{sock}->_set_state( OTConstant( 'OT_STATUS_INACTIVE' ) );
373              
374 0         0 return;
375             }
376              
377             sub _status_changed
378             {
379 6     6   1624 my( $self, $status ) = @_[OBJECT, ARG0];
380              
381 6         20 return( $self->{READY} = $status );
382             }
383              
384             # Pass on event notifications to their recipients
385             sub _notify_of_event
386             {
387 9     9   1959 my( $self, $kernel, $event_type, $extra_recips, @args )
388             = @_[OBJECT,KERNEL,ARG0..$#_];
389             # Resolve event properly.
390 9 50       53 my $event = ( $event_type =~ /^\d+$/ )
391             ? OTEvent( $event_type )
392             : $event_type;
393 9         13 my( $notify_count, %seen );
394              
395             # Prepend our extra recipients to the list, but don't send two events.
396 2     2   15 no warnings 'uninitialized';
  2         4  
  2         3346  
397 17         67 my @recipients = grep { not $seen{$_}++ }
  9         45  
398 9         20 ( @$extra_recips, keys( %{$self->{events}->{$event}} ) );
399              
400             # Send!
401 9         22 for my $recipient ( @recipients )
402             {
403 17         60 $poe_kernel->post( $recipient, $event, @args );
404 17         1619 $notify_count++;
405             }
406              
407 9         49 return( $notify_count );
408             }
409              
410             ### API event receiver/dispatcher
411             sub _api_dispatch
412             {
413 0     0   0 my( $self, $kernel, $event, $sender, @args )
414             = @_[OBJECT, KERNEL, STATE, SENDER, ARG0..$#_];
415              
416 0         0 O_DEBUG( "_api_dispatch( $event ) from sender: " . $sender->ID() );
417              
418             # Find the command number, and report on irregularities.
419 0         0 my ($cmd_number, $deprecated) = OTAPItoCommand( $event );
420 0 0       0 O_WARN( "$event is deprecated by opentick; please use " .
421             OTCommandtoAPI( $deprecated ) . " instead." )
422             if( $deprecated );
423 0 0       0 O_ERROR( "No known command mapping for $event." )
424             unless( $cmd_number );
425            
426             # Dispatch the command
427 0 0       0 my $retval = $kernel->call( $self->{alias}, '_ot_proto_issue_command',
428             $cmd_number, @args )
429             if( $cmd_number );
430              
431 0         0 return( $retval );
432             }
433              
434             # Logout event trap.
435             sub _logged_out
436             {
437 1     1   203 my( $self, $kernel ) = @_[OBJECT, KERNEL];
438              
439 1 50 33     22 $self->{sock}->_reset_object() if( $self and $self->{sock} );
440              
441 1         8 $self->yield( _notify_of_event => OTEvent( 'OT_ON_LOGOUT' ) );
442              
443 1 50       6 $self->_final_cleanup() if( $self->_is_disconnecting() );
444              
445 1         6 return;
446             }
447              
448             # We got some unknown event.
449             # XXX: Perhaps we should send this back as an ot_on_error event.
450             sub _unknown_event
451             {
452 0     0   0 my( $self, $event ) = @_[OBJECT, ARG0];
453              
454 0         0 O_DEBUG( "Unhandled event '$event'" );
455              
456 0         0 return;
457             }
458              
459             # Do nothing, for useless events
460 0     0   0 sub _do_nothing {}
461              
462             ########################################################################
463             ### Private methods ###
464             ########################################################################
465              
466             # Start me up.
467             sub _POE_startup
468             {
469 1     1   3 my( $self ) = @_;
470              
471 1         7 POE::Session->create(
472             object_states => [
473             # General events for the entire interface
474             $self => {
475             _start => '_ot_start',
476             _stop => '_ot_stop',
477             _default => '_unknown_event',
478             _server_redirect => '_server_redirect',
479             _reconn_giveup => '_reconn_giveup',
480             _notify_of_event => '_notify_of_event',
481             _logged_out => '_logged_out',
482             # public sendable events
483             shutdown => '_POE_shutdown',
484             register => '_register',
485             unregister => '_unregister',
486             statistics => 'statistics',
487             # public receivable events that we need to handle, too
488             OTEvent('OT_ON_LOGIN') => '_ot_on_login',
489             OTEvent('OT_ON_DATA') => '_do_nothing',
490             OTEvent('OT_ON_LOGOUT') => '_do_nothing',
491             OTEvent('OT_ON_ERROR') => '_do_nothing',
492             OTEvent('OT_REQUEST_COMPLETE') => '_do_nothing',
493             OTEvent('OT_REQUEST_CANCELLED') => '_do_nothing',
494             OTEvent('OT_STATUS_CHANGED') => '_status_changed',
495             # API commands
496             requestSplits => '_api_dispatch',
497             requestDividends => '_api_dispatch',
498             requestOptionInit => '_api_dispatch',
499             requestHistData => '_api_dispatch',
500             requestHistTicks => '_api_dispatch',
501             requestTickStream => '_api_dispatch',
502             requestTickStreamEx => '_api_dispatch',
503             requestTickSnapshot => '_api_dispatch',
504             requestOptionChain => '_api_dispatch',
505             requestOptionChainEx => '_api_dispatch',
506             requestOptionChainU => '_api_dispatch',
507             requestOptionChainSnapshot => '_api_dispatch',
508             requestEqInit => '_api_dispatch',
509             requestEquityInit => '_api_dispatch', # alias
510             requestBookStream => '_api_dispatch',
511             requestBookStreamEx => '_api_dispatch',
512             requestHistBooks => '_api_dispatch',
513             requestListSymbols => '_api_dispatch',
514             requestListSymbolsEx => '_api_dispatch',
515             requestListExchanges => '_api_dispatch',
516             cancelTickStream => '_api_dispatch',
517             cancelBookStream => '_api_dispatch',
518             cancelHistData => '_api_dispatch',
519             cancelOptionChain => '_api_dispatch',
520             },
521             $self->{sock} => [
522             # Socket events
523             qw(
524             connect
525             disconnect
526             reconnect
527             _redirect
528             _ot_sock_connected
529             _ot_sock_connfail
530             _ot_sock_conntimeout
531             _ot_sock_error
532             _ot_sock_receive_packet
533             _ot_sock_send_packet
534             ),
535             ],
536             $self->{protocol} => [
537             # Protocol events
538             qw(
539             logout
540             login
541             _ot_proto_issue_command
542             _ot_proto_process_response
543             _ot_proto_end_of_data
544             _ot_proto_heartbeat_send
545             _ot_proto_heartbeat_stop
546             ),
547             ],
548             $self->{protocol}->{state_obj} => [
549             # Individual protocol message type events
550             qw(
551             _ot_msg_login_o
552             _ot_msg_generic_o
553             _ot_msg_nobody_o
554             _ot_msg_login_i
555             _ot_msg_logout_i
556             _ot_msg_single_i
557             _ot_msg_singledt_i
558             _ot_msg_multi_i
559             _ot_msg_multidt_i
560             _ot_msg_listex_i
561             _ot_msg_cancel_i
562             _ot_msg_nobody_i
563             ),
564             ],
565             ],
566             heap => $self,
567             );
568              
569 1         120 return;
570             }
571              
572             # Shut the client down gracefully.
573             sub _POE_shutdown
574             {
575 1     1   122 my( $self, $kernel ) = @_[OBJECT, KERNEL];
576              
577 1         5 $self->_is_disconnecting( $TRUE );
578 1         6 $self->{sock}->disconnect();
579              
580 1         3 return;
581             }
582              
583             # Clean up everything and die already, already.
584             sub _final_cleanup
585             {
586 1     1   3 my( $self ) = @_;
587              
588 1         4 delete( $self->{sock} );
589 1         3 delete( $self->{protocol} );
590 1         7 $poe_kernel->alarm_remove_all();
591 1         188 $poe_kernel->alias_remove( $self->{alias} );
592 1         61 undef( $self );
593              
594 1         4 return;
595             }
596              
597             # Register an event handler message to be sent to a session from opentick
598             # $events = \@aryref
599             sub _reg_event
600             {
601 1     1   3 my( $self, $sender_id, $events ) = @_;
602              
603 1         2 my $regged = 0;
604 1 50 33     12 if( $sender_id && ref( $events ) eq 'ARRAY' )
605             {
606 1 50       5 $events = [ OTEventList() ] if( grep { /^all$/i } @$events );
  1         10  
607 1         5 for( @$events )
608             {
609 8 50       24 if( OTEventByEvent( $_ ) )
610             {
611 8         32 $self->{events}->{$_}->{$sender_id} = $TRUE;
612 8         13 $regged++;
613             }
614             }
615             }
616              
617 1         5 return( $regged );
618             }
619              
620             # Register an event handler message to be sent to a session from opentick
621             # $events = \@aryref
622             sub _unreg_event
623             {
624 0     0   0 my( $self, $sender_id, $events ) = @_;
625              
626 0         0 my $unregged = 0;
627 0 0 0     0 if( $sender_id && ref( $events ) eq 'ARRAY' )
628             {
629 0 0       0 $events = [ OTEventList() ] if( grep { /^all$/i } @$events );
  0         0  
630 0         0 for( @$events )
631             {
632 0 0       0 next unless( OTEventByEvent( $_ ) );
633 0         0 $unregged += delete( $self->{events}->{$_}->{$sender_id} );
634             }
635             }
636              
637 0         0 return( $unregged );
638             }
639              
640             #######################################################################
641             ### Accessor methods ###
642             #######################################################################
643              
644             sub _auto_login
645             {
646 1     1   2 my( $self ) = @_;
647              
648 1 50       11 return( $self->{autologin} ? $TRUE : $FALSE );
649             }
650              
651             sub _is_disconnecting
652             {
653 2     2   5 my( $self, $value ) = @_;
654              
655 2 50       12 $self->{is_disconnecting} = $value ? $TRUE : $FALSE
    100          
656             if( defined( $value ) );
657              
658 2         12 return( $self->{is_disconnecting} );
659             }
660              
661             1;
662              
663             __END__