lightstreamer 0.1 → 0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf90908d8a97af1417f8391f16e2c26a10baf73d
4
- data.tar.gz: 6e89354b80f3712ca9c4278f09342123d3844878
3
+ metadata.gz: 7dd1721bffbb480dfa570531e520c727084a58d9
4
+ data.tar.gz: 67ee866151859546536fca838dabfe05e84a1d2d
5
5
  SHA512:
6
- metadata.gz: a17d1e9276c986f6c436e3aad883f4f068b319e9e88e30b7e7eb844b1af33ca18c35dab74a76e2e25763263199c7f251acf45d999a07afeb749b424d6ea58985
7
- data.tar.gz: d359e20a661d5dc953f5ba02d3984f4db0206886b38ebdc1ab555c20c5b367eab9aa8499071eef6ba6f074da116925cf2774742c043e314bdd21f419126535a9
6
+ metadata.gz: 0e52c034fa5c273733b97373038f1ccb9ac18959b9ab8c47b47c515ce64a954f3b397966cb02d5e924183364ec47d0402454faf117e4dbef3d3451fb5cfdaafa
7
+ data.tar.gz: 8a79799ea6def575bd5aed0bbbe9d514512d4db4bd32b5dd6417a320d8b929739931714403f4312b3316f326280dc049c15ca883c5af929be9ba92c62687451a
@@ -1,5 +1,12 @@
1
1
  # Lightstreamer Changelog
2
2
 
3
- ### 0.1 — July 23, 2016
3
+ ### 0.2 — July 23, 2016
4
+
5
+ - Added complete test suite
6
+ - Added `Lightstreamer::Session#subscribed?` and `Lightstreamer::Session#disconnect`
7
+ - Fixed `Lightstreamer::ProtocolError#code` not being set
8
+ - Renamed the command-line client's `--address` option to `--server-url`
9
+
10
+ ### 0.1 — July 22, 2016
4
11
 
5
12
  - Initial release
data/README.md CHANGED
@@ -75,7 +75,7 @@ subscription, then print streaming output from the server as it becomes availabl
75
75
  To print streaming data from the demo server run the following command:
76
76
 
77
77
  ```
78
- lightstreamer --address http://push.lightstreamer.com --adapter-set DEMO --adapter QUOTE_ADAPTER \
78
+ lightstreamer --server-url http://push.lightstreamer.com --adapter-set DEMO --adapter QUOTE_ADAPTER \
79
79
  --items item1 item2 item3 item4 item5 --fields time stock_name bid ask bid
80
80
  ```
81
81
 
@@ -11,7 +11,7 @@ require 'lightstreamer/subscription'
11
11
  require 'lightstreamer/version'
12
12
 
13
13
  require 'lightstreamer/cli/main'
14
- require 'lightstreamer/cli/stream_command'
14
+ require 'lightstreamer/cli/commands/stream_command'
15
15
 
16
16
  # This module contains all the code for the Lightstreamer gem. See `README.md` to get started with using this gem.
17
17
  module Lightstreamer
@@ -4,7 +4,7 @@ module Lightstreamer
4
4
  class Main < Thor
5
5
  desc 'stream', 'Streams a set of items and fields from a Lightstreamer server and prints the live output'
6
6
 
7
- option :address, required: true, desc: 'The address of the Lightstreamer server'
7
+ option :server_url, required: true, desc: 'The URL of the Lightstreamer server'
8
8
  option :username, desc: 'The username for the session'
9
9
  option :password, desc: 'The password for the session'
10
10
  option :adapter_set, desc: 'The name of the adapter set for the session'
@@ -30,7 +30,7 @@ module Lightstreamer
30
30
 
31
31
  # Creates a new session from the specified options.
32
32
  def create_session
33
- Lightstreamer::Session.new server_url: options[:address], username: options[:username],
33
+ Lightstreamer::Session.new server_url: options[:server_url], username: options[:username],
34
34
  password: options[:password], adapter_set: options[:adapter_set]
35
35
  end
36
36
 
@@ -39,12 +39,14 @@ module Lightstreamer
39
39
  subscription = Lightstreamer::Subscription.new items: options[:items], fields: options[:fields],
