local setmetatable, rawget, rawset = setmetatable, rawget, rawset
local error, pairs, ipairs, tostring = error, pairs, ipairs, tostring
-local require, getmetatable = require, getmetatable
+local require, getmetatable, type = require, getmetatable, type
--- LuCI UCI model library.
+-- The typical workflow for UCI is: Get a cursor instance from the
+-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
+-- save the changes to the staging area via Cursor.save and finally
+-- Cursor.commit the data to the actual config files.
+-- LuCI then needs to Cursor.apply the changes so deamons etc. are
+-- reloaded.
-- @cstyle instance
module "luci.model.uci"
end
-local Cursor = getmetatable(cursor())
+inst = cursor()
+inst_state = cursor_state()
---- Applies the new config
--- @param config UCI config
-function Cursor.apply(self, config)
- local conf = require "luci.config"
- return conf.uci_oncommit[config] and
- os.execute(conf.uci_oncommit[config] .. " >/dev/null 2>&1")
+local Cursor = getmetatable(inst)
+
+--- Applies UCI configuration changes
+-- @param configlist List of UCI configurations
+-- @param command Don't apply only return the command
+function Cursor.apply(self, configlist, command)
+ configlist = self:_affected(configlist)
+ local reloadcmd = "/sbin/luci-reload " .. table.concat(configlist, " ")
+
+ return command and reloadcmd or os.execute(reloadcmd .. " >/dev/null 2>&1")
end
+
--- Delete all sections of a given type that match certain criteria.
-- @param config UCI config
-- @param type UCI section type
-- @param comparator Function that will be called for each section and
-- returns a boolean whether to delete the current section (optional)
-function Cursor.delete_all(self, config, type, comparator)
+function Cursor.delete_all(self, config, stype, comparator)
local del = {}
+
+ if type(comparator) == "table" then
+ local tbl = comparator
+ comparator = function(section)
+ for k, v in pairs(tbl) do
+ if section[k] ~= v then
+ return false
+ end
+ end
+ return true
+ end
+ end
+
local function helper (section)
+
if not comparator or comparator(section) then
- table.insert(del, section[".name"])
+ del[#del+1] = section[".name"]
end
end
- self:foreach(config, type, helper)
+ self:foreach(config, stype, helper)
for i, j in ipairs(del) do
self:delete(config, j)
return stat
end
+--- Get a boolean option and return it's value as true or false.
+-- @param config UCI config
+-- @param section UCI section name
+-- @param option UCI option
+-- @return Boolean
+function Cursor.get_bool(self, ...)
+ local val = self:get(...)
+ return ( val == "1" or val == "true" or val == "yes" or val == "on" )
+end
+
--- Get an option or list and return values as table.
-- @param config UCI config
-- @param section UCI section name
return false
end
+-- Return a list of initscripts affected by configuration changes.
+function Cursor._affected(self, configlist)
+ configlist = type(configlist) == "table" and configlist or {configlist}
-Cursor._changes = Cursor.changes
-function Cursor.changes(self, config)
- if config then
- return Cursor._changes(self, config)
- else
- local changes = {}
- for k,v in pairs(require "luci.fs".dir(self:get_savedir())) do
- if v ~= "." and v ~= ".." then
- util.update(changes, Cursor._changes(self, v))
+ local c = cursor()
+ c:load("ucitrack")
+
+ -- Resolve dependencies
+ local reloadlist = {}
+
+ local function _resolve_deps(name)
+ local reload = {name}
+ local deps = {}
+
+ c:foreach("ucitrack", name,
+ function(section)
+ if section.affects then
+ for i, aff in ipairs(section.affects) do
+ deps[#deps+1] = aff
+ end
+ end
+ end)
+
+ for i, dep in ipairs(deps) do
+ for j, add in ipairs(_resolve_deps(dep)) do
+ reload[#reload+1] = add
+ end
+ end
+
+ return reload
+ end
+
+ -- Collect initscripts
+ for j, config in ipairs(configlist) do
+ for i, e in ipairs(_resolve_deps(config)) do
+ if not util.contains(reloadlist, e) then
+ reloadlist[#reloadlist+1] = e
end
end
- return changes
end
+
+ return reloadlist
end
-- @param type UCI section type
-- @return Name of created section
---- Get a table of unsaved changes.
+--- Get a table of saved but uncommitted changes.
-- @class function
-- @name Cursor.changes
-- @param config UCI config
-- @return Table of changes
+-- @see Cursor.save
---- Commit unsaved changes.
+--- Commit saved changes.
-- @class function
-- @name Cursor.commit
-- @param config UCI config
-- @return Boolean whether operation succeeded
-- @see Cursor.revert
+-- @see Cursor.save
--- Deletes a section or an option.
-- @class function
-- @see Cursor.save
-- @see Cursor.unload
---- Revert unsaved changes.
+--- Revert saved but uncommitted changes.
-- @class function
-- @name Cursor.revert
-- @param config UCI config
-- @return Boolean whether operation succeeded
-- @see Cursor.commit
+-- @see Cursor.save
--- Saves changes made to a config to make them committable.
-- @class function