X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=2d439b8bc69a82166b578be8565a154a111bd2dd;hp=9393c46a47860a40aa4fe9ceeb12733f51f3367a;hb=e0e58e842dbe7531bb7ae8b53f487e0fe9a92415;hpb=91ba7c42f5b45614c9f4c803d09399f08a8e27b1 diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index 9393c46a4..2d439b8bc 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -29,16 +29,19 @@ module("luci.cbi", package.seeall) require("luci.template") require("luci.util") require("luci.http") -require("luci.model.uci") require("luci.uvl") +require("luci.fs") -local uci = luci.model.uci +--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 +FORM_PROCEED = 0 FORM_VALID = 1 FORM_INVALID = -1 +FORM_CHANGED = 2 AUTO = true @@ -48,39 +51,147 @@ REMOVE_PREFIX = "cbi.rts." -- Loads a CBI map from given file, creating an environment and returns it function load(cbimap, ...) require("luci.fs") - require("luci.i18n") + local i18n = require "luci.i18n" require("luci.config") require("luci.util") + local upldir = "/lib/uci/upload/" local cbidir = luci.util.libpath() .. "/model/cbi/" - local func, err = loadfile(cbidir..cbimap..".lua") - + + assert(luci.fs.stat(cbimap) or luci.fs.stat(cbidir..cbimap..".lua"), + "Model not found!") + + local func, err = loadfile(cbimap) if not func then - return nil + func, err = loadfile(cbidir..cbimap..".lua") end + assert(func, err) luci.i18n.loadc("cbi") + luci.i18n.loadc("uvl") + + local env = { + translate=i18n.translate, + translatef=i18n.translatef, + arg={...} + } - luci.util.resfenv(func) - luci.util.updfenv(func, luci.cbi) - luci.util.extfenv(func, "translate", luci.i18n.translate) - luci.util.extfenv(func, "translatef", luci.i18n.translatef) - luci.util.extfenv(func, "arg", {...}) + setfenv(func, setmetatable(env, {__index = + function(tbl, key) + 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 + 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 + 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 +local function _uvl_validate_section(node, name) + local co = node.map:get() + + luci.uvl.STRICT_UNKNOWN_OPTIONS = false + luci.uvl.STRICT_UNKNOWN_SECTIONS = false + + local function tag_fields(e) + if e.option and node.fields[e.option] then + if node.fields[e.option].error then + node.fields[e.option].error[name] = e + else + node.fields[e.option].error = { [name] = e } + end + elseif e.childs then + for _, c in ipairs(e.childs) do tag_fields(c) end + end + end + + local function tag_section(e) + local s = { } + for _, c in ipairs(e.childs) do + if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then + table.insert( s, c.childs[1]:string() ) + else + table.insert( s, c:string() ) + end + end + if #s > 0 then + if node.error then + node.error[name] = s + else + node.error = { [name] = s } + end + end + end + + local stat, err = node.map.validator:validate_section(node.config, name, co) + if err then + node.map.save = false + tag_fields(err) + tag_section(err) + end + +end -function _uvl_strip_remote_dependencies(deps) +local function _uvl_strip_remote_dependencies(deps) local clean = {} for k, v in pairs(deps) do @@ -120,6 +231,13 @@ function Node._i18n(self, config, section, option, title, description) 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) @@ -175,13 +293,29 @@ function Map.__init__(self, config, ...) self.config = config self.parsechain = {self.config} self.template = "cbi/map" + self.apply_on_parse = nil + self.readinput = true + 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 self.validator = luci.uvl.UVL() self.scheme = self.validator:get_scheme(self.config) + +end + +function Map.formvalue(self, key) + return self.readinput and luci.http.formvalue(key) +end + +function Map.formvaluetable(self, key) + return self.readinput and luci.http.formvaluetable(key) end function Map.get_scheme(self, sectiontype, option) @@ -193,34 +327,75 @@ function Map.get_scheme(self, sectiontype, option) end end +function Map.submitstate(self) + return self:formvalue("cbi.submit") +end -- Chain foreign config function Map.chain(self, config) table.insert(self.parsechain, config) end +function Map.state_handler(self, state) + return state +end + -- Use optimized UCI writing -function Map.parse(self, ...) +function Map.parse(self, readinput, ...) + self.readinput = (readinput ~= false) Node.parse(self, ...) - for i, config in ipairs(self.parsechain) do - self.uci:save(config) - end - if luci.http.formvalue("cbi.apply") then + if self.save then for i, config in ipairs(self.parsechain) do - self.uci:commit(config) - self.uci:apply(config) - - -- Refresh data because commit changes section names - self.uci:load(config) + self.uci:save(config) end + 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) + + -- Refresh data because commit changes section names + self.uci:load(config) + end + if self.apply_on_parse then + self.uci:apply(self.parsechain) + else + self._apply = function() + local cmd = self.uci:apply(self.parsechain, true) + return io.popen(cmd) + end + end + + -- Reparse sections + Node.parse(self, true) - -- Reparse sections - Node.parse(self, ...) + 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 - for i, config in ipairs(self.parsechain) do - self.uci:unload(config) + + return self:state_handler(self.state) +end + +function Map.render(self, ...) + Node.render(self, ...) + if self._apply then + local fp = self._apply() + fp:read("*a") + fp:close() end end @@ -269,6 +444,90 @@ function Map.get(self, section, option) end end +--[[ +Compound - Container +]]-- +Compound = class(Node) + +function Compound.__init__(self, ...) + Node.__init__(self) + self.children = {...} +end + +function Compound.parse(self, ...) + local cstate, state = 0, 0 + + for k, child in ipairs(self.children) do + cstate = child:parse(...) + state = (not state or cstate < state) and cstate or state + end + + return state +end + + +--[[ +Delegator - Node controller +]]-- +Delegator = class(Node) +function Delegator.__init__(self, ...) + Node.__init__(self, ...) + self.nodes = {} + self.template = "cbi/delegator" +end + +function Delegator.state(self, name, node, transitor) + transitor = transitor or self.transistor_linear + local state = {node=node, name=name, transitor=transitor} + + assert(instanceof(node, Node), "Invalid node") + assert(not self.nodes[name], "Duplicate entry") + + self.nodes[name] = state + self:append(state) + + return state +end + +function Delegator.get(self, name) + return self.nodes[name] +end + +function Delegator.transistor_linear(self, state, cstate) + if cstate > 0 then + for i, child in ipairs(self.children) do + if state == child then + return self.children[i+1] + end + end + else + return state + end +end + +function Delegator.parse(self, ...) + local active = self:getactive() + assert(active, "Invalid state") + + local cstate = active.node:parse() + self.active = active.transistor(self, active.node, cstate) + + if not self.active then + return FORM_DONE + else + self.active:parse(false) + return FROM_PROCEED + end +end + +function Delegator.render(self, ...) + self.active.node:render(...) +end + +function Delegator.getactive(self) + return self:get(Map.formvalue(self, "cbi.delegated") + or (self.children[1] and self.children[1].name)) +end --[[ Page - A simple node @@ -290,10 +549,16 @@ function SimpleForm.__init__(self, config, title, description, data) self.data = data or {} self.template = "cbi/simpleform" self.dorender = true + self.pageaction = false + self.readinput = true end -function SimpleForm.parse(self, ...) - if luci.http.formvalue("cbi.submit") then +SimpleForm.formvalue = Map.formvalue +SimpleForm.formvaluetable = Map.formvaluetable + +function SimpleForm.parse(self, readinput, ...) + self.readinput = (readinput ~= false) + if self:submitstate() then Node.parse(self, 1, ...) end @@ -307,11 +572,12 @@ function SimpleForm.parse(self, ...) 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 + return state end function SimpleForm.render(self, ...) @@ -320,6 +586,10 @@ function SimpleForm.render(self, ...) end end +function SimpleForm.submitstate(self) + return self:formvalue("cbi.submit") +end + function SimpleForm.section(self, class, ...) if instanceof(class, AbstractSection) then local obj = class(self, ...) @@ -344,7 +614,7 @@ function SimpleForm.field(self, class, ...) end if instanceof(class, AbstractValue) then - local obj = class(self, ...) + local obj = class(self, section, ...) obj.track_missing = true section:append(obj) return obj @@ -386,6 +656,11 @@ function AbstractSection.__init__(self, map, sectiontype, ...) self.config = map.config self.optionals = {} self.defaults = {} + self.fields = {} + self.tag_error = {} + self.tag_invalid = {} + self.tag_deperror = {} + self.changed = false self.optional = true self.addremove = false @@ -414,6 +689,7 @@ function AbstractSection.option(self, class, option, ...) Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...) self:append(obj) + self.fields[option] = obj return obj elseif class == true then error("No valid class was given and autodetection failed.") @@ -430,7 +706,7 @@ function AbstractSection.parse_optionals(self, section) self.optionals[section] = {} - local field = luci.http.formvalue("cbi.opt."..self.config.."."..section) + local field = self.map:formvalue("cbi.opt."..self.config.."."..section) for k,v in ipairs(self.children) do if v.optional and not v:cfgvalue(section) then if field == v.option then @@ -459,7 +735,7 @@ function AbstractSection.parse_dynamic(self, section) end local arr = luci.util.clone(self:cfgvalue(section)) - local form = luci.http.formvaluetable("cbid."..self.config.."."..section) + local form = self.map:formvaluetable("cbid."..self.config.."."..section) for k, v in pairs(form) do arr[k] = v end @@ -484,6 +760,12 @@ function AbstractSection.cfgvalue(self, section) 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) @@ -494,7 +776,7 @@ function AbstractSection.create(self, section) 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 @@ -530,10 +812,18 @@ function Table.__init__(self, form, data, ...) local datasource = {} datasource.config = "table" self.data = data + + datasource.formvalue = Map.formvalue + datasource.formvaluetable = Map.formvaluetable + datasource.readinput = true function datasource.get(self, section, option) return data[section] and data[section][option] end + + function datasource.submitstate(self) + return Map.formvalue(self, "cbi.submit") + end function datasource.del(...) return true @@ -549,9 +839,10 @@ function Table.__init__(self, form, data, ...) self.anonymous = true end -function Table.parse(self) +function Table.parse(self, readinput) + self.map.readinput = (readinput ~= false) 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 @@ -594,19 +885,19 @@ function NamedSection.__init__(self, map, section, stype, ...) self.section = section end -function NamedSection.parse(self) +function NamedSection.parse(self, novld) local s = self.section local active = self:cfgvalue(s) - if self.addremove then local path = self.config.."."..s if active then -- Remove the section - if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then + if self.map:formvalue("cbi.rns."..path) and self:remove(s) then + self:push_events() return end else -- Create and apply default values - if luci.http.formvalue("cbi.cns."..path) then + if self.map:formvalue("cbi.cns."..path) then self:create(s) return end @@ -615,10 +906,18 @@ function NamedSection.parse(self) if active then AbstractSection.parse_dynamic(self, s) - if luci.http.formvalue("cbi.submit") then + if self.map:submitstate() then Node.parse(self, s) + + 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) + + if self.changed then + self:push_events() + end end end @@ -668,14 +967,42 @@ function TypedSection.depends(self, option, value) 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 + local name = self.map:formvaluetable(crval) + for k,v in pairs(name) do + if k:sub(-2) == ".x" then + k = k:sub(1, #k - 2) + end + if self:cfgvalue(k) and self:checkscope(k) then + self:remove(k) + end + end + end + + local co + for i, k in ipairs(self:cfgsections()) do + AbstractSection.parse_dynamic(self, k) + if self.map:submitstate() then + Node.parse(self, k) + + if not novld and not self.override_scheme and self.map.scheme then + _uvl_validate_section(self, k) + end + end + AbstractSection.parse_optionals(self, k) + end + if self.addremove then -- Create + local created local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype - local name = luci.http.formvalue(crval) + local name = self.map:formvalue(crval) if self.anonymous then if name then - self:create() + created = self:create() end else if name then @@ -691,27 +1018,21 @@ function TypedSection.parse(self) end if name and #name > 0 then - self:create(name) + created = self:create(name) and name + if not created then + self.invalid_cts = true + end end end end - -- Remove - crval = REMOVE_PREFIX .. self.config - name = luci.http.formvaluetable(crval) - for k,v in pairs(name) do - if self:cfgvalue(k) and self:checkscope(k) then - self:remove(k) - end + if created then + AbstractSection.parse_optionals(self, created) end end - for i, k in ipairs(self:cfgsections()) do - AbstractSection.parse_dynamic(self, k) - if luci.http.formvalue("cbi.submit") then - Node.parse(self, k) - end - AbstractSection.parse_optionals(self, k) + if created or self.changed then + self:push_events() end end @@ -767,24 +1088,34 @@ function AbstractValue.__init__(self, map, section, option, ...) self.config = map.config self.tag_invalid = {} self.tag_missing = {} + self.tag_reqerror = {} self.tag_error = {} self.deps = {} - self.cast = "string" + --self.cast = "string" self.track_missing = false - self.rmempty = false + --self.rmempty = 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) - 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 + 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 @@ -795,6 +1126,8 @@ function AbstractValue.__init__(self, map, section, option, ...) end end end + + self.cast = self.cast or "string" end -- Add a dependencie to another section field @@ -818,12 +1151,12 @@ end -- Return whether this object should be created function AbstractValue.formcreated(self, section) local key = "cbi.opt."..self.config.."."..section - return (luci.http.formvalue(key) == self.option) + return (self.map:formvalue(key) == self.option) end -- Returns the formvalue for this object function AbstractValue.formvalue(self, section) - return luci.http.formvalue(self:cbid(section)) + return self.map:formvalue(self:cbid(section)) end function AbstractValue.additional(self, value) @@ -838,17 +1171,25 @@ function AbstractValue.parse(self, 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 - 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 - 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 @@ -888,14 +1229,16 @@ end -- 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} + return luci.util.split(value, "%s+", nil, true) end end @@ -949,6 +1292,20 @@ function DummyValue.__init__(self, ...) 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 @@ -1002,26 +1359,33 @@ function ListValue.__init__(self, ...) 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 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 vs.enum_depends and vs.enum_depends[k] then - deps = _uvl_strip_remote_dependencies(vs.enum_depends[k]) + if not self.override_dependencies + 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 - self:value(k, v, unpack(deps)) + self:value(v.value, v.title or v.value, unpack(deps)) end end end end function ListValue.value(self, key, val, ...) + if luci.util.contains(self.keylist, key) then + return + end + val = val or key table.insert(self.keylist, tostring(key)) table.insert(self.vallist, tostring(val)) @@ -1068,6 +1432,10 @@ function MultiValue.render(self, ...) end function MultiValue.value(self, key, val) + if luci.util.contains(self.keylist, key) then + return + end + val = val or key table.insert(self.keylist, tostring(key)) table.insert(self.vallist, tostring(val)) @@ -1121,7 +1489,7 @@ function StaticList.validate(self, value) 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 @@ -1145,13 +1513,15 @@ function DynamicList.value(self, key, val) 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 = {} for i, v in ipairs(value) do - if v and #v > 0 and - not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then + 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) end end @@ -1182,3 +1552,58 @@ function Button.__init__(self, ...) 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 + self.map:formvalue("cbi.rlf."..section.."."..self.option) or + self.map: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 self.map:formvalue("cbi.rlf."..section.."."..self.option) and + not self.map: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