1 --[==========================================================================[
2 host
.lua
: VLC Lua interface command line host module
3 --[==========================================================================[
4 Copyright (C
) 2007-2012 the VideoLAN team
7 Authors
: Antoine Cellerier
<dionoea at videolan dot org
>
9 This program is free software
; you can redistribute it
and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation
; either version
2 of the License
, or
12 (at your option
) any later version
.
14 This program is distributed
in the hope that it will be useful
,
15 but WITHOUT ANY WARRANTY
; without even the implied warranty of
16 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE
. See the
17 GNU General Public License
for more details
.
19 You should have received a copy of the GNU General Public License
20 along with this program
; if not, write to the Free Software
21 Foundation
, Inc
., 51 Franklin Street
, Fifth Floor
, Boston MA
02110-1301, USA
.
22 --]==========================================================================]
24 --[==========================================================================[
30 -- Bypass any authentication
31 function on_password( client
)
32 client
:switch_status( host
.status
.read )
34 h
.status_callbacks
[host
.status
.password
] = on_password
36 h
:listen( "localhost:4212" )
37 h
:listen( "*console" )
38 --or h:listen( { "localhost:4212", "*console" } )
42 -- accept new connections and select active clients
43 local write, read = h
:accept_and_select()
45 -- handle clients in write mode
46 for _
, client
in pairs(write) do
49 client
:switch_status( host
.status
.read )
52 -- handle clients in read mode
53 for _
, client
in pairs(read) do
54 local str
= client
:recv(1000)
55 if not str
then break end
56 str
= string.gsub(str
,"\r?\n$","")
57 client
.buffer
= "Got `"..str
.."'.\r\n"
58 client
:switch_status( host
.status
.write )
62 For complete examples see existing VLC Lua interface
modules (ie cli
.lua
)
63 --]==========================================================================]
65 module("host",package
.seeall
)
67 status
= { init
= 0, read = 1, write = 2, password
= 3 }
68 client_type
= { net
= 1, stdio
= 2, fifo
= 3, telnet
= 4 }
70 function is_flag_set(val
, flag
)
76 if val
>= bit
and flag
>= bit
then return true end
85 local status_callbacks
= {}
88 local function fd_client( client
)
89 if client
.status
== status
.read then
96 local function send( client
, data
, len
)
98 return vlc
.net
.send( client
.wfd
, data
, len
)
100 return vlc
.net
.send( client
.wfd
, data
or client
.buffer
)
104 local function recv( client
, len
)
106 return vlc
.net
.recv( client
.rfd
, len
)
108 return vlc
.net
.recv( client
.rfd
)
112 local function write( client
, data
)
113 return vlc
.net
.write( client
.wfd
, data
or client
.buffer
)
116 local function read( client
, len
)
118 return vlc
.net
.read( client
.rfd
, len
)
120 return vlc
.net
.read( client
.rfd
)
124 local function write_console( client
, data
)
125 -- FIXME: this method shouldn't be needed. vlc.net.write should
127 vlc
.win
.console_write(data
or client
.buffer
)
128 return string.len(data
or client
.buffer
)
131 local function read_console( client
, len
)
132 -- Read stdin from a windows console (beware: select/poll doesn't work!)
133 return vlc
.win
.console_read()
136 local function del_client( client
)
137 if not clients
[client
] then
138 vlc
.msg
.err("couldn't find client to remove.")
142 if client
.type == client_type
.stdio
then
143 h
:broadcast("Shutting down.\r\n")
144 vlc
.msg
.info("Requested shutdown.")
146 elseif client
.type == client_type
.net
147 or client
.type == client_type
.telnet
then
148 if client
.wfd
~= client
.rfd
then
149 vlc
.net
.close( client
.rfd
)
151 vlc
.net
.close( client
.wfd
)
153 clients
[client
] = nil
156 local function switch_status( client
, s
)
157 if client
.status
== s
then return end
159 if status_callbacks
[s
] then
160 status_callbacks
[s
]( client
)
164 -- append a line to a client's (output) buffer
165 local function append( client
, string )
166 client
.buffer
= client
.buffer
.. string .. "\r\n"
169 local function new_client( h
, fd
, wfd
, t
)
170 if fd
< 0 then return end
172 if t
== client_type
.net
or t
== client_type
.telnet
then
175 elseif t
== client_type
.stdio
or t
== client_type
.fifo
then
176 if vlc
.win
and t
== client_type
.stdio
then
177 vlc
.win
.console_init()
185 error("Unknown client type", t
)
188 local client
= { -- data
191 status
= status
.init
,
200 switch_status
= switch_status
,
203 client
:send( "VLC media player "..vlc
.misc
.version().."\n" )
204 clients
[client
] = client
205 client
:switch_status(status
.password
)
209 local function _listen_tcp( h
, host
, port
, telnet
)
210 if listeners
.tcp
and listeners
.tcp
[host
]
211 and listeners
.tcp
[host
][port
] then
212 error("Already listening on tcp host `"..host
..":"..tostring(port
).."'")
214 if listeners
.stdio
and vlc
.win
then
215 error("Cannot listen on console and sockets concurrently on Windows")
217 if not listeners
.tcp
then
220 if not listeners
.tcp
[host
] then
221 listeners
.tcp
[host
] = {}
223 listeners
.tcp
[host
][port
] = true
224 if not listeners
.tcp
.list
then
225 -- FIXME: if host == "list" we'll have a problem
226 listeners
.tcp
.list
= {}
228 local listener
= vlc
.net
.listen_tcp( host
, port
)
229 local type = telnet
and client_type
.telnet
or client_type
.net
;
230 table.insert( listeners
.tcp
.list
, { data
= listener
,
235 local function _listen_stdio( h
)
236 if listeners
.stdio
then
237 error("Already listening on stdio")
239 if listeners
.tcp
and vlc
.win
then
240 error("Cannot listen on console and sockets concurrently on Windows")
242 new_client( h
, 0, 1, client_type
.stdio
)
243 listeners
.stdio
= true
246 local function _listen( h
, url
)
247 if type(url
)==type({}) then
248 for _
,u
in pairs(url
) do
252 vlc
.msg
.info( "Listening on host \""..url
.."\"." )
253 if url
== "*console" then
256 u
= vlc
.strings
.url_parse( url
)
257 if url
.host
== nil then
258 u
= vlc
.strings
.url_parse( "//" .. url
)
260 h
:listen_tcp( u
.host
, u
.port
, (u
.protocol
== "telnet") )
265 local function _accept_and_select( h
)
268 if not (vlc
.win
and listeners
.stdio
) then
269 local function filter_client( fds
, status
, event
)
270 for _
, client
in pairs(clients
) do
271 if client
.status
== status
then
272 fds
[client
:fd()] = event
278 filter_client( pollfds
, status
.read, vlc
.net
.POLLIN
)
279 filter_client( pollfds
, status
.password
, vlc
.net
.POLLIN
)
280 filter_client( pollfds
, status
.write, vlc
.net
.POLLOUT
)
281 if listeners
.tcp
then
282 for _
, listener
in pairs(listeners
.tcp
.list
) do
283 for _
, fd
in pairs({listener
.data
:fds()}) do
284 pollfds
[fd
] = vlc
.net
.POLLIN
289 local ret
= vlc
.net
.poll( pollfds
)
291 for _
, client
in pairs(clients
) do
292 if is_flag_set(pollfds
[client
:fd()], vlc
.net
.POLLIN
) then
293 table.insert(rclients
, client
)
294 elseif is_flag_set(pollfds
[client
:fd()], vlc
.net
.POLLERR
)
295 or is_flag_set(pollfds
[client
:fd()], vlc
.net
.POLLHUP
)
296 or is_flag_set(pollfds
[client
:fd()], vlc
.net
.POLLNVAL
) then
298 elseif is_flag_set(pollfds
[client
:fd()], vlc
.net
.POLLOUT
) then
299 table.insert(wclients
, client
)
302 if listeners
.tcp
then
303 for _
, listener
in pairs(listeners
.tcp
.list
) do
304 for _
, fd
in pairs({listener
.data
:fds()}) do
305 if is_flag_set(pollfds
[fd
], vlc
.net
.POLLIN
) then
306 local afd
= listener
.data
:accept()
307 new_client( h
, afd
, afd
, listener
.type )
315 for _
, client
in pairs(clients
) do
316 if client
.type == client_type
.stdio
then
317 if client
.status
== status
.read or client
.status
== status
.password
then
318 if vlc
.win
.console_wait(50) then
319 table.insert(rclients
, client
)
322 table.insert(wclients
, client
)
327 return wclients
, rclients
330 local function destructor( h
)
331 for _
,client
in pairs(clients
) do
332 if client
.type ~= client_type
.stdio
then
338 local function _broadcast( h
, msg
)
339 for _
,client
in pairs(clients
) do
345 -- We're running Lua 5.1
346 -- See http://lua-users.org/wiki/HiddenFeatures for more info.
347 local proxy
= newproxy(true)
348 getmetatable(proxy
).__gc
= destructor
353 local h
= setmetatable(
355 status_callbacks
= status_callbacks
,
358 listen_tcp
= _listen_tcp
,
359 listen_stdio
= _listen_stdio
,
360 accept_and_select
= _accept_and_select
,
361 broadcast
= _broadcast
,
364 __gc
= destructor
, -- Should work in Lua 5.2 without the new proxytrick as __gc is also called on tables (needs to be tested)