Initial implementation of command interface

This commit is contained in:
Nedko Arnaudov 2010-07-04 15:53:58 +03:00
parent 84f968dbf0
commit 17d172f7cc
6 changed files with 427 additions and 110 deletions

395
irc.lua
View File

@ -1,4 +1,4 @@
-- -*- Mode: Lua; indent-tabs-mode: t; lua-indent-level: 2 -*-
-- -*- Mode: Lua; indent-tabs-mode: nil; lua-indent-level: 2 -*-
-- PERsonal MESsage HUb (permshu)
--
-- Copyright (C) 2010 Nedko Arnaudov <nedko@arnaudov.name>
@ -18,6 +18,9 @@
-- or write to the Free Software Foundation, Inc.,
-- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
require 'misc'
require 'protocols'
module('irc', package.seeall)
local function parse_and_consume(buffer, regexp)
@ -30,6 +33,90 @@ local function parse_and_consume(buffer, regexp)
return rest, a1, a2, a3, a4
end
local function process_raw_msg(command_handlers, raw_msg)
-- print('----receive----' .. tostring(raw_msg))
local prefix
local command
local rest = raw_msg
-- prefix
rest, prefix = parse_and_consume(rest, "^:([^ ]*) *")
--if prefix then print(("prefix: '%s'"):format(prefix)) end
--if rest then print(("rest: '%s'"):format(rest)) end
-- command and params
rest, command, params = parse_and_consume(rest, "^([^ ]*) *(.*)")
--if command then print(("command: '%s'"):format(command)) end
--if params then print(("params: '%s'"):format(params)) end
assert(not rest)
params_table = {}
local param
while params do
params, param = parse_and_consume(params, '^:(.*)')
if param then table.insert(params_table, param) break end
params, param = parse_and_consume(params, '^ *([^ \r\n]+) *')
table.insert(params_table, param)
end
local ret
if command_handlers[command] then
ret = command_handlers[command](prefix, command, params_table)
elseif command_handlers[''] then
ret = command_handlers[''](prefix, command, params_table)
else
-- print('----receive----' .. tostring(raw_msg))
-- if prefix then print(("prefix: '%s'"):format(prefix)) end
-- print(("command: '%s'"):format(command))
-- for _, param in pairs(params_table) do print('[' .. param .. ']') end
msg = 'Unknown msg [' .. command .. ']'
if prefix then msg = msg .. ', prefix: [' .. prefix .. ']' end
if params_table then
msg = msg .. ', params:'
for _,param in pairs(params_table) do
msg = msg .. ' [' .. param .. ']'
end
end
print(msg)
end
return ret
end
local function receive(peer, command_handlers)
local buffer
while true do
local raw_msg
local data, err = peer.receive(4000)
-- print('[' .. tostring(data) .. ']')
-- print('[' .. tostring(err) .. ']')
if not data then return err end
if buffer then
buffer = buffer .. data
else
buffer = data
end
-- print('---> [' .. tostring(buffer) .. ']')
while buffer do
buffer, raw_msg = parse_and_consume(buffer, '^([^\r\n]*)\r\n')
-- print('[' .. tostring(buffer) .. ']')
-- print('[' .. tostring(raw_msg) .. ']')
if not raw_msg then break end
if process_raw_msg(command_handlers, raw_msg) then return end
end
end
assert(false)
end
local function send_to_peer(peer, msg)
-- print('-----send------' .. msg)
peer.send(msg .. "\r\n")
end
function connect(args)
assert(args.host)
assert(args.nick)
@ -39,13 +126,11 @@ function connect(args)
local nick = args.nick
local username = args.username or nick
local realname = args.realname or nick
local join = args.join
local peer
local function send(msg)
--print('-----send------' .. msg)
peer.send(msg .. "\r\n")
end
local function send(msg) send_to_peer(peer, msg) end
local function print_notice(msg, prefix)
if msg then
@ -131,92 +216,242 @@ function connect(args)
command_handlers['372'] = motd_reply -- MOTD middle
command_handlers['376'] = motd_reply -- MOTD end
local function process_raw_msg(raw_msg)
--print('----receive----' .. tostring(raw_msg))
local prefix
local command
local rest = raw_msg
-- prefix
rest, prefix = parse_and_consume(rest, "^:([^ ]*) *")
--if prefix then print(("prefix: '%s'"):format(prefix)) end
--if rest then print(("rest: '%s'"):format(rest)) end
-- command and params
rest, command, params = parse_and_consume(rest, "^([^ ]*) *(.*)")
--if command then print(("command: '%s'"):format(command)) end
--if params then print(("params: '%s'"):format(params)) end
assert(not rest)
params_table = {}
local param
while params do
params, param = parse_and_consume(params, '^:(.*)')
if param then table.insert(params_table, param) break end
params, param = parse_and_consume(params, '^ *([^ \r\n]+) *')
table.insert(params_table, param)
end
if command_handlers[command] then
command_handlers[command](prefix, command, params_table)
else
print('----receive----' .. tostring(raw_msg))
-- if prefix then print(("prefix: '%s'"):format(prefix)) end
-- print(("command: '%s'"):format(command))
-- for _, param in pairs(params_table) do print('[' .. param .. ']') end
end
return true
end
peer, err = remotes.connect_tcp(host, port)
if not peer then return err end
if not peer then return nil, err end
local disconnect_function = nil
remotes.add_thread(function()
send(("NICK %s"):format(nick))
send(("USER %s %s %s :%s"):format(username, peer.get_local_ip(), host, realname))
send(("NICK %s"):format(nick))
send(("USER %s %s %s :%s"):format(username, peer.get_local_ip(), host, realname))
if join then send("JOIN " .. join) end
err = receive(peer, command_handlers)
-- print(err)
peer.close()
if disconnect_function then
-- print("Calling disconnect callback")
disconnect_function()
end
peer = nil
end)
return function(disconnect_function_param)
print("Disconnecting from " .. host)
peer.close() -- this will break the receive loop
disconnect_function = disconnect_function_param
end
end
local buffer
while true do
local raw_msg
local data, err = peer.receive(4000)
-- print('[' .. tostring(data) .. ']')
-- print('[' .. tostring(err) .. ']')
if not data then break end
local descriptor = {
name = 'IRC',
required_params = {
host="IP address or a host name if IRC server",
nick="Nickname",
},
optional_params = {
port="TCP port, defaults to 6667",
username="Username, defaults to nick",
realname="Real name, defaults to nick",
},
connect=connect,
}
if buffer then
buffer = buffer .. data
else
buffer = data
end
protocols.register(descriptor)
-- print('---> [' .. tostring(buffer) .. ']')
while buffer do
buffer, raw_msg = parse_and_consume(buffer, '^([^\r\n]*)\r\n')
-- print('[' .. tostring(buffer) .. ']')
-- print('[' .. tostring(raw_msg) .. ']')
if not raw_msg then break end
process_raw_msg(raw_msg)
end
end
peer.close()
end)
channel = {users={}}
function channel:new()
o = {}
setmetatable(o, self)
self.__index = self
return o
end
function channel:set_topic(topic)
self.topic = topic
end
function channel:join(peer, name, nick)
self.peer = peer
self.name = name
self.nick = nick
send_to_peer(self.peer, ":" .. self.nick .. " JOIN " .. self.name)
send_to_peer(self.peer, ":permeshu 332 " .. self.nick .. ' ' .. self.name .. ' :' .. (self.topic or ''))
--self:who()
end
function channel:who()
for nick, user in pairs(self.users) do
send_to_peer(self.peer, ":permeshu 353 " .. self.nick .. ' = ' .. self.name .. ' :' .. nick)
end
send_to_peer(self.peer, ":permeshu 315 " .. self.nick .. ' ' .. self.name .. ' :End of NAMES list')
end
function channel:mode()
-- send_to_peer(self.peer, "MODE " .. self.name .. ' +tn')
end
local control_channel = channel:new()
function control_channel:join(peer, nick)
self:set_topic('permeshu control channel')
self.users['@permeshu'] = {}
self.users[nick] = {}
channel.join(self, peer, '&control', nick)
end
function control_channel:send_reply(msg)
send_to_peer(self.peer, ':permeshu PRIVMSG ' .. self.name .. ' :' .. msg)
end
function control_channel:disconnect_location(name)
location = locations.registry[name]
assert(location.connected)
location.connected(function()
self:send_reply("Location disconnected successfully")
location.connected = nil
end)
end
function control_channel:privmsg(command)
commands = {}
commands['quit'] =
function()
self.peer.accept_disable()
for name,location in pairs(locations.registry) do
if location.connected then self:disconnect_location(name) end
end
send_to_peer(self.peer, 'ERROR :Goodbye!')
return true -- break the receive loop
end
commands['locations'] =
function()
for name, dict in pairs(locations.registry) do
if dict.connected then status = "connected" else status = "disconnected" end
self:send_reply(("%s\t- %s"):format(name, status))
end
end
commands['connect'] =
function()
location = locations.registry['freenode']
if location.connected then
self:send_reply("Location is already connected")
return
end
context, err = protocols.registry[location.protocol].connect(location.args)
assert(context or err)
if not context then
self:send_reply(err)
else
self:send_reply("Location connected successfully")
location.connected = context
end
end
commands['disconnect'] =
function()
location = locations.registry['freenode']
if not location.connected then
self:send_reply("Location is not connected")
else
self:disconnect_location('freenode')
end
end
if commands[command] then
return commands[command]()
else
msg = 'Unknown control command [' .. command .. ']'
print(msg)
self:send_reply(msg)
end
end
local function remote_client_thread(peer)
print("Remote " .. peer.get_description() .. " connected")
peer.send('coroutines rock!\n')
local err
local data
while true do
data, err = peer.receive(4000)
print('[' .. tostring(data) .. ']')
print('[' .. tostring(err) .. ']')
if not data then break end
local function send(msg) send_to_peer(peer, msg) end
local user
local realname
local nick
local host = peer.get_ip()
local channels = {}
local function nop() end
local function unknown_command(prefix, command, params)
msg = 'Unknown/wrong command [' .. command .. ']'
if prefix then msg = msg .. ', prefix: [' .. prefix .. ']' end
if params then
msg = msg .. ', params:'
for _,param in pairs(params) do
msg = msg .. ' [' .. param .. ']'
end
end
print(msg)
send(msg)
end
print(("Remote %s:%s disconnected (%s)"):format(peer.get_description(), tostring(err)))
sock:close()
local function maybe_welcome()
if not user or not nick then return end
send(":permeshu 001 " .. nick .. " Welcome to the Internet Relay Network " .. nick .. "!" .. user .. "@" .. host)
c = control_channel:new()
c:join(peer, nick)
channels[c.name] = c
end
local command_handlers = {}
command_handlers[''] = unknown_command
command_handlers['USER'] = function(prefix, command, params)
user = params[1]
realname = params[4]
if not nick then unknown_command(prefix, command, params) return end
maybe_welcome()
end
command_handlers['PASS'] = nop
command_handlers['NICK'] = function(prefix, command, params)
nick = params[1]
if not nick then unknown_command(prefix, command, params) return end
maybe_welcome()
end
command_handlers['QUIT'] = function(prefix, command, params)
print(("Client is terminating its session [%s]"):format(tostring(params[1])))
send('ERROR :Goodbye!')
return true -- break the receive loop
end
command_handlers['PRIVMSG'] = function(prefix, command, params)
if not params[1] or not params[2] then unknown_command(prefix, command, params) return end
if channels[params[1]] then
return channels[params[1]]:privmsg(params[2])
else
print(("client sends '%s' to '%s'"):format(params[2], params[1]))
end
end
command_handlers['WHO'] =
function(prefix, command, params)
if not params[1] or not channels[params[1]] then unknown_command(prefix, command, params) return end
channels[params[1]]:who()
end
command_handlers['MODE'] =
function(prefix, command, params)
if not params[1] or not channels[params[1]] then unknown_command(prefix, command, params) return end
channels[params[1]]:mode()
end
-- command_handlers['JOIN'] = function(prefix, command, params)
-- end
local err = receive(peer, command_handlers)
print(("Remote %s disconnected (%s)"):format(peer.get_description(), tostring(err)))
end
function create_server()

