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