* 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