]> git.ipfire.org Git - ipfire.org.git/blob - src/backend/talk.py
talk: Add a search
[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_sip_channels(self, account):
38 res = self.db.query("SELECT * FROM channels \
39 WHERE (direction = %s AND cid_num = %s) OR \
40 (direction = %s AND callee_num = %s) ORDER BY created_epoch",
41 "inbound", account.sip_id, "outbound", account.sip_id)
42
43 channels = []
44
45 for row in res:
46 c = Channel(self, data=row)
47 channels.append(c)
48
49 return channels
50
51
52 class SIPRegistration(object):
53 def __init__(self, freeswitch, data):
54 self.freeswitch = freeswitch
55 self.data = data
56
57 @lazy_property
58 def protocol(self):
59 m = re.match(r"Registered\(([A-Z]+)(\-NAT)?\)", self.data.status)
60
61 if m:
62 return m.group(1)
63
64 @property
65 def network_ip(self):
66 return ipaddress.ip_address(self.data.network_ip)
67
68 @property
69 def network_port(self):
70 return self.data.network_port
71
72 @property
73 def user_agent(self):
74 return self.data.user_agent
75
76 def is_reachable(self):
77 return self.data.ping_status == "Reachable"
78
79 @lazy_property
80 def latency(self):
81 if self.is_reachable() and self.data.ping_time:
82 return self.data.ping_time / 1000.0
83
84
85 class Channel(object):
86 def __init__(self, freeswitch, data):
87 self.freeswitch = freeswitch
88 self.data = data
89
90 @property
91 def backend(self):
92 return self.freeswitch.backend
93
94 @property
95 def uuid(self):
96 return self.data.uuid
97
98 @property
99 def direction(self):
100 return self.data.direction
101
102 @lazy_property
103 def caller(self):
104 return self.backend.accounts.get_by_sip_id(self.caller_number)
105
106 @property
107 def caller_name(self):
108 return self.data.cid_name
109
110 @property
111 def caller_number(self):
112 return self.data.cid_num
113
114 @lazy_property
115 def callee(self):
116 return self.backend.accounts.get_by_sip_id(self.callee_number)
117
118 @property
119 def callee_name(self):
120 return self.data.callee_name
121
122 @property
123 def callee_number(self):
124 return self.data.callee_num
125
126 @property
127 def called_number(self):
128 return self.data.dest
129
130 @property
131 def application(self):
132 return self.data.application
133
134 @property
135 def application_data(self):
136 return self.data.application_data
137
138 @property
139 def duration(self):
140 return time.time() - self.data.created_epoch
141
142 @property
143 def codec(self):
144 # We always assume a symmetric codec
145 s = [
146 "%s @ %s kHz" % (self.data.write_codec, int(self.data.write_rate) / 1000.0),
147 ]
148
149 if self.data.write_bit_rate == "0":
150 s.append("VBR")
151 else:
152 s.append("%.0f kBit/s" % (int(self.data.write_bit_rate) / 1000.0))
153
154 return " ".join(s)
155
156 @property
157 def secure(self):
158 return self.data.secure
159
160
161 class Talk(Object):
162 def init(self):
163 # Connect to FreeSWITCH
164 self.freeswitch = Freeswitch(self.backend)
165
166 @property
167 def accounts(self):
168 for account in self.backend.accounts:
169 if not account.is_talk_enabled():
170 continue
171
172 yield account
173
174 def search(self, query):
175 accounts = []
176
177 for account in self.backend.accounts.search(query):
178 if not account.is_talk_enabled():
179 continue
180
181 accounts.append(account)
182
183 return accounts
184
185 def get_lines(self, account=None):
186 accounts_cache = {}
187
188 if account and not account.is_talk_enabled():
189 return []
190
191 if account:
192 res = self.db.query("SELECT contact AS location, expires, user_agent, socket \
193 FROM location WHERE domain = %s AND username = %s ORDER BY expires DESC",
194 "ipfire.org", account.sip_id)
195 else:
196 res = self.db.query("SELECT username, domain, contact AS location, \
197 expires, user_agent, socket FROM location ORDER BY username, expires DESC")
198
199 result = []
200 for row in res:
201 if account:
202 row.account = account
203 elif row.username:
204 try:
205 row.account = accounts_cache[row.username]
206 except KeyError:
207 row.account = accounts_cache[row.username] = \
208 self.accounts.get_by_sip_id(row.username)
209 else:
210 row.account = None
211
212 # Check if TLS is used
213 row.tls_enabled = row.socket.startswith("tls:")
214
215 # Cut off everything after ;
216 row.location, sep, rest = row.location.partition(";")
217
218 result.append(row)
219
220 return result
221
222 def _process_sip_uri(self, sip_uri):
223 sip_uri, delimiter, rest = sip_uri.partition(";")
224
225 m = re.match(r"^sip:([0-9]{4})@ipfire\.org", sip_uri)
226 if m:
227 return m.group(1)
228
229 # Remove trailing default port
230 if sip_uri.endswith(":5060"):
231 sip_uri = sip_uri.replace(":5060", "")
232
233 return sip_uri
234
235 def _process_cdr(self, entries, replace_sip_uris=False):
236 accounts_cache = {}
237
238 result = []
239 for row in entries:
240 if replace_sip_uris:
241 row["caller"] = self._process_sip_uri(row.caller)
242
243 try:
244 row["caller_account"] = accounts_cache[row.caller]
245 except KeyError:
246 row["caller_account"] = accounts_cache[row.caller] = \
247 self.accounts.get_by_sip_id(row.caller)
248
249 if replace_sip_uris:
250 row["called"] = self._process_sip_uri(row.called)
251
252 try:
253 row["called_account"] = accounts_cache[row.called]
254 except KeyError:
255 row["called_account"] = accounts_cache[row.called] = \
256 self.accounts.get_by_sip_id(row.called)
257
258 if not row.called_account:
259 row["called_conference"] = self.get_conference(row.called)
260 else:
261 row["called_conference"] = None
262
263 try:
264 row["time"] = datetime.datetime.fromutctimestamp(row.time)
265 except:
266 pass
267
268 result.append(row)
269
270 return result
271
272 def get_call_log(self, account, limit=25):
273 if account:
274 res = self.db.query("SELECT * FROM cdr WHERE (caller = %s OR called = %s) \
275 ORDER BY time DESC LIMIT %s", account.sip_id, account.sip_id, limit)
276 else:
277 res = self.db.query("SELECT * FROM cdr ORDER BY time DESC LIMIT %s", limit)
278
279 return self._process_cdr(res)
280
281 def get_channels(self, account=None):
282 channels = []
283 for c in a.list_channels():
284 if account and not account.sip_id in (c.caller, c.callee):
285 continue
286
287 channels.append(c)
288
289 return sorted(channels)
290
291 def get_channel(self, channel_id, account=None):
292 channels = self.get_channels(account=account)
293
294 for channel in channels:
295 if channel.id == channel_id:
296 return channel
297
298 def get_ongoing_calls(self, account=None, sip_id=None):
299 if account and sip_id is None:
300 sip_id = account.sip_id
301
302 # If the given account does not have a SIP ID,
303 # we not need to search for any active sessions
304 if sip_id is None:
305 return []
306
307 if sip_id:
308 sip_id = "sip:%s@%%" % sip_id
309
310 res = self.db.query("SELECT from_uri AS caller, \
311 to_uri AS called, start_time AS time FROM dialog \
312 WHERE from_uri LIKE %s OR to_uri LIKE %s ORDER BY time",
313 sip_id, sip_id)
314 else:
315 res = self.db.query("SELECT from_uri AS caller, to_uri AS called, \
316 start_time AS time FROM dialog ORDER BY time")
317
318 return self._process_cdr(res, replace_sip_uris=True)
319
320 # Conferences
321
322 @property
323 def conferences(self):
324 conferences = []
325
326 for no in range(1, 10):
327 conference = Conference(self.backend, no)
328 conferences.append(conference)
329
330 return conferences
331
332 def get_conference(self, id):
333 for c in self.conferences:
334 if not c.sip_id == id:
335 continue
336
337 return c
338
339
340 class Conference(Object):
341 def __init__(self, backend, no):
342 Object.__init__(self, backend)
343 self.no = no
344
345 def __cmp__(self, other):
346 return cmp(self.no, other.no)
347
348 @property
349 def name(self):
350 return "IPFire Conference Room %s" % self.no
351
352 @property
353 def sip_id(self):
354 return "%s" % (9000 + self.no)
355
356 @property
357 def sip_url(self):
358 return "%s@ipfire.org" % self.sip_id
359
360 @property
361 def participants(self):
362 if not hasattr(self, "_participants"):
363 self._participants = self.backend.talk.get_ongoing_calls(sip_id=self.sip_id)
364
365 return self._participants