]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/talk.py
people: Show SIP agent status
[ipfire.org.git] / src / backend / talk.py
CommitLineData
66862195
MT
1#!/usr/bin/python
2
917434b8
MT
3import ipaddress
4import logging
77431b9c 5import re
9f05796c 6import time
77431b9c 7
5ac74b02 8from . import database
66862195 9
11347e46 10from .misc import Object
13e89ccc
MT
11from .decorators import *
12
13class Freeswitch(Object):
14 @lazy_property
15 def db(self):
16 credentials = {
17 "host" : self.settings.get("freeswitch_database_host"),
18 "database" : self.settings.get("freeswitch_database_name", "freeswitch"),
19 "user" : self.settings.get("freeswitch_database_user"),
20 "password" : self.settings.get("freeswitch_database_password"),
21 }
22
23 return database.Connection(**credentials)
24
917434b8
MT
25 def get_sip_registrations(self, sip_uri):
26 logging.debug("Fetching SIP registrations for %s" % sip_uri)
27
28 user, delim, domain = sip_uri.partition("@")
29
30 res = self.db.query("SELECT * FROM sip_registrations \
31 WHERE sip_user = %s AND sip_host = %s AND expires >= EXTRACT(epoch FROM CURRENT_TIMESTAMP) \
32 ORDER BY contact", user, domain)
33
34 for row in res:
35 yield SIPRegistration(self, data=row)
36
30aeccdb
MT
37 def _get_channels(self, query, *args):
38 res = self.db.query(query, *args)
9f05796c 39
9c50164e 40 channels = []
9f05796c 41 for row in res:
9c50164e
MT
42 c = Channel(self, data=row)
43 channels.append(c)
44
45 return channels
9f05796c 46
30aeccdb
MT
47 def get_sip_channels(self, account):
48 return self._get_channels("SELECT * FROM channels \
49 WHERE (direction = %s AND cid_num = %s) OR \
50 (direction = %s AND (callee_num = %s OR callee_num = ANY(%s))) \
e2b63b7f 51 AND callstate != %s ORDER BY created_epoch",
30aeccdb 52 "inbound", account.sip_id, "outbound", account.sip_id,
e2b63b7f 53 account._all_telephone_numbers, "DOWN")
30aeccdb 54
bdaf6b46 55 def get_cdr_by_account(self, account, date=None, limit=None):
525c01f7 56 res = self.db.query("SELECT * FROM cdr \
8476e80f
MT
57 WHERE ((caller_id_number = ANY(%s) AND bleg_uuid IS NOT NULL) \
58 OR (destination_number = ANY(%s) AND bleg_uuid IS NULL)) \
bdaf6b46 59 AND (%s IS NULL OR start_stamp::date = %s) \
8476e80f
MT
60 ORDER BY end_stamp DESC LIMIT %s", account._all_telephone_numbers,
61 account._all_telephone_numbers, date, date, limit)
525c01f7
MT
62
63 for row in res:
64 yield CDR(self, data=row)
65
68ece434
MT
66 def get_call_by_uuid(self, uuid):
67 res = self.db.get("SELECT * FROM cdr \
68 WHERE uuid = %s", uuid)
69
70 if res:
71 return CDR(self, data=res)
72
30aeccdb
MT
73 def get_conferences(self):
74 res = self.db.query("SELECT DISTINCT application_data AS handle FROM channels \
75 WHERE application = %s AND application_data LIKE %s \
76 ORDER BY application_data", "conference", "%%@ipfire.org")
77
78 conferences = []
79 for row in res:
80 c = Conference(self, row.handle)
81 conferences.append(c)
82
83 return conferences
84
c66f2152
MT
85 def get_agent_status(self, account):
86 res = self.db.get("SELECT status FROM agents \
87 WHERE name = %s", account.sip_url)
88
89 if res:
90 return res.status
917434b8
MT
91
92class SIPRegistration(object):
93 def __init__(self, freeswitch, data):
94 self.freeswitch = freeswitch
95 self.data = data
96
97 @lazy_property
98 def protocol(self):
99 m = re.match(r"Registered\(([A-Z]+)(\-NAT)?\)", self.data.status)
100
101 if m:
102 return m.group(1)
103
104 @property
105 def network_ip(self):
106 return ipaddress.ip_address(self.data.network_ip)
107
108 @property
109 def network_port(self):
110 return self.data.network_port
111
112 @property
113 def user_agent(self):
114 return self.data.user_agent
115
116 def is_reachable(self):
117 return self.data.ping_status == "Reachable"
118
119 @lazy_property
120 def latency(self):
b78ec69b 121 if self.is_reachable() and self.data.ping_time:
917434b8
MT
122 return self.data.ping_time / 1000.0
123
124
9f05796c
MT
125class Channel(object):
126 def __init__(self, freeswitch, data):
127 self.freeswitch = freeswitch
128 self.data = data
66862195 129
9f05796c
MT
130 @property
131 def backend(self):
132 return self.freeswitch.backend
13e89ccc 133
9f05796c
MT
134 @property
135 def uuid(self):
136 return self.data.uuid
66862195 137
9f05796c
MT
138 @property
139 def direction(self):
140 return self.data.direction
141
142 @lazy_property
143 def caller(self):
144 return self.backend.accounts.get_by_sip_id(self.caller_number)
145
146 @property
147 def caller_name(self):
148 return self.data.cid_name
149
150 @property
151 def caller_number(self):
152 return self.data.cid_num
153
154 @lazy_property
155 def callee(self):
156 return self.backend.accounts.get_by_sip_id(self.callee_number)
66862195 157
9f05796c
MT
158 @property
159 def callee_name(self):
160 return self.data.callee_name
161
162 @property
163 def callee_number(self):
164 return self.data.callee_num
165
166 @property
167 def called_number(self):
168 return self.data.dest
169
e2b63b7f
MT
170 @property
171 def state(self):
172 return self.data.callstate
173
9f05796c
MT
174 @property
175 def application(self):
176 return self.data.application
66862195 177
9f05796c
MT
178 @property
179 def application_data(self):
180 return self.data.application_data
66862195 181
30aeccdb
MT
182 @lazy_property
183 def conference(self):
184 if self.application == "conference":
185 return Conference(self.freeswitch, self.application_data)
186
9f05796c
MT
187 @property
188 def duration(self):
189 return time.time() - self.data.created_epoch
66862195 190
9f05796c
MT
191 @property
192 def codec(self):
193 # We always assume a symmetric codec
8f06a493 194 return format_codec(self.data.write_codec, int(self.data.write_rate or 0), int(self.data.write_bit_rate or 0))
9f05796c 195
fe55357f
MT
196 def is_secure(self):
197 if self.data.secure:
198 return True
199
200 return False
201
9f05796c
MT
202 @property
203 def secure(self):
fe55357f
MT
204 try:
205 transport_protocol, key_negotiation, cipher_suite = self.data.secure.split(":")
206 except:
207 return
208
209 return "%s: %s" % (key_negotiation.upper(), cipher_suite.replace("_", "-"))
9f05796c
MT
210
211
525c01f7
MT
212class CDR(object):
213 def __init__(self, freeswitch, data):
214 self.freeswitch = freeswitch
215 self.data = data
216
217 @property
218 def backend(self):
219 return self.freeswitch.backend
220
68ece434
MT
221 @property
222 def db(self):
223 return self.freeswitch.db
224
225 @property
226 def uuid(self):
227 return self.data.uuid
228
229 @lazy_property
230 def bleg(self):
231 if self.data.bleg_uuid:
232 return self.freeswitch.get_call_by_uuid(self.data.bleg_uuid)
233
234 # If we are the bleg, we need to search for one where UUID is the bleg
235 res = self.db.get("SELECT * FROM cdr WHERE bleg_uuid = %s", self.uuid)
236
237 if res:
238 return CDR(self.freeswitch, data=res)
239
525c01f7
MT
240 @property
241 def direction(self):
242 if self.data.bleg_uuid:
243 return "inbound"
244
245 return "outbound"
246
247 @lazy_property
248 def caller(self):
249 return self.backend.accounts.get_by_phone_number(self.data.caller_id_number)
250
251 @property
252 def caller_number(self):
253 return self.data.caller_id_number
254
255 @lazy_property
256 def callee(self):
257 return self.backend.accounts.get_by_phone_number(self.data.destination_number)
258
259 @property
260 def callee_number(self):
261 return self.data.destination_number
262
263 @property
264 def time_start(self):
265 return self.data.start_stamp
266
267 @property
268 def time_answered(self):
269 return self.data.answer_stamp
270
271 @property
272 def duration(self):
273 return self.data.duration
274
275 @property
276 def codec(self):
8f06a493 277 return format_codec(self.data.write_codec, int(self.data.write_rate or 0), int(self.data.write_bit_rate or 0))
525c01f7
MT
278
279 @property
280 def user_agent(self):
68ece434
MT
281 if self.data.user_agent:
282 return self.data.user_agent.replace("_", " ")
525c01f7 283
354445ed
MT
284 @property
285 def size(self):
286 return sum((self.data.rtp_audio_in_raw_bytes or 0, self.data.rtp_audio_out_raw_bytes or 0))
287
288 @property
289 def mos(self):
290 return self.data.rtp_audio_in_mos
291
525c01f7 292
30aeccdb
MT
293class Conference(object):
294 def __init__(self, freeswitch, handle):
295 self.freeswitch = freeswitch
296 self.handle = handle
66862195 297
30aeccdb
MT
298 def __repr__(self):
299 return "<%s %s>" % (self.__class__.__name__, self.handle)
66862195 300
30aeccdb
MT
301 def __len__(self):
302 return len(self.channels)
66862195 303
30aeccdb
MT
304 def __eq__(self, other):
305 if isinstance(other, self.__class__):
306 return self.handle == other.handle
66862195 307
30aeccdb
MT
308 def __iter__(self):
309 return iter(self.channels)
66862195 310
30aeccdb
MT
311 @lazy_property
312 def number(self):
313 m = re.match(r"conf(\d+)@", self.handle)
314 if m:
315 i = m.group(1)
66862195 316
30aeccdb 317 return int(i)
66862195
MT
318
319 @property
320 def sip_id(self):
30aeccdb 321 return 900 + self.number
66862195 322
30aeccdb
MT
323 @lazy_property
324 def channels(self):
325 return self.freeswitch._get_channels("SELECT * FROM channels \
326 WHERE application = %s AND application_data = %s \
327 ORDER BY created_epoch", "conference", self.handle)
66862195 328
66862195 329
30aeccdb
MT
330class Talk(Object):
331 def init(self):
332 # Connect to FreeSWITCH
333 self.freeswitch = Freeswitch(self.backend)
334
335 @property
336 def conferences(self):
337 return self.freeswitch.get_conferences()
8f06a493
MT
338
339
340def format_codec(name, bit_rate, bandwidth):
e2b63b7f
MT
341 if not name:
342 return
343
8f06a493
MT
344 s = [
345 name,
346 ]
347
348 if bit_rate:
349 s.append("%.0f kHz" % (bit_rate / 1000.0))
350
351 if bandwidth:
352 s.append("%.0f kBit/s" % (bandwidth / 1000.0))
353 else:
354 s.append("VBR")
355
356 return " ".join(s)