cpee 2.0.6 → 2.0.13

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/cockpit/config.json +3 -0
  3. data/cockpit/css/track.css +17 -6
  4. data/cockpit/css/ui.css +6 -2
  5. data/cockpit/css/wfadaptor.css +1 -1
  6. data/cockpit/edit.html +1 -0
  7. data/cockpit/index.html +2 -2
  8. data/cockpit/js/instance.js +75 -55
  9. data/cockpit/js/track.js +10 -0
  10. data/cockpit/js/ui.js +47 -16
  11. data/cockpit/templates/Track Test Local.xml +82 -0
  12. data/cockpit/templates/Track Test.xml +82 -0
  13. data/cockpit/templates/instantiate.local/Take_Sub.xml +59 -0
  14. data/cockpit/templates/instantiate/Take_Perf.xml +46 -0
  15. data/cockpit/templates/instantiate/Take_Sub.xml +58 -0
  16. data/cockpit/templates/instantiate/Take_X.xml +48 -0
  17. data/cockpit/themes/compact/rngs/call.rng +5 -0
  18. data/cockpit/themes/compact/rngs/callmanipulate.rng +6 -1
  19. data/cockpit/themes/compact/rngs/loop.rng +1 -1
  20. data/cockpit/themes/compact/rngs/parallel.rng +1 -1
  21. data/cockpit/themes/default/rngs/call.rng +5 -0
  22. data/cockpit/themes/default/rngs/callmanipulate.rng +6 -1
  23. data/cockpit/themes/default/rngs/loop.rng +1 -1
  24. data/cockpit/themes/default/rngs/parallel.rng +1 -1
  25. data/cockpit/themes/extended/rngs/call.rng +5 -0
  26. data/cockpit/themes/extended/rngs/callmanipulate.rng +6 -1
  27. data/cockpit/themes/extended/rngs/loop.rng +1 -1
  28. data/cockpit/themes/extended/rngs/parallel.rng +1 -1
  29. data/cockpit/themes/extended/theme.js +4 -0
  30. data/cockpit/themes/model/theme.js +1 -1
  31. data/cockpit/themes/packed/rngs/call.rng +5 -0
  32. data/cockpit/themes/packed/rngs/callmanipulate.rng +6 -1
  33. data/cockpit/themes/packed/rngs/loop.rng +1 -1
  34. data/cockpit/themes/packed/rngs/parallel.rng +1 -1
  35. data/cockpit/themes/preset/rngs/call.rng +5 -0
  36. data/cockpit/themes/preset/rngs/callmanipulate.rng +6 -1
  37. data/cockpit/themes/preset/rngs/loop.rng +1 -1
  38. data/cockpit/themes/preset/rngs/parallel.rng +1 -1
  39. data/cockpit/track.html +37 -8
  40. data/cpee.gemspec +3 -3
  41. data/lib/cpee/implementation.rb +15 -6
  42. data/lib/cpee/implementation_notifications.rb +80 -51
  43. data/lib/cpee/implementation_properties.rb +5 -2
  44. data/lib/cpee/persistence.rb +3 -0
  45. data/server/handlerwrappers/default.rb +3 -0
  46. data/server/routing/forward-events.rb +2 -1
  47. data/server/routing/forward-votes.rb +1 -1
  48. data/server/server.conf +4 -0
  49. data/tools/cpee +17 -9
  50. metadata +17 -10
  51. data/cockpit/templates/convert_cpee2.rb +0 -15
  52. data/cockpit/themes/convert_cpee2.rb +0 -8
