krpc 0.1.1.1 → 0.2.0

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.
@@ -9,4 +9,3 @@ module KRPC
9
9
  end
10
10
 
11
11
  include KRPC::REPLTools
12
-
@@ -1,13 +1,14 @@
1
1
  require 'krpc/gen'
2
2
  require 'krpc/attributes'
3
3
  require 'krpc/doc'
4
+ require 'krpc/streaming'
4
5
 
5
6
  module KRPC
6
7
  module Services
7
8
  class << self
8
9
 
9
10
  # Generate classes and methods for the service - see documentation for Client#generate_services_api!
10
- def create_service(service_msg, client)
11
+ def create_service(service_msg)
11
12
  service_name = service_msg.name
12
13
 
13
14
  # Create service class
@@ -16,12 +17,12 @@ module KRPC
16
17
 
17
18
  # Create service' classes
18
19
  service_msg.classes.map(&:name).each do |sc_name|
19
- client.type_store.as_type("Class(#{service_name}.#{sc_name})")
20
+ TypeStore["Class(#{service_name}.#{sc_name})"]
20
21
  end
21
22
 
22
23
  # Create service' enums
23
24
  service_msg.enumerations.each do |enum|
24
- enum_type = client.type_store.as_type("Enum(#{service_name}.#{enum.name})")
25
+ enum_type = TypeStore["Enum(#{service_name}.#{enum.name})"]
25
26
  enum_type.set_values(enum.values)
26
27
  end
27
28
 
@@ -29,28 +30,28 @@ module KRPC
29
30
  service_msg.procedures.each do |proc|
30
31
  if Attributes.is_a_class_method_or_property_accessor(proc.attributes)
31
32
  class_name = Attributes.get_class_name(proc.attributes)
32
- class_cls = client.type_store.as_type("Class(#{service_name}.#{class_name})").ruby_type
33
+ class_cls = TypeStore["Class(#{service_name}.#{class_name})"].ruby_type
33
34
  method_name = Attributes.get_class_method_or_property_name(proc.attributes)
34
35
  if Attributes.is_a_class_property_accessor(proc.attributes) # service' class property
35
36
  if Attributes.is_a_class_property_getter(proc.attributes)
36
- Gen.add_rpc_method(class_cls, method_name, service_name, proc, client, :prepend_self_to_args)
37
+ Gen.add_rpc_method(class_cls, method_name, service_name, proc, :prepend_self_to_args)
37
38
  else
38
- Gen.add_rpc_method(class_cls, method_name + '=', service_name, proc, client, :prepend_self_to_args)
39
+ Gen.add_rpc_method(class_cls, method_name + '=', service_name, proc, :prepend_self_to_args, :no_stream)
39
40
  end
40
41
  elsif Attributes.is_a_class_method(proc.attributes) # service' class method
41
- Gen.add_rpc_method(class_cls, method_name, service_name, proc, client, :prepend_self_to_args)
42
+ Gen.add_rpc_method(class_cls, method_name, service_name, proc, :prepend_self_to_args)
42
43
  else # service' static class method
43
- Gen.add_rpc_method(class_cls, method_name, service_name, proc, client, :static)
44
+ Gen.add_rpc_method(class_cls, method_name, service_name, proc, :static)
44
45
  end
45
46
  elsif Attributes.is_a_property_accessor(proc.attributes) # service' property
46
47
  property_name = Attributes.get_property_name(proc.attributes)
47
48
  if Attributes.is_a_property_getter(proc.attributes)
48
- Gen.add_rpc_method(service_class, property_name, service_name, proc, client)
49
+ Gen.add_rpc_method(service_class, property_name, service_name, proc)
49
50
  elsif Attributes.is_a_property_setter(proc.attributes)
50
- Gen.add_rpc_method(service_class, property_name + '=', service_name, proc, client)
51
+ Gen.add_rpc_method(service_class, property_name + '=', service_name, proc, :no_stream)
51
52
  end
52
53
  else # plain procedure = method available to service class and its instance
53
- Gen.add_rpc_method(service_class, proc.name, service_name, proc, client, :static)
54
+ Gen.add_rpc_method(service_class, proc.name, service_name, proc, :static)
54
55
  end
