Assign correct path attribute to nodes
[project/luci.git] / libs / web / luasrc / dispatcher.lua
index 0665ce4..5fb50dc 100644 (file)
@@ -108,6 +108,7 @@ function httpdispatch(request)
 
        local stat, err = util.copcall(dispatch, context.request)
        if not stat then
+               luci.util.perror(err)
                error500(err)
        end
 
@@ -133,7 +134,8 @@ function dispatch(request)
 
        local track = {}
        local args = {}
-       context.args = args
+       ctx.args = args
+       ctx.requestargs = ctx.requestargs or args
        local n
 
        for i, s in ipairs(request) do
@@ -161,8 +163,20 @@ function dispatch(request)
        end
 
        -- Init template engine
-       if not track.notemplate then
+       if (c and c.index) or not track.notemplate then
                local tpl = require("luci.template")
+               local media = track.mediaurlbase or luci.config.main.mediaurlbase
+               if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
+                       media = nil
+                       for name, theme in pairs(luci.config.themes) do
+                               if name:sub(1,1) ~= "." and pcall(tpl.Template,
+                                "themes/%s/header" % fs.basename(theme)) then
+                                       media = theme
+                               end
+                       end
+                       assert(media, "No valid theme found")
+               end
+
                local viewns = setmetatable({}, {__index=_G})
                tpl.context.viewns = viewns
                viewns.write       = luci.http.write
@@ -170,7 +184,8 @@ function dispatch(request)
                viewns.translate   = function(...) return require("luci.i18n").translate(...) end
                viewns.striptags   = util.striptags
                viewns.controller  = luci.http.getenv("SCRIPT_NAME")
-               viewns.media       = luci.config.main.mediaurlbase
+               viewns.media       = media
+               viewns.theme       = fs.basename(media)
                viewns.resource    = luci.config.main.resourcebase
                viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
        end
@@ -219,9 +234,20 @@ function dispatch(request)
                luci.sys.process.setuser(track.setuser)
        end
 
-       if c and type(c.target) == "function" then
-               context.dispatched = c
+       if c and (c.index or type(c.target) == "function") then
+               ctx.dispatched = c
+               ctx.requested = ctx.requested or ctx.dispatched
+       end
 
