source: ipk/source.sh4/network_xupnpd/_path_/etc/xupnpd/xupnpd_main.lua

Last change on this file was 34402, checked in by Stephan, 11 years ago

new xupnpd version 1.033

File size: 9.5 KB
Line 
1-- Copyright (C) 2011-2015 Anton Burdinuk
2-- clark15b@gmail.com
3-- https://tsdemuxer.googlecode.com/svn/trunk/xupnpd
4
5http.sendurl_buffer_size(32768,1);
6
7if cfg.daemon==true then core.detach() end
8
9core.openlog(cfg.log_ident,cfg.log_facility)
10
11if cfg.daemon==true then core.touchpid(cfg.pid_file) end
12
13if cfg.embedded==true then cfg.debug=0 end
14
15function clone_table(t)
16 local tt={}
17 for i,j in pairs(t) do
18 tt[i]=j
19 end
20 return tt
21end
22
23function split_string(s,d)
24 local t={}
25 d='([^'..d..']+)'
26 for i in string.gmatch(s,d) do
27 table.insert(t,i)
28 end
29 return t
30end
31
32function load_plugins(path,what)
33 local d=util.dir(path)
34
35 if d then
36 for i,j in ipairs(d) do
37 if string.find(j,'^[%w_-]+%.lua$') then
38 if cfg.debug>0 then print(what..' \''..j..'\'') end
39 dofile(path..j)
40 end
41 end
42 end
43end
44
45
46-- options for profiles
47cfg.dev_desc_xml='/dev.xml' -- UPnP Device Description XML
48cfg.upnp_container='object.container' -- UPnP class for containers
49cfg.upnp_artist=false -- send <upnp:artist> / <upnp:actor> in SOAP response
50cfg.upnp_feature_list='' -- X_GetFeatureList response body
51cfg.upnp_albumart=0 -- 0: <upnp:albumArtURI>direct url</upnp:albumArtURI>, 1: <res>direct url<res>, 2: <upnp:albumArtURI>local url</upnp:albumArtURI>, 3: <res>local url<res>
52cfg.dlna_headers=true -- send TransferMode.DLNA.ORG and ContentFeatures.DLNA.ORG in HTTP response
53cfg.dlna_extras=true -- DLNA extras in headers and SOAP
54cfg.content_disp=false -- send Content-Disposition when streaming
55cfg.soap_length=true -- send Content-Length in SOAP response
56cfg.wdtv=false -- WDTV Live compatible mode
57cfg.sec_extras=false -- Samsung extras
58
59
60update_id=1 -- system update_id
61
62subscr={} -- event sessions (for UPnP notify engine)
63plugins={} -- external plugins (YouTube, Vimeo ...)
64profiles={} -- device profiles
65cache={} -- real URL cache for plugins
66cache_size=0
67
68if not cfg.feeds_path then cfg.feeds_path=cfg.playlists_path end
69
70-- create feeds directory
71if cfg.feeds_path~=cfg.playlists_path then os.execute('mkdir -p '..cfg.feeds_path) end
72
73-- load config, plugins and profiles
74load_plugins(cfg.plugin_path,'plugin')
75load_plugins(cfg.config_path,'config')
76
77dofile('xupnpd_mime.lua')
78
79if cfg.profiles then load_plugins(cfg.profiles,'profile') end
80
81dofile('xupnpd_m3u.lua')
82dofile('xupnpd_ssdp.lua')
83dofile('xupnpd_http.lua')
84
85-- download feeds from external sources (child process)
86function update_feeds_async()
87 local num=0
88 for i,j in ipairs(feeds) do
89 local plugin=plugins[ j[1] ]
90 if plugin and plugin.disabled~=true and plugin.updatefeed then
91 if plugin.updatefeed(j[2],j[3])==true then num=num+1 end
92 end
93 end
94
95 if num>0 then core.sendevent('reload') end
96
97end
98
99-- spawn child process for feeds downloading
100function update_feeds(what,sec)
101 core.fspawn(update_feeds_async)
102 core.timer(cfg.feeds_update_interval,what)
103end
104
105
106-- subscribe player for ContentDirectory events
107function subscribe(event,sid,callback,ttl)
108 local s=nil
109
110 if subscr[sid] then
111 s=subscr[sid]
112 s.timestamp=os.time()
113 else
114 if callback=='' then return end
115 s={}
116 subscr[sid]=s
117 s.event=event
118 s.sid=sid
119 s.callback=callback
120 s.timestamp=os.time()
121 s.ttl=tonumber(ttl)
122 s.seq=0
123 end
124
125 if cfg.debug>0 then print('subscribe: '..s.sid..', '..s.event..', '..s.callback) end
126
127end
128
129-- unsubscribe player
130function unsubscribe(sid)
131 if subscr[sid] then
132 subscr[sid]=nil
133
134 if cfg.debug>0 then print('unsubscribe: '..sid) end
135 end
136end
137
138--store to cache
139function cache_store(k,v)
140 local time=os.time()
141
142 local cc=cache[k]
143
144 if cc then cc.value=v cc.time=time return end
145
146 if cache_size>=cfg.cache_size then
147 local min_k=nil
148 local min_time=nil
149 for i,j in pairs(cache) do
150 if not min_time or min_time>j.time then min_k=i min_time=j.time end
151 end
152 if min_k then
153 if cfg.debug>0 then print('remove URL from cache (overflow): '..min_k) end
154 cache[min_k]=nil
155 cache_size=cache_size-1
156 end
157 end
158
159 local t={}
160 t.time=time
161 t.value=v
162 cache[k]=t
163 cache_size=cache_size+1
164end
165
166
167-- garbage collection
168function sys_gc(what,sec)
169
170 local t=os.time()
171
172 -- force unsubscribe
173 local g={}
174
175 for i,j in pairs(subscr) do
176 if os.difftime(t,j.timestamp)>=j.ttl then
177 table.insert(g,i)
178 end
179 end
180
181 for i,j in ipairs(g) do
182 subscr[j]=nil
183
184 if cfg.debug>0 then print('force unsubscribe (timeout): '..j) end
185 end
186
187 -- cache clear
188 g={}
189
190 for i,j in pairs(cache) do
191 if os.difftime(t,j.time)>=cfg.cache_ttl then
192 table.insert(g,i)
193 end
194 end
195
196 cache_size=cache_size-table.maxn(g)
197
198 for i,j in ipairs(g) do
199 cache[j]=nil
200
201 if cfg.debug>0 then print('remove URL from cache (timeout): '..j) end
202 end
203
204 core.timer(sec,what)
205end
206
207
208-- ContentDirectory event deliver (child process)
209function subscr_notify_iterate_tree(pls,tt)
210 if pls.elements then
211 table.insert(tt,pls.objid..','..update_id)
212
213 for i,j in ipairs(pls.elements) do
214 subscr_notify_iterate_tree(j,tt)
215 end
216 end
217end
218
219function subscr_notify_async(t)
220
221 local tt={}
222 subscr_notify_iterate_tree(playlist_data,tt)
223
224 local data=string.format(
225 '<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\"><e:property><SystemUpdateID>%s</SystemUpdateID><ContainerUpdateIDs>%s</ContainerUpdateIDs></e:property></e:propertyset>',
226 update_id,table.concat(tt,','))
227
228 for i,j in ipairs(t) do
229 if cfg.debug>0 then print('notify: '..j.callback..', sid='..j.sid..', seq='..j.seq) end
230 http.notify(j.callback,j.sid,data,j.seq)
231 end
232end
233
234
235-- reload all playlists
236function reload_playlist()
237 reload_playlists()
238 update_id=update_id+1
239
240 if update_id>100000 then update_id=1 end
241
242 if cfg.debug>0 then print('reload playlist, update_id='..update_id) end
243
244 if cfg.dlna_notify==true then
245 local t={}
246
247 for i,j in pairs(subscr) do
248 if j.event=='cds' then
249 table.insert(t, { ['callback']=j.callback, ['sid']=j.sid, ['seq']=j.seq } )
250 j.seq=j.seq+1
251 if j.seq>100000 then j.seq=0 end
252 end
253 end
254
255 if table.maxn(t)>0 then
256 core.fspawn(subscr_notify_async,t)
257 end
258 end
259end
260
261-- change child process status (for UI)
262function set_child_status(pid,status)
263 pid=tonumber(pid)
264 if childs[pid] then
265 childs[pid].status=status
266 childs[pid].time=os.time()
267 end
268end
269
270function get_drive_state(drive)
271 local s
272
273 local f=io.popen('/sbin/hdparm -C '..drive..' 2>/dev/null | grep -i state','r')
274
275 if f then
276 s=f:read('*a')
277 f:close()
278 end
279
280 return string.match(s,'drive state is:%s+(.+)%s+')
281end
282
283
284function profile_change(user_agent,req)
285 if not user_agent or user_agent=='' then return end
286
287 for name,profile in pairs(profiles) do
288 local match=profile.match
289
290 if profile.disabled~=true and match and match(user_agent,req) then
291
292 local options=profile.options
293 local mtypes=profile.mime_types
294
295 if options then for i,j in pairs(options) do cfg[i]=j end end
296
297 if mtypes then
298 if profile.replace_mime_types==true then
299 mime=mtypes
300 else
301 for i,j in pairs(mtypes) do mime[i]=j end
302 end
303 end
304
305 return name
306 end
307 end
308 return nil
309end
310
311
312-- event handlers
313events['SIGUSR1']=reload_playlist
314events['reload']=reload_playlist
315events['store']=cache_store
316events['sys_gc']=sys_gc
317events['subscribe']=subscribe
318events['unsubscribe']=unsubscribe
319events['update_feeds']=update_feeds
320events['status']=set_child_status
321events['config']=function() load_plugins(cfg.config_path,'config') cache={} cache_size=0 end
322events['remove_feed']=function(id) table.remove(feeds,tonumber(id)) end
323events['add_feed']=function(plugin,feed,name) table.insert(feeds,{[1]=plugin,[2]=feed,[3]=name}) end
324events['plugin']=function(name,status) if status=='on' then plugins[name].disabled=false else plugins[name].disabled=true end end
325events['profile']=function(name,status) if status=='on' then profiles[name].disabled=false else profiles[name].disabled=true end end
326events['bookmark']=function(objid,pos) local pls=find_playlist_object(objid) if pls then pls.bookmark=pos end end
327
328events['update_playlists']=
329function(what,sec)
330 if cfg.drive and cfg.drive~='' then
331 if get_drive_state(cfg.drive)=='active/idle' then
332 reload_playlist()
333 end
334 else
335 reload_playlist()
336 end
337
338 core.timer(cfg.playlists_update_interval,what)
339end
340
341
342if cfg.embedded==true then print=function () end end
343
344-- start garbage collection system
345core.timer(300,'sys_gc')
346
347http.timeout(cfg.http_timeout)
348http.user_agent(cfg.user_agent)
349
350-- start feeds update system
351if cfg.feeds_update_interval>0 then
352 core.timer(3,'update_feeds')
353end
354
355if cfg.playlists_update_interval>0 then
356 core.timer(cfg.playlists_update_interval,'update_playlists')
357end
358
359load_plugins(cfg.config_path..'postinit/','postinit')
360
361print("start "..cfg.log_ident)
362
363core.mainloop()
364
365print("stop "..cfg.log_ident)
366
367if cfg.daemon==true then os.execute('rm -f '..cfg.pid_file) end
Note: See TracBrowser for help on using the repository browser.