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 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