@@ -241,7 +241,7 @@ function WFAdaptorManifestation(adaptor) {
241
241
  'label': function(node){
242
242
  var ret;
243
243
  if ($('> url',$(node).children('parameters').children('arguments')).length > 0) {
244
- ret = [ { column: 'Label', value: '<a target="blank_" href="' + $('> url',$(node).children('parameters').children('arguments')).text() + '">' + $('> label',$(node).children('parameters')).text().replace(/^['"]/,'').replace(/['"]$/,'') + '</a>' } ];
244
+ ret = [ { column: 'Label', value: $('> label',$(node).children('parameters')).text().replace(/^['"]/,'').replace(/['"]$/,'') + ' <a target="blank_" href="' + $('> url',$(node).children('parameters').children('arguments')).text() + '"></a>' } ];
245
245
  } else {
246
246
  ret = [ { column: 'Label', value: $('> label',$(node).children('parameters')).text().replace(/^['"]/,'').replace(/['"]$/,'') } ];
247
247
  }
@@ -89,6 +89,11 @@
89
89
  </zeroOrMore>
90
90
  </element>
91
91
  </element>
92
+ <element name="report" rngui:header="Reporting Annotation" rngui:fold="closed">
93
+ <element name="url" rngui:label='HTML Snippet'>
94
+ <data type="string" rngui:label="url to report snippet"/>
95
+ </element>
96
+ </element>
92
97
  </element>
93
98
  <element name="annotations" rngui:header="Annotations">
94
99
  <element name="_timing" rngui:header="Timing" rngui:fold="closed">
@@ -89,6 +89,11 @@
89
89
  </zeroOrMore>
90
90
  </element>
91
91
  </element>
92
+ <element name="report" rngui:header="Reporting Annotation" rngui:fold="closed">
93
+ <element name="url" rngui:label='HTML Snippet'>
94
+ <data type="string" rngui:label="url to report snippet"/>
95
+ </element>
96
+ </element>
92
97
  </element>
93
98
  <element name="code" rngui:header="Implementation">
94
99
  <element name="prepare" rngui:header="Prepare" rngui:label="Code" rngui:fold="closed_conditional">
@@ -137,4 +142,4 @@
137
142
  </element>
138
143
  </element>
139
144
  </element>
140
- </element>
145
+ </element>
@@ -19,4 +19,4 @@
19
19
  <data type="nonNegativeInteger" rngui:label="Times"/>
20
20
  </element>
21
21
  </element>
22
- </element>
22
+ </element>
@@ -18,4 +18,4 @@
18
18
  <data type="string"/>
19
19
  </attribute>
20
20
  </element>
21
- </element>
21
+ </element>
@@ -89,6 +89,11 @@
89
89
  </zeroOrMore>
90
90
  </element>
91
91
  </element>
92
+ <element name="report" rngui:header="Reporting Annotation" rngui:fold="closed">
93
+ <element name="url" rngui:label='HTML Snippet'>
94
+ <data type="string" rngui:label="url to report snippet"/>
95
+ </element>
96
+ </element>
92
97
  </element>
93
98
  <element name="annotations" rngui:header="Annotations">
94
99
  <element name="_timing" rngui:header="Timing" rngui:fold="closed">
@@ -89,6 +89,11 @@
89
89
  </zeroOrMore>
90
90
  </element>
91
91
  </element>
92
+ <element name="report" rngui:header="Reporting Annotation" rngui:fold="closed">
93
+ <element name="url" rngui:label='HTML Snippet'>
94
+ <data type="string" rngui:label="url to report snippet"/>
95
+ </element>
96
+ </element>
92
97
  </element>
93
98
  <element name="code" rngui:header="Implementation">
94
99
  <element name="prepare" rngui:header="Prepare" rngui:label="Code" rngui:fold="closed_conditional">
@@ -137,4 +142,4 @@
137
142
  </element>
138
143
  </element>
139
144
  </element>
140
- </element>
145
+ </element>
@@ -19,4 +19,4 @@
19
19
  <data type="nonNegativeInteger" rngui:label="Times"/>
20
20
  </element>
21
21
  </element>
22
- </element>
22
+ </element>
@@ -18,4 +18,4 @@
18
18
  <data type="string"/>
19
19
  </attribute>
20
20
  </element>
21
- </element>
21
+ </element>
@@ -62,12 +62,41 @@
62
62
  <div class='hidden' id='relaxngworker'></div>
63
63
  <div id="trackfull">
64
64
  <div id='graphcolumn'>
65
- <div id="usage">
66
- <span id="title"></span> | <span id='state'>
67
- <span id="state_text"></span>
65
+ <div id="trackusage">
66
+ <div id='tracktitle'>
67
+ <span>
68
+ <a class="x-ui-button" name="glob_reload" title='reload' href=''>
69
+ <svg viewBox="0 -1.5 7 12" width="10" height="16">
70
+ <path
71
+ style="fill:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stop-color:#000000"
72
+ d="M 3.5,1.5 C 1.431441,1.5001975 -0.24540803,3.1770466 -0.24560554,5.2456055 -0.2454082,7.3141647 1.4314408,8.991014 3.5,8.9912113 5.5685591,8.991014 7.2454081,7.3141647 7.2456054,5.2456055 7.2454079,3.1770466 5.5685589,1.5001975 3.5,1.5 Z M 3.437195,4.0616198 c 0.020931,-5.551e-4 0.041873,-5.551e-4 0.062805,0 0.6537674,3.133e-4 1.1836723,0.5302183 1.1839856,1.1839857 C 4.6836723,5.8993729 4.1537674,6.4292778 3.5,6.4295912 2.8462325,6.4292778 2.3163276,5.8993729 2.3160142,5.2456055 2.315414,4.6155841 2.8080758,4.0953252 3.437195,4.0616198 Z"/>
73
+ </svg>
74
+ </a>
75
+ <a class="x-ui-button" name="glob_edit" title='edit' target='_blank' href='' id='current-monitor'><svg viewBox="0 -1.5 7 12" width="10" height="16">
76
+ <path
77
+ style="fill:#000000;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
78
+ d="M -0.25,7.8194404 0.57057194,10.599984 3.1882886,9.3540573 Z"/>
79
+ <path
80
+ style="fill:#000000;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
81
+ d="M 3.5215262,8.6074434 6.0527848,2.9361926 2.6144961,1.4015756 0.08323736,7.0728263 Z"/>
82
+ <path
83
+ style="fill:#000000;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
84
+ d="M 6.4439758,2.0597335 6.9719316,0.87685566 3.533643,-0.6577613 3.005687,0.52511656 Z"/>
85
+ </svg></a>
86
+ <a class="x-ui-button" name="glob_unshow" title='unshow'>
87
+ <svg viewBox="0 -1.5 7 12" width="10" height="16">
88
+ <path
89
+ style="fill:#000000;stroke-width:0;stop-color:#000000"
90
+ d="M 5.75,1.4999999 3.5,3.75 1.25,1.4999999 -0.25000013,3 2,5.25 -0.25000013,7.5 1.25,9.0000002 3.5,6.75 5.75,9.0000002 7.2500001,7.5 5,5.25 7.2500001,3 Z"/>
91
+ </svg>
92
+ </a>
93
+ </span>
94
+ <span> - </span>
95
+ <span id="title">Loading ...</span>
96
+ </div>
97
+ <div id='state'>
68
98
  <span id="state_any">
69
- <span> </span>
70
- <button name="state_start" title='start'>
99
+ <button name="state_start" title='start' style='display:none'>
71
100
  <svg viewBox="0 -1.5 7 12" width="10" height="16">
72
101
  <path
73
102
  style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
@@ -82,7 +111,6 @@
82
111
  </svg>
83
112
  </button>
84
113
  <span id="state_extended">
85
- <span> / </span>
86
114
  <button name="state_replay" title='replay'>
87
115
  <svg viewBox="0 -1.5 7 12" width="10" height="16">
88
116
  <path
@@ -90,7 +118,6 @@
90
118
  d="m -0.24999978,8.9999993 c 0,-2.7488167 0,-5.4976332 0,-8.24644997 C 1.1183278,0.76955385 2.4884861,0.71666052 3.8555376,0.79065567 4.5083287,0.83810218 5.2238942,1.0790874 5.5755039,1.7307256 6.1661398,2.7080902 6.0299484,4.2180754 5.1108772,4.9019222 4.7622397,5.1617467 4.3480204,5.2956156 3.9336064,5.3605366 4.9287868,5.9394132 5.4254071,7.0996814 6.0167729,8.0858876 6.1549588,8.3769029 6.4657247,8.7723933 6.5,8.9999999 c -0.5944185,0 -1.1888371,0 -1.7832559,0 C 4.0758679,7.9893475 3.4946311,6.9294648 2.7923497,5.9678488 2.4230355,5.5319748 1.8508715,5.5458842 1.3513114,5.5576027 c -0.1507526,-0.00412 -0.044576,0.272902 -0.076255,0.3990664 0,1.0144436 0,2.028887 0,3.0433307 -0.50835205,-3e-7 -1.01670555,8e-7 -1.52505618,-5e-7 z M 1.2750578,4.2411336 C 2.111059,4.2189781 2.9581852,4.3060525 3.7849326,4.1387394 4.4863715,3.8915585 4.5732952,2.6432813 3.9026802,2.2941453 3.5098807,2.068434 3.050719,2.1857044 2.6267379,2.1489138 c -0.4505578,-0.00242 -0.90112,0.00147 -1.3516801,-3.456e-4 0,0.6975176 0,1.3950347 0,2.0925522 z"
91
119
  </svg>
92
120
  </button>
93
- <span> / </span>
94
121
  <button name="state_abandon" title='abandon'>
95
122
  <svg viewBox="0 -1.5 7 12" width="10" height="16">
96
123
  <path
@@ -99,8 +126,10 @@
99
126
  </svg>
100
127
  </button>
101
128
  </span>
129
+ <span> - Status: </span>
102
130
  </span>
103
- </span>
131
+ <span id="state_text">loading ...</span>
132
+ </div>
104
133
  </div>
105
134
  <div id='graphgrid'>
106
135
  <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:x="http://www.w3.org/1999/xlink" id='graphcanvas' width='1' height='1'></svg>
@@ -1,13 +1,13 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "cpee"
3
- s.version = "2.0.6"
3
+ s.version = "2.0.13"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.license = "LGPL-3.0"
6
6
  s.summary = "Preliminary release of cloud process execution engine (cpee.org). If you just need workflow execution, without a rest service exposing it, then use WEEL."
7
7
 
8
8
  s.description = "see http://cpee.org"
9
9
 
10
- s.files = Dir['{example/**/*,server/**/*,tools/**/*,lib/**/*,cockpit/**/*,cockpit/themes/*/*/*,contrib/logo*,contrib/Screen*}'] - Dir['{server/instances/**/*,cockpit/js_libs/**/*}'] + %w(COPYING FEATURES.md INSTALL.md Rakefile cpee.gemspec README.md AUTHORS)
10
+ s.files = Dir['{example/**/*,server/**/*,tools/**/*,tools/archive,lib/**/*,cockpit/**/*,cockpit/themes/*/*/*,contrib/logo*,contrib/Screen*}'] - Dir['{server/instances/**/*,cockpit/js_libs/**/*}'] + %w(COPYING FEATURES.md INSTALL.md Rakefile cpee.gemspec README.md AUTHORS)
11
11
  s.require_path = 'lib'
12
12
  s.extra_rdoc_files = ['README.md']
13
13
  s.bindir = 'tools'
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.email = 'juergen.mangler@gmail.com'
22
22
  s.homepage = 'http://cpee.org/'
23
23
 
24
- s.add_runtime_dependency 'riddl', '~> 0.105'
24
+ s.add_runtime_dependency 'riddl', '~> 0.108'
25
25
  s.add_runtime_dependency 'weel', '~> 1.99', '>= 1.99.90'
26
26
  s.add_runtime_dependency 'highline', '~> 2.0'
27
27
  s.add_runtime_dependency 'json', '~>2.1'
@@ -72,7 +72,9 @@ module CPEE
72
72
  opts[:infinite_loop_stop] ||= 10000
73
73
  opts[:redis_path] ||= '/tmp/redis.sock'
74
74
  opts[:redis_db] ||= 3
75
+ opts[:sse_keepalive_frequency] ||= 10
75
76
 
77
+ opts[:sse_connections] = {}
76
78
  opts[:redis] = Redis.new(path: opts[:redis_path], db: opts[:redis_db])
77
79
  opts[:statemachine] = CPEE::StateMachine.new opts[:states], %w{running simulating replaying finishing stopping abandoned finished} do |id|
78
80
  opts[:redis].get("instance:#{id}/state")
@@ -88,13 +90,20 @@ module CPEE
88
90
 
89
91
  Proc.new do
90
92
  parallel do
91
- CPEE::watch_services(@riddl_opts[:watchdog_start_off])
92
- EM.add_periodic_timer(@riddl_opts[:watchdog_frequency]) do
93
- CPEE::watch_services(@riddl_opts[:watchdog_start_off])
93
+ CPEE::watch_services(opts[:watchdog_start_off])
94
+ EM.add_periodic_timer(opts[:watchdog_frequency]) do ### start services
95
+ CPEE::watch_services(opts[:watchdog_start_off])
96
+ end
97
+ EM.defer do ### catch all sse connections
98
+ CPEE::Notifications::sse_distributor(opts)
99
+ end
100
+ EM.add_periodic_timer(opts[:sse_keepalive_frequency]) do
101
+ CPEE::Notifications::sse_heartbeat(opts)
94
102
  end
95
103
  end
104
+
96
105
  cleanup do
97
- CPEE::cleanup_services(@riddl_opts[:watchdog_start_off])
106
+ CPEE::cleanup_services(opts[:watchdog_start_off])
98
107
  end
99
108
 
100
109
  interface 'main' do
@@ -183,7 +192,7 @@ module CPEE
183
192
  doc = XML::Smart::open_unprotected(opts[:properties_init])
184
193
  doc.register_namespace 'p', 'http://cpee.org/ns/properties/2.0'
185
194
  name = @p[0].value
186
- id = redis.zcount('instances','-inf','+inf').to_i + 1
195
+ id = redis.zrevrange('instances', 0, 0).first.to_i + 1
187
196
  uuid = SecureRandom.uuid
188
197
  instance = 'instance:' + id.to_s
189
198
  redis.multi do |multi|
@@ -218,7 +227,7 @@ module CPEE
218
227
  end
219
228
  end rescue nil # all the ones that are not ok, are ignored
220
229
  end
221
- multi.set(File.join(instance, 'attributes', 'uuid'), SecureRandom.uuid)
230
+ multi.set(File.join(instance, 'attributes', 'uuid'), uuid)
222
231
  multi.zadd(File.join(instance, 'attributes'), -2, 'uuid')
223
232
  multi.set(File.join(instance, 'attributes', 'info'), name)
224
233
  multi.zadd(File.join(instance, 'attributes'), -1, 'info')
@@ -73,35 +73,40 @@ module CPEE
73
73
  id = @a[0]
74
74
  opts = @a[1]
75
75
  key = @r[-1]
76
- Riddl::Parameter::Complex.new("subscriptions","text/xml") do
77
- ret = XML::Smart::string <<-END
78
- <subscription xmlns='http://riddl.org/ns/common-patterns/notifications-producer/2.0'/>
79
- END
80
- url = CPEE::Persistence::extract_item(id,opts,File.join('handler',key,'url'))
81
- ret.root.attributes['url'] = url if url && !url.empty?
82
- items = {}
83
- CPEE::Persistence::extract_handler(id,opts,key).each do |h|
84
- t, i, v = h.split('/')
85
- items[t] ||= []
86
- items[t] << [i,v]
87
- end
88
- items.each do |k,v|
89
- ret.root.add('topic').tap do |n|
90
- n.attributes['id'] = k
91
- v.each do |e|
92
- n.add *e
76
+ if CPEE::Persistence::exists_handler?(id,opts,key)
77
+ Riddl::Parameter::Complex.new("subscriptions","text/xml") do
78
+ ret = XML::Smart::string <<-END
79
+ <subscription xmlns='http://riddl.org/ns/common-patterns/notifications-producer/2.0'/>
80
+ END
81
+ url = CPEE::Persistence::extract_item(id,opts,File.join('handler',key,'url'))
82
+ ret.root.attributes['url'] = url if url && !url.empty?
83
+ items = {}
84
+ CPEE::Persistence::extract_handler(id,opts,key).each do |h|
85
+ t, i, v = h.split('/')
86
+ items[t] ||= []
87
+ items[t] << [i,v]
88
+ end
89
+ items.each do |k,v|
90
+ ret.root.add('topic').tap do |n|
91
+ n.attributes['id'] = k
92
+ v.each do |e|
93
+ n.add *e
94
+ end
93
95
  end
94
96
  end
97
+ ret.to_s
95
98
  end
96
- ret.to_s
97
- end
99
+ else
100
+ @status = 404
101
+ end
98
102
  end
99
103
  end #}}}
100
104
 
101
- class CreateSubscription < Riddl::Implementation #{{{
105
+ class CreateSubscription < Riddl::Implementation #{{{
102
106
  def response
103
107
  id = @a[0]
104
108
  opts = @a[1]
109
+
105
110
  key = Digest::MD5.hexdigest(Kernel::rand().to_s)
106
111
 
107
112
  url = @p[0].name == 'url' ? @p.shift.value : nil
@@ -124,15 +129,19 @@ module CPEE
124
129
  opts = @a[1]
125
130
  key = @r.last
126
131
 
127
- url = @p[0].name == 'url' ? @p.shift.value : nil
128
- values = []
129
- while @p.length > 0
130
- topic = @p.shift.value
131
- base = @p.shift
132
- type = base.name
133
- values += base.value.split(',').map { |i| File.join(topic,type[0..-2],i) }
132
+ if CPEE::Persistence::exists_handler?(id,opts,key)
133
+ url = @p[0].name == 'url' ? @p.shift.value : nil
134
+ values = []
135
+ while @p.length > 0
136
+ topic = @p.shift.value
137
+ base = @p.shift
138
+ type = base.name
139
+ values += base.value.split(',').map { |i| File.join(topic,type[0..-2],i) }
140
+ end
141
+ @header = CPEE::Persistence::set_handler(id,opts,key,url,values,true)
142
+ else
143
+ @status = 404
134
144
  end
135
- @header = CPEE::Persistence::set_handler(id,opts,key,url,values,true)
136
145
  end
137
146
  end #}}}
138
147
 
@@ -146,41 +155,61 @@ module CPEE
146
155
  opts = @a[1]
147
156
  key = @r.last
148
157
 
149
- DeleteSubscription::set(id,opts,key)
158
+ if CPEE::Persistence::exists_handler?(id,opts,key)
159
+ DeleteSubscription::set(id,opts,key)
160
+ else
161
+ @status = 404
162
+ end
150
163
  nil
151
164
  end
152
165
  end #}}}
153
166
 
154
- class SSE < Riddl::SSEImplementation #{{{
155
- def onopen
156
- @id = @a[0]
157
- @opts = @a[1]
158
- @key = @r[-2]
159
- @conn = Redis.new(path: @opts[:redis_path], db: @opts[:redis_db])
160
- EM.defer do
161
- @conn.subscribe("forward:#{@id}/#{@key}", "forward-end:#{@id}/#{@key}") do |on|
162
- on.message do |what, message|
163
- if what == "forward-end:#{@id}/#{@key}"
164
- @conn.unsubscribe
165
- else
166
- send message
167
+ def self::sse_distributor(opts) #{{{
168
+ conn = Redis.new(path: opts[:redis_path], db: opts[:redis_db])
169
+ conn.psubscribe('forward:*','event:state/change') do |on|
170
+ on.pmessage do |pat, what, message|
171
+ if pat == 'forward:*'
172
+ _, id, key = what.match(/forward(-end)?:([^\/]+)\/(.+)/).captures
173
+ opts.dig(:sse_connections,id.to_i,key)&.send message
174
+ elsif pat == 'event:state/change'
175
+ mess = JSON.parse(message[message.index(' ')+1..-1])
176
+ state = mess.dig('content','state')
177
+ if state == 'finished' || state == 'abandoned'
178
+ opts.dig(:sse_connections,mess.dig('instance').to_i)&.each do |key,sse|
179
+ EM.add_timer(10) do # just to be sure that all messages arrived. 10 seconds should be enough ... we think ... therefore we are (not sure)
180
+ sse.close
181
+ end
167
182
  end
168
183
  end
169
184
  end
170
- @conn.close
171
185
  end
172
- EM.defer do
173
- until closed?
174
- send_with_id 'hearbeat', '42'
175
- sleep 10
176
- end
186
+ end
187
+ conn.close
188
+ end #}}}
189
+ def self::sse_heartbeat(opts) #{{{
190
+ opts.dig(:sse_connections).each do |id,keys|
191
+ keys.each do |key,sse|
192
+ sse.send_with_id('heartbeat', '42') unless sse&.closed?
193
+ end
194
+ end
195
+ end #}}}
196
+ class SSE < Riddl::SSEImplementation #{{{
197
+ def onopen
198
+ @opts = @a[1]
199
+ @id = @a[0]
200
+ @key = @r[-2]
201
+ if CPEE::Persistence::exists_handler?(@id,@opts,@key)
202
+ @opts[:sse_connections][@id] ||= {}
203
+ @opts[:sse_connections][@id][@key] = self
204
+ true
205
+ else
206
+ false
177
207
  end
178
208
  end
179
209
 
180
210
  def onclose
181
- tredis = Redis.new(path: @opts[:redis_path], db: @opts[:redis_db])
182
- tredis.publish("forward-end:#{@id}/#{@key}",true)
183
- tredis.close
211
+ @opts.dig(:sse_connections,@id)&.delete(@key)
212
+ @opts.dig(:sse_connections)&.delete(@id) if @opts.dig(:sse_connections,@id)&.length == 0
184
213
  DeleteSubscription::set(@id,@opts,@key)
185
214
  end
186
215
  end #}}}
@@ -660,8 +660,11 @@ module CPEE
660
660
  @status = 422 # semantic error
661
661
  else
662
662
  begin
663
- PutDescription::set(id,opts,@p[0].value.read)
664
- rescue
663
+ # force-encoding because johannes managed to sneak in ascii special characters. why the browser is not sanitizing it is beyond me.
664
+ PutDescription::set(id,opts,@p[0].value.read.force_encoding('UTF-8'))
665
+ rescue => e
666
+ puts e.message
667
+ puts e.backtrace
665
668
  @status = 400
666
669
  end
667
670
  end
@@ -125,6 +125,9 @@ module CPEE
125
125
  def self::extract_handler(id,opts,key) #{{{
126
126
  opts[:redis].smembers("instance:#{id}/handlers/#{key}")
127
127
  end #}}}
128
+ def self::exists_handler?(id,opts,key) #{{{
129
+ opts[:redis].exists?("instance:#{id}/handlers/#{key}")
130
+ end #}}}
128
131
  def self::extract_handlers(id,opts) #{{{
129
132
  opts[:redis].smembers("instance:#{id}/handlers").map do |e|
130
133
  [e, opts[:redis].get("instance:#{id}/handlers/#{e}/url")]