em-simple_telnet 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/em-simple_telnet.rb +204 -102
  3. metadata +3 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c4aab9248d4b5ccdcf26df97d65c76538547400
4
- data.tar.gz: c0f70fd5b4944e728c69efb73e0670c7b652b094
3
+ metadata.gz: 78218823aba97e9250159f56bbb7c666a52178f8
4
+ data.tar.gz: 3035ce24632a5f23788253e8ff9c022175711965
5
5
  SHA512:
6
- metadata.gz: 0826dd91834c7a2c0de84af593b8c3b5adfbbb5027e02e2474116f405613a550a855c47af9b92bdd0cd291e2f38aea2cb036640ac37b6ba205bed5c755cdcdd9
7
- data.tar.gz: aeb3ce7d37acff62fd09dad0956517ac4069478426d429a1d5cc5c5b6c87ea77ee215163a7045bd8a81efbf3dc886250883002d4793373c250a376990fcbe0f3
6
+ metadata.gz: 7d7c0638b72d5c0b1fe0cee058b9391d965edf491af743a6ab19572656d6e1cb717aecf1f05304a4b5441e0c57875dc209b1e48cd6c875b6a532292559ebd937
7
+ data.tar.gz: 3f0bb2ee015a659e426afce49fd3148fde96bee0e501cce2e8e909763c47e89046a79513b769eceab498ffda37c10decab1ebe35095c0ab0bd13774653a8e21a
@@ -187,6 +187,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
187
187
  fiber = Fiber.new do | callback |
188
188
  connection = connect(opts, &blk)
189
189
  callback.call if callback
190
+ @@_fiber_statuses.delete opts[:hostname]
190
191
  end
191
192
 
192
193
  if EventMachine.reactor_running?
@@ -229,19 +230,11 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
229
230
  # start establishing the connection
230
231
  connection = EventMachine.connect(*params)
231
232
 
232
- # set callback to be executed when connection establishing
233
- # fails/succeeds
234
- f = Fiber.current
235
- connection.connection_state_callback = ->(o){ f.resume(o) }
236
-
237
- # block here and get result from establishing connection
238
- state = Fiber.yield
239
-
240
- # raise if exception (e.g. Telnet::ConnectionFailed)
241
- raise state if state.is_a? Exception
233
+ # will be resumed by #connection_completed or #unbind
234
+ connection.pause_and_wait_for_result
242
235
 
243
236
  # login
244
- connection.instance_eval { login }
237
+ connection.__send__(:login)
245
238
 
246
239
  begin
247
240
  yield connection
@@ -365,11 +358,13 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
365
358
 
366
359
  @logged_in = nil
367
360
  @connection_state = :connecting
368
- @connection_state_callback = nil
361
+ f = Fiber.current
362
+ @fiber_resumer = ->(result = nil){ f.resume(result) }
369
363
  @input_buffer = ""
370
364
  @input_rest = ""
371
365
  @wait_time_timer = nil
372
366
  @check_input_buffer_timer = nil
367
+ @recently_received_data = "" if $DEBUG
373
368
 
374
369
  setup_logging
375
370
  end
@@ -386,8 +381,13 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
386
381
  # used telnet options Hash
387
382
  attr_reader :telnet_options
388
383
 
389
- # the callback executed after connection established or failed
390
- attr_accessor :connection_state_callback
384
+ # the callback executed again and again to resume this connection's Fiber
385
+ attr_accessor :fiber_resumer
386
+
387
+ def connection_state_callback
388
+ warn "#connection_state_callback deprecated. use #fiber_resumer instead"
389
+ fiber_resumer
390
+ end
391
391
 
392
392
  # last prompt matched
393
393
  attr_reader :last_prompt
@@ -428,6 +428,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
428
428
  #
429
429
  def timeout= seconds
430
430
  @telnet_options[:timeout] = seconds
431
+ # warn "#{node}: Setting comm_inactivity_timeout to #{seconds} ...".yellow
431
432
  set_comm_inactivity_timeout( seconds )
432
433
  end
433
434
 
@@ -492,6 +493,8 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
492
493
  # #log_output. Then calls #process_payload.
493
494
  #
494
495
  def receive_data data
496
+ @last_received_data = Time.now
497
+ @recently_received_data << data if $DEBUG
495
498
  if @telnet_options[:telnet_mode]
496
499
  c = @input_rest + data