40
40
  mode: options[:mode], adapter: options[:adapter]
41
41
 
42
- subscription.add_data_callback do |_subscription, item_name, _item_data, new_values|
43
- @queue.push "#{item_name} - #{new_values.map { |key, value| "#{key}: #{value}" }.join ', '}"
44
- end
42
+ subscription.add_data_callback(&method(:subscription_data_callback))
45
43
 
46
44
  subscription
47
45
  end
46
+
47
+ def subscription_data_callback(_subscription, item_name, _item_data, new_values)
48
+ @queue.push "#{item_name} - #{new_values.map { |key, value| "#{key}: #{value}" }.join ', '}"
49
+ end
48
50
  end
49
51
  end
50
52
  end
@@ -26,10 +26,7 @@ module Lightstreamer
26
26
  def execute(options)
27
27
  result = execute_post_request build_payload(options)
28
28
 
29
- return if result.first == 'OK'
30
- raise ProtocolError, result[2], result[1] if result.first == 'ERROR'
31
-
32
- warn "Lightstreamer: unexpected response from server: #{result}"
29
+ raise ProtocolError.new(result[2], result[1]) if result.first == 'ERROR'
33
30
  end
34
31
 
35
32
  private
@@ -14,7 +14,7 @@ module Lightstreamer
14
14
  # @param [Integer] code The numeric code for the error.
15
15
  def initialize(error, code)
16
16
  @error = error.to_s
17
- @http_code = code.to_i
17
+ @code = code.to_i
18
18
 
19
19
  super "Lightstreamer error: #{@error}, code: #{code}"
20
20
  end
@@ -34,8 +34,9 @@ module Lightstreamer
34
34
  # Creates a new Lightstreamer session using the details passed to {#initialize}. If an error occurs then
35
35
  # {ProtocolError} will be raised.
36
36
  def connect
37
+ return if @stream_connection
38
+
37
39
  @stream_connection = StreamConnection.new self
38
- @subscriptions = []
39
40
 
40
41
  first_line = @stream_connection.read_line
41
42
 
@@ -48,6 +49,19 @@ module Lightstreamer
48
49
  end
49
50
  end
50
51
 
52
+ # Disconnects this session and shuts down its stream and processing threads.
53
+ def disconnect
54
+ @stream_connection.disconnect if @stream_connection
55
+
56
+ if @processing_thread
57
+ Thread.kill @processing_thread
58
+ @processing_thread.join
59
+ end
60
+
61
+ @processing_thread = @stream_connection = @control_connection = nil
62
+ @subscriptions = []
63
+ end
64
+
51
65
  # Subscribes this Lightstreamer session to the specified subscription.
52
66
  #
53
67
  # @param [Subscription] subscription The new subscription to subscribe to.
@@ -66,13 +80,20 @@ module Lightstreamer
66
80
  end
67
81
  end
68
82
 
83
+ # Returns whether the specified subscription is currently active on this session.
84
+ #
85
+ # @param [Subscription] subscription The subscription to return the status for.
86
+ #
87
+ # @return [Boolean] Whether the specified subscription is currently active on this session.
88
+ def subscribed?(subscription)
89
+ @subscriptions_mutex.synchronize { @subscriptions.include? subscription }
90
+ end
91
+
69
92
  # Unsubscribes this Lightstreamer session from the specified subscription.
70
93
  #
71
94
  # @param [Subscription] subscription The existing subscription to unsubscribe from.
72
95
  def unsubscribe(subscription)
73
- @subscriptions_mutex.synchronize do
74
- raise ArgumentError, 'Unknown subscription' unless @subscriptions.detect subscription
75
- end
96
+ raise ArgumentError, 'Unknown subscription' unless subscribed? subscription
76
97
 
77
98
  @control_connection.execute table: subscription.id, operation: :delete
78
99
 
@@ -105,13 +126,10 @@ module Lightstreamer
105
126
  # Starts the processing thread that reads and processes incoming data from the stream connection.
106
127
  def create_processing_thread
107
128
  @processing_thread = Thread.new do
