trunk: s/ / /g
[project/luci.git] / libs / web / luasrc / cbi.lua
index 8dd16b1..5dd57e2 100644 (file)
@@ -50,6 +50,8 @@ AUTO = true
 
 CREATE_PREFIX = "cbi.cts."
 REMOVE_PREFIX = "cbi.rts."
+RESORT_PREFIX = "cbi.sts."
+FEXIST_PREFIX = "cbi.cbe."
 
 -- Loads a CBI map from given file, creating an environment and returns it
 function load(cbimap, ...)
@@ -304,9 +306,11 @@ function Map.parse(self, readinput, ...)
        Node.parse(self, ...)
 
        if self.save then
+               self:_run_hooks("on_save", "on_before_save")
                for i, config in ipairs(self.parsechain) do
                        self.uci:save(config)
                end
+               self:_run_hooks("on_after_save")
                if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
                        self:_run_hooks("on_before_commit")
                        for i, config in ipairs(self.parsechain) do
@@ -320,10 +324,10 @@ function Map.parse(self, readinput, ...)
                                self.uci:apply(self.parsechain)
                                self:_run_hooks("on_apply", "on_after_apply")
                        else
-                               self._apply = function()
-                                       local cmd = self.uci:apply(self.parsechain, true)
-                                       return io.popen(cmd)
-                               end
+                               -- This is evaluated by the dispatcher and delegated to the
+                               -- template which in turn fires XHR to perform the actual
+                               -- apply actions.
+                               self.apply_needed = true
                        end
 
                        -- Reparse sections
@@ -356,12 +360,6 @@ end
 function Map.render(self, ...)
        self:_run_hooks("on_init")
        Node.render(self, ...)
-       if false and self._apply then
-               local fp = self._apply()
-               fp:read("*a")
-               fp:close()
-               self:_run_hooks("on_apply")
-       end
 end
 
 -- Creates a child section
@@ -382,10 +380,14 @@ end
 
 -- UCI set
 function Map.set(self, section, option, value)
-       if option then
-               return self.uci:set(self.config, section, option, value)
+       if type(value) ~= "table" or #value > 0 then
+               if option then
+                       return self.uci:set(self.config, section, option, value)
+               else
+                       return self.uci:set(self.config, section, value)
+               end
        else
-               return self.uci:set(self.config, section, value)
+               return Map.del(self, section, option)
        end
 end
 
@@ -1121,6 +1123,20 @@ function TypedSection.parse(self, novld)
                end
        end
 
+       if self.sortable then
+               local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
+               local order = self.map:formvalue(stval)
+               if order and #order > 0 then
+                       local sid
+                       local num = 0
+                       for sid in util.imatch(order) do
+                               self.map.uci:reorder(self.config, sid, num)
+                               num = num + 1
+                       end
+                       self.changed = (num > 0)
+               end
+       end
+
        if created or self.changed then
                self:push_events()
        end
@@ -1232,6 +1248,24 @@ function AbstractValue.mandatory(self, value)
        self.rmempty = not value
 end
 
+function AbstractValue.add_error(self, section, type, msg)
+       self.error = self.error or { }
+       self.error[section] = msg or type
+
+       self.section.error = self.section.error or { }
+       self.section.error[section] = self.section.error[section] or { }
+       table.insert(self.section.error[section], msg or type)
+
+       if type == "invalid" then
+               self.tag_invalid[section] = true
+       elseif type == "missing" then
+               self.tag_missing[section] = true
+       end
+
+       self.tag_error[section] = true
+       self.map.save = false
+end
+
 function AbstractValue.parse(self, section, novld)
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
@@ -1253,21 +1287,15 @@ function AbstractValue.parse(self, section, novld)
        end
 
        if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
-               fvalue = self:transform(self:validate(fvalue, section))
+               local val_err
+               fvalue, val_err = self:validate(fvalue, section)
+               fvalue = self:transform(fvalue)
+
                if not fvalue and not novld then
-                       if self.error then
-                               self.error[section] = "invalid"
-                       else
-                               self.error = { [section] = "invalid" }
-                       end
-                       if self.section.error then
-                               table.insert(self.section.error[section], "invalid")
-                       else
-                               self.section.error = {[section] = {"invalid"}}
-                       end
-                       self.map.save = false
+                       self:add_error(section, "invalid", val_err)
                end
-               if fvalue and not (fvalue == cvalue) then
+
+               if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
                        if self:write(section, fvalue) then
                                -- Push events
                                self.section.changed = true
@@ -1282,13 +1310,9 @@ function AbstractValue.parse(self, section, novld)
                                --luci.util.append(self.map.events, self.events)
                        end
                elseif cvalue ~= fvalue and not novld then
-                       self:write(section, fvalue or "")
-                       if self.error then
-                               self.error[section] = "missing"
-                       else
-                               self.error = { [section] = "missing" }
-                       end
-                       self.map.save = false
+                       -- trigger validator with nil value to get custom user error msg.
+                       local _, val_err = self:validate(nil, section)
+                       self:add_error(section, "missing", val_err)
                end
        end
 end
@@ -1326,8 +1350,13 @@ end
 
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
-       local value = (self.error and self.error[section] == "invalid")
-               and self:formvalue(section) or self.map:get(section, self.option)
+       local value
+       if self.tag_error[section] then
+               value = self:formvalue(section)
+       else
+               value = self.map:get(section, self.option)
+       end
+
        if not value then
                return nil
        elseif not self.cast or self.cast == type(value) then
