Merge CBI change-detection
[project/luci.git] / libs / cbi / luasrc / cbi.lua
index c1eefca..78f6b7d 100644 (file)
@@ -30,14 +30,18 @@ require("luci.template")
 require("luci.util")
 require("luci.http")
 require("luci.uvl")
 require("luci.util")
 require("luci.http")
 require("luci.uvl")
+require("luci.fs")
 
 
+--local event      = require "luci.sys.event"
 local uci        = require("luci.model.uci")
 local class      = luci.util.class
 local instanceof = luci.util.instanceof
 
 FORM_NODATA  =  0
 local uci        = require("luci.model.uci")
 local class      = luci.util.class
 local instanceof = luci.util.instanceof
 
 FORM_NODATA  =  0
+FORM_PROCEED =  0
 FORM_VALID   =  1
 FORM_INVALID = -1
 FORM_VALID   =  1
 FORM_INVALID = -1
+FORM_CHANGED =  2
 
 AUTO = true
 
 
 AUTO = true
 
@@ -51,6 +55,7 @@ function load(cbimap, ...)
        require("luci.config")
        require("luci.util")
 
        require("luci.config")
        require("luci.util")
 
+       local upldir = "/lib/uci/upload/"
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
        assert(func, err)
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
        assert(func, err)
@@ -69,15 +74,68 @@ function load(cbimap, ...)
                        return rawget(tbl, key) or _M[key] or _G[key]
                end}))
 
                        return rawget(tbl, key) or _M[key] or _G[key]
                end}))
 
-       local maps = {func()}
+       local maps       = { func() }
+       local uploads    = { }
+       local has_upload = false
 
        for i, map in ipairs(maps) do
                if not instanceof(map, Node) then
                        error("CBI map returns no valid map object!")
                        return nil
 
        for i, map in ipairs(maps) do
                if not instanceof(map, Node) then
                        error("CBI map returns no valid map object!")
                        return nil
+               else
+                       map:prepare()
+                       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
+                       end
                end
        end
 
                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 = upldir .. field.name
+                                                       fd = io.open(path, "w")
+                                                       if fd then
+                                                               cbid = field.name
+                                                               prm[cbid] = path
+                                                       end
+                                               end
+                                       end
+                               end
+
+                               if field.name == cbid and fd then
+                                       fd:write(chunk)
+                               end
+
+                               if eof and fd then
+                                       fd:close()
+                                       fd   = nil
+                                       cbid = nil
+                               end
+                       end
+               )
+       end
+
        return maps
 end
 
        return maps
 end
 
@@ -166,6 +224,13 @@ function Node._i18n(self, config, section, option, title, description)
        end
 end
 
        end
 end
 
+-- Prepare nodes
+function Node.prepare(self, ...)
+       for k, child in ipairs(self.children) do
+               child:prepare(...)
+       end
+end
+
 -- Append child nodes
 function Node.append(self, obj)
        table.insert(self.children, obj)
 -- Append child nodes
 function Node.append(self, obj)
        table.insert(self.children, obj)
@@ -224,6 +289,9 @@ function Map.__init__(self, config, ...)
        self.apply_on_parse = nil
        self.uci = uci.cursor()
        self.save = true
        self.apply_on_parse = nil
        self.uci = uci.cursor()
        self.save = true
+       
+       self.changed = false
+       
        if not self.uci:load(self.config) then
                error("Unable to read UCI data: " .. self.config)
        end
        if not self.uci:load(self.config) then
                error("Unable to read UCI data: " .. self.config)
        end
@@ -242,6 +310,9 @@ function Map.get_scheme(self, sectiontype, option)
        end
 end
 
        end
 end
 
+function Map.submitstate(self)
+       return luci.http.formvalue("cbi.submit")
+end
 
 -- Chain foreign config
 function Map.chain(self, config)
 
 -- Chain foreign config
 function Map.chain(self, config)
