diff options
author | Youness Alaoui <youness.alaoui@collabora.co.uk> | 2009-03-12 16:58:39 -0400 |
---|---|---|
committer | Youness Alaoui <youness.alaoui@collabora.co.uk> | 2009-03-12 16:58:39 -0400 |
commit | a9b468fe482a9a9ac5512683a1b9b0cb7de4dc14 (patch) | |
tree | 03cc72198ff3bd0fcf2e374163f01914f418c8f8 | |
parent | 16b62be40a55b67232a23ca3b5479997bdc6b487 (diff) |
Adding Richard Spiers and my own code for handling webcam requests
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | pymsn/client.py | 12 | ||||
-rw-r--r-- | pymsn/msnp2p/SLP.py | 46 | ||||
-rw-r--r-- | pymsn/msnp2p/constants.py | 2 | ||||
-rw-r--r-- | pymsn/msnp2p/session.py | 430 | ||||
-rw-r--r-- | pymsn/msnp2p/session_manager.py | 32 | ||||
-rw-r--r-- | pymsn/msnp2p/transport/switchboard.py | 1 | ||||
-rw-r--r-- | pymsn/p2p.py | 44 |
8 files changed, 540 insertions, 28 deletions
@@ -1,3 +1,4 @@ Ali Sabil علي سبيل <ali.sabil@gmail.com> Johann Prieur <johann.prieur@gmail.com> Ole André Vadla Ravnås <oleavr@gmail.com> +Richard Spiers <richard.spiers@gmail.com> diff --git a/pymsn/client.py b/pymsn/client.py index a77d684..6e2782a 100644 --- a/pymsn/client.py +++ b/pymsn/client.py @@ -5,6 +5,7 @@ # Copyright (C) 2005-2007 Ali Sabil <ali.sabil@gmail.com> # Copyright (C) 2006-2007 Ole André Vadla Ravnås <oleavr@gmail.com> # Copyright (C) 2007 Johann Prieur <johann.prieur@gmail.com> +# Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -90,7 +91,7 @@ from pymsn.util.decorator import rw_property from pymsn.transport import * from pymsn.switchboard_manager import SwitchboardManager from pymsn.msnp2p import P2PSessionManager -from pymsn.p2p import MSNObjectStore +from pymsn.p2p import MSNObjectStore, WebcamHandler from pymsn.conversation import SwitchboardConversation, \ ExternalNetworkConversation from pymsn.event import ClientState, ClientErrorType, \ @@ -137,7 +138,12 @@ class Client(EventsDispatcher): self._switchboard_manager.register_handler(SwitchboardConversation) self._p2p_session_manager = P2PSessionManager(self) + self._webcam_handler = WebcamHandler(self) + self._p2p_session_manager.register_handler(self._webcam_handler) + self._msn_object_store = MSNObjectStore(self) + + self._external_conversations = {} @@ -160,6 +166,10 @@ class Client(EventsDispatcher): return self._msn_object_store @property + def webcam_handler(self): + return self._webcam_handler + + @property def profile(self): """The profile of the current user @type: L{User<pymsn.profile.Profile>}""" diff --git a/pymsn/msnp2p/SLP.py b/pymsn/msnp2p/SLP.py index c285f83..6453cf6 100644 --- a/pymsn/msnp2p/SLP.py +++ b/pymsn/msnp2p/SLP.py @@ -3,6 +3,7 @@ # pymsn - a python client library for Msn # # Copyright (C) 2007 Ali Sabil <ali.sabil@gmail.com> +# Copyright (C) 2008 Richard Spiers<richard.spiers@gmail.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,7 +27,7 @@ import base64 __all__ = ['SLPMessage', 'SLPRequestMessage', 'SLPResponseMessage', 'SLPMessageBody', 'SLPNullBody', 'SLPSessionRequestBody', - 'SLPSessionCloseBody', 'SLPSessionFailureResponseBody'] + 'SLPSessionCloseBody', 'SLPSessionFailureResponseBody', 'SLPTransferRequestBody'] class SLPMessage(HTTPMessage): @@ -236,7 +237,7 @@ SLPMessageBody.register_content(SLPContentType.NULL, SLPNullBody) class SLPSessionRequestBody(SLPMessageBody): def __init__(self, euf_guid=None, app_id=None, context=None, session_id=None, s_channel_state=0, capabilities_flags=1): - SLPMessageBody.__init__(self, SLPContentType.SESSION_REQUEST, + SLPMessageBody.__init__(self,SLPContentType.SESSION_REQUEST, session_id, s_channel_state, capabilities_flags) if euf_guid is not None: @@ -273,6 +274,47 @@ class SLPSessionRequestBody(SLPMessageBody): SLPMessageBody.register_content(SLPContentType.SESSION_REQUEST, SLPSessionRequestBody) +class SLPTransferRequestBody(SLPMessageBody): + def __init__(self, euf_guid=None, app_id=None, context=None, + session_id=None, s_channel_state=None, capabilities_flags=None): + SLPMessageBody.__init__(self,SLPContentType.TRANSFER_REQUEST, + session_id, s_channel_state, capabilities_flags) + + #Examples of headers that might be useful + + #self.add_header("Bridges","TRUDPv1 TCPv1") + #self.add_header("NetID",0) + #self.add_header("Conn-Type","Firewall") + #self.add_header("UPnPNat","false") + #self.add_header("ICF","false") + + + @property + def euf_guid(self): + try: + return self.get_header("EUF-GUID") + except (KeyError, ValueError): + return "" + + @property + def context(self): + try: + context = self.get_header("Context") + # Make the b64 string correct by append '=' to get a length as a + # multiple of 4. Kopete client seems to use incorrect b64 strings. + context += '=' * (len(context) % 4) + return base64.b64decode(context) + except KeyError: + return None + + @property + def application_id(self): + try: + return int(self.get_header("AppID")) + except (KeyError, ValueError): + return 0 + +SLPMessageBody.register_content(SLPContentType.TRANSFER_REQUEST, SLPTransferRequestBody) class SLPSessionCloseBody(SLPMessageBody): def __init__(self, context=None, session_id=None, s_channel_state=0, diff --git a/pymsn/msnp2p/constants.py b/pymsn/msnp2p/constants.py index 9b42376..b0c139a 100644 --- a/pymsn/msnp2p/constants.py +++ b/pymsn/msnp2p/constants.py @@ -3,6 +3,7 @@ # pymsn - a python client library for Msn # # Copyright (C) 2007 Ole André Vadla Ravnås <oleavr@gmail.com> +# Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,6 +30,7 @@ class EufGuid(object): class ApplicationID(object): CUSTOM_EMOTICON_TRANSFER = 11 DISPLAY_PICTURE_TRANSFER = 12 + WEBCAM = 4 # Do we need a better name ? class SLPContentType(object): """MSNSLP content types""" diff --git a/pymsn/msnp2p/session.py b/pymsn/msnp2p/session.py index 6332519..9f69886 100644 --- a/pymsn/msnp2p/session.py +++ b/pymsn/msnp2p/session.py @@ -3,6 +3,7 @@ # pymsn - a python client library for Msn # # Copyright (C) 2007 Ali Sabil <ali.sabil@gmail.com> +# Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,14 +23,20 @@ from pymsn.msnp2p.constants import * from pymsn.msnp2p.SLP import * from pymsn.msnp2p.transport import * from pymsn.msnp2p.exceptions import * +import pymsn.util.element_tree as ElementTree +import struct import pymsn.util.guid as guid import gobject import base64 import random +#Farsight/GStreamer imports +import pygst +pygst.require('0.10') +import farsight, gst, gobject, sys -__all__ = ['OutgoingP2PSession'] +__all__ = ['IncomingP2PSession', 'OutgoingP2PSession','WebcamSessionRecv,WebcamSessionSend'] MAX_INT32 = 0x7fffffff MAX_INT16 = 0x7fff @@ -79,7 +86,6 @@ class P2PSession(gobject.GObject): def _close(self): body = SLPSessionCloseBody() - self._cseq = 0 self._branch = "{%s}" % guid.generate_guid() message = SLPRequestMessage(SLPRequestMethod.BYE, @@ -162,14 +168,15 @@ class IncomingP2PSession(P2PSession): raise SLPError("Incoming INVITE without context") def accept(self, data_file): - gobject.idle_add(self._start_transfer, data_file) - + #Made an edit here, removing the file transfer code + #gobject.idle_add(self._start_transfer, data_file) + self._respond(200) + def reject(self): self._respond(603) def _respond(self, status_code): - body = SLPSessionRequestBody(session_id=self._id) - + body = SLPSessionRequestBody(session_id=self._id,capabilities_flags=None,s_channel_state=None) self._cseq += 1 response = SLPResponseMessage(status_code, to=self._peer.account, @@ -186,8 +193,7 @@ class IncomingP2PSession(P2PSession): self._send_p2p_data(data_file) return False - -class OutgoingP2PSession(P2PSession): +class OutgoingP2PSession(P2PSession): def __init__(self, session_manager, peer, context, euf_guid, application_id): P2PSession.__init__(self, session_manager, peer, euf_guid, application_id) gobject.idle_add(self._invite, str(context)) @@ -208,4 +214,412 @@ class OutgoingP2PSession(P2PSession): message.body = body self._send_p2p_data(message) return False + +class WebcamSession(P2PSession): #Based off P2PSession, rework to base off OutgoingSession? + + def __init__(self, producer, session_manager, peer, \ + euf_guid, application_id, \ + session_id = None, message = None): + P2PSession.__init__(self, session_manager, peer, \ + euf_guid, application_id) + + self._producer = producer + if session_id is None: + self._id = _generate_id() + else: + self._id = session_id + + if message is not None: + self._call_id = message.call_id + self._cseq = message.cseq + self._branch = message.branch + + self._pipeline = None + self._sent_syn = False + self._session_manager._register_session(self) + + + def invite(self): + context = "{B8BE70DE-E2CA-4400-AE03-88FF85B9F4E8}" + body = SLPSessionRequestBody(EufGuid.MEDIA_SESSION,ApplicationID.WEBCAM, + context.decode('ascii').encode('utf-16_le'), self._id) + message = SLPRequestMessage(SLPRequestMethod.INVITE, + "MSNMSGR:" + self._peer.account, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + branch=self._branch, + cseq=self._cseq, + call_id=self._call_id) + message.body = body + + self._call_id = message.call_id + self._cseq = message.cseq + self._branch = message.branch + self._send_p2p_data(message) + + def _respond(self, status_code): + body = SLPSessionRequestBody(session_id=self._id,capabilities_flags=None,s_channel_state=None) + self._cseq += 1 + response = SLPResponseMessage(status_code, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + cseq=self._cseq, + branch=self._branch, + call_id=self._call_id) + response.body = body + self._send_p2p_data(response) + + + def accept(self): + temp_application_id = self._application_id + self._application_id = 0 + self._respond(200) + self.send_transreq() + self._application_id = temp_application_id + def _send_req(self): + body = SLPSessionRequestBody(session_id=self._id, \ + capabilities_flags=None, \ + s_channel_state=None) + self._cseq += 1 + response = SLPResponseMessage(status_code, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + cseq=self._cseq, + branch=self._branch, + call_id=self._call_id) + response.body = body + self._send_p2p_data(response) + + def send_transreq(self): + self._cseq=0 + body = SLPTransferRequestBody(self._euf_guid, self._application_id,None, + None) + message = SLPRequestMessage(SLPRequestMethod.INVITE, + "MSNMSGR:" + self._peer.account, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + branch=self._branch, + cseq=self._cseq, + call_id=self._call_id) + message.body = body + self._application_id=0 + self._send_p2p_data(message) + self._application_id=4 + self.send_binary_syn() + + + def _on_blob_received(self, blob): + if blob.session_id == 0: + # FIXME: handle the signaling correctly + # Determine if it actually is a transreq or not + # send 603 + return + data = blob.data.read() + if not self._sent_syn: + self.send_binary_syn() #Send 603 first ? + if '\x00s\x00y\x00n\x00\x00\x00' in data: + self.send_binary_ack() + elif '\x00a\x00c\x00k\x00\x00\x00' in data: + if self._producer: + self._setup_conference(farsight.DIRECTION_SEND) + pass + elif ('\x00<\x00p\x00r\x00o\x00d\x00u\x00c\x00e\x00r\x00>\x00' in data) \ + or ('\x00<\x00v\x00i\x00e\x00w\x00e\x00r\x00>\x00' in data): + self._handle_xml(blob) + + def send_binary_syn(self): + syn='\x80\x11\x11\x01\x08\x00\x08\x00\x00\x00s\x00y\x00n\x00\x00\x00' + footer='\x00\x00\x00\x04' + self._send_p2p_data(syn) + self._sent_syn = True + + def send_binary_ack(self): + ack='\x80\xea\x00\x00\x08\x00\x08\x00\x00\x00a\x00c\x00k\x00\x00\x00' + footer='\x00\x00\x00\x04' + self._send_p2p_data(ack) + + def send_binary_viewer_data(self): + data = '\x80\xec\xc7\x03\x08\x00&\x00\x00\x00r\x00e\x00c\x00e\x00i\x00v\x00e\x00d\x00V\x00i\x00e\x00w\x00e\x00r\x00D\x00a\x00t\x00a\x00\x00\x00' + footer='\x00\x00\x00\x04' + self._send_p2p_data(data) + + def _send_xml(self): + session_id = self._fssession.get_property("session-id") + if self._producer: + s = "<producer>" + else: + s = "<viewer>" + s += "<version>2.0</version><rid>%s</rid><session>%u</session><ctypes>0</ctypes><cpu>2010</cpu>" % \ + (self._local_candidates[0].foundation, + session_id) + + s += "<tcp>" + s += "<tcpport>%(port)u</tcpport>\t\t\t\t\t\t\t\t <tcplocalport>%(port)u</tcplocalport>\t\t\t\t\t\t\t\t <tcpexternalport>%(port)u</tcpexternalport>" \ + % { "port" : self._local_candidates[0].port } + for i, candidate in enumerate(self._local_candidates): + s += "<tcpipaddress%u>%s</tcpipaddress%u>" % (i + 1, candidate.ip, i + 1) + s += "</tcp>" + s += "<codec></codec><channelmode>2</channelmode>" + + if self._producer: + s += "</producer>" + else: + s += "</viewer>" + s += "\r\n\r\n" + message_bytes = s.encode("utf-16-le") + "\x00\x00" + id = (_generate_id() << 8) | 0x80 + header = struct.pack("<LHL", id, 8, len(message_bytes)) + self._send_p2p_data(header+message_bytes) + if self._producer is False: + self._stream.set_remote_candidates (self._remote_candidates) + self._pipeline.set_state(gst.STATE_PLAYING) + self._remote_candidates = None + + + def _handle_xml(self,blob): + blob.data.seek(10, 0) + data = blob.data.read() + datastr = str(data).replace("\000","") + message = unicode(data, "utf-16-le").rstrip("\x00") + tree = ElementTree.fromstring(datastr) + ips = [] + ports = [] + for node in tree.findall("tcp/*"): + if node.tag == "tcpport": + ports.append(int(node.text)) + elif node.tag.startswith("tcpipaddress"): + ips.append(node.text) + rid = tree.find("rid").text + session_id = int(tree.find("session").text) + + candidates = [] + for ip in ips: + for port in ports: + candidate = farsight.Candidate() + candidate.ip = ip + candidate.port = port + candidate.ttl = 1 + candidate.foundation = rid + candidates.append(candidate) + + # Signalling is done, now pass it off to the handler to control it further + print "Received xml %s" % message + if self._producer: + self.send_binary_viewer_data() + self._stream.set_remote_candidates (candidates) + self._pipeline.set_state(gst.STATE_PLAYING) + else: + self._remote_candidates = candidates + self._setup_conference(farsight.DIRECTION_RECV, session_id) + + + + def _on_bus_message(self, bus, msg): + if msg.type == gst.MESSAGE_ELEMENT: + s = msg.structure + if s.has_name("farsight-new-local-candidate"): + self._local_candidates.append(s["candidate"]) + if s.has_name("farsight-local-candidates-prepared"): + self._send_xml() + + print "Received message on bus : %s" % s + + def _src_pad_added(self, stream, pad, codec): + print "SOURCE PAD ADDED" + self._videosink = self.make_video_sink() + self._pipeline.add (self._videosink) + self._videosink.set_state(gst.STATE_PLAYING) + pad.link(self._videosink.get_pad("sink")) + + def _setup_conference(self, direction, session_id=0): + self._local_candidates = [] + self._pipeline = gst.Pipeline() + bus = self._pipeline.get_bus() + bus.add_signal_watch() + bus.connect("message", self._on_bus_message) + + self._conference = gst.element_factory_make ("fsmsnconference") + + # For fututre work, when implementing the msn video conferencing + # the variables below should be renamed to be type specific, + # i.e. self._session_video etc + self._pipeline.add (self._conference) + self._fssession = \ + self._conference.new_session (farsight.MEDIA_TYPE_VIDEO) + if session_id != 0: + self._fssession.set_property("session-id", session_id) + + participant = self._conference.new_participant (self._peer.account) + if direction == farsight.DIRECTION_SEND: + self._stream = \ + self._fssession.new_stream (participant, \ + farsight.DIRECTION_SEND, \ + None) + self._videosrc = self.make_video_source() + self._pipeline.add (self._videosrc) + self._videosrc.get_pad("src"). \ + link(self._fssession.get_property ("sink-pad")) + self._videosrc.set_state(gst.STATE_PLAYING) + elif direction == farsight.DIRECTION_RECV: + self._stream = \ + self._fssession.new_stream (participant, \ + farsight.DIRECTION_RECV, \ + None) + self._stream.connect("src-pad-added", self._src_pad_added) + else: + print "Error not send or receive direction in conference setup" + return + + def make_video_source(self, name="videotestsrc"): + "Make a bin with a video source in it, defaulting to first webcamera " + bin = gst.Bin("videosrc") + src = gst.element_factory_make(name, name) + bin.add(src) + colorspace = gst.element_factory_make("ffmpegcolorspace") + bin.add(colorspace) + videoscale = gst.element_factory_make("videoscale") + bin.add(videoscale) + src.link(colorspace) + colorspace.link(videoscale) + bin.add_pad(gst.GhostPad("src", videoscale.get_pad("src"))) + return bin + + def make_video_sink(self, async=False): + "Make a bin with a video sink in it, that will be displayed on xid." + bin = gst.Bin("videosink") + sink = gst.element_factory_make("ximagesink", "imagesink") + sink.set_property("sync", async) + sink.set_property("async", async) + bin.add(sink) + colorspace = gst.element_factory_make("ffmpegcolorspace") + bin.add(colorspace) + videoscale = gst.element_factory_make("videoscale") + bin.add(videoscale) + videoscale.link(colorspace) + colorspace.link(sink) + bin.add_pad(gst.GhostPad("sink", videoscale.get_pad("sink"))) + #sink.set_data("xid", xid) #Future work - proper gui place for imagesink ? + return bin + +class WebcamSessionRecv(P2PSession): + + def __init__(self, session_manager,peer,euf_guid, application_id,session_id,message,): + P2PSession.__init__(self, session_manager, peer, euf_guid, application_id) + + self._id = session_id + self.message = message + self._pipeline = None + self._call_id = self.message.call_id + self._state = 0 + self._cseq = self.message.cseq + self._branch = self.message.branch + + self._local_ip = self._session_manager._client._webcam_handler.local_ip + self._local_port = self._session_manager._client._webcam_handler.local_port + + def _send_req(self): + body = SLPSessionRequestBody(session_id=self._id,capabilities_flags=None,s_channel_state=None) + self._cseq += 1 + response = SLPResponseMessage(status_code, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + cseq=self._cseq, + branch=self._branch, + call_id=self._call_id) + response.body = body + self._send_p2p_data(response) + + def _respond(self, status_code): + body = SLPSessionRequestBody(session_id=self._id,capabilities_flags=None,s_channel_state=None) + self._cseq += 1 + response = SLPResponseMessage(status_code, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + cseq=self._cseq, + branch=self._branch, + call_id=self._call_id) + response.body = body + self._send_p2p_data(response) + + def send_transreq(self): + self._cseq=0 + body = SLPTransferRequestBody(self._euf_guid, self._application_id,None, + None) + message = SLPRequestMessage(SLPRequestMethod.INVITE, + "MSNMSGR:" + self._peer.account, + to=self._peer.account, + frm=self._session_manager._client.profile.account, + branch=self._branch, + cseq=self._cseq, + call_id=self._call_id) + message.body = body + self._application_id=0 + self._send_p2p_data(message) + self._application_id=4 + + def _on_blob_received(self, blob): + if blob.session_id == 0: + # FIXME: handle the signaling correctly + return + data = blob.data.read() + if '\x00s\x00y\x00n\x00\x00\x00' in data: + self.send_binary_syn() + self._state +=1 + elif '\x00a\x00c\x00k\x00\x00\x00' in data: + self.send_binary_ack() + self._state +=1 + elif (self._state == 2): + self._handle_xml(blob) + + def send_binary_syn(self): + syn='\x80\x11\x11\x01\x08\x00\x08\x00\x00\x00s\x00y\x00n\x00\x00\x00' + footer='\x00\x00\x00\x04' + self._send_p2p_data(syn) + + def send_binary_ack(self): + ack='\x80\xea\x00\x00\x08\x00\x08\x00\x00\x00a\x00c\x00k\x00\x00\x00' + footer='\x00\x00\x00\x04' + self._send_p2p_data(ack) + + def _handle_xml(self,blob): + + local_ip = self._local_ip + local_port = self._local_port + self._state = 0 + blob.data.seek(10, 0) + data = blob.data.read() + datastr = str(data).replace("\000","") + message = unicode(data, "utf-16-le").rstrip("\x00") + tree = ElementTree.fromstring(datastr) + self.remote_ips = [] + self.port = -1 + for node in tree.findall("tcp/*"): + if node.tag == "tcpport": + self.port = int(node.text) + elif node.tag.startswith("tcpipaddress"): + self.remote_ips.append(node.text) + self._remote_rid = int(tree.find("rid").text) + self._vid_session = int(tree.find("session").text) + self._local_rid = random.randint(100, 200) + if self._local_rid == self._remote_rid: + self._local_rid += 2 + + s = "<viewer>" + s += "<version>2.0</version><rid>%u</rid><session>%u</session><ctypes>0</ctypes><cpu>2010</cpu>" % \ + (self._local_rid,self._vid_session) + + s += "<tcp>" + s += "<tcpport>%(port)u</tcpport>\t\t\t\t\t\t\t\t <tcplocalport>%(port)u</tcplocalport>\t\t\t\t\t\t\t\t <tcpexternalport>%(port)u</tcpexternalport>" \ + % { "port" : local_port } + for i, ip in enumerate(local_ip): + s += "<tcpipaddress%u>%s</tcpipaddress%u>" % (i + 1, ip, i + 1) + s += "</tcp>" + s += "<codec></codec><channelmode>2</channelmode>" + s += "</viewer>\r\n\r\n" + message_bytes = s.encode("utf-16-le") + "\x00\x00" + id = (_generate_id() << 8) | 0x80 + header = struct.pack("<LHL", id, 8, len(message_bytes)) + self._send_p2p_data(header+message_bytes) + self._session_manager._client._webcam_handler.setup_multimedia(self,farsight.DIRECTION_RECV ) + diff --git a/pymsn/msnp2p/session_manager.py b/pymsn/msnp2p/session_manager.py index 6f9b027..87c4087 100644 --- a/pymsn/msnp2p/session_manager.py +++ b/pymsn/msnp2p/session_manager.py @@ -3,6 +3,7 @@ # pymsn - a python client library for Msn # # Copyright (C) 2007 Ali Sabil <ali.sabil@gmail.com> +# Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -47,12 +48,16 @@ class P2PSessionManager(gobject.GObject): self._client = client self._sessions = weakref.WeakValueDictionary() # session_id => session + self._handlers = [] self._transport_manager = P2PTransportManager(self._client) self._transport_manager.connect("blob-received", lambda tr, blob: self._on_blob_received(blob)) self._transport_manager.connect("blob-sent", lambda tr, blob: self._on_blob_sent(blob)) - + + def register_handler(self, handler_class): + self._handlers.append(handler_class) + def _register_session(self, session): self._sessions[session.id] = session @@ -105,6 +110,7 @@ class P2PSessionManager(gobject.GObject): # This means that we received a data packet for an unknown session # We must RESET the session just like the official client does # TODO send a TLP + logger.error("SLPSessionError") return new_session = session is None @@ -128,6 +134,9 @@ class P2PSessionManager(gobject.GObject): # an existing session if session_id == 0: # TODO send a 500 internal error + logger.error("Session_id == 0") + + return # If there was no session then create one only if it's an INVITE @@ -148,27 +157,24 @@ class P2PSessionManager(gobject.GObject): # Create the session depending on the type of the message if isinstance(message.body, SLPSessionRequestBody): try: - session = IncomingP2PSession(self, peer, session_id, message) + for handler in self._handlers: + if handler._can_handle_euf_guid(message): + session = handler._create_new_recv_session(peer,session_id,message) + self._register_session(session) + except SLPError: #TODO: answer with a 603 Decline ? - return - #elif isinstance(message.body, SLPTransferRequestBody): - # pass + logger.error("SLPError") + return else: - logger.warning('Received initial blob with SessionID=0 and non INVITE SLP data') + logger.warning('Received initial blob with SessionID=0 and non INVITE SLP data') #FIXME - pick up properly on the transreq + #TODO: answer with a 500 Internal Error return None # The session should be notified of this blob session._on_blob_received(blob) - # emit the new session signal only after the session got notified of this blob - # if one of the functions connected to the signal ends the session it needs to - # first know its initial INVITE before knowing about it's BYE - if new_session: - logger.info("Creating new incomming session") - self.emit("incoming-session", session) - def _on_blob_sent(self, blob): session = None try: diff --git a/pymsn/msnp2p/transport/switchboard.py b/pymsn/msnp2p/transport/switchboard.py index 98ecfe8..867c8b4 100644 --- a/pymsn/msnp2p/transport/switchboard.py +++ b/pymsn/msnp2p/transport/switchboard.py @@ -37,6 +37,7 @@ class SwitchboardP2PTransport(BaseP2PTransport, SwitchboardClient): SwitchboardClient.__init__(self, client, contacts) BaseP2PTransport.__init__(self, transport_manager, "switchboard") + def close(self): BaseP2PTransport.close(self) self._leave() diff --git a/pymsn/p2p.py b/pymsn/p2p.py index d6aa1da..dd16f00 100644 --- a/pymsn/p2p.py +++ b/pymsn/p2p.py @@ -4,6 +4,7 @@ # # Copyright (C) 2007 Ali Sabil <ali.sabil@gmail.com> # Copyright (C) 2007 Johann Prieur <johann.prieur@gmail.com> +# Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,7 +25,7 @@ This module contains the classes needed to engage in a peer to peer transfer with a contact. @group MSNObject: MSNObjectStore, MSNObject, MSNObjectType @sort: MSNObjectStore, MSNObject, MSNObjectType""" - +from msnp2p.session import IncomingP2PSession, WebcamSession from msnp2p import OutgoingP2PSession, EufGuid, ApplicationID from msnp2p.exceptions import ParseError from profile import NetworkID @@ -38,7 +39,12 @@ import base64 import sha import logging -__all__ = ['MSNObjectType', 'MSNObject', 'MSNObjectStore'] +#Farsight/GST imports +import pygst +pygst.require('0.10') +import farsight, gst, gobject, sys + +__all__ = ['MSNObjectType', 'MSNObject', 'MSNObjectStore', 'WebcamHandler'] logger = logging.getLogger('p2p') @@ -204,8 +210,9 @@ class MSNObjectStore(object): self._outgoing_sessions = {} # session => (handle_id, callback, errback) self._incoming_sessions = {} self._published_objects = set() - self._client._p2p_session_manager.connect("incoming-session", - self._incoming_session_received) + # Made an edit here - do we really want to call MSNObjectStore on each new session ? + #self._client._p2p_session_manager.connect("incoming-session", + # self._incoming_session_received) def request(self, msn_object, callback, errback=None): if msn_object._data is not None: @@ -262,3 +269,32 @@ class MSNObjectStore(object): session.disconnect(handle_id) del self._incoming_sessions[session] +class WebcamHandler(object): + + def __init__(self, client): + self._client = client + self._sessions = [] + + def _can_handle_euf_guid(self,message): + euf_guid = message.body.euf_guid + if (euf_guid == EufGuid.MEDIA_SESSION): + return True + else: + return False + + def _create_new_recv_session(self, peer, session_id, message): + session = WebcamSession(False, self._client._p2p_session_manager, \ + peer, message.body.euf_guid, \ + ApplicationID.WEBCAM, session_id) + self._sessions.append(session) + session.accept() + return session + + def _create_new_send_session(self, peer): + print "Creating New Send Session" + session = WebcamSession(True, self._client._p2p_session_manager, \ + peer, EufGuid.MEDIA_SESSION, \ + ApplicationID.WEBCAM) + self._sessions.append(session) + session.invite() + return session |