applications/luci-asterisk: begin reworking dialplan stuff to separate dialzones...
[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 io interface
29 -- Handles low level io.
30 -- @type        module
31 io = luci.util.class()
32
33 --- Execute command and return output
34 -- @param command       String containing the command to execute
35 -- @return                      String containing the command output
36 function io.exec(command)
37         local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
38         assert(fh, "Failed to invoke asterisk")
39
40         local buffer = fh:read("*a")
41         fh:close()
42         return buffer
43 end
44
45 --- Execute command and invoke given callback for each readed line
46 -- @param command       String containing the command to execute
47 -- @param callback      Function to call back for each line
48 -- @return                      Always true
49 function io.execl(command, callback)
50         local ln
51         local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
52         assert(fh, "Failed to invoke asterisk")
53
54         repeat
55                 ln = fh:read("*l")
56                 callback(ln)
57         until not ln
58
59         fh:close()
60         return true
61 end
62
63 --- Execute command and return an iterator that returns one line per invokation
64 -- @param command       String containing the command to execute
65 -- @return                      Iterator function
66 function io.execi(command)
67         local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
68         assert(fh, "Failed to invoke asterisk")
69
70         return function()
71                 local ln = fh:read("*l")
72                 if not ln then fh:close() end
73                 return ln
74         end
75 end
76
77
78 --- LuCI Asterisk - core status
79 core = luci.util.class()
80
81 --- Retrive version string.
82 -- @return      String containing the reported asterisk version
83 function core.version(self)
84         local version = io.exec("core show version")
85         return version:gsub(" *\n", "")
86 end
87
88
89 --- LuCI Asterisk - SIP information.
90 -- @type module
91 sip = luci.util.class()
92
93 --- Get a list of known SIP peers
94 -- @return              Table containing each SIP peer
95 function sip.peers(self)
96         local head  = false
97         local peers = { }
98
99         for line in io.execi("sip show peers") do
100                 if not head then
101                         head = true
102                 elseif not line:match(" sip peers ") then
103                         local online, delay, id, uid
104                         local name, host, dyn, nat, acl, port, status =
105                                 line:match("(.-) +(.-) +([D ])   ([N ])   (.)  (%d+) +(.+)")
106
107                         if host == '(Unspecified)' then host = nil end
108                         if port == '0' then port = nil else port = tonumber(port) end
109
110                         dyn = ( dyn == 'D' and true or false )
111                         nat = ( nat == 'N' and true or false )
112                         acl = ( acl ~= ' ' and true or false )
113
114                         online, delay = status:match("(OK) %((%d+) ms%)")
115
116                         if online == 'OK' then
117                                 online = true
118                                 delay  = tonumber(delay)
119                         elseif status ~= 'Unmonitored' then
120                                 online = false
121                                 delay  = 0
122                         else
123                                 online = nil
124                                 delay  = 0
125                         end
126
127                         id, uid = name:match("(.+)/(.+)")
128
129                         if not ( id and uid ) then
130                                 id  = name .. "..."
131                                 uid = nil
132                         end
133
134                         peers[#peers+1] = {
135                                 online  = online,
136                                 delay   = delay,
137                                 name    = id,
138                                 user    = uid,
139                                 dynamic = dyn,
140                                 nat     = nat,
141                                 acl     = acl,
142                                 host    = host,
143                                 port    = port
144                         }
145                 end
146         end
147
148         return peers
149 end
150
151 --- Get informations of given SIP peer
152 -- @param peer  String containing the name of the SIP peer
153 function sip.peer(peer)
154         local info = { }
155         local keys = { }
156
157         for line in io.execi("sip show peer " .. peer) do
158                 if #line > 0 then
159                         local key, val = line:match("(.-) *: +(.*)")
160                         if key and val then
161
162                                 key = key:gsub("^ +",""):gsub(" +$", "")
163                                 val = val:gsub("^ +",""):gsub(" +$", "")
164
165                                 if key == "* Name" then
166                                         key = "Name"
167                                 elseif key == "Addr->IP" then
168                                         info.address, info.port = val:match("(.+) Port (.+)")
169                                         info.port = tonumber(info.port)
170                                 elseif key == "Status" then
171                                         info.online, info.delay = val:match("(OK) %((%d+) ms%)")
172                                         if info.online == 'OK' then
173                                                 info.online = true
174                                                 info.delay  = tonumber(info.delay)
175                                         elseif status ~= 'Unmonitored' then
176                                                 info.online = false
177                                                 info.delay  = 0
178                                         else
179                                                 info.online = nil
180                                                 info.delay  = 0
181                                         end
182                                 end
183
184                                 if val == 'Yes' or val == 'yes' or val == '<Set>' then
185                                         val = true
186                                 elseif val == 'No' or val == 'no' then
187                                         val = false
188                                 elseif val == '<Not set>' or val == '(none)' then
189                                         val = nil
190                                 end
191
192                                 keys[#keys+1] = key
193                                 info[key] = val
194                         end
195                 end
196         end
197
198         return info, keys
199 end
200
201
202 --- LuCI Asterisk - Internal helpers
203 -- @type module
204 tools = luci.util.class()
205
206 --- Convert given value to a list of tokens. Split by white space.
207 -- @param val   String or table value
208 -- @return              Table containing tokens
209 function tools.parse_list(v)
210         local tokens = { }
211
212         v = type(v) == "table" and v or { v }
213         for _, v in ipairs(v) do
214                 if type(v) == "string" then
215                         for v in v:gmatch("(%S+)") do
216                                 tokens[#tokens+1] = v
217                         end
218                 end
219         end
220
221         return tokens
222 end
223
224 --- Convert given list to a collection of hyperlinks
225 -- @param list  Table of tokens
226 -- @param url   String pattern or callback function to construct urls (optional)
227 -- @param sep   String containing the seperator (optional, default is ", ")
228 -- @return              String containing the html fragment
229 function tools.hyperlinks(list, url, sep)
230         local html
231
232         local function mkurl(p, t)
233                 if type(p) == "string" then
234                         return p:format(t)
235                 elseif type(p) == "function" then
236                         return p(t)
237                 else
238                         return '#'
239                 end
240         end
241
242         list = list or { }
243         url  = url  or "%s"
244         sep  = sep  or ", "
245
246         for _, token in ipairs(list) do
247                 html = ( html and html .. sep or '' ) ..
248                         '<a href="%s">%s</a>' %{ mkurl(url, token), token }
249         end
250
251         return html or ''
252 end
253
254
255 --- LuCI Asterisk - Dialzone
256 -- @type        module
257 dialzone = luci.util.class()
258
259 --- Parse a dialzone section
260 -- @param zone  Table containing the zone info
261 -- @return              Table with parsed information
262 function dialzone.parse(z)
263         if z['.name'] then
264                 return {
265                         trunks          = tools.parse_list(z.uses),
266                         name            = z['.name'],
267                         description     = z.description or z['.name'],
268                         addprefix       = z.addprefix,
269                         matches         = tools.parse_list(z.match),
270                         intlmatches     = tools.parse_list(z.international),
271                         countrycode     = z.countrycode,
272                         localzone       = z.localzone,
273                         localprefix     = z.localprefix
274                 }
275         end
276 end
277
278 --- Get a list of known dial zones
279 -- @return              Associative table of zones and table of zone names
280 function dialzone.zones()
281         local zones  = { }
282         local znames = { }
283         uci:foreach("asterisk", "dialzone",
284                 function(z)
285                         zones[z['.name']] = dialzone.parse(z)
286                         znames[#znames+1] = z['.name']
287                 end)
288         return zones, znames
289 end
290
291 --- Get a specific dial zone
292 -- @param name  Name of the dial zone
293 -- @return              Table containing zone information
294 function dialzone.zone(n)
295         local zone
296         uci:foreach("asterisk", "dialzone",
297                 function(z)
298                         if z['.name'] == n then
299                                 zone = dialzone.parse(z)
300                         end
301                 end)
302         return zone
303 end
304
305 --- Find uci section hash for given zone number
306 -- @param idx   Zone number
307 -- @return              String containing the uci hash pointing to the section
308 function dialzone.ucisection(i)
309         local hash
310         local index = 1
311         i = tonumber(i)
312         uci:foreach("asterisk", "dialzone",
313                 function(z)
314                         if not hash and index == i then
315                                 hash = z['.name']
316                         end
317                         index = index + 1
318                 end)
319         return hash
320 end