@@ -249,14 +320,14 @@ function Map.chain(self, config)
 end
 
 -- Use optimized UCI writing
 end
 
 -- Use optimized UCI writing
-function Map.parse(self, ...)
-       Node.parse(self, ...)
+function Map.parse(self)
+       Node.parse(self)
 
        if self.save then
                for i, config in ipairs(self.parsechain) do
                        self.uci:save(config)
                end
 
        if self.save then
                for i, config in ipairs(self.parsechain) do
                        self.uci:save(config)
                end
-               if luci.http.formvalue("cbi.apply") then
+               if self:submitstate() and (self.autoapply or luci.http.formvalue("cbi.apply")) then
                        for i, config in ipairs(self.parsechain) do
                                self.uci:commit(config)
 
                        for i, config in ipairs(self.parsechain) do
                                self.uci:commit(config)
 
@@ -273,13 +344,28 @@ function Map.parse(self, ...)
                        end
 
                        -- Reparse sections
                        end
 
                        -- Reparse sections
-                       Node.parse(self, ...)
+                       Node.parse(self, true)
 
                end
                for i, config in ipairs(self.parsechain) do
                        self.uci:unload(config)
                end
 
                end
                for i, config in ipairs(self.parsechain) do
                        self.uci:unload(config)
                end
+               if type(self.commit_handler) == "function" then
+                       self:commit_handler(self:submitstate())
+               end
+       end
+
+       if self:submitstate() then
+               if self.save then
+                       self.state = self.changed and FORM_CHANGED or FORM_VALID
+               else
+                       self.state = FORM_INVALID
+               end
+       else
+               self.state = FORM_NODATA
        end
        end
+
+       return self.state
 end
 
 function Map.render(self, ...)
 end
 
 function Map.render(self, ...)
@@ -357,6 +443,7 @@ function SimpleForm.__init__(self, config, title, description, data)
        self.data = data or {}
        self.template = "cbi/simpleform"
        self.dorender = true
        self.data = data or {}
        self.template = "cbi/simpleform"
        self.dorender = true
+       self.pageaction = false
 end
 
 function SimpleForm.parse(self, ...)
 end
 
 function SimpleForm.parse(self, ...)
@@ -374,11 +461,12 @@ function SimpleForm.parse(self, ...)
        end
 
        local state =
        end
 
        local state =
-               not luci.http.formvalue("cbi.submit") and 0
-               or valid and 1
-               or -1
+               not self:submitstate() and FORM_NODATA
+               or valid and FORM_VALID
+               or FORM_INVALID
 
        self.dorender = not self.handle or self:handle(state, self.data) ~= false
 
        self.dorender = not self.handle or self:handle(state, self.data) ~= false
+       return state
 end
 
 function SimpleForm.render(self, ...)
 end
 
 function SimpleForm.render(self, ...)
@@ -387,6 +475,10 @@ function SimpleForm.render(self, ...)
        end
 end
 
        end
 end
 
+function SimpleForm.submitstate(self)
+       return luci.http.formvalue("cbi.submit")
+end
+
 function SimpleForm.section(self, class, ...)
        if instanceof(class, AbstractSection) then
                local obj  = class(self, ...)
 function SimpleForm.section(self, class, ...)
        if instanceof(class, AbstractSection) then
                local obj  = class(self, ...)
@@ -457,6 +549,7 @@ function AbstractSection.__init__(self, map, sectiontype, ...)
        self.tag_error = {}
        self.tag_invalid = {}
        self.tag_deperror = {}
        self.tag_error = {}
        self.tag_invalid = {}
        self.tag_deperror = {}
+       self.changed = false
 
        self.optional = true
        self.addremove = false
 
        self.optional = true
        self.addremove = false
@@ -556,6 +649,12 @@ function AbstractSection.cfgvalue(self, section)
        return self.map:get(section)
 end
 
        return self.map:get(section)
 end
 
