def _NH_DNSLookupDidSucceed(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self._ended: return routes = notification.data.result if not routes: self._terminate(failure_reason="Destination not found") return self.session = Session(self.account) self.stream = FileTransferStream(self._file_selector, 'recvonly', transfer_id=self.ft_info.transfer_id) self.handler = self.stream.handler notification.center.add_observer(self, sender=self.stream) notification.center.add_observer(self, sender=self.handler) self.session.connect(ToHeader(self.target_uri), routes, [self.stream])
def _NH_DNSLookupDidSucceed(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self._ended: self.log_info("File transfer was already cancelled") return routes = notification.data.result if not routes: self._terminate(failure_reason="Destination not found") return self.session = Session(self.account) self.stream = FileTransferStream(self._file_selector, 'sendonly', transfer_id=self.ft_info.transfer_id) self.handler = self.stream.handler notification.center.add_observer(self, sender=self.stream) notification.center.add_observer(self, sender=self.handler) self.log_info("Sending push file transfer request via %s..." % routes[0]) self.session.connect(ToHeader(self.target_uri), routes, [self.stream])
def init_outgoing(self, name, uri, streams, account): self.name = name self.uri = uri self.session = Session(account) for stream in streams: self._set_stream(stream.type, stream) self.initialized = True self.notification_center.add_observer(self, sender=self.session) self.notification_center.post_notification('SessionItemNewOutgoing', sender=self)
def call(self, account_name, callee, wave_file, length=None): logging.info("calling from: %s - to: %s", account_name, callee) # Setup wave playback self.player = WavePlayer(SIPApplication.voice_audio_mixer, wave_file, loop_count=0, initial_play=False) # configure callee and route to him/her callee_header = ToHeader(SIPURI.parse(callee)) routes = [Route("62.220.31.184", 5060, "udp")] # locate caller account = self.accounts.get(account_name, None) if account is None: raise Exception("No account with that name found") # finally make the call session = Session(account) session.connect(callee_header, routes, [AudioStream()]) # if we got a length, end the call after it if not length is None: time.sleep(length) session.end()
def _NH_BlinkFileTransferDidComputeHash(self, sender, data): notification_center = NotificationCenter() settings = SIPSimpleSettings() self.stream = FileTransferStream(self.account, self.file_selector, 'sendonly') self.session = Session(self.account) notification_center.add_observer(self, sender=self.session) notification_center.add_observer(self, sender=self.stream) self.status = "Offering File..." self.ft_info.status = "proposing" BlinkLogger().log_info(u"Initiating DNS Lookup of %s to %s" % (self.account, self.target_uri)) lookup = DNSLookup() notification_center.add_observer(self, sender=lookup) if isinstance(self.account, Account) and self.account.sip.outbound_proxy is not None: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={ 'transport': self.account.sip.outbound_proxy.transport }) BlinkLogger().log_info( u"Initiating DNS Lookup for SIP routes of %s (through proxy %s)" % (self.target_uri, uri)) elif isinstance(self.account, Account) and self.account.sip.always_use_my_proxy: uri = SIPURI(host=self.account.id.domain) BlinkLogger().log_info( u"Initiating DNS Lookup for SIP routes of %s (through account %s proxy)" % (self.target_uri, self.account.id)) else: uri = self.target_uri BlinkLogger().log_info( u"Initiating DNS Lookup for SIP routes of %s" % self.target_uri) notification_center.post_notification( "BlinkFileTransferInitiated", self, data=TimestampedNotificationData()) lookup.lookup_sip_proxy(uri, settings.sip.transport_list)
class OutgoingPullFileTransferHandler(FileTransfer): direction = 'outgoing' def __init__(self, account, target_uri, filename, hash): self.account = account self._file_selector = FileSelector(name=os.path.basename(filename), hash=hash) self.remote_identity = format_identity_to_string(target_uri) self.target_uri = SIPURI.new(target_uri) self._ended = False @property def target_text(self): f = NSLocalizedString("From %s", "Label") % self.target_uri t = NSLocalizedString("to account %s", "Label") % self.account.id return f + " " + t @property def progress_text(self): return self.status def start(self): notification_center = NotificationCenter() file_path = self._file_selector.name.decode() if isinstance( self._file_selector.name, bytes) else self._file_selector.name self.ft_info = FileTransferInfo( transfer_id=str(uuid.uuid4()), direction='incoming', local_uri=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour.local', file_size=0, remote_uri=self.remote_identity, file_path=file_path) self.log_info("Pull File Transfer Request started %s" % file_path) self.status = NSLocalizedString("Requesting File...", "Label") self.ft_info.status = "requesting" self.log_info("Initiating DNS Lookup of %s to %s" % (self.account, self.target_uri)) lookup = DNSLookup() notification_center.add_observer(self, sender=lookup) if isinstance(self.account, Account) and self.account.sip.outbound_proxy is not None: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={ 'transport': self.account.sip.outbound_proxy.transport }) self.log_info("Initiating DNS Lookup for %s (through proxy %s)" % (self.target_uri, uri)) elif isinstance(self.account, Account) and self.account.sip.always_use_my_proxy: uri = SIPURI(host=self.account.id.domain) self.log_info( "Initiating DNS Lookup for %s (through account %s proxy)" % (self.target_uri, self.account.id)) else: uri = self.target_uri self.log_info("Initiating DNS Lookup for %s" % self.target_uri) settings = SIPSimpleSettings() tls_name = None if isinstance(self.account, Account): tls_name = self.account.sip.tls_name or self.account.id.domain lookup.lookup_sip_proxy(uri, settings.sip.transport_list, tls_name=tls_name) notification_center.post_notification("BlinkFileTransferNewOutgoing", self) def end(self): if self._ended: return self._ended = True if self.session is not None: self.session.end() else: status = NSLocalizedString("Cancelled", "Label") self._terminate(failure_reason="Cancelled", failure_status=status) self.log_info("File Transfer has been cancelled") def _NH_DNSLookupDidSucceed(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self._ended: return routes = notification.data.result if not routes: self._terminate(failure_reason="Destination not found") return self.session = Session(self.account) self.stream = FileTransferStream(self._file_selector, 'recvonly', transfer_id=self.ft_info.transfer_id) self.handler = self.stream.handler notification.center.add_observer(self, sender=self.stream) notification.center.add_observer(self, sender=self.handler) self.log_info("Sending pull file transfer request via %s..." % routes[0]) self.session.connect(ToHeader(self.target_uri), routes, [self.stream]) def _NH_DNSLookupDidFail(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self._ended: return self.log_info("DNS Lookup for SIP routes failed: '%s'" % notification.data.error) status = NSLocalizedString("DNS Lookup failed", "Label") self._terminate(failure_reason=notification.data.error, failure_status=status)
class OutgoingPushFileTransferHandler(FileTransfer): direction = 'outgoing' def __init__(self, account, target_uri, file_path): self.account = account self._file_selector = FileSelector.for_file(file_path) self.remote_identity = format_identity_to_string(target_uri) self.target_uri = target_uri self._ended = False @property def target_text(self): t = NSLocalizedString("To %s", "Label") % self.remote_identity f = NSLocalizedString("from account %s", "Label") % self.account.id return t + " " + f @property def progress_text(self): return self.status def retry(self): self._ended = False self._file_selector = FileSelector.for_file(self._file_selector.name) self.bytes = 0 self.total_bytes = 0 self.progress = None self.last_rate_pos = 0 self.last_rate_time = None self.rate_history = None self.session = None self.stream = None self.handler = None self.transfer_rate = None self.start(restart=True) def start(self, restart=False): notification_center = NotificationCenter() file_path = self._file_selector.name.decode() if isinstance( self._file_selector.name, bytes) else self._file_selector.name self.ft_info = FileTransferInfo( transfer_id=str(uuid.uuid4()), direction='outgoing', file_size=self._file_selector.size, local_uri=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour.local', remote_uri=self.remote_identity, file_path=file_path) self.status = NSLocalizedString("Offering File...", "Label") self.ft_info.status = "proposing" self.log_info("Initiating DNS Lookup of %s to %s" % (self.account, self.target_uri)) lookup = DNSLookup() notification_center.add_observer(self, sender=lookup) if isinstance(self.account, Account) and self.account.sip.outbound_proxy is not None: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={ 'transport': self.account.sip.outbound_proxy.transport }) self.log_info("Initiating DNS Lookup for %s (through proxy %s)" % (self.target_uri, uri)) elif isinstance(self.account, Account) and self.account.sip.always_use_my_proxy: uri = SIPURI(host=self.account.id.domain) self.log_info( "Initiating DNS Lookup for %s (through account %s proxy)" % (self.target_uri, self.account.id)) else: uri = self.target_uri self.log_info("Initiating DNS Lookup for %s" % self.target_uri) settings = SIPSimpleSettings() tls_name = None if isinstance(self.account, Account): tls_name = self.account.sip.tls_name or self.account.id.domain lookup.lookup_sip_proxy(uri, settings.sip.transport_list, tls_name=tls_name) if restart: notification_center.post_notification( "BlinkFileTransferWillRestart", self) else: notification_center.post_notification( "BlinkFileTransferNewOutgoing", sender=self) def end(self): if self._ended: return self._ended = True if self.session is not None: self.session.end() else: status = NSLocalizedString("Cancelled", "Label") self._terminate(failure_reason="Cancelled", failure_status=status) self.log_info("File Transfer has been cancelled") def _NH_DNSLookupDidSucceed(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self._ended: self.log_info("File transfer was already cancelled") return routes = notification.data.result if not routes: self._terminate(failure_reason="Destination not found") return self.session = Session(self.account) self.stream = FileTransferStream(self._file_selector, 'sendonly', transfer_id=self.ft_info.transfer_id) self.handler = self.stream.handler notification.center.add_observer(self, sender=self.stream) notification.center.add_observer(self, sender=self.handler) self.log_info("Sending push file transfer request via %s..." % routes[0]) self.session.connect(ToHeader(self.target_uri), routes, [self.stream]) def _NH_DNSLookupDidFail(self, notification): self.log_info("DNS Lookup failed: '%s'" % notification.data.error) notification.center.remove_observer(self, sender=notification.sender) if self._ended: return status = NSLocalizedString("DNS Lookup failed", "Label") self._terminate(failure_reason=notification.data.error, failure_status=status) def _NH_FileTransferHandlerHashProgress(self, notification): progress = int(notification.data.processed * 100 / notification.data.total) if self.progress is None or progress > self.progress: self.progress = progress notification.center.post_notification( 'BlinkFileTransferHashProgress', sender=self, data=NotificationData(progress=progress))
def start(self): notification_center = NotificationCenter() settings = SIPSimpleSettings() download_folder = unicodedata.normalize( 'NFC', NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, True)[0]) for name in self.filename_generator( os.path.join(download_folder, self.file_name)): if not os.path.exists(name) and not os.path.exists(name + ".download"): self.file_path = name + '.download' break BlinkLogger().log_info(u"File will be written to %s" % self.file_path) self.file_selector.fd = open(self.file_path, "w+") self.ft_info = FileTransferInfo( transfer_id=self.transfer_id, direction='incoming', local_uri=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour', file_size=self.file_size, remote_uri=self.remote_identity, file_path=self.file_path) BlinkLogger().log_info("Pull File Transfer Request started %s" % self.file_path) self.stream = FileTransferStream(self.account, self.file_selector, 'recvonly') self.session = Session(self.account) notification_center.add_observer(self, sender=self) notification_center.add_observer(self, sender=self.session) notification_center.add_observer(self, sender=self.stream) self.status = "Requesting File..." self.ft_info.status = "requesting" BlinkLogger().log_info(u"Initiating DNS Lookup of %s to %s" % (self.account, self.target_uri)) lookup = DNSLookup() notification_center.add_observer(self, sender=lookup) if isinstance(self.account, Account) and self.account.sip.outbound_proxy is not None: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={ 'transport': self.account.sip.outbound_proxy.transport }) BlinkLogger().log_info( u"Initiating DNS Lookup for SIP routes of %s (through proxy %s)" % (self.target_uri, uri)) elif isinstance(self.account, Account) and self.account.sip.always_use_my_proxy: uri = SIPURI(host=self.account.id.domain) BlinkLogger().log_info( u"Initiating DNS Lookup for SIP routes of %s (through account %s proxy)" % (self.target_uri, self.account.id)) else: uri = self.target_uri BlinkLogger().log_info( u"Initiating DNS Lookup for SIP routes of %s" % self.target_uri) notification_center.post_notification( "BlinkFileTransferInitializing", self, data=TimestampedNotificationData()) notification_center.post_notification( "BlinkFileTransferInitiated", self, data=TimestampedNotificationData()) lookup.lookup_sip_proxy(uri, settings.sip.transport_list)
class SessionItem(object): implements(IObserver) def __init__(self): self.name = None self.uri = None self.session = None self.streams = {} self.initialized = False self.timer = LoopingCall(self._timer_fired) self.outbound_ringtone = Null self.offer_in_progress = False self.local_hold = False self.remote_hold = False self._active = False self._codec_info = u'' self._tls = False self._srtp = False self._duration = timedelta(0) self._latency = 0 self._packet_loss = 0 self.status = None self.notification_center = NotificationCenter() def init_incoming(self, session, streams): self.name = session.remote_identity.display_name self.uri = session.remote_identity.uri self.session = session for stream in streams: self._set_stream(stream.type, stream) self.initialized = True self.notification_center.add_observer(self, sender=self.session) self.notification_center.post_notification('SessionItemNewIncoming', sender=self) def init_outgoing(self, name, uri, streams, account): self.name = name self.uri = uri self.session = Session(account) for stream in streams: self._set_stream(stream.type, stream) self.initialized = True self.notification_center.add_observer(self, sender=self.session) self.notification_center.post_notification('SessionItemNewOutgoing', sender=self) def _set_stream(self, stream_type, stream): old_stream = self.streams.get(stream_type, None) self.streams[stream_type] = stream if old_stream is not None: self.notification_center.remove_observer(self, sender=old_stream) if stream_type == 'audio': self.hold_tone = Null if stream is not None: self.notification_center.add_observer(self, sender=stream) if stream_type == 'audio': self.hold_tone = WavePlayer(stream.bridge.mixer, Resources.get('sounds/hold_tone.wav'), loop_count=0, pause_time=45, volume=30) stream.bridge.add(self.hold_tone) @property def audio_stream(self): return self.streams.get('audio', None) @property def video_stream(self): return self.streams.get('video', None) @property def codec_info(self): return self._codec_info @property def tls(self): return self._tls @property def srtp(self): return self._srtp @property def duration(self): return self._duration @property def latency(self): return self._latency @property def packet_loss(self): return self._packet_loss def _get_status(self): return self.__dict__.get('status', None) def _set_status(self, value): old_value = self.__dict__.get('status', None) if old_value == value: return self.__dict__['status'] = value self.notification_center.post_notification('SessionItemDidChange', sender=self) status = property(_get_status, _set_status) @property def pending_removal(self): return not bool(self.streams.values()) def _get_active(self): return self._active def _set_active(self, value): value = bool(value) if self._active == value: return self._active = value if self.audio_stream: self.audio_stream.device.output_muted = not value if value: self.unhold() self.notification_center.post_notification('SessionItemDidActivate', sender=self) else: self.hold() self.notification_center.post_notification('SessionItemDidDeactivate', sender=self) active = property(_get_active, _set_active) del _get_active, _set_active def connect(self): self.offer_in_progress = True account = self.session.account settings = SIPSimpleSettings() if isinstance(account, Account): if account.sip.outbound_proxy is not None: proxy = account.sip.outbound_proxy uri = SIPURI(host=proxy.host, port=proxy.port, parameters={'transport': proxy.transport}) elif account.sip.always_use_my_proxy: uri = SIPURI(host=account.id.domain) else: uri = self.uri else: uri = self.uri self.status = u'Looking up destination' lookup = DNSLookup() self.notification_center.add_observer(self, sender=lookup) lookup.lookup_sip_proxy(uri, settings.sip.transport_list) def hold(self): if not self.pending_removal and not self.local_hold: self.local_hold = True self.session.hold() self.hold_tone.start() def unhold(self): if not self.pending_removal and self.local_hold: self.local_hold = False self.session.unhold() def send_dtmf(self, digit): if self.audio_stream is not None: try: self.audio_stream.send_dtmf(digit) except RuntimeError: pass else: digit_map = {'*': 'star'} filename = 'sounds/dtmf_%s_tone.wav' % digit_map.get(digit, digit) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get(filename)) self.notification_center.add_observer(self, sender=player) if self.session.account.rtp.inband_dtmf: self.audio_stream.bridge.add(player) SIPApplication.voice_audio_bridge.add(player) player.start() def end(self): if self.session.state is None: self.status = u'Call canceled' self._cleanup() else: self.session.end() def _cleanup(self): if self.timer.running: self.timer.stop() self.notification_center.remove_observer(self, sender=self.session) for k in self.streams.keys(): self._set_stream(k, None) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get('sounds/hangup_tone.wav'), volume=60) self.notification_center.add_observer(self, sender=player) SIPApplication.voice_audio_bridge.add(player) player.start() self.notification_center.post_notification('SessionItemDidEnd', sender=self) def _reset_status(self): if self.pending_removal or self.offer_in_progress: return if self.local_hold: self.status = u'On hold' elif self.remote_hold: self.status = u'Hold by remote' else: self.status = None def _set_codec_info(self): codecs = [] if self.video_stream is not None: desc = 'HD Video' if self.video_stream.bit_rate/1024 >= 512 else 'Video' codecs.append('[%s] %s %dkbit' % (desc, self.video_stream.codec, self.video_stream.bit_rate/1024)) if self.audio_stream is not None: desc = 'HD Audio' if self.audio_stream.sample_rate/1000 >= 16 else 'Audio' codecs.append('[%s] %s %dkHz' % (desc, self.audio_stream.codec, self.audio_stream.sample_rate/1000)) self._codec_info = ', '.join(codecs) def _timer_fired(self): if self.audio_stream is not None: stats = self.audio_stream.statistics if stats is not None: self._latency = stats['rtt']['avg'] / 1000 self._packet_loss = int(stats['rx']['packets_lost']*100.0/stats['rx']['packets']) if stats['rx']['packets'] else 0 self._duration += timedelta(seconds=1) self.notification_center.post_notification('SessionItemDidChange', sender=self) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_AudioStreamICENegotiationStateDidChange(self, notification): if notification.data.state == 'GATHERING': self.status = u'Gathering ICE candidates' elif notification.data.state == 'NEGOTIATING': self.status = u'Negotiating ICE' elif notification.data.state == 'RUNNING': self.status = u'Connecting...' elif notification.data.state == 'FAILED': self.status = u'ICE failed' def _NH_AudioStreamGotDTMF(self, notification): digit_map = {'*': 'star'} filename = 'sounds/dtmf_%s_tone.wav' % digit_map.get(notification.data.digit, notification.data.digit) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get(filename)) notification.center.add_observer(self, sender=player) SIPApplication.voice_audio_bridge.add(player) player.start() def _NH_DNSLookupDidSucceed(self, notification): settings = SIPSimpleSettings() notification.center.remove_observer(self, sender=notification.sender) if self.pending_removal: return if self.audio_stream: outbound_ringtone = settings.sounds.outbound_ringtone if outbound_ringtone: self.outbound_ringtone = WavePlayer(self.audio_stream.mixer, outbound_ringtone.path, outbound_ringtone.volume, loop_count=0, pause_time=5) self.audio_stream.bridge.add(self.outbound_ringtone) routes = notification.data.result self._tls = routes[0].transport=='tls' if routes else False self.status = u'Connecting...' self.session.connect(ToHeader(self.uri), routes, self.streams.values()) def _NH_DNSLookupDidFail(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self.pending_removal: return self.status = u'Destination not found' self._cleanup() def _NH_SIPSessionGotRingIndication(self, notification): self.status = u'Ringing...' self.outbound_ringtone.start() def _NH_SIPSessionWillStart(self, notification): self.outbound_ringtone.stop() def _NH_SIPSessionDidStart(self, notification): if self.audio_stream not in notification.data.streams: self._set_stream('audio', None) if self.video_stream not in notification.data.streams: self._set_stream('video', None) # TODO: rework the above code if not self.local_hold: self.offer_in_progress = False if not self.pending_removal: self.timer.start(1) self._set_codec_info() self.status = u'Connected' self._srtp = all(stream.srtp_active for stream in (self.audio_stream, self.video_stream) if stream is not None) self._tls = self.session.transport == 'tls' reactor.callLater(1, self._reset_status) else: self.status = u'Ending...' self._cleanup() def _NH_SIPSessionDidFail(self, notification): self.offer_in_progress = False if notification.data.failure_reason == 'user request': if notification.data.code == 487: reason = u'Call canceled' else: reason = unicode(notification.data.reason) else: reason = notification.data.failure_reason self.status = reason self.outbound_ringtone.stop() self._cleanup() def _NH_SIPSessionDidEnd(self, notification): self.offer_in_progress = False self.status = u'Call ended' if notification.data.originator=='local' else u'Call ended by remote' self._cleanup() def _NH_SIPSessionDidChangeHoldState(self, notification): if notification.data.originator == 'remote': self.remote_hold = notification.data.on_hold if self.local_hold: if not self.offer_in_progress: self.status = u'On hold' elif self.remote_hold: if not self.offer_in_progress: self.status = u'Hold by remote' self.hold_tone.start() else: self.status = None self.hold_tone.stop() self.offer_in_progress = False def _NH_SIPSessionGotAcceptProposal(self, notification): if self.audio_stream not in notification.data.proposed_streams and self.video_stream not in notification.data.proposed_streams: return if self.audio_stream in notification.data.proposed_streams and self.audio_stream not in notification.data.streams: self._set_stream('audio', None) if self.video_stream in notification.data.proposed_streams and self.video_stream not in notification.data.streams: self._set_stream('video', None) # TODO: rework the above self.offer_in_progress = False if not self.pending_removal: self._set_codec_info() self.status = u'Connected' reactor.callLater(1, self._reset_status) else: self.status = u'Ending...' self._cleanup() def _NH_SIPSessionGotRejectProposal(self, notification): if self.audio_stream not in notification.data.streams and self.video_stream not in notification.data.streams: return if self.audio_stream in notification.data.streams: self._set_stream('audio', None) if self.video_stream in notification.data.streams: self._set_stream('video', None) # TODO: rework the above self.offer_in_progress = False self.status = u'Stream refused' if not self.pending_removal: self._set_codec_info() reactor.callLater(1, self._reset_status) else: self._cleanup() def _NH_SIPSessionDidRenegotiateStreams(self, notification): if notification.data.action != 'remove': # TODO: what? return if self.audio_stream not in notification.data.streams and self.video_stream not in notification.data.streams: return if self.audio_stream in notification.data.streams: self._set_stream('audio', None) if self.video_stream in notification.data.streams: self._set_stream('video', None) # TODO: rework the above self.offer_in_progress = False self.status = u'Stream removed' if not self.pending_removal: self._set_codec_info() reactor.callLater(1, self._reset_status) else: self._cleanup() def _NH_WavePlayerDidFail(self, notification): notification.center.remove_observer(self, sender=notification.sender) def _NH_WavePlayerDidEnd(self, notification): notification.center.remove_observer(self, sender=notification.sender)