feda28d51e3f7a5e004205ac0ddf2efebee8e479
[project/luci.git] / libs / web / luasrc / dispatcher.lua
1 --[[
2 LuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26 module("luci.dispatcher", package.seeall)
27 require("luci.http")
28 require("luci.sys")
29 require("luci.fs")
30
31 -- Dirty OpenWRT fix
32 if (os.time() < luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua")) then
33         os.execute('date -s '..os.date('%m%d%H%M%Y', luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua"))..' > /dev/null 2>&1')
34 end
35
36 -- Local dispatch database
37 local tree = {nodes={}}
38
39 -- Index table
40 local index = {}
41
42 -- Global request object
43 request = {}
44
45 -- Active dispatched node
46 dispatched = nil
47
48 -- Status fields
49 built_index = false
50 built_tree  = false
51
52 -- Fastindex
53 local fi
54
55
56 -- Builds a URL
57 function build_url(...)
58         return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
59 end
60
61 -- Sends a 404 error code and renders the "error404" template if available
62 function error404(message)
63         luci.http.status(404, "Not Found")
64         message = message or "Not Found"
65
66         require("luci.template")
67         if not pcall(luci.template.render, "error404") then
68                 luci.http.prepare_content("text/plain")
69                 print(message)
70         end
71         return false
72 end
73
74 -- Sends a 500 error code and renders the "error500" template if available
75 function error500(message)
76         luci.http.status(500, "Internal Server Error")
77
78         require("luci.template")
79         if not pcall(luci.template.render, "error500", {message=message}) then
80                 luci.http.prepare_content("text/plain")
81                 print(message)
82         end
83         return false
84 end
85
86 -- Creates a request object for dispatching
87 function httpdispatch()
88         local pathinfo = luci.http.env.PATH_INFO or ""
89         local c = tree
90
91         for s in pathinfo:gmatch("([%w-]+)") do
92                 table.insert(request, s)
93         end
94
95         dispatch()
96 end
97
98 -- Dispatches a request
99 function dispatch()
100         if not built_tree then
101                 createtree()
102         end
103
104         local c = tree
105         local track = {}
106
107         for i, s in ipairs(request) do
108                 c = c.nodes[s]
109                 if not c or c.leaf then
110                         break
111                 end
112
113                 for k, v in pairs(c) do
114                         track[k] = v
115                 end
116         end
117
118
119         if track.i18n then
120                 require("luci.i18n").loadc(track.i18n)
121         end
122
123         if track.setgroup then
124                 luci.sys.process.setgroup(track.setgroup)
125         end
126
127         if track.setuser then
128                 luci.sys.process.setuser(track.setuser)
129         end
130         
131         -- Init template engine
132         local tpl = require("luci.template")
133         tpl.viewns.translate   = function(...) return require("luci.i18n").translate(...) end
134         tpl.viewns.controller  = luci.http.dispatcher()
135         tpl.viewns.uploadctrl  = luci.http.dispatcher_upload()
136         tpl.viewns.media       = luci.config.main.mediaurlbase
137         tpl.viewns.resource    = luci.config.main.resourcebase
138         tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
139         
140
141         if c and type(c.target) == "function" then
142                 dispatched = c
143                 stat, mod = pcall(require, c.module)
144                 if stat then
145                         luci.util.updfenv(c.target, mod)
146                 end
147                 
148                 stat, err = pcall(c.target)
149                 if not stat then
150                         error500(err)
151                 end
152         else
153                 error404()
154         end
155 end
156
157 -- Generates the dispatching tree
158 function createindex()
159         index = {}
160         local path = luci.sys.libpath() .. "/controller/"
161         local suff = ".lua"
162         
163         if pcall(require, "luci.fastindex") then
164                 createindex_fastindex(path, suff)
165         else
166                 createindex_plain(path, suff)
167         end
168         
169         built_index = true
170 end
171
172 -- Uses fastindex to create the dispatching tree
173 function createindex_fastindex(path, suffix)    
174         if not fi then
175                 fi = luci.fastindex.new("index")
176                 fi.add(path .. "*" .. suffix)
177                 fi.add(path .. "*/*" .. suffix)
178         end
179         fi.scan()
180         
181         for k, v in pairs(fi.indexes) do
182                 index[v[2]] = v[1]
183         end
184 end
185
186 -- Calls the index function of all available controllers
187 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
188 function createindex_plain(path, suffix)
189         if built_index then
190                 return
191         end
192
193         local cache = nil 
194         
195         local controllers = luci.util.combine(
196                 luci.fs.glob(path .. "*" .. suffix) or {},
197                 luci.fs.glob(path .. "*/*" .. suffix) or {}
198         )
199         
200         if indexcache then
201                 cache = luci.fs.mtime(indexcache)
202                 
203                 if not cache then
204                         luci.fs.mkdir(indexcache)
205                         luci.fs.chmod(indexcache, "a=,u=rwx")
206                         cache = luci.fs.mtime(indexcache)
207                 end
208         end
209
210         for i,c in ipairs(controllers) do
211                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
212                 local cachefile
213                 local stime
214                 local ctime
215                 
216                 if cache then
217                         cachefile = indexcache .. "/" .. module
218                         stime = luci.fs.mtime(c) or 0
219                         ctime = luci.fs.mtime(cachefile) or 0
220                 end
221                 
222                 if not cache or stime > ctime then 
223                         stat, mod = pcall(require, module)
224         
225                         if stat and mod and type(mod.index) == "function" then
226                                 index[module] = mod.index
227                                 
228                                 if cache then
229                                         luci.fs.writefile(cachefile, luci.util.dump(mod.index))
230                                 end
231                         end
232                 else
233                         index[module] = loadfile(cachefile)
234                 end
235         end
236 end
237
238 -- Creates the dispatching tree from the index
239 function createtree()
240         if not built_index then
241                 createindex()
242         end
243         
244         require("luci.i18n")
245                 
246         -- Load default translation
247         luci.i18n.loadc("default")
248         
249         local scope = luci.util.clone(_G)
250         for k,v in pairs(_M) do
251                 if type(v) == "function" then
252                         scope[k] = v
253                 end
254         end
255
256         for k, v in pairs(index) do
257                 scope._NAME = k
258                 setfenv(v, scope)
259                 
260                 local stat, err = pcall(v)
261                 if not stat then
262                         error500(err)   
263                         os.exit(1)
264                 end
265         end
266         
267         built_tree = true
268 end
269
270 -- Shortcut for creating a dispatching node
271 function entry(path, target, title, order, add)
272         add = add or {}
273
274         local c = node(path)
275         c.target = target
276         c.title  = title
277         c.order  = order
278         c.module = getfenv(2)._NAME
279
280         for k,v in pairs(add) do
281                 c[k] = v
282         end
283
284         return c
285 end
286
287 -- Fetch a dispatching node
288 function node(...)
289         local c = tree
290
291         if arg[1] and type(arg[1]) == "table" then
292                 arg = arg[1]
293         end
294
295         for k,v in ipairs(arg) do
296                 if not c.nodes[v] then
297                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
298                 end
299
300                 c = c.nodes[v]
301         end
302
303         return c
304 end
305
306 -- Subdispatchers --
307 function alias(...)
308         local req = arg
309         return function()
310                 request = req
311                 dispatch()
312         end
313 end
314
315 function rewrite(n, ...)
316         local req = arg
317         return function()
318                 for i=1,n do 
319                         table.remove(request, 1)
320                 end
321                 
322                 for i,r in ipairs(req) do
323                         table.insert(request, i, r)
324                 end
325                 
326                 dispatch()
327         end
328 end
329
330 function call(name)
331         return function() getfenv()[name]() end
332 end
333
334 function template(name)
335         require("luci.template")
336         return function() luci.template.render(name) end
337 end
338
339 function cbi(model)
340         require("luci.cbi")
341         require("luci.template")
342
343         return function()
344                 local stat, res = pcall(luci.cbi.load, model)
345                 if not stat then
346                         error500(res)
347                         return true
348                 end
349
350                 local stat, err = pcall(res.parse, res)
351                 if not stat then
352                         error500(err)
353                         return true
354                 end
355
356                 luci.template.render("cbi/header")
357                 res:render()
358                 luci.template.render("cbi/footer")
359         end
360 end