+-- Push events
+function AbstractSection.push_events(self)
+       --luci.util.append(self.map.events, self.events)
+       self.map.changed = true
+end
+
 -- Removes the section
 function AbstractSection.remove(self, section)
        return self.map:del(section)
 -- Removes the section
 function AbstractSection.remove(self, section)
        return self.map:del(section)
@@ -566,7 +665,7 @@ function AbstractSection.create(self, section)
        local stat
 
        if section then
        local stat
 
        if section then
-               stat = self.map:set(section, nil, self.sectiontype)
+               stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
        else
                section = self.map:add(self.sectiontype)
                stat = section
        else
                section = self.map:add(self.sectiontype)
                stat = section
@@ -606,6 +705,10 @@ function Table.__init__(self, form, data, ...)
        function datasource.get(self, section, option)
                return data[section] and data[section][option]
        end
        function datasource.get(self, section, option)
                return data[section] and data[section][option]
        end
+       
+       function datasource.submitstate(self)
+               return luci.http.formvalue("cbi.submit")
+       end
 
        function datasource.del(...)
                return true
 
        function datasource.del(...)
                return true
@@ -623,7 +726,7 @@ end
 
 function Table.parse(self)
        for i, k in ipairs(self:cfgsections()) do
 
 function Table.parse(self)
        for i, k in ipairs(self:cfgsections()) do
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, k)
                end
        end
                        Node.parse(self, k)
                end
        end
@@ -666,7 +769,7 @@ function NamedSection.__init__(self, map, section, stype, ...)
        self.section = section
 end
 
        self.section = section
 end
 
-function NamedSection.parse(self)
+function NamedSection.parse(self, novld)
        local s = self.section
        local active = self:cfgvalue(s)
 
        local s = self.section
        local active = self:cfgvalue(s)
 
@@ -686,14 +789,18 @@ function NamedSection.parse(self)
 
        if active then
                AbstractSection.parse_dynamic(self, s)
 
        if active then
                AbstractSection.parse_dynamic(self, s)
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, s)
 
                        Node.parse(self, s)
 
-                       if not self.override_scheme and self.map.scheme then
+                       if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, s)
                        end
                end
                AbstractSection.parse_optionals(self, s)
                                _uvl_validate_section(self, s)
                        end
                end
                AbstractSection.parse_optionals(self, s)
+               
+               if self.changed then
+                       self:push_events()
+               end
        end
 end
 
        end
 end
 
@@ -743,7 +850,7 @@ function TypedSection.depends(self, option, value)
        table.insert(self.deps, {option=option, value=value})
 end
 
        table.insert(self.deps, {option=option, value=value})
 end
 
-function TypedSection.parse(self)
+function TypedSection.parse(self, novld)
        if self.addremove then
                -- Remove
                local crval = REMOVE_PREFIX .. self.config
        if self.addremove then
                -- Remove
                local crval = REMOVE_PREFIX .. self.config
@@ -761,10 +868,10 @@ function TypedSection.parse(self)
        local co
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
        local co
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, k)
 
                        Node.parse(self, k)
 
-                       if not self.override_scheme and self.map.scheme then
+                       if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, k)
                        end
                end
                                _uvl_validate_section(self, k)
                        end
                end
@@ -795,6 +902,9 @@ function TypedSection.parse(self)
 
                                if name and #name > 0 then
                                        created = self:create(name) and name
 
                                if name and #name > 0 then
                                        created = self:create(name) and name
+                                       if not created then
+                                               self.invalid_cts = true
+                                       end
                                end
                        end
                end
                                end
                        end
                end
@@ -803,6 +913,10 @@ function TypedSection.parse(self)
                        AbstractSection.parse_optionals(self, created)
                end
        end
                        AbstractSection.parse_optionals(self, created)
                end
        end
+
+       if created or self.changed then
+               self:push_events()
+       end
 end
 
 -- Verifies scope of sections
 end
 
 -- Verifies scope of sections
@@ -860,23 +974,31 @@ function AbstractValue.__init__(self, map, section, option, ...)
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
-       self.cast = "string"
+       --self.cast = "string"
 
        self.track_missing = false
 
        self.track_missing = false
