Move inline documentation into separate files.
[project/luci.git] / modules / luci-base / luasrc / model / uci.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local os    = require "os"
5 local uci   = require "uci"
6 local util  = require "luci.util"
7 local table = require "table"
8
9
10 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
11 local require, getmetatable = require, getmetatable
12 local error, pairs, ipairs = error, pairs, ipairs
13 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
14
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
20 -- reloaded.
21 module "luci.model.uci"
22
23 cursor = uci.cursor
24
25 APIVERSION = uci.APIVERSION
26
27 function cursor_state()
28         return cursor(nil, "/var/state")
29 end
30
31
32 inst = cursor()
33 inst_state = cursor_state()
34
35 local Cursor = getmetatable(inst)
36
37 function Cursor.apply(self, configlist, command)
38         configlist = self:_affected(configlist)
39         if command then
40                 return { "/sbin/luci-reload", unpack(configlist) }
41         else
42                 return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
43                         % table.concat(configlist, " "))
44         end
45 end
46
47
48 -- returns a boolean whether to delete the current section (optional)
49 function Cursor.delete_all(self, config, stype, comparator)
50         local del = {}
51
52         if type(comparator) == "table" then
53                 local tbl = comparator
54                 comparator = function(section)
55                         for k, v in pairs(tbl) do
56                                 if section[k] ~= v then
57                                         return false
58                                 end
59                         end
60                         return true
61                 end
62         end
63
64         local function helper (section)
65
66                 if not comparator or comparator(section) then
67                         del[#del+1] = section[".name"]
68                 end
69         end
70
71         self:foreach(config, stype, helper)
72
73         for i, j in ipairs(del) do
74                 self:delete(config, j)
75         end
76 end
77
78 function Cursor.section(self, config, type, name, values)
79         local stat = true
80         if name then
81                 stat = self:set(config, name, type)
82         else
83                 name = self:add(config, type)
84                 stat = name and true
85         end
86
87         if stat and values then
88                 stat = self:tset(config, name, values)
89         end
90
91         return stat and name
92 end
93
94 function Cursor.tset(self, config, section, values)
95         local stat = true
96         for k, v in pairs(values) do
97                 if k:sub(1, 1) ~= "." then
98                         stat = stat and self:set(config, section, k, v)
99                 end
100         end
101         return stat
102 end
103
104 function Cursor.get_bool(self, ...)
105         local val = self:get(...)
106         return ( val == "1" or val == "true" or val == "yes" or val == "on" )
107 end
108
109 function Cursor.get_list(self, config, section, option)
110         if config and section and option then
111                 local val = self:get(config, section, option)
112                 return ( type(val) == "table" and val or { val } )
113         end
114         return nil
115 end
116
117 function Cursor.get_first(self, conf, stype, opt, def)
118         local rv = def
119
120         self:foreach(conf, stype,
121                 function(s)
122                         local val = not opt and s['.name'] or s[opt]
123
124                         if type(def) == "number" then
125                                 val = tonumber(val)
126                         elseif type(def) == "boolean" then
127                                 val = (val == "1" or val == "true" or
128                                        val == "yes" or val == "on")
129                         end
130
131                         if val ~= nil then
132                                 rv = val
133                                 return false
134                         end
135                 end)
136
137         return rv
138 end
139
140 function Cursor.set_list(self, config, section, option, value)
141         if config and section and option then
142                 return self:set(
143                         config, section, option,
144                         ( type(value) == "table" and value or { value } )
145                 )
146         end
147         return false
148 end
149
150 -- Return a list of initscripts affected by configuration changes.
151 function Cursor._affected(self, configlist)
152         configlist = type(configlist) == "table" and configlist or {configlist}
153
154         local c = cursor()
155         c:load("ucitrack")
156
157         -- Resolve dependencies
158         local reloadlist = {}
159
160         local function _resolve_deps(name)
161                 local reload = {name}
162                 local deps = {}
163
164                 c:foreach("ucitrack", name,
165                         function(section)
166                                 if section.affects then
167                                         for i, aff in ipairs(section.affects) do
168                                                 deps[#deps+1] = aff
169                                         end
170                                 end
171                         end)
172
173                 for i, dep in ipairs(deps) do
174                         for j, add in ipairs(_resolve_deps(dep)) do
175                                 reload[#reload+1] = add
176                         end
177                 end
178
179                 return reload
180         end
181
182         -- Collect initscripts
183         for j, config in ipairs(configlist) do
184                 for i, e in ipairs(_resolve_deps(config)) do
185                         if not util.contains(reloadlist, e) then
186                                 reloadlist[#reloadlist+1] = e
187                         end
188                 end
189         end
190
191         return reloadlist
192 end
193
194 -- curser, means it the parent unloads or loads configs, the sub state will
195 -- do so as well.
196 function Cursor.substate(self)
197         Cursor._substates = Cursor._substates or { }
198         Cursor._substates[self] = Cursor._substates[self] or cursor_state()
199         return Cursor._substates[self]
200 end
201
202 local _load = Cursor.load
203 function Cursor.load(self, ...)
204         if Cursor._substates and Cursor._substates[self] then
205                 _load(Cursor._substates[self], ...)
206         end
207         return _load(self, ...)
208 end
209
210 local _unload = Cursor.unload
211 function Cursor.unload(self, ...)
212         if Cursor._substates and Cursor._substates[self] then
213                 _unload(Cursor._substates[self], ...)
214         end
215         return _unload(self, ...)
216 end
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233