* Core translation
[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.init")
28 require("luci.http")
29 require("luci.sys")
30 require("luci.fs")
31
32 -- Dirty OpenWRT fix
33 if (os.time() < 1000000000) then
34         os.execute('date -s '..os.date('%m%d%H%M%Y', luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua"))..' > /dev/null 2>&1')
35 end
36
37 -- Local dispatch database
38 local tree = {nodes={}}
39
40 -- Index table
41 local index = {}
42
43 -- Global request object
44 request = {}
45
46 -- Active dispatched node
47 dispatched = nil
48
49 -- Status fields
50 built_index = false
51 built_tree  = false
52
53 -- Fastindex
54 local fi
55
56
57 -- Builds a URL
58 function build_url(...)
59         return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
60 end
61
62 -- Prints an error message or renders the "error401" template if available
63 function error401(message)
64         message = message or "Unauthorized"
65
66         require("luci.template")
67         if not pcall(luci.template.render, "error401") then
68                 luci.http.prepare_content("text/plain")
69                 print(message)
70         end
71         return false
72 end
73
74 -- Sends a 404 error code and renders the "error404" template if available
75 function error404(message)
76         luci.http.status(404, "Not Found")
77         message = message or "Not Found"
78
79         require("luci.template")
80         if not pcall(luci.template.render, "error404") then
81                 luci.http.prepare_content("text/plain")
82                 print(message)
83         end
84         return false
85 end
86
87 -- Sends a 500 error code and renders the "error500" template if available
88 function error500(message)
89         luci.http.status(500, "Internal Server Error")
90
91         require("luci.template")
92         if not pcall(luci.template.render, "error500", {message=message}) then
93                 luci.http.prepare_content("text/plain")
94                 print(message)
95         end
96         return false
97 end
98
99 -- Creates a request object for dispatching
100 function httpdispatch()
101         local pathinfo = luci.http.env.PATH_INFO or ""
102
103         for node in pathinfo:gmatch("[^/]+") do
104                 table.insert(request, node)
105         end
106
107         dispatch()
108 end
109
110 -- Dispatches a request
111 function dispatch()
112         if not built_tree then
113                 createtree()
114         end
115
116         local c = tree
117         local track = {}
118
119         for i, s in ipairs(request) do
120                 c = c.nodes[s]
121                 if not c or c.leaf then
122                         break
123                 end
124
125                 for k, v in pairs(c) do
126                         track[k] = v
127                 end
128         end
129
130         if track.sysauth then
131                 local accs = track.sysauth
132                 accs = (type(accs) == "string") and {accs} or accs
133                 
134                 local function sysauth(user, password)
135                         return (luci.util.contains(accs, user)
136                                 and luci.sys.user.checkpasswd(user, password)) 
137                 end
138                 
139                 if not luci.http.basic_auth(sysauth) then
140                         error401()
141                         return
142                 end
143         end
144
145         if track.i18n then
146                 require("luci.i18n").loadc(track.i18n)
147         end
148
149         if track.setgroup then
150                 luci.sys.process.setgroup(track.setgroup)
151         end
152
153         if track.setuser then
154                 luci.sys.process.setuser(track.setuser)
155         end
156         
157         -- Init template engine
158         local tpl = require("luci.template")
159         tpl.viewns.translate   = function(...) return require("luci.i18n").translate(...) end
160         tpl.viewns.controller  = luci.http.dispatcher()
161         tpl.viewns.uploadctrl  = luci.http.dispatcher_upload() -- DEPRECATED
162         tpl.viewns.media       = luci.config.main.mediaurlbase
163         tpl.viewns.resource    = luci.config.main.resourcebase
164         tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. (luci.http.env.PATH_INFO or "")
165         
166
167         if c and type(c.target) == "function" then
168                 dispatched = c
169                 stat, mod = pcall(require, c.module)
170                 if stat then
171                         luci.util.updfenv(c.target, mod)
172                 end
173                 
174                 stat, err = pcall(c.target)
175                 if not stat then
176                         error500(err)
177                 end
178         else
179                 error404()
180         end
181 end
182
183 -- Generates the dispatching tree
184 function createindex()
185         index = {}
186         local path = luci.sys.libpath() .. "/controller/"
187         local suff = ".lua"
188         
189         if pcall(require, "luci.fastindex") then
190                 createindex_fastindex(path, suff)
191         else
192                 createindex_plain(path, suff)
193         end
194         
195         built_index = true
196 end
197
198 -- Uses fastindex to create the dispatching tree
199 function createindex_fastindex(path, suffix)    
200         if not fi then
201                 fi = luci.fastindex.new("index")
202                 fi.add(path .. "*" .. suffix)
203                 fi.add(path .. "*/*" .. suffix)
204         end
205         fi.scan()
206         
207         for k, v in pairs(fi.indexes) do
208                 index[v[2]] = v[1]
209         end
210 end
211
212 -- Calls the index function of all available controllers
213 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
214 function createindex_plain(path, suffix)
215         if built_index then
216                 return
217         end
218
219         local cache = nil 
220         
221         local controllers = luci.util.combine(
222                 luci.fs.glob(path .. "*" .. suffix) or {},
223                 luci.fs.glob(path .. "*/*" .. suffix) or {}
224         )
225         
226         if indexcache then
227                 cache = luci.fs.mtime(indexcache)
228                 
229                 if not cache then
230                         luci.fs.mkdir(indexcache)
231                         luci.fs.chmod(indexcache, "a=,u=rwx")
232                         cache = luci.fs.mtime(indexcache)
233                 end
234         end
235
236         for i,c in ipairs(controllers) do
237                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
238                 local cachefile
239                 local stime
240                 local ctime
241                 
242                 if cache then
243                         cachefile = indexcache .. "/" .. module
244                         stime = luci.fs.mtime(c) or 0
245                         ctime = luci.fs.mtime(cachefile) or 0
246                 end
247                 
248                 if not cache or stime > ctime then 
249                         stat, mod = pcall(require, module)
250         
251                         if stat and mod and type(mod.index) == "function" then
252                                 index[module] = mod.index
253                                 
254                                 if cache then
255                                         luci.fs.writefile(cachefile, luci.util.dump(mod.index))
256                                 end
257                         end
258                 else
259                         index[module] = loadfile(cachefile)
260                 end
261         end
262 end
263
264 -- Creates the dispatching tree from the index
265 function createtree()
266         if not built_index then
267                 createindex()
268         end
269         
270         require("luci.i18n")
271                 
272         -- Load default translation
273         luci.i18n.loadc("default")
274         
275         local scope = luci.util.clone(_G)
276         for k,v in pairs(_M) do
277                 if type(v) == "function" then
278                         scope[k] = v
279                 end
280         end
281
282         for k, v in pairs(index) do
283                 scope._NAME = k
284                 setfenv(v, scope)
285                 
286                 local stat, err = pcall(v)
287                 if not stat then
288                         error500(err)   
289                         os.exit(1)
290                 end
291         end
292         
293         built_tree = true
294 end
295
296 -- Reassigns a node to another position
297 function assign(path, clone, title, order)
298         local obj  = node(path)
299         obj.nodes  = nil
300         obj.module = nil
301         
302         obj.title = title
303         obj.order = order
304         
305         local c = tree
306         for k, v in ipairs(clone) do
307                 if not c.nodes[v] then
308                         c.nodes[v] = {nodes={}}
309                 end
310
311                 c = c.nodes[v]
312         end
313         
314         setmetatable(obj, {__index = c})
315         
316         return obj
317 end
318
319 -- Shortcut for creating a dispatching node
320 function entry(path, target, title, order)
321         local c = node(path)
322         
323         c.target = target
324         c.title  = title
325         c.order  = order
326         c.module = getfenv(2)._NAME
327
328         return c
329 end
330
331 -- Fetch a dispatching node
332 function node(...)
333         local c = tree
334
335         arg.n = nil
336         if arg[1] then
337                 if type(arg[1]) == "table" then
338                         arg = arg[1]
339                 end
340         end
341
342         for k,v in ipairs(arg) do
343                 if not c.nodes[v] then
344                         c.nodes[v] = {nodes={}}
345                 end
346
347                 c = c.nodes[v]
348         end
349
350         c.module = getfenv(2)._NAME
351         c.path = arg
352
353         return c
354 end
355
356 -- Subdispatchers --
357 function alias(...)
358         local req = arg
359         return function()
360                 request = req
361                 dispatch()
362         end
363 end
364
365 function rewrite(n, ...)
366         local req = arg
367         return function()
368                 for i=1,n do 
369                         table.remove(request, 1)
370                 end
371                 
372                 for i,r in ipairs(req) do
373                         table.insert(request, i, r)
374                 end
375                 
376                 dispatch()
377         end
378 end
379
380 function call(name)
381         return function() getfenv()[name]() end
382 end
383
384 function template(name)
385         require("luci.template")
386         return function() luci.template.render(name) end
387 end
388
389 function cbi(model)
390         require("luci.cbi")
391         require("luci.template")
392
393         return function()
394                 local stat, res = pcall(luci.cbi.load, model)
395                 if not stat then
396                         error500(res)
397                         return true
398                 end
399
400                 local stat, err = pcall(res.parse, res)
401                 if not stat then
402                         error500(err)
403                         return true
404                 end
405
406                 luci.template.render("cbi/header")
407                 res:render()
408                 luci.template.render("cbi/footer")
409         end
410 end