Rewrote UCI apply logic
[project/luci.git] / libs / uci / luasrc / model / uci.lua
index e4c5d5d..8852d1e 100644 (file)
@@ -1,5 +1,5 @@
 --[[
-LuCI - UCI mpdel
+LuCI - UCI model
 
 Description:
 Generalized UCI model
@@ -23,37 +23,79 @@ See the License for the specific language governing permissions and
 limitations under the License.
 
 ]]--
-local uci  = require("uci")
-local util = require("luci.util")
+local os    = require "os"
+local uci   = require "uci"
+local util  = require "luci.util"
+local table = require "table"
+
+
 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
 local error, pairs, ipairs, tostring = error, pairs, ipairs, tostring
-local table = table
+local require, getmetatable, type = require, getmetatable, type
 
 --- LuCI UCI model library.
-module("luci.model.uci", function(m) setmetatable(m, {__index = uci}) end)
+-- @cstyle     instance
+module "luci.model.uci"
+
+--- Create a new UCI-Cursor.
+-- @class function
+-- @name cursor
+-- @return     UCI-Cursor
+cursor = uci.cursor
+
+APIVERSION = uci.APIVERSION
 
-savedir_default = "/tmp/.uci"
-confdir_default = "/etc/config"
+--- Create a new Cursor initialized to the state directory.
+-- @return UCI cursor
+function cursor_state()
+       return cursor(nil, "/var/state")
+end
+
+
+local Cursor = getmetatable(cursor())
+
+--- 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
 