108
- begin
109
- loop do
110
- process_stream_data @stream_connection.read_line
111
- end
112
- rescue StandardError => error
113
- warn "Lightstreamer: exception in processing thread: #{error}"
114
- exit 1
129
+ loop do
130
+ line = @stream_connection.read_line
131
+
132
+ process_stream_data line unless line.empty?
115
133
  end
116
134
  end
117
135
  end
@@ -137,7 +155,7 @@ module Lightstreamer
137
155
  @stream_connection = nil
138
156
  @control_connection = nil
139
157
 
140
- raise ProtocolError, error_message, error_code
158
+ raise ProtocolError.new(error_message, error_code)
141
159
  end
142
160
  end
143
161
  end
@@ -2,6 +2,9 @@ module Lightstreamer
2
2
  # Manages a long-running Lightstreamer connection that handles incoming streaming data on a separate thread and
3
3
  # makes it available for consumption via the {#read_line} method.
4
4
  class StreamConnection
5
+ # @return [Thread] The thread used to process incoming streaming data.
6
+ attr_reader :thread
7
+
5
8
  # Establishes a new stream connection using the authentication details from the passed session.
6
9
  #
7
10
  # @param [Session] session The session to create a stream connection for.
@@ -13,6 +16,14 @@ module Lightstreamer
13
16
  create_stream_thread
14
17
  end
15
18
 
19
+ # Disconnects this stream connection by shutting down the streaming thread.
20
+ def disconnect
21
+ return unless @thread
22
+
23
+ Thread.kill @thread
24
+ @thread.join
25
+ end
26
+
16
27
  # Reads the next line of streaming data. This method blocks the calling thread until a line of data is available.
17
28
  def read_line
18
29
  @queue.pop
@@ -29,8 +40,6 @@ module Lightstreamer
29
40
  @thread = Thread.new do
30
41
  begin
31
42
  connect_stream_and_queue_data
32
-
33
- warn 'Lightstreamer: stream connection closed'
34
43
  rescue StandardError => error
35
44
  warn "Lightstreamer: exception in stream thread: #{error}"
36
45
  exit 1
@@ -8,10 +8,10 @@ module Lightstreamer
8
8
  # in incoming Lightstreamer data.
9
9
  attr_reader :id
10
10
 
11
- # @return [Array<String>] The names of the items to subscribe to.
11
+ # @return [Array] The names of the items to subscribe to.
12
12
  attr_reader :items
13
13
 
14
- # @return [Array<String>] The names of the fields to subscribe to on the items.
14
+ # @return [Array] The names of the fields to subscribe to on the items.
15
15
  attr_reader :fields
16
16
 
17
17
  # @return [:distinct, :merge] The operation mode of this subscription.
@@ -25,8 +25,8 @@ module Lightstreamer
25
25
  # {Session#subscribe} to activate the subscription on a Lightstreamer session.
26
26
  #
27
27
  # @param [Hash] options The options to create the subscription with.
28
- # @option options [Array<String>] :items The names of the items to subscribe to.
29
- # @option options [Array<String>] :fields The names of the fields to subscribe to on the items.
28
+ # @option options [Array] :items The names of the items to subscribe to.
29
+ # @option options [Array] :fields The names of the fields to subscribe to on the items.
30
30
  # @option options [:distinct, :merge] :mode The operation mode of this subscription.
31
31
  # @option options [String] :adapter The name of the data adapter from the Lightstreamer session's adapter set that
32
32
  # should be used. If `nil` then the default data adapter will be used.
@@ -55,7 +55,7 @@ module Lightstreamer
55
55
  # Clears the current data stored for the specified item. This is important to do when {#mode} is `:distinct` as
56
56
  # otherwise the incoming data will build up indefinitely.
57
57
  #
58
- # @param [String] item_name The name of the item to clear the current data of.
58
+ # @param [String] item_name The name of the item to clear the current data for.
59
59
  def clear_data_for_item(item_name)
60
60
  index = @items.index item_name
61
61
  raise ArgumentError, 'Unrecognized item name' unless index
@@ -74,8 +74,6 @@ module Lightstreamer
74
74
  # @return [Proc] The same `Proc` object that was passed to this method. This can be used to remove this data
75
75
  # callback at a later stage using {#remove_data_callback}.
76
76
  def add_data_callback(&block)
