#!/usr/bin/ruby # # This file is part of centurio.work/ing/commands. # # centurio.work/ing/commands is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # centurio.work/ing/commands is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along with # centurio.work/ing/commands (file COPYING in the main directory). If not, see # . require 'rubygems' require 'json' require 'xml/smart' require 'riddl/server' require 'fileutils' require 'typhoeus' require 'sqlite3' class ConfigSites < Riddl::Implementation def response Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','index.html'))) end end class GetAllConfigs < Riddl::Implementation def response Dir.chdir( __dir__ + '/data') databack = JSON::pretty_generate(Dir.glob('*/').sort_by{|x| x.downcase}); Dir.chdir( __dir__) Riddl::Parameter::Complex.new('list','application/json',databack) end end class GetAllVisus < Riddl::Implementation def response Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'data', @r[-2],'visus.html'))) end end class Get < Riddl::Implementation def response #Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','template.html'))) Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'data', @r.last,'index.html'))) end end class GetAllData < Riddl::Implementation def response fname = File.join('data',@r[-2],'data.db') db = SQLite3::Database.open fname if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty? #db.execute "CREATE TABLE IF NOT EXISTS Entries(seriennummer TEXT PRIMARY KEY, produktcode TEXT, activity TEXT, cpeeInstance INTEGER)" db.results_as_hash = true alldata = {}; alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC") Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata)) else Riddl::Parameter::Complex.new('value','application/json',JSON.dump({})) end end end class GetNData < Riddl::Implementation def response fname = File.join('data',@r[-2],'data.db') db = SQLite3::Database.open fname if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty? db.results_as_hash = true alldata = {}; alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1 OFFSET " + @r[-1]) Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata)) else Riddl::Parameter::Complex.new('value','application/json',JSON.dump({})) end end end class DeleteNData < Riddl::Implementation def response fname = File.join('data',@r[-2],'data.db') db = SQLite3::Database.open fname if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty? db.results_as_hash = true alldata = {}; alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1 OFFSET " + @r[-1]) if alldata["data"] != [] id = alldata["data"][0]["__orderID__"].to_s db.execute("DELETE FROM Entries WHERE __orderID__ = '" + id + "'") end else nil end @a[0].send("reset") end end # used when executed from e.g. website, in such a case no cpee instance is given class AddEntry < Riddl::Implementation def response newEntry = JSON.parse(@p[0].value.read) fname = File.join('data',@r.last,'data.db') db = SQLite3::Database.open fname # in case primary key is given # PRIMARY KEY Momentan noch nicht implementiert if newEntry.has_key?("pkvalue") db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, pkvalue TEXT, cpeeInstance INTEGER)" db.execute("INSERT OR REPLACE INTO Entries (alldata, pkvalue, cpeeInstance) VALUES (?,?,?)",JSON.dump(newEntry["alldata"]), newEntry["pkvalue"], "Direkt") hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: JSON.dump(newEntry["alldata"]), pkvalue: newEntry["pkvalue"], cpeeInstance: "Direkt"}; else db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, cpeeInstance INTEGER)" db.execute("INSERT OR REPLACE INTO Entries (alldata, cpeeInstance) VALUES (?,?)",JSON.dump(newEntry["alldata"]), "Direkt") hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: JSON.dump(newEntry["alldata"]), cpeeInstance: "Direkt"}; end @a[0].send(JSON.dump(hash)) nil end end class Put < Riddl::Implementation def self::putentry(r, p, h) Dir.mkdir(File.join('data',r.last)) rescue nil Dir.mkdir(File.join('data',r.last, "js")) rescue nil Dir.mkdir(File.join('data',r.last, "visus")) rescue nil #prevent overriding ui and index in case user changed something there if !File.file?(File.join('data',r.last,'js/ui.js')) File.write(File.join('data',r.last,'js/ui.js'),File.open("defaultContent/js/ui.js").read) end if !File.file?(File.join('data',r.last,'index.html')) file = File.open("defaultContent/index.html").read newfile = file.gsub("!replaceThisString!", r.last) File.write(File.join('data',r.last,'index.html'), newfile) end #prevent overriding visualization if !File.file?(File.join('data',r.last,'visus.html')) File.write(File.join('data',r.last,'visus.html'),File.open("defaultContent/visus.html").read) end fname = File.join('data',r.last,'data.db') db = SQLite3::Database.open fname #in case primary key is given if p[1] != nil keyfunction = "" if p[2] != nil keyfunction = p[2].value end db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, pkvalue TEXT " + keyfunction + ", cpeeInstance INTEGER)" db.execute("INSERT OR REPLACE INTO Entries (alldata, pkvalue, cpeeInstance) VALUES (?,?,?)",p[0].value, p[1].value, h['CPEE_INSTANCE']) hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: p[0].value, pkvalue: p[1].value, cpeeInstance: h['CPEE_INSTANCE']}; else db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, cpeeInstance INTEGER)" db.execute("INSERT OR REPLACE INTO Entries (alldata, cpeeInstance) VALUES (?,?)",p[0].value, h['CPEE_INSTANCE']) hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: p[0].value, cpeeInstance: h['CPEE_INSTANCE']}; end hash; end def response hash = Put::putentry(@r, @p, @h) @a[0].send(JSON.dump(hash)) nil end end class PutPrio < Riddl::Implementation def response hash = Put::putentry(@r, @p, @h) #add entry hash = Move::moveentry(hash[:__orderID__][0], 0,@r) #move to front @a[0].send("reset") nil end end class PutPrioN < Riddl::Implementation def response #move all back fname = File.join('data',@r.last,'data.db') db = SQLite3::Database.open fname #Updating entries directly + X would result in unique constraint failed #Therefore updates first to negative values then *-1 db.execute "UPDATE Entries SET __orderID__= 0 - (__orderID__ + " + @p[1].value + ")" db.execute "UPDATE Entries SET __orderID__= __orderID__ *-1" #Add entries i = 0 while i < @p[1].value.to_i do #db.execute("INSERT OR REPLACE INTO Entries (__orderID__, alldata, cpeeInstance) VALUES (?,?,?)",i, @p[0].value, @h['CPEE_INSTANCE']) #in case primary key is given if @p[2] != nil db.execute("INSERT OR REPLACE INTO Entries (__orderID__, alldata, pkvalue, cpeeInstance) VALUES (?,?,?,?)",i, @p[0].value, @p[2].value, @h['CPEE_INSTANCE']) else db.execute("INSERT OR REPLACE INTO Entries (__orderID__, alldata, cpeeInstance) VALUES (?,?,?)",i, @p[0].value, @h['CPEE_INSTANCE']) end i +=1 end @a[0].send("reset") nil end end class Move < Riddl::Implementation def self::moveentry(from, to,r) fname = File.join('data',r.last,'data.db') db = SQLite3::Database.open fname #getMaxID result = db.execute "SELECT MAX(__orderID__) FROM Entries" if(result[0][0] == nil) maxImgId = 0 else maxImgId = result[0][0] +1 end i = 0 if to < from while to < from do #nutze max ID zum tauschen db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [maxImgId, from - 1]) db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [from - 1, from]) db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [from, maxImgId]) from -=1 end elsif from < to while from < to do #nutze max ID zum tauschen db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [maxImgId, from + 1]) db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [from + 1, from]) db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [from, maxImgId]) from +=1 end end nil end def response movement = JSON.parse(@p[0].value.read) from = movement["From"] to = movement["To"] hash = Move::moveentry(from, to,@r) nil end end class Update < Riddl::Implementation def response fname = File.join('data',@r.last,'data.db') db = SQLite3::Database.open fname updateData = JSON.parse(@p[0].value.read) orderID = updateData["ID"] alldata = JSON.generate(updateData["alldata"]) db.execute("UPDATE Entries SET alldata = ? WHERE __orderID__ = ?", [alldata, orderID]) nil end end class Storesortedtable < Riddl::Implementation def response fname = File.join('data',@r.last,'data.db') db = SQLite3::Database.open fname #invert keys from + to - to prevent duplicate primary keys when reordering db.execute("UPDATE Entries SET __orderID__ = __orderID__ *-1 -1") updateData = JSON.parse(@p[0].value.read) updateData.each_with_index do |child, idx| db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [idx, child.to_i*-1 -1]) end #security #in case there are negative entries remaining add them with positive value to the end result = db.execute "SELECT MAX(__orderID__) FROM Entries" if(result[0][0] == nil) maxorderID = 0 else maxorderID = result[0][0] +1 end resultpattern = db.execute "SELECT * FROM Entries WHERE __orderID__ < 0" resultpattern.each do |row2| db.execute("UPDATE Entries SET __orderID__ = ? WHERE __orderID__ = ?", [maxorderID, row2[0]]); maxorderID = maxorderID +1; end @a[0].send("reset") nil end end class Delete < Riddl::Implementation def response fname = File.join('data',@r.last,'data.db') db = SQLite3::Database.open fname alldata = @p[0].value.read db.execute("DELETE FROM Entries WHERE alldata = '" + alldata + "'") nil end end class DeleteID < Riddl::Implementation def self::remove(dbfolder, id) fname = File.join('data',dbfolder,'data.db') db = SQLite3::Database.open fname db.execute("DELETE FROM Entries WHERE __orderID__ = '" + id.to_s + "'") end def response DeleteID::remove(@r.last, @p[0].value.read) nil end end class DeleteIDDirect < Riddl::Implementation def response DeleteID::remove(@r.last, @p[0].value) nil end end class DeletePK < Riddl::Implementation def response fname = File.join('data',@r.last,'data.db') db = SQLite3::Database.open fname pkvalue = @p[0].value db.execute("DELETE FROM Entries WHERE pkvalue = '" + pkvalue + "'") nil end end class SearchPK < Riddl::Implementation def response fname = File.join('data',@r[-4],'data.db') db = SQLite3::Database.open fname pkvalue = @r.last if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty? db.results_as_hash = true alldata = {}; alldata["data"] = (db.execute "Select * from Entries WHERE pkvalue = '" + pkvalue + "'") Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata)) else Riddl::Parameter::Complex.new('value','application/json',JSON.dump({})) end end end class GetNext < Riddl::Implementation def response fname = File.join('data',@r[-2],'data.db') db = SQLite3::Database.open fname #get value db.results_as_hash = true alldata = {}; alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1")[0] Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata)) end end class DeleteNext < Riddl::Implementation def response fname = File.join('data',@r[-2],'data.db') db = SQLite3::Database.open fname #get value db.results_as_hash = true alldata = {}; alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1")[0] #remove from DB DeleteID::remove(@r[-2], alldata["data"]["__orderID__"]) @a[0].send("reset") Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata)) end end class DeleteAll < Riddl::Implementation def response File.unlink(File.join('data',@r.last,'data.db')) rescue nil nil end end class SSE < Riddl::SSEImplementation #{{{ def onopen signals = @a[0] signals.add self send 'started' true end def onclose signals = @a[0] signals.remove self nil end end #}}} class Signaling # {{{ def initialize @binding = [] end def add(binding) @binding << binding end def remove(binding) @binding.delete(binding) end def length @binding.length end def send(value) @binding.each do |b| b.send(value) end end end #}}} server = Riddl::Server.new(File.join(__dir__,'/dashboard.xml'), :host => 'localhost') do |opts| accessible_description true cross_site_xhr true opts[:signals] = {} parallel do loop do opts[:signals].each do |k,v| v.send('keepalive') end sleep 5 end end on resource do run ConfigSites if get on resource 'getconfigs' do run GetAllConfigs if get end on resource do |r| idx = r[:r][0] opts[:signals][idx] ||= Signaling.new run Get if get run Put, opts[:signals][idx] if put 'input' run PutPrio, opts[:signals][idx] if put 'inputprio' run PutPrioN, opts[:signals][idx] if put 'inputNprio' run Move if put 'move' run Update if put 'update' run Storesortedtable, opts[:signals][idx] if put 'storesortedtable' run AddEntry, opts[:signals][idx] if post 'directADD' run Delete if delete 'deleteMsg' run DeleteID if delete 'deleteByID' run DeletePK if delete 'deletePK' run DeleteAll if delete 'deleteAll' run DeleteIDDirect if delete on resource '\d+' do run GetNData, opts if get run DeleteNData, opts[:signals][idx] if delete end on resource 'sse' do run SSE, opts[:signals][idx] if sse end on resource 'data.db' do run GetAllData if get end on resource 'search' do on resource 'PK' do on resource '.*' do run SearchPK if get end end end on resource 'visus' do run GetAllVisus if get end on resource 'next' do run GetNext, opts[:signals][idx] if get run DeleteNext, opts[:signals][idx] if delete end end end end.loop!