55
56
  end
56
57
 
@@ -60,8 +61,8 @@ module KRPC
60
61
  service_classes = mod.constants.map{|c| mod.const_get(c)}.select {|c| c.is_a? Class}
61
62
  service_classes.each(&:add_methods_available_to_class_and_instance)
62
63
 
63
- # Return service class instance
64
- service_class.new(client)
64
+ # Return service class
65
+ service_class
65
66
  end
66
67
 
67
68
  end
@@ -71,6 +72,7 @@ module KRPC
71
72
  class ServiceBase
72
73
  extend Gen::AvailableToClassAndInstanceMethodsHandler
73
74
  include Doc::SuffixMethods
75
+ include Streaming::StreamConstructors
74
76
 
75
77
  attr_reader :client
76
78
 
@@ -83,19 +85,17 @@ module KRPC
83
85
  # Core kRPC service, e.g. for querying for the available services.
84
86
  class KRPC < ServiceBase
85
87
  include Gen::RPCMethodGenerator
86
-
88
+
87
89
  def initialize(client)
88
90
  super(client)
89
91
  unless respond_to? :get_status
90
- include_rpc_method("get_status", "KRPC", "GetStatus", return_type: "KRPC.Status")
91
- include_rpc_method("get_services", "KRPC", "GetServices", return_type: "KRPC.Services")
92
- # TODO: implement me:
93
- # include_rpc_method("add_stream", "KRPC", "AddStream", ...)
94
- # include_rpc_method("remove_stream", "KRPC", "RemoveStream", ...)
92
+ include_rpc_method("get_status", "KRPC", "GetStatus", return_type: "KRPC.Status", xmldoc: "<doc><summary>Gets a status message from the server containing information including the server’s version string and performance statistics.</summary></doc>", options: :no_stream)
93
+ include_rpc_method("get_services", "KRPC", "GetServices", return_type: "KRPC.Services", xmldoc: "<doc><summary>Gets available services and procedures.</summary></doc>", options: :no_stream)
94
+ include_rpc_method("add_stream", "KRPC", "AddStream", params: [PB::Parameter.new(name: "request", type: "KRPC.Request")], return_type: "uint32", xmldoc: "<doc><summary>Add a streaming request. Returns it's identifier.</summary></doc>", options: :no_stream)
95
+ include_rpc_method("remove_stream", "KRPC", "RemoveStream", params: [PB::Parameter.new(name: "id", type: "uint32")], xmldoc: "<doc><summary>Remove a streaming request</summary></doc>", options: :no_stream)
95
96
  end
96
97
  end
97
98
  end
98
99
 
99
100
  end
100
101
  end