@@ -1337,26 +1366,41 @@ function AbstractValue.cfgvalue(self, section)
                        return value[1]
                end
        elseif self.cast == "table" then
-               return luci.util.split(value, "%s+", nil, true)
+               return { value }
        end
 end
 
 -- Validate the form value
 function AbstractValue.validate(self, value)
-       if self.datatype and value and datatypes[self.datatype] then
-               if type(value) == "table" then
-                       local v
-                       for _, v in ipairs(value) do
-                               if v and #v > 0 and not datatypes[self.datatype](v) then
-                                       return nil
-                               end
+       if self.datatype and value then
+               local args = { }
+               local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
+
+               if dt and ar then
+                       local a
+                       for a in ar:gmatch("[^%s,]+") do
+                               args[#args+1] = a
                        end
                else
-                       if not datatypes[self.datatype](value) then
-                               return nil
+                       dt = self.datatype
+               end
+
+               if dt and datatypes[dt] then
+                       if type(value) == "table" then
+                               local v
+                               for _, v in ipairs(value) do
+                                       if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
+                                               return nil
+                                       end
+                               end
+                       else
+                               if not datatypes[dt](value, unpack(args)) then
+                                       return nil
+                               end
                        end
                end
        end
+
        return value
 end
 
@@ -1389,6 +1433,11 @@ function Value.__init__(self, ...)
        self.vallist = {}
 end
 
+function Value.reset_values(self)
+       self.keylist = {}
+       self.vallist = {}
+end
+
 function Value.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
@@ -1433,29 +1482,31 @@ function Flag.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/fvalue"
 
-       self.enabled = "1"
+       self.enabled  = "1"
        self.disabled = "0"
+       self.default  = self.disabled
 end
 
 -- A flag can only have two states: set or unset
 function Flag.parse(self, section)
-       local fvalue = self:formvalue(section)
+       local fexists = self.map:formvalue(
+               FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
 
-       if fvalue then
-               fvalue = self.enabled
-       else
-               fvalue = self.disabled
-       end
-
-       if fvalue == self.enabled or (not self.optional and not self.rmempty) then
-               if not(fvalue == self:cfgvalue(section)) then
+       if fexists then
+               local fvalue = self:formvalue(section) and self.enabled or self.disabled
+               if fvalue ~= self.default or (not self.optional and not self.rmempty) then
                        self:write(section, fvalue)
+               else
+                       self:remove(section)
                end
        else
                self:remove(section)
        end
 end
 
+function Flag.cfgvalue(self, section)
+       return AbstractValue.cfgvalue(self, section) or self.default
+end
 
 
 --[[
@@ -1474,6 +1525,11 @@ function ListValue.__init__(self, ...)
        self.widget = "select"
 end
 
+function ListValue.reset_values(self)
+       self.keylist = {}
+       self.vallist = {}
+end
+
 function ListValue.value(self, key, val, ...)
        if luci.util.contains(self.keylist, key) then
                return
@@ -1524,6 +1580,11 @@ function MultiValue.render(self, ...)
        AbstractValue.render(self, ...)
 end
 
+function MultiValue.reset_values(self)
+       self.keylist = {}
+       self.vallist = {}
+end
+
 function MultiValue.value(self, key, val)
        if luci.util.contains(self.keylist, key) then
                return
@@ -1600,31 +1661,74 @@ function DynamicList.__init__(self, ...)
        self.vallist = {}
 end
 
+function DynamicList.reset_values(self)
+       self.keylist = {}
+       self.vallist = {}
+end
+
 function DynamicList.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
        table.insert(self.vallist, tostring(val))
 end
 
-function DynamicList.write(self, ...)
-       self.map.proceed = true
-       return AbstractValue.write(self, ...)
+function DynamicList.write(self, section, value)
+       local t = { }
+
+       if type(value) == "table" then
+               local x
+               for _, x in ipairs(value) do
+                       if x and #x > 0 then
+                               t[#t+1] = x
+                       end
+               end
+       else
+               t = { value }
+       end
+
+       if self.cast == "string" then
+               value = table.concat(t, " ")
+       else
+               value = t
+       end
+
+       return AbstractValue.write(self, section, value)
+end
+
+function DynamicList.cfgvalue(self, section)
+       local value = AbstractValue.cfgvalue(self, section)
+
+       if type(value) == "string" then
+               local x
+               local t = { }
+               for x in value:gmatch("%S+") do
+                       if #x > 0 then
+                               t[#t+1] = x
+                       end
+               end
+               value = t
+       end
+
+       return value
 end
 
 function DynamicList.formvalue(self, section)
        local value = AbstractValue.formvalue(self, section)
-       value = (type(value) == "table") and value or {value}
 
-       local valid = {}
-       for i, v in ipairs(value) do
-               if v and #v > 0
-                and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
-                and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
-                       table.insert(valid, v)
+       if type(value) == "string" then
+               if self.cast == "string" then
+                       local x
+                       local t = { }
+                       for x in value:gmatch("%S+") do
+                               t[#t+1] = x
+                       end
+                       value = t
+               else
+                       value = { value }
                end
        end
 
-       return valid
+       return value
 end