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

Last change on this file since 27918 was 27918, checked in by Stephan, 10 years ago

[xupnpd] fixes

File size: 9.4 KB
Line 
1-- Copyright (C) 2011-2013 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={}
109    subscr[sid]=s
110
111    s.event=event
112    s.sid=sid
113    s.callback=callback
114    s.timestamp=os.time()
115    s.ttl=tonumber(ttl)
116    s.seq=0
117
118    if cfg.debug>0 then print('subscribe: '..sid..', '..event..', '..callback) end
119
120end
121
122-- unsubscribe player
123function unsubscribe(sid)
124    if subscr[sid] then
125        subscr[sid]=nil
126
127        if cfg.debug>0 then print('unsubscribe: '..sid) end
128    end
129end
130
131--store to cache
132function cache_store(k,v)
133    local time=os.time()
134
135    local cc=cache[k]
136
137    if cc then cc.value=v cc.time=time return end
138
139    if cache_size>=cfg.cache_size then
140        local min_k=nil
141        local min_time=nil
142        for i,j in pairs(cache) do
143            if not min_time or min_time>j.time then min_k=i min_time=j.time end
144        end
145        if min_k then
146            if cfg.debug>0 then print('remove URL from cache (overflow): '..min_k) end
147            cache[min_k]=nil
148            cache_size=cache_size-1
149        end
150    end
151
152    local t={}
153    t.time=time
154    t.value=v
155    cache[k]=t
156    cache_size=cache_size+1
157end
158
159
160-- garbage collection
161function sys_gc(what,sec)
162
163    local t=os.time()
164
165    -- force unsubscribe
166    local g={}
167
168    for i,j in pairs(subscr) do
169        if os.difftime(t,j.timestamp)>=j.ttl then
170            table.insert(g,i)
171        end
172    end
173
174    for i,j in ipairs(g) do
175        subscr[j]=nil
176
177        if cfg.debug>0 then print('force unsubscribe (timeout): '..j) end
178    end
179
180    -- cache clear
181    g={}
182
183    for i,j in pairs(cache) do
184        if os.difftime(t,j.time)>=cfg.cache_ttl then
185            table.insert(g,i)
186        end
187    end
188
189    cache_size=cache_size-table.maxn(g)
190
191    for i,j in ipairs(g) do
192        cache[j]=nil
193
194        if cfg.debug>0 then print('remove URL from cache (timeout): '..j) end
195    end
196
197    core.timer(sec,what)
198end
199
200
201-- ContentDirectory event deliver (child process)
202function subscr_notify_iterate_tree(pls,tt)
203    if pls.elements then
204        table.insert(tt,pls.objid..','..update_id)
205
206        for i,j in ipairs(pls.elements) do
207            subscr_notify_iterate_tree(j,tt)
208        end
209    end   
210end
211
212function subscr_notify_async(t)
213
214    local tt={}
215    subscr_notify_iterate_tree(playlist_data,tt)
216
217    local data=string.format(
218        '<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\"><e:property><SystemUpdateID>%s</SystemUpdateID><ContainerUpdateIDs>%s</ContainerUpdateIDs></e:property></e:propertyset>',
219        update_id,table.concat(tt,','))
220
221    for i,j in ipairs(t) do
222        if cfg.debug>0 then print('notify: '..j.callback..', sid='..j.sid..', seq='..j.seq) end
223        http.notify(j.callback,j.sid,data,j.seq)
224    end
225end
226
227
228-- reload all playlists
229function reload_playlist()
230    reload_playlists()
231    update_id=update_id+1
232
233    if update_id>100000 then update_id=1 end
234
235    if cfg.debug>0 then print('reload playlist, update_id='..update_id) end
236
237    if cfg.dlna_notify==true then
238        local t={}
239
240        for i,j in pairs(subscr) do
241            if j.event=='cds' then
242                table.insert(t, { ['callback']=j.callback, ['sid']=j.sid, ['seq']=j.seq } )
243                j.seq=j.seq+1
244                if j.seq>100000 then j.seq=0 end
245            end
246        end
247
248        if table.maxn(t)>0 then
249            core.fspawn(subscr_notify_async,t)
250        end
251    end
252end
253
254-- change child process status (for UI)
255function set_child_status(pid,status)
256    pid=tonumber(pid)
257    if childs[pid] then
258        childs[pid].status=status
259        childs[pid].time=os.time()
260    end
261end
262
263function get_drive_state(drive)
264    local s
265
266    local f=io.popen('/sbin/hdparm -C '..drive..' 2>/dev/null | grep -i state','r')
267
268    if f then
269        s=f:read('*a')
270        f:close()
271    end
272
273    return string.match(s,'drive state is:%s+(.+)%s+')
274end
275
276
277function profile_change(user_agent,req)
278    if not user_agent or user_agent=='' then return end
279
280    for name,profile in pairs(profiles) do
281        local match=profile.match
282
283        if profile.disabled~=true and  match and match(user_agent,req) then
284
285            local options=profile.options
286            local mtypes=profile.mime_types
287
288            if options then for i,j in pairs(options) do cfg[i]=j end end
289
290            if mtypes then
291                if profile.replace_mime_types==true then
292                    mime=mtypes
293                else
294                    for i,j in pairs(mtypes) do mime[i]=j end
295                end
296            end
297
298            return name
299        end
300    end
301    return nil
302end
303
304
305-- event handlers
306events['SIGUSR1']=reload_playlist
307events['reload']=reload_playlist
308events['store']=cache_store
309events['sys_gc']=sys_gc
310events['subscribe']=subscribe
311events['unsubscribe']=unsubscribe
312events['update_feeds']=update_feeds
313events['status']=set_child_status
314events['config']=function() load_plugins(cfg.config_path,'config') cache={} cache_size=0 end
315events['remove_feed']=function(id) table.remove(feeds,tonumber(id)) end
316events['add_feed']=function(plugin,feed,name) table.insert(feeds,{[1]=plugin,[2]=feed,[3]=name}) end
317events['plugin']=function(name,status) if status=='on' then plugins[name].disabled=false else plugins[name].disabled=true end end
318events['profile']=function(name,status) if status=='on' then profiles[name].disabled=false else profiles[name].disabled=true end end
319events['bookmark']=function(objid,pos) local pls=find_playlist_object(objid) if pls then pls.bookmark=pos end end
320
321events['update_playlists']=
322function(what,sec)
323    if cfg.drive and cfg.drive~='' then
324        if get_drive_state(cfg.drive)=='active/idle' then
325            reload_playlist()
326        end
327    else
328        reload_playlist()
329    end
330
331    core.timer(cfg.playlists_update_interval,what)
332end
333
334
335if cfg.embedded==true then print=function () end end
336
337-- start garbage collection system
338core.timer(300,'sys_gc')
339
340http.timeout(cfg.http_timeout)
341http.user_agent(cfg.user_agent)
342
343-- start feeds update system
344if cfg.feeds_update_interval>0 then
345    core.timer(15,'update_feeds')
346end
347
348if cfg.playlists_update_interval>0 then
349    core.timer(cfg.playlists_update_interval,'update_playlists')
350end
351
352load_plugins(cfg.config_path..'postinit/','postinit')
353
354print("start "..cfg.log_ident)
355
356core.mainloop()
357
358print("stop "..cfg.log_ident)
359
360if cfg.daemon==true then os.execute('rm -f '..cfg.pid_file) end
Note: See TracBrowser for help on using the repository browser.