You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

358 lines
12 KiB

--[==========================================================================[
host.lua: VLC Lua interface command line host module
--[==========================================================================[
Copyright (C) 2007-2012 the VideoLAN team
$Id$
Authors: Antoine Cellerier <dionoea at videolan dot org>
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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
--]==========================================================================]
--[==========================================================================[
Example use:
require "host"
h = host.host()
-- Bypass any authentication
function on_password( client )
client:switch_status( host.status.read )
end
h.status_callbacks[host.status.password] = on_password
h:listen( "localhost:4212" )
h:listen( "*console" )
--or h:listen( { "localhost:4212", "*console" } )
-- The main loop
while true do
-- accept new connections and select active clients
local write, read = h:accept_and_select()
-- handle clients in write mode
for _, client in pairs(write) do
client:send()
client.buffer = ""
client:switch_status( host.status.read )
end
-- handle clients in read mode
for _, client in pairs(read) do
local str = client:recv(1000)
if not str then break end
str = string.gsub(str,"\r?\n$","")
client.buffer = "Got `"..str.."'.\r\n"
client:switch_status( host.status.write )
end
end
For complete examples see existing VLC Lua interface modules (ie cli.lua)
--]==========================================================================]
module("host",package.seeall)
status = { init = 0, read = 1, write = 2, password = 3 }
client_type = { net = 1, stdio = 2, fifo = 3, telnet = 4 }
function is_flag_set(val, flag)
return (((val - (val % flag)) / flag) % 2 ~= 0)
end
function host()
-- private data
local clients = {}
local listeners = {}
local status_callbacks = {}
-- private methods
local function fd_client( client )
if client.status == status.read then
return client.rfd
else -- status.write
return client.wfd
end
end
local function send( client, data, len )
if len then
return vlc.net.send( client.wfd, data, len )
else
return vlc.net.send( client.wfd, data or client.buffer )
end
end
local function recv( client, len )
if len then
return vlc.net.recv( client.rfd, len )
else
return vlc.net.recv( client.rfd )
end
end
local function write( client, data )
return vlc.net.write( client.wfd, data or client.buffer )
end
local function read( client, len )
if len then
return vlc.net.read( client.rfd, len )
else
return vlc.net.read( client.rfd )
end
end
local function write_console( client, data )
-- FIXME: this method shouldn't be needed. vlc.net.write should
-- just work
io.write(data or client.buffer)
return string.len(data or client.buffer)
end
local function read_console( client, len )
-- Read stdin from a windows console (beware: select/poll doesn't work!)
return vlc.win.console_read()
end
local function del_client( client )
if not clients[client] then
vlc.msg.err("couldn't find client to remove.")
return
end
if client.type == client_type.stdio then
h:broadcast("Shutting down.\r\n")
vlc.msg.info("Requested shutdown.")
vlc.misc.quit()
elseif client.type == client_type.net
or client.type == client_type.telnet then
if client.wfd ~= client.rfd then
vlc.net.close( client.rfd )
end
vlc.net.close( client.wfd )
end
clients[client] = nil
end
local function switch_status( client, s )
if client.status == s then return end
client.status = s
if status_callbacks[s] then
status_callbacks[s]( client )
end
end
-- append a line to a client's (output) buffer
local function append( client, string )
client.buffer = client.buffer .. string .. "\r\n"
end
local function new_client( h, fd, wfd, t )
if fd < 0 then return end
local w, r
if t == client_type.net or t == client_type.telnet then
w = send
r = recv
elseif t == client_type.stdio or t == client_type.fifo then
if vlc.win and t == client_type.stdio then
vlc.win.console_init()
w = write_console
r = read_console
else
w = write
r = read
end
else
error("Unknown client type", t )
end
local client = { -- data
rfd = fd,
wfd = wfd or fd,
status = status.init,
buffer = "",
cmds = "",
type = t,
-- methods
fd = fd_client,
send = w,
recv = r,
del = del_client,
switch_status = switch_status,
append = append,
}
client:send( "VLC media player "..vlc.misc.version().."\n" )
clients[client] = client
client:switch_status(status.password)
end
-- public methods
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).."'")
end
if listeners.stdio and vlc.win then
error("Cannot listen on console and sockets concurrently on Windows")
end
if not listeners.tcp then
listeners.tcp = {}
end
if not listeners.tcp[host] then
listeners.tcp[host] = {}
end
listeners.tcp[host][port] = true
if not listeners.tcp.list then
-- FIXME: if host == "list" we'll have a problem
listeners.tcp.list = {}
end
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 )
if listeners.stdio then
error("Already listening on stdio")
end
if listeners.tcp and vlc.win then
error("Cannot listen on console and sockets concurrently on Windows")
end
new_client( h, 0, 1, client_type.stdio )
listeners.stdio = true
end
local function _listen( h, url )
if type(url)==type({}) then
for _,u in pairs(url) do
h:listen( u )
end
else
vlc.msg.info( "Listening on host \""..url.."\"." )
if url == "*console" then
h:listen_stdio()
else
u = vlc.net.url_parse( url )
h:listen_tcp( u.host, u.port, (u.protocol == "telnet") )
end
end
end
local function _accept_and_select( h )
local wclients = {}
local rclients = {}
if not (vlc.win and listeners.stdio) then
local function filter_client( fds, status, event )
for _, client in pairs(clients) do
if client.status == status then
fds[client:fd()] = event
end
end
end
local pollfds = {}
filter_client( pollfds, status.read, vlc.net.POLLIN )
filter_client( pollfds, status.password, vlc.net.POLLIN )
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.data:fds()}) do
pollfds[fd] = vlc.net.POLLIN
end
end
end
local ret = vlc.net.poll( pollfds )
if ret > 0 then
for _, client in pairs(clients) do
if is_flag_set(pollfds[client:fd()], vlc.net.POLLIN) then
table.insert(rclients, client)
elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLERR)
or is_flag_set(pollfds[client:fd()], vlc.net.POLLHUP)
or is_flag_set(pollfds[client:fd()], vlc.net.POLLNVAL) then
client:del()
elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLOUT) then
table.insert(wclients, client)
end
end
if listeners.tcp then
for _, listener in pairs(listeners.tcp.list) do
for _, fd in pairs({listener.data:fds()}) do
if is_flag_set(pollfds[fd], vlc.net.POLLIN) then
local afd = listener.data:accept()
new_client( h, afd, afd, listener.type )
break
end
end
end
end
end
else
for _, client in pairs(clients) do
if client.type == client_type.stdio then
if client.status == status.read or client.status == status.password then
if vlc.win.console_wait(50) then
table.insert(rclients, client)
end
else
table.insert(wclients, client)
end
end
end
end
return wclients, rclients
end
local function destructor( h )
for _,client in pairs(clients) do
if client.type ~= client_type.stdio then
client:del()
end
end
end
local function _broadcast( h, msg )
for _,client in pairs(clients) do
client:send( msg )
end
end
if setfenv then
-- We're running Lua 5.1
-- See http://lua-users.org/wiki/HiddenFeatures for more info.
local proxy = newproxy(true)
getmetatable(proxy).__gc = destructor
destructor = proxy
end
-- the instance
local h = setmetatable(
{ -- data
status_callbacks = status_callbacks,
-- methods
listen = _listen,
listen_tcp = _listen_tcp,
listen_stdio = _listen_stdio,
accept_and_select = _accept_and_select,
broadcast = _broadcast,
},
{ -- metatable
__gc = destructor, -- Should work in Lua 5.2 without the new proxytrick as __gc is also called on tables (needs to be tested)
__metatable = "",
})
return h
end