seapig-router 0.2.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +2 -0
- data/app/assets/javascripts/seapig/seapig-router.js.coffee +334 -0
- data/app/models/seapig_router_session.rb +5 -0
- data/app/models/seapig_router_session_state.rb +5 -0
- data/bin/seapig-router-session-manager +55 -0
- data/db/migrate/20151221110834_create_seapig_router_sessions.rb +9 -0
- data/db/migrate/20151221111628_create_seapig_router_session_states.rb +13 -0
- data/db/migrate/20161231183822_add_token_to_seapig_sessions.rb +7 -0
- data/lib/seapig-router.rb +6 -0
- data/lib/seapig-router/engine.rb +6 -0
- data/lib/seapig-router/version.rb +3 -0
- data/lib/seapigs/seapig_router_saved_session.rb +20 -0
- data/lib/seapigs/seapig_router_session_state.rb +19 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3768c130c423cc69ca19d096eeda6c82b8b5358f
|
4
|
+
data.tar.gz: 77e72a3c2eea960238e86e1e213e96975796a2c8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c931ea140f573f728241091a7cac85e619a0472629a4d982697bb423d128c2df507287fa79b7e5e095024d79d059e432e185e61fd1e4df905b9b2fa6abdd4ebf
|
7
|
+
data.tar.gz: 742b59786fa95ca5a675883d97f9fa76857858553221e908a21e521a38b6d446358fe4d9c267745c83a2e4e5e4d397508aedd7be94c5bd75fc32808f6f11b95e
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015-2017 yunta
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
class @SeapigRouter
|
2
|
+
|
3
|
+
|
4
|
+
constructor: (seapig_client, options={})->
|
5
|
+
@seapig_client = seapig_client
|
6
|
+
@session_id = undefined
|
7
|
+
@mountpoint = (options.mountpoint or "/")
|
8
|
+
@default_state = (options.default or {})
|
9
|
+
@debug = options.debug
|
10
|
+
@expose = (options.expose or [])
|
11
|
+
@cast = (options.cast or (state)-> state)
|
12
|
+
@onsessionopen = options.onsessionopen
|
13
|
+
|
14
|
+
@token = (String.fromCharCode(48 + code + (if code > 9 then 7 else 0) + (if code > 35 then 6 else 0)) for code in (Math.floor(Math.random()*62) for i in [0..11])).join("")
|
15
|
+
console.log('ROUTER: Generated token: ', @token) if @debug
|
16
|
+
|
17
|
+
@state = undefined
|
18
|
+
@state_id = 1
|
19
|
+
@state_raw = { session_id: undefined, state_id: 0, state_parent: undefined, state_committed: true }
|
20
|
+
@state_valid = false
|
21
|
+
|
22
|
+
commit_scheduled_at = null
|
23
|
+
commit_timer = null
|
24
|
+
remote_state = null
|
25
|
+
|
26
|
+
priv = {}
|
27
|
+
@private = priv if options.expose_privates
|
28
|
+
|
29
|
+
session_data = @seapig_client.master('SeapigRouter::Session::'+@token+'::Data', object: { token: @token, session: @session_id, states: {}})
|
30
|
+
session_data.bump()
|
31
|
+
|
32
|
+
session_data_saved = @seapig_client.slave('SeapigRouter::Session::'+@token+'::Saved')
|
33
|
+
session_data_saved.onchange =>
|
34
|
+
return if not session_data_saved.valid
|
35
|
+
for state_id in _.keys(session_data.object.states)
|
36
|
+
delete session_data.object.states[state_id] if parseInt(state_id) < session_data_saved.object.max_state_id
|
37
|
+
if not @session_id?
|
38
|
+
@session_id = session_data_saved.object.session_id
|
39
|
+
@state.session_id = @state_raw.session_id = @session_id if @state? and not @state_raw.session_id?
|
40
|
+
console.log('ROUTER: Session opened', @session_id) if @debug
|
41
|
+
@onsessionopen(@session_id) if @onsessionopen?
|
42
|
+
console.log('ROUTER: Session saved up till:', session_data_saved.object.max_state_id) if @debug
|
43
|
+
location_update(false) if @state_valid
|
44
|
+
|
45
|
+
|
46
|
+
document.onclick = (event) =>
|
47
|
+
href = (event.target.getAttribute("href") or "")
|
48
|
+
console.log('ROUTER: A-element clicked, changing location to:', href) if @debug
|
49
|
+
return true if not (href[0] == '?')
|
50
|
+
@navigate(href)
|
51
|
+
false
|
52
|
+
|
53
|
+
|
54
|
+
window.onpopstate = (event) =>
|
55
|
+
@state_raw = JSON.parse(JSON.stringify(event.state))
|
56
|
+
@state_raw.session_id = @session_id if not @state_raw.session_id?
|
57
|
+
@state = @cast(JSON.parse(JSON.stringify(@state_raw)))
|
58
|
+
console.log('ROUTER: History navigation triggered. Going to:', event.state) if @debug
|
59
|
+
location_update(false)
|
60
|
+
@onchange(@state_raw, previous_state) if @onchange?
|
61
|
+
|
62
|
+
|
63
|
+
state_permanent = (state)=>
|
64
|
+
_.omit(state, (value, key)-> key.indexOf("_") == 0 or key == "session_id" or key == "state_id" or key == "state_parent" or key == "state_committed")
|
65
|
+
|
66
|
+
|
67
|
+
state_diff_generate = priv.state_diff_generate = (state1, state2)=>
|
68
|
+
|
69
|
+
element_diff = (diff, address, object1, object2)->
|
70
|
+
same_type = ((typeof object1 == typeof object2) and (Array.isArray(object1) == Array.isArray(object2)))
|
71
|
+
if (not object2?) or (object1? and not same_type)
|
72
|
+
diff.push ['-'+address, '-']
|
73
|
+
object1 = undefined
|
74
|
+
if Array.isArray(object2)
|
75
|
+
array_diff(diff, address+"~", object1 or [], object2)
|
76
|
+
else if typeof object2 == 'object'
|
77
|
+
object_diff(diff, address+".", object1 or {}, object2)
|
78
|
+
else if object2?
|
79
|
+
diff.push [address, object2] if object1 != object2
|
80
|
+
|
81
|
+
object_diff = (diff, address, object1, object2)->
|
82
|
+
for key in _.uniq(_.union(_.keys(object1), _.keys(object2)))
|
83
|
+
element_diff(diff, address+key, object1[key], object2[key])
|
84
|
+
diff
|
85
|
+
|
86
|
+
array_diff = (diff, address, array1, array2)->
|
87
|
+
j = 0
|
88
|
+
for element1, i in array1
|
89
|
+
if _.isEqual(element1, array2[j])
|
90
|
+
j++
|
91
|
+
else
|
92
|
+
k = j
|
93
|
+
k++ while (not _.isEqual(element1,array2[k])) and (k < array2.length)
|
94
|
+
if k == array2.length
|
95
|
+
if typeof element1 == 'object'
|
96
|
+
diff.push ["-"+address+j+"~", "-"]
|
97
|
+
else
|
98
|
+
diff.push ["-"+address+"~", element1]
|
99
|
+
else
|
100
|
+
while j < k
|
101
|
+
element_diff(diff, address+j+"~", undefined, array2[j++])
|
102
|
+
j++
|
103
|
+
|
104
|
+
while j < array2.length
|
105
|
+
element_diff(diff, address+"~", undefined, array2[j++])
|
106
|
+
|
107
|
+
object_diff([], "", state1, state2)
|
108
|
+
|
109
|
+
|
110
|
+
state_diff_apply = priv.state_diff_apply = (state, diff)=>
|
111
|
+
for entry in diff
|
112
|
+
address = entry[0]
|
113
|
+
value = entry[1]
|
114
|
+
add = (address[0] != '-')
|
115
|
+
address = address[1..-1] if address[0] == '-'
|
116
|
+
obj = state
|
117
|
+
spl = address.split('.')
|
118
|
+
for subobj,i in spl
|
119
|
+
if i < (spl.length-1)
|
120
|
+
if subobj[subobj.length-1] == '~'
|
121
|
+
if subobj.split("~")[1].length > 0
|
122
|
+
obj[parseInt(subobj.split("~")[1])] = {} if not obj[parseInt(subobj.split("~")[1])]
|
123
|
+
obj = obj[parseInt(subobj.split("~")[1])]
|
124
|
+
else
|
125
|
+
obj[subobj.split("~")[0]] = [new_obj = {}]
|
126
|
+
obj = new_obj
|
127
|
+
else
|
128
|
+
obj[subobj] = {} if not obj[subobj]?
|
129
|
+
obj = obj[subobj]
|
130
|
+
address = spl[spl.length-1]
|
131
|
+
hash = (address[address.length-1] != '~')
|
132
|
+
index = undefined
|
133
|
+
index = parseInt(address.split('~')[1]) if (not hash) and address.split("~")[1].length > 0
|
134
|
+
address = address.split("~")[0]
|
135
|
+
if add
|
136
|
+
if hash
|
137
|
+
obj[address] = value
|
138
|
+
else
|
139
|
+
if index?
|
140
|
+
(obj[address] ||= []).splice(index,0,value)
|
141
|
+
else
|
142
|
+
(obj[address] ||= []).push(value)
|
143
|
+
else
|
144
|
+
if hash
|
145
|
+
delete obj[address]
|
146
|
+
else
|
147
|
+
if index?
|
148
|
+
obj[address].splice(index,1)
|
149
|
+
else
|
150
|
+
obj[address].splice(_.indexOf(obj[address], value),1)
|
151
|
+
state
|
152
|
+
|
153
|
+
|
154
|
+
url_to_state_description = (pathname, search)=>
|
155
|
+
|
156
|
+
# URL FORMAT:
|
157
|
+
# /VERSION/SESSION_ID/STATE_ID/[EXPOSED_DIFF/][-/BUFFER_DIFF][?CHANGE_DIFF]
|
158
|
+
# VERSION - format code
|
159
|
+
# SESSION_ID
|
160
|
+
# STATE_ID - id of latest state saved on server
|
161
|
+
# EXPOSED DIFF - "pretty" part of the url, exposing selected state components for end user manipulation.
|
162
|
+
# BUFFER_DIFF - temporary section, holding the difference between STATE_ID state and current state. vanishes after current state gets saved on server.
|
163
|
+
# CHANGE_DIFF - temporary section, holding state change intended by <A> link (e.g. href="?view=users&user=10"). vanishes immediately and gets transeferred to BUFFER_DIFF.
|
164
|
+
|
165
|
+
state_description = { session_id: null, state_id: null, buffer: [], exposed: [], change: [] }
|
166
|
+
|
167
|
+
spl = pathname.split(@mountpoint)
|
168
|
+
spl.shift()
|
169
|
+
spl = (decodeURIComponent(part) for part in spl.join(@mountpoint).split('/'))
|
170
|
+
|
171
|
+
version = spl.shift()
|
172
|
+
if version == 'a'
|
173
|
+
state_description.session_id = spl.shift()
|
174
|
+
state_description.session_id = undefined if state_description.session_id == '_'
|
175
|
+
state_description.state_id = spl.shift()
|
176
|
+
|
177
|
+
if state_description.state_id?
|
178
|
+
while spl.length > 0
|
179
|
+
key = spl.shift()
|
180
|
+
break if key == '-'
|
181
|
+
component = _.find @expose, (component)-> component[1]
|
182
|
+
next if not component
|
183
|
+
state_description.exposed.push([component[0],spl.shift()])
|
184
|
+
|
185
|
+
while spl.length > 0
|
186
|
+
state_description.buffer.push([spl.shift(),spl.shift()])
|
187
|
+
else
|
188
|
+
state_description.session_id = @session_id
|
189
|
+
state_description.state_id = 0
|
190
|
+
state_description.buffer = state_diff_generate(state_permanent(@state_raw), state_permanent(@default_state))
|
191
|
+
|
192
|
+
if search.length > 1
|
193
|
+
for pair in search.split('?')[1].split('&')
|
194
|
+
decoded_pair = (decodeURIComponent(part) for part in pair.split('=',2))
|
195
|
+
state_description.change.push(decoded_pair)
|
196
|
+
|
197
|
+
console.log('ROUTER: Parsed location', state_description) if @debug
|
198
|
+
state_description
|
199
|
+
|
200
|
+
|
201
|
+
state_description_to_url = (state_description)=>
|
202
|
+
console.log('ROUTER: Calculating url for state description:', state_description) if @debug
|
203
|
+
url = @mountpoint+'a/'+(state_description.session_id or '_')+'/'+state_description.state_id
|
204
|
+
url += "/"+(encodeURIComponent(component) for component in _.flatten(state_description.exposed)).join("/") if state_description.exposed.length > 0
|
205
|
+
url += "/-/"+(encodeURIComponent(component) for component in _.flatten(state_description.buffer)).join("/") if state_description.buffer.length > 0
|
206
|
+
console.log('ROUTER: Calculated url:', url) if @debug
|
207
|
+
url
|
208
|
+
|
209
|
+
|
210
|
+
state_set_from_state_description = (state_description, defer, replace)=>
|
211
|
+
|
212
|
+
state_commit = (replace) =>
|
213
|
+
console.log("ROUTER: Committing state:",@state_raw) if @debug
|
214
|
+
@state_raw.state_committed = true
|
215
|
+
session_data.object.states[@state_raw.state_id] = state_permanent(@state_raw)
|
216
|
+
session_data.bump()
|
217
|
+
clearTimeout(commit_timer) if commit_timer
|
218
|
+
commit_scheduled_at = null
|
219
|
+
commit_timer = null
|
220
|
+
location_update(replace)
|
221
|
+
|
222
|
+
commit_needed_at = Date.now() + defer
|
223
|
+
|
224
|
+
if not @state_raw.state_committed
|
225
|
+
last_committed_state = @state_raw.state_parent
|
226
|
+
else
|
227
|
+
last_committed_state = @state_raw
|
228
|
+
|
229
|
+
console.log("ROUTER: Changing state. Commit deferred by", defer, "to be done at", commit_needed_at, " State before mutation:",last_committed_state) if @debug
|
230
|
+
|
231
|
+
previous_state = @state_raw
|
232
|
+
new_state = JSON.parse(JSON.stringify(state_permanent(@state_raw)))
|
233
|
+
new_state = state_diff_apply(new_state, state_description.buffer)
|
234
|
+
new_state = state_diff_apply(new_state, state_description.exposed)
|
235
|
+
new_state = state_diff_apply(new_state, state_description.change)
|
236
|
+
_.extend(new_state, _.pick(previous_state, (value,key)-> key.indexOf("_") == 0))
|
237
|
+
|
238
|
+
if state_diff_generate(state_permanent(last_committed_state), state_permanent(new_state)).length > 0
|
239
|
+
new_state.state_committed = false
|
240
|
+
if previous_state.state_committed
|
241
|
+
new_state.session_id = @session_id
|
242
|
+
new_state.state_id = @state_id++
|
243
|
+
new_state.state_parent = previous_state
|
244
|
+
else
|
245
|
+
new_state.session_id = previous_state.session_id
|
246
|
+
new_state.state_id = previous_state.state_id
|
247
|
+
new_state.state_parent = previous_state.state_parent
|
248
|
+
else
|
249
|
+
new_state.session_id = last_committed_state.session_id
|
250
|
+
new_state.state_id = last_committed_state.state_id
|
251
|
+
new_state.state_parent = last_committed_state.state_parent
|
252
|
+
new_state.state_committed = last_committed_state.state_committed
|
253
|
+
|
254
|
+
@filter(new_state, previous_state) if @filter?
|
255
|
+
@state_raw = new_state
|
256
|
+
@state = @cast(JSON.parse(JSON.stringify(@state_raw)))
|
257
|
+
@state_valid = true
|
258
|
+
|
259
|
+
if @state_raw.state_committed
|
260
|
+
clearTimeout(commit_timer) if commit_timer
|
261
|
+
commit_scheduled_at = null
|
262
|
+
commit_timer = null
|
263
|
+
else
|
264
|
+
if commit_needed_at <= Date.now()
|
265
|
+
state_commit(replace)
|
266
|
+
else
|
267
|
+
location_update(false)
|
268
|
+
if (not commit_scheduled_at) or (commit_needed_at < commit_scheduled_at)
|
269
|
+
console.log("ROUTER: Deferring commit by:", defer, "till", commit_needed_at) if @debug
|
270
|
+
@state_raw.state_committed = false
|
271
|
+
clearTimeout(commit_timer) if commit_timer
|
272
|
+
commit_scheduled_at = commit_needed_at
|
273
|
+
commit_timer = setTimeout((()=> state_commit(replace)), commit_scheduled_at - Date.now())
|
274
|
+
|
275
|
+
@onchange(@state_raw,previous_state) if @onchange?
|
276
|
+
|
277
|
+
|
278
|
+
state_get_as_state_description = (state)=>
|
279
|
+
last_committed_state = state
|
280
|
+
while last_committed_state.state_parent and ((not last_committed_state.session_id) or last_committed_state.session_id == @session_id) and ((session_data_saved.object.max_state_id or 0 ) < last_committed_state.state_id)
|
281
|
+
last_committed_state = last_committed_state.state_parent
|
282
|
+
console.log('ROUTER: Last shareable state:', last_committed_state) if @debug
|
283
|
+
buffer = state_diff_generate(state_permanent(last_committed_state), state_permanent(state))
|
284
|
+
exposed = ([component[1], pair[1]] for pair in state_diff_generate({}, state) when component = _.find @expose, (component)-> component[0] == pair[0])
|
285
|
+
buffer = (pair for pair in buffer when not _.find @expose, (component)-> component[0] == pair[0])
|
286
|
+
{ session_id: last_committed_state.session_id, state_id: last_committed_state.state_id, exposed: exposed, buffer: buffer, change: [] }
|
287
|
+
|
288
|
+
|
289
|
+
location_update = (new_history_entry)=>
|
290
|
+
url = state_description_to_url(state_get_as_state_description(@state_raw))
|
291
|
+
console.log("ROUTER: Updating location: state:", @state_raw, ' url:', url) if @debug
|
292
|
+
if new_history_entry
|
293
|
+
window.history.pushState(@state_raw,null,url)
|
294
|
+
else
|
295
|
+
window.history.replaceState(@state_raw,null,url)
|
296
|
+
|
297
|
+
|
298
|
+
@navigate = (search, options = {})->
|
299
|
+
pathname = window.location.pathname
|
300
|
+
|
301
|
+
console.log('ROUTER: Navigating to: pathname:', pathname, ' search:', search) if @debug
|
302
|
+
state_description = url_to_state_description(pathname, search)
|
303
|
+
console.log('ROUTER: New state description:', state_description) if @debug
|
304
|
+
|
305
|
+
if remote_state?
|
306
|
+
remote_state.unlink()
|
307
|
+
remote_state = null
|
308
|
+
|
309
|
+
if state_description.session_id == @session_id or state_description.state_id == "0"
|
310
|
+
state_set_from_state_description(state_description, (options.defer or 0), !options.replace)
|
311
|
+
else
|
312
|
+
@state_valid = false
|
313
|
+
remote_state = @seapig_client.slave('SeapigRouter::Session::'+state_description.session_id+'::State::'+state_description.state_id)
|
314
|
+
remote_state.onchange ()=>
|
315
|
+
return if not remote_state.valid
|
316
|
+
console.log("ROUTER: Received remote state", remote_state.object) if @debug
|
317
|
+
@state_raw = JSON.parse(JSON.stringify(remote_state.object))
|
318
|
+
@state_raw.state_committed = true
|
319
|
+
@state_raw.session_id = state_description.session_id
|
320
|
+
@state_raw.state_id = state_description.state_id
|
321
|
+
@state_raw.state_parent = undefined
|
322
|
+
state_set_from_state_description(state_description, (options.defer or 0), !options.replace)
|
323
|
+
remote_state.unlink()
|
324
|
+
remote_state = null
|
325
|
+
|
326
|
+
|
327
|
+
@volatile = (data...)->
|
328
|
+
if data.length == 1 and typeof data[0] == 'object'
|
329
|
+
for key, value of data[0]
|
330
|
+
@state["_"+key] = value
|
331
|
+
_.extend(@state_raw, _.pick(@state, (value,key)-> key.indexOf("_") == 0))
|
332
|
+
window.history.replaceState(@state_raw,null,window.location)
|
333
|
+
else
|
334
|
+
_.object(([key, @state["_"+key]] for key in data))
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
|
3
|
+
require 'slop'
|
4
|
+
require 'yaml'
|
5
|
+
require 'seapig-client'
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
require 'seapig-postgresql-notifier'
|
9
|
+
require 'seapig-router'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
OPTIONS = Slop.parse { |o|
|
14
|
+
o.string '-c', '--connect', "Seapig server address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001"
|
15
|
+
o.string '-d', '--database-url', 'Database URL (e.g. postgres://USER:PASS@PGHOST/DBNAME)'
|
16
|
+
o.string '-e', '--environment' , 'Rails environment to use when loading database config from config/database.yml'
|
17
|
+
o.on '-h', '--help' do puts o; exit end
|
18
|
+
}
|
19
|
+
|
20
|
+
if (not OPTIONS["database-url"]) and (not File.exist?("config/database.yml")) then puts "Either -d or config/database.yml is needed"; exit end
|
21
|
+
database_config = (OPTIONS["database-url"] or YAML.load_file("config/database.yml")[(OPTIONS["environment"] or ENV["RAILS_ENV"] or "development")])
|
22
|
+
ActiveRecord::Base.establish_connection(database_config)
|
23
|
+
|
24
|
+
|
25
|
+
EM.run {
|
26
|
+
|
27
|
+
SeapigClient.new(OPTIONS["connect"],name: 'session-manager').slave('SeapigRouter::Session::*::Data').onchange { |session_data|
|
28
|
+
token = session_data["token"]
|
29
|
+
next if token != session_data.id.split('::')[2]
|
30
|
+
if not session = SeapigRouterSession.find_by(token: token)
|
31
|
+
begin
|
32
|
+
session_id = (('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a).shuffle[0..11].join('')
|
33
|
+
session = SeapigRouterSession.create!(key: session_id, token: token)
|
34
|
+
rescue ActiveRecord::RecordNotUnique
|
35
|
+
retry #FIXME: DOS
|
36
|
+
end
|
37
|
+
puts "Created new session: "+session.key+" for token: "+token
|
38
|
+
end
|
39
|
+
next if session_data["session_id"] and session.key != session_data["session_id"]
|
40
|
+
|
41
|
+
print "Saving session "+session.key+" states: "
|
42
|
+
max_state = session.seapig_router_session_states.order("state_id DESC").first
|
43
|
+
max_state_id = (max_state and max_state.state_id or -1)
|
44
|
+
session_data['states'].each_pair { |id, state|
|
45
|
+
if id.to_i > max_state_id
|
46
|
+
print ' '+id
|
47
|
+
SeapigRouterSessionState.create!(seapig_router_session_id: session.id, state_id: id, state: state)
|
48
|
+
end
|
49
|
+
}
|
50
|
+
puts
|
51
|
+
SeapigDependency.bump("SeapigRouter::Session::"+token)
|
52
|
+
SeapigDependency.bump("SeapigRouter::Session::"+session.key)
|
53
|
+
}
|
54
|
+
|
55
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateSeapigRouterSessionStates < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :seapig_router_session_states do |t|
|
4
|
+
t.integer :seapig_router_session_id
|
5
|
+
t.integer :state_id
|
6
|
+
t.jsonb :state
|
7
|
+
|
8
|
+
t.timestamps null: false
|
9
|
+
end
|
10
|
+
add_index :seapig_router_session_states, [:seapig_router_session_id,:state_id], unique: true, name: "seapig_router_session_states_index_1"
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddTokenToSeapigSessions < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
add_column :seapig_router_sessions, :token, :text
|
4
|
+
add_index :seapig_router_sessions, :token, unique: true, name: "seapig_router_sessions_token_index"
|
5
|
+
add_index :seapig_router_sessions, [:key,:token], unique: true, name: "seapig_router_sessions_key_token_index"
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class SeapigRouterSessionSaved < Producer
|
2
|
+
|
3
|
+
@patterns = [ 'SeapigRouter::Session::*::Saved' ]
|
4
|
+
|
5
|
+
|
6
|
+
def self.produce(seapig_object_id)
|
7
|
+
seapig_object_id =~ /SeapigRouter::Session::([^\:]+)::Saved/
|
8
|
+
token = $1
|
9
|
+
version = SeapigDependency.versions('SeapigRouter::Session::'+token)
|
10
|
+
session = SeapigRouterSession.find_by(token: token)
|
11
|
+
return [false, version] if not session
|
12
|
+
max_state = session.seapig_router_session_states.select("state_id").order("state_id DESC").first
|
13
|
+
data = {
|
14
|
+
session_id: session.key,
|
15
|
+
max_state_id: (max_state and max_state.state_id or -1)
|
16
|
+
}
|
17
|
+
[data, version]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class SeapigRouterSessionStateProducer < Producer
|
2
|
+
|
3
|
+
@patterns = [ 'SeapigRouter::Session::*::State::*' ]
|
4
|
+
|
5
|
+
|
6
|
+
def self.produce(seapig_object_id)
|
7
|
+
seapig_object_id =~ /SeapigRouter::Session::([^\:]+)::State::([^\:]+)/
|
8
|
+
session_key = $1
|
9
|
+
state_id = $2.to_i
|
10
|
+
version = Time.new.to_f
|
11
|
+
session = SeapigRouterSession.find_by(key: session_key)
|
12
|
+
return [false, SeapigDependency.versions('SeapigRouter::Session::'+session_key)] if not session
|
13
|
+
state = SeapigRouterSessionState.find_by(seapig_router_session_id: session.id, state_id: state_id)
|
14
|
+
return [false, SeapigDependency.versions('SeapigRouter::Session::'+session_key)] if not state
|
15
|
+
data = state.state
|
16
|
+
[data, version]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seapig-router
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yunta
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: seapig-client-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: seapig-postgresql-notifier
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.2.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.2.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pg
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: meh
|
84
|
+
email:
|
85
|
+
- maciej.blomberg@mikoton.com
|
86
|
+
executables:
|
87
|
+
- seapig-router-session-manager
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- MIT-LICENSE
|
92
|
+
- Rakefile
|
93
|
+
- app/assets/javascripts/seapig/seapig-router.js.coffee
|
94
|
+
- app/models/seapig_router_session.rb
|
95
|
+
- app/models/seapig_router_session_state.rb
|
96
|
+
- bin/seapig-router-session-manager
|
97
|
+
- db/migrate/20151221110834_create_seapig_router_sessions.rb
|
98
|
+
- db/migrate/20151221111628_create_seapig_router_session_states.rb
|
99
|
+
- db/migrate/20161231183822_add_token_to_seapig_sessions.rb
|
100
|
+
- lib/seapig-router.rb
|
101
|
+
- lib/seapig-router/engine.rb
|
102
|
+
- lib/seapig-router/version.rb
|
103
|
+
- lib/seapigs/seapig_router_saved_session.rb
|
104
|
+
- lib/seapigs/seapig_router_session_state.rb
|
105
|
+
homepage: https://github.com/yunta-mb/seapig-rails
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
metadata: {}
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.5.2
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: Transient object synchronization lib - rails
|
129
|
+
test_files: []
|