applications/luci-asterisk: rework dialplan management
[project/luci.git] / applications / luci-asterisk / luasrc / asterisk.lua
1 --[[
2 LuCI - Lua Configuration Interface
3 Asterisk PBX interface library
4
5 Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17 module("luci.asterisk", package.seeall)
18
19 local _io  = require("io")
20 local uci  = require("luci.model.uci").cursor()
21 local sys  = require("luci.sys")
22 local util = require("luci.util")
23
24 AST_BIN   = "/usr/sbin/asterisk"
25 AST_FLAGS = "-r -x"
26
27
28 --- LuCI Asterisk - Resync uci context
29 function uci_resync()
30         uci = luci.model.uci.cursor()
31 end
32
33 --- LuCI Asterisk io interface
34 -- Handles low level io.
35 -- @type        module
36 io = luci.util.class()
37
38 --- Execute command and return output
39 -- @param command       String containing the command to execute
40 -- @return                      String containing the command output
41 function io.exec(command)
42         local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
43         assert(fh, "Failed to invoke asterisk")
44
45         local buffer = fh:read("*a")
46         fh:close()
47         return buffer
48 end
49
50 --- Execute command and invoke given callback for each readed line
51 -- @param command       String containing the command to execute
52 -- @param callback      Function to call back for each line
53 -- @return                      Always true
54 function io.execl(command, callback)
55         local ln
56         local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
57         assert(fh, "Failed to invoke asterisk")
58
59         repeat
60                 ln = fh:read("*l")
61                 callback(ln)
62         until not ln
63
64         fh:close()
65         return true
66 end
67
68 --- Execute command and return an iterator that returns one line per invokation
69 -- @param command       String containing the command to execute
70 -- @return                      Iterator function
71 function io.execi(command)
72         local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
73         assert(fh, "Failed to invoke asterisk")
74
75         return function()
76                 local ln = fh:read("*l")
77                 if not ln then fh:close() end
78                 return ln
79         end
80 end
81
82
83 --- LuCI Asterisk - core status
84 core = luci.util.class()
85
86 --- Retrive version string.
87 -- @return      String containing the reported asterisk version
88 function core.version(self)
89         local version = io.exec("core show version")
90         return version:gsub(" *\n", "")
91 end
92
93
94 --- LuCI Asterisk - SIP information.
95 -- @type module
96 sip = luci.util.class()
97
98 --- Get a list of known SIP peers
99 -- @return              Table containing each SIP peer
100 function sip.peers(self)
101         local head  = false
102         local peers = { }
103
104         for line in io.execi("sip show peers") do
105                 if not head then
106                         head = true
107                 elseif not line:match(" sip peers ") then
108                         local online, delay, id, uid
109                         local name, host, dyn, nat, acl, port, status =
110                                 line:match("(.-) +(.-) +([D ])   ([N ])   (.)  (%d+) +(.+)")
111
112                         if host == '(Unspecified)' then host = nil end
113                         if port == '0' then port = nil else port = tonumber(port) end
114
115                         dyn = ( dyn == 'D' and true or false )
116                         nat = ( nat == 'N' and true or false )
117                         acl = ( acl ~= ' ' and true or false )
118
119                         online, delay = status:match("(OK) %((%d+) ms%)")
120
121                         if online == 'OK' then
122                                 online = true
123                                 delay  = tonumber(delay)
124                         elseif status ~= 'Unmonitored' then
125                                 online = false
126                                 delay  = 0
127                         else
128                                 online = nil
129                                 delay  = 0
130                         end
131
132                         id, uid = name:match("(.+)/(.+)")
133
134                         if not ( id and uid ) then
135                                 id  = name .. "..."
136                                 uid = nil
137                         end
138
139                         peers[#peers+1] = {
140                                 online  = online,
141                                 delay   = delay,
142                                 name    = id,
143                                 user    = uid,
144                                 dynamic = dyn,
145                                 nat     = nat,
146                                 acl     = acl,
147                                 host    = host,
148                                 port    = port
149                         }
150                 end
151         end
152
153         return peers
154 end
155
156 --- Get informations of given SIP peer
157 -- @param peer  String containing the name of the SIP peer
158 function sip.peer(peer)
159         local info = { }
160         local keys = { }
161
162         for line in io.execi("sip show peer " .. peer) do
163                 if #line > 0 then
164                         local key, val = line:match("(.-) *: +(.*)")
165                         if key and val then
166
167                                 key = key:gsub("^ +",""):gsub(" +$", "")
168                                 val = val:gsub("^ +",""):gsub(" +$", "")
169
170                                 if key == "* Name" then
171                                         key = "Name"
172                                 elseif key == "Addr->IP" then
173                                         info.address, info.port = val:match("(.+) Port (.+)")
174                                         info.port = tonumber(info.port)
175                                 elseif key == "Status" then
176                                         info.online, info.delay = val:match("(OK) %((%d+) ms%)")
177                                         if info.online == 'OK' then
178                                                 info.online = true
179                                                 info.delay  = tonumber(info.delay)
180                                         elseif status ~= 'Unmonitored' then
181                                                 info.online = false
182                                                 info.delay  = 0
183                                         else
184                                                 info.online = nil
185                                                 info.delay  = 0
186                                         end
187                                 end
188
189                                 if val == 'Yes' or val == 'yes' or val == '<Set>' then
190                                         val = true
191                                 elseif val == 'No' or val == 'no' then
192                                         val = false
193                                 elseif val == '<Not set>' or val == '(none)' then
194                                         val = nil
195                                 end
196
197                                 keys[#keys+1] = key
198                                 info[key] = val
199                         end
200                 end
201         end
202
203         return info, keys
204 end
205
206
207 --- LuCI Asterisk - Internal helpers
208 -- @type module
209 tools = luci.util.class()
210
211 --- Convert given value to a list of tokens. Split by white space.
212 -- @param val   String or table value
213 -- @return              Table containing tokens
214 function tools.parse_list(v)
215         local tokens = { }
216
217         v = type(v) == "table" and v or { v }
218         for _, v in ipairs(v) do
219                 if type(v) == "string" then
220                         for v in v:gmatch("(%S+)") do
221                                 tokens[#tokens+1] = v
222                         end
223                 end
224         end
225
226         return tokens
227 end
228
229 --- Convert given list to a collection of hyperlinks
230 -- @param list  Table of tokens
231 -- @param url   String pattern or callback function to construct urls (optional)
232 -- @param sep   String containing the seperator (optional, default is ", ")
233 -- @return              String containing the html fragment
234 function tools.hyperlinks(list, url, sep)
235         local html
236
237         local function mkurl(p, t)
238                 if type(p) == "string" then
239                         return p:format(t)
240                 elseif type(p) == "function" then
241                         return p(t)
242                 else
243                         return '#'
244                 end
245         end
246
247         list = list or { }
248         url  = url  or "%s"
249         sep  = sep  or ", "
250
251         for _, token in ipairs(list) do
252                 html = ( html and html .. sep or '' ) ..
253                         '<a href="%s">%s</a>' %{ mkurl(url, token), token }
254         end
255
256         return html or ''
257 end
258
259
260 --- LuCI Asterisk - Dialzone
261 -- @type        module
262 dialzone = luci.util.class()
263
264 --- Parse a dialzone section
265 -- @param zone  Table containing the zone info
266 -- @return              Table with parsed information
267 function dialzone.parse(z)
268         if z['.name'] then
269                 return {
270                         trunks          = tools.parse_list(z.uses),
271                         name            = z['.name'],
272                         description     = z.description or z['.name'],
273                         addprefix       = z.addprefix,
274                         matches         = tools.parse_list(z.match),
275                         intlmatches     = tools.parse_list(z.international),
276                         countrycode     = z.countrycode,
277                         localzone       = z.localzone,
278                         localprefix     = z.localprefix
279                 }
280         end
281 end
282
283 --- Get a list of known dial zones
284 -- @return              Associative table of zones and table of zone names
285 function dialzone.zones()
286         local zones  = { }
287         local znames = { }
288         uci:foreach("asterisk", "dialzone",
289                 function(z)
290                         zones[z['.name']] = dialzone.parse(z)
291                         znames[#znames+1] = z['.name']
292                 end)
293         return zones, znames
294 end
295
296 --- Get a specific dial zone
297 -- @param name  Name of the dial zone
298 -- @return              Table containing zone information
299 function dialzone.zone(n)
300         local zone
301         uci:foreach("asterisk", "dialzone",
302                 function(z)
303                         if z['.name'] == n then
304                                 zone = dialzone.parse(z)
305                         end
306                 end)
307         return zone
308 end
309
310 --- Find uci section hash for given zone number
311 -- @param idx   Zone number
312 -- @return              String containing the uci hash pointing to the section
313 function dialzone.ucisection(i)
314         local hash
315         local index = 1
316         i = tonumber(i)
317         uci:foreach("asterisk", "dialzone",
318                 function(z)
319                         if not hash and index == i then
320                                 hash = z['.name']
321                         end
322                         index = index + 1
323                 end)
324         return hash
325 end
326
327
328 --- LuCI Asterisk - Dialplan
329 -- @type        module
330 dialplan = luci.util.class()
331
332 --- Parse a dialplan section
333 -- @param plan  Table containing the plan info
334 -- @return              Table with parsed information
335 function dialplan.parse(z)
336         if z['.name'] then
337                 local plan = {
338                         zones           = { },
339                         name            = z['.name'],
340                         description     = z.description or z['.name']
341                 }
342
343                 for _, name in ipairs(tools.parse_list(z.include)) do
344                         local zone = dialzone.zone(name)
345                         if zone then
346                                 plan.zones[#plan.zones+1] = zone
347                         end
348                 end
349
350                 return plan
351         end
352 end
353
354 --- Get a list of known dial plans
355 -- @return              Associative table of plans and table of plan names
356 function dialplan.plans()
357         local plans  = { }
358         local pnames = { }
359         uci:foreach("asterisk", "dialplan",
360                 function(p)
361                         plans[p['.name']] = dialplan.parse(p)
362                         pnames[#pnames+1] = p['.name']
363                 end)
364         return plans, pnames
365 end
366
367 --- Get a specific dial plan
368 -- @param name  Name of the dial plan
369 -- @return              Table containing plan information
370 function dialplan.plan(n)
371         local plan
372         uci:foreach("asterisk", "dialplan",
373                 function(p)
374                         if p['.name'] == n then
375                                 plan = dialplan.parse(p)
376                         end
377                 end)
378         return plan
379 end