/SessionController.py
Python | 2077 lines | 1971 code | 95 blank | 11 comment | 153 complexity | 82b2ed3e08dc1b3d16cf4478ae9b142e MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- # Copyright (C) 2009-2011 AG Projects. See LICENSE for details.
- #
- import cjson
- import hashlib
- import re
- import time
- import socket
- import urllib
- import urlparse
- import uuid
- from itertools import chain
- from application.notification import IObserver, NotificationCenter
- from application.python import Null
- from application.python.types import Singleton
- from datetime import datetime, timedelta
- from dateutil.tz import tzlocal
- from sipsimple.account import Account, AccountManager, BonjourAccount
- from sipsimple.application import SIPApplication
- from sipsimple.configuration.settings import SIPSimpleSettings
- from sipsimple.core import SIPURI, ToHeader, SIPCoreError
- from sipsimple.lookup import DNSLookup
- from sipsimple.session import Session, IllegalStateError, IllegalDirectionError
- from sipsimple.util import TimestampedNotificationData, Timestamp
- from sipsimple.threading.green import run_in_green_thread
- from zope.interface import implements
- from AppKit import *
- from Foundation import *
- from AlertPanel import AlertPanel
- from AudioController import AudioController
- from AccountSettings import AccountSettings
- from BlinkLogger import BlinkLogger
- from ChatController import ChatController
- from DesktopSharingController import DesktopSharingController, DesktopSharingServerController, DesktopSharingViewerController
- from FileTransferController import FileTransferController
- from FileTransferSession import OutgoingPushFileTransferHandler
- from HistoryManager import ChatHistory, SessionHistory
- from MediaStream import *
- from SessionRinger import Ringer
- from SessionInfoController import SessionInfoController
- from SIPManager import SIPManager
- from VideoController import VideoController
- from interfaces.itunes import MusicApplications
- from util import *
- SessionIdentifierSerial = 0
- OUTBOUND_AUDIO_CALLS = 0
- StreamHandlerForType = {
- "chat" : ChatController,
- "audio" : AudioController,
- # "video" : VideoController,
- "video" : ChatController,
- "file-transfer" : FileTransferController,
- "desktop-sharing" : DesktopSharingController,
- "desktop-server" : DesktopSharingServerController,
- "desktop-viewer" : DesktopSharingViewerController
- }
- class SessionControllersManager(object):
- __metaclass__ = Singleton
- implements(IObserver)
- def __init__(self):
- self.notification_center = NotificationCenter()
- self.notification_center.add_observer(self, name='AudioStreamGotDTMF')
- self.notification_center.add_observer(self, name='BlinkSessionDidEnd')
- self.notification_center.add_observer(self, name='BlinkSessionDidFail')
- self.notification_center.add_observer(self, name='CFGSettingsObjectDidChange')
- self.notification_center.add_observer(self, name='SIPAccountDidActivate')
- self.notification_center.add_observer(self, name='SIPAccountDidDeactivate')
- self.notification_center.add_observer(self, name='SIPApplicationWillEnd')
- self.notification_center.add_observer(self, name='SIPSessionNewIncoming')
- self.notification_center.add_observer(self, name='SIPSessionNewOutgoing')
- self.notification_center.add_observer(self, name='SIPSessionDidStart')
- self.notification_center.add_observer(self, name='SIPSessionDidFail')
- self.notification_center.add_observer(self, name='SIPSessionDidEnd')
- self.notification_center.add_observer(self, name='SIPSessionGotProposal')
- self.notification_center.add_observer(self, name='SIPSessionGotRejectProposal')
- self.notification_center.add_observer(self, name='SystemWillSleep')
- self.notification_center.add_observer(self, name='MediaStreamDidInitialize')
- self.notification_center.add_observer(self, name='MediaStreamDidEnd')
- self.notification_center.add_observer(self, name='MediaStreamDidFail')
- self.last_calls_connections = {}
- self.last_calls_connections_authRequestCount = {}
- self.sessionControllers = []
- self.ringer = Ringer(self)
- self.incomingSessions = set()
- self.activeAudioStreams = set()
- self.pause_music = True
- @allocate_autorelease_pool
- @run_in_gui_thread
- def handle_notification(self, notification):
- handler = getattr(self, '_NH_%s' % notification.name, Null)
- handler(notification.sender, notification.data)
- def _NH_SIPApplicationDidStart(self, sender, data):
- self.ringer.start()
- self.ringer.update_ringtones()
- settings = SIPSimpleSettings()
- self.pause_music = settings.audio.pause_music if settings.audio.pause_music and NSApp.delegate().applicationName != 'Blink Lite' else False
- def _NH_SIPApplicationWillEnd(self, sender, data):
- self.ringer.stop()
- def _NH_SIPSessionDidFail(self, session, data):
- self.incomingSessions.discard(session)
- if self.pause_music:
- self.incomingSessions.discard(session)
- if not self.activeAudioStreams and not self.incomingSessions:
- music_applications = MusicApplications()
- music_applications.resume()
- def _NH_SIPSessionDidStart(self, session, data):
- self.incomingSessions.discard(session)
- if self.pause_music:
- if all(stream.type != 'audio' for stream in data.streams):
- if not self.activeAudioStreams and not self.incomingSessions:
- music_applications = MusicApplications()
- music_applications.resume()
- if session.direction == 'incoming':
- if session.account is not BonjourAccount() and session.account.web_alert.show_alert_page_after_connect:
- self.show_web_alert_page(session)
- def _NH_SIPSessionDidEnd(self, session, data):
- if self.pause_music:
- self.incomingSessions.discard(session)
- if not self.activeAudioStreams and not self.incomingSessions:
- music_applications = MusicApplications()
- music_applications.resume()
- def _NH_SIPSessionGotProposal(self, session, data):
- if self.pause_music:
- if any(stream.type == 'audio' for stream in data.streams):
- music_applications = MusicApplications()
- music_applications.pause()
- def _NH_SIPSessionGotRejectProposal(self, session, data):
- if self.pause_music:
- if any(stream.type == 'audio' for stream in data.streams):
- if not self.activeAudioStreams and not self.incomingSessions:
- music_applications = MusicApplications()
- music_applications.resume()
- def _NH_MediaStreamDidInitialize(self, stream, data):
- if stream.type == 'audio':
- self.activeAudioStreams.add(stream)
- def _NH_MediaStreamDidEnd(self, stream, data):
- if self.pause_music:
- if stream.type == "audio":
- self.activeAudioStreams.discard(stream)
- # TODO: check if session has other streams and if yes, resume itunes
- # in case of session ends, resume is handled by the Session Controller
- if not self.activeAudioStreams and not self.incomingSessions:
- music_applications = MusicApplications()
- music_applications.resume()
- def _NH_MediaStreamDidFail(self, stream, data):
- if self.pause_music:
- if stream.type == "audio":
- self.activeAudioStreams.discard(stream)
- if not self.activeAudioStreams and not self.incomingSessions:
- music_applications = MusicApplications()
- music_applications.resume()
- @run_in_gui_thread
- def _NH_SIPSessionNewIncoming(self, session, data):
- streams = [stream for stream in data.streams if self.isProposedMediaTypeSupported([stream])]
- stream_type_list = list(set(stream.type for stream in streams))
- if not streams:
- BlinkLogger().log_info(u"Rejecting session for unsupported media type")
- session.reject(488, 'Incompatible media')
- return
- # if call waiting is disabled and we have audio calls reject with busy
- hasAudio = any(sess.hasStreamOfType("audio") for sess in self.sessionControllers)
- if 'audio' in stream_type_list and hasAudio and session.account is not BonjourAccount() and session.account.audio.call_waiting is False:
- BlinkLogger().log_info(u"Refusing audio call from %s because we are busy and call waiting is disabled" % format_identity_to_string(session.remote_identity))
- session.reject(486, 'Busy Here')
- return
- if 'audio' in stream_type_list and session.account is not BonjourAccount() and session.account.audio.do_not_disturb:
- BlinkLogger().log_info(u"Refusing audio call from %s because do not disturb is enabled" % format_identity_to_string(session.remote_identity))
- session.reject(session.account.sip.do_not_disturb_code, 'Do Not Disturb')
- return
- if 'audio' in stream_type_list and session.account is not BonjourAccount() and session.account.audio.reject_anonymous and session.remote_identity.uri.user.lower() in ('anonymous', 'unknown', 'unavailable'):
- BlinkLogger().log_info(u"Rejecting audio call from anonymous caller")
- session.reject(403, 'Anonymous Not Acceptable')
- return
- # at this stage call is allowed and will alert the user
- self.incomingSessions.add(session)
- if self.pause_music:
- music_applications = MusicApplications()
- music_applications.pause()
- self.ringer.add_incoming(session, streams)
- session.blink_supported_streams = streams
- settings = SIPSimpleSettings()
- stream_type_list = list(set(stream.type for stream in streams))
- if NSApp.delegate().contactsWindowController.hasContactMatchingURI(session.remote_identity.uri):
- if settings.chat.auto_accept and stream_type_list == ['chat']:
- BlinkLogger().log_info(u"Automatically accepting chat session from %s" % format_identity_to_string(session.remote_identity))
- self.startIncomingSession(session, streams)
- return
- elif settings.file_transfer.auto_accept and stream_type_list == ['file-transfer']:
- BlinkLogger().log_info(u"Automatically accepting file transfer from %s" % format_identity_to_string(session.remote_identity))
- self.startIncomingSession(session, streams)
- return
- elif session.account is BonjourAccount() and stream_type_list == ['chat']:
- BlinkLogger().log_info(u"Automatically accepting Bonjour chat session from %s" % format_identity_to_string(session.remote_identity))
- self.startIncomingSession(session, streams)
- return
- if stream_type_list == ['file-transfer'] and streams[0].file_selector.name.decode("utf8").startswith('xscreencapture'):
- BlinkLogger().log_info(u"Automatically accepting screenshot from %s" % format_identity_to_string(session.remote_identity))
- self.startIncomingSession(session, streams)
- return
- try:
- session.send_ring_indication()
- except IllegalStateError, e:
- BlinkLogger().log_info(u"IllegalStateError: %s" % e)
- else:
- if settings.answering_machine.enabled and settings.answering_machine.answer_delay == 0:
- self.startIncomingSession(session, [s for s in streams if s.type=='audio'], answeringMachine=True)
- else:
- sessionController = self.addControllerWithSession_(session)
- if not NSApp.delegate().contactsWindowController.alertPanel:
- NSApp.delegate().contactsWindowController.alertPanel = AlertPanel.alloc().init()
- NSApp.delegate().contactsWindowController.alertPanel.addIncomingSession(session)
- NSApp.delegate().contactsWindowController.alertPanel.show()
- if session.account is not BonjourAccount() and not session.account.web_alert.show_alert_page_after_connect:
- self.show_web_alert_page(session)
- @run_in_gui_thread
- def _NH_SIPSessionNewOutgoing(self, session, data):
- self.ringer.add_outgoing(session, data.streams)
- if session.transfer_info is not None:
- # This Session was created as a result of a transfer
- self.addControllerWithSessionTransfer_(session)
- def _NH_AudioStreamGotDTMF(self, sender, data):
- key = data.digit
- filename = 'dtmf_%s_tone.wav' % {'*': 'star', '#': 'pound'}.get(key, key)
- wave_player = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get(filename))
- self.notification_center.add_observer(self, sender=wave_player)
- SIPApplication.voice_audio_bridge.add(wave_player)
- wave_player.start()
- def _NH_WavePlayerDidFail(self, sender, data):
- self.notification_center.remove_observer(self, sender=sender)
- def _NH_WavePlayerDidEnd(self, sender, data):
- self.notification_center.remove_observer(self, sender=sender)
-
- @run_in_gui_thread
- def _NH_BlinkSessionDidEnd(self, session_controller, data):
- if session_controller.session.direction == "incoming":
- self.log_incoming_session_ended(session_controller, data)
- else:
- self.log_outgoing_session_ended(session_controller, data)
- @run_in_gui_thread
- def _NH_BlinkSessionDidFail(self, session_controller, data):
- if data.direction == "outgoing":
- if data.code == 487:
- self.log_outgoing_session_cancelled(session_controller, data)
- else:
- self.log_outgoing_session_failed(session_controller, data)
- elif data.direction == "incoming":
- session = session_controller.session
- if data.code == 487 and data.failure_reason == 'Call completed elsewhere':
- self.log_incoming_session_answered_elsewhere(session_controller, data)
- else:
- self.log_incoming_session_missed(session_controller, data)
-
- if data.code == 487 and data.failure_reason == 'Call completed elsewhere':
- pass
- elif data.streams == ['file-transfer']:
- pass
- else:
- session_controller.log_info(u"Missed incoming session from %s" % format_identity_to_string(session.remote_identity))
- if 'audio' in data.streams:
- NSApp.delegate().noteMissedCall()
- growl_data = TimestampedNotificationData()
- growl_data.caller = format_identity_to_string(session.remote_identity, check_contact=True, format='compact')
- growl_data.timestamp = data.timestamp
- growl_data.streams = ",".join(data.streams)
- growl_data.account = session.account.id.username + '@' + session.account.id.domain
- self.notification_center.post_notification("GrowlMissedCall", sender=self, data=growl_data)
- def _NH_SIPAccountDidActivate(self, account, data):
- if account is not BonjourAccount():
- self.get_last_calls(account)
- def _NH_SIPAccountDidDeactivate(self, account, data):
- if account is not BonjourAccount():
- self.close_last_call_connection(account)
- def _NH_CFGSettingsObjectDidChange(self, account, data):
- if 'audio.pause_music' in data.modified:
- settings = SIPSimpleSettings()
- self.pause_music = settings.audio.pause_music if settings.audio.pause_music and NSApp.delegate().applicationName != 'Blink Lite' else False
- def addControllerWithSession_(self, session):
- sessionController = SessionController.alloc().initWithSession_(session)
- self.sessionControllers.append(sessionController)
- return sessionController
- def addControllerWithAccount_target_displayName_(self, account, target, display_name):
- sessionController = SessionController.alloc().initWithAccount_target_displayName_(account, target, display_name)
- self.sessionControllers.append(sessionController)
- return sessionController
- def addControllerWithSessionTransfer_(self, session):
- sessionController = SessionController.alloc().initWithSessionTransfer_(session)
- self.sessionControllers.append(sessionController)
- return sessionController
- def removeController(self, controller):
- try:
- self.sessionControllers.remove(controller)
- except ValueError:
- pass
- def send_files_to_contact(self, account, contact_uri, filenames):
- if not self.isMediaTypeSupported('file-transfer'):
- return
- target_uri = normalize_sip_uri_for_outgoing_session(contact_uri, AccountManager().default_account)
- for file in filenames:
- try:
- xfer = OutgoingPushFileTransferHandler(account, target_uri, file)
- xfer.start()
- except Exception, exc:
- BlinkLogger().log_error(u"Error while attempting to transfer file %s: %s" % (file, exc))
- def startIncomingSession(self, session, streams, answeringMachine=False):
- try:
- session_controller = (controller for controller in self.sessionControllers if controller.session == session).next()
- except StopIteration:
- session_controller = self.addControllerWithSession_(session)
- session_controller.setAnsweringMachineMode_(answeringMachine)
- session_controller.handleIncomingStreams(streams, False)
- def isScreenSharingEnabled(self):
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect(('127.0.0.1', 5900))
- s.close()
- return True
- except socket.error, msg:
- s.close()
- return False
- def isProposedMediaTypeSupported(self, streams):
- settings = SIPSimpleSettings()
- stream_type_list = list(set(stream.type for stream in streams))
- if 'desktop-sharing' in stream_type_list:
- ds = [s for s in streams if s.type == "desktop-sharing"]
- if ds and ds[0].handler.type != "active":
- if settings.desktop_sharing.disabled:
- BlinkLogger().log_info(u"Screen Sharing is disabled in Blink Preferences")
- return False
- if not self.isScreenSharingEnabled():
- BlinkLogger().log_info(u"Screen Sharing is disabled in System Preferences")
- return False
- if settings.file_transfer.disabled and 'file-transfer' in stream_type_list:
- BlinkLogger().log_info(u"File Transfers are disabled")
- return False
- if settings.chat.disabled and 'chat' in stream_type_list:
- BlinkLogger().log_info(u"Chat sessions are disabled")
- return False
- if 'video' in stream_type_list:
- # TODO: enable Video -adi
- return False
- return True
- def isMediaTypeSupported(self, type):
- settings = SIPSimpleSettings()
- if type == 'desktop-server':
- if settings.desktop_sharing.disabled:
- return False
- if not self.isScreenSharingEnabled():
- return False
- if settings.file_transfer.disabled and type == 'file-transfer':
- BlinkLogger().log_info(u"File Transfers are disabled")
- return False
- if settings.chat.disabled and type == 'chat':
- BlinkLogger().log_info(u"Chat sessions are disabled")
- return False
- if type == 'video':
- # TODO: enable Video -adi
- return False
- return True
- def log_incoming_session_missed(self, controller, data):
- account = controller.account
- if account is BonjourAccount():
- return
-
- id=str(uuid.uuid1())
- media_types = ",".join(data.streams)
- participants = ",".join(data.participants)
- local_uri = format_identity_to_string(account)
- remote_uri = format_identity_to_string(controller.target_uri)
- focus = "1" if data.focus else "0"
- failure_reason = ''
- duration = 0
- call_id = data.call_id if data.call_id is not None else ''
- from_tag = data.from_tag if data.from_tag is not None else ''
- to_tag = data.to_tag if data.to_tag is not None else ''
-
- self.add_to_history(id, media_types, 'incoming', 'missed', failure_reason, data.timestamp, data.timestamp, duration, local_uri, data.target_uri, focus, participants, call_id, from_tag, to_tag)
-
- if 'audio' in data.streams:
- message = '<h3>Missed Incoming Audio Call</h3>'
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'missed-call'
- direction = 'incoming'
- status = 'delivered'
- cpim_from = data.target_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
-
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(controller.target_uri), local_party=local_uri if account is not BonjourAccount() else 'bonjour', check_contact=True))
- def log_incoming_session_ended(self, controller, data):
- account = controller.account
- session = controller.session
- if account is BonjourAccount():
- return
-
- id=str(uuid.uuid1())
- media_types = ",".join(data.streams)
- participants = ",".join(data.participants)
- local_uri = format_identity_to_string(account)
- remote_uri = format_identity_to_string(controller.target_uri)
- focus = "1" if data.focus else "0"
- failure_reason = ''
- if session.start_time is None and session.end_time is not None:
- # Session could have ended before it was completely started
- session.start_time = session.end_time
-
- duration = session.end_time - session.start_time
- call_id = data.call_id if data.call_id is not None else ''
- from_tag = data.from_tag if data.from_tag is not None else ''
- to_tag = data.to_tag if data.to_tag is not None else ''
-
- self.add_to_history(id, media_types, 'incoming', 'completed', failure_reason, session.start_time, session.end_time, duration.seconds, local_uri, data.target_uri, focus, participants, call_id, from_tag, to_tag)
-
- if 'audio' in data.streams:
- duration = self.get_printed_duration(session.start_time, session.end_time)
- message = '<h3>Incoming Audio Call</h3>'
- message += '<p>Call duration: %s' % duration
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'audio'
- direction = 'incoming'
- status = 'delivered'
- cpim_from = data.target_uri
- cpim_to = format_identity_to_string(account)
- timestamp = str(Timestamp(datetime.now(tzlocal())))
-
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(controller.target_uri), local_party=local_uri if account is not BonjourAccount() else 'bonjour', check_contact=True))
- def log_incoming_session_answered_elsewhere(self, controller, data):
- account = controller.account
- if account is BonjourAccount():
- return
-
- id=str(uuid.uuid1())
- media_types = ",".join(data.streams)
- participants = ",".join(data.participants)
- local_uri = format_identity_to_string(account)
- remote_uri = format_identity_to_string(controller.target_uri)
- focus = "1" if data.focus else "0"
- failure_reason = 'Answered elsewhere'
- call_id = data.call_id if data.call_id is not None else ''
- from_tag = data.from_tag if data.from_tag is not None else ''
- to_tag = data.to_tag if data.to_tag is not None else ''
-
- self.add_to_history(id, media_types, 'incoming', 'completed', failure_reason, data.timestamp, data.timestamp, 0, local_uri, data.target_uri, focus, participants, call_id, from_tag, to_tag)
-
- if 'audio' in data.streams:
- message= '<h3>Incoming Audio Call</h3>'
- message += '<p>The call has been answered elsewhere'
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'audio'
- local_uri = local_uri
- remote_uri = remote_uri
- direction = 'incoming'
- status = 'delivered'
- cpim_from = data.target_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
-
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(controller.target_uri), local_party=local_uri if account is not BonjourAccount() else 'bonjour', check_contact=True))
- def log_outgoing_session_failed(self, controller, data):
- account = controller.account
- if account is BonjourAccount():
- return
-
- id=str(uuid.uuid1())
- media_types = ",".join(data.streams)
- participants = ",".join(data.participants)
- focus = "1" if data.focus else "0"
- local_uri = format_identity_to_string(account)
- remote_uri = format_identity_to_string(controller.target_uri)
- failure_reason = '%s (%s)' % (data.reason or data.failure_reason, data.code)
- call_id = data.call_id if data.call_id is not None else ''
- from_tag = data.from_tag if data.from_tag is not None else ''
- to_tag = data.to_tag if data.to_tag is not None else ''
-
- self.add_to_history(id, media_types, 'outgoing', 'failed', failure_reason, data.timestamp, data.timestamp, 0, local_uri, data.target_uri, focus, participants, call_id, from_tag, to_tag)
-
- if 'audio' in data.streams:
- message = '<h3>Failed Outgoing Audio Call</h3>'
- message += '<p>Reason: %s (%s)' % (data.reason or data.failure_reason, data.code)
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'audio'
- local_uri = local_uri
- remote_uri = remote_uri
- direction = 'incoming'
- status = 'delivered'
- cpim_from = data.target_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
-
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(controller.target_uri), local_party=local_uri if account is not BonjourAccount() else 'bonjour', check_contact=True))
- def log_outgoing_session_cancelled(self, controller, data):
- account = controller.account
- if account is BonjourAccount():
- return
-
- id=str(uuid.uuid1())
- media_types = ",".join(data.streams)
- participants = ",".join(data.participants)
- focus = "1" if data.focus else "0"
- local_uri = format_identity_to_string(account)
- remote_uri = format_identity_to_string(controller.target_uri)
- failure_reason = ''
- call_id = data.call_id if data.call_id is not None else ''
- from_tag = data.from_tag if data.from_tag is not None else ''
- to_tag = data.to_tag if data.to_tag is not None else ''
-
- self.add_to_history(id, media_types, 'outgoing', 'cancelled', failure_reason, data.timestamp, data.timestamp, 0, local_uri, data.target_uri, focus, participants, call_id, from_tag, to_tag)
-
- if 'audio' in data.streams:
- message= '<h3>Cancelled Outgoing Audio Call</h3>'
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'audio'
- direction = 'incoming'
- status = 'delivered'
- cpim_from = data.target_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
-
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(controller.target_uri), local_party=local_uri if account is not BonjourAccount() else 'bonjour', check_contact=True))
- def log_outgoing_session_ended(self, controller, data):
- account = controller.account
- session = controller.session
- if account is BonjourAccount():
- return
-
- id=str(uuid.uuid1())
- media_types = ",".join(data.streams)
- participants = ",".join(data.participants)
- focus = "1" if data.focus else "0"
- local_uri = format_identity_to_string(account)
- remote_uri = format_identity_to_string(controller.target_uri)
- direction = 'incoming'
- status = 'delivered'
- failure_reason = ''
- call_id = data.call_id if data.call_id is not None else ''
- from_tag = data.from_tag if data.from_tag is not None else ''
- to_tag = data.to_tag if data.to_tag is not None else ''
-
- if session.start_time is None and session.end_time is not None:
- # Session could have ended before it was completely started
- session.start_time = session.end_time
-
- duration = session.end_time - session.start_time
-
- self.add_to_history(id, media_types, 'outgoing', 'completed', failure_reason, session.start_time, session.end_time, duration.seconds, local_uri, data.target_uri, focus, participants, call_id, from_tag, to_tag)
-
- if 'audio' in data.streams:
- duration = self.get_printed_duration(session.start_time, session.end_time)
- message= '<h3>Outgoing Audio Call</h3>'
- message += '<p>Call duration: %s' % duration
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'audio'
- cpim_from = data.target_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
-
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(controller.target_uri), local_party=local_uri if account is not BonjourAccount() else 'bonjour', check_contact=True))
- def get_printed_duration(self, start_time, end_time):
- duration = end_time - start_time
- if (duration.days > 0 or duration.seconds > 0):
- duration_print = ""
- if duration.days > 0 or duration.seconds > 3600:
- duration_print += "%i hours, " % (duration.days*24 + duration.seconds/3600)
- seconds = duration.seconds % 3600
- duration_print += "%02i:%02i" % (seconds/60, seconds%60)
- else:
- duration_print = "00:00"
-
- return duration_print
- def add_to_history(self, id, media_types, direction, status, failure_reason, start_time, end_time, duration, local_uri, remote_uri, remote_focus, participants, call_id, from_tag, to_tag):
- SessionHistory().add_entry(id, media_types, direction, status, failure_reason, start_time, end_time, duration, local_uri, remote_uri, remote_focus, participants, call_id, from_tag, to_tag)
- def add_to_chat_history(self, id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, skip_replication=False):
- ChatHistory().add_message(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, "html", "0", status, skip_replication=skip_replication)
- def updateGetCallsTimer_(self, timer):
- try:
- key = (account for account in self.last_calls_connections.keys() if self.last_calls_connections[account]['timer'] == timer).next()
- except StopIteration:
- return
- else:
- try:
- connection = self.last_calls_connections[key]['connection']
- nsurl = NSURL.URLWithString_(self.last_calls_connections[key]['url'])
- except KeyError:
- pass
- else:
- if connection:
- connection.cancel()
- request = NSURLRequest.requestWithURL_cachePolicy_timeoutInterval_(nsurl, NSURLRequestReloadIgnoringLocalAndRemoteCacheData, 15)
- connection = NSURLConnection.alloc().initWithRequest_delegate_(request, self)
- self.last_calls_connections[key]['data'] = ''
- self.last_calls_connections[key]['authRequestCount'] = 0
- self.last_calls_connections[key]['connection'] = connection
-
- # NSURLConnection delegate method
- def connection_didReceiveData_(self, connection, data):
- try:
- key = (account for account in self.last_calls_connections.keys() if self.last_calls_connections[account]['connection'] == connection).next()
- except StopIteration:
- pass
- else:
- try:
- account = AccountManager().get_account(key)
- except KeyError:
- pass
- else:
- self.last_calls_connections[key]['data'] = self.last_calls_connections[key]['data'] + str(data)
-
- def connectionDidFinishLoading_(self, connection):
- try:
- key = (account for account in self.last_calls_connections.keys() if self.last_calls_connections[account]['connection'] == connection).next()
- except StopIteration:
- pass
- else:
- BlinkLogger().log_debug(u"Calls history for %s retrieved from %s" % (key, self.last_calls_connections[key]['url']))
- try:
- account = AccountManager().get_account(key)
- except KeyError:
- pass
- else:
- try:
- calls = cjson.decode(self.last_calls_connections[key]['data'])
- except (TypeError, cjson.DecodeError):
- BlinkLogger().log_debug(u"Failed to parse calls history for %s from %s" % (key, self.last_calls_connections[key]['url']))
- else:
- self.syncServerHistoryWithLocalHistory(account, calls)
-
- # NSURLConnection delegate method
- def connection_didFailWithError_(self, connection, error):
- try:
- key = (account for account in self.last_calls_connections.keys() if self.last_calls_connections[account]['connection'] == connection).next()
- except StopIteration:
- return
- BlinkLogger().log_debug(u"Failed to retrieve calls history for %s from %s" % (key, self.last_calls_connections[key]['url']))
-
- @run_in_green_thread
- def syncServerHistoryWithLocalHistory(self, account, calls):
- growl_notifications = {}
- try:
- for call in calls['received']:
- direction = 'incoming'
- local_entry = SessionHistory().get_entries(direction=direction, count=1, call_id=call['sessionId'], from_tag=call['fromTag'])
- if not len(local_entry):
- id=str(uuid.uuid1())
- participants = ""
- focus = "0"
- local_uri = str(account.id)
- try:
- remote_uri = call['remoteParty']
- start_time = datetime.strptime(call['startTime'], "%Y-%m-%d %H:%M:%S")
- end_time = datetime.strptime(call['stopTime'], "%Y-%m-%d %H:%M:%S")
- status = call['status']
- duration = call['duration']
- call_id = call['sessionId']
- from_tag = call['fromTag']
- to_tag = call['toTag']
- media_types = ", ".join(call['media']) or 'audio'
- except KeyError:
- continue
- success = 'completed' if duration > 0 else 'missed'
- BlinkLogger().log_debug(u"Adding incoming %s call at %s from %s from server history" % (success, start_time, remote_uri))
- self.add_to_history(id, media_types, direction, success, status, start_time, end_time, duration, local_uri, remote_uri, focus, participants, call_id, from_tag, to_tag)
- if 'audio' in call['media']:
- direction = 'incoming'
- status = 'delivered'
- cpim_from = remote_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
- if success == 'missed':
- message = '<h3>Missed Incoming Audio Call</h3>'
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'missed-call'
- else:
- duration = self.get_printed_duration(start_time, end_time)
- message = '<h3>Incoming Audio Call</h3>'
- message += '<p>The call has been answered elsewhere'
- message += '<p>Call duration: %s' % duration
- #message += '<h4>Technicall Information</h4><table class=table_session_info><tr><td class=td_session_info>Call Id</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>From Tag</td><td class=td_session_info>%s</td></tr><tr><td class=td_session_info>To Tag</td><td class=td_session_info>%s</td></tr></table>' % (call_id, from_tag, to_tag)
- media_type = 'audio'
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, time=start_time, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction=direction, history_entry=False, remote_party=remote_uri, local_party=local_uri, check_contact=True))
-
- if 'audio' in call['media'] and success == 'missed' and remote_uri not in growl_notifications.keys():
- now = datetime(*time.localtime()[:6])
- elapsed = now - start_time
- elapsed_hours = elapsed.seconds / (60*60)
- if elapsed_hours < 48:
- growl_data = TimestampedNotificationData()
- try:
- uri = SIPURI.parse('sip:'+str(remote_uri))
- except Exception:
- pass
- else:
- growl_data.caller = format_identity_to_string(uri, check_contact=True, format='compact')
- growl_data.timestamp = start_time
- growl_data.streams = media_types
- growl_data.account = str(account.id)
- self.notification_center.post_notification("GrowlMissedCall", sender=self, data=growl_data)
- growl_notifications[remote_uri] = True
- except (KeyError, TypeError):
- pass
-
- try:
- for call in calls['placed']:
- direction = 'outgoing'
- local_entry = SessionHistory().get_entries(direction=direction, count=1, call_id=call['sessionId'], from_tag=call['fromTag'])
- if not len(local_entry):
- id=str(uuid.uuid1())
- participants = ""
- focus = "0"
- local_uri = str(account.id)
- try:
- remote_uri = call['remoteParty']
- start_time = datetime.strptime(call['startTime'], "%Y-%m-%d %H:%M:%S")
- end_time = datetime.strptime(call['stopTime'], "%Y-%m-%d %H:%M:%S")
- status = call['status']
- duration = call['duration']
- call_id = call['sessionId']
- from_tag = call['fromTag']
- to_tag = call['toTag']
- media_types = ", ".join(call['media']) or 'audio'
- except KeyError:
- continue
-
- if duration > 0:
- success = 'completed'
- else:
- if status == "487":
- success = 'cancelled'
- else:
- success = 'failed'
-
- BlinkLogger().log_debug(u"Adding outgoing %s call at %s to %s from server history" % (success, start_time, remote_uri))
- self.add_to_history(id, media_types, direction, success, status, start_time, end_time, duration, local_uri, remote_uri, focus, participants, call_id, from_tag, to_tag)
- if 'audio' in call['media']:
- local_uri = local_uri
- remote_uri = remote_uri
- direction = 'incoming'
- status = 'delivered'
- cpim_from = remote_uri
- cpim_to = local_uri
- timestamp = str(Timestamp(datetime.now(tzlocal())))
- media_type = 'audio'
- if success == 'failed':
- message = '<h3>Failed Outgoing Audio Call</h3>'
- message += '<p>Reason: %s' % status
- elif success == 'cancelled':
- message= '<h3>Cancelled Outgoing Audio Call</h3>'
- else:
- duration = self.get_printed_duration(start_time, end_time)
- message= '<h3>Outgoing Audio Call</h3>'
- message += '<p>Call duration: %s' % duration
- self.add_to_chat_history(id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status, time=start_time, skip_replication=True)
- NotificationCenter().post_notification('AudioCallLoggedToHistory', sender=self, data=TimestampedNotificationData(direction=direction, history_entry=False, remote_party=remote_uri, local_party=local_uri, check_contact=True))
- except (KeyError, TypeError):
- pass
-
- # NSURLConnection delegate method
- def connection_didReceiveAuthenticationChallenge_(self, connection, challenge):
- try:
- key = (account for account in self.last_calls_connections.keys() if self.last_calls_connections[account]['connection'] == connection).next()
- except StopIteration:
- pass
- else:
- try:
- account = AccountManager().get_account(key)
- except KeyError:
- pass
- else:
- try:
- self.last_calls_connections[key]['authRequestCount'] += 1
- except KeyError:
- self.last_calls_connections[key]['authRequestCount'] = 1
-
- if self.last_calls_connections[key]['authRequestCount'] < 2:
- credential = NSURLCredential.credentialWithUser_password_persistence_(account.id, account.server.web_password or account.auth.password, NSURLCredentialPersistenceNone)
- challenge.sender().useCredential_forAuthenticationChallenge_(credential, challenge)
- @run_in_gui_thread
- def show_web_alert_page(self, session):
- # open web page with caller information
- if NSApp.delegate().applicationName == 'Blink Lite':
- return
- try:
- session_controller = (controller for controller in self.sessionControllers if controller.session == session).next()
- except StopIteration:
- return
- if session.account is not BonjourAccount() and session.account.web_alert.alert_url:
- url = unicode(session.account.web_alert.alert_url)
-
- replace_caller = urllib.urlencode({'x:': '%s@%s' % (session.remote_identity.uri.user, session.remote_identity.uri.host)})
- caller_key = replace_caller[5:]
- url = url.replace('$caller_party', caller_key)
-
- replace_username = urllib.urlencode({'x:': '%s' % session.remote_identity.uri.user})
- url = url.replace('$caller_username', replace_username[5:])
-
- replace_account = urllib.urlencode({'x:': '%s' % session.account.id})
- url = url.replace('$called_party', replace_account[5:])
-
- settings = SIPSimpleSettings()
-
- if settings.gui.use_default_web_browser_for_alerts:
- session_controller.log_info(u"Opening HTTP URL in default browser %s"% url)
- NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
- else:
- session_controller.log_info(u"Opening HTTP URL %s"% url)
- if not SIPManager()._delegate.accountSettingsPanels.has_key(caller_key):
- SIPManager()._delegate.accountSettingsPanels[caller_key] = AccountSettings.createWithOwner_(self)
- SIPManager()._delegate.accountSettingsPanels[caller_key].showIncomingCall(session, url)
- @run_in_gui_thread
- def get_last_calls(self, account):
- if not account.server.settings_url:
- return
- query_string = "action=get_history"
- url = urlparse.urlunparse(account.server.settings_url[:4] + (query_string,) + account.server.settings_url[5:])
- nsurl = NSURL.URLWithString_(url)
- request = NSURLRequest.requestWithURL_cachePolicy_timeoutInterval_(nsurl, NSURLRequestReloadIgnoringLocalAndRemoteCacheData, 15)
- connection = NSURLConnection.alloc().initWithRequest_delegate_(request, self)
- timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(300, self, "updateGetCallsTimer:", None, True)
- NSRunLoop.currentRunLoop().addTimer_forMode_(timer, NSRunLoopCommonModes)
- NSRunLoop.currentRunLoop().addTimer_forMode_(timer, NSEventTrackin…
Large files files are truncated, but you can click here to view the full file