implementation.rb 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. #!/usr/bin/ruby
  2. #
  3. # This file is part of CPEE-FRAMES.
  4. #
  5. # CPEE-FRAMES 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. # CPEE-FRAMES 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. # CPEE-FRAMES (file LICENSE 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. module CPEE
  25. module Frames
  26. SERVER = File.expand_path(File.join(__dir__,'frames.xml'))
  27. # https://coderwall.com/p/atyfyq/ruby-string-to-boolean
  28. # showbutton
  29. refine String do #{{{
  30. def to_bool
  31. return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
  32. return false if self == false || self.empty? || self =~ (/(false|f|no|n|0)$/i)
  33. raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
  34. end
  35. end #}}}
  36. class GetTutorial < Riddl::Implementation #{{{
  37. def response
  38. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'ui','tutorial.html')))
  39. end
  40. end #}}}
  41. class GetAllConfigs < Riddl::Implementation #{{{
  42. def response
  43. data_dir = @a[0]
  44. databack = JSON::pretty_generate(Dir.glob(File.join(data_dir,'*')).map{|f| File.basename(f)}.sort_by{|x| x.downcase})
  45. Riddl::Parameter::Complex.new('list','application/json',databack)
  46. end
  47. end #}}}
  48. class Get < Riddl::Implementation #{{{
  49. def response
  50. if @r[0] == 'test'
  51. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'ui','test.html')))
  52. elsif @r[0] == 'menu'
  53. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'ui','menu.html')))
  54. elsif @r[0] == 'framedata'
  55. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'ui','framedata.html')))
  56. else
  57. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'ui','template.html')))
  58. end
  59. end
  60. end #}}}
  61. class InitFrame < Riddl::Implementation #{{{
  62. def response
  63. Dir.mkdir(File.join('data',@r.last)) rescue nil
  64. if !@p[0].value.to_s.empty?
  65. File.write(File.join('data',@r.last,'style.url'),@p[0].value)
  66. end
  67. File.write(File.join('data',@r.last,'frames.json'),JSON.dump(JSON.parse('{"data":[]}')))
  68. #for handler
  69. File.write(File.join('data',@r.last,'dataelements.json'),JSON.dump(JSON.parse('{"data":[]}')))
  70. 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 + '"], "document_name": "' + @p[5].value + '"}')))
  71. File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])
  72. File.write(File.join('data',@r.last,'cpeeinstance.url'),@h['CPEE_INSTANCE_URL'])
  73. @a[0].send('new')
  74. nil
  75. end
  76. end #}}}
  77. class NewFrameSet < Riddl::Implementation
  78. def response
  79. path = File.join('data',@r.last,'frames.json')
  80. file = File.read(path)
  81. data_hash = JSON::parse(file)
  82. #check if new frame overlaps others if it does, delete overlapped frames
  83. data_hash["data"].each do | c |
  84. if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[1].value.to_i, @p[2].value.to_i, (@p[1].value.to_i + @p[3].value.to_i - 1), (@p[2].value.to_i + @p[4].value.to_i - 1))
  85. data_hash["data"].delete(c)
  86. end
  87. end
  88. #check if url is set
  89. if @p[7].value != ""
  90. urls = JSON::parse(@p[7].value);
  91. if @p[8].value == ""
  92. hash = {lx: @p[1].value.to_i, ly: @p[2].value.to_i, rx: (@p[1].value.to_i + @p[3].value.to_i - 1), ry: (@p[2].value.to_i + @p[4].value.to_i - 1), url: urls, showbutton: @p[5].value, style: @p[6].value, default: "{}", callback: @h['CPEE_CALLBACK']};
  93. else
  94. hash = {lx: @p[1].value.to_i, ly: @p[2].value.to_i, rx: (@p[1].value.to_i + @p[3].value.to_i - 1), ry: (@p[2].value.to_i + @p[4].value.to_i - 1), url: urls, showbutton: @p[5].value, style: @p[6].value, default: JSON::parse(@p[8].value), callback: @h['CPEE_CALLBACK']};
  95. end
  96. data_hash["data"].push(hash)
  97. File.write(path, JSON.dump(data_hash))
  98. #only send active url to client
  99. infofile = File.join('data',@r.last,'info.json')
  100. infojson = JSON::parse(File.read(infofile))
  101. hash["url"] = urls.find{ |h| h['lang'] == infojson["lang"]}['url']
  102. @a[0].send(JSON.dump(hash))
  103. else
  104. File.write(path, JSON.dump(data_hash))
  105. hash = {lx: @p[1].value.to_i, ly: @p[2].value.to_i, rx: (@p[1].value.to_i + @p[3].value.to_i - 1), ry: (@p[2].value.to_i + @p[4].value.to_i - 1), url: "empty", showbutton: @p[5].value, style: @p[6].value, default: "{}", callback: @h['CPEE_CALLBACK']};
  106. @a[0].send(JSON.dump(hash))
  107. end
  108. nil
  109. end
  110. end
  111. class NewFrameWait < Riddl::Implementation
  112. def response
  113. path = File.join('data',@r.last,'frames.json')
  114. file = File.read(path)
  115. data_hash = JSON::parse(file)
  116. #check if new frame overlaps others if it does, delete overlapped frames
  117. data_hash["data"].each do | c |
  118. if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[1].value.to_i, @p[2].value.to_i, (@p[1].value.to_i + @p[3].value.to_i - 1), (@p[2].value.to_i + @p[4].value.to_i - 1))
  119. data_hash["data"].delete(c)
  120. end
  121. end
  122. #check if url is set
  123. if @p[7].value != ""
  124. urls = JSON::parse(@p[7].value);
  125. if @p[8].value == ""
  126. hash = {lx: @p[1].value.to_i, ly: @p[2].value.to_i, rx: (@p[1].value.to_i + @p[3].value.to_i - 1), ry: (@p[2].value.to_i + @p[4].value.to_i - 1), url: urls, showbutton: @p[5].value, style: @p[6].value, default: "{}", callback: @h['CPEE_CALLBACK']};
  127. else
  128. hash = {lx: @p[1].value.to_i, ly: @p[2].value.to_i, rx: (@p[1].value.to_i + @p[3].value.to_i - 1), ry: (@p[2].value.to_i + @p[4].value.to_i - 1), url: urls, showbutton: @p[5].value, style: @p[6].value, default: JSON::parse(@p[8].value), callback: @h['CPEE_CALLBACK']};
  129. end
  130. data_hash["data"].push(hash)
  131. File.write(path, JSON.dump(data_hash))
  132. #only send active url to client
  133. infofile = File.join('data',@r.last,'info.json')
  134. infojson = JSON::parse(File.read(infofile))
  135. hash["url"] = urls.find{ |h| h['lang'] == infojson["lang"]}['url']
  136. File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])
  137. @a[0].send(JSON.dump(hash))
  138. else
  139. File.write(path, JSON.dump(data_hash))
  140. hash = {lx: @p[1].value.to_i, ly: @p[2].value.to_i, rx: (@p[1].value.to_i + @p[3].value.to_i - 1), ry: (@p[2].value.to_i + @p[4].value.to_i - 1), url: "empty", showbutton: @p[5].value, style: @p[6].value, default: "{}", callback: @h['CPEE_CALLBACK']};
  141. File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])
  142. @a[0].send(JSON.dump(hash))
  143. Typhoeus.put(@h['CPEE_CALLBACK'], body: "No Frame Set")
  144. end
  145. nil
  146. end
  147. def headers
  148. Riddl::Header.new('CPEE-CALLBACK', 'true')
  149. end
  150. end
  151. class DeleteFrame < Riddl::Implementation
  152. def response
  153. path = File.join('data',@r.last,'frames.json')
  154. file = File.read(path)
  155. data_hash = JSON::parse(file)
  156. data_hash["data"].each do | c |
  157. 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))
  158. data_hash["data"].delete(c)
  159. end
  160. end
  161. File.write(path, JSON.dump(data_hash))
  162. end
  163. end
  164. def doOverlap(l1x, l1y, r1x, r1y, l2x, l2y, r2x, r2y)
  165. if l1x > r2x || l2x > r1x
  166. return false;
  167. end
  168. if l1y > r2y || l2y > r1y
  169. return false;
  170. end
  171. return true;
  172. end
  173. class Delete < Riddl::Implementation
  174. def response
  175. pp "in delete"
  176. if cbu = File.read(File.join('data',@r.last,'callback'))
  177. pp "XYZ"
  178. send = { 'operation' => @p[0].value }
  179. case send['operation']
  180. when 'result'
  181. send['target'] = JSON::parse(@p[1].value.read)
  182. end
  183. cbu += '/' unless cbu[-1] == '/'
  184. pp "Sending"
  185. Typhoeus.put(cbu, body: JSON::generate(send), headers: { 'content-type' => 'application/json'})
  186. end
  187. #File.unlink(File.join('data',@r.last,'callback')) rescue nil
  188. #File.unlink(File.join('data',@r.last,'cpeeinstance.url')) rescue nil
  189. #File.unlink(File.join('data',@r.last,'style.url')) rescue nil
  190. #File.unlink(File.join('data',@r.last,'document.xml')) rescue nil
  191. #File.unlink(File.join('data',@r.last,'info.json')) rescue nil
  192. @a[0].send('reset')
  193. nil
  194. end
  195. end
  196. class GetFrames < Riddl::Implementation #{{{
  197. def response
  198. fname = File.join('data',@r[-2],'frames.json')
  199. if File.exists? fname
  200. infofile = File.join('data',@r[-2],'info.json')
  201. infojson = JSON::parse(File.read(infofile))
  202. #remove not used languages
  203. file = JSON::parse(File.read(fname))
  204. file["data"].each do |child|
  205. child["url"] = child["url"].find{ |h| h['lang'] == infojson["lang"]}['url']
  206. end
  207. Riddl::Parameter::Complex.new('value','application/json',JSON.dump(file))
  208. else
  209. @status = 404
  210. end
  211. end
  212. end #}}}
  213. class SetDataElements < Riddl::Implementation #{{{
  214. def response
  215. savejson = @p.map { |o| Hash[o.name, o.value] }.to_json
  216. path = File.join('data',@r[0],'dataelements.json')
  217. File.write(path, savejson)
  218. #puts xyz
  219. #puts JSON.pretty_generate(@p.to_json)
  220. #puts @p.length()
  221. #puts @p[0].name
  222. #puts @p[0].value
  223. #fname = File.join('data',@r[-2],'dataelements.json')
  224. #if File.exists? fname
  225. # Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
  226. #else
  227. # @status = 404
  228. #end
  229. end
  230. end #}}}
  231. class GetDataElements < Riddl::Implementation #{{{
  232. def response
  233. fname = File.join('data',@r[-2],'dataelements.json')
  234. if File.exists? fname
  235. Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
  236. else
  237. @status = 404
  238. end
  239. end
  240. end #}}}
  241. class GetInfo < Riddl::Implementation #{{{
  242. def response
  243. fname = File.join('data',@r[-2],'info.json')
  244. if File.exists? fname
  245. Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
  246. else
  247. @status = 404
  248. end
  249. end
  250. end #}}}
  251. class GetLangs < Riddl::Implementation #{{{
  252. def response
  253. fname = File.join('data',@r[-2],'info.json')
  254. if File.exists? fname
  255. infojson = JSON::parse(File.read(fname))
  256. Riddl::Parameter::Complex.new('value','application/json',infojson["langs"])
  257. else
  258. @status = 404
  259. end
  260. end
  261. end #}}}
  262. class SetLang < Riddl::Implementation #{{{
  263. def response
  264. fname = File.join('data',@r[-2],'info.json')
  265. if File.exists? fname
  266. infojson = JSON::parse(File.read(fname))
  267. infojson["lang"] = @p[0].value
  268. #add to langs
  269. if !infojson["langs"].include?(@p[0].value)
  270. infojson["langs"].push(@p[0].value)
  271. end
  272. File.write(fname, JSON.dump(infojson))
  273. @a[0].send('reset')
  274. nil
  275. else
  276. @status = 404
  277. end
  278. end
  279. end #}}}
  280. class GetStyle < Riddl::Implementation #{{{
  281. def response
  282. fname = File.join('data',@r[-2],'style.url')
  283. if File.exists? fname
  284. Riddl::Parameter::Complex.new('url','text/plain',File.read(fname).strip)
  285. else
  286. @status = 404
  287. end
  288. end
  289. end #}}}
  290. class GetCpeeInstance < Riddl::Implementation #{{{
  291. def response
  292. fname = File.join('data',@r[-2],'cpeeinstance.url')
  293. if File.exists? fname
  294. Riddl::Parameter::Complex.new('url','text/plain',File.read(fname).strip)
  295. else
  296. @status = 404
  297. end
  298. end
  299. end #}}}
  300. class OutputTest < Riddl::Implementation #{{{
  301. def response
  302. puts "Test"
  303. end
  304. end #}}}
  305. class Handler < Riddl::Implementation
  306. def response
  307. topic = @p[1].value
  308. event_name = @p[2].value
  309. notification = JSON.parse(@p[3].value.read)
  310. instancenr = notification['instance']
  311. content = notification['content']
  312. activity = content['activity']
  313. parameters = content['parameters']
  314. receiving = content['received']
  315. #puts instancenr
  316. #puts activity
  317. puts content['values']
  318. if content['values']&.any?
  319. #puts alldata['ausfuehrungen']
  320. puts "writing file"
  321. path = File.join('data',@r[0],'dataelements.json')
  322. File.write(path, JSON.dump(content['values']))
  323. end
  324. @a[0].send(@r[0])
  325. nil
  326. end
  327. end
  328. class SSE < Riddl::SSEImplementation #{{{
  329. def onopen
  330. signals = @a[0]
  331. signals.add self
  332. send 'started'
  333. true
  334. end
  335. def onclose
  336. signals = @a[0]
  337. signals.remove self
  338. nil
  339. end
  340. end #}}}
  341. class SSE2 < Riddl::SSEImplementation #{{{
  342. def onopen
  343. signals = @a[0]
  344. signals.add self
  345. send 'started'
  346. true
  347. end
  348. def onclose
  349. signals = @a[0]
  350. signals.remove self
  351. nil
  352. end
  353. end #}}}
  354. class Signaling # {{{
  355. def initialize
  356. @binding = []
  357. end
  358. def add(binding)
  359. @binding << binding
  360. end
  361. def remove(binding)
  362. @binding.delete(binding)
  363. end
  364. def length
  365. @binding.length
  366. end
  367. def send(value)
  368. @binding.each do |b|
  369. b.send(value)
  370. end
  371. end
  372. end #}}}
  373. def self::implementation(opts)
  374. opts[:signals] = {}
  375. opts[:signals2] = {}
  376. opts[:data_dir] ||= File.expand_path(File.join(__dir__,'data'))
  377. Proc.new do
  378. parallel do
  379. loop do
  380. opts[:signals].each do |k,v|
  381. v.send('keepalive')
  382. end
  383. opts[:signals2].each do |k,v|
  384. v.send('keepalive')
  385. end
  386. sleep 5
  387. end
  388. end
  389. on resource do
  390. run GetTutorial if get
  391. on resource 'getConfigs' do
  392. run GetAllConfigs, opts[:data_dir] if get
  393. end
  394. on resource do |r|
  395. idx = r[:r][0]
  396. opts[:signals][idx] ||= Signaling.new
  397. opts[:signals2]["handler"] ||= Signaling.new
  398. run Get, "test" if get
  399. run InitFrame, opts[:signals][idx] if post 'input'
  400. run NewFrameSet, opts[:signals][idx] if put 'sframe'
  401. run NewFrameWait, opts[:signals][idx] if put 'wframe'
  402. run DeleteFrame, opts[:signals][idx] if post 'deleteframe'
  403. on resource 'handler' do
  404. run Handler, opts[:signals2]["handler"] if post
  405. on resource 'sse' do
  406. run SSE2, opts[:signals2]["handler"] if sse
  407. end
  408. end
  409. run Delete, opts[:signals][idx] if delete 'opa'
  410. run Delete, opts[:signals][idx] if delete 'opb'
  411. run Delete, opts[:signals][idx] if delete 'opc'
  412. on resource 'sse' do
  413. run SSE, opts[:signals][idx] if sse
  414. end
  415. on resource 'languages' do
  416. run GetLangs if get
  417. run SetLang, opts[:signals][idx] if post 'lang'
  418. end
  419. on resource 'style.url' do
  420. run GetStyle if get
  421. end
  422. on resource 'cpeeinstance.url' do
  423. run GetCpeeInstance if get
  424. end
  425. on resource 'info.json' do
  426. run GetInfo if get
  427. end
  428. on resource 'frames.json' do
  429. run GetFrames if get
  430. end
  431. on resource 'test' do
  432. run OutputTest if put
  433. end
  434. on resource 'dataelements.json' do
  435. run SetDataElements if post
  436. run GetDataElements if get
  437. end
  438. end
  439. end
  440. end
  441. end
  442. end
  443. end