101
-
@@ -0,0 +1,176 @@
1
+ require 'thread'
2
+ require 'colorize'
3
+
4
+ module KRPC
5
+ module Streaming
6
+
7
+ class StreamsManager
8
+ attr_reader :client
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ @streams = {}
13
+ @streams_mutex = Mutex.new
14
+ @streaming_thread = Thread.new {}
15
+ end
16
+
17
+ # Send a streaming request, create related Stream object and return it. If identical Stream
18
+ # already exists, doesn't create new Stream and return the existing one.
19
+ def create_stream(request, return_type, method, *args, **kwargs)
20
+ raise RuntimeError("Cannot stream a property setter") if method.name.to_s.end_with? '='
21
+ id = client.krpc.add_stream(request)
22
+ @streams_mutex.synchronize do
23
+ if @streams.include? id
24
+ @streams[id]
25
+ else
26
+ value = method.call(*args, **kwargs)
27
+ @streams[id] = Stream.new(self, id, return_type, value, method, *args, **kwargs)
28
+ end
29
+ end
30
+ end
31
+
32
+ # Remove a streaming request and deactivate the Stream object. Returns `true` if
33
+ # streaming request is removed or `false` if passed Stream object is already inactive.
34
+ def remove_stream(stream)
35
+ return false unless stream.active?
36
+ @streams_mutex.synchronize do
37
+ return false unless @streams.include? stream.id
38
+ client.krpc.remove_stream stream.id
39
+ @streams.delete stream.id
40
+ end
41
+ stream.value = RuntimeError.new("Stream has been removed")
42
+ stream.mark_as_inactive
43
+ true
44
+ end
45
+
46
+ # Remove all streams created by this streams manager.
47
+ def remove_all_streams
48
+ @streams.each {|_,stream| remove_stream(stream)}
49
+ end
50
+
51
+ # Start streaming thread. It receives stream data, and updates Stream object's `value` attribute.
52
+ def start_streaming_thread
53
+ stop_streaming_thread
54
+ @streaming_thread = Thread.new do
55
+ connection = client.stream_connection
56
+ stream_message_type = TypeStore["KRPC.StreamMessage"]
57
+ loop do
58
+ size = connection.recv_varint
59
+ data = connection.recv(size)
60
+ stream_msg = Decoder.decode(data, stream_message_type, client)
61
+ @streams_mutex.synchronize do
62
+ stream_msg.responses.each do |stream_resp|
63
+ next unless @streams.include? stream_resp.id
64
+ stream = @streams[stream_resp.id]
65
+ if stream_resp.response.has_field?("error")
66
+ stream.value = RPCError.new(stream_resp.response.error)
67
+ else
68
+ stream.value = Decoder.decode(stream_resp.response.return_value, stream.return_type, client)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ # Stop streaming thread.
77
+ def stop_streaming_thread
78
+ @streaming_thread.terminate
79
+ end
80
+ end
81
+
82
+ class Stream
83
+ attr_reader :id, :method, :args, :kwargs, :return_type, :manager
84
+ attr_writer :value
85
+
86
+ def initialize(manager, id, return_type, value, method, *args, **kwargs)
87
+ @manager = manager
88
+ @id = id
89
+ @return_type, @value = return_type, value
90
+ @method, @args, @kwargs = method, args, kwargs
91
+ @active = true
92
+ end
93
+
94
+ # Get the current stream value. Has alias method `value`.
95
+ def get
96
+ raise @value if @value.is_a?(Exception)
97
+ @value
98
+ end
99
+ alias_method :value, :get
100
+
101
+ # Remove stream. Has alias method `close`.
102
+ def remove
103
+ manager.remove_stream self
104
+ end
105
+ alias_method :close, :remove
106
+
107
+ # Check if stream is active (i.e. not removed).
108
+ def active?; @active end
109
+
110
+ # Mark stream as inactive.
111
+ # WARNING: This method does not remove the stream. To remove the stream call Stream#remove instead.
112
+ def mark_as_inactive; @active = false end
113
+
114
+ def to_s
115
+ inspect.gsub(/\n|\t/," ").squeeze(" ").uncolorize
116
+ end
117
+
118
+ def inspect
119
+ def coderay(x)
120
+ require 'coderay'
121
+ if x.is_a?(Array) then "[" + x.map{|e| e.is_a?(Gen::ClassBase) ? e.inspect : coderay(e.inspect)}.join(", ") + "]"
122
+ elsif x.is_a?(Hash) then "{" + x.map{|k,v| coderay(k.inspect) + "=>" + (v.is_a?(Gen::ClassBase) ? v.inspect : coderay(v.inspect))}.join(", ") + "}"
123
+ else CodeRay.scan(x, :ruby).term end
124
+ rescue Exception
125
+ x.inspect
126
+ end
127
+ "#<#{self.class}".green +
128
+ " @id" + "=".green + id.to_s.bold.blue +
129
+ " @active" + "=".green + @active.to_s.bold.light_cyan +
130
+ "\n\t@method" + "=".green + method.inspect.green +
131
+ (args.empty? ? "" : "\n\t@args" + "=".green + coderay(args)) +
132
+ (kwargs.empty? ? "" : "\n\t@kwargs" + "=".green + coderay(kwargs)) +
133
+ "\n\treturn_ruby_type" + "=".green + coderay(return_type.ruby_type) +
134
+ ">".green
135
+ end
136
+ end
137
+
138
+ module StreamConstructors
139
+ STREAM_METHOD_SUFFIX = "_stream"
140
+ STREAM_METHOD_REGEX = /^(.+)(?:#{STREAM_METHOD_SUFFIX})$/
141
+
142
+ module ClassMethods
143
+ def stream_constructors
144
+ @stream_constructors ||= {}
145
+ end
146
+ end
147
+
148
+ def self.included(base)
149
+ base.extend ClassMethods
150
+ base.extend self
151
+ end
152
+
153
+ def method_missing(method, *args, **kwargs, &block)
154
+ if STREAM_METHOD_REGEX =~ method.to_s
155
+ if respond_to? $1.to_sym
156
+ ctors = self.is_a?(Module) ? stream_constructors : self.class.stream_constructors
157
+ return ctors[$1].call(self, *args, **kwargs) if ctors.include? $1
158
+ end
159
+ end
160
+ super
161
+ end
162
+
163
+ def respond_to_missing?(method, *)
164
+ if STREAM_METHOD_REGEX =~ method.to_s
165
+ if respond_to? $1.to_sym
166
+ ctors = self.is_a?(Module) ? stream_constructors : self.class.stream_constructors
167
+ return true if ctors.include? $1
168
+ end
169
+ end
170
+ super
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+ end
@@ -21,93 +21,91 @@ module KRPC
21
21
  "bytes" => String
22
22
  }