-       self.rmempty   = false
+       --self.rmempty   = false
        self.default   = nil
        self.size      = nil
        self.optional  = false
        self.default   = nil
        self.size      = nil
        self.optional  = false
+end
 
 
+function AbstractValue.prepare(self)
        -- Use defaults from UVL
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
        -- Use defaults from UVL
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
-               self.rmempty     = not vs.required
-               self.cast        = (vs.type == "list") and "list" or "string"
+               if self.rmempty == nil then
+                       self.rmempty = not vs.required
+               end
+               if self.cast == nil then
+                       self.cast = (vs.type == "list") and "list" or "string"
+               end
                self.title       = self.title or vs.title
                self.description = self.description or vs.descr
                self.title       = self.title or vs.title
                self.description = self.description or vs.descr
-               self.default     = vs.default
+               if self.default == nil then
+                       self.default = vs.default
+               end
 
                if vs.depends and not self.override_dependencies then
                        for i, deps in ipairs(vs.depends) do
 
                if vs.depends and not self.override_dependencies then
                        for i, deps in ipairs(vs.depends) do
@@ -887,6 +1009,8 @@ function AbstractValue.__init__(self, map, section, option, ...)
                        end
                end
        end
                        end
                end
        end
+
+       self.cast = self.cast or "string"
 end
 
 -- Add a dependencie to another section field
 end
 
 -- Add a dependencie to another section field
@@ -930,17 +1054,25 @@ function AbstractValue.parse(self, section)
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
 
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
 
-       if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
+       if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
                fvalue = self:transform(self:validate(fvalue, section))
                if not fvalue then
                        self.tag_invalid[section] = true
                end
                if fvalue and not (fvalue == cvalue) then
                fvalue = self:transform(self:validate(fvalue, section))
                if not fvalue then
                        self.tag_invalid[section] = true
                end
                if fvalue and not (fvalue == cvalue) then
-                       self:write(section, fvalue)
+                       if self:write(section, fvalue) then
+                               -- Push events
+                               self.section.changed = true
+                               --luci.util.append(self.map.events, self.events)                        
+                       end
                end
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
                end
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
-                       self:remove(section)
+                       if self:remove(section) then
+                               -- Push events
+                               self.section.changed = true
+                               --luci.util.append(self.map.events, self.events)
+                       end
                elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
                        self.tag_missing[section] = true
                end
                elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
                        self.tag_missing[section] = true
                end
@@ -980,14 +1112,16 @@ end
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
        local value = self.map:get(section, self.option)
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
        local value = self.map:get(section, self.option)
-       if not self.cast or self.cast == type(value) then
+       if not value then
+               return nil
+       elseif not self.cast or self.cast == type(value) then
                return value
        elseif self.cast == "string" then
                if type(value) == "table" then
                        return value[1]
                end
        elseif self.cast == "table" then
                return value
        elseif self.cast == "string" then
                if type(value) == "table" then
                        return value[1]
                end
        elseif self.cast == "table" then
-               return {value}
+               return luci.util.split(value, "%s+", nil, true)
        end
 end
 
        end
 end
 
@@ -1041,6 +1175,20 @@ function DummyValue.__init__(self, ...)
        self.value = nil
 end
 
        self.value = nil
 end
 
+function DummyValue.cfgvalue(self, section)
+       local value
+       if self.value then
+               if type(self.value) == "function" then
+                       value = self:value(section)
+               else
+                       value = self.value
+               end
+       else
+               value = AbstractValue.cfgvalue(self, section)
+       end
+       return value
+end
+
 function DummyValue.parse(self)
 
 end
 function DummyValue.parse(self)
 
 end
@@ -1094,23 +1242,23 @@ function ListValue.__init__(self, ...)
        self.vallist = {}
        self.size   = 1
        self.widget = "select"
        self.vallist = {}
        self.size   = 1
        self.widget = "select"
