Merge pull request #2643 from kuoruan/aria2
[project/luci.git] / modules / luci-base / luasrc / cbi.lua
index 1aa00eed9e8aeb2e1bc588203015033f5082fb5a..450e413916774669e9e7c4bca75247ab6c25c407 100644 (file)
@@ -38,7 +38,7 @@ function load(cbimap, ...)
        require("luci.config")
        require("luci.util")
 
-       local upldir = "/lib/uci/upload/"
+       local upldir = "/etc/luci-uploads/"
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err
 
@@ -262,6 +262,7 @@ function Node.render_children(self, ...)
        local k, node
        for k, node in ipairs(self.children) do
                node.last_child = (k == #self.children)
+               node.index = k
                node:render(...)
        end
 end
@@ -336,7 +337,7 @@ function Map.__init__(self, config, ...)
 end
 
 function Map.formvalue(self, key)
-       return self.readinput and luci.http.formvalue(key)
+       return self.readinput and luci.http.formvalue(key) or nil
 end
 
 function Map.formvaluetable(self, key)
@@ -385,41 +386,45 @@ function Map.parse(self, readinput, ...)
 
        Node.parse(self, ...)
 
-       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 (not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply") then
-               self:_run_hooks("on_before_commit")
+       if self.save then
+               self:_run_hooks("on_save", "on_before_save")
+               local i, config
                for i, config in ipairs(self.parsechain) do
-                       self.uci:commit(config)
+                       self.uci:save(config)
+               end
+               self:_run_hooks("on_after_save")
+               if (not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply") then
+                       self:_run_hooks("on_before_commit")
+                       if self.apply_on_parse == false then
+                               for i, config in ipairs(self.parsechain) do
+                                       self.uci:commit(config)
+                               end
+                       end
+                       self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
+                       if self.apply_on_parse == true or self.apply_on_parse == false then
+                               self.uci:apply(self.apply_on_parse)
+                               self:_run_hooks("on_apply", "on_after_apply")
+                       else
+                               -- 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
 
-                       -- Refresh data because commit changes section names
-                       self.uci:load(config)
+                       -- Reparse sections
+                       Node.parse(self, true)
                end
-               self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
-               if self.apply_on_parse then
-                       self.uci:apply(self.parsechain)
-                       self:_run_hooks("on_apply", "on_after_apply")
-               else
-                       -- 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
+               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
-
-               -- Reparse sections
-               Node.parse(self, true)
-       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
 
-       if self.proceed then
+       if not self.save then
+               self.state = FORM_INVALID
+       elseif self.proceed then
                self.state = FORM_PROCEED
        elseif self.changed then
                self.state = FORM_CHANGED
@@ -881,19 +886,24 @@ function AbstractSection.render_tab(self, tab, ...)
        local k, node
        for k, node in ipairs(self.tabs[tab].childs) do
                node.last_child = (k == #self.tabs[tab].childs)
+               node.index = k
                node:render(...)
        end
 end
 
 -- Parse optional options
-function AbstractSection.parse_optionals(self, section)
+function AbstractSection.parse_optionals(self, section, noparse)
        if not self.optional then
                return
        end
 
        self.optionals[section] = {}
 
-       local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
+       local field = nil
+       if not noparse then
+               field = self.map:formvalue("cbi.opt."..self.config.."."..section)
+       end
+
        for k,v in ipairs(self.children) do
                if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
                        if field == v.option then
@@ -1071,6 +1081,11 @@ function NamedSection.__init__(self, map, section, stype, ...)
        self.section = section
 end
 
+function NamedSection.prepare(self)
+       AbstractSection.prepare(self)
+       AbstractSection.parse_optionals(self, self.section, true)
+end
+
 function NamedSection.parse(self, novld)
        local s = self.section
        local active = self:cfgvalue(s)
@@ -1120,6 +1135,15 @@ function TypedSection.__init__(self, map, type, ...)
        self.anonymous = false
 end
 
+function TypedSection.prepare(self)
+       AbstractSection.prepare(self)
+
+       local i, s
+       for i, s in ipairs(self:cfgsections()) do
+               AbstractSection.parse_optionals(self, s, true)
+       end
+end
+
 -- Return all matching UCI sections for this TypedSection
 function TypedSection.cfgsections(self)
        local sections = {}
@@ -1175,19 +1199,20 @@ function TypedSection.parse(self, novld)
                        if name then
                                -- Ignore if it already exists
                                if self:cfgvalue(name) then
-                                       name = nil;
-                               end
-
-                               name = self:checkscope(name)
-
-                               if not name then
+                                       name = nil
                                        self.err_invalid = true
-                               end
+                               else
+                                       name = self:checkscope(name)
 
-                               if name and #name > 0 then
-                                       created = self:create(name, origin) and name
-                                       if not created then
-                                               self.invalid_cts = true
+                                       if not name then
+                                               self.err_invalid = true
+                                       end
+
+                                       if name and #name > 0 then
+                                               created = self:create(name, origin) and name
+                                               if not created then
+                                                       self.invalid_cts = true
+                                               end
                                        end
                                end
                        end
@@ -1202,13 +1227,14 @@ function TypedSection.parse(self, novld)
                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
+                       local sids, sid = { }, nil
                        for sid in util.imatch(order) do
-                               self.map.uci:reorder(self.config, sid, num)
-                               num = num + 1
+                               sids[#sids+1] = sid
+                       end
+                       if #sids > 0 then
+                               self.map.uci:reorder(self.config, sids)
+                               self.changed = true
                        end
-                       self.changed = (num > 0)
                end
        end
 
@@ -1272,7 +1298,6 @@ function AbstractValue.__init__(self, map, section, option, ...)
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
-       self.subdeps = {}
        --self.cast = "string"
 
        self.track_missing = false
@@ -1296,7 +1321,42 @@ function AbstractValue.depends(self, field, value)
                deps = field
        end
 
-       table.insert(self.deps, {deps=deps, add=""})
+       table.insert(self.deps, deps)
+end
+
+-- Serialize dependencies
+function AbstractValue.deplist2json(self, section, deplist)
+       local deps, i, d = { }
+
+       if type(self.deps) == "table" then
+               for i, d in ipairs(deplist or self.deps) do
+                       local a, k, v = { }
+                       for k, v in pairs(d) do
+                               if k:find("!", 1, true) then
+                                       a[k] = v
+                               elseif k:find(".", 1, true) then
+                                       a['cbid.%s' % k] = v
+                               else
+                                       a['cbid.%s.%s.%s' %{ self.config, section, k }] = v
+                               end
+                       end
+                       deps[#deps+1] = a
+               end
+       end
+
+       return util.serialize_json(deps)
+end
+
+-- Serialize choices
+function AbstractValue.choices(self)
+       if type(self.keylist) == "table" and #self.keylist > 0 then
+               local i, k, v = nil, nil, {}
+               for i, k in ipairs(self.keylist) do
+                       v[k] = self.vallist[i] or k
+               end
+               return v
+       end
+       return nil
 end
 
 -- Generates the unique CBID
@@ -1370,6 +1430,12 @@ function AbstractValue.parse(self, section, novld)
                        self:add_error(section, "invalid", val_err)
                end
 
+               if self.alias then
+                       self.section.aliased = self.section.aliased or {}
+                       self.section.aliased[section] = self.section.aliased[section] or {}
+                       self.section.aliased[section][self.alias] = true
+               end
+
                if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
                        if self:write(section, fvalue) then
                                -- Push events
@@ -1379,10 +1445,16 @@ function AbstractValue.parse(self, section, novld)
                end
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
-                       if self:remove(section) then
-                               -- Push events
-                               self.section.changed = true
-                               --luci.util.append(self.map.events, self.events)
+                       if not self.alias or
+                          not self.section.aliased or
+                          not self.section.aliased[section] or
+                          not self.section.aliased[section][self.alias]
+                       then
+                               if self:remove(section) then
+                                       -- Push events
+                                       self.section.changed = true
+                                       --luci.util.append(self.map.events, self.events)
+                               end
                        end
                elseif cvalue ~= fvalue and not novld then
                        -- trigger validator with nil value to get custom user error msg.
@@ -1408,7 +1480,7 @@ function AbstractValue.cfgvalue(self, section)
        if self.tag_error[section] then
                value = self:formvalue(section)
        else
-               value = self.map:get(section, self.option)
+               value = self.map:get(section, self.alias or self.option)
        end
 
        if not value then
@@ -1449,12 +1521,12 @@ AbstractValue.transform = AbstractValue.validate
 
 -- Write to UCI
 function AbstractValue.write(self, section, value)
-       return self.map:set(section, self.option, value)
+       return self.map:set(section, self.alias or self.option, value)
 end
 
 -- Remove from UCI
 function AbstractValue.remove(self, section)
-       return self.map:del(section, self.option)
+       return self.map:del(section, self.alias or self.option)
 end
 
 
@@ -1471,6 +1543,7 @@ function Value.__init__(self, ...)
        self.template  = "cbi/value"
        self.keylist = {}
        self.vallist = {}
+       self.readonly = nil
 end
 
 function Value.reset_values(self)
@@ -1484,6 +1557,10 @@ function Value.value(self, key, val)
        table.insert(self.vallist, tostring(val))
 end
 
+function Value.parse(self, section, novld)
+       if self.readonly then return end
+       AbstractValue.parse(self, section, novld)
+end
 
 -- DummyValue - This does nothing except being there
 DummyValue = class(AbstractValue)
@@ -1572,15 +1649,16 @@ function ListValue.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/lvalue"
 
-       self.keylist = {}
-       self.vallist = {}
        self.size   = 1
        self.widget = "select"
+
+       self:reset_values()
 end
 
 function ListValue.reset_values(self)
        self.keylist = {}
        self.vallist = {}
+       self.deplist = {}
 end
 
 function ListValue.value(self, key, val, ...)
@@ -1591,10 +1669,7 @@ function ListValue.value(self, key, val, ...)
        val = val or key
        table.insert(self.keylist, tostring(key))
        table.insert(self.vallist, tostring(val))
-
-       for i, deps in ipairs({...}) do
-               self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
-       end
+       table.insert(self.deplist, {...})
 end
 
 function ListValue.validate(self, val)
@@ -1618,11 +1693,10 @@ function MultiValue.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template = "cbi/mvalue"
 
-       self.keylist = {}
-       self.vallist = {}
-
        self.widget = "checkbox"
        self.delimiter = " "
+
+       self:reset_values()
 end
 
 function MultiValue.render(self, ...)
@@ -1636,6 +1710,7 @@ end
 function MultiValue.reset_values(self)
        self.keylist = {}
        self.vallist = {}
+       self.deplist = {}
 end
 
 function MultiValue.value(self, key, val)
@@ -1710,8 +1785,7 @@ function DynamicList.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/dynlist"
        self.cast = "table"
-       self.keylist = {}
-       self.vallist = {}
+       self:reset_values()
 end
 
 function DynamicList.reset_values(self)
@@ -1785,6 +1859,15 @@ function DynamicList.formvalue(self, section)
 end
 
 
+DropDown = class(MultiValue)
+
+function DropDown.__init__(self, ...)
+       ListValue.__init__(self, ...)
+       self.template = "cbi/dropdown"
+       self.delimiter = " "
+end
+
+
 --[[
 TextValue - A multi-line value
        rows:   Rows
@@ -1806,6 +1889,7 @@ function Button.__init__(self, ...)
        self.template  = "cbi/button"
        self.inputstyle = nil
        self.rmempty = true
+        self.unsafeupload = false
 end
 
 
@@ -1822,9 +1906,15 @@ function FileUpload.__init__(self, ...)
 end
 
 function FileUpload.formcreated(self, section)
-       return AbstractValue.formcreated(self, section) or
-               self.map:formvalue("cbi.rlf."..section.."."..self.option) or
-               self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+       if self.unsafeupload then
+               return AbstractValue.formcreated(self, section) or
+                       self.map:formvalue("cbi.rlf."..section.."."..self.option) or
+                       self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") or
+                       self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
+       else
+               return AbstractValue.formcreated(self, section) or
+                       self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
+       end
 end
 
 function FileUpload.cfgvalue(self, section)
@@ -1835,27 +1925,50 @@ function FileUpload.cfgvalue(self, section)
        return nil
 end
 
+-- If we have a new value, use it
+-- otherwise use old value
+-- deletion should be managed by a separate button object
+-- unless self.unsafeupload is set in which case if the user
+-- choose to remove the old file we do so.
+-- Also, allow to specify (via textbox) a file already on router
 function FileUpload.formvalue(self, section)
        local val = AbstractValue.formvalue(self, section)
        if val then
-               if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
-                  not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
-               then
+               if self.unsafeupload then
+                       if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
+                           not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+                       then
+                               return val
+                       end
+                       fs.unlink(val)
+                       self.value = nil
+                       return nil
+                elseif val ~= "" then
                        return val
-               end
-               fs.unlink(val)
-               self.value = nil
+                end
        end
-       return nil
+       val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
+       if val == "" then
+               val = nil
+       end
+        if not self.unsafeupload then
+               if not val then
+                       val = self.map:formvalue("cbi.rlf."..section.."."..self.option)
+               end
+        end
+       return val
 end
 
 function FileUpload.remove(self, section)
-       local val = AbstractValue.formvalue(self, section)
-       if val and fs.access(val) then fs.unlink(val) end
-       return AbstractValue.remove(self, section)
+       if self.unsafeupload then
+               local val = AbstractValue.formvalue(self, section)
+               if val and fs.access(val) then fs.unlink(val) end
+               return AbstractValue.remove(self, section)
+       else
+               return nil
+       end
 end
 
-
 FileBrowser = class(AbstractValue)
 
 function FileBrowser.__init__(self, ...)