seapig-client 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|