seapig-client 0.0.7 → 0.1.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.
- checksums.yaml +4 -4
- data/lib/seapig/#client.rb# +251 -0
- data/lib/seapig/client.rb +65 -28
- data/lib/seapig/version.rb +1 -1
- metadata +19 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4de1419541d11ae6d08c5d34da0cea89a9194a2f
|
4
|
+
data.tar.gz: b6b6f5a07058c499bf764ab0367393d8460efc4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d98afaf6d50f95b10b6655cbb6416ad7241ab4ca0112a1d353bd2a29f07b090de5d03cabb7ee4e7d1382e1b3dd41f15d93217c7238de0848ba0144fc7f2df1f8
|
7
|
+
data.tar.gz: a8b2f5160f7a5e1c2d3f160a4c3d0d50bb84159c9d47606bb0d0a8e248ebe1d8fd79e81dfe1846c8813d62186e0dd47e94479c989a1f0a8379cbd1022e2fb05d
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'websocket-eventmachine-client'
|
2
|
+
require 'json'
|
3
|
+
require 'jsondiff'
|
4
|
+
require 'hana'
|
5
|
+
|
6
|
+
class SeapigServer
|
7
|
+
|
8
|
+
attr_reader :socket
|
9
|
+
|
10
|
+
def initialize(uri, options={})
|
11
|
+
@connected = false
|
12
|
+
@uri = uri
|
13
|
+
@options = options
|
14
|
+
@slave_objects = {}
|
15
|
+
@master_objects = {}
|
16
|
+
|
17
|
+
connect
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def connect
|
22
|
+
|
23
|
+
if @socket
|
24
|
+
@socket.onclose {}
|
25
|
+
@socket.close
|
26
|
+
end
|
27
|
+
|
28
|
+
@timeout_timer ||= EM.add_periodic_timer(10) {
|
29
|
+
next if not @socket
|
30
|
+
next if Time.new.to_f - @last_communication_at < 20
|
31
|
+
puts "Seapig ping timeout, reconnecting"
|
32
|
+
connect
|
33
|
+
}
|
34
|
+
|
35
|
+
@connected = false
|
36
|
+
|
37
|
+
@last_communication_at = Time.new.to_f
|
38
|
+
@socket = WebSocket::EventMachine::Client.connect(uri: @uri)
|
39
|
+
|
40
|
+
@socket.onopen {
|
41
|
+
@connected = true
|
42
|
+
@socket.send JSON.dump(action: 'client-options-set', options: @options)
|
43
|
+
@slave_objects.each_pair { |object_id, object|
|
44
|
+
@socket.send JSON.dump(action: 'object-consumer-register', id: object_id, latest_known_version: object.version)
|
45
|
+
}
|
46
|
+
@master_objects.each_pair { |object_id, object|
|
47
|
+
@socket.send JSON.dump(action: 'object-producer-register', pattern: object_id)
|
48
|
+
}
|
49
|
+
@last_communication_at = Time.new.to_f
|
50
|
+
}
|
51
|
+
|
52
|
+
@socket.onmessage { |message|
|
53
|
+
message = JSON.load message
|
54
|
+
#p message['action'], message['id'], message['patch']
|
55
|
+
case message['action']
|
56
|
+
when 'object-update'
|
57
|
+
@slave_objects.values.each { |object|
|
58
|
+
object.patch(message) if object.matches?(message['id'])
|
59
|
+
}
|
60
|
+
when 'object-destroy'
|
61
|
+
@slave_objects.values.each { |object|
|
62
|
+
object.destroy(message) if object.matches?(message['id'])
|
63
|
+
}
|
64
|
+
when 'object-produce'
|
65
|
+
handler = @master_objects.keys.find { |key| key.include?('*') and (message['id'] =~ Regexp.new(Regexp.escape(key).gsub('\*','.*?'))) or (message['id'] == key) }
|
66
|
+
@master_objects[handler].onproduce_proc.call(message['id']) if @master_objects[handler].onproduce_proc
|
67
|
+
@master_objects[handler].upload(0,{},message['id']) if @master_objects[handler]
|
68
|
+
else
|
69
|
+
p :wtf, message
|
70
|
+
end
|
71
|
+
@last_communication_at = Time.new.to_f
|
72
|
+
}
|
73
|
+
|
74
|
+
@socket.onclose { |code, reason|
|
75
|
+
puts 'Seapig connection died unexpectedly (code:'+code.inspect+', reason:'+reason.inspect+'), reconnecting in 1s'
|
76
|
+
EM.add_timer(1) {
|
77
|
+
connect
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
@socket.onerror { |error|
|
82
|
+
puts 'Seapig error: '+error.inspect
|
83
|
+
}
|
84
|
+
|
85
|
+
@socket.onping {
|
86
|
+
@last_communication_at = Time.new.to_f
|
87
|
+
}
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
def disconnect(detach_fd = false)
|
94
|
+
@connected = false
|
95
|
+
if @timeout_timer
|
96
|
+
@timeout_timer.cancel
|
97
|
+
@timeout_timer = nil
|
98
|
+
end
|
99
|
+
if @socket
|
100
|
+
@socket.onclose {}
|
101
|
+
if detach_fd
|
102
|
+
IO.new(@socket.detach).close
|
103
|
+
else
|
104
|
+
@socket.close
|
105
|
+
end
|
106
|
+
@socket = nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def detach_fd
|
112
|
+
disconnect(true)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def slave(object_id)
|
117
|
+
object = if object_id.include?('*') then SeapigWildcardObject.new(self, object_id) else SeapigObject.new(self, object_id) end
|
118
|
+
@socket.send JSON.dump(action: 'object-consumer-register', id: object_id, latest_known_version: object.version) if @connected
|
119
|
+
@slave_objects[object_id] = object
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def master(object_id)
|
124
|
+
object = SeapigObject.new(self, object_id)
|
125
|
+
object.version = Time.new.to_f
|
126
|
+
@socket.send JSON.dump(action: 'object-producer-register', pattern: object_id) if @connected
|
127
|
+
@master_objects[object_id] = object
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
class SeapigObject < Hash
|
133
|
+
|
134
|
+
attr_accessor :version, :object_id, :valid, :onproduce_proc, :stall, :parent, :destroyed
|
135
|
+
|
136
|
+
|
137
|
+
def matches?(id)
|
138
|
+
id =~ Regexp.new(Regexp.escape(@object_id).gsub('\*','.*?'))
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def initialize(server, object_id, parent = nil)
|
143
|
+
@server = server
|
144
|
+
@object_id = object_id
|
145
|
+
@version = 0
|
146
|
+
@onchange_proc = nil
|
147
|
+
@onproduce_proc = nil
|
148
|
+
@valid = false
|
149
|
+
@shadow = JSON.load(JSON.dump(self))
|
150
|
+
@stall = false
|
151
|
+
@parent = parent
|
152
|
+
@destroyed = false
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def onchange(&block)
|
157
|
+
@onchange_proc = block
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def onproduce(&block)
|
162
|
+
@onproduce_proc = block
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def patch(message)
|
167
|
+
if not message['old_version']
|
168
|
+
self.clear
|
169
|
+
elsif message['old_version'] == 0
|
170
|
+
self.clear
|
171
|
+
elsif not @version == message['old_version']
|
172
|
+
p @version, message
|
173
|
+
puts "Seapig lost some updates, this should never happen"
|
174
|
+
exit 2
|
175
|
+
end
|
176
|
+
Hana::Patch.new(message['patch']).apply(self)
|
177
|
+
@version = message['new_version']
|
178
|
+
@valid = true
|
179
|
+
@onchange_proc.call(self) if @onchange_proc
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def set(data, version)
|
184
|
+
if data
|
185
|
+
@stall = false
|
186
|
+
self.clear
|
187
|
+
self.merge!(data)
|
188
|
+
@shadow = sanitized
|
189
|
+
else
|
190
|
+
@stall = true
|
191
|
+
end
|
192
|
+
@version = version
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def changed
|
197
|
+
old_version = @version
|
198
|
+
old_object = @shadow
|
199
|
+
@version += 1
|
200
|
+
@shadow = sanitized
|
201
|
+
upload(old_version, old_object, @object_id)
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
def sanitized
|
206
|
+
JSON.load(JSON.dump(self))
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
def upload(old_version, old_object, object_id)
|
211
|
+
message = {
|
212
|
+
id: object_id,
|
213
|
+
action: 'object-patch',
|
214
|
+
old_version: old_version,
|
215
|
+
new_version: @version,
|
216
|
+
}
|
217
|
+
if old_version == 0 or @stall
|
218
|
+
message.merge!(value: (if @stall then false else @shadow end))
|
219
|
+
else
|
220
|
+
message.merge!(patch: JsonDiff.generate(old_object, @shadow))
|
221
|
+
end
|
222
|
+
@server.socket.send JSON.dump(message)
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
class SeapigWildcardObject < SeapigObject
|
232
|
+
|
233
|
+
|
234
|
+
def patch(message)
|
235
|
+
self[message['id']] ||= SeapigObject.new(@server, message['id'], self)
|
236
|
+
self[message['id']].patch(message)
|
237
|
+
# puts JSON.dump(self)
|
238
|
+
@onchange_proc.call(self[message['id']]) if @onchange_proc
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def destroy(message)
|
243
|
+
if destroyed = self.delete(message['id'])
|
244
|
+
destroyed.destroyed = true
|
245
|
+
@onchange_proc.call(destroyed) if @onchange_proc
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
|
data/lib/seapig/client.rb
CHANGED
@@ -2,18 +2,39 @@ require 'websocket-eventmachine-client'
|
|
2
2
|
require 'json'
|
3
3
|
require 'jsondiff'
|
4
4
|
require 'hana'
|
5
|
+
require 'narray'
|
6
|
+
|
7
|
+
|
8
|
+
module WebSocket
|
9
|
+
module Frame
|
10
|
+
class Data < String
|
11
|
+
def getbytes(start_index, count)
|
12
|
+
data = self[start_index, count]
|
13
|
+
if @masking_key
|
14
|
+
payload_na = NArray.to_na(data,"byte")
|
15
|
+
mask_na = NArray.to_na((@masking_key.pack("C*")*((data.size/4) + 1))[0...data.size],"byte")
|
16
|
+
data = (mask_na ^ payload_na).to_s
|
17
|
+
end
|
18
|
+
data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
5
25
|
|
6
26
|
class SeapigServer
|
7
27
|
|
8
|
-
attr_reader :socket
|
9
|
-
|
28
|
+
attr_reader :socket, :connected
|
29
|
+
|
10
30
|
def initialize(uri, options={})
|
11
31
|
@connected = false
|
12
32
|
@uri = uri
|
13
33
|
@options = options
|
14
34
|
@slave_objects = {}
|
15
35
|
@master_objects = {}
|
16
|
-
|
36
|
+
@notifier_objects = {}
|
37
|
+
|
17
38
|
connect
|
18
39
|
end
|
19
40
|
|
@@ -30,14 +51,15 @@ class SeapigServer
|
|
30
51
|
next if Time.new.to_f - @last_communication_at < 20
|
31
52
|
puts "Seapig ping timeout, reconnecting"
|
32
53
|
connect
|
33
|
-
}
|
34
|
-
|
54
|
+
}
|
55
|
+
|
35
56
|
@connected = false
|
36
57
|
|
37
58
|
@last_communication_at = Time.new.to_f
|
38
59
|
@socket = WebSocket::EventMachine::Client.connect(uri: @uri)
|
39
60
|
|
40
61
|
@socket.onopen {
|
62
|
+
puts 'Connected to seapig server'
|
41
63
|
@connected = true
|
42
64
|
@socket.send JSON.dump(action: 'client-options-set', options: @options)
|
43
65
|
@slave_objects.each_pair { |object_id, object|
|
@@ -48,7 +70,7 @@ class SeapigServer
|
|
48
70
|
}
|
49
71
|
@last_communication_at = Time.new.to_f
|
50
72
|
}
|
51
|
-
|
73
|
+
|
52
74
|
@socket.onmessage { |message|
|
53
75
|
message = JSON.load message
|
54
76
|
#p message['action'], message['id'], message['patch']
|
@@ -70,15 +92,16 @@ class SeapigServer
|
|
70
92
|
end
|
71
93
|
@last_communication_at = Time.new.to_f
|
72
94
|
}
|
73
|
-
|
95
|
+
|
74
96
|
@socket.onclose { |code, reason|
|
75
97
|
puts 'Seapig connection died unexpectedly (code:'+code.inspect+', reason:'+reason.inspect+'), reconnecting in 1s'
|
76
|
-
|
77
|
-
connect
|
98
|
+
EM.add_timer(1) { connect }
|
78
99
|
}
|
79
100
|
|
80
101
|
@socket.onerror { |error|
|
81
102
|
puts 'Seapig error: '+error.inspect
|
103
|
+
@socket.close
|
104
|
+
EM.add_timer(1) { connect }
|
82
105
|
}
|
83
106
|
|
84
107
|
@socket.onping {
|
@@ -86,9 +109,8 @@ class SeapigServer
|
|
86
109
|
}
|
87
110
|
|
88
111
|
end
|
89
|
-
|
90
112
|
|
91
|
-
|
113
|
+
|
92
114
|
def disconnect(detach_fd = false)
|
93
115
|
@connected = false
|
94
116
|
if @timeout_timer
|
@@ -110,8 +132,8 @@ class SeapigServer
|
|
110
132
|
def detach_fd
|
111
133
|
disconnect(true)
|
112
134
|
end
|
113
|
-
|
114
|
-
|
135
|
+
|
136
|
+
|
115
137
|
def slave(object_id)
|
116
138
|
object = if object_id.include?('*') then SeapigWildcardObject.new(self, object_id) else SeapigObject.new(self, object_id) end
|
117
139
|
@socket.send JSON.dump(action: 'object-consumer-register', id: object_id, latest_known_version: object.version) if @connected
|
@@ -121,13 +143,22 @@ class SeapigServer
|
|
121
143
|
|
122
144
|
def master(object_id)
|
123
145
|
object = SeapigObject.new(self, object_id)
|
124
|
-
object.version = Time.new.to_f
|
146
|
+
object.version = (Time.new.to_f*1000000).to_i
|
125
147
|
@socket.send JSON.dump(action: 'object-producer-register', pattern: object_id) if @connected
|
126
148
|
@master_objects[object_id] = object
|
127
149
|
end
|
150
|
+
|
151
|
+
|
152
|
+
def notifier(object_id)
|
153
|
+
object = SeapigObject.new(self, object_id)
|
154
|
+
object.version = 0
|
155
|
+
@notifier_objects[object_id] = object
|
156
|
+
end
|
157
|
+
|
128
158
|
end
|
129
159
|
|
130
160
|
|
161
|
+
|
131
162
|
class SeapigObject < Hash
|
132
163
|
|
133
164
|
attr_accessor :version, :object_id, :valid, :onproduce_proc, :stall, :parent, :destroyed
|
@@ -151,7 +182,7 @@ class SeapigObject < Hash
|
|
151
182
|
@destroyed = false
|
152
183
|
end
|
153
184
|
|
154
|
-
|
185
|
+
|
155
186
|
def onchange(&block)
|
156
187
|
@onchange_proc = block
|
157
188
|
end
|
@@ -161,18 +192,20 @@ class SeapigObject < Hash
|
|
161
192
|
@onproduce_proc = block
|
162
193
|
end
|
163
194
|
|
164
|
-
|
195
|
+
|
165
196
|
def patch(message)
|
166
|
-
if not message['old_version']
|
167
|
-
self.clear
|
168
|
-
elsif message['old_version'] == 0
|
197
|
+
if (not message['old_version']) or (message['old_version'] == 0) or (message['value'])
|
169
198
|
self.clear
|
170
199
|
elsif not @version == message['old_version']
|
171
200
|
p @version, message
|
172
201
|
puts "Seapig lost some updates, this should never happen"
|
173
202
|
exit 2
|
174
|
-
end
|
175
|
-
|
203
|
+
end
|
204
|
+
if message['value']
|
205
|
+
self.merge!(message['value'])
|
206
|
+
else
|
207
|
+
Hana::Patch.new(message['patch']).apply(self)
|
208
|
+
end
|
176
209
|
@version = message['new_version']
|
177
210
|
@valid = true
|
178
211
|
@onchange_proc.call(self) if @onchange_proc
|
@@ -191,13 +224,13 @@ class SeapigObject < Hash
|
|
191
224
|
@version = version
|
192
225
|
end
|
193
226
|
|
194
|
-
|
195
|
-
def changed
|
227
|
+
|
228
|
+
def changed(new_version=nil)
|
196
229
|
old_version = @version
|
197
230
|
old_object = @shadow
|
198
|
-
@version
|
231
|
+
@version = (new_version or (Time.new.to_f*1000000).to_i)
|
199
232
|
@shadow = sanitized
|
200
|
-
upload(old_version, old_object, @object_id)
|
233
|
+
upload(old_version, old_object, @object_id)
|
201
234
|
end
|
202
235
|
|
203
236
|
|
@@ -216,12 +249,16 @@ class SeapigObject < Hash
|
|
216
249
|
if old_version == 0 or @stall
|
217
250
|
message.merge!(value: (if @stall then false else @shadow end))
|
218
251
|
else
|
219
|
-
|
252
|
+
diff = JsonDiff.generate(old_object, @shadow)
|
253
|
+
value = @shadow
|
254
|
+
if JSON.dump(diff.size) < JSON.dump(value.size) #can we afford this?
|
255
|
+
message.merge!(patch: diff)
|
256
|
+
else
|
257
|
+
message.merge!(old_version: 0, value: @shadow)
|
258
|
+
end
|
220
259
|
end
|
221
260
|
@server.socket.send JSON.dump(message)
|
222
261
|
end
|
223
|
-
|
224
|
-
|
225
262
|
|
226
263
|
end
|
227
264
|
|
data/lib/seapig/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seapig-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yunta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: websocket-eventmachine-client
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: narray
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: meh
|
56
70
|
email:
|
57
71
|
- maciej.blomberg@mikoton.com
|
@@ -67,6 +81,7 @@ files:
|
|
67
81
|
- bin/seapig-observer
|
68
82
|
- bin/seapig-worker
|
69
83
|
- lib/seapig-client.rb
|
84
|
+
- lib/seapig/#client.rb#
|
70
85
|
- lib/seapig/client.rb
|
71
86
|
- lib/seapig/version.rb
|
72
87
|
- test/integration/navigation_test.rb
|
@@ -92,11 +107,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
107
|
version: '0'
|
93
108
|
requirements: []
|
94
109
|
rubyforge_project:
|
95
|
-
rubygems_version: 2.
|
110
|
+
rubygems_version: 2.5.1
|
96
111
|
signing_key:
|
97
112
|
specification_version: 4
|
98
113
|
summary: Transient object synchronization lib - client
|
99
114
|
test_files:
|
115
|
+
- test/seapig_test.rb
|
100
116
|
- test/integration/navigation_test.rb
|
101
117
|
- test/test_helper.rb
|
102
|
-
- test/seapig_test.rb
|