+       if c and c.index then
+               local tpl = require "luci.template"
+
+               if util.copcall(tpl.render, "indexer", {}) then
+                       return true
+               end
+       end
+
+       if c and type(c.target) == "function" then
                util.copcall(function()
                        local oldenv = getfenv(c.target)
                        local module = require(c.module)
@@ -274,28 +300,35 @@ end
 -- @param path         Controller base directory
 -- @param suffix       Controller file suffix
 function createindex_plain(path, suffix)
+       local controllers = util.combine(
+               luci.fs.glob(path .. "*" .. suffix) or {},
+               luci.fs.glob(path .. "*/*" .. suffix) or {}
+       )
+
        if indexcache then
                local cachedate = fs.mtime(indexcache)
-               if cachedate and cachedate > fs.mtime(path) then
+               if cachedate then
+                       local realdate = 0
+                       for _, obj in ipairs(controllers) do
+                               local omtime = fs.mtime(path .. "/" .. obj)
+                               realdate = (omtime and omtime > realdate) and omtime or realdate
+                       end
 
-                       assert(
-                               sys.process.info("uid") == fs.stat(indexcache, "uid")
-                               and fs.stat(indexcache, "mode") == "rw-------",
-                               "Fatal: Indexcache is not sane!"
-                       )
+                       if cachedate > realdate then
+                               assert(
+                                       sys.process.info("uid") == fs.stat(indexcache, "uid")
+                                       and fs.stat(indexcache, "mode") == "rw-------",
+                                       "Fatal: Indexcache is not sane!"
+                               )
 
-                       index = loadfile(indexcache)()
-                       return index
+                               index = loadfile(indexcache)()
+                               return index
+                       end
                end
        end
 
        index = {}
 
-       local controllers = util.combine(
-               luci.fs.glob(path .. "*" .. suffix) or {},
-               luci.fs.glob(path .. "*/*" .. suffix) or {}
-       )
-
        for i,c in ipairs(controllers) do
                local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
                local mod = require(module)
@@ -321,9 +354,11 @@ function createtree()
 
        local ctx  = context
        local tree = {nodes={}}
+       local modi = {}
 
        ctx.treecache = setmetatable({}, {__mode="v"})
        ctx.tree = tree
+       ctx.modifiers = modi
 
        -- Load default translation
        require "luci.i18n".loadc("default")
@@ -336,9 +371,30 @@ function createtree()
                v()
        end
 
+       local function modisort(a,b)
+               return modi[a].order < modi[b].order
+       end
+
+       for _, v in util.spairs(modi, modisort) do
+               scope._NAME = v.module
+               setfenv(v.func, scope)
+               v.func()
+       end
+
        return tree
 end
 
+--- Register a tree modifier.
+-- @param      func    Modifier function
+-- @param      order   Modifier order value (optional)
+function modifier(func, order)
+       context.modifiers[#context.modifiers+1] = {
+               func = func,
+               order = order or 0,
+               module = getfenv(2)._NAME
+       }
+end
+
 --- Clone a node of the dispatching tree to another position.
 -- @param      path    Virtual path destination
 -- @param      clone   Virtual path source
@@ -382,7 +438,6 @@ function node(...)
        local c = _create_node({...})
 
        c.module = getfenv(2)._NAME
-       c.path = arg
        c.auto = nil
 
        return c
@@ -398,10 +453,11 @@ function _create_node(path, cache)
        local c = cache[name]
 
        if not c then
+               local new = {nodes={}, auto=true, path=util.clone(path)}
                local last = table.remove(path)
+
                c = _create_node(path, cache)
 
-               local new = {nodes={}, auto=true}
                c.nodes[last] = new
                cache[name] = new
 
@@ -416,8 +472,12 @@ end
 --- Create a redirect to another dispatching node.
 -- @param      ...             Virtual path destination
 function alias(...)
-       local req = arg
-       return function()
+       local req = {...}
+       return function(...)
+               for _, r in ipairs({...}) do
+                       req[#req+1] = r
+               end
+
                dispatch(req)
        end
 end
@@ -426,17 +486,23 @@ end
 -- @param      n               Number of path values to replace
 -- @param      ...             Virtual path to replace removed path values with
 function rewrite(n, ...)
-       local req = arg
-       return function()
+       local req = {...}
+       return function(...)
+               local dispatched = util.clone(context.dispatched)
+
                for i=1,n do
-                       table.remove(context.path, 1)
+                       table.remove(dispatched, 1)
                end
 
-               for i,r in ipairs(req) do
-                       table.insert(context.path, i, r)
+               for i, r in ipairs(req) do
+                       table.insert(dispatched, i, r)
                end
 
-               dispatch()
+               for _, r in ipairs({...}) do
+                       dispatched[#dispatched+1] = r
+               end
+
+               dispatch(dispatched)
        end
 end
 
@@ -445,7 +511,13 @@ end
 -- @param      ...             Additional parameters passed to the function
 function call(name, ...)
        local argv = {...}
-       return function() return getfenv()[name](unpack(argv)) end
+       return function(...)
+               if #argv > 0 then 
+                       return getfenv()[name](unpack(argv), ...)
+               else
+                       return getfenv()[name](...)
+               end
+       end
 end
 
 --- Create a template render dispatching target.
@@ -458,80 +530,59 @@ function template(name)
 end
 
 --- Create a CBI model dispatching target.
--- @param      model   CBI model tpo be rendered
-function cbi(model)
+-- @param      model   CBI model to be rendered
+function cbi(model, config)
+       config = config or {}
        return function(...)
                require("luci.cbi")
                require("luci.template")
+               local http = require "luci.http"
 
                maps = luci.cbi.load(model, ...)
 
-               local uploads    = { }
-               local has_upload = false
-
-               for _, map in ipairs(maps) do
-                       if map.upload_fields then
-                               has_upload = true
-                               for _, field in ipairs(map.upload_fields) do
-                                       uploads[
-                                               field.config .. '.' ..
-                                               field.section.sectiontype .. '.' ..
-                                               field.option
-                                       ] = true
-                               end
+               local state = nil
+
+               for i, res in ipairs(maps) do
+                       if config.autoapply then
+                               res.autoapply = config.autoapply
+                       end
+                       local cstate = res:parse()
+                       if not state or cstate < state then
+                               state = cstate
                        end
                end
 
-               if has_upload then
-                       local uci = luci.model.uci.cursor()
-                       local prm = luci.http.context.request.message.params
-                       local fd, cbid
-
-                       luci.http.setfilehandler(
-                               function( field, chunk, eof )
-                                       if not field then return end
-                                       if field.name and not cbid then
-                                               local c, s, o = field.name:gmatch(
-                                                       "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
-                                               )()
-
-                                               if c and s and o then
-                                                       local t = uci:get( c, s )
-                                                       if t and uploads[c.."."..t.."."..o] then
-                                                               local path = "/lib/uci/upload/"..field.name
-                                                               fd = io.open(path, "w")
-                                                               if fd then
-                                                                       cbid = field.name
-                                                                       prm[cbid] = path
-                                                       --      else
-                                                       --              io.stderr:write("E: " .. err .. "\n")
-                                                               end
-                                                       end
-                                               end
-                                       end
+               if config.on_valid_to and state and state > 0 and state < 2 then
+                       luci.http.redirect(config.on_valid_to)
+                       return
+               end
 
-                                       if field.name == cbid and fd then
-                                               fd:write(chunk)
-                                       end
+               if config.on_changed_to and state and state > 1 then
+                       luci.http.redirect(config.on_changed_to)
+                       return
+               end
 
-                                       if eof and fd then
-                                               fd:close()
-                                               fd   = nil
-                                               cbid = nil
-                                       end
-                               end
-                       )
+               if config.on_success_to and state and state > 0 then
+                       luci.http.redirect(config.on_success_to)
+                       return
                end
 
-               for i, res in ipairs(maps) do
-                       res:parse()
+               if config.state_handler then
+                       if not config.state_handler(state, maps) then
+                               return
+                       end
                end
 
-               luci.template.render("cbi/header")
+               local pageaction = true
+               http.header("X-CBI-State", state or 0)
+               luci.template.render("cbi/header", {state = state})
                for i, res in ipairs(maps) do
                        res:render()
+                       if res.pageaction == false then
+                               pageaction = false
+                       end
                end
-               luci.template.render("cbi/footer")
+               luci.template.render("cbi/footer", {pageaction=pageaction, state = state, autoapply = config.autoapply})
        end
 end
 
@@ -541,13 +592,20 @@ function form(model)
        return function(...)
                require("luci.cbi")
                require("luci.template")
+               local http = require "luci.http"
 
                maps = luci.cbi.load(model, ...)
 
+               local state = nil
+
                for i, res in ipairs(maps) do
-                       res:parse()
+                       local cstate = res:parse()
+                       if not state or cstate < state then
+                               state = cstate
+                       end
                end
 
+               http.header("X-CBI-State", state or 0)
                luci.template.render("header")
                for i, res in ipairs(maps) do
                        res:render()