]> git.sesse.net Git - vlc/blob - share/luaintf/modules/host.lua
Detect and allow older versions of ffmpeg to be used in conjunction with VLC.
[vlc] / share / luaintf / modules / host.lua
1 --[==========================================================================[
2  host.lua: VLC Lua interface command line host module
3 --[==========================================================================[
4  Copyright (C) 2007 the VideoLAN team
5  $Id$
6
7  Authors: Antoine Cellerier <dionoea at videolan dot org>
8
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.
13
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.
18
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 --]==========================================================================]
23
24 --[==========================================================================[
25 Example use:
26
27     require "host"
28     h = host.host()
29
30     -- Bypass any authentification
31     function on_password( client )
32         client:switch_status( host.status.read )
33     end
34     h.status_callbacks[host.status.password] = on_password
35
36     h:listen( "localhost:4212" )
37     h:listen( "*console" )
38     --or h:listen( { "localhost:4212", "*console" } )
39
40     -- The main loop
41     while not vlc.should_die() do
42         -- accept new connections
43         h:accept()
44
45         -- select active clients
46         local write, read = h:select( 0.1 ) -- 0.1 is a timeout in seconds
47
48         -- handle clients in write mode
49         for _, client in pairs(write) do
50             client:send()
51             client.buffer = ""
52             client:switch_status( host.status.read )
53         end
54
55         -- handle clients in read mode
56         for _, client in pairs(read) do
57             local str = client:recv(1000)
58             str = string.gsub(str,"\r?\n$","")
59             client.buffer = "Got `"..str.."'.\r\n"
60             client:switch_status( host.status.write )
61         end
62     end
63
64 For complete examples see existing VLC Lua interface modules (ie telnet.lua)
65 --]==========================================================================]
66
67 module("host",package.seeall)
68
69 status = { init = 0, read = 1, write = 2, password = 3 }
70 client_type = { net = 1, stdio = 2, fifo = 3 }
71
72 function host()
73     -- private data
74     local clients = {}
75     local listeners = {}
76     local status_callbacks = {}
77
78     -- private methods
79     local function new_fd_set()
80         function foo_fds(foo)
81             return function(fd,...) return foo(fd.fds,...) end
82         end
83         return { -- data
84                  fds = vlc.fd.new_fd_set(),
85                  -- methods
86                  zero = foo_fds( vlc.fd.fd_zero ),
87                  set = foo_fds( vlc.fd.fd_set ),
88                  isset = foo_fds( vlc.fd.fd_isset ),
89                  clr = foo_fds( vlc.fd.fd_clr ),
90                }
91     end
92
93     -- private data
94     local fds_read = new_fd_set()
95     local fds_write = new_fd_set()
96
97     -- private methods
98     local function client_accept( clients, listen )
99         local wait
100         if #clients == 0 then
101             wait = -1
102         else
103             wait = 0
104         end
105         return vlc.net.accept( listen, wait )
106     end
107
108     local function fd_client( client )
109         if client.status == status.read then
110             return client.rfd
111         else -- status.write
112             return client.wfd
113         end
114     end
115
116     local function send( client, data, len )
117         if len then
118             return vlc.net.send( client.wfd, data, len )
119         else
120             return vlc.net.send( client.wfd, data or client.buffer )
121         end
122     end
123
124     local function recv( client, len )
125         if len then
126             return vlc.net.recv( client.rfd, len )
127         else
128             return vlc.net.recv( client.rfd )
129         end
130     end
131
132     local function write( client, data )
133         return vlc.fd.write( client.wfd, data or client.buffer )
134     end
135
136     local function read( client, len )
137         if len then
138             return vlc.fd.read( client.rfd, len )
139         else
140             return vlc.fd.read( client.rfd )
141         end
142     end
143
144     local function del_client( client )
145         if client.type == client_type.stdio then
146             client:send( "Cannot delete stdin/stdout client.\n" )
147             return
148         end
149         for i, c in pairs(clients) do
150             if c == client then
151                 if client.type == client_type.net then
152                     if client.wfd ~= client.rfd then
153                         vlc.net.close( client.rfd )
154                     end
155                     vlc.net.close( client.wfd )
156                 end
157                 clients[i] = nil
158                 return
159             end
160         end
161         vlc.msg.err("couldn't find client to remove.")
162     end
163     
164     local function switch_status( client, s )
165         if client.status == s then return end
166         client.status = s
167         if status_callbacks[s] then
168             status_callbacks[s]( client )
169         end
170     end
171
172     -- append a line to a client's (output) buffer
173     local function append( client, string )
174         client.buffer = client.buffer .. string .. "\r\n"
175     end
176
177     local function new_client( h, fd, wfd, t )
178         if fd < 0 then return end
179         local w, r
180         if t == client_type.net then
181             w = send
182             r = recv
183         else if t == client_type.stdio or t == client_type.fifo then
184             w = write
185             r = read
186         else
187             error("Unknown client type", t )
188         end end
189         local client = { -- data
190                          rfd = fd,
191                          wfd = wfd or fd,
192                          status = status.init,
193                          buffer = "",
194                          type = t,
195                          -- methods
196                          fd = fd_client,
197                          send = w,
198                          recv = r,
199                          del = del_client,
200                          switch_status = switch_status,
201                          append = append,
202                        }
203         client:send( "VLC media player "..vlc.version().."\n" )
204         table.insert(clients, client)
205         client:switch_status(status.password)
206     end
207
208     function filter_client( fd, status, status2 )
209         local l = 0
210         fd:zero()
211         for _, client in pairs(clients) do
212             if client.status == status or client.status == status2 then
213                 fd:set( client:fd() )
214                 l = math.max( l, client:fd() )
215             end
216         end
217         return l
218     end
219
220     -- public methods
221     local function _listen_tcp( h, host, port )
222         if listeners.tcp and listeners.tcp[host]
223                          and listeners.tcp[host][port] then
224             error("Already listening on tcp host `"..host..":"..tostring(port).."'")
225         end
226         if not listeners.tcp then
227             listeners.tcp = {}
228         end
229         if not listeners.tcp[host] then
230             listeners.tcp[host] = {}
231         end
232         local listener = vlc.net.listen_tcp( host, port )
233         listeners.tcp[host][port] = listener
234         if not listeners.tcp.list then
235             -- FIXME: if host == "list" we'll have a problem
236             listeners.tcp.list = {}
237             local m = { __mode = "v" } -- week values
238             setmetatable( listeners.tcp.list, m )
239         end
240         table.insert( listeners.tcp.list, listener )
241     end
242
243     local function _listen_stdio( h )
244         
245         if listeners.stdio then
246             error("Already listening on stdio")
247         end
248         new_client( h, 0, 1, client_type.stdio )
249         listeners.stdio = true
250     end
251
252     local function _listen( h, url )
253         if type(url)==type({}) then
254             for _,u in pairs(url) do
255                 h:listen( u )
256             end
257         else
258             vlc.msg.info( "Listening on host \""..url.."\"." )
259             if url == "*console" then
260                 h:listen_stdio()
261             else
262                 u = vlc.net.url_parse( url )
263                 h:listen_tcp( u.host, u.port )
264             end
265         end
266     end
267
268     local function _accept( h )
269         if listeners.tcp then
270             local wait
271             if #clients == 0 and not listeners.stdio and #listeners.tcp.list == 1 then
272                 wait = -1 -- blocking
273             else
274                 wait = 0
275             end
276             for _, listener in pairs(listeners.tcp.list) do
277                 local fd = vlc.net.accept( listener, wait )
278                 new_client( h, fd, fd, client_type.net )
279             end
280         end
281     end
282
283     local function _select( h, timeout )
284         local nfds = math.max( filter_client( fds_read, status.read, status.password ),
285                                filter_client( fds_write, status.write ) ) + 1
286         local ret = vlc.net.select( nfds, fds_read.fds, fds_write.fds,
287                                     timeout or 0.5 )
288         local wclients = {}
289         local rclients = {}
290         if ret > 0 then
291             for _, client in pairs(clients) do
292                 if fds_write:isset( client:fd() ) then
293                     table.insert(wclients,client)
294                 end
295                 if fds_read:isset( client:fd() ) then
296                     table.insert(rclients,client)
297                 end
298             end
299         end
300         return wclients, rclients
301     end
302
303     local function destructor( h )
304         print "destructor"
305         for _,client in pairs(clients) do
306             client:send("Shutting down.")
307             if client.type == client_type.tcp then
308                 if client.wfd ~= client.rfd then
309                     vlc.net.close(client.rfd)
310                 end
311                 vlc.net.close(client.wfd)
312             end
313         end
314
315         if listeners.tcp then
316             for _, listener in pairs(listeners.tcp.list) do
317                 vlc.net.listen_close( listener )
318             end
319         end
320     end
321
322     local function _broadcast( h, msg )
323         for _,client in pairs(clients) do
324             client:send( msg )
325         end
326     end
327
328     -- the instance
329     local h = { -- data
330                 status_callbacks = status_callbacks,
331                 -- methods
332                 listen = _listen,
333                 listen_tcp = _listen_tcp,
334                 listen_stdio = _listen_stdio,
335                 accept = _accept,
336                 select = _select,
337                 broadcast = _broadcast,
338               }
339
340     -- the metatable
341     local m = { -- data
342                 __metatable = "Nothing to see here. Move along.",
343                 -- methods
344                 __gc = destructor,
345               }
346
347     setmetatable( h, m )
348
349     return h
350 end