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