]>
Commit | Line | Data |
---|---|---|
66862195 MT |
1 | #!/usr/bin/python |
2 | ||
917434b8 MT |
3 | import ipaddress |
4 | import logging | |
77431b9c MT |
5 | import re |
6 | ||
5ac74b02 | 7 | from . import database |
66862195 | 8 | |
11347e46 | 9 | from .misc import Object |
13e89ccc MT |
10 | from .decorators import * |
11 | ||
12 | class Freeswitch(Object): | |
13 | @lazy_property | |
14 | def db(self): | |
15 | credentials = { | |
16 | "host" : self.settings.get("freeswitch_database_host"), | |
17 | "database" : self.settings.get("freeswitch_database_name", "freeswitch"), | |
18 | "user" : self.settings.get("freeswitch_database_user"), | |
19 | "password" : self.settings.get("freeswitch_database_password"), | |
20 | } | |
21 | ||
22 | return database.Connection(**credentials) | |
23 | ||
917434b8 MT |
24 | def get_sip_registrations(self, sip_uri): |
25 | logging.debug("Fetching SIP registrations for %s" % sip_uri) | |
26 | ||
27 | user, delim, domain = sip_uri.partition("@") | |
28 | ||
29 | res = self.db.query("SELECT * FROM sip_registrations \ | |
30 | WHERE sip_user = %s AND sip_host = %s AND expires >= EXTRACT(epoch FROM CURRENT_TIMESTAMP) \ | |
31 | ORDER BY contact", user, domain) | |
32 | ||
33 | for row in res: | |
34 | yield SIPRegistration(self, data=row) | |
35 | ||
36 | ||
37 | class SIPRegistration(object): | |
38 | def __init__(self, freeswitch, data): | |
39 | self.freeswitch = freeswitch | |
40 | self.data = data | |
41 | ||
42 | @lazy_property | |
43 | def protocol(self): | |
44 | m = re.match(r"Registered\(([A-Z]+)(\-NAT)?\)", self.data.status) | |
45 | ||
46 | if m: | |
47 | return m.group(1) | |
48 | ||
49 | @property | |
50 | def network_ip(self): | |
51 | return ipaddress.ip_address(self.data.network_ip) | |
52 | ||
53 | @property | |
54 | def network_port(self): | |
55 | return self.data.network_port | |
56 | ||
57 | @property | |
58 | def user_agent(self): | |
59 | return self.data.user_agent | |
60 | ||
61 | def is_reachable(self): | |
62 | return self.data.ping_status == "Reachable" | |
63 | ||
64 | @lazy_property | |
65 | def latency(self): | |
66 | if self.is_reachable(): | |
67 | return self.data.ping_time / 1000.0 | |
68 | ||
69 | ||
66862195 MT |
70 | |
71 | class Talk(Object): | |
13e89ccc MT |
72 | def init(self): |
73 | # Connect to FreeSWITCH | |
74 | self.freeswitch = Freeswitch(self.backend) | |
75 | ||
66862195 MT |
76 | def get_phonebook(self, account=None): |
77 | accounts = [] | |
78 | for a in self.accounts.list(): | |
79 | if account and a == account: | |
80 | continue | |
81 | ||
82 | if not a.is_talk_enabled(): | |
83 | continue | |
84 | ||
85 | accounts.append(a) | |
86 | ||
87 | return accounts | |
88 | ||
89 | def user_is_online(self, sip_id): | |
77431b9c | 90 | res = self.db.get("SELECT 1 FROM location WHERE username = %s \ |
66862195 MT |
91 | AND expires >= NOW() LIMIT 1", sip_id) |
92 | ||
93 | if res: | |
94 | return True | |
95 | ||
96 | return False | |
97 | ||
77431b9c MT |
98 | def get_lines(self, account=None): |
99 | accounts_cache = {} | |
66862195 | 100 | |
77431b9c | 101 | if account and not account.is_talk_enabled(): |
66862195 MT |
102 | return [] |
103 | ||
77431b9c MT |
104 | if account: |
105 | res = self.db.query("SELECT contact AS location, expires, user_agent, socket \ | |
106 | FROM location WHERE domain = %s AND username = %s ORDER BY expires DESC", | |
107 | "ipfire.org", account.sip_id) | |
108 | else: | |
109 | res = self.db.query("SELECT username, domain, contact AS location, \ | |
110 | expires, user_agent, socket FROM location ORDER BY username, expires DESC") | |
66862195 MT |
111 | |
112 | result = [] | |
113 | for row in res: | |
77431b9c MT |
114 | if account: |
115 | row.account = account | |
116 | elif row.username: | |
117 | try: | |
118 | row.account = accounts_cache[row.username] | |
119 | except KeyError: | |
120 | row.account = accounts_cache[row.username] = \ | |
121 | self.accounts.get_by_sip_id(row.username) | |
122 | else: | |
123 | row.account = None | |
66862195 | 124 | |
77431b9c MT |
125 | # Check if TLS is used |
126 | row.tls_enabled = row.socket.startswith("tls:") | |
66862195 MT |
127 | |
128 | # Cut off everything after ; | |
129 | row.location, sep, rest = row.location.partition(";") | |
130 | ||
131 | result.append(row) | |
132 | ||
133 | return result | |
134 | ||
77431b9c MT |
135 | def _process_sip_uri(self, sip_uri): |
136 | sip_uri, delimiter, rest = sip_uri.partition(";") | |
137 | ||
138 | m = re.match(r"^sip:([0-9]{4})@ipfire\.org", sip_uri) | |
139 | if m: | |
140 | return m.group(1) | |
141 | ||
142 | # Remove trailing default port | |
143 | if sip_uri.endswith(":5060"): | |
144 | sip_uri = sip_uri.replace(":5060", "") | |
145 | ||
146 | return sip_uri | |
147 | ||
148 | def _process_cdr(self, entries, replace_sip_uris=False): | |
66862195 MT |
149 | accounts_cache = {} |
150 | ||
151 | result = [] | |
152 | for row in entries: | |
77431b9c MT |
153 | if replace_sip_uris: |
154 | row["caller"] = self._process_sip_uri(row.caller) | |
155 | ||
66862195 | 156 | try: |
77431b9c | 157 | row["caller_account"] = accounts_cache[row.caller] |
66862195 | 158 | except KeyError: |
77431b9c | 159 | row["caller_account"] = accounts_cache[row.caller] = \ |
66862195 MT |
160 | self.accounts.get_by_sip_id(row.caller) |
161 | ||
77431b9c MT |
162 | if replace_sip_uris: |
163 | row["called"] = self._process_sip_uri(row.called) | |
164 | ||
66862195 | 165 | try: |
77431b9c | 166 | row["called_account"] = accounts_cache[row.called] |
66862195 | 167 | except KeyError: |
77431b9c | 168 | row["called_account"] = accounts_cache[row.called] = \ |
66862195 MT |
169 | self.accounts.get_by_sip_id(row.called) |
170 | ||
171 | if not row.called_account: | |
77431b9c | 172 | row["called_conference"] = self.get_conference(row.called) |
66862195 | 173 | else: |
77431b9c MT |
174 | row["called_conference"] = None |
175 | ||
176 | try: | |
177 | row["time"] = datetime.datetime.fromutctimestamp(row.time) | |
178 | except: | |
179 | pass | |
66862195 MT |
180 | |
181 | result.append(row) | |
182 | ||
183 | return result | |
184 | ||
185 | def get_call_log(self, account, limit=25): | |
77431b9c MT |
186 | if account: |
187 | res = self.db.query("SELECT * FROM cdr WHERE (caller = %s OR called = %s) \ | |
188 | ORDER BY time DESC LIMIT %s", account.sip_id, account.sip_id, limit) | |
189 | else: | |
190 | res = self.db.query("SELECT * FROM cdr ORDER BY time DESC LIMIT %s", limit) | |
66862195 MT |
191 | |
192 | return self._process_cdr(res) | |
193 | ||
a69e87a1 MT |
194 | def get_channels(self, account=None): |
195 | channels = [] | |
196 | for c in a.list_channels(): | |
197 | if account and not account.sip_id in (c.caller, c.callee): | |
198 | continue | |
5ac74b02 | 199 | |
a69e87a1 | 200 | channels.append(c) |
5ac74b02 | 201 | |
a69e87a1 | 202 | return sorted(channels) |
5ac74b02 | 203 | |
a69e87a1 MT |
204 | def get_channel(self, channel_id, account=None): |
205 | channels = self.get_channels(account=account) | |
40818cf2 | 206 | |
a69e87a1 MT |
207 | for channel in channels: |
208 | if channel.id == channel_id: | |
209 | return channel | |
40818cf2 | 210 | |
66862195 MT |
211 | def get_ongoing_calls(self, account=None, sip_id=None): |
212 | if account and sip_id is None: | |
213 | sip_id = account.sip_id | |
214 | ||
77431b9c MT |
215 | # If the given account does not have a SIP ID, |
216 | # we not need to search for any active sessions | |
217 | if sip_id is None: | |
218 | return [] | |
219 | ||
66862195 | 220 | if sip_id: |
77431b9c MT |
221 | sip_id = "sip:%s@%%" % sip_id |
222 | ||
223 | res = self.db.query("SELECT from_uri AS caller, \ | |
224 | to_uri AS called, start_time AS time FROM dialog \ | |
225 | WHERE from_uri LIKE %s OR to_uri LIKE %s ORDER BY time", | |
226 | sip_id, sip_id) | |
66862195 | 227 | else: |
77431b9c MT |
228 | res = self.db.query("SELECT from_uri AS caller, to_uri AS called, \ |
229 | start_time AS time FROM dialog ORDER BY time") | |
66862195 | 230 | |
77431b9c | 231 | return self._process_cdr(res, replace_sip_uris=True) |
66862195 | 232 | |
66862195 MT |
233 | # Favourites |
234 | ||
235 | def get_favourite_contacts(self, account, limit=6): | |
77431b9c MT |
236 | res = self.db.query("SELECT src_user AS caller, dst_ouser AS called, \ |
237 | COUNT(*) AS count FROM acc WHERE method = %s AND src_user = %s AND \ | |
238 | dst_ouser BETWEEN %s AND %s AND time >= NOW() - INTERVAL '1 year' \ | |
239 | GROUP BY caller, called ORDER BY count DESC LIMIT %s", | |
240 | "INVITE", account.sip_id, "1000", "1999", limit) | |
66862195 MT |
241 | |
242 | return self._process_cdr(res) | |
243 | ||
244 | # Conferences | |
245 | ||
246 | @property | |
247 | def conferences(self): | |
248 | conferences = [] | |
249 | ||
250 | for no in range(1, 10): | |
251 | conference = Conference(self.backend, no) | |
252 | conferences.append(conference) | |
253 | ||
254 | return conferences | |
255 | ||
256 | def get_conference(self, id): | |
257 | for c in self.conferences: | |
258 | if not c.sip_id == id: | |
259 | continue | |
260 | ||
261 | return c | |
262 | ||
263 | ||
264 | class Conference(Object): | |
265 | def __init__(self, backend, no): | |
266 | Object.__init__(self, backend) | |
267 | self.no = no | |
268 | ||
269 | def __cmp__(self, other): | |
270 | return cmp(self.no, other.no) | |
271 | ||
272 | @property | |
273 | def name(self): | |
274 | return "IPFire Conference Room %s" % self.no | |
275 | ||
276 | @property | |
277 | def sip_id(self): | |
278 | return "%s" % (9000 + self.no) | |
279 | ||
280 | @property | |
281 | def sip_url(self): | |
282 | return "%s@ipfire.org" % self.sip_id | |
283 | ||
284 | @property | |
285 | def participants(self): | |
286 | if not hasattr(self, "_participants"): | |
287 | self._participants = self.backend.talk.get_ongoing_calls(sip_id=self.sip_id) | |
288 | ||
289 | return self._participants |