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