1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
4 local os = require "os"
5 local util = require "luci.util"
6 local conf = require "luci.config"
7 local table = require "table"
10 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
11 local require, getmetatable, assert = require, getmetatable, assert
12 local error, pairs, ipairs = error, pairs, ipairs
13 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
15 -- The typical workflow for UCI is: Get a cursor instance from the
16 -- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
17 -- save the changes to the staging area via Cursor.save and finally
18 -- Cursor.commit the data to the actual config files.
19 -- LuCI then needs to Cursor.apply the changes so deamons etc. are
21 module "luci.model.uci"
36 local session_id = nil
38 local function call(cmd, args)
39 if type(args) == "table" and session_id then
40 args.ubus_rpc_session = session_id
42 return util.ubus("uci", cmd, args)
50 function cursor_state()
54 function substate(self)
59 function get_confdir(self)
63 function get_savedir(self)
67 function get_session_id(self)
71 function set_confdir(self, directory)
75 function set_savedir(self, directory)
79 function set_session_id(self, id)
85 function load(self, config)
89 function save(self, config)
93 function unload(self, config)
98 function changes(self, config)
99 local rv = call("changes", { config = config })
102 if type(rv) == "table" and type(rv.changes) == "table" then
103 local package, changes
104 for package, changes in pairs(rv.changes) do
108 for _, change in ipairs(changes) do
109 local operation, section, option, value = unpack(change)
110 if option and value and operation ~= "add" then
111 res[package][section] = res[package][section] or { }
113 if operation == "list-add" then
114 local v = res[package][section][option]
115 if type(v) == "table" then
116 v[#v+1] = value or ""
118 res[package][section][option] = { v, value }
120 res[package][section][option] = { value }
123 res[package][section][option] = value or ""
126 res[package][section] = res[package][section] or {}
127 res[package][section][".type"] = option or ""
137 function revert(self, config)
138 local _, err = call("revert", { config = config })
139 return (err == nil), ERRSTR[err]
142 function commit(self, config)
143 local _, err = call("commit", { config = config })
144 return (err == nil), ERRSTR[err]
147 function apply(self, rollback)
151 local timeout = tonumber(conf.apply and conf.apply.rollback or "") or 0
153 _, err = call("apply", {
154 timeout = (timeout > 30) and timeout or 30,
159 util.ubus("session", "set", {
160 ubus_rpc_session = session_id,
161 values = { rollback = os.time() + timeout }
165 _, err = call("changes", {})
168 if type(_) == "table" and type(_.changes) == "table" then
170 for k, v in pairs(_.changes) do
171 _, err = call("commit", { config = k })
180 _, err = call("apply", { rollback = false })
184 return (err == nil), ERRSTR[err]
187 function confirm(self)
188 local _, err = call("confirm", {})
190 util.ubus("session", "set", {
191 ubus_rpc_session = session_id,
192 values = { rollback = 0 }
195 return (err == nil), ERRSTR[err]
198 function rollback(self)
199 local _, err = call("rollback", {})
201 util.ubus("session", "set", {
202 ubus_rpc_session = session_id,
203 values = { rollback = 0 }
206 return (err == nil), ERRSTR[err]
209 function rollback_pending(self)
210 local deadline, err = util.ubus("session", "get", {
211 ubus_rpc_session = session_id,
212 keys = { "rollback" }
215 if type(deadline) == "table" and
216 type(deadline.values) == "table" and
217 type(deadline.values.rollback) == "number" and
218 deadline.values.rollback > os.time()
220 return true, deadline.values.rollback - os.time()
223 return false, ERRSTR[err]
227 function foreach(self, config, stype, callback)
228 if type(callback) == "function" then
229 local rv, err = call("get", {
234 if type(rv) == "table" and type(rv.values) == "table" then
240 for _, section in pairs(rv.values) do
241 section[".index"] = section[".index"] or index
242 sections[index] = section
246 table.sort(sections, function(a, b)
247 return a[".index"] < b[".index"]
250 for _, section in ipairs(sections) do
251 local continue = callback(section)
253 if continue == false then
259 return false, ERRSTR[err] or "No data"
262 return false, "Invalid argument"
266 local function _get(self, operation, config, section, option)
267 if section == nil then
269 elseif type(option) == "string" and option:byte(1) ~= 46 then
270 local rv, err = call(operation, {
276 if type(rv) == "table" then
277 return rv.value or nil
279 return false, ERRSTR[err]
283 elseif option == nil then
284 local values = self:get_all(config, section)
286 return values[".type"], values[".name"]
291 return false, "Invalid argument"
295 function get(self, ...)
296 return _get(self, "get", ...)
299 function get_state(self, ...)
300 return _get(self, "state", ...)
303 function get_all(self, config, section)
304 local rv, err = call("get", {
309 if type(rv) == "table" and type(rv.values) == "table" then
312 return false, ERRSTR[err]
318 function get_bool(self, ...)
319 local val = self:get(...)
320 return (val == "1" or val == "true" or val == "yes" or val == "on")
323 function get_first(self, config, stype, option, default)
326 self:foreach(config, stype, function(s)
327 local val = not option and s[".name"] or s[option]
329 if type(default) == "number" then
331 elseif type(default) == "boolean" then
332 val = (val == "1" or val == "true" or
333 val == "yes" or val == "on")
345 function get_list(self, config, section, option)
346 if config and section and option then
347 local val = self:get(config, section, option)
348 return (type(val) == "table" and val or { val })
354 function section(self, config, stype, name, values)
355 local rv, err = call("add", {
362 if type(rv) == "table" then
365 return false, ERRSTR[err]
372 function add(self, config, stype)
373 return self:section(config, stype)
376 function set(self, config, section, option, value)
378 local sname, err = self:section(config, option, section)
379 return (not not sname), err
381 local _, err = call("set", {
384 values = { [option] = value }
386 return (err == nil), ERRSTR[err]
390 function set_list(self, config, section, option, value)
391 if section == nil or option == nil then
393 elseif value == nil or (type(value) == "table" and #value == 0) then
394 return self:delete(config, section, option)
395 elseif type(value) == "table" then
396 return self:set(config, section, option, value)
398 return self:set(config, section, option, { value })
402 function tset(self, config, section, values)
403 local _, err = call("set", {
408 return (err == nil), ERRSTR[err]
411 function reorder(self, config, section, index)
414 if type(section) == "string" and type(index) == "number" then
419 self:foreach(config, nil, function(s)
424 if s[".name"] ~= section then
426 sections[pos] = s[".name"]
428 sections[index + 1] = section
431 elseif type(section) == "table" then
434 return false, "Invalid argument"
437 local _, err = call("order", {
442 return (err == nil), ERRSTR[err]
446 function delete(self, config, section, option)
447 local _, err = call("delete", {
452 return (err == nil), ERRSTR[err]
455 function delete_all(self, config, stype, comparator)
457 if type(comparator) == "table" then
458 _, err = call("delete", {
463 elseif type(comparator) == "function" then
464 local rv = call("get", {
469 if type(rv) == "table" and type(rv.values) == "table" then
471 for sname, section in pairs(rv.values) do
472 if comparator(section) then
473 _, err = call("delete", {
480 elseif comparator == nil then
481 _, err = call("delete", {
486 return false, "Invalid argument"
489 return (err == nil), ERRSTR[err]