* libs/web: Added .leaf attribute to dispatching tree to stop dispatching at a certai...
[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         -- Load default translation
132         require("luci.i18n").loadc("default")
133         
134
135         if c and type(c.target) == "function" then
136                 dispatched = c
137                 stat, mod = pcall(require, c.module)
138                 if stat then
139                         luci.util.updfenv(c.target, mod)
140                 end
141                 
142                 stat, err = pcall(c.target)
143                 if not stat then
144                         error500(err)
145                 end
146         else
147                 error404()
148         end
149 end
150
151 -- Generates the dispatching tree
152 function createindex()
153         index = {}
154         local path = luci.sys.libpath() .. "/controller/"
155         local suff = ".lua"
156         
157         if pcall(require, "fastindex") then
158                 createindex_fastindex(path, suff)
159         else
160                 createindex_plain(path, suff)
161         end
162         
163         built_index = true
164 end
165
166 -- Uses fastindex to create the dispatching tree
167 function createindex_fastindex(path, suffix)    
168         local fi = fastindex.new("index")
169         fi.add(path .. "*" .. suffix)
170         fi.add(path .. "*/*" .. suffix)
171         fi.scan()
172         
173         for k, v in pairs(fi.indexes) do
174                 index[v[2]] = v[1]
175         end
176 end
177
178 -- Calls the index function of all available controllers
179 function createindex_plain(path, suffix)
180         local cachetime = nil 
181         
182         local controllers = luci.util.combine(
183                 luci.fs.glob(path .. "*" .. suffix) or {},
184                 luci.fs.glob(path .. "*/*" .. suffix) or {}
185         )
186         
187         if indexcache then
188                 cachetime = luci.fs.mtime(indexcache)
189                 
190                 if not cachetime then
191                         luci.fs.mkdir(indexcache)
192                         luci.fs.chmod(indexcache, "a=,u=rwx")
193                 end
194         end
195
196         if not cachetime then
197                 for i,c in ipairs(controllers) do
198                         c = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
199                         stat, mod = pcall(require, c)
200         
201                         if stat and mod and type(mod.index) == "function" then
202                                 index[c] = mod.index
203                                 
204                                 if indexcache then
205                                         luci.fs.writefile(indexcache .. "/" .. c, string.dump(mod.index))
206                                 end
207                         end
208                 end
209         else
210                 for i,c in ipairs(luci.fs.dir(indexcache)) do
211                         if c:sub(1) ~= "." then
212                                 index[c] = loadfile(indexcache .. "/" .. c)
213                         end
214                 end
215         end
216 end
217
218 -- Creates the dispatching tree from the index
219 function createtree()
220         if not built_index then
221                 createindex()
222         end
223         
224         require("luci.i18n")
225
226         for k, v in pairs(index) do
227                 luci.util.updfenv(v, _M)
228                 luci.util.extfenv(v, "_NAME", k)
229                 
230                 local stat, err = pcall(v)
231                 if not stat then
232                         error500(err)   
233                         os.exit(1)
234                 end
235         end
236         
237         built_tree = true
238 end
239
240 -- Shortcut for creating a dispatching node
241 function entry(path, target, title, order, add)
242         add = add or {}
243
244         local c = node(path)
245         c.target = target
246         c.title  = title
247         c.order  = order
248         c.module = getfenv(2)._NAME
249
250         for k,v in pairs(add) do
251                 c[k] = v
252         end
253
254         return c
255 end
256
257 -- Fetch a dispatching node
258 function node(...)
259         local c = tree
260
261         if arg[1] and type(arg[1]) == "table" then
262                 arg = arg[1]
263         end
264
265         for k,v in ipairs(arg) do
266                 if not c.nodes[v] then
267                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
268                 end
269
270                 c = c.nodes[v]
271         end
272
273         return c
274 end
275
276 -- Subdispatchers --
277 function alias(...)
278         local req = arg
279         return function()
280                 request = req
281                 dispatch()
282         end
283 end
284
285 function rewrite(n, ...)
286         local req = arg
287         return function()
288                 for i=1,n do 
289                         table.remove(request, 1)
290                 end
291                 
292                 for i,r in ipairs(req) do
293                         table.insert(request, i, r)
294                 end
295                 
296                 dispatch()
297         end
298 end
299
300 function call(name)
301         return function() getfenv()[name]() end
302 end
303
304 function template(name)
305         require("luci.template")
306         return function() luci.template.render(name) end
307 end
308
309 function cbi(model)
310         require("luci.cbi")
311         require("luci.template")
312
313         return function()
314                 local stat, res = pcall(luci.cbi.load, model)
315                 if not stat then
316                         error500(res)
317                         return true
318                 end
319
320                 local stat, err = pcall(res.parse, res)
321                 if not stat then
322                         error500(err)
323                         return true
324                 end
325
326                 luci.template.render("cbi/header")
327                 res:render()
328                 luci.template.render("cbi/footer")
329         end
330 end