]> git.ipfire.org Git - ipfire.org.git/blob - src/backend/talk.py
Update Christman campaign copy
[ipfire.org.git] / src / backend / talk.py
1 #!/usr/bin/python
2
3 import ipaddress
4 import logging
5 import re
6 import time
7
8 from . import database
9
10 from .misc import Object
11 from .decorators import *
12
13 class 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
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
37 def _get_channels(self, query, *args):
38 res = self.db.query(query, *args)
39
40 channels = []
41 for row in res:
42 c = Channel(self, data=row)
43 channels.append(c)
44
45 return channels
46
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))) \
51 AND callstate != %s ORDER BY created_epoch",
52 "inbound", account.sip_id, "outbound", account.sip_id,
53 account._all_telephone_numbers, "DOWN")
54
55 def get_cdr_by_account(self, account, date=None, limit=None):
56 res = self.db.query("SELECT * FROM cdr \
57 WHERE ((caller_id_number = ANY(%s) AND bleg_uuid IS NOT NULL) \
58 OR (destination_number = ANY(%s) AND bleg_uuid IS NULL)) \
59 AND (%s IS NULL OR start_stamp::date = %s) \
60 ORDER BY end_stamp DESC LIMIT %s", account._all_telephone_numbers,
61 account._all_telephone_numbers, date, date, limit)
62
63 for row in res:
64 yield CDR(self, data=row)
65
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
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
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
91
92 class 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):
121 if self.is_reachable() and self.data.ping_time:
122 return self.data.ping_time / 1000.0
123
124
125 class Channel(object):
126 def __init__(self, freeswitch, data):
127 self.freeswitch = freeswitch
128 self.data = data
129
130 @property
131 def backend(self):
132 return self.freeswitch.backend
133
134 @property
135 def uuid(self):
136 return self.data.uuid
137
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)
157
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
170 @property
171 def state(self):
172 return self.data.callstate
173
174 @property
175 def application(self):
176 return self.data.application
177
178 @property
179 def application_data(self):
180 return self.data.application_data
181
182 @lazy_property
183 def conference(self):
184 if self.application == "conference":
185 return Conference(self.freeswitch, self.application_data)
186
187 @property
188 def duration(self):
189 return time.time() - self.data.created_epoch
190
191 @property
192 def codec(self):
193 # We always assume a symmetric codec
194 return format_codec(self.data.write_codec, int(self.data.write_rate or 0), int(self.data.write_bit_rate or 0))
195
196 def is_secure(self):
197 if self.data.secure:
198 return True
199
200 return False
201
202 @property
203 def secure(self):
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("_", "-"))
210
211
212 class 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
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
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):
277 return format_codec(self.data.write_codec, int(self.data.write_rate or 0), int(self.data.write_bit_rate or 0))
278
279 @property
280 def user_agent(self):
281 if self.data.user_agent:
282 return self.data.user_agent.replace("_", " ")
283
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
292
293 class Conference(object):
294 def __init__(self, freeswitch, handle):
295 self.freeswitch = freeswitch
296 self.handle = handle
297
298 def __repr__(self):
299 return "<%s %s>" % (self.__class__.__name__, self.handle)
300
301 def __len__(self):
302 return len(self.channels)
303
304 def __eq__(self, other):
305 if isinstance(other, self.__class__):
306 return self.handle == other.handle
307
308 def __iter__(self):
309 return iter(self.channels)
310
311 @lazy_property
312 def number(self):
313 m = re.match(r"conf(\d+)@", self.handle)
314 if m:
315 i = m.group(1)
316
317 return int(i)
318
319 @property
320 def sip_id(self):
321 return 900 + self.number
322
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)
328
329
330 class 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()
338
339
340 def format_codec(name, bit_rate, bandwidth):
341 if not name:
342 return
343
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)