23
23
  PROTOBUF_TO_MESSAGE_TYPE = ProtobufUtils.create_PB_to_PB_message_class_hash("KRPC")
24
-
25
- class TypeStore
26
24
 
27
- def initialize
28
- @cache = {}
29
- end
25
+ class TypeStore
26
+ @cache = {}
27
+ class << self
30
28
 
31
- def as_type(type_string)
32
- return @cache[type_string] if @cache.include? type_string
29
+ def [](type_string)
30
+ return @cache[type_string] if @cache.include? type_string
31
+
32
+ type =
33
+ if PROTOBUF_VALUE_TYPES.include? type_string then ValueType.new(type_string)
34
+ elsif type_string.start_with? "Class(" || type_string == "Class" then ClassType.new(type_string)
35
+ elsif type_string.start_with? "Enum(" || type_string == "Enum" then EnumType.new(type_string)
36
+ elsif type_string.start_with? "List(" || type_string == "List" then ListType.new(type_string)
37
+ elsif type_string.start_with? "Dictionary(" || type_string == "Dictionary" then DictionaryType.new(type_string)
38
+ elsif type_string.start_with? "Set(" || type_string == "Set" then SetType.new(type_string)
39
+ elsif type_string.start_with? "Tuple(" || type_string == "Tuple" then TupleType.new(type_string)
40
+ else # A message type (eg. type_string = "KRPC.List" or "KRPC.Services")
41
+ raise(ValueError, "\"#{type_string}\" is not a valid type string") unless /^[A-Za-z0-9_\.]+$/ =~ type_string
42
+ if PROTOBUF_TO_MESSAGE_TYPE.has_key? type_string
43
+ MessageType.new(type_string)
44
+ else
45
+ raise(ValueError, "\"#{type_string}\" is not a valid type string")
46
+ end
47
+ end
48
+
49
+ @cache[type_string] = type
50
+ type
51
+ end
33
52
 
34
- type =
35
- if PROTOBUF_VALUE_TYPES.include? type_string then ValueType.new(type_string)
36
- elsif type_string.start_with? "Class(" || type_string == "Class" then ClassType.new(type_string)
37
- elsif type_string.start_with? "Enum(" || type_string == "Enum" then EnumType.new(type_string)
38
- elsif type_string.start_with? "List(" || type_string == "List" then ListType.new(type_string, self)
39
- elsif type_string.start_with? "Dictionary(" || type_string == "Dictionary" then DictionaryType.new(type_string, self)
40
- elsif type_string.start_with? "Set(" || type_string == "Set" then SetType.new(type_string, self)
41
- elsif type_string.start_with? "Tuple(" || type_string == "Tuple" then TupleType.new(type_string, self)
42
- else # A message type (eg. type_string = "KRPC.List" or "KRPC.Services")
43
- raise(ValueError, "\"#{type_string}\" is not a valid type string") unless /^[A-Za-z0-9_\.]+$/ =~ type_string
44
- if PROTOBUF_TO_MESSAGE_TYPE.has_key? type_string
45
- MessageType.new(type_string)
46
- else
47
- raise(ValueError, "\"#{type_string}\" is not a valid type string")
53
+ def get_parameter_type(pos, type, attrs)
54
+ type_attrs = Attributes.get_parameter_type_attrs(pos, attrs)
55
+ type_attrs.each do |ta|
56
+ begin
57
+ return self[ta]
58
+ rescue ValueError
48
59
  end
