]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/asterisk.py
asterisk: Use correct event loop to connect
[ipfire.org.git] / src / backend / asterisk.py
CommitLineData
d6c41da2
MT
1#!/usr/bin/python3
2
3import asyncio
4import datetime
5import logging
6import panoramisk
4235ba55 7import urllib.parse
d6c41da2 8
4235ba55 9from . import accounts
d6c41da2
MT
10from . import misc
11from .decorators import *
12
d6c41da2
MT
13class Asterisk(misc.Object):
14 def init(self):
15 self.__manager = None
16
57476094
MT
17 loop = asyncio.get_event_loop()
18
d6c41da2
MT
19 # Connect as soon as the event loop starts
20 loop.create_task(self.connect())
21
22 @property
23 def manager(self):
24 if not self.__manager:
25 raise RuntimeError("Asterisk is not connected")
26
27 return self.__manager
28
29 async def connect(self):
30 """
31 Connects to Asterisk
32 """
33 manager = panoramisk.Manager(
34 host = self.settings.get("asterisk-ami-host"),
35 username = self.settings.get("asterisk-ami-username"),
36 secret = self.settings.get("asterisk-ami-secret"),
37
38 on_connect=self._on_connect,
39 )
40
41 # Connect
42 await manager.connect()
43
44 return manager
45
46 def _on_connect(self, manager):
47 logging.debug("Connection to Asterisk established")
48
49 # Close any existing connections
50 if self.__manager:
51 self.__manager.close()
52
53 self.__manager = manager
54
214a68a0 55 async def _fetch(self, cls, action, filter=None, data={}):
ed17ffb1 56 objects = []
d6c41da2 57
214a68a0
MT
58 # Collect arguments
59 args = { "Action" : action } | data
60
ed17ffb1 61 # Run the action and parse all messages
214a68a0 62 for data in await self.manager.send_action(args):
d8fa7a5a 63 if not "Event" in data or not data.Event == cls.event:
d6c41da2
MT
64 continue
65
ed17ffb1
MT
66 # Create the object and append it to the list
67 o = cls(self.backend, data)
d6c41da2 68
ed17ffb1
MT
69 # Filter out anything unwanted
70 if filter and not o.matches(filter):
d6c41da2
MT
71 continue
72
ed17ffb1 73 objects.append(o)
d6c41da2 74
ed17ffb1 75 return objects
4235ba55 76
ed17ffb1
MT
77 async def get_sip_channels(self, filter=None):
78 return await self._fetch(Channel, "CoreShowChannels", filter=filter)
4235ba55 79
ed17ffb1
MT
80 async def get_registrations(self, filter=None):
81 return await self._fetch(Registration, "PJSIPShowContacts", filter=filter)
4235ba55 82
00465786 83 async def get_outbound_registrations(self):
ed17ffb1 84 return await self._fetch(OutboundRegistration, "PJSIPShowRegistrationsOutbound")
00465786 85
8e93325b 86 async def get_queues(self):
8e93325b 87 # Fetch all queues
ed17ffb1 88 queues = { q.name : q for q in await self._fetch(Queue, "QueueSummary") }
8e93325b
MT
89
90 # Fetch all members
ed17ffb1 91 for member in await self._fetch(QueueMember, "QueueStatus"):
8e93325b
MT
92 # Append to the matching queue
93 try:
94 queues[member.queue].members.append(member)
95 except KeyError:
96 pass
97
98 return queues.values()
99
214a68a0
MT
100 async def get_conferences(self):
101 conferences = await self._fetch(Conference, "ConfbridgeListRooms")
102
103 # Fetch everything else
104 async with asyncio.TaskGroup() as tasks:
105 for c in conferences:
106 tasks.create_task(c._fetch())
107
108 return conferences
109
d6c41da2
MT
110
111class Channel(misc.Object):
ed17ffb1
MT
112 event = "CoreShowChannel"
113
d6c41da2
MT
114 def init(self, data):
115 self.data = data
116
117 def __str__(self):
118 return self.connected_line
119
120 @property
121 def account_code(self):
122 return self.data.AccountCode
123
124 @property
125 def connected_line(self):
126 return self.data.ConnectedLineName or self.data.ConnectedLineNum
127
128 def matches(self, filter):
129 return filter in (
130 self.data.CallerIDNum,
131 )
132
133 @property
134 def duration(self):
135 h, m, s = self.data.Duration.split(":")
136
137 try:
138 h, m, s = int(h), int(m), int(s)
139 except TypeError:
140 return 0
141
142 return datetime.timedelta(hours=h, minutes=m, seconds=s)
143
144 def is_connected(self):
145 return self.data.ChannelStateDesc == "Up"
146
147 def is_ringing(self):
148 return self.data.ChannelStateDesc == "Ringing"
4235ba55
MT
149
150
151class Registration(misc.Object):
ed17ffb1
MT
152 event = "ContactList"
153
4235ba55
MT
154 def init(self, data):
155 self.data = data
156
157 def __lt__(self, other):
158 if isinstance(other, self.__class__):
159 if isinstance(self.user, accounts.Account):
160 if isinstance(other.user, accounts.Account):
161 return self.user < other.user
162 else:
163 return self.user.name < other.user
164 else:
165 if isinstance(other.user, accounts.Account):
166 return self.user < other.user.name
167 else:
168 return self.user < other.user
169
170 return NotImplemented
171
172 def __str__(self):
173 return self.user_agent
174
175 def matches(self, filter):
176 return self.data.Endpoint == filter
177
178 @lazy_property
179 def uri(self):
180 return urllib.parse.urlparse(self.data.Uri)
181
182 @lazy_property
183 def uri_params(self):
184 params = {}
185
186 for param in self.uri.params.split(";"):
187 key, _, value = param.partition("=")
188
189 params[key] = value
190
191 return params
192
193 @property
194 def transport(self):
195 return self.uri_params.get("transport")
196
197 @lazy_property
198 def user(self):
199 return self.backend.accounts.get_by_sip_id(self.data.Endpoint) or self.data.Endpoint
200
201 @property
202 def address(self):
203 # Remove the user
204 user, _, address = self.uri.path.partition("@")
205
206 # Remove the port
207 address, _, port = address.rpartition(":")
208
209 return address
210
211 @property
212 def user_agent(self):
213 return self.data.UserAgent.replace("_", " ")
214
215 @property
216 def roundtrip(self):
df3e4d31
MT
217 try:
218 return int(self.data.RoundtripUsec) / 1000
219 except ValueError:
220 pass
00465786
MT
221
222
223class OutboundRegistration(misc.Object):
ed17ffb1
MT
224 event = "OutboundRegistrationDetail"
225
00465786
MT
226 def init(self, data):
227 self.data = data
228
229 def __lt__(self, other):
230 if isinstance(other, self.__class__):
231 return self.server < other.server or self.username < other.username
232
233 return NotImplemented
234
235 @lazy_property
236 def uri(self):
237 return urllib.parse.urlparse(self.data.ClientUri)
238
239 @property
240 def server(self):
241 username, _, server = self.uri.path.partition("@")
242
243 return server
244
245 @property
246 def username(self):
247 username, _, server = self.uri.path.partition("@")
248
249 return username
250
251 @property
252 def status(self):
253 return self.data.Status
8e93325b
MT
254
255
256class Queue(misc.Object):
ed17ffb1
MT
257 event = "QueueSummary"
258
8e93325b
MT
259 def init(self, data):
260 self.data = data
261
262 self.members = []
263
264 def __str__(self):
265 return self.name
266
267 @property
268 def name(self):
269 return self.data.Queue
270
271 def is_available(self):
272 return self.data.Available == "1"
273
274 @property
275 def callers(self):
276 return int(self.data.Callers)
277
278
279class QueueMember(misc.Object):
ed17ffb1
MT
280 event = "QueueMember"
281
8e93325b
MT
282 def init(self, data):
283 self.data = data
284
285 def __str__(self):
286 return self.name
287
288 @property
289 def name(self):
290 return self.data.Name
291
292 @property
293 def queue(self):
294 return self.data.Queue
295
296 @property
297 def calls_taken(self):
298 return int(self.data.CallsTaken)
299
300 def is_in_call(self):
301 return self.data.InCall == "1"
302
303 @property
304 def last_call_at(self):
305 return datetime.datetime.fromtimestamp(int(self.data.LastCall))
306
307 @property
308 def logged_in_at(self):
309 return datetime.datetime.fromtimestamp(int(self.data.LoginTime))
310
311 # XXX status?
214a68a0
MT
312
313
314class Conference(misc.Object):
315 event = "ConfbridgeListRooms"
316
317 def init(self, data):
318 self.data = data
319
320 def __str__(self):
321 return self.name
322
323 @property
324 def name(self):
325 return self.data.Conference
326
327 async def _fetch(self):
328 # Fetch all members
329 self.members = await self.backend.asterisk._fetch(
330 ConferenceMember, "ConfbridgeList", data={ "Conference" : self.name, })
331
332
333class ConferenceMember(misc.Object):
334 event = "ConfbridgeList"
335
336 def init(self, data):
337 self.data = data
338
339 def __str__(self):
340 return self.name
341
342 def __lt__(self, other):
343 if isinstance(other, self.__class__):
344 return not self.duration < other.duration
345
346 return NotImplemented
347
348 @property
349 def name(self):
350 return "%s <%s>" % (self.data.CallerIDName, self.data.CallerIDNum)
351
352 def is_admin(self):
353 return self.data.Admin == "Yes"
354
355 def is_muted(self):
356 return self.data.Muted == "Yes"
357
358 @property
359 def duration(self):
360 return datetime.timedelta(seconds=int(self.data.AnsweredTime))