-savedir_state = "/var/state"
 
 --- 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 delete_all(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 not 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"])
-                       end
+
+               if not comparator or comparator(section) then
+                       table.insert(del, section[".name"])
+               end
        end
 
-       foreach(config, type, helper)
+       self:foreach(config, stype, helper)
 
        for i, j in ipairs(del) do
-               delete(config, j)
+               self:delete(config, j)
        end
 end
 
@@ -63,73 +105,31 @@ end
 -- @param name         UCI section name (optional)
 -- @param values       Table of key - value pairs to initialize the section with
 -- @return                     Name of created section
-function section(config, type, name, values)
+function Cursor.section(self, config, type, name, values)
        local stat = true
        if name then
-               stat = set(config, name, type)
+               stat = self:set(config, name, type)
        else
-               name = add(config, type)
+               name = self:add(config, type)
                stat = name and true
        end
 
        if stat and values then
-               stat = tset(config, name, values)
+               stat = self:tset(config, name, values)
        end
 
        return stat and name
 end
 
---- Savely load the configuration.
--- @param config       Configuration to load
--- @return                     Sucess status
--- @see                                load_state
--- @see                                load
-function load_config(...)
-       set_confdir(confdir_default)
-       set_savedir(savedir_default)
-       return load(...)
-end
-
---- Savely load state values.
--- @param config       Configuration to load
--- @return                     Sucess status
--- @see                                load_config
--- @see                                load
-function load_state(config)
-       set_confdir(confdir_default)
-       set_savedir(savedir_state)
-       return load(config)
-end
-
---- Save changes to config values.
--- @param config       Configuration to save
--- @return                     Sucess status
--- @see                                save_state
--- @see                                save
-function save_config(config)
-       set_savedir(savedir_default)
-       return save(config)
-end
-
---- Save changes to state values.
--- @param config       Configuration to save
--- @return                     Sucess status
--- @see                                save_config
--- @see                                save
-function save_state(config)
-       set_savedir(savedir_state)
-       return save(config)
-end
-
 --- Updated the data of a section using data from a table.
 -- @param config       UCI config
 -- @param section      UCI section name (optional)
 -- @param values       Table of key - value pairs to update the section with
-function tset(config, section, values)
+function Cursor.tset(self, config, section, values)
        local stat = true
        for k, v in pairs(values) do
                if k:sub(1, 1) ~= "." then
-                       stat = stat and set(config, section, k, v)
+                       stat = stat and self:set(config, section, k, v)
                end
        end
        return stat
@@ -140,24 +140,23 @@ end
 -- @param section      UCI section name
 -- @param option       UCI option
 -- @return                     UCI value
-function get_list(config, section, option)
+function Cursor.get_list(self, config, section, option)
        if config and section and option then
-               local val = get(config, section, option)
+               local val = self:get(config, section, option)
                return ( type(val) == "table" and val or { val } )
        end
        return nil
 end
 
 --- Set given values as list.
--- Warning: This function is unsave! You should use save_config or save_state if possible.
 -- @param config       UCI config
 -- @param section      UCI section name
 -- @param option       UCI option
 -- @param value                UCI value
 -- @return                     Boolean whether operation succeeded
-function set_list(config, section, option, value)
+function Cursor.set_list(self, config, section, option, value)
        if config and section and option then
-               return set(
+               return self:set(
                        config, section, option,
                        ( type(value) == "table" and value or { value } )
                )
@@ -166,29 +165,92 @@ function set_list(config, section, option, value)
 end
 
 
+Cursor._changes = Cursor.changes
+function Cursor.changes(self, config)
+       if config then
+               return Cursor._changes(self, config)
+       else
+               local changes = Cursor._changes(self)
+               util.copcall(function()
+                       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))
+                               end
+                       end
+               end)
+               return changes
+       end
+end
+
+
+-- Return a list of initscripts affected by configuration changes.
+function Cursor._affected(self, configlist)
+       configlist = type(configlist) == "table" and configlist or {configlist}
+
+       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
+                                               table.insert(deps, aff)
+                                       end
+                               end
+                       end)
+               
+               for i, dep in ipairs(deps) do
+                       for j, add in ipairs(_resolve_deps(dep)) do
+                               table.insert(reload, 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
+                               table.insert(reloadlist, e)
+                       end
+               end
+       end
+       
+       return reloadlist
+end
+
+
 --- Add an anonymous section.
 -- @class function
--- @name add
+-- @name Cursor.add
 -- @param config       UCI config
 -- @param type         UCI section type
 -- @return                     Name of created section
 
 --- Get a table of unsaved changes.
 -- @class function
--- @name changes
+-- @name Cursor.changes
 -- @param config       UCI config
 -- @return                     Table of changes
 
 --- Commit unsaved changes.
 -- @class function
--- @name commit
+-- @name Cursor.commit
 -- @param config       UCI config
 -- @return                     Boolean whether operation succeeded
--- @see revert
+-- @see Cursor.revert
 
 --- Deletes a section or an option.
 -- @class function
--- @name delete
+-- @name Cursor.delete
 -- @param config       UCI config
 -- @param section      UCI section name
 -- @param option       UCI option (optional)
@@ -196,7 +258,7 @@ end
 
 --- Call a function for every section of a certain type.
 -- @class function
--- @name foreach
+-- @name Cursor.foreach
 -- @param config       UCI config
 -- @param type         UCI section type
 -- @param callback     Function to be called
@@ -204,7 +266,7 @@ end
 
 --- Get a section type or an option
 -- @class function
--- @name get
+-- @name Cursor.get
 -- @param config       UCI config
 -- @param section      UCI section name
 -- @param option       UCI option (optional)
@@ -212,63 +274,69 @@ end
 
 --- Get all sections of a config or all values of a section.
 -- @class function
--- @name get_all
+-- @name Cursor.get_all
 -- @param config       UCI config
 -- @param section      UCI section name (optional)
 -- @return                     Table of UCI sections or table of UCI values
 
 --- Manually load a config.
--- Warning: This function is unsave! You should use load_config or load_state if possible.
 -- @class function
--- @name load
+-- @name Cursor.load
 -- @param config       UCI config
 -- @return                     Boolean whether operation succeeded
--- @see load_config
--- @see load_state
--- @see save
--- @see unload
+-- @see Cursor.save
+-- @see Cursor.unload
 
 --- Revert unsaved changes.
 -- @class function
--- @name revert
+-- @name Cursor.revert
 -- @param config       UCI config
 -- @return                     Boolean whether operation succeeded
--- @see commit
+-- @see Cursor.commit
 
 --- Saves changes made to a config to make them committable.
 -- @class function
--- @name save
+-- @name Cursor.save
 -- @param config       UCI config
 -- @return                     Boolean whether operation succeeded
--- @see load
--- @see unload
+-- @see Cursor.load
+-- @see Cursor.unload
 
 --- Set a value or create a named section.
--- Warning: This function is unsave! You should use save_config or save_state if possible.
 -- @class function
--- @name set
+-- @name Cursor.set
 -- @param config       UCI config
 -- @param section      UCI section name
 -- @param option       UCI option or UCI section type
 -- @param value                UCI value or nil if you want to create a section
 -- @return                     Boolean whether operation succeeded
 
+--- Get the configuration directory.
+-- @class function
+-- @name Cursor.get_confdir
+-- @return                     Configuration directory
+
+--- Get the directory for uncomitted changes.
+-- @class function
+-- @name Cursor.get_savedir
+-- @return                     Save directory
+
 --- Set the configuration directory.
 -- @class function
--- @name set_confdir
+-- @name Cursor.set_confdir
 -- @param directory    UCI configuration directory
 -- @return                     Boolean whether operation succeeded
 
 --- Set the directory for uncommited changes.
 -- @class function
--- @name set_savedir
+-- @name Cursor.set_savedir
 -- @param directory    UCI changes directory
 -- @return                     Boolean whether operation succeeded
 
 --- Discard changes made to a config.
 -- @class function
--- @name unload
+-- @name Cursor.unload
 -- @param config       UCI config
 -- @return                     Boolean whether operation succeeded
--- @see load
--- @see save
+-- @see Cursor.load
+-- @see Cursor.save