c7e1ed6fe2b4a6d30f64a563680ac7ac4c29bb46
[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         tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
131         
132
133         if c and type(c.target) == "function" then
134                 dispatched = c
135                 stat, mod = pcall(require, c.module)
136                 if stat then
137                         luci.util.updfenv(c.target, mod)
138                 end
139                 
140                 stat, err = pcall(c.target)
141                 if not stat then
142                         error500(err)
143                 end
144         else
145                 error404()
146         end
147 end
148
149 -- Generates the dispatching tree
150 function createindex()
151         index = {}
152         local path = luci.sys.libpath() .. "/controller/"
153         local suff = ".lua"
154         
155         --[[if pcall(require, "fastindex") then
156                 createindex_fastindex(path, suff)
157         else
158                 createindex_plain(path, suff)
159         end]]--
160         
161         createindex_plain(path, suff)
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 cache = 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                 cache = luci.fs.mtime(indexcache)
189                 
190                 if not cache then
191                         luci.fs.mkdir(indexcache)
192                         luci.fs.chmod(indexcache, "a=,u=rwx")
193                         cache = luci.fs.mtime(indexcache)
194                 end
195         end
196
197         for i,c in ipairs(controllers) do
198                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
199                 local cachefile = indexcache .. "/" .. module
200                 local stime
201                 local ctime
202                 
203                 if cache then
204                         stime = luci.fs.mtime(c) or 0
205                         ctime = luci.fs.mtime(cachefile) or 0
206                 end
207                 
208                 if not cache or stime > ctime then 
209                         stat, mod = pcall(require, module)
210         
211                         if stat and mod and type(mod.index) == "function" then
212                                 index[module] = mod.index
213                                 
214                                 if cache then
215                                         luci.fs.writefile(cachefile, luci.util.dump(mod.index))
216                                 end
217                         end
218                 else
219                         index[module] = loadfile(cachefile)
220                 end
221         end
222 end
223
224 -- Creates the dispatching tree from the index
225 function createtree()
226         if not built_index then
227                 createindex()
228         end
229         
230         require("luci.i18n")
231                 
232         -- Load default translation
233         luci.i18n.loadc("default")
234         
235         local scope = luci.util.clone(_G)
236         for k,v in pairs(_M) do
237                 if type(v) == "function" then
238                         scope[k] = v
239                 end
240         end
241
242         for k, v in pairs(index) do
243                 scope._NAME = k
244                 setfenv(v, scope)
245                 
246                 local stat, err = pcall(v)
247                 if not stat then
248                         error500(err)   
249                         os.exit(1)
250                 end
251         end
252         
253         built_tree = true
254 end
255
256 -- Shortcut for creating a dispatching node
257 function entry(path, target, title, order, add)
258         add = add or {}
259
260         local c = node(path)
261         c.target = target
262         c.title  = title
263         c.order  = order
264         c.module = getfenv(2)._NAME
265
266         for k,v in pairs(add) do
267                 c[k] = v
268         end
269
270         return c
271 end
272
273 -- Fetch a dispatching node
274 function node(...)
275         local c = tree
276
277         if arg[1] and type(arg[1]) == "table" then
278                 arg = arg[1]
279         end
280
281         for k,v in ipairs(arg) do
282                 if not c.nodes[v] then
283                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
284                 end
285
286                 c = c.nodes[v]
287         end
288
289         return c
290 end
291
292 -- Subdispatchers --
293 function alias(...)
294         local req = arg
295         return function()
296                 request = req
297                 dispatch()
298         end
299 end
300
301 function rewrite(n, ...)
302         local req = arg
303         return function()
304                 for i=1,n do 
305                         table.remove(request, 1)
306                 end
307                 
308                 for i,r in ipairs(req) do
309                         table.insert(request, i, r)
310                 end
311                 
312                 dispatch()
313         end
314 end
315
316 function call(name)
317         return function() getfenv()[name]() end
318 end
319
320 function template(name)
321         require("luci.template")
322         return function() luci.template.render(name) end
323 end
324
325 function cbi(model)
326         require("luci.cbi")
327         require("luci.template")
328
329         return function()
330                 local stat, res = pcall(luci.cbi.load, model)
331                 if not stat then
332                         error500(res)
333                         return true
334                 end
335
336                 local stat, err = pcall(res.parse, res)
337                 if not stat then
338                         error500(err)
339                         return true
340                 end
341
342                 luci.template.render("cbi/header")
343                 res:render()
344                 luci.template.render("cbi/footer")
345         end
346 end