* Reintegrated fastindex support
[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