local cqueues = require'cqueues' local signal = require'cqueues.signal' local class = require'r.class' local rtable = require'r.table' local pprint = require'pprint' local config = require'config' local Thing = require'thing' local Log = require 'log' local Store = require 'store' local pylon_classes = { irc = require'irc.pylon', xmpp = require'xmpp.pylon', discord = require'discord.pylon', nanochat = require'nanochat.pylon', quaddle = require'quaddle.pylon', } local Wilson = class() function Wilson.make(cls, conf) local tlc = conf.top.level or {} local self = setmetatable({ pylons={}, busses={}, cq = cqueues.new(), log = Log('(toplevel)',tlc.loglevel or 'info'), store = Store(), }, Wilson) for name, pylon_conf in pairs(conf.pylon) do pylon_conf.name = name local pylon_class = pylon_classes[pylon_conf.type] assert(pylon_class, "no such pylon type "..pylon_conf.type) self.pylons[name] = pylon_class:make(self, pylon_conf) self.log("constructed pylon",name,pylon_conf.type) end for name, bus_conf in pairs(conf.bus) do local bus = {} for _, item in ipairs(bus_conf) do local pylon_name, channel_id = item[1], item[2] local pylon = self.pylons[pylon_name] assert(pylon,"no such pylon named "..pylon_name) table.insert(bus, Thing(pylon, channel_id)) end self.log("constructed bus ",name,'with '..#bus_conf..' channels') self.busses[name] = bus end return self end function Wilson.add_channels_for(self, pylon) if not pylon.channels then return end for busname, bus in pairs(busses) do for _, channel in ipairs(bus) do if channel.pylon == pylon and not pylon.channels[channel.id] then pylon:add_channel(channel) end end end end function Wilson.load_config(self, conf) local pneed,phave = rtable.keydiff(conf.pylon, self.pylons) if next(phave) then error"don't know how to remove pylons yet" end for pname,p in pairs(conf.pylons) do if self.pylons[pname] then for k,v in pairs(p) do if v ~= self.pylons[pname][k] then error("different field "..k.." on "..pname) end end end end -- loop thru buses, form list of channels we need to care about -- don't know how to remove channels yet -- then for pylons need to be constructed, pass them the list of channels to connect to -- verify pylon names are valid here too local ch4p = {} for k in pairs(conf.pylon) do ch4p[k] = {} end for busname,b in pairs(conf.bus) do for i,item in ipairs(b) do local pname,cid = item[1],item[2] if not ch4p[pname] then error("no such pylon "..pname) end table.insert(ch4p[pname], cid) end end for pname,p in pairs(conf.pylon) do p.name = pname if self.pylons[pname] then self.pylons[pname]:sync_channels(ch4p[pname]) else local cls = assert(pylon_class[p.type], "no such pylon type "..p.type) self.pylon[name] = cls(self, p, ch4p[pname]) end end end -- find bus containing given channel function Wilson._find_bus(self, channel) for name, bus in pairs(self.busses) do for _, q in ipairs(bus) do if q == channel then return bus end end end self.log:warn("unfound bus for",channel) end function Wilson.deliver(self, message) local bus = self:_find_bus(assert(message.channel)) if bus then self.store:store(message) for _, dest_channel in ipairs(bus) do if message.channel ~= dest_channel then self.log:debug(message.channel, "-->", dest_channel, message) dest_channel.pylon:post(dest_channel, message) end end end end function Wilson.run(self) for pylonname, pylon in pairs(self.pylons) do self.cq:wrap(pylon.run, pylon) self.log("now running pylon", pylonname) end self.cq:wrap(self.store.run, self.store) self.cq:wrap(self.hup, self) self.log:loop(self.cq) end function Wilson.hup(self) signal.block(signal.SIGHUP) local sl = signal.listen(signal.SIGHUP) while true do local s = sl:wait() self:reload_config() end end local config_file = assert(io.open("wilson.ini","r")) local conf = config.parse(config_file) config_file:close() local wilson = Wilson:make(conf) wilson:run()