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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f9aab4c9100b539a30ae428a7bc46603cec793ea
4
- data.tar.gz: 8b46246f0818e75e123c2d15b33be11ed71af2de
3
+ metadata.gz: 4de1419541d11ae6d08c5d34da0cea89a9194a2f
4
+ data.tar.gz: b6b6f5a07058c499bf764ab0367393d8460efc4f
5
5
  SHA512:
6
- metadata.gz: 6c4e04f7146c701250d55acc9520394b4208afc081f65177b2350d136c56ba89d6728c47e0974a779c5cd2c42dde7707103510c0cbbd4139e93c29466524c1b6
7
- data.tar.gz: e03e11fdff88a91ab0d78062914beb59668759d423f839158e4fb20a8dae66351a14cde48133d44944002f00a08d4db456d6318b6edf1b11a6a7c926f2592a62
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
- sleep 1
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
- Hana::Patch.new(message['patch']).apply(self)
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 += 1
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
- message.merge!(patch: JsonDiff.generate(old_object, @shadow))
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
 
@@ -1,3 +1,3 @@
1
1
  module Seapig
2
- VERSION = "0.0.7"
2
+ VERSION = "0.1.0"
3
3
  end
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.7
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-06-13 00:00:00.000000000 Z
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.4.8
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