497
500
  se_pos = c.rindex(/#{IAC}#{SE}/no) || 0
@@ -536,8 +539,8 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
536
539
 
537
540
  ##
538
541
  # Appends _buf_ to the <tt>@input_buffer</tt>.
539
- # Then cancels the @wait_time_timer if there was one.
540
- # Does nothing else if there's no @connection_state_callback.
542
+ # Then cancels the @wait_time_timer and @check_input_buffer_timer if they're
543
+ # set.
541
544
  #
542
545
  # Does some performance optimizations in case the input buffer is becoming
543
546
  # huge and finally calls #check_input_buffer.
@@ -546,77 +549,95 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
546
549
  # append output from server to input buffer and log it
547
550
  @input_buffer << buf
548
551
 
549
- # cancel the timer for wait_time value because we received more data
550
- if @wait_time_timer
551
- @wait_time_timer.cancel
552
- @wait_time_timer = nil
553
- end
552
+ case @connection_state
553
+ when :waiting_for_prompt
554
554
 
555
- # we only need to do something if there's a connection state callback
556
- return unless @connection_state_callback
555
+ # cancel the timer for wait_time value because we received more data
556
+ if @wait_time_timer
557
+ @wait_time_timer.cancel
558
+ @wait_time_timer = nil
559
+ end
557
560
 
558
- # we ensure there's no timer running for checking the input buffer
559
- if @check_input_buffer_timer
560
- @check_input_buffer_timer.cancel
561
- @check_input_buffer_timer = nil
562
- end
561
+ # we ensure there's no timer running for checking the input buffer
562
+ if @check_input_buffer_timer
563
+ @check_input_buffer_timer.cancel
564
+ @check_input_buffer_timer = nil
565
+ end
563
566
 
564
- if @input_buffer.size >= 100_000
565
- ##
566
- # if the input buffer is really big
567
- #
567
+ if @input_buffer.size >= 100_000
568
+ ##
569
+ # if the input buffer is really big
570
+ #
571
+
572
+ # We postpone checking the input buffer by one second because the regular
573
+ # expression matches can get quite slow.
574
+ #
575
+ # So as long as data is being received (continuously), the input buffer
576
+ # is not checked. It's only checked one second after the whole output
577
+ # has been received.
578
+ @check_input_buffer_timer = EventMachine::Timer.new(1) do
579
+ @check_input_buffer_timer = nil
580
+ check_input_buffer
581
+ end
582
+ else
583
+ ##
584
+ # as long as the input buffer is small
585
+ #
568
586
 
569
- # We postpone checking the input buffer by one second because the regular
570
- # expression matches can get quite slow.
571
- #
572
- # So as long as data is being received (continuously), the input buffer
573
- # is not checked. It's only checked one second after the whole output
574
- # has been received.
575
- @check_input_buffer_timer = EventMachine::Timer.new(1) do
576
- @check_input_buffer_timer = nil
587
+ # check the input buffer now
577
588
  check_input_buffer
578
589
  end
590
+ when :listening
591
+ @fiber_resumer.(buf)
592
+ when :connected, :sleeping
593
+ if $VERBOSE
594
+ warn "#{node}: Discarding data that was received while not waiting " +
595
+ "for data (state = #{@connection_state.inspect}): #{buf.inspect}"
596
+ end
579
597
  else
580
- ##
581
- # as long as the input buffer is small
582
- #
583
-
584
- # check the input buffer now
585
- check_input_buffer
598
+ raise "Don't know what to do with received data while being in " +
599
+ "connection state #{@connection_state.inspect}"
586
600
  end
587
601
  end
588
602
 
589
603
  ##
590
604
  # Checks the input buffer (<tt>@input_buffer</tt>) for the prompt we're
591
- # waiting for. Calls #call_connection_state_callback with the output if the
592
- # prompt has been found. Thus, call this method *only* if
593
- # <tt>@connection_state_callback</tt> is set!
605
+ # waiting for. Calls @fiber_resumer with the output if the
606
+ # prompt has been found.
594
607
  #
595
608
  # If <tt>@telnet_options[:wait_time]</tt> is set, it will wait this amount
596
609
  # of seconds after seeing what looks like the prompt before calling
597
- # #call_connection_state_callback. This way, more data can be received
610
+ # @fiber_resumer. This way, more data can be received
598
611
  # until the real prompt is received. This is useful for commands that send
599
612
  # multiple prompts.
600
613
  #
601
614
  def check_input_buffer
602
615
  return unless md = @input_buffer.match(@telnet_options[:prompt])
603
616
 
604
- blk = lambda do
605
- @last_prompt = md.to_s # remember last prompt
606
- output = md.pre_match + @last_prompt
607
- @input_buffer = md.post_match
608
- call_connection_state_callback(output)
609
- end
610
-
611
617
  if s = @telnet_options[:wait_time] and s > 0
612
- # fire @connection_state_callback after s seconds
613
- @wait_time_timer = EventMachine::Timer.new(s, &blk)
618
+ # resume Fiber after s seconds
619
+ @wait_time_timer = EventMachine::Timer.new(s) { process_match_data(md) }
614
620
  else
615
- # fire @connection_state_callback now
616
- blk.call
621
+ # resume Fiber now
622
+ process_match_data(md)
617
623
  end
618
624
  end
619
625
 
626
+ # Takes out the @last_prompt from _md_ (MatchData) and remembers it.
627
+ # Resumes the fiber (using @fiber_resumer) with the output (which
628
+ # includes the prompt and everything before).
629
+ def process_match_data(md)
630
+ @last_prompt = md.to_s # remember the prompt matched
631
+ output = md.pre_match + @last_prompt
632
+ @input_buffer = md.post_match
633
+ @fiber_resumer.(output)
634
+ end
635
+
636
+ @@_fiber_statuses = {}
637
+ def self._fiber_statuses
638
+ @@_fiber_statuses
639
+ end
640
+
620
641
  ##
621
642
  # Read data from the host until a certain sequence is matched.
622
643
  #
@@ -633,6 +654,9 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
633
654
  # * +:wait_time+ (actually used by #check_input_buffer)
634
655
  #
635
656
  def waitfor prompt=nil, opts={}
657
+ if closed?
658
+ abort "Can't wait for anything when connection is already closed!"
659
+ end
636
660
  options_were = @telnet_options
637
661
  timeout_was = self.timeout if opts.key?(:timeout)
638
662
  opts[:prompt] = prompt if prompt
@@ -649,25 +673,78 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
649
673
 
650
674
  # so #unbind knows we were waiting for a prompt (in case that inactivity
651
675
  # timeout fires)
652
- @connection_state = :waiting_for_prompt
676
+ self.connection_state = :waiting_for_prompt
653
677
 
654
- # for the block in @connection_state_callback
655
- f = Fiber.current
678
+ pause_and_wait_for_result
679
+ ensure
680
+ @telnet_options = options_were
681
+ self.timeout = timeout_was if opts.key?(:timeout)
682
+
683
+ # #unbind could have been called in the meantime
684
+ self.connection_state = :connected if !closed?
685
+ end
656
686
 
657
- # will be called by #receive_data to resume at "Fiber.yield" below
658
- @connection_state_callback = ->(output){ f.resume(output) }
687
+ # Pauses the current Fiber. When resumed, returns the value passed. If the
688
+ # value passed is an Exeption, it's raised.
689
+ def pause_and_wait_for_result
690
+ @@_fiber_statuses[node] = :paused
691
+ result = nil
692
+ while result == nil
693
+ # measure how long Fiber is paused
694
+ if $DEBUG
695
+ before_pause = Time.now
696
+ result = Fiber.yield
697
+ pause_duration = Time.now - before_pause
698
+
699
+ m = "#{node}: Fiber was paused for #{pause_duration * 1000}ms and " +
700
+ "is resumed with: #{result.inspect}"
701
+ result.nil? ? warn(m.red) : warn(m)
702
+ else
703
+ result = Fiber.yield
704
+ end
705
+ end
659
706
 
660
- result = Fiber.yield
707
+ @@_fiber_statuses[node] = :running
661
708
 
662
709
  raise result if result.is_a? Exception
663
710
  return result
711
+ end
712
+
713
+ # Identifier for this connection. Like an IP address or hostname. In this
714
+ # case, it's <tt>@telnet_options[:host]</tt>.
715
+ def node
716
+ @telnet_options[:host]
717
+ end
718
+
719
+ # Listen for anything that's received from the node. Each received chunk
720
+ # will be yielded to the block passed. To make it stop listening, the block
721
+ # should +return+ or +raise+ something.
722
+ #
723
+ # The default timeout during listening is 90 seconds. Use the option
724
+ # +:timeout+ to change this.
725
+ def listen(opts = {}, &blk)
726
+ self.connection_state = :listening
727
+ timeout(opts.fetch(:timeout, 90)) do
728
+ yield pause_and_wait_for_result while true
729
+ end
664
730
  ensure
665
- @telnet_options = options_were
666
- self.timeout = timeout_was if opts.key?(:timeout)
667
- @connection_state = :connected
731
+ self.connection_state = :connected
732
+ end
733
+
734
+ # Passes argument to #send_data.
735
+ def write(s)
736
+ send_data(s)
668
737
  end
669
738
 
670
- alias :write :send_data
739
+ # Raises Errno::ENOTCONN in case the connection is closed (#unbind has been
740
+ # called before). Also contains some debugging stuff depending on $DEBUG.
741
+ def send_data(s)
742
+ raise Errno::ENOTCONN, "Connection is already closed." if closed?
743
+ @last_sent_data = Time.now
744
+ print_recently_received_data if $DEBUG
745
+ warn "#{node}: Sending #{s.inspect}" if $DEBUG
746
+ super
747
+ end
671
748
 
672
749
  ##
673
750
  # Sends a string to the host.
@@ -830,56 +907,74 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
830
907
  #
831
908
  # Decreases <tt>@@_telnet_connection_count</tt> by one and calls #close_logs.
832
909
  #
833
- # After that and if <tt>@connection_state_callback</tt> is set, it takes a
910
+ # After that is set, it takes a
834
911
  # look at <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
835
- # #call_connection_state_callback with a new instance of ConnectionFailed.
912
+ # @fiber_resumer with a new instance of ConnectionFailed.
836
913
  # If it was <tt>:waiting_for_prompt</tt>, calls the same method with a new
837
914
  # instance of TimeoutError.
838
915
  #
839
916
  # Finally, the <tt>@connection_state</tt> is set to +closed+.
840
917
  #
841
- def unbind
918
+ def unbind(reason)
919
+ @unbound_at = Time.now
920
+ prev_conn_state = @connection_state
921
+ self.connection_state = :closed
922
+ if $DEBUG
923
+ warn "#{node}: unbinding because of: " + reason.inspect.red.bold
924
+ end
925
+ @@unbound_at[node] = [ Time.now, prev_conn_state ]
842
926
  @@_telnet_connection_count -= 1
843
927
  close_logs
844
928
 
845
- if @connection_state_callback
846
- # if we were connecting or waiting for a prompt, return an exception to
847
- # #waitfor
848
- case @connection_state
849
- when :connecting
850
- call_connection_state_callback(ConnectionFailed.new)
851
- when :waiting_for_prompt
852
- error = TimeoutError.new
853
-
854
- # set hostname and command
855
- if hostname = @telnet_options[:host]
856
- error.hostname = hostname
857
- end
858
- error.command = @last_command if @last_command
929
+ # if we were connecting or waiting for a prompt, return an exception to
930
+ # #waitfor
931
+ case prev_conn_state
932
+ when :waiting_for_prompt, :listening
933
+ # NOTE: reason should be Errno::ETIMEDOUT in these cases.
934
+ error = TimeoutError.new
859
935
 
860
- call_connection_state_callback(error)
936
+ # set hostname and command
937
+ if hostname = @telnet_options[:host]
938
+ error.hostname = hostname
861
939
  end
862
- end
940
+ error.command = @last_command if @last_command
863
941
 
864
- @connection_state = :closed
865
- end
942
+ @fiber_resumer.(error)
943
+ when :sleeping, :connected
866
944
 
867
- ##
868
- # Calls the @connection_state_callback with _obj_ and sets it to +nil+.
869
- #
870
- # Call this method *only* if <tt>@connection_state_callback</tt> is set!
871
- #
872
- def call_connection_state_callback(obj=nil)
873
- callback, @connection_state_callback = @connection_state_callback, nil
874
- callback.call(obj)
945
+ when :connecting
946
+ @fiber_resumer.(ConnectionFailed.new)
947
+ else
948
+ m = "#{node}: bad connection state #{prev_conn_state.inspect} " +
949
+ "while unbinding"
950
+ warn m.red
951
+ debugger
952
+ end
875
953
  end
876
954
 
955
+ @@unbound_at = {}
956
+
957
+ def self.unbound_at() @@unbound_at end
958
+
877
959
  ##
878
960
  # Called by EventMachine after the connection is successfully established.
879
961
  #
880
962
  def connection_completed
881
- @connection_state = :connected
882
- call_connection_state_callback if @connection_state_callback
963
+ self.connection_state = :connected
964
+ @fiber_resumer.(:connection_completed)
965
+
966
+ # print received data in a more readable way
967
+ if $DEBUG
968
+ EventMachine.add_periodic_timer(0.5) { print_recently_received_data }
969
+ end
970
+ end
971
+
972
+ # Prints recently received data (@recently_received), if there's any, and
973
+ # empties that buffer afterwards.
974
+ def print_recently_received_data
975
+ return if @recently_received_data.empty?
976
+ warn "#{node}: Received: #{@recently_received_data.inspect}".cyan
977
+ @recently_received_data = ""
883
978
  end
884
979
 
885
980
  ##
@@ -907,6 +1002,13 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
907
1002
 
908
1003
  private
909
1004
 
1005
+ # Sets the @connection_state to _new_state_. Raises if current (old) state is
1006
+ # :closed, because that can't be changed.
1007
+ def connection_state=(new_state)
1008
+ raise Errno::ENOTCONN, "Connection is already closed." if closed?
1009
+ @connection_state = new_state
1010
+ end
1011
+
910
1012
  ##
911
1013
  # Sets up output and command logging.
912
1014
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-simple_telnet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-03 00:00:00.000000000 Z
11
+ date: 2013-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -54,9 +54,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
54
  version: '0'
55
55
  requirements: []
56
56
  rubyforge_project:
57
- rubygems_version: 2.0.0
57
+ rubygems_version: 2.0.2
58
58
  signing_key:
59
59
  specification_version: 4
60
60
  summary: Simple telnet client on EventMachine
61
61
  test_files: []
62
- has_rdoc: true