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