krpc 0.1.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-