27
locations.lua Normal file
View File

@ -0,0 +1,27 @@
-- -*- Mode: Lua; indent-tabs-mode: nil; lua-indent-level: 2 -*-
-- PERsonal MESsage HUb (permshu)
--
-- Copyright (C) 2010 Nedko Arnaudov <nedko@arnaudov.name>
--
-- permshu 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 2 of the License, or
-- (at your option) any later version.
--
-- permshu 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 permshu. If not, see <http://www.gnu.org/licenses/>
-- or write to the Free Software Foundation, Inc.,
-- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
module(..., package.seeall)
registry = {}
function register(name, protocol, args)
registry[name] = {protocol=protocol, args=args}
end

View File

@ -1,4 +1,4 @@
-- -*- Mode: Lua; indent-tabs-mode: t; lua-indent-level: 2 -*-
-- -*- Mode: Lua; indent-tabs-mode: nil; lua-indent-level: 2 -*-
-- PERsonal MESsage HUb (permshu)
--
-- Copyright (C) 2010 Nedko Arnaudov <nedko@arnaudov.name>

28
permshu
View File

@ -1,5 +1,5 @@
#!/usr/bin/env lua
-- -*- Mode: Lua; indent-tabs-mode: t; lua-indent-level: 2 -*-
-- -*- Mode: Lua; indent-tabs-mode: nil; lua-indent-level: 2 -*-
-- PERsonal MESsage HUb (permshu)
--
-- Copyright (C) 2010 Nedko Arnaudov <nedko@arnaudov.name>
@ -19,27 +19,17 @@
-- or write to the Free Software Foundation, Inc.,
-- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
require 'socket'
require 'remotes'
require 'irc'
require 'locations'
locations.regisyter('freenode',
'IRC',
{host='chat.freenode.net', nick='nedko_lua', realname='nedko@arnaudov.name', join='#ladi,#permeshu'})
locations.regisyter('unibg',
'IRC',
{host='irc.itdnet.net', nick='nedko_lua'}
irc.create_server()
---[[
connect_args = {
host = "irc.itdnet.net",
-- host = "chat.freenode.net",
nick = "nedko_lua",
-- realname = "nedko@arnaudov.name",
}
err = irc.connect(connect_args)
if err then print(err) return end
--]]
while remotes.dispatch() do end
-- httpm.get_http('download.berlios.de', "/supergrub/super_grub_disk_0.9799.iso.bz2")
-- httpm.get_http('download.berlios.de', "/supergrub/super_grub_disk_hybrid-1.98s1.iso")
-- httpm.dispatch()

52
protocols.lua Normal file
View File

@ -0,0 +1,52 @@
-- -*- Mode: Lua; indent-tabs-mode: nil; lua-indent-level: 2 -*-
-- PERsonal MESsage HUb (permshu)
--
-- Copyright (C) 2010 Nedko Arnaudov <nedko@arnaudov.name>
--
-- permshu 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 2 of the License, or
-- (at your option) any later version.
--
-- permshu 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 permshu. If not, see <http://www.gnu.org/licenses/>
-- or write to the Free Software Foundation, Inc.,
-- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
module(..., package.seeall)
registry = {}
local function dump_params(name, t)
print('\t'..name)
for i, v in pairs(t) do
print(("\t\t%s\t- %s"):format(tostring(i), tostring(v)))
end
end
local function dump()
for _,t in pairs(registry) do
print('----')
print(t.name)
for i, v in pairs(t) do
if i == 'required_params' then
dump_params("Required params", v)
elseif i == 'optional_params' then
dump_params("Optional params", v)
else
--print(("%30s - %s"):format(tostring(i), tostring(v)))
end
end
end
print('----')
end
function register(descriptor)
registry[descriptor.name] = descriptor
dump()
end

View File

@ -18,7 +18,9 @@
-- or write to the Free Software Foundation, Inc.,
-- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
require('misc')
require 'socket'
require 'misc'
module('remotes', package.seeall)
local threads = {}
@ -36,13 +38,14 @@ local function receive(sock, block_size)
local partial
sock:settimeout(0) -- do not block
block, status, partial = sock:receive(block_size)
-- print('[' .. tostring(block) .. ']')
-- print('[' .. tostring(status) .. ']')
-- print('[' .. tostring(partial) .. ']')
-- print('block [' .. tostring(block) .. ']')
-- print('status [' .. tostring(status) .. ']')
-- print('partial [' .. tostring(partial) .. ']')
if block and string.len(block) == 0 then block = nil end
block = block or partial
if block and string.len(block) == 0 then block = nil end
if not block and status == 'timeout' then
if block then break end
if status == 'timeout' then
-- if data is not available, tell the dispatcher so it can eventually wait on this socket
coroutine.yield(sock)
else
@ -53,19 +56,26 @@ local function receive(sock, block_size)
end
local function accept_thread_factory(sock, client_thread)
local accept_enabled = true
return
function()
while true do
while accept_enabled do
sock:settimeout(0) -- do not block
local client, err = sock:accept()
if client then
add_thread(function()
local pair = {
local ip = client:getpeername()
local description = ("%s:%s"):format(client:getpeername())
local peer = {
send = function(data) client:send(data) end,
receive = function(block_size) return receive(client, block_size) end,
get_description = function() return ("%s:%s"):format(client:getpeername()) end,
get_description = function() return description end,
get_ip = function() return ip end,
accept_disable = function() accept_enabled = false end
}
client_thread(pair)
client_thread(peer)
-- print('closing client socket')
client:close()
end)
elseif err == 'timeout' then
-- if data is not available, tell the dispatcher so it can eventually wait on this socket
@ -90,7 +100,7 @@ function dispatch()
-- print('resuming ' .. tostring(threads[i]))
local status, sock = coroutine.resume(threads[i])
if not sock then -- thread finished its task?
-- print(('finished %s (%s)'):format(tostring(threads[i]), sock))
-- print(('finished %s'):format(tostring(threads[i])))
table.remove(threads, i)
-- misc.dump_table(threads)
else
@ -131,6 +141,9 @@ function create_tcp_server(client_thread, binds, backlog)
sock, err = socket.tcp()
if not sock then return err end
res, err = sock:setoption('linger', {on=true, timeout=0})
if not res then return err end
for _, bind in pairs(binds) do
res, err = sock:bind(bind.host, bind.port)
if not res then return err end