|
1
|
#!/usr/bin/env python
|
|
2
|
# Copyright (C) 2008-2010 AG Projects. See LICENSE for details.
|
|
3
|
#
|
|
4
|
|
|
5
|
import datetime
|
|
6
|
import os
|
|
7
|
import random
|
|
8
|
import select
|
|
9
|
import sys
|
|
10
|
import termios
|
|
11
|
import urllib
|
|
12
|
|
|
13
|
from collections import deque
|
|
14
|
from optparse import OptionParser
|
|
15
|
from threading import Thread
|
|
16
|
from time import time
|
|
17
|
|
|
18
|
from application import log
|
|
19
|
from application.notification import IObserver, NotificationCenter, NotificationData
|
|
20
|
from application.python.queue import EventQueue
|
|
21
|
from eventlet.twistedutil import join_reactor
|
|
22
|
from twisted.internet import reactor
|
|
23
|
from twisted.internet.error import ReactorNotRunning
|
|
24
|
from zope.interface import implements
|
|
25
|
|
|
26
|
from sipsimple.account import Account, AccountManager, BonjourAccount
|
|
27
|
from sipsimple.application import SIPApplication
|
|
28
|
from sipsimple.lookup import DNSLookup
|
|
29
|
from sipsimple.configuration import ConfigurationError, ConfigurationManager
|
|
30
|
from sipsimple.configuration.settings import SIPSimpleSettings
|
|
31
|
from sipsimple.core import ContactHeader, Engine, FromHeader, RouteHeader, SIPCoreError, SIPURI, Subscription, ToHeader, Route
|
|
32
|
from sipsimple.payloads import ParserError
|
|
33
|
from sipsimple.payloads import rpid # needed to register RPID extensions
|
|
34
|
from sipsimple.payloads.pidf import Device, Person, Service, PIDF, PIDFDocument
|
|
35
|
from sipsimple.storage import FileStorage
|
|
36
|
from sipsimple.threading import run_in_twisted_thread
|
|
37
|
|
|
38
|
from sipclient.configuration import config_directory
|
|
39
|
from sipclient.configuration.account import AccountExtension
|
|
40
|
from sipclient.configuration.settings import SIPSimpleSettingsExtension
|
|
41
|
from sipclient.log import Logger
|
|
42
|
|
|
43
|
|
|
44
|
class InputThread(Thread):
|
|
45
|
def __init__(self, application):
|
|
46
|
Thread.__init__(self)
|
|
47
|
self.application = application
|
|
48
|
self.daemon = True
|
|
49
|
self._old_terminal_settings = None
|
|
50
|
|
|
51
|
def run(self):
|
|
52
|
notification_center = NotificationCenter()
|
|
53
|
while True:
|
|
54
|
for char in self._getchars():
|
|
55
|
if char == "\x04":
|
|
56
|
self.application.stop()
|
|
57
|
return
|
|
58
|
else:
|
|
59
|
notification_center.post_notification('SAInputWasReceived', sender=self, data=NotificationData(input=char))
|
|
60
|
|
|
61
|
def stop(self):
|
|
62
|
self._termios_restore()
|
|
63
|
|
|
64
|
def _termios_restore(self):
|
|
65
|
if self._old_terminal_settings is not None:
|
|
66
|
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, self._old_terminal_settings)
|
|
67
|
|
|
68
|
def _getchars(self):
|
|
69
|
fd = sys.stdin.fileno()
|
|
70
|
if os.isatty(fd):
|
|
71
|
self._old_terminal_settings = termios.tcgetattr(fd)
|
|
72
|
new = termios.tcgetattr(fd)
|
|
73
|
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO
|
|
74
|
new[6][termios.VMIN] = '\000'
|
|
75
|
try:
|
|
76
|
termios.tcsetattr(fd, termios.TCSADRAIN, new)
|
|
77
|
if select.select([fd], [], [], None)[0]:
|
|
78
|
return sys.stdin.read(4192)
|
|
79
|
finally:
|
|
80
|
self._termios_restore()
|
|
81
|
else:
|
|
82
|
return os.read(fd, 4192)
|
|
83
|
|
|
84
|
|
|
85
|
class SubscriptionApplication(object):
|
|
86
|
implements(IObserver)
|
|
87
|
|
|
88
|
def __init__(self, account_name, target, trace_sip, trace_pjsip, trace_notifications):
|
|
89
|
self.account_name = account_name
|
|
90
|
self.target = target
|
|
91
|
self.input = InputThread(self)
|
|
92
|
self.output = EventQueue(self._write)
|
|
93
|
self.logger = Logger(sip_to_stdout=trace_sip, pjsip_to_stdout=trace_pjsip, notifications_to_stdout=trace_notifications)
|
|
94
|
self.success = False
|
|
95
|
self.account = None
|
|
96
|
self.subscription = None
|
|
97
|
self.stopping = False
|
|
98
|
|
|
99
|
self._subscription_routes = None
|
|
100
|
self._subscription_timeout = 0.0
|
|
101
|
self._subscription_wait = 0.5
|
|
102
|
|
|
103
|
account_manager = AccountManager()
|
|
104
|
engine = Engine()
|
|
105
|
notification_center = NotificationCenter()
|
|
106
|
notification_center.add_observer(self, sender=account_manager)
|
|
107
|
notification_center.add_observer(self, sender=engine)
|
|
108
|
notification_center.add_observer(self, sender=self.input)
|
|
109
|
|
|
110
|
log.level.current = log.level.WARNING
|
|
111
|
|
|
112
|
def _write(self, message):
|
|
113
|
if isinstance(message, unicode):
|
|
114
|
message = message.encode(sys.getfilesystemencoding())
|
|
115
|
sys.stdout.write(message+'\n')
|
|
116
|
|
|
117
|
def run(self):
|
|
118
|
account_manager = AccountManager()
|
|
119
|
configuration = ConfigurationManager()
|
|
120
|
engine = Engine()
|
|
121
|
|
|
122
|
# start output thread
|
|
123
|
self.output.start()
|
|
124
|
|
|
125
|
# startup configuration
|
|
126
|
Account.register_extension(AccountExtension)
|
|
127
|
BonjourAccount.register_extension(AccountExtension)
|
|
128
|
SIPSimpleSettings.register_extension(SIPSimpleSettingsExtension)
|
|
129
|
SIPApplication.storage = FileStorage(config_directory)
|
|
130
|
try:
|
|
131
|
configuration.start()
|
|
132
|
except ConfigurationError, e:
|
|
133
|
raise RuntimeError("failed to load sipclient's configuration: %s\nIf an old configuration file is in place, delete it or move it and recreate the configuration using the sip_settings script." % str(e))
|
|
134
|
account_manager.load()
|
|
135
|
if self.account_name is None:
|
|
136
|
self.account = account_manager.default_account
|
|
137
|
else:
|
|
138
|
possible_accounts = [account for account in account_manager.iter_accounts() if self.account_name in account.id and account.enabled]
|
|
139
|
if len(possible_accounts) > 1:
|
|
140
|
raise RuntimeError("More than one account exists which matches %s: %s" % (self.account_name, ", ".join(sorted(account.id for account in possible_accounts))))
|
|
141
|
if len(possible_accounts) == 0:
|
|
142
|
raise RuntimeError("No enabled account that matches %s was found. Available and enabled accounts: %s" % (self.account_name, ", ".join(sorted(account.id for account in account_manager.get_accounts() if account.enabled))))
|
|
143
|
self.account = possible_accounts[0]
|
|
144
|
if self.account is None:
|
|
145
|
raise RuntimeError("Unknown account %s. Available accounts: %s" % (self.account_name, ', '.join(account.id for account in account_manager.iter_accounts())))
|
|
146
|
elif self.account == BonjourAccount():
|
|
147
|
raise RuntimeError("Cannot use bonjour account for presence subscription")
|
|
148
|
elif not self.account.presence.enabled:
|
|
149
|
raise RuntimeError("Presence is not enabled for account %s" % self.account.id)
|
|
150
|
for account in account_manager.iter_accounts():
|
|
151
|
if account == self.account:
|
|
152
|
account.sip.register = False
|
|
153
|
else:
|
|
154
|
account.enabled = False
|
|
155
|
self.output.put('Using account %s' % self.account.id)
|
|
156
|
settings = SIPSimpleSettings()
|
|
157
|
|
|
158
|
# start logging
|
|
159
|
self.logger.start()
|
|
160
|
|
|
161
|
# start the engine
|
|
162
|
engine.start(
|
|
163
|
auto_sound=False,
|
|
164
|
events={'presence': [PIDFDocument.content_type]},
|
|
165
|
udp_port=settings.sip.udp_port if "udp" in settings.sip.transport_list else None,
|
|
166
|
tcp_port=settings.sip.tcp_port if "tcp" in settings.sip.transport_list else None,
|
|
167
|
tls_port=settings.sip.tls_port if "tls" in settings.sip.transport_list else None,
|
|
168
|
tls_protocol=settings.tls.protocol,
|
|
169
|
tls_verify_server=self.account.tls.verify_server,
|
|
170
|
tls_ca_file=os.path.expanduser(settings.tls.ca_list) if settings.tls.ca_list else None,
|
|
171
|
tls_cert_file=os.path.expanduser(self.account.tls.certificate) if self.account.tls.certificate else None,
|
|
172
|
tls_privkey_file=os.path.expanduser(self.account.tls.certificate) if self.account.tls.certificate else None,
|
|
173
|
tls_timeout=settings.tls.timeout,
|
|
174
|
ec_tail_length=settings.audio.tail_length,
|
|
175
|
user_agent=settings.user_agent,
|
|
176
|
sample_rate=settings.audio.sample_rate,
|
|
177
|
rtp_port_range=(settings.rtp.port_range.start, settings.rtp.port_range.end),
|
|
178
|
trace_sip=settings.logs.trace_sip or self.logger.sip_to_stdout,
|
|
179
|
log_level=settings.logs.pjsip_level if (settings.logs.trace_pjsip or self.logger.pjsip_to_stdout) else 0
|
|
180
|
)
|
|
181
|
|
|
182
|
if self.target is None:
|
|
183
|
self.target = ToHeader(SIPURI(user=self.account.id.username, host=self.account.id.domain))
|
|
184
|
else:
|
|
185
|
if '@' not in self.target:
|
|
186
|
self.target = '%s@%s' % (self.target, self.account.id.domain)
|
|
187
|
if not self.target.startswith('sip:') and not self.target.startswith('sips:'):
|
|
188
|
self.target = 'sip:' + self.target
|
|
189
|
try:
|
|
190
|
self.target = ToHeader(SIPURI.parse(self.target))
|
|
191
|
except SIPCoreError:
|
|
192
|
self.output.put('Illegal SIP URI: %s' % self.target)
|
|
193
|
engine.stop()
|
|
194
|
return 1
|
|
195
|
self.output.put('Subscribing to %s for the presence event' % self.target.uri)
|
|
196
|
|
|
197
|
# start the input thread
|
|
198
|
self.input.start()
|
|
199
|
|
|
200
|
reactor.callLater(0, self._subscribe)
|
|
201
|
|
|
202
|
# start twisted
|
|
203
|
try:
|
|
204
|
reactor.run()
|
|
205
|
finally:
|
|
206
|
self.input.stop()
|
|
207
|
|
|
208
|
# stop the output
|
|
209
|
self.output.stop()
|
|
210
|
self.output.join()
|
|
211
|
|
|
212
|
self.logger.stop()
|
|
213
|
|
|
214
|
return 0 if self.success else 1
|
|
215
|
|
|
216
|
def stop(self):
|
|
217
|
self.stopping = True
|
|
218
|
if self.subscription is not None and self.subscription.state.lower() in ('accepted', 'pending', 'active'):
|
|
219
|
self.subscription.end(timeout=1)
|
|
220
|
else:
|
|
221
|
engine = Engine()
|
|
222
|
engine.stop()
|
|
223
|
|
|
224
|
def print_help(self):
|
|
225
|
message = 'Available control keys:\n'
|
|
226
|
message += ' t: toggle SIP trace on the console\n'
|
|
227
|
message += ' j: toggle PJSIP trace on the console\n'
|
|
228
|
message += ' n: toggle notifications trace on the console\n'
|
|
229
|
message += ' Ctrl-d: quit the program\n'
|
|
230
|
message += ' ?: display this help message\n'
|
|
231
|
self.output.put('\n'+message)
|
|
232
|
|
|
233
|
def handle_notification(self, notification):
|
|
234
|
handler = getattr(self, '_NH_%s' % notification.name, None)
|
|
235
|
if handler is not None:
|
|
236
|
handler(notification)
|
|
237
|
|
|
238
|
def _NH_SIPSubscriptionDidStart(self, notification):
|
|
239
|
route = Route(notification.sender.route_header.uri.host, notification.sender.route_header.uri.port, notification.sender.route_header.uri.parameters.get('transport', 'udp'))
|
|
240
|
self._subscription_routes = None
|
|
241
|
self._subscription_wait = 0.5
|
|
242
|
self.output.put('Subscription succeeded at %s:%d;transport=%s' % (route.address, route.port, route.transport))
|
|
243
|
self.success = True
|
|
244
|
|
|
245
|
def _NH_SIPSubscriptionChangedState(self, notification):
|
|
246
|
route = Route(notification.sender.route_header.uri.host, notification.sender.route_header.uri.port, notification.sender.route_header.uri.parameters.get('transport', 'udp'))
|
|
247
|
if notification.data.state.lower() == "pending":
|
|
248
|
self.output.put('Subscription pending at %s:%d;transport=%s' % (route.address, route.port, route.transport))
|
|
249
|
elif notification.data.state.lower() == "active":
|
|
250
|
self.output.put('Subscription active at %s:%d;transport=%s' % (route.address, route.port, route.transport))
|
|
251
|
|
|
252
|
def _NH_SIPSubscriptionDidEnd(self, notification):
|
|
253
|
notification_center = NotificationCenter()
|
|
254
|
notification_center.remove_observer(self, sender=notification.sender)
|
|
255
|
self.subscription = None
|
|
256
|
route = Route(notification.sender.route_header.uri.host, notification.sender.route_header.uri.port, notification.sender.route_header.uri.parameters.get('transport', 'udp'))
|
|
257
|
self.output.put('Unsubscribed from %s:%d;transport=%s' % (route.address, route.port, route.transport))
|
|
258
|
self.stop()
|
|
259
|
|
|
260
|
def _NH_SIPSubscriptionDidFail(self, notification):
|
|
261
|
notification_center = NotificationCenter()
|
|
262
|
notification_center.remove_observer(self, sender=notification.sender)
|
|
263
|
self.subscription = None
|
|
264
|
route = Route(notification.sender.route_header.uri.host, notification.sender.route_header.uri.port, notification.sender.route_header.uri.parameters.get('transport', 'udp'))
|
|
265
|
if notification.data.code:
|
|
266
|
status = ': %d %s' % (notification.data.code, notification.data.reason)
|
|
267
|
else:
|
|
268
|
status = ': %s' % notification.data.reason
|
|
269
|
self.output.put('Subscription failed at %s:%d;transport=%s%s' % (route.address, route.port, route.transport, status))
|
|
270
|
if self.stopping or notification.data.code in (401, 403, 407) or self.success:
|
|
271
|
self.success = False
|
|
272
|
self.stop()
|
|
273
|
else:
|
|
274
|
if not self._subscription_routes or time() > self._subscription_timeout:
|
|
275
|
self._subscription_wait = min(self._subscription_wait*2, 30)
|
|
276
|
timeout = random.uniform(self._subscription_wait, 2*self._subscription_wait)
|
|
277
|
reactor.callFromThread(reactor.callLater, timeout, self._subscribe)
|
|
278
|
else:
|
|
279
|
route = self._subscription_routes.popleft()
|
|
280
|
route_header = RouteHeader(route.get_uri())
|
|
281
|
self.subscription = Subscription(self.target.uri,
|
|
282
|
FromHeader(self.account.uri, self.account.display_name),
|
|
283
|
self.target,
|
|
284
|
ContactHeader(self.account.contact[route]),
|
|
285
|
"presence",
|
|
286
|
route_header,
|
|
287
|
credentials=self.account.credentials,
|
|
288
|
refresh=self.account.sip.subscribe_interval)
|
|
289
|
notification_center.add_observer(self, sender=self.subscription)
|
|
290
|
self.subscription.subscribe(timeout=5)
|
|
291
|
|
|
292
|
def _NH_SIPSubscriptionGotNotify(self, notification):
|
|
293
|
if notification.data.content_type == PIDFDocument.content_type:
|
|
294
|
self.output.put('Received NOTIFY:')
|
|
295
|
try:
|
|
296
|
pidf = PIDF.parse(notification.data.body)
|
|
297
|
except ParserError, e:
|
|
298
|
self.output.put('Got illegal PIDF document: %s\n%s' % (str(e), notification.data.body))
|
|
299
|
else:
|
|
300
|
self._display_pidf(pidf)
|
|
301
|
self.print_help()
|
|
302
|
|
|
303
|
def _NH_DNSLookupDidSucceed(self, notification):
|
|
304
|
# create subscription and register to get notifications from it
|
|
305
|
self._subscription_routes = deque(notification.data.result)
|
|
306
|
route = self._subscription_routes.popleft()
|
|
307
|
route_header = RouteHeader(route.uri)
|
|
308
|
self.subscription = Subscription(self.target.uri,
|
|
309
|
FromHeader(self.account.uri, self.account.display_name),
|
|
310
|
self.target,
|
|
311
|
ContactHeader(self.account.contact[route]),
|
|
312
|
"presence",
|
|
313
|
route_header,
|
|
314
|
credentials=self.account.credentials,
|
|
315
|
refresh=self.account.sip.subscribe_interval)
|
|
316
|
notification_center = NotificationCenter()
|
|
317
|
notification_center.add_observer(self, sender=self.subscription)
|
|
318
|
self.subscription.subscribe(timeout=5)
|
|
319
|
|
|
320
|
def _NH_DNSLookupDidFail(self, notification):
|
|
321
|
self.output.put('DNS lookup failed: %s' % notification.data.error)
|
|
322
|
timeout = random.uniform(1.0, 2.0)
|
|
323
|
reactor.callLater(timeout, self._subscribe)
|
|
324
|
|
|
325
|
def _NH_SAInputWasReceived(self, notification):
|
|
326
|
engine = Engine()
|
|
327
|
settings = SIPSimpleSettings()
|
|
328
|
key = notification.data.input
|
|
329
|
if key == 't':
|
|
330
|
self.logger.sip_to_stdout = not self.logger.sip_to_stdout
|
|
331
|
engine.trace_sip = self.logger.sip_to_stdout or settings.logs.trace_sip
|
|
332
|
self.output.put('SIP tracing to console is now %s.' % ('activated' if self.logger.sip_to_stdout else 'deactivated'))
|
|
333
|
elif key == 'j':
|
|
334
|
self.logger.pjsip_to_stdout = not self.logger.pjsip_to_stdout
|
|
335
|
engine.log_level = settings.logs.pjsip_level if (self.logger.pjsip_to_stdout or settings.logs.trace_pjsip) else 0
|
|
336
|
self.output.put('PJSIP tracing to console is now %s.' % ('activated' if self.logger.pjsip_to_stdout else 'deactivated'))
|
|
337
|
elif key == 'n':
|
|
338
|
self.logger.notifications_to_stdout = not self.logger.notifications_to_stdout
|
|
339
|
self.output.put('Notification tracing to console is now %s.' % ('activated' if self.logger.notifications_to_stdout else 'deactivated'))
|
|
340
|
elif key == '?':
|
|
341
|
self.print_help()
|
|
342
|
|
|
343
|
@run_in_twisted_thread
|
|
344
|
def _NH_SIPEngineDidEnd(self, notification):
|
|
345
|
self._stop_reactor()
|
|
346
|
|
|
347
|
@run_in_twisted_thread
|
|
348
|
def _NH_SIPEngineDidFail(self, notification):
|
|
349
|
self.output.put('Engine failed.')
|
|
350
|
self._stop_reactor()
|
|
351
|
|
|
352
|
def _NH_SIPEngineGotException(self, notification):
|
|
353
|
self.output.put('An exception occured within the SIP core:\n'+notification.data.traceback)
|
|
354
|
|
|
355
|
def _stop_reactor(self):
|
|
356
|
try:
|
|
357
|
reactor.stop()
|
|
358
|
except ReactorNotRunning:
|
|
359
|
pass
|
|
360
|
|
|
361
|
def _subscribe(self):
|
|
362
|
settings = SIPSimpleSettings()
|
|
363
|
|
|
364
|
self._subscription_timeout = time()+30
|
|
365
|
|
|
366
|
lookup = DNSLookup()
|
|
367
|
notification_center = NotificationCenter()
|
|
368
|
notification_center.add_observer(self, sender=lookup)
|
|
369
|
if self.account.sip.outbound_proxy is not None:
|
|
370
|
uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={'transport': self.account.sip.outbound_proxy.transport})
|
|
371
|
elif self.account.sip.always_use_my_proxy:
|
|
372
|
uri = SIPURI(host=self.account.id.domain)
|
|
373
|
else:
|
|
374
|
uri = self.target.uri
|
|
375
|
lookup.lookup_sip_proxy(uri, settings.sip.transport_list)
|
|
376
|
|
|
377
|
def _format_note(self, note):
|
|
378
|
text = "Note"
|
|
379
|
if note.lang is not None:
|
|
380
|
text += "(%s)" % note.lang
|
|
381
|
text += ": %s" % note
|
|
382
|
return text
|
|
383
|
|
|
384
|
def _format_person(self, person, pidf):
|
|
385
|
buf = []
|
|
386
|
# display class
|
|
387
|
if person.rpid_class is not None:
|
|
388
|
buf.append(" Class: %s" % person.rpid_class)
|
|
389
|
# display timestamp
|
|
390
|
if person.timestamp is not None:
|
|
391
|
buf.append(" Timestamp: %s" % person.timestamp)
|
|
392
|
# display notes
|
|
393
|
if person.notes:
|
|
394
|
for note in person.notes:
|
|
395
|
buf.append(" %s" % self._format_note(note))
|
|
396
|
elif pidf.notes:
|
|
397
|
for note in pidf.notes:
|
|
398
|
buf.append(" %s" % self._format_note(note))
|
|
399
|
# display activities
|
|
400
|
if person.activities is not None:
|
|
401
|
activities = list(person.activities)
|
|
402
|
if len(activities) > 0:
|
|
403
|
text = " Activities"
|
|
404
|
if person.activities.since is not None or person.activities.until is not None:
|
|
405
|
text += " valid"
|
|
406
|
if person.activities.since is not None:
|
|
407
|
text += " from %s" % person.activities.since
|
|
408
|
if person.activities.until is not None:
|
|
409
|
text += " until %s" % person.activities.until
|
|
410
|
text += ": %s" % ', '.join(str(activity) for activity in activities)
|
|
411
|
buf.append(text)
|
|
412
|
if len(person.activities.notes) > 0:
|
|
413
|
for note in person.activities.notes:
|
|
414
|
buf.append(" %s" % self._format_note(note))
|
|
415
|
elif len(person.activities.notes) > 0:
|
|
416
|
buf.append(" Activities")
|
|
417
|
for note in person.activities.notes:
|
|
418
|
buf.append(" %s" % self._format_note(note))
|
|
419
|
# display mood
|
|
420
|
if person.mood is not None:
|
|
421
|
moods = list(person.mood)
|
|
422
|
if len(moods) > 0:
|
|
423
|
text = " Mood"
|
|
424
|
if person.mood.since is not None or person.mood.until is not None:
|
|
425
|
text += " valid"
|
|
426
|
if person.mood.since is not None:
|
|
427
|
text += " from %s" % person.mood.since
|
|
428
|
if person.mood.until is not None:
|
|
429
|
text += " until %s" % person.mood.until
|
|
430
|
text += ": %s" % ', '.join(str(mood) for mood in moods)
|
|
431
|
buf.append(text)
|
|
432
|
if len(person.mood.notes) > 0:
|
|
433
|
for note in person.mood.notes:
|
|
434
|
buf.append(" %s" % self._format_note(note))
|
|
435
|
# display place is
|
|
436
|
if person.place_is is not None:
|
|
437
|
place_info = ', '.join('%s %s' % (key.capitalize(), getattr(person.place_is, key).value) for key in ('audio', 'video', 'text') if getattr(person.place_is, key) and getattr(person.place_is, key).value)
|
|
438
|
if place_info != '':
|
|
439
|
buf.append(" Place information: " + place_info)
|
|
440
|
# display privacy
|
|
441
|
if person.privacy is not None:
|
|
442
|
text = " Private conversation possible with: "
|
|
443
|
private = []
|
|
444
|
if person.privacy.audio:
|
|
445
|
private.append("Audio")
|
|
446
|
if person.privacy.video:
|
|
447
|
private.append("Video")
|
|
448
|
if person.privacy.text:
|
|
449
|
private.append("Text")
|
|
450
|
if len(private) > 0:
|
|
451
|
text += ", ".join(private)
|
|
452
|
else:
|
|
453
|
text += "None"
|
|
454
|
buf.append(text)
|
|
455
|
# display sphere
|
|
456
|
if person.sphere is not None:
|
|
457
|
timeinfo = []
|
|
458
|
if person.sphere.since is not None:
|
|
459
|
timeinfo.append('from %s' % str(person.sphere.since))
|
|
460
|
if person.sphere.until is not None:
|
|
461
|
timeinfo.append('until %s' % str(person.sphere.until))
|
|
462
|
if len(timeinfo) != 0:
|
|
463
|
timeinfo = ' (' + ', '.join(timeinfo) + ')'
|
|
464
|
else:
|
|
465
|
timeinfo = ''
|
|
466
|
buf.append(" Current sphere%s: %s" % (timeinfo, person.sphere.value))
|
|
467
|
# display status icon
|
|
468
|
if person.status_icon is not None:
|
|
469
|
buf.append(" Status icon: %s" % person.status_icon)
|
|
470
|
# display time and time offset
|
|
471
|
if person.time_offset is not None:
|
|
472
|
ctime = datetime.datetime.utcnow() + datetime.timedelta(minutes=int(person.time_offset))
|
|
473
|
time_offset = int(person.time_offset)/60.0
|
|
474
|
if time_offset == int(time_offset):
|
|
475
|
offset_info = '(UTC+%d%s)' % (time_offset, (person.time_offset.description is not None and (' (%s)' % person.time_offset.description) or ''))
|
|
476
|
else:
|
|
477
|
offset_info = '(UTC+%.1f%s)' % (time_offset, (person.time_offset.description is not None and (' (%s)' % person.time_offset.description) or ''))
|
|
478
|
buf.append(" Current user time: %s %s" % (ctime.strftime("%H:%M"), offset_info))
|
|
479
|
# display user input
|
|
480
|
if person.user_input is not None:
|
|
481
|
buf.append(" User is %s" % person.user_input)
|
|
482
|
if person.user_input.last_input:
|
|
483
|
buf.append(" Last input at: %s" % person.user_input.last_input)
|
|
484
|
if person.user_input.idle_threshold:
|
|
485
|
buf.append(" Idle threshold: %s seconds" % person.user_input.idle_threshold)
|
|
486
|
return buf
|
|
487
|
|
|
488
|
def _format_service(self, service, pidf):
|
|
489
|
buf = []
|
|
490
|
# display class
|
|
491
|
if service.rpid_class is not None:
|
|
492
|
buf.append(" Class: %s" % service.rpid_class)
|
|
493
|
# display timestamp
|
|
494
|
if service.timestamp is not None:
|
|
495
|
buf.append(" Timestamp: %s" % service.timestamp)
|
|
496
|
# display notes
|
|
497
|
for note in service.notes:
|
|
498
|
buf.append(" %s" % self._format_note(note))
|
|
499
|
# display status
|
|
500
|
if service.status is not None:
|
|
501
|
if service.status.basic is not None:
|
|
502
|
buf.append(" Basic status: %s" % service.status.basic)
|
|
503
|
if service.status.extended is not None:
|
|
504
|
buf.append(" Extended status: %s" % service.status.extended)
|
|
505
|
# display contact
|
|
506
|
if service.contact is not None:
|
|
507
|
buf.append(" Contact%s: %s" % ((service.contact.priority is not None) and (' priority %s' % service.contact.priority) or '', urllib.unquote(service.contact.value)))
|
|
508
|
# display device ID
|
|
509
|
if service.device_info is not None:
|
|
510
|
description = " (%s)" % urllib.unquote(service.device_info.description).decode('utf-8') if service.device_info.description else ""
|
|
511
|
buf.append(" Service offered by device: %s%s" % (service.device_info.id, description))
|
|
512
|
# display relationship
|
|
513
|
if service.relationship is not None:
|
|
514
|
buf.append(" Relationship: %s" % service.relationship.value)
|
|
515
|
# display service-class
|
|
516
|
if service.service_class is not None:
|
|
517
|
buf.append(" Service class: %s" % service.service_class.value)
|
|
518
|
# display status icon
|
|
519
|
if service.status_icon is not None:
|
|
520
|
buf.append(" Status icon: %s" % service.status_icon)
|
|
521
|
# display user input
|
|
522
|
if service.user_input is not None:
|
|
523
|
buf.append(" Service is %s" % service.user_input)
|
|
524
|
if service.user_input.last_input:
|
|
525
|
buf.append(" Last input at: %s" % service.user_input.last_input)
|
|
526
|
if service.user_input.idle_threshold:
|
|
527
|
buf.append(" Idle threshold: %s seconds" % service.user_input.idle_threshold)
|
|
528
|
return buf
|
|
529
|
|
|
530
|
def _format_device(self, device, pidf):
|
|
531
|
buf = []
|
|
532
|
# display device ID
|
|
533
|
if device.device_id is not None:
|
|
534
|
buf.append(" Device id: %s" % device.device_id)
|
|
535
|
# display class
|
|
536
|
if device.rpid_class is not None:
|
|
537
|
buf.append(" Class: %s" % device.rpid_class)
|
|
538
|
# display timestamp
|
|
539
|
if device.timestamp is not None:
|
|
540
|
buf.append(" Timestamp: %s" % device.timestamp)
|
|
541
|
# display notes
|
|
542
|
for note in device.notes:
|
|
543
|
buf.append(" %s" % self._format_note(note))
|
|
544
|
# display user input
|
|
545
|
if device.user_input is not None:
|
|
546
|
buf.append(" Device is %s" % device.user_input)
|
|
547
|
if device.user_input.last_input:
|
|
548
|
buf.append(" Last input at: %s" % device.user_input.last_input)
|
|
549
|
if device.user_input.idle_threshold:
|
|
550
|
buf.append(" Idle threshold: %s seconds" % device.user_input.idle_threshold)
|
|
551
|
return buf
|
|
552
|
|
|
553
|
def _display_pidf(self, pidf):
|
|
554
|
buf = ["-"*16]
|
|
555
|
buf.append("Presence for %s:" % urllib.unquote(pidf.entity))
|
|
556
|
persons = {}
|
|
557
|
devices = {}
|
|
558
|
services = {}
|
|
559
|
printed_sep = True
|
|
560
|
for child in pidf:
|
|
561
|
if isinstance(child, Person):
|
|
562
|
persons[child.id] = child
|
|
563
|
elif isinstance(child, Device):
|
|
564
|
devices[child.id] = child
|
|
565
|
elif isinstance(child, Service):
|
|
566
|
services[child.id] = child
|
|
567
|
|
|
568
|
# handle person information
|
|
569
|
if len(persons) == 0:
|
|
570
|
if list(pidf.notes):
|
|
571
|
buf.append(" Person information:")
|
|
572
|
for note in pidf.notes:
|
|
573
|
buf.append(" %s" % self._format_note(note))
|
|
574
|
printed_sep = False
|
|
575
|
else:
|
|
576
|
for person in persons.values():
|
|
577
|
buf.append(" Person: %s" % person.id)
|
|
578
|
buf.extend(self._format_person(person, pidf))
|
|
579
|
printed_sep = False
|
|
580
|
|
|
581
|
|
|
582
|
# handle services informaation
|
|
583
|
if len(services) > 0:
|
|
584
|
if not printed_sep:
|
|
585
|
buf.append(" " + "-"*3)
|
|
586
|
for service in services.values():
|
|
587
|
buf.append(" Service: %s" % service.id)
|
|
588
|
buf.extend(self._format_service(service, pidf))
|
|
589
|
|
|
590
|
# handle devices informaation
|
|
591
|
if len(devices) > 0:
|
|
592
|
if not printed_sep:
|
|
593
|
buf.append(" " + "-"*3)
|
|
594
|
for device in devices.values():
|
|
595
|
buf.append(" Device: %s" % device.id)
|
|
596
|
buf.extend(self._format_device(device, pidf))
|
|
597
|
|
|
598
|
buf.append("-"*16)
|
|
599
|
|
|
600
|
# push the data
|
|
601
|
self.output.put('\n'.join(buf))
|
|
602
|
|
|
603
|
|
|
604
|
if __name__ == "__main__":
|
|
605
|
description = "This script subscribes to the presence event package published by the specified SIP target. If a SIP target is not specified, it will subscribe to its own address. It will then interprete PIDF bodies contained in NOTIFYs and display their meaning. The program will un-SUBSCRIBE and quit when CTRL+D is pressed."
|
|
606
|
usage = "%prog [options] [target-user@target-domain.com]"
|
|
607
|
parser = OptionParser(usage=usage, description=description)
|
|
608
|
parser.print_usage = parser.print_help
|
|
609
|
parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The name of the account to use.")
|
|
610
|
parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", default=False, help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).")
|
|
611
|
parser.add_option("-j", "--trace-pjsip", action="store_true", dest="trace_pjsip", default=False, help="Print PJSIP logging output (disabled by default).")
|
|
612
|
parser.add_option("-n", "--trace-notifications", action="store_true", dest="trace_notifications", default=False, help="Print all notifications (disabled by default).")
|
|
613
|
options, args = parser.parse_args()
|
|
614
|
|
|
615
|
try:
|
|
616
|
application = SubscriptionApplication(options.account_name, args[0] if args else None, options.trace_sip, options.trace_pjsip, options.trace_notifications)
|
|
617
|
return_code = application.run()
|
|
618
|
except RuntimeError, e:
|
|
619
|
print "Error: %s" % str(e)
|
|
620
|
sys.exit(1)
|
|
621
|
except SIPCoreError, e:
|
|
622
|
print "Error: %s" % str(e)
|
|
623
|
sys.exit(1)
|
|
624
|
else:
|
|
625
|
sys.exit(return_code)
|
|
626
|
|
|
627
|
|