Commit 51b60275 authored by Pierre Ynard's avatar Pierre Ynard

lua: merge telnet interface into rc

The crappy input buffering code of rc is replaced by the (now) decent
one of telnet. A new telnet transport is relatively cleanly added, and
VLM commands are made available (over any transport). Example of use:

vlc -Irc --rc-host "telnet://localhost:4212"
parent 6addf026
......@@ -64,7 +64,7 @@ For complete examples see existing VLC Lua interface modules (ie telnet.lua)
module("host",package.seeall)
status = { init = 0, read = 1, write = 2, password = 3 }
client_type = { net = 1, stdio = 2, fifo = 3 }
client_type = { net = 1, stdio = 2, fifo = 3, telnet = 4 }
function is_flag_set(val, flag)
return (((val - (val % flag)) / flag) % 2 ~= 0)
......@@ -120,7 +120,8 @@ function host()
end
for i, c in pairs(clients) do
if c == client then
if client.type == client_type.net then
if client.type == client_type.net
or client.type == client_type.telnet then
if client.wfd ~= client.rfd then
vlc.net.close( client.rfd )
end
......@@ -149,7 +150,7 @@ function host()
local function new_client( h, fd, wfd, t )
if fd < 0 then return end
local w, r
if t == client_type.net then
if t == client_type.net or t == client_type.telnet then
w = send
r = recv
else if t == client_type.stdio or t == client_type.fifo then
......@@ -179,7 +180,7 @@ function host()
end
-- public methods
local function _listen_tcp( h, host, port )
local function _listen_tcp( h, host, port, telnet )
if listeners.tcp and listeners.tcp[host]
and listeners.tcp[host][port] then
error("Already listening on tcp host `"..host..":"..tostring(port).."'")
......@@ -190,15 +191,16 @@ function host()
if not listeners.tcp[host] then
listeners.tcp[host] = {}
end
local listener = vlc.net.listen_tcp( host, port )
listeners.tcp[host][port] = listener
listeners.tcp[host][port] = true
if not listeners.tcp.list then
-- FIXME: if host == "list" we'll have a problem
listeners.tcp.list = {}
local m = { __mode = "v" } -- week values
setmetatable( listeners.tcp.list, m )
end
table.insert( listeners.tcp.list, listener )
local listener = vlc.net.listen_tcp( host, port )
local type = telnet and client_type.telnet or client_type.net;
table.insert( listeners.tcp.list, { data = listener,
type = type,
} )
end
local function _listen_stdio( h )
......@@ -221,7 +223,7 @@ function host()
h:listen_stdio()
else
u = vlc.net.url_parse( url )
h:listen_tcp( u.host, u.port )
h:listen_tcp( u.host, u.port, (u.protocol == "telnet") )
end
end
end
......@@ -241,7 +243,7 @@ function host()
filter_client( pollfds, status.write, vlc.net.POLLOUT )
if listeners.tcp then
for _, listener in pairs(listeners.tcp.list) do
for _, fd in pairs({listener:fds()}) do
for _, fd in pairs({listener.data:fds()}) do
pollfds[fd] = vlc.net.POLLIN
end
end
......@@ -264,10 +266,10 @@ function host()
end
if listeners.tcp then
for _, listener in pairs(listeners.tcp.list) do
for _, fd in pairs({listener:fds()}) do
for _, fd in pairs({listener.data:fds()}) do
if is_flag_set(pollfds[fd], vlc.net.POLLIN) then
local afd = listener:accept()
new_client( h, afd, afd, client_type.net )
local afd = listener.data:accept()
new_client( h, afd, afd, listener.type )
break
end
end
......@@ -282,8 +284,9 @@ function host()
local function destructor( h )
print "destructor"
for _,client in pairs(clients) do
client:send("Shutting down.")
if client.type == client_type.tcp then
--client:send("Shutting down.")
if client.type == client_type.net
or client.type == client_type.telnet then
if client.wfd ~= client.rfd then
vlc.net.close(client.rfd)
end
......
--[==========================================================================[
rc.lua: remote control module for VLC
--[==========================================================================[
Copyright (C) 2007-2009 the VideoLAN team
Copyright (C) 2007-2011 the VideoLAN team
$Id$
Authors: Antoine Cellerier <dionoea at videolan dot org>
Pierre Ynard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -25,14 +26,17 @@ description=
[============================================================================[
Remote control interface for VLC
This is a modules/control/rc.c look alike (with a bunch of new features)
This is a modules/control/rc.c look alike (with a bunch of new features).
It also provides a VLM interface copied from the telnet interface.
Use on local term:
vlc -I rc
Use on tcp connection:
vlc -I rc --lua-config "rc={host='localhost:4212'}"
Use on multiple hosts (term + 2 tcp ports):
vlc -I rc --lua-config "rc={hosts={'*console','localhost:4212','localhost:5678'}}"
Use on telnet connection:
vlc -I rc --lua-config "rc={host='telnet://localhost:4212'}"
Use on multiple hosts (term + plain tcp port + telnet):
vlc -I rc --lua-config "rc={hosts={'*console','localhost:4212','telnet://localhost:5678'}}"
Note:
-I rc and -I luarc are aliases for -I lua --lua-intf rc
......@@ -40,6 +44,7 @@ description=
Configuration options setable throught the --lua-config option are:
* hosts: A list of hosts to listen on.
* host: A host to listen on. (won't be used if `hosts' is set)
* password: The password used for telnet clients.
The following can be set using the --lua-config option or in the interface
itself using the `set' command:
* prompt: The prompt.
......@@ -129,8 +134,18 @@ function alias(client,value)
end
end
function lock(name,client)
if client.type == host.client_type.telnet then
client:switch_status( host.status.password )
client.buffer = ""
else
client:append("Error: the prompt can only be locked when logged in through telnet")
end
end
function logout(name,client)
if client.type == host.client_type.net then
if client.type == host.client_type.net
or client.type == host.client_type.telnet then
client:send("Bye-bye!\r\n")
client:del()
else
......@@ -140,13 +155,14 @@ end
function shutdown(name,client)
client:append("Bye-bye!")
h:broadcast("Shutting down.")
h:broadcast("Shutting down.\r\n")
vlc.msg.info("Requested shutdown.")
vlc.misc.quit()
end
function quit(name,client)
if client.type == host.client_type.net then
if client.type == host.client_type.net
or client.type == host.client_type.telnet then
logout(name,client)
else
shutdown(name,client)
......@@ -279,6 +295,12 @@ function print_text(label,text)
end
function help(name,client,arg)
if arg == nil then
client:append("+----[ VLM commands ]")
local message, vlc_err = vlm:execute_command("help")
vlm_message_to_string( client, message, "|" )
end
local width = client.env.width
local long = (name == "longhelp")
local extra = ""
......@@ -551,6 +573,7 @@ commands_ordered = {
{ "license"; { func = print_text("License message",vlc.misc.license()); help = "print VLC's license message"; adv = true } };
{ "help"; { func = help; args = "[pattern]"; help = "a help message"; aliases = { "?" } } };
{ "longhelp"; { func = help; args = "[pattern]"; help = "a longer help message" } };
{ "lock"; { func = lock; help = "lock the telnet prompt" } };
{ "logout"; { func = logout; help = "exit (if in a socket connection)" } };
{ "quit"; { func = quit; help = "quit VLC (or logout if in a socket connection)" } };
{ "shutdown"; { func = shutdown; help = "shutdown VLC" } };
......@@ -589,6 +612,21 @@ function split_input(input)
end
end
function vlm_message_to_string(client,message,prefix)
local prefix = prefix or ""
if message.value then
client:append(prefix .. message.name .. " : " .. message.value)
else
client:append(prefix .. message.name)
end
if message.children then
for i,c in ipairs(message.children) do
vlm_message_to_string(client,c,prefix.." ")
end
end
end
--[[ Command dispatch ]]
function call_command(cmd,client,arg)
if type(commands[cmd]) == type("") then
cmd = commands[cmd]
......@@ -605,6 +643,15 @@ function call_command(cmd,client,arg)
end
end
function call_vlm_command(cmd,client,arg)
if arg ~= nil then
cmd = cmd.." "..arg
end
local message, vlc_err = vlm:execute_command( cmd )
vlm_message_to_string( client, message )
return vlc_err
end
function call_libvlc_command(cmd,client,arg)
local ok, vlcerr = pcall( vlc.var.libvlc_command, cmd, arg )
if not ok then
......@@ -631,23 +678,98 @@ function call_object_command(cmd,client,arg)
return vlcerr
end
--[[ Setup host ]]
require("host")
h = host.host()
-- No auth
h.status_callbacks[host.status.password] = function(client)
function client_command( client )
local cmd,arg = split_input(client.buffer)
client.buffer = ""
if commands[cmd] then
call_command(cmd,client,arg)
elseif string.sub(cmd,0,1)=='@'
and call_object_command(string.sub(cmd,2,#cmd),client,arg) == 0 then
--
elseif call_vlm_command(cmd,client,arg) == 0 then
--
elseif client.type == host.client_type.stdio
and call_libvlc_command(cmd,client,arg) == 0 then
--
else
local choices = {}
if client.env.autocompletion ~= 0 then
for v,_ in common.pairs_sorted(commands) do
if string.sub(v,0,#cmd)==cmd then
table.insert(choices, v)
end
end
end
if #choices == 1 and client.env.autoalias ~= 0 then
-- client:append("Aliasing to \""..choices[1].."\".")
cmd = choices[1]
call_command(cmd,client,arg)
else
client:append("Unknown command `"..cmd.."'. Type `help' for help.")
if #choices ~= 0 then
client:append("Possible choices are:")
local cols = math.floor(client.env.width/(client.env.colwidth+1))
local fmt = "%-"..client.env.colwidth.."s"
for i = 1, #choices do
choices[i] = string.format(fmt,choices[i])
end
for i = 1, #choices, cols do
local j = i + cols - 1
if j > #choices then j = #choices end
client:append(" "..table.concat(choices," ",i,j))
end
end
end
end
end
--[[ Some telnet command special characters ]]
WILL = "\251" -- Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option.
WONT = "\252" -- Indicates the refusal to perform, or continue performing, the indicated option.
DO = "\253" -- Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option.
DONT = "\254" -- Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option.
IAC = "\255" -- Interpret as command
ECHO = "\001"
function telnet_commands( client )
-- remove telnet command replies from the client's data
client.buffer = string.gsub( client.buffer, IAC.."["..DO..DONT..WILL..WONT.."].", "" )
end
--[[ Client status change callbacks ]]
function on_password( client )
client.env = common.table_copy( env )
if client.env.welcome ~= "" then
client:send( client.env.welcome .. "\r\n")
if client.type == host.client_type.telnet then
client:send( "Password: " ..IAC..WILL..ECHO )
else
if client.env.welcome ~= "" then
client:send( client.env.welcome .. "\r\n")
end
client:switch_status( host.status.read )
end
client:switch_status(host.status.read)
end
-- Print prompt when switching a client's status to `read'
h.status_callbacks[host.status.read] = function(client)
function on_read( client )
client:send( client.env.prompt )
end
function on_write( client )
end
--[[ Setup host ]]
require("host")
h = host.host()
h.status_callbacks[host.status.password] = on_password
h.status_callbacks[host.status.read] = on_read
h.status_callbacks[host.status.write] = on_write
h:listen( config.hosts or config.host or "*console" )
password = config.password or "admin"
--[[ Launch vlm ]]
vlm = vlc.vlm()
--[[ The main loop ]]
while not vlc.misc.should_die() do
......@@ -661,60 +783,53 @@ while not vlc.misc.should_die() do
for _, client in pairs(read) do
local input = client:recv(1000)
local done = false
if string.match(input,"\n$") then
client.buffer = string.gsub(client.buffer..input,"\r?\n$","")
done = true
elseif input == ""
or (client.type == host.client_type.net and input == "\004") then
if input == "" -- the telnet client program has left
or ((client.type == host.client_type.net
or client.type == host.client_type.telnet)
and input == "\004") then
-- Caught a ^D
client.buffer = "quit"
done = true
client.cmds = "quit\n"
else
client.buffer = client.buffer .. input
client.cmds = client.cmds .. input
end
if done then
local cmd,arg = split_input(client.buffer)
client.buffer = ""
client:switch_status(host.status.write)
if commands[cmd] then
call_command(cmd,client,arg)
elseif string.sub(cmd,0,1)=='@'
and call_object_command(string.sub(cmd,2,#cmd),client,arg) == 0 then
--
elseif client.type == host.client_type.stdio
and call_libvlc_command(cmd,client,arg) == 0 then
--
else
local choices = {}
if client.env.autocompletion ~= 0 then
for v,_ in common.pairs_sorted(commands) do
if string.sub(v,0,#cmd)==cmd then
table.insert(choices, v)
end
end
end
if #choices == 1 and client.env.autoalias ~= 0 then
-- client:append("Aliasing to \""..choices[1].."\".")
cmd = choices[1]
call_command(cmd,client,arg)
client.buffer = ""
-- split the command at the first '\n'
while string.find(client.cmds, "\n") do
-- save the buffer to send to the client
local saved_buffer = client.buffer
-- get the next command
local index = string.find(client.cmds, "\n")
client.buffer = string.gsub(string.sub(client.cmds, 0, index - 1), "^%s*(.-)%s*$", "%1")
client.cmds = string.sub(client.cmds, index + 1)
-- Remove telnet commands from the command line
if client.type == host.client_type.telnet then
telnet_commands( client )
end
-- Run the command
if client.status == host.status.password then
if client.buffer == password then
client:send( IAC..WONT..ECHO.."\r\nWelcome, Master\r\n" )
client.buffer = ""
client:switch_status( host.status.write )
elseif client.buffer == "quit" then
client_command( client )
else
client:append("Unknown command `"..cmd.."'. Type `help' for help.")
if #choices ~= 0 then
client:append("Possible choices are:")
local cols = math.floor(client.env.width/(client.env.colwidth+1))
local fmt = "%-"..client.env.colwidth.."s"
for i = 1, #choices do
choices[i] = string.format(fmt,choices[i])
end
for i = 1, #choices, cols do
local j = i + cols - 1
if j > #choices then j = #choices end
client:append(" "..table.concat(choices," ",i,j))
end
end
client:send( "\r\nWrong password\r\nPassword: " )
client.buffer = ""
end
else
client:switch_status( host.status.write )
client_command( client )
end
client.buffer = saved_buffer .. client.buffer
end
end
end
--[[ Clean up ]]
vlm = nil
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment