Performance optimisations:
[project/luci.git] / libs / uci / luasrc / model / uci.lua
1 --[[
2 LuCI - UCI model
3
4 Description:
5 Generalized UCI model
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26 local os    = require "os"
27 local uci   = require "uci"
28 local util  = require "luci.util"
29 local table = require "table"
30
31
32 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
33 local error, pairs, ipairs, tostring = error, pairs, ipairs, tostring
34 local require, getmetatable, type = require, getmetatable, type
35
36 --- LuCI UCI model library.
37 -- @cstyle      instance
38 module "luci.model.uci"
39
40 --- Create a new UCI-Cursor.
41 -- @class function
42 -- @name cursor
43 -- @return      UCI-Cursor
44 cursor = uci.cursor
45
46 APIVERSION = uci.APIVERSION
47
48 --- Create a new Cursor initialized to the state directory.
49 -- @return UCI cursor
50 function cursor_state()
51         return cursor(nil, "/var/state")
52 end
53
54
55 local Cursor = getmetatable(cursor())
56
57 --- Applies UCI configuration changes
58 -- @param configlist            List of UCI configurations
59 -- @param command                       Don't apply only return the command
60 function Cursor.apply(self, configlist, command)
61         configlist = self:_affected(configlist)
62         local reloadcmd = "/sbin/luci-reload " .. table.concat(configlist, " ")
63         
64         return command and reloadcmd or os.execute(reloadcmd .. " >/dev/null 2>&1")
65 end
66
67
68 --- Delete all sections of a given type that match certain criteria.
69 -- @param config                UCI config
70 -- @param type                  UCI section type
71 -- @param comparator    Function that will be called for each section and
72 -- returns a boolean whether to delete the current section (optional)
73 function Cursor.delete_all(self, config, stype, comparator)
74         local del = {}
75         
76         if type(comparator) == "table" then
77                 local tbl = comparator
78                 comparator = function(section)
79                         for k, v in pairs(tbl) do
80                                 if not section[k] == v then
81                                         return false
82                                 end
83                         end 
84                         return true
85                 end
86         end
87         
88         local function helper (section)
89
90                 if not comparator or comparator(section) then
91                         del[#del+1] = section[".name"]
92                 end
93         end
94
95         self:foreach(config, stype, helper)
96
97         for i, j in ipairs(del) do
98                 self:delete(config, j)
99         end
100 end
101
102 --- Create a new section and initialize it with data.
103 -- @param config        UCI config
104 -- @param type          UCI section type
105 -- @param name          UCI section name (optional)
106 -- @param values        Table of key - value pairs to initialize the section with
107 -- @return                      Name of created section
108 function Cursor.section(self, config, type, name, values)
109         local stat = true
110         if name then
111                 stat = self:set(config, name, type)
112         else
113                 name = self:add(config, type)
114                 stat = name and true
115         end
116
117         if stat and values then
118                 stat = self:tset(config, name, values)
119         end
120
121         return stat and name
122 end
123
124 --- Updated the data of a section using data from a table.
125 -- @param config        UCI config
126 -- @param section       UCI section name (optional)
127 -- @param values        Table of key - value pairs to update the section with
128 function Cursor.tset(self, config, section, values)
129         local stat = true
130         for k, v in pairs(values) do
131                 if k:sub(1, 1) ~= "." then
132                         stat = stat and self:set(config, section, k, v)
133                 end
134         end
135         return stat
136 end
137
138 --- Get an option or list and return values as table.
139 -- @param config        UCI config
140 -- @param section       UCI section name
141 -- @param option        UCI option
142 -- @return                      UCI value
143 function Cursor.get_list(self, config, section, option)
144         if config and section and option then
145                 local val = self:get(config, section, option)
146                 return ( type(val) == "table" and val or { val } )
147         end
148         return nil
149 end
150
151 --- Set given values as list.
152 -- @param config        UCI config
153 -- @param section       UCI section name
154 -- @param option        UCI option
155 -- @param value         UCI value
156 -- @return                      Boolean whether operation succeeded
157 function Cursor.set_list(self, config, section, option, value)
158         if config and section and option then
159                 return self:set(
160                         config, section, option,
161                         ( type(value) == "table" and value or { value } )
162                 )
163         end
164         return false
165 end
166
167
168 Cursor._changes = Cursor.changes
169 function Cursor.changes(self, config)
170         if config then
171                 return Cursor._changes(self, config)
172         else
173                 local changes = Cursor._changes(self)
174                 util.copcall(function()
175                         for k,v in pairs(require "luci.fs".dir(self:get_savedir())) do
176                                 if v ~= "." and v ~= ".." then
177                                         util.update(changes, Cursor._changes(self, v))
178                                 end
179                         end
180                 end)
181                 return changes
182         end
183 end
184
185
186 -- Return a list of initscripts affected by configuration changes.
187 function Cursor._affected(self, configlist)
188         configlist = type(configlist) == "table" and configlist or {configlist}
189
190         local c = cursor()
191         c:load("ucitrack")
192
193         -- Resolve dependencies
194         local reloadlist = {}
195
196         local function _resolve_deps(name)
197                 local reload = {name}
198                 local deps = {}
199         
200                 c:foreach("ucitrack", name,
201                         function(section)
202                                 if section.affects then
203                                         for i, aff in ipairs(section.affects) do
204                                                 deps[#deps+1] = aff
205                                         end
206                                 end
207                         end)
208                 
209                 for i, dep in ipairs(deps) do
210                         for j, add in ipairs(_resolve_deps(dep)) do
211                                 reload[#reload+1] = add
212                         end
213                 end
214                 
215                 return reload
216         end
217         
218         -- Collect initscripts
219         for j, config in ipairs(configlist) do
220                 for i, e in ipairs(_resolve_deps(config)) do
221                         if not util.contains(reloadlist, e) then
222                                 reloadlist[#reloadlist+1] = e
223                         end
224                 end
225         end
226         
227         return reloadlist
228 end
229
230
231 --- Add an anonymous section.
232 -- @class function
233 -- @name Cursor.add
234 -- @param config        UCI config
235 -- @param type          UCI section type
236 -- @return                      Name of created section
237
238 --- Get a table of unsaved changes.
239 -- @class function
240 -- @name Cursor.changes
241 -- @param config        UCI config
242 -- @return                      Table of changes
243
244 --- Commit unsaved changes.
245 -- @class function
246 -- @name Cursor.commit
247 -- @param config        UCI config
248 -- @return                      Boolean whether operation succeeded
249 -- @see Cursor.revert
250
251 --- Deletes a section or an option.
252 -- @class function
253 -- @name Cursor.delete
254 -- @param config        UCI config
255 -- @param section       UCI section name
256 -- @param option        UCI option (optional)
257 -- @return                      Boolean whether operation succeeded
258
259 --- Call a function for every section of a certain type.
260 -- @class function
261 -- @name Cursor.foreach
262 -- @param config        UCI config
263 -- @param type          UCI section type
264 -- @param callback      Function to be called
265 -- @return                      Boolean whether operation succeeded
266
267 --- Get a section type or an option
268 -- @class function
269 -- @name Cursor.get
270 -- @param config        UCI config
271 -- @param section       UCI section name
272 -- @param option        UCI option (optional)
273 -- @return                      UCI value
274
275 --- Get all sections of a config or all values of a section.
276 -- @class function
277 -- @name Cursor.get_all
278 -- @param config        UCI config
279 -- @param section       UCI section name (optional)
280 -- @return                      Table of UCI sections or table of UCI values
281
282 --- Manually load a config.
283 -- @class function
284 -- @name Cursor.load
285 -- @param config        UCI config
286 -- @return                      Boolean whether operation succeeded
287 -- @see Cursor.save
288 -- @see Cursor.unload
289
290 --- Revert unsaved changes.
291 -- @class function
292 -- @name Cursor.revert
293 -- @param config        UCI config
294 -- @return                      Boolean whether operation succeeded
295 -- @see Cursor.commit
296
297 --- Saves changes made to a config to make them committable.
298 -- @class function
299 -- @name Cursor.save
300 -- @param config        UCI config
301 -- @return                      Boolean whether operation succeeded
302 -- @see Cursor.load
303 -- @see Cursor.unload
304
305 --- Set a value or create a named section.
306 -- @class function
307 -- @name Cursor.set
308 -- @param config        UCI config
309 -- @param section       UCI section name
310 -- @param option        UCI option or UCI section type
311 -- @param value         UCI value or nil if you want to create a section
312 -- @return                      Boolean whether operation succeeded
313
314 --- Get the configuration directory.
315 -- @class function
316 -- @name Cursor.get_confdir
317 -- @return                      Configuration directory
318
319 --- Get the directory for uncomitted changes.
320 -- @class function
321 -- @name Cursor.get_savedir
322 -- @return                      Save directory
323
324 --- Set the configuration directory.
325 -- @class function
326 -- @name Cursor.set_confdir
327 -- @param directory     UCI configuration directory
328 -- @return                      Boolean whether operation succeeded
329
330 --- Set the directory for uncommited changes.
331 -- @class function
332 -- @name Cursor.set_savedir
333 -- @param directory     UCI changes directory
334 -- @return                      Boolean whether operation succeeded
335
336 --- Discard changes made to a config.
337 -- @class function
338 -- @name Cursor.unload
339 -- @param config        UCI config
340 -- @return                      Boolean whether operation succeeded
341 -- @see Cursor.load
342 -- @see Cursor.save