frames 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. #!/usr/bin/ruby
  2. #
  3. # This file is part of centurio.work/ing/commands.
  4. #
  5. # centurio.work/ing/commands is free software: you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or (at your
  8. # option) any later version.
  9. #
  10. # centurio.work/ing/commands is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
  13. # Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # centurio.work/ing/commands (file COPYING in the main directory). If not, see
  17. # <http://www.gnu.org/licenses/>.
  18. require 'rubygems'
  19. require 'json'
  20. require 'xml/smart'
  21. require 'riddl/server'
  22. require 'fileutils'
  23. require 'typhoeus'
  24. # process:
  25. # https://centurio.work/customers/evva/flow/?monitor=https://centurio.work/flow-test/engine/729/
  26. class Get < Riddl::Implementation
  27. def response
  28. if @r[0] == 'test'
  29. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','test.html')))
  30. elsif @r[0] == 'menu'
  31. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','menu.html')))
  32. elsif @r[0] == 'framedata'
  33. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','framedata.html')))
  34. else
  35. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','template.html')))
  36. end
  37. end
  38. end
  39. class Put < Riddl::Implementation
  40. def response
  41. Dir.mkdir(File.join('data',@r.last)) rescue nil
  42. if @p[0].value.to_s.strip.empty?
  43. File.write(File.join('data',@r.last,'style.url'), "../css/frames.css")
  44. else
  45. File.write(File.join('data',@r.last,'style.url'),@p[0].value)
  46. end
  47. File.write(File.join('data',@r.last,'document.xml'),@p[1].value)
  48. File.write(File.join('data',@r.last,'frames.json'),JSON.dump(JSON.parse('{"data":[]}')))
  49. File.write(File.join('data',@r.last,'dataelements.json'),JSON.dump(JSON.parse('{"data":[]}')))
  50. File.write(File.join('data',@r.last,'info.json'),JSON.dump(JSON.parse('{"x_amount":' + @p[2].value + ', "y_amount":' + @p[3].value + ', "lang":"' + @p[4].value + '", "langs":["' + @p[4].value + '"]}')))
  51. File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])
  52. @a[0].send('new')
  53. nil
  54. end
  55. #def headers
  56. # Riddl::Header.new('CPEE-CALLBACK', 'true')
  57. #end
  58. end
  59. #https://coderwall.com/p/atyfyq/ruby-string-to-boolean
  60. class String
  61. def to_bool
  62. return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
  63. return false if self == false || self.empty? || self =~ (/(false|f|no|n|0)$/i)
  64. raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
  65. end
  66. end
  67. class NewFrame < Riddl::Implementation
  68. def response
  69. path = File.join('data',@r.last,'frames.json')
  70. file = File.read(path)
  71. data_hash = JSON::parse(file)
  72. data_hash["data"].each do | c |
  73. if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[0].value.to_i, @p[1].value.to_i, (@p[0].value.to_i + @p[2].value.to_i - 1), (@p[1].value.to_i + @p[3].value.to_i - 1))
  74. data_hash["data"].delete(c)
  75. end
  76. end
  77. # example
  78. # myObj = {
  79. # "lx":3,
  80. # "ly":3,
  81. # "rx":3,
  82. # "ry":3,
  83. # "url": {
  84. # "de-at":"xyz.at",
  85. # "en-us":"xyz.com"
  86. # }
  87. # }
  88. urls = JSON::parse(@p[4].value);
  89. hash = {lx: @p[0].value.to_i, ly: @p[1].value.to_i, rx: (@p[0].value.to_i + @p[2].value.to_i - 1), ry: (@p[1].value.to_i + @p[3].value.to_i - 1), url: urls, showbutton: @p[5].value.to_bool};
  90. data_hash["data"].push(hash)
  91. File.write(path, JSON.dump(data_hash))
  92. #only send active url to client
  93. infofile = File.join('data',@r.last,'info.json')
  94. infojson = JSON::parse(File.read(infofile))
  95. hash["url"] = urls[infojson["lang"]]
  96. @a[0].send(JSON.dump(hash))
  97. nil
  98. end
  99. end
  100. class NewFramePut < Riddl::Implementation
  101. def response
  102. path = File.join('data',@r.last,'frames.json')
  103. file = File.read(path)
  104. data_hash = JSON::parse(file)
  105. data_hash["data"].each do | c |
  106. if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[0].value.to_i, @p[1].value.to_i, (@p[0].value.to_i + @p[2].value.to_i - 1), (@p[1].value.to_i + @p[3].value.to_i - 1))
  107. data_hash["data"].delete(c)
  108. end
  109. end
  110. urls = JSON::parse(@p[4].value);
  111. hash = {lx: @p[0].value.to_i, ly: @p[1].value.to_i, rx: (@p[0].value.to_i + @p[2].value.to_i - 1), ry: (@p[1].value.to_i + @p[3].value.to_i - 1), url: urls, showbutton: @p[5].value.to_bool, callback: @h['CPEE_CALLBACK']};
  112. data_hash["data"].push(hash)
  113. File.write(path, JSON.dump(data_hash))
  114. #only send active url to client
  115. infofile = File.join('data',@r.last,'info.json')
  116. infojson = JSON::parse(File.read(infofile))
  117. hash["url"] = urls[infojson["lang"]]
  118. @a[0].send(JSON.dump(hash))
  119. nil
  120. end
  121. def headers
  122. Riddl::Header.new('CPEE-CALLBACK', 'true')
  123. end
  124. end
  125. class DeleteFrame < Riddl::Implementation
  126. def response
  127. puts "deleting"
  128. path = File.join('data',@r.last,'frames.json')
  129. file = File.read(path)
  130. data_hash = JSON::parse(file)
  131. data_hash["data"].each do | c |
  132. if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[0].value.to_i, @p[1].value.to_i, (@p[0].value.to_i + 1), (@p[1].value.to_i + 1))
  133. data_hash["data"].delete(c)
  134. end
  135. end
  136. File.write(path, JSON.dump(data_hash))
  137. end
  138. end
  139. def doOverlap(l1x, l1y, r1x, r1y, l2x, l2y, r2x, r2y)
  140. if l1x > r2x || l2x > r1x
  141. return false;
  142. end
  143. if l1y > r2y || l2y > r1y
  144. return false;
  145. end
  146. return true;
  147. end
  148. class Delete < Riddl::Implementation
  149. def response
  150. if cbu = File.read(File.join('data',@r.last,'callback'))
  151. send = { 'operation' => @p[0].value }
  152. case send['operation']
  153. when 'result'
  154. send['target'] = JSON::parse(@p[1].value.read)
  155. end
  156. cbu += '/' unless cbu[-1] == '/'
  157. Typhoeus.put(cbu, body: JSON::generate(send), headers: { 'content-type' => 'application/json'})
  158. end
  159. File.unlink(File.join('data',@r.last,'callback')) rescue nil
  160. File.unlink(File.join('data',@r.last,'style.url')) rescue nil
  161. File.unlink(File.join('data',@r.last,'document.xml')) rescue nil
  162. File.unlink(File.join('data',@r.last,'info.json')) rescue nil
  163. @a[0].send('reset')
  164. nil
  165. end
  166. end
  167. class GetFrames < Riddl::Implementation #{{{
  168. def response
  169. fname = File.join('data',@r[-2],'frames.json')
  170. if File.exists? fname
  171. infofile = File.join('data',@r[-2],'info.json')
  172. infojson = JSON::parse(File.read(infofile))
  173. #remove not used languages
  174. file = JSON::parse(File.read(fname))
  175. file["data"].each do |child|
  176. child["url"] = child["url"][infojson["lang"]]
  177. end
  178. Riddl::Parameter::Complex.new('value','application/json',JSON.dump(file))
  179. else
  180. @status = 404
  181. end
  182. end
  183. end #}}}
  184. class GetDataElements < Riddl::Implementation #{{{
  185. def response
  186. fname = File.join('data',@r[-2],'dataelements.json')
  187. if File.exists? fname
  188. Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
  189. else
  190. @status = 404
  191. end
  192. end
  193. end #}}}
  194. class GetInfo < Riddl::Implementation #{{{
  195. def response
  196. fname = File.join('data',@r[-2],'info.json')
  197. if File.exists? fname
  198. Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
  199. else
  200. @status = 404
  201. end
  202. end
  203. end #}}}
  204. class GetLangs < Riddl::Implementation #{{{
  205. def response
  206. fname = File.join('data',@r[-2],'document.xml')
  207. if File.exists? fname
  208. doc = XML::Smart.open_unprotected(fname)
  209. ndoc = XML::Smart.string('<languages/>')
  210. doc.find('//@lang').each do |e|
  211. ndoc.root.add('language',e.value)
  212. end
  213. Riddl::Parameter::Complex.new('value','text/xml',ndoc.to_s)
  214. else
  215. @status = 404
  216. end
  217. end
  218. end #}}}
  219. class SetLang < Riddl::Implementation #{{{
  220. def response
  221. fname = File.join('data',@r[-2],'info.json')
  222. if File.exists? fname
  223. infojson = JSON::parse(File.read(fname))
  224. infojson["lang"] = @p[0].value
  225. #add to langs
  226. if !infojson["langs"].include?(@p[0].value)
  227. infojson["langs"].push(@p[0].value)
  228. end
  229. File.write(fname, JSON.dump(infojson))
  230. @a[0].send('reset')
  231. nil
  232. else
  233. @status = 404
  234. end
  235. end
  236. end #}}}
  237. class GetStyle < Riddl::Implementation #{{{
  238. def response
  239. fname = File.join('data',@r[-2],'style.url')
  240. if File.exists? fname
  241. Riddl::Parameter::Complex.new('url','text/plain',File.read(fname).strip)
  242. else
  243. @status = 404
  244. end
  245. end
  246. end #}}}
  247. class GetDocument < Riddl::Implementation #{{{
  248. def response
  249. fname = File.join('data',@r[-3],'document.xml')
  250. if File.exists? fname
  251. doc = XML::Smart.open_unprotected(fname)
  252. val = nil
  253. doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
  254. val = e.text
  255. end
  256. if val
  257. Riddl::Parameter::Complex.new('url','text/plain',val.strip)
  258. else
  259. @status = 404
  260. end
  261. else
  262. @status = 404
  263. end
  264. end
  265. end #}}}
  266. class GetButton < Riddl::Implementation #{{{
  267. def response
  268. fname = File.join('data',@r[-3],'document.xml')
  269. if File.exists? fname
  270. doc = XML::Smart.open_unprotected(fname)
  271. val = nil
  272. doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
  273. val = e.attributes['button']
  274. end
  275. if val
  276. Riddl::Parameter::Complex.new('url','text/plain',val.strip)
  277. else
  278. @status = 404
  279. end
  280. else
  281. @status = 404
  282. end
  283. end
  284. end #}}}
  285. class Handler < Riddl::Implementation
  286. def response
  287. puts "handler"
  288. topic = @p[1].value
  289. event_name = @p[2].value
  290. notification = JSON.parse(@p[3].value.read)
  291. instancenr = notification['instance']
  292. content = notification['content']
  293. activity = content['activity']
  294. parameters = content['parameters']
  295. receiving = content['received']
  296. #puts instancenr
  297. #puts activity
  298. if content['values']&.any?
  299. #puts alldata['ausfuehrungen']
  300. puts "writing file"
  301. path = File.join('data',@r[0],'dataelements.json')
  302. File.write(path, JSON.dump(content['values']))
  303. end
  304. @a[0].send(@r[0])
  305. nil
  306. nil
  307. end
  308. end
  309. class SSE < Riddl::SSEImplementation #{{{
  310. def onopen
  311. signals = @a[0]
  312. signals.add self
  313. send 'started'
  314. true
  315. end
  316. def onclose
  317. signals = @a[0]
  318. signals.remove self
  319. nil
  320. end
  321. end #}}}
  322. class SSE2 < Riddl::SSEImplementation #{{{
  323. def onopen
  324. signals = @a[0]
  325. signals.add self
  326. send 'started'
  327. true
  328. end
  329. def onclose
  330. signals = @a[0]
  331. signals.remove self
  332. nil
  333. end
  334. end #}}}
  335. class Signaling # {{{
  336. def initialize
  337. @binding = []
  338. end
  339. def add(binding)
  340. @binding << binding
  341. end
  342. def remove(binding)
  343. @binding.delete(binding)
  344. end
  345. def length
  346. @binding.length
  347. end
  348. def send(value)
  349. @binding.each do |b|
  350. b.send(value)
  351. end
  352. end
  353. end #}}}
  354. server = Riddl::Server.new(File.join(__dir__,'/frames.xml'), :host => 'localhost') do |opts|
  355. accessible_description true
  356. cross_site_xhr true
  357. opts[:signals] = {}
  358. opts[:signals2] = {}
  359. parallel do
  360. loop do
  361. opts[:signals].each do |k,v|
  362. v.send('keepalive')
  363. end
  364. sleep 5
  365. end
  366. loop do
  367. opts[:signals2].each do |k,v|
  368. v.send('keepalive')
  369. end
  370. sleep 5
  371. end
  372. end
  373. on resource do
  374. on resource do |r|
  375. idx = r[:r][0]
  376. opts[:signals][idx] ||= Signaling.new
  377. opts[:signals2]["handler"] ||= Signaling.new
  378. run Get, "test" if get
  379. run Put, opts[:signals][idx] if put 'input'
  380. run NewFrame, opts[:signals][idx] if post 'frame'
  381. run NewFramePut, opts[:signals][idx] if put 'frame'
  382. run DeleteFrame, opts[:signals][idx] if post 'deleteframe'
  383. on resource 'handler' do
  384. run Handler, opts[:signals2]["handler"] if post
  385. on resource 'sse' do
  386. run SSE2, opts[:signals2]["handler"] if sse
  387. end
  388. end
  389. run Delete, opts[:signals][idx] if delete 'opa'
  390. run Delete, opts[:signals][idx] if delete 'opb'
  391. on resource 'sse' do
  392. run SSE, opts[:signals][idx] if sse
  393. end
  394. on resource 'languages' do
  395. run GetLangs if get
  396. run SetLang, opts[:signals][idx] if post 'lang'
  397. end
  398. on resource 'style.url' do
  399. run GetStyle if get
  400. end
  401. on resource 'info.json' do
  402. run GetInfo if get
  403. end
  404. on resource 'frames.json' do
  405. run GetFrames if get
  406. end
  407. on resource 'dataelements.json' do
  408. run GetDataElements if get
  409. end
  410. on resource 'buttons' do
  411. on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
  412. run GetButton if get
  413. end
  414. end
  415. on resource 'documents' do
  416. on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
  417. run GetDocument if get
  418. end
  419. end
  420. end
  421. end
  422. end.loop!