libowl 1.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 +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
|
+
|