forms 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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 NewInstance < Riddl::Implementation
  27. def response
  28. entries = Dir.entries(File.join('data')).size
  29. Dir.mkdir(File.join('data',entries.to_s)) rescue nil
  30. Riddl::Parameter::Complex.new('text','text/plain',entries.to_s)
  31. end
  32. end
  33. class Index < Riddl::Implementation
  34. def response
  35. Riddl::Parameter::Complex.new('url','text/html',File.open(File.join(__dir__,'template','index.html')))
  36. end
  37. end
  38. class Builder < Riddl::Implementation
  39. def response
  40. Riddl::Parameter::Complex.new('url','text/html',File.open(File.join(__dir__,'template','builder.html')))
  41. end
  42. end
  43. class DisplayForm < Riddl::Implementation
  44. def response
  45. #Riddl::Parameter::Complex.new('url','text/html',File.open(File.join('data',@r[0],'form_min.html')))
  46. Riddl::Parameter::Complex.new('url','text/html',File.open(File.join(__dir__,'template','form.html')))
  47. end
  48. end
  49. class SaveForm < Riddl::Implementation
  50. def response
  51. File.write(File.join('data',@r[0],'form.json'),@p[0].value.read)
  52. end
  53. end
  54. class SaveHtmlForm < Riddl::Implementation
  55. def response
  56. file = File.read(File.join(__dir__,'template','form_min.html'))
  57. File.write(File.join('data',@r[0],'form_min.html'),(file.sub! '<!--FormComesHere-->', @p[0].value.read))
  58. end
  59. end
  60. class GetJson < Riddl::Implementation #{{{
  61. def response
  62. fname = File.join('data',@r[-2],'form.json')
  63. if File.exists? fname
  64. Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
  65. else
  66. @status = 404
  67. end
  68. end
  69. end #}}}
  70. class Get < Riddl::Implementation
  71. def response
  72. if @r[0] == 'test'
  73. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','test.html')))
  74. elsif @r[0] == 'menu'
  75. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','menu.html')))
  76. else
  77. Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','template.html')))
  78. end
  79. end
  80. end
  81. class Put < Riddl::Implementation
  82. def response
  83. Dir.mkdir(File.join('data',@r.last)) rescue nil
  84. File.write(File.join('data',@r.last,'style.url'),@p[0].value)
  85. File.write(File.join('data',@r.last,'document.xml'),@p[1].value)
  86. File.write(File.join('data',@r.last,'frames.json'),JSON.dump(JSON.parse('{"data":[]}')))
  87. 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 + '"]}')))
  88. File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])
  89. @a[0].send('new')
  90. nil
  91. end
  92. #def headers
  93. # Riddl::Header.new('CPEE-CALLBACK', 'true')
  94. #end
  95. end
  96. class NewFrame < Riddl::Implementation
  97. def response
  98. path = File.join('data',@r.last,'frames.json')
  99. file = File.read(path)
  100. data_hash = JSON::parse(file)
  101. data_hash["data"].each do | c |
  102. 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))
  103. data_hash["data"].delete(c)
  104. end
  105. end
  106. # example
  107. # myObj = {
  108. # "lx":3,
  109. # "ly":3,
  110. # "rx":3,
  111. # "ry":3,
  112. # "url": {
  113. # "de-at":"xyz.at",
  114. # "en-us":"xyz.com"
  115. # }
  116. # }
  117. urls = JSON::parse(@p[4].value);
  118. 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};
  119. data_hash["data"].push(hash)
  120. File.write(path, JSON.dump(data_hash))
  121. #only send active url to client
  122. infofile = File.join('data',@r.last,'info.json')
  123. infojson = JSON::parse(File.read(infofile))
  124. hash["url"] = urls[infojson["lang"]]
  125. @a[0].send(JSON.dump(hash))
  126. nil
  127. end
  128. end
  129. class NewFramePut < Riddl::Implementation
  130. def response
  131. path = File.join('data',@r.last,'frames.json')
  132. file = File.read(path)
  133. data_hash = JSON::parse(file)
  134. data_hash["data"].each do | c |
  135. 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))
  136. data_hash["data"].delete(c)
  137. end
  138. end
  139. urls = JSON::parse(@p[4].value);
  140. 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, callback: @h['CPEE_CALLBACK']};
  141. data_hash["data"].push(hash)
  142. File.write(path, JSON.dump(data_hash))
  143. #only send active url to client
  144. infofile = File.join('data',@r.last,'info.json')
  145. infojson = JSON::parse(File.read(infofile))
  146. hash["url"] = urls[infojson["lang"]]
  147. @a[0].send(JSON.dump(hash))
  148. nil
  149. end
  150. def headers
  151. Riddl::Header.new('CPEE-CALLBACK', 'true')
  152. end
  153. end
  154. def doOverlap(l1x, l1y, r1x, r1y, l2x, l2y, r2x, r2y)
  155. if l1x > r2x || l2x > r1x
  156. return false;
  157. end
  158. if l1y > r2y || l2y > r1y
  159. return false;
  160. end
  161. return true;
  162. end
  163. class Delete < Riddl::Implementation
  164. def response
  165. if cbu = File.read(File.join('data',@r.last,'callback'))
  166. send = { 'operation' => @p[0].value }
  167. case send['operation']
  168. when 'result'
  169. send['target'] = JSON::parse(@p[1].value.read)
  170. end
  171. cbu += '/' unless cbu[-1] == '/'
  172. Typhoeus.put(cbu, body: JSON::generate(send), headers: { 'content-type' => 'application/json'})
  173. end
  174. File.unlink(File.join('data',@r.last,'callback')) rescue nil
  175. File.unlink(File.join('data',@r.last,'style.url')) rescue nil
  176. File.unlink(File.join('data',@r.last,'document.xml')) rescue nil
  177. File.unlink(File.join('data',@r.last,'info.json')) rescue nil
  178. @a[0].send('reset')
  179. nil
  180. end
  181. end
  182. class GetFrames < Riddl::Implementation #{{{
  183. def response
  184. fname = File.join('data',@r[-2],'frames.json')
  185. if File.exists? fname
  186. infofile = File.join('data',@r[-2],'info.json')
  187. infojson = JSON::parse(File.read(infofile))
  188. #remove not used languages
  189. file = JSON::parse(File.read(fname))
  190. file["data"].each do |child|
  191. child["url"] = child["url"][infojson["lang"]]
  192. end
  193. Riddl::Parameter::Complex.new('value','application/json',JSON.dump(file))
  194. else
  195. @status = 404
  196. end
  197. end
  198. end #}}}
  199. class GetLangs < Riddl::Implementation #{{{
  200. def response
  201. fname = File.join('data',@r[-2],'document.xml')
  202. if File.exists? fname
  203. doc = XML::Smart.open_unprotected(fname)
  204. ndoc = XML::Smart.string('<languages/>')
  205. doc.find('//@lang').each do |e|
  206. ndoc.root.add('language',e.value)
  207. end
  208. Riddl::Parameter::Complex.new('value','text/xml',ndoc.to_s)
  209. else
  210. @status = 404
  211. end
  212. end
  213. end #}}}
  214. class SetLang < Riddl::Implementation #{{{
  215. def response
  216. fname = File.join('data',@r[-2],'info.json')
  217. if File.exists? fname
  218. infojson = JSON::parse(File.read(fname))
  219. infojson["lang"] = @p[0].value
  220. #add to langs
  221. if !infojson["langs"].include?(@p[0].value)
  222. infojson["langs"].push(@p[0].value)
  223. end
  224. File.write(fname, JSON.dump(infojson))
  225. @a[0].send('reset')
  226. nil
  227. else
  228. @status = 404
  229. end
  230. end
  231. end #}}}
  232. class GetStyle < Riddl::Implementation #{{{
  233. def response
  234. fname = File.join('data',@r[-2],'style.url')
  235. if File.exists? fname
  236. Riddl::Parameter::Complex.new('url','text/plain',File.read(fname).strip)
  237. else
  238. @status = 404
  239. end
  240. end
  241. end #}}}
  242. class GetDocument < Riddl::Implementation #{{{
  243. def response
  244. fname = File.join('data',@r[-3],'document.xml')
  245. if File.exists? fname
  246. doc = XML::Smart.open_unprotected(fname)
  247. val = nil
  248. doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
  249. val = e.text
  250. end
  251. if val
  252. Riddl::Parameter::Complex.new('url','text/plain',val.strip)
  253. else
  254. @status = 404
  255. end
  256. else
  257. @status = 404
  258. end
  259. end
  260. end #}}}
  261. class GetButton < Riddl::Implementation #{{{
  262. def response
  263. fname = File.join('data',@r[-3],'document.xml')
  264. if File.exists? fname
  265. doc = XML::Smart.open_unprotected(fname)
  266. val = nil
  267. doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
  268. val = e.attributes['button']
  269. end
  270. if val
  271. Riddl::Parameter::Complex.new('url','text/plain',val.strip)
  272. else
  273. @status = 404
  274. end
  275. else
  276. @status = 404
  277. end
  278. end
  279. end #}}}
  280. class SSE < Riddl::SSEImplementation #{{{
  281. def onopen
  282. signals = @a[0]
  283. signals.add self
  284. send 'started'
  285. end
  286. def onclose
  287. signals = @a[0]
  288. signals.remove self
  289. nil
  290. end
  291. end #}}}
  292. class Signaling # {{{
  293. def initialize
  294. @binding = []
  295. end
  296. def add(binding)
  297. @binding << binding
  298. end
  299. def remove(binding)
  300. @binding.delete(binding)
  301. end
  302. def length
  303. @binding.length
  304. end
  305. def send(value)
  306. @binding.each do |b|
  307. b.send(value)
  308. end
  309. end
  310. end #}}}
  311. server = Riddl::Server.new(File.join(__dir__,'/forms.xml'), :host => 'localhost') do |opts|
  312. accessible_description true
  313. cross_site_xhr true
  314. opts[:signals] = {}
  315. parallel do
  316. loop do
  317. opts[:signals].each do |k,v|
  318. v.send('keepalive')
  319. end
  320. sleep 5
  321. end
  322. end
  323. on resource do
  324. run Index if get
  325. run NewInstance if post
  326. on resource do |r|
  327. run DisplayForm if get
  328. on resource 'builder' do
  329. run Builder if get
  330. run SaveForm if post 'form'
  331. run SaveHtmlForm if post 'htmlform'
  332. end
  333. on resource 'json' do
  334. run GetJson if get
  335. end
  336. idx = r[:r][0]
  337. opts[:signals][idx] ||= Signaling.new
  338. run Put, opts[:signals][idx] if put 'input'
  339. run NewFrame, opts[:signals][idx] if post 'frame'
  340. run NewFramePut, opts[:signals][idx] if put 'frame'
  341. run Delete, opts[:signals][idx] if delete 'opa'
  342. run Delete, opts[:signals][idx] if delete 'opb'
  343. on resource 'sse' do
  344. run SSE, opts[:signals][idx] if sse
  345. end
  346. on resource 'languages' do
  347. run GetLangs if get
  348. run SetLang, opts[:signals][idx] if post 'lang'
  349. end
  350. on resource 'style.url' do
  351. run GetStyle if get
  352. end
  353. on resource 'frames.json' do
  354. run GetFrames if get
  355. end
  356. on resource 'buttons' do
  357. on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
  358. run GetButton if get
  359. end
  360. end
  361. on resource 'documents' do
  362. on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
  363. run GetDocument if get
  364. end
  365. end
  366. end
  367. end
  368. end.loop!