libowl 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE +502 -0
- data/README.md +23 -0
- data/lib/libowl/aggregator_rules.rb +24 -0
- data/lib/libowl/buffer_manip.rb +121 -0
- data/lib/libowl/client_world_connection.rb +434 -0
- data/lib/libowl/client_world_model.rb +243 -0
- data/lib/libowl/message_constants.rb +27 -0
- data/lib/libowl/response.rb +72 -0
- data/lib/libowl/sensor_sample.rb +17 -0
- data/lib/libowl/solution_types.rb +49 -0
- data/lib/libowl/solver_aggregator.rb +126 -0
- data/lib/libowl/solver_world_model.rb +220 -0
- data/lib/libowl/step_response.rb +80 -0
- data/lib/libowl/transient_request.rb +14 -0
- data/lib/libowl/wm_data.rb +61 -0
- data/lib/libowl.rb +6 -0
- metadata +61 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
################################################################################
|
2
|
+
#This file contains functions to put data into and retrieve data from byte
|
3
|
+
#arrays when sending messages over a network.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013 Bernhard Firner
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or
|
9
|
+
# modify it under the terms of the GNU General Public License
|
10
|
+
# as published by the Free Software Foundation; either version 2
|
11
|
+
# of the License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
21
|
+
# or visit http://www.gnu.org/licenses/gpl-2.0.html
|
22
|
+
#
|
23
|
+
################################################################################
|
24
|
+
|
25
|
+
#Pack a 64 bit unsigned integer into a buffer
|
26
|
+
def packuint64(val)
|
27
|
+
return [val / 2**32].pack('N') + [val % 2**32].pack('N')
|
28
|
+
end
|
29
|
+
|
30
|
+
#Unpack a uint64_t big-endian integer from the buffer
|
31
|
+
def unpackuint64(buff)
|
32
|
+
high, low = buff.unpack('NN')
|
33
|
+
return high * 2**32 + low
|
34
|
+
end
|
35
|
+
|
36
|
+
#Pack a 128 bit unsigned integer into a buffer
|
37
|
+
def packuint128(val)
|
38
|
+
#TODO FIXME
|
39
|
+
#There is no 128 bit type in ruby so pad with zeros for now
|
40
|
+
return [0].pack('N') + [0].pack('N') + [val / 2**32].pack('N') + [val % 2**32].pack('N')
|
41
|
+
end
|
42
|
+
|
43
|
+
#Unpack a uint128_t big-endian integer from the buffer
|
44
|
+
def unpackuint128(buff)
|
45
|
+
#TODO FIXME
|
46
|
+
#There is no 128 bit type in ruby so pad with zeros for now
|
47
|
+
ignore1, ignore2, high, low = buff.unpack('NNNN')
|
48
|
+
return high * 2**32 + low
|
49
|
+
end
|
50
|
+
|
51
|
+
#Put a string into a buffer as a UTF16 string.
|
52
|
+
def strToUnicode(str)
|
53
|
+
unistr = ""
|
54
|
+
str.each_char { |c|
|
55
|
+
unistr << "\x00#{c}"
|
56
|
+
}
|
57
|
+
return unistr
|
58
|
+
end
|
59
|
+
|
60
|
+
#Put a string into a buffer as a UTF16 string and put the length of the string
|
61
|
+
#(in characters) at the beginning of the buffer as a 4-byte big-endian integer
|
62
|
+
def strToSizedUTF16(str)
|
63
|
+
buff = strToUnicode(str)
|
64
|
+
return "#{[buff.length].pack('N')}#{buff}"
|
65
|
+
end
|
66
|
+
|
67
|
+
#Read a sized UTF16 string (as encoded by the strToSizedUTF16 function) and
|
68
|
+
#return the string.
|
69
|
+
def readUTF16(buff)
|
70
|
+
len = buff.unpack('N')[0] / 2
|
71
|
+
rest = buff[4, buff.length - 1]
|
72
|
+
#puts "len is #{len} and rest is #{rest.length} bytes long"
|
73
|
+
str = ""
|
74
|
+
for i in 1..len do
|
75
|
+
if (rest.length >= 2)
|
76
|
+
#For now act as if the first byte will always be 0
|
77
|
+
c = rest.unpack('UU')[1]
|
78
|
+
rest = rest[2, rest.length - 1]
|
79
|
+
str << c
|
80
|
+
end
|
81
|
+
end
|
82
|
+
return str
|
83
|
+
end
|
84
|
+
|
85
|
+
def readUnsizedUTF16(buff)
|
86
|
+
len = buff.length / 2
|
87
|
+
rest = buff
|
88
|
+
#puts "len is #{len} and rest is #{rest.length} bytes long"
|
89
|
+
str = ""
|
90
|
+
for i in 1..len do
|
91
|
+
if (rest.length >= 2)
|
92
|
+
#For now act as if the first byte will always be 0
|
93
|
+
c = rest.unpack('UU')[1]
|
94
|
+
rest = rest[2, rest.length - 1]
|
95
|
+
str << c
|
96
|
+
end
|
97
|
+
end
|
98
|
+
return str
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
#Take in a buffer with a sized URI in UTF 16 format.
|
103
|
+
#Return the string that was at the beginning of the buffer and
|
104
|
+
#the rest of the buffer after the string
|
105
|
+
def splitURIFromRest(buff)
|
106
|
+
#The first four bytes are for the length of the string
|
107
|
+
strlen = buff.unpack('N')[0]
|
108
|
+
str = buff[0,strlen+4]
|
109
|
+
#Make another container for everything after the string
|
110
|
+
rest = buff[strlen+4,buff.length - 1]
|
111
|
+
if (rest == nil)
|
112
|
+
rest = []
|
113
|
+
end
|
114
|
+
if (strlen != 0)
|
115
|
+
return (readUTF16 str), rest
|
116
|
+
else
|
117
|
+
return '', rest
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
@@ -0,0 +1,434 @@
|
|
1
|
+
################################################################################
|
2
|
+
#This file defines the ClientWorldConnection class, an object that represents
|
3
|
+
#a network connection to an Owl world model.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013 Bernhard Firner
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or
|
9
|
+
# modify it under the terms of the GNU General Public License
|
10
|
+
# as published by the Free Software Foundation; either version 2
|
11
|
+
# of the License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
21
|
+
# or visit http://www.gnu.org/licenses/gpl-2.0.html
|
22
|
+
#
|
23
|
+
################################################################################
|
24
|
+
require 'socket'
|
25
|
+
require 'libowl/message_constants.rb'
|
26
|
+
require 'libowl/wm_data.rb'
|
27
|
+
require 'libowl/buffer_manip.rb'
|
28
|
+
require 'libowl/response.rb'
|
29
|
+
require 'libowl/step_response.rb'
|
30
|
+
|
31
|
+
require 'thread'
|
32
|
+
|
33
|
+
##
|
34
|
+
#A connection between a client and a world model.
|
35
|
+
#This class spawns a thread to handle incoming messages and returns
|
36
|
+
#instances of the Response and StepResponse classes to fulfill client
|
37
|
+
#requests. If a thread cannot be used then an instance of the class
|
38
|
+
#ClientWorldModel should be used instead.
|
39
|
+
class ClientWorldConnection
|
40
|
+
#Indicates if this object is successfully connected to a world model.
|
41
|
+
attr_reader :connected
|
42
|
+
@alias_to_attr_name
|
43
|
+
@alias_to_origin_name
|
44
|
+
#Data for outstanding requests. This is a map of lists with a nil entry
|
45
|
+
#inserted into the list when the request is complete. Other entries
|
46
|
+
#are maps of URIs to their attributes
|
47
|
+
@next_data
|
48
|
+
@request_errors
|
49
|
+
@connected
|
50
|
+
@promise_mutex
|
51
|
+
@cur_key
|
52
|
+
#Remember the order of URI searches. They do not use tickets so we must
|
53
|
+
#manage the order of URI search requests locally
|
54
|
+
@uri_search_keys
|
55
|
+
@single_response
|
56
|
+
|
57
|
+
|
58
|
+
##
|
59
|
+
#Creates a new connection and spawns a thread to call handleMessage
|
60
|
+
#automatically.
|
61
|
+
def initialize(host, port)
|
62
|
+
@promise_mutex = Mutex.new
|
63
|
+
@cur_key = 0
|
64
|
+
@uri_search_keys = []
|
65
|
+
@single_response = {}
|
66
|
+
@connected = false
|
67
|
+
@host = host
|
68
|
+
@port = port
|
69
|
+
@socket = TCPSocket.open(host, port)
|
70
|
+
handshake = ""
|
71
|
+
ver_string = "GRAIL client protocol"
|
72
|
+
#The handshake is the length of the message, the protocol string, and the version (0).
|
73
|
+
handshake << [ver_string.length].pack('N') << ver_string << "\x00\x00"
|
74
|
+
#Send a handshake and then receive one
|
75
|
+
@socket.send(handshake, 0)
|
76
|
+
inshake = @socket.recvfrom(handshake.length)[0]
|
77
|
+
while (inshake.length < handshake.length)
|
78
|
+
#puts "Waiting for #{handshake.length - inshake.length} byte more of handshake."
|
79
|
+
inshake += @socket.recvfrom(handshake.length - inshake.length)[0]
|
80
|
+
end
|
81
|
+
|
82
|
+
@connected = true
|
83
|
+
for i in 1..handshake.length
|
84
|
+
if handshake[i] != inshake[i]
|
85
|
+
puts "Handshake failure!"
|
86
|
+
puts "For byte i we sent #{handshake[i]} but got #{inshake[i]}"
|
87
|
+
@connected = false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@alias_to_attr_name = {}
|
92
|
+
@alias_to_origin_name = {}
|
93
|
+
@next_data = {}
|
94
|
+
@request_errors = {}
|
95
|
+
|
96
|
+
#Start the listening thread
|
97
|
+
@listen_thread = Thread.new do
|
98
|
+
while (@connected)
|
99
|
+
handleMessage()
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
#Close this connection
|
106
|
+
def close()
|
107
|
+
@socket.close()
|
108
|
+
@connected = false
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
#Handle a message of currently unknown type. This is automatically
|
113
|
+
#handled by a thread spawned at object creation.
|
114
|
+
def handleMessage()
|
115
|
+
#puts "Handling message..."
|
116
|
+
#Get the message length as n unsigned integer
|
117
|
+
inlen = (@socket.recvfrom(4)[0]).unpack('N')[0]
|
118
|
+
inbuff = @socket.recvfrom(inlen)[0]
|
119
|
+
#Keep reading until the entire packet is read
|
120
|
+
#TODO This can block forever if a communication error occurs.
|
121
|
+
while (inbuff.length < inlen)
|
122
|
+
inbuff += @socket.recvfrom(inlen-inbuff.length)[0]
|
123
|
+
end
|
124
|
+
#Byte that indicates message type
|
125
|
+
control = inbuff.unpack('C')[0]
|
126
|
+
if control == ATTRIBUTE_ALIAS
|
127
|
+
decodeAttributeAlias(inbuff[1, inbuff.length - 1])
|
128
|
+
elsif control == ORIGIN_ALIAS
|
129
|
+
decodeOriginAlias(inbuff[1, inbuff.length - 1])
|
130
|
+
elsif control == REQUEST_COMPLETE
|
131
|
+
ticket = decodeTicketMessage(inbuff[1, inbuff.length-1])
|
132
|
+
#Mark the corresponding request as complete by appending a nil value
|
133
|
+
@promise_mutex.synchronize do
|
134
|
+
if (@next_data.has_key? ticket)
|
135
|
+
#Need an empty has in case a step response is waiting for a value
|
136
|
+
@next_data[ticket].push(nil)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
elsif control == DATA_RESPONSE
|
140
|
+
data = decodeDataResponse(inbuff[1, inbuff.length - 1])
|
141
|
+
#If the request was cancelled then don't try to push any more data
|
142
|
+
@promise_mutex.synchronize do
|
143
|
+
if (@next_data.has_key? data.ticket)
|
144
|
+
@next_data[data.ticket][-1].store(data.uri, data.attributes)
|
145
|
+
#Add a new entry for the next value
|
146
|
+
if (not @single_response[data.ticket])
|
147
|
+
@next_data[data.ticket].push({})
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
elsif control == URI_RESPONSE
|
152
|
+
uris = decodeURIResponse(inbuff[1, inbuff.length - 1])
|
153
|
+
@promise_mutex.synchronize do
|
154
|
+
uri_ticket = @uri_search_keys.shift
|
155
|
+
puts "Finishing uri response for ticket #{uri_ticket}"
|
156
|
+
#Make world model entries with no attributes for each URI
|
157
|
+
uris.each{|uri| @next_data[uri_ticket][-1].store(uri, [])}
|
158
|
+
#This request is complete now so push a nil value to finish it
|
159
|
+
@next_data[uri_ticket].push(nil)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
#puts "processed message with id #{control}"
|
163
|
+
return control
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
##
|
168
|
+
#See if a request is still being serviced (only for StepResponse - regular
|
169
|
+
#requests can't be cancelled since they only have a single response).
|
170
|
+
def isComplete(key)
|
171
|
+
@promise_mutex.synchronize do
|
172
|
+
if ((not @next_data.has_key?(key)))
|
173
|
+
return true
|
174
|
+
elsif (@next_data[key].empty?)
|
175
|
+
return false
|
176
|
+
else
|
177
|
+
return (nil == @next_data[key][-1])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
#getNext should only be called if hasNext is true, otherwise
|
184
|
+
#the future will be given an exception since there is no data.
|
185
|
+
#The response and stepResponse classes can call this directly
|
186
|
+
#so there usually won't be a need for a developer to call this
|
187
|
+
#function directly.
|
188
|
+
def hasNext(key)
|
189
|
+
@promise_mutex.synchronize do
|
190
|
+
return ((@next_data.has_key? key) and (@next_data[key].length > 1))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
#Get the data from the response object corresponding to this key.
|
196
|
+
#The response and stepResponse classes can call this directly
|
197
|
+
#so there usually won't be a need for a developer to call this
|
198
|
+
#function directly.
|
199
|
+
def getNext(key)
|
200
|
+
if (not hasNext(key))
|
201
|
+
raise "No next value in request"
|
202
|
+
else
|
203
|
+
data = {}
|
204
|
+
@promise_mutex.synchronize do
|
205
|
+
data = @next_data[key].shift
|
206
|
+
end
|
207
|
+
#If there is no more data in this request delete its associatd data
|
208
|
+
if (isComplete(key))
|
209
|
+
@request_errors.delete key
|
210
|
+
@next_data.delete key
|
211
|
+
end
|
212
|
+
return data
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
##
|
217
|
+
#Check if a request has an error.
|
218
|
+
#The response and stepResponse classes can call this directly
|
219
|
+
#so there usually won't be a need for a developer to call this
|
220
|
+
#function directly.
|
221
|
+
def hasError(key)
|
222
|
+
@promise_mutex.synchronize do
|
223
|
+
return (@request_errors.has_key? key)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
#Get error (will return std::exception("No error") is there is none
|
229
|
+
def getError(key)
|
230
|
+
if (not hasError(key))
|
231
|
+
raise "no error but getError called"
|
232
|
+
else
|
233
|
+
@promise_mutex.synchronize do
|
234
|
+
return @request_errors[key]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
#Decode attribute alias message
|
240
|
+
def decodeAttributeAlias(inbuff)
|
241
|
+
num_aliases = inbuff.unpack('N')[0]
|
242
|
+
rest = inbuff[4, inbuff.length - 1]
|
243
|
+
for i in 1..num_aliases do
|
244
|
+
attr_alias = rest.unpack('N')[0]
|
245
|
+
name, rest = splitURIFromRest(rest[4, rest.length - 1])
|
246
|
+
#Assign this name to the given alias
|
247
|
+
@alias_to_attr_name[attr_alias] = name
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
#Decode origin alias message
|
252
|
+
def decodeOriginAlias(inbuff)
|
253
|
+
num_aliases = inbuff.unpack('N')[0]
|
254
|
+
rest = inbuff[4, inbuff.length - 1]
|
255
|
+
for i in 1..num_aliases do
|
256
|
+
origin_alias = rest.unpack('N')[0]
|
257
|
+
name, rest = splitURIFromRest(rest[4, rest.length - 1])
|
258
|
+
#Assign this name to the given alias
|
259
|
+
@alias_to_origin_name[origin_alias] = name
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
#Decode a ticket message or a request complete message.
|
265
|
+
def decodeTicketMessage(inbuff)
|
266
|
+
return inbuff.unpack('N')[0]
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
#Decode a URI response message, returning an array of WMData
|
271
|
+
def decodeURIResponse(inbuff)
|
272
|
+
uris = []
|
273
|
+
if (inbuff != nil)
|
274
|
+
rest = inbuff
|
275
|
+
while (rest.length > 4)
|
276
|
+
name, rest = splitURIFromRest(rest)
|
277
|
+
uris.push(name)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
return uris
|
281
|
+
end
|
282
|
+
|
283
|
+
##
|
284
|
+
#Decode a data response message, returning an array of WMData
|
285
|
+
def decodeDataResponse(inbuff)
|
286
|
+
attributes = []
|
287
|
+
object_uri, rest = splitURIFromRest(inbuff)
|
288
|
+
ticket = rest.unpack('N')[0]
|
289
|
+
total_attributes = rest[4, rest.length - 1].unpack('N')[0]
|
290
|
+
rest = rest[8, rest.length]
|
291
|
+
#puts "Decoding #{total_attributes} attributes"
|
292
|
+
for i in 1..total_attributes do
|
293
|
+
name_alias = rest.unpack('N')[0]
|
294
|
+
creation_date = unpackuint64(rest[4, rest.length - 1])
|
295
|
+
expiration_date = unpackuint64(rest[12, rest.length - 1])
|
296
|
+
origin_alias = rest[20, rest.length - 1].unpack('N')[0]
|
297
|
+
data_len = rest[24, rest.length - 1].unpack('N')[0]
|
298
|
+
data = rest[28, data_len]
|
299
|
+
rest = rest[28+data_len, rest.length - 1]
|
300
|
+
attributes.push(WMAttribute.new(@alias_to_attr_name[name_alias], data, creation_date, expiration_date, @alias_to_origin_name[origin_alias]))
|
301
|
+
end
|
302
|
+
return WMData.new(object_uri, attributes, ticket)
|
303
|
+
end
|
304
|
+
|
305
|
+
##
|
306
|
+
#Issue a snapshot request, returning a Response object for the request.
|
307
|
+
def snapshotRequest(name_pattern, attribute_patterns, start_time = 0, stop_time = 0)
|
308
|
+
#Set up a ticket and mark this request as active by adding it to next_data
|
309
|
+
ticket = 0
|
310
|
+
@promise_mutex.synchronize do
|
311
|
+
ticket = @cur_key
|
312
|
+
@cur_key += 1
|
313
|
+
@single_response.store(ticket, true)
|
314
|
+
@next_data[ticket] = [{}]
|
315
|
+
end
|
316
|
+
buff = [SNAPSHOT_REQUEST].pack('C')
|
317
|
+
|
318
|
+
buff += [ticket].pack('N')
|
319
|
+
buff += strToSizedUTF16(name_pattern)
|
320
|
+
buff += [attribute_patterns.length].pack('N')
|
321
|
+
|
322
|
+
attribute_patterns.each{|pattern|
|
323
|
+
buff += strToSizedUTF16(pattern)
|
324
|
+
}
|
325
|
+
|
326
|
+
buff += packuint64(start_time)
|
327
|
+
buff += packuint64(stop_time)
|
328
|
+
#Send the message with its length prepended to the front
|
329
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
330
|
+
return Response.new(self, ticket)
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
#Issue a range request, returning a Response object for the request.
|
335
|
+
def rangeRequest(name_pattern, attribute_patterns, start_time, stop_time)
|
336
|
+
#Set up a ticket and mark this request as active by adding it to next_data
|
337
|
+
ticket = 0
|
338
|
+
@promise_mutex.synchronize do
|
339
|
+
ticket = @cur_key
|
340
|
+
@cur_key += 1
|
341
|
+
@single_response.store(ticket, false)
|
342
|
+
@next_data[ticket] = [{}]
|
343
|
+
end
|
344
|
+
buff = [RANGE_REQUEST].pack('C')
|
345
|
+
|
346
|
+
buff += [ticket].pack('N')
|
347
|
+
buff += strToSizedUTF16(name_pattern)
|
348
|
+
buff += [attribute_patterns.length].pack('N')
|
349
|
+
|
350
|
+
attribute_patterns.each{|pattern|
|
351
|
+
buff += strToSizedUTF16(pattern)
|
352
|
+
}
|
353
|
+
|
354
|
+
buff += packuint64(start_time)
|
355
|
+
buff += packuint64(stop_time)
|
356
|
+
|
357
|
+
#Send the message with its length prepended to the front
|
358
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
359
|
+
return StepResponse.new(self, ticket)
|
360
|
+
end
|
361
|
+
|
362
|
+
##
|
363
|
+
#Issue a stream request, returning a StepResponse object for the request.
|
364
|
+
def streamRequest(name_pattern, attribute_patterns, update_interval)
|
365
|
+
#Set up a ticket and mark this request as active by adding it to next_data
|
366
|
+
ticket = 0
|
367
|
+
@promise_mutex.synchronize do
|
368
|
+
ticket = @cur_key
|
369
|
+
@cur_key += 1
|
370
|
+
@single_response.store(ticket, false)
|
371
|
+
@next_data[ticket] = [{}]
|
372
|
+
end
|
373
|
+
buff = [STREAM_REQUEST].pack('C')
|
374
|
+
|
375
|
+
buff += [ticket].pack('N')
|
376
|
+
buff += strToSizedUTF16(name_pattern)
|
377
|
+
buff += [attribute_patterns.length].pack('N')
|
378
|
+
|
379
|
+
attribute_patterns.each{|pattern|
|
380
|
+
buff += strToSizedUTF16(pattern)
|
381
|
+
}
|
382
|
+
|
383
|
+
buff += packuint64(0)
|
384
|
+
buff += packuint64(update_interval)
|
385
|
+
|
386
|
+
#Send the message with its length prepended to the front
|
387
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
388
|
+
return StepResponse.new(self, ticket)
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
#Search for any objects in the world model matching the given
|
393
|
+
#POSIX REGEX pattern.
|
394
|
+
def URISearch(name_pattern)
|
395
|
+
#Set up a ticket and mark this request as active by adding it to next_data
|
396
|
+
ticket = 0
|
397
|
+
@promise_mutex.synchronize do
|
398
|
+
ticket = @cur_key
|
399
|
+
@cur_key += 1
|
400
|
+
@single_response.store(ticket, true)
|
401
|
+
@next_data[ticket] = [{}]
|
402
|
+
@uri_search_keys.push(ticket)
|
403
|
+
end
|
404
|
+
buff = [URI_SEARCH].pack('C')
|
405
|
+
buff += strToUnicode(name_pattern)
|
406
|
+
#Send the message with its length prepended to the front
|
407
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
408
|
+
return Response.new(self, ticket)
|
409
|
+
end
|
410
|
+
|
411
|
+
##
|
412
|
+
#Set a preference in the world model for certain origins.
|
413
|
+
def setOriginPreference(origin_weights)
|
414
|
+
buff = [ORIGIN_PREFERENCE].pack('C')
|
415
|
+
#Each origin weight should be a pair of a name and a value
|
416
|
+
origin_weights.each{|ow|
|
417
|
+
#It's okay to pack using N since this operates the same
|
418
|
+
#for signed and unsigned values.
|
419
|
+
buff += strToSizedUTF16(ow[0]) + [ow[1]].pack('N')
|
420
|
+
}
|
421
|
+
#Send the message with its length prepended to the front
|
422
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
423
|
+
end
|
424
|
+
|
425
|
+
##Cancel a request with the given ticket number
|
426
|
+
def cancelRequest(ticket_number)
|
427
|
+
buff = [CANCEL_REQUEST].pack('C')
|
428
|
+
#Now append the ticket number as a 4 byte value
|
429
|
+
buff += [ticket_number].pack('N')
|
430
|
+
#Send the message with its length prepended to the front
|
431
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|