49
60
  end
61
+ self[type]
62
+ end
50
63
 
51
- @cache[type_string] = type
52
- type
53
- end
54
-
55
- def get_parameter_type(pos, type, attrs)
56
- type_attrs = Attributes.get_parameter_type_attrs(pos, attrs)
57
- type_attrs.each do |ta|
58
- begin
59
- return as_type(ta)
60
- rescue ValueError
64
+ def get_return_type(type, attrs)
65
+ type_attrs = Attributes.get_return_type_attrs(attrs)
66
+ type_attrs.each do |ta|
67
+ begin
68
+ return self[ta]
69
+ rescue ValueError
70
+ end
61
71
  end
72
+ self[type]
62
73
  end
63
- as_type(type)
64
- end
65
-
66
- def get_return_type(type, attrs)
67
- type_attrs = Attributes.get_return_type_attrs(attrs)
68
- type_attrs.each do |ta|
74
+
75
+ def coerce_to(value, type)
76
+ return value if type.is_a?(EnumType) && value.class == Symbol # Enum handling
77
+ return value if value.is_a?(type.ruby_type)
78
+ # A NilClass can be coerced to a ClassType
79
+ return nil if type.is_a?(ClassType) && value.nil?
80
+ # Handle service' class instance
81
+ if type.is_a?(ClassType) && value.is_a?(Gen::ClassBase) &&
82
+ type.ruby_type == value.class
83
+ return value
84
+ end
85
+ # -- Collection types --
69
86
  begin
70
- return as_type(ta)
87
+ # coerce "list" to array
88
+ if type.is_a?(ListType) && value.respond_to?(:map) && value.respond_to?(:to_a)
89
+ return type.ruby_type.new(value.map{|x| coerce_to(x, type.value_type) }.to_a)
90
+ end
91
+ # coerce "tuple" to array + check elements count
92
+ if type.is_a?(TupleType) && value.respond_to?(:map) && value.respond_to?(:to_a) && value.respond_to?(:size)
93
+ raise ValueError if value.size != type.value_types.size
94
+ return type.ruby_type.new(value.map.with_index{|x,i| coerce_to(x, type.value_types[i]) }.to_a)
95
+ end
71
96
  rescue ValueError
97
+ raise(ValueError, "Failed to coerce value #{value.to_s} of type #{value.class} to type #{type}")
72
98
  end
73
- end
74
- as_type(type)
75
- end
76
-
77
- def coerce_to(value, type)
78
- return value if type.is_a?(EnumType) && value.class == Symbol # Enum handling
79
- return value if value.is_a?(type.ruby_type)
80
- # A NilClass can be coerced to a ClassType
81
- return nil if type.is_a?(ClassType) && value == nil
82
- # Coerce identical class types from different client connections
83
- if type.is_a?(ClassType) && value.is_a?(Gen::ClassBase) &&
84
- type.ruby_type.service_name == value.class.service_name &&
85
- type.ruby_type.name == value.class.name
86
- return type.ruby_type.new(value.remote_oid)
87
- end
88
- # -- Collection types --
89
- begin
90
- # coerce "list" to array
91
- if type.is_a?(ListType) && value.respond_to?(:map) && value.respond_to?(:to_a)
92
- return type.ruby_type.new(value.map{|x| coerce_to(x, type.value_type) }.to_a)
93
- end
94
- # coerce "tuple" to array + check elements count
95
- if type.is_a?(TupleType) && value.respond_to?(:map) && value.respond_to?(:to_a) && value.respond_to?(:size)
96
- raise ValueError if value.size != type.value_types.size
97
- return type.ruby_type.new(value.map.with_index{|x,i| coerce_to(x, type.value_types[i]) }.to_a)
99
+ # Numeric types
100
+ if type.ruby_type == Float && value.respond_to?(:to_f)
101
+ return value.to_f
102
+ elsif type.ruby_type == Integer && value.respond_to?(:to_i)
103
+ return value.to_i
98
104
  end
