summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYouness Alaoui <youness.alaoui@collabora.co.uk>2009-03-12 16:58:39 -0400
committerYouness Alaoui <youness.alaoui@collabora.co.uk>2009-03-12 16:58:39 -0400
commita9b468fe482a9a9ac5512683a1b9b0cb7de4dc14 (patch)
tree03cc72198ff3bd0fcf2e374163f01914f418c8f8
parent16b62be40a55b67232a23ca3b5479997bdc6b487 (diff)
Adding Richard Spiers and my own code for handling webcam requests
-rw-r--r--AUTHORS1
-rw-r--r--pymsn/client.py12
-rw-r--r--pymsn/msnp2p/SLP.py46
-rw-r--r--pymsn/msnp2p/constants.py2
-rw-r--r--pymsn/msnp2p/session.py430
-rw-r--r--pymsn/msnp2p/session_manager.py32
-rw-r--r--pymsn/msnp2p/transport/switchboard.py1
-rw-r--r--pymsn/p2p.py44
8 files changed, 540 insertions, 28 deletions
diff --git a/AUTHORS b/AUTHORS
index 24ae6c6..79f2844 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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