Package papyon :: Module p2p

Source Code for Module papyon.p2p

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # papyon - a python client library for Msn 
  4  # 
  5  # Copyright (C) 2007 Ali Sabil <ali.sabil@gmail.com> 
  6  # Copyright (C) 2007 Johann Prieur <johann.prieur@gmail.com> 
  7  # Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com> 
  8  # 
  9  # This program is free software; you can redistribute it and/or modify 
 10  # it under the terms of the GNU General Public License as published by 
 11  # the Free Software Foundation; either version 2 of the License, or 
 12  # (at your option) any later version. 
 13  # 
 14  # This program is distributed in the hope that it will be useful, 
 15  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 16  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 17  # GNU General Public License for more details. 
 18  # 
 19  # You should have received a copy of the GNU General Public License 
 20  # along with this program; if not, write to the Free Software 
 21  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 22   
 23  """P2P 
 24  This module contains the classes needed to engage in a peer to peer transfer 
 25  with a contact. 
 26      @group MSNObject: MSNObjectStore, MSNObject, MSNObjectType 
 27      @sort: MSNObjectStore, MSNObject, MSNObjectType""" 
 28  from msnp2p.msnobject import MSNObjectSession 
 29  from msnp2p.webcam import WebcamSession 
 30  from msnp2p import EufGuid, ApplicationID 
 31  from msnp2p.exceptions import ParseError 
 32  from profile import NetworkID 
 33   
 34  import papyon.util.element_tree as ElementTree 
 35  import papyon.util.string_io as StringIO 
 36   
 37  import gobject 
 38  import xml.sax.saxutils as xml 
 39  import urllib 
 40  import base64 
 41  import hashlib 
 42  import logging 
 43   
 44  __all__ = ['MSNObjectType', 'MSNObject', 'MSNObjectStore', 'WebcamHandler'] 
 45   
 46  logger = logging.getLogger('p2p') 
