--[==========================================================================[ host.lua: VLC Lua interface command line host module --[==========================================================================[ Copyright (C) 2007-2012 the VideoLAN team Authors: Antoine Cellerier 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: local host = 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) --]==========================================================================] 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) local bit = 65536 while bit > 1 do val = val % bit flag = flag % bit bit = bit / 2 if val >= bit and flag >= bit then return true end end return false 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 vlc.win.console_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.strings.url_parse( url ) if u.host == nil then u = vlc.strings.url_parse( "//" .. url ) end 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 local hostfuncs = { host = host, status = status, client_type = client_type, is_flag_set = is_flag_set, } _G.host = hostfuncs return hostfuncs