|
Package papyon ::
Module p2p
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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')
67
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
112 return not (self == other)
113
115 if other == None:
116 return False
117 return other._type == self._type and \
118 other._data_sha == self._data_sha
119
121 return hash(str(self._type) + self._data_sha)
122
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()
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
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
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
189
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
204
206 self._client = client
207 self._outgoing_sessions = {}
208 self._incoming_sessions = {}
209 self._published_objects = set()
210
212 euf_guid = message.body.euf_guid
213 if euf_guid == EufGuid.MSN_OBJECT:
214 return True
215 else:
216 return False
217
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
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
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
270 handle_id = self._incoming_sessions[session]
271 session.disconnect(handle_id)
272 del self._incoming_sessions[session]
273
275
276 __gsignals__ = {
277 "session-created" : (gobject.SIGNAL_RUN_FIRST,
278 gobject.TYPE_NONE,
279 (object, bool))
280 }
281
283 gobject.GObject.__init__(self)
284 self._client = client
285 self._sessions = []
286
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
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