]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/talk.py
CSS: Add CSS for file listings
[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
917434b8
MT
85
86class SIPRegistration(object):
87 def __init__(self, freeswitch, data):
88 self.freeswitch = freeswitch
89 self.data = data
90
91 @lazy_property
92 def protocol(self):
93 m = re.match(r"Registered\(([A-Z]+)(\-NAT)?\)", self.data.status)
94
95 if m:
96 return m.group(1)
97
98 @property
99 def network_ip(self):
100 return ipaddress.ip_address(self.data.network_ip)
101
102 @property
103 def network_port(self):
104 return self.data.network_port
105
106 @property
107 def user_agent(self):
108 return self.data.user_agent
109
110 def is_reachable(self):
111 return self.data.ping_status == "Reachable"
112
113 @lazy_property
114 def latency(self):
b78ec69b 115 if self.is_reachable() and self.data.ping_time:
917434b8
MT
116 return self.data.ping_time / 1000.0
117
118
9f05796c
MT
119class Channel(object):
120 def __init__(self, freeswitch, data):
121 self.freeswitch = freeswitch
122 self.data = data
66862195 123
9f05796c
MT
124 @property
125 def backend(self):
126 return self.freeswitch.backend
13e89ccc 127
9f05796c
MT
128 @property
129 def uuid(self):
130 return self.data.uuid
66862195 131
9f05796c
MT
132 @property
133 def direction(self):
134 return self.data.direction
135
136 @lazy_property
137 def caller(self):
138 return self.backend.accounts.get_by_sip_id(self.caller_number)
139
140 @property
141 def caller_name(self):
142 return self.data.cid_name
143
144 @property
145 def caller_number(self):
146 return self.data.cid_num
147
148 @lazy_property
149 def callee(self):
150 return self.backend.accounts.get_by_sip_id(self.callee_number)
66862195 151
9f05796c
MT
152 @property
153 def callee_name(self):
154 return self.data.callee_name
155
156 @property
157 def callee_number(self):
158 return self.data.callee_num
159
160 @property
161 def called_number(self):
162 return self.data.dest
163
e2b63b7f
MT
164 @property
165 def state(self):
166 return self.data.callstate
167
9f05796c
MT
168 @property
169 def application(self):
170 return self.data.application
66862195 171
9f05796c
MT
172 @property
173 def application_data(self):
174 return self.data.application_data
66862195 175
30aeccdb
MT
176 @lazy_property
177 def conference(self):
178 if self.application == "conference":
179 return Conference(self.freeswitch, self.application_data)
180
9f05796c
MT
181 @property
182 def duration(self):
183 return time.time() - self.data.created_epoch
66862195 184
9f05796c
MT
185 @property
186 def codec(self):
187 # We always assume a symmetric codec
8f06a493 188 return format_codec(self.data.write_codec, int(self.data.write_rate or 0), int(self.data.write_bit_rate or 0))
9f05796c 189
fe55357f
MT
190 def is_secure(self):
191 if self.data.secure:
192 return True
193
194 return False
195
9f05796c
MT
196 @property
197 def secure(self):
fe55357f
MT
198 try:
199 transport_protocol, key_negotiation, cipher_suite = self.data.secure.split(":")
200 except:
201 return
202
203 return "%s: %s" % (key_negotiation.upper(), cipher_suite.replace("_", "-"))
9f05796c
MT
204
205
525c01f7
MT
206class CDR(object):
207 def __init__(self, freeswitch, data):
208 self.freeswitch = freeswitch
209 self.data = data
210
211 @property
212 def backend(self):
213 return self.freeswitch.backend
214
68ece434
MT
215 @property
216 def db(self):
217 return self.freeswitch.db
218
219 @property
220 def uuid(self):
221 return self.data.uuid
222
223 @lazy_property
224 def bleg(self):
225 if self.data.bleg_uuid:
226 return self.freeswitch.get_call_by_uuid(self.data.bleg_uuid)
227
228 # If we are the bleg, we need to search for one where UUID is the bleg
229 res = self.db.get("SELECT * FROM cdr WHERE bleg_uuid = %s", self.uuid)
230
231 if res:
232 return CDR(self.freeswitch, data=res)
233
525c01f7
MT
234 @property
235 def direction(self):
236 if self.data.bleg_uuid:
237 return "inbound"
238
239 return "outbound"
240
241 @lazy_property
242 def caller(self):
243 return self.backend.accounts.get_by_phone_number(self.data.caller_id_number)
244
245 @property
246 def caller_number(self):
247 return self.data.caller_id_number
248
249 @lazy_property
250 def callee(self):
251 return self.backend.accounts.get_by_phone_number(self.data.destination_number)
252
253 @property
254 def callee_number(self):
255 return self.data.destination_number
256
257 @property
258 def time_start(self):
259 return self.data.start_stamp
260
261 @property
262 def time_answered(self):
263 return self.data.answer_stamp
264
265 @property
266 def duration(self):
267 return self.data.duration
268
269 @property
270 def codec(self):
8f06a493 271 return format_codec(self.data.write_codec, int(self.data.write_rate or 0), int(self.data.write_bit_rate or 0))
525c01f7
MT
272
273 @property
274 def user_agent(self):
68ece434
MT
275 if self.data.user_agent:
276 return self.data.user_agent.replace("_", " ")
525c01f7 277
354445ed
MT
278 @property
279 def size(self):
280 return sum((self.data.rtp_audio_in_raw_bytes or 0, self.data.rtp_audio_out_raw_bytes or 0))
281
282 @property
283 def mos(self):
284 return self.data.rtp_audio_in_mos
285
525c01f7 286
30aeccdb
MT
287class Conference(object):
288 def __init__(self, freeswitch, handle):
289 self.freeswitch = freeswitch
290 self.handle = handle
66862195 291
30aeccdb
MT
292 def __repr__(self):
293 return "<%s %s>" % (self.__class__.__name__, self.handle)
66862195 294
30aeccdb
MT
295 def __len__(self):
296 return len(self.channels)
66862195 297
30aeccdb
MT
298 def __eq__(self, other):
299 if isinstance(other, self.__class__):
300 return self.handle == other.handle
66862195 301
30aeccdb
MT
302 def __iter__(self):
303 return iter(self.channels)
66862195 304
30aeccdb
MT
305 @lazy_property
306 def number(self):
307 m = re.match(r"conf(\d+)@", self.handle)
308 if m:
309 i = m.group(1)
66862195 310
30aeccdb 311 return int(i)
66862195
MT
312
313 @property
314 def sip_id(self):
30aeccdb 315 return 900 + self.number
66862195 316
30aeccdb
MT
317 @lazy_property
318 def channels(self):
319 return self.freeswitch._get_channels("SELECT * FROM channels \
320 WHERE application = %s AND application_data = %s \
321 ORDER BY created_epoch", "conference", self.handle)
66862195 322
66862195 323
30aeccdb
MT
324class Talk(Object):
325 def init(self):
326 # Connect to FreeSWITCH
327 self.freeswitch = Freeswitch(self.backend)
328
329 @property
330 def conferences(self):
331 return self.freeswitch.get_conferences()
8f06a493
MT
332
333
334def format_codec(name, bit_rate, bandwidth):
e2b63b7f
MT
335 if not name:
336 return
337
8f06a493
MT
338 s = [
339 name,
340 ]
341
342 if bit_rate:
343 s.append("%.0f kHz" % (bit_rate / 1000.0))
344
345 if bandwidth:
346 s.append("%.0f kBit/s" % (bandwidth / 1000.0))
347 else:
348 s.append("VBR")
349
350 return " ".join(s)