|
Package papyon ::
Module client
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """Client
25
26 This module contains the main class used to login into the MSN Messenger
27 network. The following example demonstrates a simple client.
28
29 >>> import papyon
30 >>>
31 >>> server = ('messenger.hotmail.com', 1863)
32 >>> account = ('papyon@hotmail.com', 'papyon is great !')
33 >>>
34 >>> client = papyon.Client(server)
35 >>> client.login(*account)
36 >>>
37 >>> if __name__ == "__main__":
38 ... import gobject
39 ... import logging
40 ... logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug
41 ...
42 ... mainloop = gobject.MainLoop()
43 ... mainloop.run()
44
45 This client will try to login, but will probably fail because of the wrong
46 password, so let's enhance this client so that it displays an error if the
47 password was wrong, this will lead us to use the L{papyon.event} interfaces:
48
49 >>> import papyon
50 >>> import papyon.event
51 >>>
52 >>> class ClientEventHandler(papyon.event.ClientEventInterface):
53 ... def on_client_error(self, error_type, error):
54 ... if error_type == papyon.event.ClientErrorType.AUTHENTICATION:
55 ... print ""
56 ... print "********************************************************"
57 ... print "* You bummer ! you did input a wrong username/password *"
58 ... print "********************************************************"
59 ... else:
60 ... print "ERROR :", error_type, " ->", error
61 >>>
62 >>>
63 >>> server = ('messenger.hotmail.com', 1863)
64 >>> account = ('papyon@hotmail.com', 'papyon is great !')
65 >>>
66 >>> client = papyon.Client(server)
67 >>> client_events_handler = ClientEventHandler(client)
68 >>>
69 >>> client.login(*account)
70 >>>
71 >>> if __name__ == "__main__":
72 ... import gobject
73 ... import logging
74 ...
75 ... logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug
76 ...
77 ... mainloop = gobject.MainLoop()
78 ... mainloop.run()
79
80 """
81
82
83 import papyon.profile as profile
84 import papyon.msnp as msnp
85
86 import papyon.service.SingleSignOn as SSO
87 import papyon.service.AddressBook as AB
88 import papyon.service.OfflineIM as OIM
89 import papyon.service.Spaces as Spaces
90
91 from papyon.util.decorator import rw_property
92 from papyon.transport import *
93 from papyon.switchboard_manager import SwitchboardManager
94 from papyon.msnp2p import P2PSessionManager
95 from papyon.p2p import MSNObjectStore, WebcamHandler
96 from papyon.conversation import SwitchboardConversation, \
97 ExternalNetworkConversation
98 from papyon.event import ClientState, ClientErrorType, \
99 AuthenticationError, ProtocolError, EventsDispatcher
100
101 import logging
102
103 __all__ = ['Client']
104
105 logger = logging.getLogger('client')
106
107 -class Client(EventsDispatcher):
108 """This class provides way to connect to the notification server as well
109 as methods to manage the contact list, and the personnal settings.
110 @sort: __init__, login, logout, state, profile, address_book,
111 msn_object_store, oim_box, spaces"""
112
113 - def __init__(self, server, proxies={}, transport_class=DirectConnection):
114 """Initializer
115
116 @param server: the Notification server to connect to.
117 @type server: tuple(host, port)
118
119 @param proxies: proxies that we can use to connect
120 @type proxies: {type: string => L{gnet.proxy.ProxyInfos}}
121
122 @param transport_class: the transport class to use for the network
123 connection
124 @type transport_class: L{papyon.transport.BaseTransport}"""
125 EventsDispatcher.__init__(self)
126
127 self.__state = ClientState.CLOSED
128
129 self._proxies = proxies
130 self._transport_class = transport_class
131 self._proxies = proxies
132
133 self._transport = transport_class(server, ServerType.NOTIFICATION,
134 self._proxies)
135 self._protocol = msnp.NotificationProtocol(self, self._transport,
136 self._proxies)
137
138 self._switchboard_manager = SwitchboardManager(self)
139 self._switchboard_manager.register_handler(SwitchboardConversation)
140
141 self._p2p_session_manager = P2PSessionManager(self)
142 self._webcam_handler = WebcamHandler(self)
143 self._p2p_session_manager.register_handler(self._webcam_handler)
144
145 self._msn_object_store = MSNObjectStore(self)
146 self._p2p_session_manager.register_handler(self._msn_object_store)
147
148
149
150 self._external_conversations = {}
151
152 self._sso = None
153
154 self._profile = None
155 self._address_book = None
156 self._oim_box = None
157 self._mailbox = None
158
159 self.__die = False
160 self.__connect_transport_signals()
161 self.__connect_protocol_signals()
162 self.__connect_switchboard_manager_signals()
163 self.__connect_webcam_handler_signals()
164
165
166 @property
168 """The MSNObjectStore instance associated with this client.
169 @rtype: L{MSNObjectStore<papyon.p2p.MSNObjectStore>}"""
170 return self._msn_object_store
171
172 @property
174 return self._webcam_handler
175
176 @property
178 """The profile of the current user
179 @rtype: L{User<papyon.profile.Profile>}"""
180 return self._profile
181
182 @property
184 """The address book of the current user
185 @rtype: L{AddressBook<papyon.service.AddressBook>}"""
186 return self._address_book
187
188 @property
190 """The offline IM for the current user
191 @rtype: L{OfflineIM<papyon.service.OfflineIM>}"""
192 return self._oim_box
193
194 @property
196 """The mailbox of the current user
197 @rtype: L{<papyon.msnp.mailbox.Mailbox>}"""
198 return self._mailbox
199
200 @property
202 """The MSN Spaces of the current user
203 @rtype: L{Spaces<papyon.service.Spaces>}"""
204 return self._spaces
205
206 @property
208 """The state of this Client
209 @rtype: L{papyon.event.ClientState}"""
210 return self.__state
211
212 - def login(self, account, password):
213 """Login to the server.
214
215 @param account: the account to use for authentication.
216 @type account: utf-8 encoded string
217
218 @param password: the password needed to authenticate to the account
219 @type password: utf-8 encoded string
220 """
221 if (self._state != ClientState.CLOSED):
222 logger.warning('login already in progress')
223 self.__die = False
224 self._state = ClientState.CONNECTING
225 self._profile = profile.Profile((account, password), self._protocol)
226 self.__connect_profile_signals()
227 self._mailbox = msnp.Mailbox(self._protocol)
228 self.__connect_mailbox_signals()
229 self._transport.establish_connection()
230
232 """Logout from the server."""
233 if self._state == ClientState.CLOSED:
234 logger.warning('alreay logged out')
235 return
236 self.__die = True
237 self._switchboard_manager.close()
238 if self.__state < ClientState.AUTHENTICATING:
239 self._transport.lose_connection()
240 else:
241 self._protocol.signoff()
242 self.__state = ClientState.CLOSED
243
244
245 @rw_property
247 def fget(self):
248 return self.__state
249 def fset(self, state):
250 self.__state = state
251 self._dispatch("on_client_state_changed", state)
252 return locals()
253
262
267
268
270 """Connect profile signals"""
271 def property_changed(profile, pspec):
272 method_name = "on_profile_%s_changed" % pspec.name.replace("-", "_")
273 self._dispatch(method_name)
274
275 self.profile.connect("notify::presence", property_changed)
276 self.profile.connect("notify::display-name", property_changed)
277 self.profile.connect("notify::personal-message", property_changed)
278 self.profile.connect("notify::current-media", property_changed)
279 self.profile.connect("notify::msn-object", property_changed)
280
282 """Connect mailbox signals"""
283 def new_mail_received(mailbox, mail):
284 self._dispatch("on_mailbox_new_mail_received", mail)
285
286 def unread_changed(mailbox, unread_count, initial):
287 method_name = "on_mailbox_unread_mail_count_changed"
288 self._dispatch(method_name, unread_count, initial)
289
290 self.mailbox.connect("unread-mail-count-changed", unread_changed)
291 self.mailbox.connect("new-mail-received", new_mail_received)
292
300
301 def property_changed(contact, pspec):
302 method_name = "on_contact_%s_changed" % pspec.name.replace("-", "_")
303 self._dispatch(method_name, contact)
304
305 contact.connect("notify::memberships", property_changed)
306 contact.connect("notify::presence", property_changed)
307 contact.connect("notify::display-name", property_changed)
308 contact.connect("notify::personal-message", property_changed)
309 contact.connect("notify::current-media", property_changed)
310 contact.connect("notify::msn-object", property_changed)
311 contact.connect("notify::client-capabilities", property_changed)
312
313 def connect_signal(name):
314 contact.connect(name, event, name)
315 connect_signal("infos-changed")
316
318 """Connect transport signals"""
319 def connect_success(transp):
320 self._sso = SSO.SingleSignOn(self.profile.account,
321 self.profile.password,
322 self._proxies)
323 self._address_book = AB.AddressBook(self._sso, self, self._proxies)
324 self.__connect_addressbook_signals()
325 self._oim_box = OIM.OfflineMessagesBox(self._sso, self, self._proxies)
326 self.__connect_oim_box_signals()
327 self._spaces = Spaces.Spaces(self._sso, self._proxies)
328
329 self._state = ClientState.CONNECTED
330
331 def connect_failure(transp, reason):
332 self._dispatch("on_client_error", ClientErrorType.NETWORK, reason)
333 self._state = ClientState.CLOSED
334
335 def disconnected(transp, reason):
336 if not self.__die:
337 self._dispatch("on_client_error", ClientErrorType.NETWORK, reason)
338 self.__die = False
339 self._state = ClientState.CLOSED
340
341 self._transport.connect("connection-success", connect_success)
342 self._transport.connect("connection-failure", connect_failure)
343 self._transport.connect("connection-lost", disconnected)
344
362
363 def authentication_failed(proto):
364 self._dispatch("on_client_error", ClientErrorType.AUTHENTICATION,
365 AuthenticationError.INVALID_USERNAME_OR_PASSWORD)
366 self.__die = True
367 self._transport.lose_connection()
368
369 def disconnected_by_other(proto):
370 self._dispatch("on_client_error", ClientErrorType.PROTOCOL,
371 ProtocolError.OTHER_CLIENT)
372 self.__die = True
373 self._transport.lose_connection()
374
375 def server_down(proto):
376 self._dispatch("on_client_error", ClientErrorType.PROTOCOL,
377 ProtocolError.SERVER_DOWN)
378 self.__die = True
379 self._transport.lose_connection()
380
381 def unmanaged_message_received(proto, sender, message):
382 if sender in self._external_conversations:
383 conversation = self._external_conversations[sender]
384 conversation._on_message_received(message)
385 else:
386 conversation = ExternalNetworkConversation(self, [sender])
387 self._register_external_conversation(conversation)
388 if self._dispatch("on_invite_conversation", conversation) == 0:
389 logger.warning("No event handler attached for conversations")
390 conversation._on_message_received(message)
391
392 self._protocol.connect("notify::state", state_changed)
393 self._protocol.connect("authentication-failed", authentication_failed)
394 self._protocol.connect("disconnected-by-other", disconnected_by_other)
395 self._protocol.connect("server-down", server_down)
396 self._protocol.connect("unmanaged-message-received", unmanaged_message_received)
397
399 """Connect Switchboard Manager signals"""
400 def handler_created(switchboard_manager, handler_class, handler):
401 if handler_class is SwitchboardConversation:
402 if self._dispatch("on_invite_conversation", handler) == 0:
403 logger.warning("No event handler attached for conversations")
404 else:
405 logger.warning("Unknown Switchboard Handler class %s" % handler_class)
406
407 self._switchboard_manager.connect("handler-created", handler_created)
408
410 """Connect AddressBook signals"""
411 def event(address_book, *args):
412 event_name = args[-1]
413 event_args = args[:-1]
414 if event_name == "messenger-contact-added":
415 self.__connect_contact_signals(event_args[0])
416 method_name = "on_addressbook_%s" % event_name.replace("-", "_")
417 self._dispatch(method_name, *event_args)
418 def error(address_book, error_code):
419 self._dispatch("on_client_error", ClientErrorType.ADDRESSBOOK, error_code)
420 self.__die = True
421 self._transport.lose_connection()
422
423 self.address_book.connect('error', error)
424
425 def connect_signal(name):
426 self.address_book.connect(name, event, name)
427
428 connect_signal("messenger-contact-added")
429 connect_signal("contact-deleted")
430 connect_signal("contact-blocked")
431 connect_signal("contact-unblocked")
432 connect_signal("group-added")
433 connect_signal("group-deleted")
434 connect_signal("group-renamed")
435 connect_signal("group-contact-added")
436 connect_signal("group-contact-deleted")
437
439 """Connect Offline IM signals"""
440 def event(oim_box, *args):
441 method_name = "on_oim_%s" % args[-1].replace("-", "_")
442 self._dispatch(method_name, *args[:-1])
443 def state_changed(oim_box, pspec):
444 self._dispatch("on_oim_state_changed", oim_box.state)
445 def error(oim_box, error_code):
446 self._dispatch("on_client_error", ClientErrorType.OFFLINE_MESSAGES, error_code)
447
448 self.oim_box.connect("notify::state", state_changed)
449 self.oim_box.connect('error', error)
450
451 def connect_signal(name):
452 self.oim_box.connect(name, event, name)
453 connect_signal("messages-received")
454 connect_signal("messages-fetched")
455 connect_signal("message-sent")
456 connect_signal("messages-deleted")
457
459 """Connect Webcam Handler signals"""
460 def session_created(webcam_handler, session, producer):
461 self._dispatch("on_invite_webcam", session, producer)
462
463 self._webcam_handler.connect("session-created", session_created)
464