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