2013-07-28 05:01:58 +02:00
|
|
|
--[[--
|
|
|
|
The settings interface reads and writes configuration keys using UCI.
|
|
|
|
All keys have pre-defined defaults in @{conf_defaults} which will be used
|
|
|
|
if no value is stored in the UCI config. The UCI config file is
|
|
|
|
'/etc/config/wifibox'.
|
|
|
|
The default values guarantee there will always be a set of reasonable settings
|
|
|
|
to use and provide a clear overview of all existing configuration keys as well.
|
2013-07-26 02:17:05 +02:00
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
By the way, returning correct values in get()/fromUciValue() for booleans has
|
|
|
|
been fixed at a relatively convenient time purely thanks to the unit tests...
|
|
|
|
just to indicate how useful they are. :)
|
|
|
|
]]
|
|
|
|
local uci = require('uci').cursor()
|
2013-07-26 02:17:05 +02:00
|
|
|
local utils = require('util.utils')
|
2013-07-17 17:43:33 +02:00
|
|
|
local baseconfig = require('conf_defaults')
|
2013-10-17 14:04:13 +02:00
|
|
|
local utils = require('util.utils')
|
|
|
|
local log = require('util.logger')
|
2013-07-17 17:43:33 +02:00
|
|
|
|
|
|
|
local M = {}
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- UCI config name (i.e. file under /etc/config)
|
|
|
|
local UCI_CONFIG_NAME = 'wifibox'
|
|
|
|
|
|
|
|
--- Absolute path to the UCI config file
|
2013-07-17 17:43:33 +02:00
|
|
|
local UCI_CONFIG_FILE = '/etc/config/' .. UCI_CONFIG_NAME
|
2013-07-28 05:01:58 +02:00
|
|
|
|
|
|
|
--- Section type that will be used in UCI\_CONFIG\_FILE
|
|
|
|
local UCI_CONFIG_TYPE = 'settings'
|
|
|
|
|
2013-08-22 18:34:45 +02:00
|
|
|
--- Section name that will be used for 'public' settings (as predefined in conf_defaults.lua) in UCI\_CONFIG\_FILE
|
2013-07-28 05:01:58 +02:00
|
|
|
local UCI_CONFIG_SECTION = 'general'
|
|
|
|
|
2013-08-22 18:34:45 +02:00
|
|
|
--- Section name that will be used for 'firmware-local' settings in UCI\_CONFIG\_FILE
|
|
|
|
local UCI_CONFIG_SYSTEM_SECTION = 'system'
|
|
|
|
|
2013-07-17 17:43:33 +02:00
|
|
|
local ERR_NO_SUCH_KEY = "key does not exist"
|
|
|
|
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Returns a key with all periods ('.') replaced by underscores ('_').
|
|
|
|
-- @tparam string key The key for which to substitute dots.
|
2013-07-26 02:17:05 +02:00
|
|
|
-- @return The substituted key, or the key parameter itself if it is not of type 'string'.
|
|
|
|
local function replaceDots(key)
|
|
|
|
if type(key) ~= 'string' then return key end
|
2013-07-27 00:25:43 +02:00
|
|
|
local r = key:gsub('%.', '_')
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Returns a key with all underscores ('_') replaced by periods ('.').
|
|
|
|
-- @tparam string key The key for which to substitute underscores.
|
|
|
|
-- @return The substituted key, or the key parameter itself if it is not of type 'string'.
|
2013-07-27 00:25:43 +02:00
|
|
|
local function replaceUnderscores(key)
|
|
|
|
if type(key) ~= 'string' then return key end
|
|
|
|
local r = key:gsub('_', '%.')
|
|
|
|
return r
|
2013-07-26 02:17:05 +02:00
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Converts a lua value to equivalent representation for UCI.
|
|
|
|
-- Boolean values are converted to '1' and '0', everything else is converted to a string.
|
|
|
|
--
|
|
|
|
-- @param v The value to convert.
|
|
|
|
-- @param vType The type of the given value.
|
|
|
|
-- @return A value usable to write to UCI.
|
2013-07-26 02:17:05 +02:00
|
|
|
local function toUciValue(v, vType)
|
|
|
|
if vType == 'bool' then return v and '1' or '0' end
|
2013-08-28 17:12:41 +02:00
|
|
|
if(vType == 'string') then
|
|
|
|
v = v:gsub('[\n\r]', '\\n')
|
|
|
|
end
|
|
|
|
|
2013-07-17 17:43:33 +02:00
|
|
|
return tostring(v)
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Converts a value read from UCI to a correctly typed lua value.
|
|
|
|
-- For boolean, '1' is converted to true and everything else to false. Floats
|
|
|
|
-- and ints are converted to numbers and everything else will be returned as is.
|
|
|
|
--
|
|
|
|
-- @param v The value to convert.
|
|
|
|
-- @param vType The type of the given value.
|
|
|
|
-- @return A lua value typed correctly with regard to the vType parameter.
|
2013-07-26 02:17:05 +02:00
|
|
|
local function fromUciValue(v, vType)
|
|
|
|
if v == nil then return nil end
|
|
|
|
|
|
|
|
if vType == 'bool' then
|
2013-07-17 17:43:33 +02:00
|
|
|
return (v == '1') and true or false
|
2013-07-26 02:17:05 +02:00
|
|
|
elseif vType == 'float' or vType == 'int' then
|
2013-07-17 17:43:33 +02:00
|
|
|
return tonumber(v)
|
2013-08-28 17:12:41 +02:00
|
|
|
elseif vType == 'string' then
|
|
|
|
v = v:gsub('\\n', '\n')
|
|
|
|
return v
|
2013-07-17 17:43:33 +02:00
|
|
|
else
|
|
|
|
return v
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Reports whether a value is valid given the constraints specified in a base table.
|
|
|
|
-- @param value The value to test.
|
|
|
|
-- @tparam table baseTable The base table to use constraint data from (min,max,regex).
|
|
|
|
-- @treturn bool Returns true if the value is valid, false if it is not.
|
2013-07-17 17:43:33 +02:00
|
|
|
local function isValid(value, baseTable)
|
2013-08-29 01:40:51 +02:00
|
|
|
local varType, min, max, regex, isValid = baseTable.type, baseTable.min, baseTable.max, baseTable.regex, baseTable.isValid
|
|
|
|
|
|
|
|
if isValid then
|
2013-10-17 15:28:57 +02:00
|
|
|
local ok,msg = isValid(value)
|
|
|
|
if msg == nil then msg = "invalid value" end
|
|
|
|
return ok or nil,msg
|
2013-08-29 01:40:51 +02:00
|
|
|
end
|
|
|
|
|
2013-07-26 02:17:05 +02:00
|
|
|
if varType == 'bool' then
|
|
|
|
return type(value) == 'boolean' or nil,"invalid bool value"
|
2013-07-17 22:55:27 +02:00
|
|
|
|
2013-07-26 02:17:05 +02:00
|
|
|
elseif varType == 'int' or varType == 'float' then
|
2013-07-17 22:55:27 +02:00
|
|
|
local numValue = tonumber(value)
|
2013-10-17 14:04:13 +02:00
|
|
|
if numValue == nil then
|
|
|
|
return nil, "invalid number"
|
|
|
|
elseif varType == 'int' and math.floor(numValue) ~= numValue then
|
|
|
|
return nil, "invalid int"
|
|
|
|
elseif min and numValue < min then
|
|
|
|
return nil, "to low"
|
|
|
|
elseif max and numValue > max then
|
|
|
|
return nil, "to high"
|
|
|
|
end
|
2013-07-17 22:55:27 +02:00
|
|
|
|
2013-07-26 02:17:05 +02:00
|
|
|
elseif varType == 'string' then
|
2013-07-17 17:43:33 +02:00
|
|
|
local ok = true
|
2013-10-17 14:04:13 +02:00
|
|
|
if min and value:len() < min then
|
|
|
|
return nil,"to short"
|
|
|
|
elseif max and value:len() > max then
|
|
|
|
return nil,"to long"
|
|
|
|
elseif regex and value:match(regex) == nil then
|
|
|
|
return nil,"invalid value"
|
|
|
|
end
|
2013-07-17 17:43:33 +02:00
|
|
|
end
|
2013-08-29 01:40:51 +02:00
|
|
|
|
2013-07-17 17:43:33 +02:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Looks up the table in conf_defaults.lua corresponding to a key.
|
|
|
|
-- @tparam string key The key for which to return the base table.
|
|
|
|
-- @treturn table The base table for key, or nil if it does not exist.
|
2013-07-17 17:43:33 +02:00
|
|
|
local function getBaseKeyTable(key)
|
|
|
|
local base = baseconfig[key]
|
|
|
|
return type(base) == 'table' and base.default ~= nil and base or nil
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2013-07-26 02:17:05 +02:00
|
|
|
--- Returns the value of the requested key if it exists.
|
|
|
|
-- @param key The key to return the associated value for.
|
|
|
|
-- @return The associated value, beware (!) that this may be boolean false for keys of 'bool' type.
|
2013-07-17 17:43:33 +02:00
|
|
|
function M.get(key)
|
2013-07-26 02:17:05 +02:00
|
|
|
key = replaceDots(key)
|
2013-07-17 17:43:33 +02:00
|
|
|
local base = getBaseKeyTable(key)
|
|
|
|
|
|
|
|
if not base then return nil,ERR_NO_SUCH_KEY end
|
|
|
|
|
|
|
|
local v = base.default
|
2013-07-17 22:55:27 +02:00
|
|
|
local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key), base.type)
|
2013-08-28 17:12:41 +02:00
|
|
|
|
2013-07-26 02:17:05 +02:00
|
|
|
local actualV = v
|
|
|
|
if uciV ~= nil then actualV = uciV end
|
|
|
|
|
|
|
|
return actualV
|
2013-07-17 17:43:33 +02:00
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Returns all configuration keys with their current values.
|
|
|
|
-- @treturn table A table containing a key/value pair for each configuration key.
|
2013-07-27 00:25:43 +02:00
|
|
|
function M.getAll()
|
|
|
|
local result = {}
|
|
|
|
for k,_ in pairs(baseconfig) do
|
|
|
|
if not k:match('^[A-Z_]*$') then --TEMP: skip 'constants', which should be moved anyway
|
|
|
|
local key = replaceUnderscores(k)
|
|
|
|
result[key] = M.get(key)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Reports whether or not a key exists.
|
|
|
|
-- @tparam string key The key to find.
|
|
|
|
-- @treturn bool True if the key exists, false if not.
|
2013-07-17 17:43:33 +02:00
|
|
|
function M.exists(key)
|
2013-07-26 02:17:05 +02:00
|
|
|
key = replaceDots(key)
|
2013-07-17 17:43:33 +02:00
|
|
|
return getBaseKeyTable(key) ~= nil
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Reports whether or not a key is at its default value.
|
|
|
|
-- 'Default' in this regard means that no UCI value is defined. This means that
|
|
|
|
-- if for instance, the default is 'abc', and UCI contains a configured value of
|
|
|
|
-- 'abc' as well, that key is _not_ a default value.
|
|
|
|
--
|
|
|
|
-- @tparam string key The key to report about.
|
|
|
|
-- @treturn bool True if the key is currently at its default value, false if not.
|
2013-07-17 17:43:33 +02:00
|
|
|
function M.isDefault(key)
|
2013-07-26 02:17:05 +02:00
|
|
|
key = replaceDots(key)
|
2013-07-17 17:43:33 +02:00
|
|
|
if not M.exists(key) then return nil,ERR_NO_SUCH_KEY end
|
|
|
|
return uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key) == nil
|
|
|
|
end
|
|
|
|
|
2013-07-28 05:01:58 +02:00
|
|
|
--- Sets a key to a new value or reverts it to the default value.
|
|
|
|
-- @tparam string key The key to set.
|
|
|
|
-- @param value The value or set, or nil to revert key to its default value.
|
|
|
|
-- @treturn bool|nil True if everything went well, nil in case of error.
|
|
|
|
-- @treturn ?string Error message in case first return value is nil (invalid key).
|
2013-07-17 17:43:33 +02:00
|
|
|
function M.set(key, value)
|
2013-10-17 14:04:13 +02:00
|
|
|
--log:info("settings:set: "..utils.dump(key))
|
2013-07-26 02:17:05 +02:00
|
|
|
key = replaceDots(key)
|
2013-08-29 01:40:51 +02:00
|
|
|
|
2013-07-26 02:17:05 +02:00
|
|
|
local r = utils.create(UCI_CONFIG_FILE)
|
2013-07-17 17:43:33 +02:00
|
|
|
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, UCI_CONFIG_TYPE)
|
|
|
|
|
|
|
|
local base = getBaseKeyTable(key)
|
|
|
|
if not base then return nil,ERR_NO_SUCH_KEY end
|
|
|
|
|
2013-10-17 14:04:13 +02:00
|
|
|
if M.isDefault(key) and value == nil then return true end -- key is default already
|
|
|
|
--log:info(" not default")
|
2013-07-17 17:43:33 +02:00
|
|
|
local current = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
2013-10-17 14:04:13 +02:00
|
|
|
--log:info(" base.type: "..utils.dump(base.type))
|
2013-07-29 13:48:56 +02:00
|
|
|
if base.type == 'bool' then
|
|
|
|
if value ~= "" then
|
|
|
|
value = utils.toboolean(value)
|
|
|
|
else
|
|
|
|
value = nil
|
|
|
|
end
|
|
|
|
elseif base.type == 'int' or base.type == 'float' then
|
|
|
|
value = tonumber(value)
|
2013-08-29 01:40:51 +02:00
|
|
|
if(value == nil) then
|
|
|
|
return nil,"Value isn't a valid int or float"
|
|
|
|
end
|
2013-07-29 13:48:56 +02:00
|
|
|
end
|
2013-08-28 17:12:41 +02:00
|
|
|
|
2013-10-17 14:04:13 +02:00
|
|
|
|
|
|
|
local valid,m = isValid(value, base)
|
|
|
|
if not valid then
|
|
|
|
return nil,m
|
|
|
|
end
|
|
|
|
|
2013-07-17 22:55:27 +02:00
|
|
|
if fromUciValue(current, base.type) == value then return true end
|
2013-08-28 17:12:41 +02:00
|
|
|
|
2013-07-17 17:43:33 +02:00
|
|
|
if value ~= nil then
|
2013-10-17 14:04:13 +02:00
|
|
|
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key, toUciValue(value, base.type))
|
2013-07-17 17:43:33 +02:00
|
|
|
else
|
|
|
|
uci:delete(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
|
|
|
end
|
|
|
|
|
|
|
|
uci:commit(UCI_CONFIG_NAME)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2013-08-22 18:34:45 +02:00
|
|
|
--- Returns a UCI configuration key from the system section.
|
|
|
|
-- @tparam string key The key for which to return the value, must be non-empty.
|
|
|
|
-- @return Requested value or false if it does not exist or nil on invalid key.
|
|
|
|
function M.getSystemKey(key)
|
|
|
|
if type(key) ~= 'string' or key:len() == 0 then return nil end
|
|
|
|
local v = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SYSTEM_SECTION, key)
|
|
|
|
return v or false
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Sets the given key to the given value.
|
|
|
|
-- Note that unlike the public settings, system keys are untyped and value must
|
|
|
|
-- be of type string; UCI generally uses '1' and '0' for boolean values.
|
|
|
|
-- @tparam string key The key to set, must be non-empty.
|
|
|
|
-- @tparam string value The value to set key to.
|
|
|
|
-- @return True on success or false if key or value arguments are invalid.
|
|
|
|
function M.setSystemKey(key, value)
|
|
|
|
if type(key) ~= 'string' or key:len() == 0 then return nil end
|
|
|
|
if type(value) ~= 'string' then return nil end
|
|
|
|
|
|
|
|
local r = utils.create(UCI_CONFIG_FILE) -- make sure the file exists for uci to write to
|
|
|
|
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SYSTEM_SECTION, UCI_CONFIG_TYPE)
|
|
|
|
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SYSTEM_SECTION, key, value)
|
|
|
|
uci:commit(UCI_CONFIG_NAME)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2013-07-17 17:43:33 +02:00
|
|
|
return M
|