e9d3b24d1b8094ac691ff63b164996a12d8bfd3f
[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 -- Local dispatch database
32 local tree = {nodes={}}
33
34 -- Index table
35 local index = {}
36
37 -- Global request object
38 request = {}
39
40 -- Active dispatched node
41 dispatched = nil
42
43 -- Status fields
44 built_index = false
45 built_tree  = false
46
47
48 -- Builds a URL
49 function build_url(...)
50         return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
51 end
52
53 -- Sends a 404 error code and renders the "error404" template if available
54 function error404(message)
55         luci.http.status(404, "Not Found")
56         message = message or "Not Found"
57
58         require("luci.template")
59         if not pcall(luci.template.render, "error404") then
60                 luci.http.prepare_content("text/plain")
61                 print(message)
62         end
63         return false
64 end
65
66 -- Sends a 500 error code and renders the "error500" template if available
67 function error500(message)
68         luci.http.status(500, "Internal Server Error")
69
70         require("luci.template")
71         if not pcall(luci.template.render, "error500", {message=message}) then
72                 luci.http.prepare_content("text/plain")
73                 print(message)
74         end
75         return false
76 end
77
78 -- Creates a request object for dispatching
79 function httpdispatch()
80         local pathinfo = luci.http.env.PATH_INFO or ""
81         local c = tree
82
83         for s in pathinfo:gmatch("([%w-]+)") do
84                 table.insert(request, s)
85         end
86
87         dispatch()
88 end
89
90 -- Dispatches a request
91 function dispatch()
92         if not built_tree then
93                 createtree()
94         end
95
96         local c = tree
97         local track = {}
98
99         for i, s in ipairs(request) do
100                 c = c.nodes[s]
101                 if not c or c.leaf then
102                         break
103                 end
104
105                 for k, v in pairs(c) do
106                         track[k] = v
107                 end
108         end
109
110
111         if track.i18n then
112                 require("luci.i18n").loadc(track.i18n)
113         end
114
115         if track.setgroup then
116                 luci.sys.process.setgroup(track.setgroup)
117         end
118
119         if track.setuser then
120                 luci.sys.process.setuser(track.setuser)
121         end
122         
123         -- Init template engine
124         local tpl = require("luci.template")
125         tpl.viewns.translate  = function(...) return require("luci.i18n").translate(...) end
126         tpl.viewns.controller = luci.http.dispatcher()
127         tpl.viewns.uploadctrl = luci.http.dispatcher_upload()
128         tpl.viewns.media      = luci.config.main.mediaurlbase
129         tpl.viewns.resource   = luci.config.main.resourcebase
130         
131
132         if c and type(c.target) == "function" then
133                 dispatched = c
134                 stat, mod = pcall(require, c.module)
135                 if stat then
136                         luci.util.updfenv(c.target, mod)
137                 end
138                 
139                 stat, err = pcall(c.target)
140                 if not stat then
141                         error500(err)
142                 end
143         else
144                 error404()
145         end
146 end
147
148 -- Generates the dispatching tree
149 function createindex()
150         index = {}
151         local path = luci.sys.libpath() .. "/controller/"
152         local suff = ".lua"
153         
154         --[[if pcall(require, "fastindex") then
155                 createindex_fastindex(path, suff)
156         else
157                 createindex_plain(path, suff)
158         end]]--
159         
160         createindex_plain(path, suff)
161         
162         built_index = true
163 end
164
165 -- Uses fastindex to create the dispatching tree
166 function createindex_fastindex(path, suffix)    
167         local fi = fastindex.new("index")
168         fi.add(path .. "*" .. suffix)
169         fi.add(path .. "*/*" .. suffix)
170         fi.scan()
171         
172         for k, v in pairs(fi.indexes) do
173                 index[v[2]] = v[1]
174         end
175 end
176
177 -- Calls the index function of all available controllers
178 function createindex_plain(path, suffix)
179         local cachetime = nil 
180         
181         local controllers = luci.util.combine(
182                 luci.fs.glob(path .. "*" .. suffix) or {},
183                 luci.fs.glob(path .. "*/*" .. suffix) or {}
184         )
185         
186         if indexcache then
187                 cachetime = luci.fs.mtime(indexcache)
188                 
189                 if not cachetime then
190                         luci.fs.mkdir(indexcache)
191                         luci.fs.chmod(indexcache, "a=,u=rwx")
192                 end
193         end
194
195         if not cachetime then
196                 for i,c in ipairs(controllers) do
197                         c = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
198                         stat, mod = pcall(require, c)
199         
200                         if stat and mod and type(mod.index) == "function" then
201                                 index[c] = mod.index
202                                 
203                                 if indexcache then
204                                         luci.fs.writefile(indexcache .. "/" .. c, string.dump(mod.index))
205                                 end
206                         end
207                 end
208         else
209                 for i,c in ipairs(luci.fs.dir(indexcache)) do
210                         if c:sub(1) ~= "." then
211                                 index[c] = loadfile(indexcache .. "/" .. c)
212                         end
213                 end
214         end
215 end
216
217 -- Creates the dispatching tree from the index
218 function createtree()
219         if not built_index then
220                 createindex()
221         end
222         
223         require("luci.i18n")
224                 
225         -- Load default translation
226         luci.i18n.loadc("default")
227         
228         local scope = _G
229         for k,v in pairs(_M) do
230                 if type(v) == "function" then
231                         scope[k] = v
232                 end
233         end
234
235         for k, v in pairs(index) do
236                 scope._NAME = k
237                 setfenv(v, scope)
238                 
239                 local stat, err = pcall(v)
240                 if not stat then
241                         error500(err)   
242                         os.exit(1)
243                 end
244         end
245         
246         built_tree = true
247 end
248
249 -- Shortcut for creating a dispatching node
250 function entry(path, target, title, order, add)
251         add = add or {}
252
253         local c = node(path)
254         c.target = target
255         c.title  = title
256         c.order  = order
257         c.module = getfenv(2)._NAME
258
259         for k,v in pairs(add) do
260                 c[k] = v
261         end
262
263         return c
264 end
265
266 -- Fetch a dispatching node
267 function node(...)
268         local c = tree
269
270         if arg[1] and type(arg[1]) == "table" then
271                 arg = arg[1]
272         end
273
274         for k,v in ipairs(arg) do
275                 if not c.nodes[v] then
276                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
277                 end
278
279                 c = c.nodes[v]
280         end
281
282         return c
283 end
284
285 -- Subdispatchers --
286 function alias(...)
287         local req = arg
288         return function()
289                 request = req
290                 dispatch()
291         end
292 end
293
294 function rewrite(n, ...)
295         local req = arg
296         return function()
297                 for i=1,n do 
298                         table.remove(request, 1)
299                 end
300                 
301                 for i,r in ipairs(req) do
302                         table.insert(request, i, r)
303                 end
304                 
305                 dispatch()
306         end
307 end
308
309 function call(name)
310         return function() getfenv()[name]() end
311 end
312
313 function template(name)
314         require("luci.template")
315         return function() luci.template.render(name) end
316 end
317
318 function cbi(model)
319         require("luci.cbi")
320         require("luci.template")
321
322         return function()
323                 local stat, res = pcall(luci.cbi.load, model)
324                 if not stat then
325                         error500(res)
326                         return true
327                 end
328
329                 local stat, err = pcall(res.parse, res)
330                 if not stat then
331                         error500(err)
332                         return true
333                 end
334
335                 luci.template.render("cbi/header")
336                 res:render()
337                 luci.template.render("cbi/footer")
338         end
339 end