47 48 -class MSNObjectType(object):
49 """Represent the various MSNObject types""" 50 51 CUSTOM_EMOTICON = 2 52 "Custom smiley" 53 DISPLAY_PICTURE = 3 54 "Display picture" 55 BACKGROUND_PICTURE = 5 56 "Background picture" 57 DYNAMIC_DISPLAY_PICTURE = 7 58 "Dynamic display picture" 59 WINK = 8 60 "Wink" 61 VOICE_CLIP = 11 62 "Void clip" 63 SAVED_STATE_PROPERTY = 12 64 "Saved state property" 65 LOCATION = 14 66 "Location"
67
68 -class MSNObject(object):
69 "Represents an MSNObject."
70 - def __init__(self, creator, size, type, location, friendly, 71 shad=None, shac=None, data=None):
72 """Initializer 73 74 @param creator: the creator of this MSNObject 75 @type creator: utf-8 encoded string representing the account 76 77 @param size: the total size of the data represented by this MSNObject 78 @type size: int 79 80 @param type: the type of the data 81 @type type: L{MSNObjectType} 82 83 @param location: a filename for the MSNObject 84 @type location: utf-8 encoded string 85 86 @param friendly: a friendly name for the MSNObject 87 @type friendly: utf-8 encoded string 88 89 @param shad: sha1 digest of the data 90 91 @param shac: sha1 digest of the MSNObject itself 92 93 @param data: file object to the data represented by this MSNObject 94 @type data: File 95 """ 96 self._creator = creator 97 self._size = size 98 self._type = type 99 self._location = location 100 self._friendly = friendly 101 self._checksum_sha = shac 102 103 if shad is None: 104 if data is None: 105 raise NotImplementedError 106 shad = self.__compute_data_hash(data) 107 self._data_sha = shad 108 self.__data = data 109 self._repr = None
110
111 - def __ne__(self, other):
112 return not (self == other)
113
114 - def __eq__(self, other):
115 if other == None: 116 return False 117 return other._type == self._type and \ 118 other._data_sha == self._data_sha
119
120 - def __hash__(self):
121 return hash(str(self._type) + self._data_sha)
122
123 - def __set_data(self, data):
124 if self._data_sha != self.__compute_data_hash(data): 125 logger.warning("Received data doesn't match the MSNObject data hash.") 126 return 127 128 old_pos = data.tell() 129 data.seek(0, 2) 130 self._size = data.tell() 131 data.seek(old_pos, 0) 132 133 self.__data = data 134 self._checksum_sha = self.__compute_checksum()
135 - def __get_data(self):
136 return self.__data
137 _data = property(__get_data, __set_data) 138 139 @staticmethod
140 - def parse(client, xml_data):
141 data = StringIO.StringIO(xml_data) 142 try: 143 element = ElementTree.parse(data).getroot().attrib 144 except: 145 raise ParseError('Invalid MSNObject') 146 147 try: 148 creator = client.address_book.contacts.\ 149 search_by_account(element["Creator"]).\ 150 search_by_network_id(NetworkID.MSN)[0] 151 except IndexError: 152 creator = None 153 154 size = int(element["Size"]) 155 type = int(element["Type"]) 156 location = xml.unescape(element["Location"]) 157 friendly = base64.b64decode(xml.unescape(element["Friendly"])) 158 shad = element.get("SHA1D", None) 159 if shad is not None: 160 shad = base64.b64decode(shad) 161 shac = element.get("SHA1C", None) 162 if shac is not None: 163 shac = base64.b64decode(shac) 164 165 result = MSNObject(creator, size, type, location, \ 166 friendly, shad, shac) 167 result._repr = xml_data 168 return result
169
170 - def __compute_data_hash(self, data):
171 digest = hashlib.sha1() 172 data.seek(0, 0) 173 read_data = data.read(1024) 174 while len(read_data) > 0: 175 digest.update(read_data) 176 read_data = data.read(1024) 177 data.seek(0, 0) 178 return digest.digest()
179
180 - def __compute_checksum(self):
181 input = "Creator%sSize%sType%sLocation%sFriendly%sSHA1D%s" % \ 182 (self._creator.account, str(self._size), str(self._type),\ 183 str(self._location), base64.b64encode(self._friendly), \ 184 base64.b64encode(self._data_sha)) 185 return hashlib.sha1(input).hexdigest()
186
187 - def __str__(self):
188 return self.__repr__()
189
190 - def __repr__(self):
191 if self._repr is not None: 192 return self._repr 193 dump = "<msnobj Creator=%s Type=%s SHA1D=%s Size=%s Location=%s Friendly=%s/>" % \ 194 (xml.quoteattr(self._creator.account), 195 xml.quoteattr(str(self._type)), 196 xml.quoteattr(base64.b64encode(self._data_sha)), 197 xml.quoteattr(str(self._size)), 198 xml.quoteattr(str(self._location)), 199 xml.quoteattr(base64.b64encode(self._friendly))) 200 return dump
201
202 203 -class MSNObjectStore(object):
204
205 - def __init__(self, client):
206 self._client = client 207 self._outgoing_sessions = {} # session => (handle_id, callback, errback) 208 self._incoming_sessions = {} 209 self._published_objects = set()
210
211 - def _can_handle_message (self, message):
212 euf_guid = message.body.euf_guid 213 if euf_guid == EufGuid.MSN_OBJECT: 214 return True 215 else: 216 return False
217
218 - def _handle_message(self, peer, message):
219 session = MSNObjectSession(self._client._p2p_session_manager, 220 peer, message.body.application_id, message) 221 222 handle_id = session.connect("transfer-completed", 223 self._incoming_session_transfer_completed) 224 self._incoming_sessions[session] = handle_id 225 try: 226 msn_object = MSNObject.parse(self._client, session._context) 227 except ParseError: 228 session.reject() 229 return 230 for obj in self._published_objects: 231 if obj._data_sha == msn_object._data_sha: 232 session.accept(obj._data) 233 return session 234 session.reject()
235
236 - def request(self, msn_object, callback, errback=None):
237 if msn_object._data is not None: 238 callback[0](msn_object, *callback[1:]) 239 240 if msn_object._type == MSNObjectType.CUSTOM_EMOTICON: 241 application_id = ApplicationID.CUSTOM_EMOTICON_TRANSFER 242 elif msn_object._type == MSNObjectType.DISPLAY_PICTURE: 243 application_id = ApplicationID.DISPLAY_PICTURE_TRANSFER 244 else: 245 raise NotImplementedError 246 247 session = MSNObjectSession(self._client._p2p_session_manager, 248 msn_object._creator, application_id) 249 handle_id = session.connect("transfer-completed", 250 self._outgoing_session_transfer_completed) 251 self._outgoing_sessions[session] = \ 252 (handle_id, callback, errback, msn_object) 253 session.invite(repr(msn_object))
254
255 - def publish(self, msn_object):
256 if msn_object._data is None: 257 logger.warning("Trying to publish an empty MSNObject") 258 else: 259 self._published_objects.add(msn_object)
260
261 - def _outgoing_session_transfer_completed(self, session, data):
262 handle_id, callback, errback, msn_object = self._outgoing_sessions[session] 263 session.disconnect(handle_id) 264 msn_object._data = data 265 266 callback[0](msn_object, *callback[1:]) 267 del self._outgoing_sessions[session]
268
269 - def _incoming_session_transfer_completed(self, session, data):
270 handle_id = self._incoming_sessions[session] 271 session.disconnect(handle_id) 272 del self._incoming_sessions[session]
273
274 -class WebcamHandler(gobject.GObject):
275 276 __gsignals__ = { 277 "session-created" : (gobject.SIGNAL_RUN_FIRST, 278 gobject.TYPE_NONE, 279 (object, bool)) 280 } 281
282 - def __init__(self, client):
283 gobject.GObject.__init__(self) 284 self._client = client 285 self._sessions = []
286
287 - def _can_handle_message (self, message):
288 euf_guid = message.body.euf_guid 289 if (euf_guid == EufGuid.MEDIA_SESSION or 290 euf_guid == EufGuid.MEDIA_RECEIVE_ONLY): 291 return True 292 else: 293 return False
294
295 - def _handle_message (self, peer, message):
296 euf_guid = message.body.euf_guid 297 if (euf_guid == EufGuid.MEDIA_SESSION): 298 producer = False 299 elif (euf_guid == EufGuid.MEDIA_RECEIVE_ONLY): 300 producer = True 301 302 session = WebcamSession(producer, self._client._p2p_session_manager, \ 303 peer, message.body.euf_guid, message) 304 self._sessions.append(session) 305 self.emit("session-created", session, producer) 306 return session
307
308 - def invite(self, peer, producer=True):
309 print "Creating New Send Session" 310 if producer: 311 euf_guid = EufGuid.MEDIA_SESSION 312 else: 313 euf_guid = EufGuid.MEDIA_RECEIVE_ONLY 314 session = WebcamSession(producer, self._client._p2p_session_manager, \ 315 peer, euf_guid) 316 self._sessions.append(session) 317 session.invite() 318 return session
319