+end
 
 
+function ListValue.prepare(self, ...)
+       AbstractValue.prepare(self, ...)
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
-               if self.value and vs.values and not self.override_values then
-                       if self.rmempty or self.optional then
-                               self:value("")
-                       end
-                       for k, v in pairs(vs.values) do
+               if self.value and vs.valuelist and not self.override_values then
+                       for k, v in ipairs(vs.valuelist) do
                                local deps = {}
                                if not self.override_dependencies
                                local deps = {}
                                if not self.override_dependencies
-                                and vs.enum_depends and vs.enum_depends[k] then
-                                       for i, dep in ipairs(vs.enum_depends[k]) do
+                                and vs.enum_depends and vs.enum_depends[v.value] then
+                                       for i, dep in ipairs(vs.enum_depends[v.value]) do
                                                table.insert(deps, _uvl_strip_remote_dependencies(dep))
                                        end
                                end
                                                table.insert(deps, _uvl_strip_remote_dependencies(dep))
                                        end
                                end
-                               self:value(k, v, unpack(deps))
+                               self:value(v.value, v.title or v.value, unpack(deps))
                        end
                end
        end
                        end
                end
        end
@@ -1224,7 +1372,7 @@ function StaticList.validate(self, value)
 
        local valid = {}
        for i, v in ipairs(value) do
 
        local valid = {}
        for i, v in ipairs(value) do
-               if luci.util.contains(self.valuelist, v) then
+               if luci.util.contains(self.vallist, v) then
                        table.insert(valid, v)
                end
        end
                        table.insert(valid, v)
                end
        end
@@ -1248,7 +1396,8 @@ function DynamicList.value(self, key, val)
        table.insert(self.vallist, tostring(val))
 end
 
        table.insert(self.vallist, tostring(val))
 end
 
-function DynamicList.validate(self, value, section)
+function DynamicList.formvalue(self, section)
+       local value = AbstractValue.formvalue(self, section)
        value = (type(value) == "table") and value or {value}
 
        local valid = {}
        value = (type(value) == "table") and value or {value}
 
        local valid = {}
@@ -1286,3 +1435,58 @@ function Button.__init__(self, ...)
        self.inputstyle = nil
        self.rmempty = true
 end
        self.inputstyle = nil
        self.rmempty = true
 end
+
+
+FileUpload = class(AbstractValue)
+
+function FileUpload.__init__(self, ...)
+       AbstractValue.__init__(self, ...)
+       self.template = "cbi/upload"
+       if not self.map.upload_fields then
+               self.map.upload_fields = { self }
+       else
+               self.map.upload_fields[#self.map.upload_fields+1] = self
+       end
+end
+
+function FileUpload.formcreated(self, section)
+       return AbstractValue.formcreated(self, section) or
+               luci.http.formvalue("cbi.rlf."..section.."."..self.option) or
+               luci.http.formvalue("cbi.rlf."..section.."."..self.option..".x")
+end
+
+function FileUpload.cfgvalue(self, section)
+       local val = AbstractValue.cfgvalue(self, section)
+       if val and luci.fs.access(val) then
+               return val
+       end
+       return nil
+end
+
+function FileUpload.formvalue(self, section)
+       local val = AbstractValue.formvalue(self, section)
+       if val then
+               if not luci.http.formvalue("cbi.rlf."..section.."."..self.option) and
+                  not luci.http.formvalue("cbi.rlf."..section.."."..self.option..".x")
+               then
+                       return val
+               end
+               luci.fs.unlink(val)
+               self.value = nil
+       end
+       return nil
+end
+
+function FileUpload.remove(self, section)
+       local val = AbstractValue.formvalue(self, section)
+       if val and luci.fs.access(val) then luci.fs.unlink(val) end
+       return AbstractValue.remove(self, section)
+end
+
+
+FileBrowser = class(AbstractValue)
+
+function FileBrowser.__init__(self, ...)
+       AbstractValue.__init__(self, ...)
+       self.template = "cbi/browser"
+end