* Added cache to createindex_plain
[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
141                 stat, err = pcall(c.target)
142                 if not stat then
143                         error500(err)
144                 end
145         else
146                 error404()
147         end
148 end
149
150 -- Generates the dispatching tree
151 function createindex()
152         index = {}
153         local path = luci.sys.libpath() .. "/controller/"
154         local suff = ".lua"
155         
156         if pcall(require, "fastindex") then
157                 createindex_fastindex(path, suff)
158         else
159                 createindex_plain(path, suff)
160         end
161         
162         built_index = true
163 end
164
165 -- Uses fastindex to create the dispatching tree
166 function createindex_fastindex(path, suffix)    
167         local fi = fastindex.new("index")
168         fi.add(path .. "*" .. suffix)
169         fi.add(path .. "*/*" .. suffix)
170         fi.scan()
171         
172         for k, v in pairs(fi.indexes) do
173                 index[v[2]] = v[1]
174         end
175 end
176
177 -- Calls the index function of all available controllers
178 function createindex_plain(path, suffix)
179         local cachetime = nil 
180         
181         local controllers = luci.util.combine(
182                 luci.fs.glob(path .. "*" .. suffix) or {},
183                 luci.fs.glob(path .. "*/*" .. suffix) or {}
184         )
185         
186         if indexcache then
187                 cachetime = luci.fs.mtime(indexcache)
188                 
189                 if not cachetime then
190                         luci.fs.mkdir(indexcache)
191                         luci.fs.chmod(indexcache, "a=,u=rwx")
192                 end
193         end
194
195         if not cachetime or luci.fs.mtime(path) > cachetime then
196                 for i,c in ipairs(controllers) do
197                         c = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
198                         stat, mod = pcall(require, c)
199         
200                         if stat and mod and type(mod.index) == "function" then
201                                 index[c] = mod.index
202                                 
203                                 if indexcache then
204                                         luci.fs.writefile(indexcache .. "/" .. c, string.dump(mod.index))
205                                 end
206                         end
207                 end
208                 if indexcache then
209                         luci.fs.unlink(indexcache .. "/.index")
210                         luci.fs.writefile(indexcache .. "/.index", "")
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                 
230                 local stat, mod = pcall(require, k)
231                 if stat then            
232                         luci.util.updfenv(v, mod)
233                 end
234                 
235                 pcall(v)
236         end
237         
238         built_tree = true
239 end
240
241 -- Shortcut for creating a dispatching node
242 function entry(path, target, title, order, add)
243         add = add or {}
244
245         local c = node(path)
246         c.target = target
247         c.title  = title
248         c.order  = order
249
250         for k,v in pairs(add) do
251                 c[k] = v
252         end
253
254         return c
255 end
256
257 -- Fetch a dispatching node
258 function node(...)
259         local c = tree
260
261         if arg[1] and type(arg[1]) == "table" then
262                 arg = arg[1]
263         end
264
265         for k,v in ipairs(arg) do
266                 if not c.nodes[v] then
267                         c.nodes[v] = {nodes={}}
268                 end
269
270                 c = c.nodes[v]
271         end
272
273         return c
274 end
275
276 -- Subdispatchers --
277 function alias(...)
278         local req = arg
279         return function()
280                 request = req
281                 dispatch()
282         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