77
- raise ArgumentError, 'Data callbacks must take four arguments' unless block.arity == 4
78
-
79
77
  @data_mutex.synchronize { @data_callbacks << block }
80
78
 
81
79
  block
@@ -111,16 +109,16 @@ module Lightstreamer
111
109
  # @return [Boolean] Whether the passed line of stream data was relevant to this subscription and was successfully
112
110
  # processed by it.
113
111
  def process_stream_data(line)
114
- item_index, values = parse_stream_data line
112
+ item_index, new_values = parse_stream_data line
115
113
  return false unless item_index
116
114
 
117
115
  @data_mutex.synchronize do
118
116
  data = @data[item_index]
119
117
 
120
- data << values if mode == :distinct
121
- data.merge!(values) if mode == :merge
118
+ data << new_values if mode == :distinct
119
+ data.merge!(new_values) if mode == :merge
122
120
 
123
- @data_callbacks.each { |callback| callback.call self, @items[item_index], data, values }
121
+ call_data_callbacks @items[item_index], data, new_values
124
122
  end
125
123
 
126
124
  true
@@ -172,23 +170,35 @@ module Lightstreamer
172
170
  values.each_with_index do |value, index|
173
171
  next if value == ''
174
172
 
175
- # These conversions are specified in the Lightstreamer specification
176
- value = '' if value == '$'
177
- value = nil if value == '#'
178
- value = value[1..-1] if value =~ /^($|#)/
179
-
180
- hash[fields[index]] = convert_utf16_characters value
173
+ hash[fields[index]] = parse_raw_field_value value
181
174
  end
182
175
 
183
176
  hash
184
177
  end
185
178
 
186
- # Non-ASCII characters are transmitted as UTF-16 escape sequences in the form '\uXXXX' or '\uXXXX\uYYYY'. This
187
- # method is respnsible for converting such escape sequences into native Unicode characters.
188
- #
189
- # @todo Implement this method.
190
- def convert_utf16_characters(value)
179
+ # Parses a raw field value according to to the Lightstreamer specification.
180
+ def parse_raw_field_value(value)
181
+ return '' if value == '$'
182
+ return nil if value == '#'
183
+
184
+ value = value[1..-1] if value =~ /^(\$|#)/
185
+
186
+ # TODO: parse any non-ASCII characters which are specified as UTF-16 escape sequences in the form '\uXXXX' or
187
+ # '\uXXXX\uYYYY'. They need to be transformed into native Unicode characters.
188
+
191
189
  value
192
190
  end
191
+
192
+ # Invokes all of this subscription's data callbacks with the specified arguments. Any exceptions that occur in a
193
+ # data callback are reported on `stderr` but are otherwise ignored.
194
+ def call_data_callbacks(item_name, item_data, new_values)
195
+ @data_callbacks.each do |callback|
196
+ begin
197
+ callback.call self, item_name, item_data, new_values
198
+ rescue StandardError => error
199
+ warn "Lightstreamer: exception occurred in a subscription data callback: #{error}"
200
+ end
201
+ end
202
+ end
193
203
  end
194
204
  end
@@ -1,4 +1,4 @@
1
1
  module Lightstreamer
2
2
  # The version of this gem.
3
- VERSION = '0.1'.freeze
3
+ VERSION = '0.2'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightstreamer
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Viney
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-22 00:00:00.000000000 Z
11
+ date: 2016-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -176,8 +176,8 @@ files:
176
176
  - README.md
177
177
  - bin/lightstreamer
178
178
  - lib/lightstreamer.rb
179
+ - lib/lightstreamer/cli/commands/stream_command.rb
179
180
  - lib/lightstreamer/cli/main.rb
180
- - lib/lightstreamer/cli/stream_command.rb
181
181
  - lib/lightstreamer/control_connection.rb
182
182
  - lib/lightstreamer/line_buffer.rb
183
183
  - lib/lightstreamer/protocol_error.rb
@@ -209,5 +209,5 @@ rubyforge_project:
209
209
  rubygems_version: 2.5.1
210
210
  signing_key:
211
211
  specification_version: 4
212
- summary: Ruby client for accessing a Lightstreamer server.
212
+ summary: Library and command-line client for accessing a Lightstreamer server.
213
213
  test_files: []