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