99
- rescue ValueError
100
105
  raise(ValueError, "Failed to coerce value #{value.to_s} of type #{value.class} to type #{type}")
101
106
  end
102
- # Numeric types
103
- if type.ruby_type == Float && value.respond_to?(:to_f)
104
- return value.to_f
105
- elsif type.ruby_type == Integer && value.respond_to?(:to_i)
106
- return value.to_i
107
- end
108
- raise(ValueError, "Failed to coerce value #{value.to_s} of type #{value.class} to type #{type}")
109
- end
110
107
 
108
+ end
111
109
  end
112
110
 
113
111
 
@@ -121,7 +119,7 @@ module KRPC
121
119
  protected
122
120
 
123
121
  def parse_type_string(type)
124
- raise ValueError.new if type == nil
122
+ raise ValueError.new if type.nil?
125
123
  result = ""
126
124
  level = 0
127
125
  type.each_char do |x|
@@ -181,25 +179,25 @@ module KRPC
181
179
 
182
180
  class ListType < TypeBase
183
181
  attr_reader :value_type
184
- def initialize(type_string, type_store)
182
+ def initialize(type_string)
185
183
  m = /^List\((.+)\)$/.match type_string
186
184
  raise(ValueError, "\"#{type_string}\" is not a valid type string for a list type") unless m
187
- @value_type = type_store.as_type(m[1])
185
+ @value_type = TypeStore[m[1]]
188
186
  super(type_string, Array)
189
187
  end
190
188
  end
191
189
 
192
190
  class DictionaryType < TypeBase
193
191
  attr_reader :key_type, :value_type
194
- def initialize(type_string, type_store)
192
+ def initialize(type_string)
195
193
  m = /^Dictionary\((.+)\)$/.match type_string
196
194
  raise(ValueError, "\"#{type_string}\" is not a valid type string for a dictionary type") unless m
197
195
 
198
196
  key_string, type = parse_type_string(m[1])
199
197
  value_string, type = parse_type_string(type)
200
- raise(ValueError, "\"#{type_string}\" is not a valid type string for a dictionary type") if type != nil
201
- @key_type = type_store.as_type(key_string)
202
- @value_type = type_store.as_type(value_string)
198
+ raise(ValueError, "\"#{type_string}\" is not a valid type string for a dictionary type") unless type.nil?
199
+ @key_type = TypeStore[key_string]
200
+ @value_type = TypeStore[value_string]
203
201
 
204
202
  super(type_string, Hash)
205
203
  end
@@ -207,25 +205,25 @@ module KRPC
207
205
 
208
206
  class SetType < TypeBase
209
207
  attr_reader :value_type
210
- def initialize(type_string, type_store)
208
+ def initialize(type_string)
211
209
  m = /^Set\((.+)\)$/.match type_string
212
210
  raise(ValueError, "\"#{type_string}\" is not a valid type string for a set type") unless m
213
- @value_type = type_store.as_type(m[1])
211
+ @value_type = TypeStore[m[1]]
214
212
  super(type_string, Set)
215
213
  end
216
214
  end
217
215
 
218
216
  class TupleType < TypeBase
219
217
  attr_reader :value_types
220
- def initialize(type_string, type_store)
218
+ def initialize(type_string)
221
219
  m = /^Tuple\((.+)\)$/.match type_string
222
220
  raise(ValueError, "\"#{type_string}\" is not a valid type string for a tuple type") unless m
223
221
 
224
222
  @value_types = []
225
223
  type = m[1]
226
- while type != nil
224
+ until type.nil?
227
225
  value_type, type = parse_type_string(type)
228
- @value_types << type_store.as_type(value_type)
226
+ @value_types << TypeStore[value_type]
229
227
  end
230
228
 
231
229
  super(type_string, Array)
@@ -233,5 +231,6 @@ module KRPC
233
231
  end
234
232
 
235
233
  end
234
+
235
+ TypeStore = Types::TypeStore
236
236
  end
237
-