From 8efe875bf6ba0b5cd167be9899c487c9501dd56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 17 Mar 2009 00:00:15 +0000 Subject: [PATCH 001/147] Added vim .swp files to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ab43a7d2..6cba120a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/ .svn/ Debug/ ui_* +.*.swp From f0410c6fb4d097ef6269a558dd09bd09c1914882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 17 Mar 2009 00:01:17 +0000 Subject: [PATCH 002/147] Updated curses frontend to work again --- amsn2/gui/front_ends/curses/contact_list.py | 6 ++--- amsn2/gui/front_ends/curses/curses_.py | 3 ++- amsn2/gui/front_ends/curses/login.py | 14 +++++------ amsn2/gui/front_ends/curses/main.py | 28 +++++++++++++-------- amsn2/gui/front_ends/curses/main_loop.py | 12 ++++----- amsn2/gui/front_ends/curses/splash.py | 26 +++++++++++++++++++ 6 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 amsn2/gui/front_ends/curses/splash.py diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index df43ff75..4a7b87a1 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -39,12 +39,12 @@ def count(self): return (online, total) -class aMSNContactList(base.aMSNContactList): - def __init__(self, amsn_core): +class aMSNContactListWindow(base.aMSNContactListWindow): + def __init__(self, amsn_core, parent): self._amsn_core = amsn_core self.groups = {} self.contacts = {} - self._stdscr = self._amsn_core.getMainWindow()._stdscr + self._stdscr = parent._stdscr self._win = curses.newwin(100, 100, 3, 3) def show(self): diff --git a/amsn2/gui/front_ends/curses/curses_.py b/amsn2/gui/front_ends/curses/curses_.py index 6bdd2f25..3ebdb1bd 100644 --- a/amsn2/gui/front_ends/curses/curses_.py +++ b/amsn2/gui/front_ends/curses/curses_.py @@ -2,4 +2,5 @@ from main import * from contact_list import * from login import * - +from amsn2.gui.base import SkinManager +from splash import * diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index b10d9120..8af7a4b7 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -4,26 +4,26 @@ class TextBox(object): def __init__(self, win, y, x, txt): - self._win = win.derwin(1, 30, y, x) - self._win.clear() - self._txtbox = curses.textpad.Textbox(self._win) - self._txtbox.stripspaces = True + self._win = win.derwin(1, 30, y, x) + self._win.clear() + self._txtbox = curses.textpad.Textbox(self._win) + self._txtbox.stripspaces = True if txt is not None: for x in txt: self._txtbox.do_command(x) def edit(self): - return self._txtbox.edit() + return self._txtbox.edit() def value(self): return self._txtbox.gather() class aMSNLoginWindow(object): - def __init__(self, amsn_core): + def __init__(self, amsn_core, parent): self._amsn_core = amsn_core self.switch_to_profile(None) - self._stdscr = self._amsn_core.getMainWindow()._stdscr + self._stdscr = parent._stdscr self._win = curses.newwin(20, 100, 5, 5) def show(self): diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index c57b4ee3..84239167 100644 --- a/amsn2/gui/front_ends/curses/main.py +++ b/amsn2/gui/front_ends/curses/main.py @@ -7,20 +7,26 @@ def __init__(self, amsn_core): self._amsn_core = amsn_core def show(self): - self._stdscr = curses.initscr() - curses.noecho() - curses.cbreak() - self._stdscr.keypad(1) + self._stdscr = curses.initscr() + curses.noecho() + curses.cbreak() + self._stdscr.keypad(1) self._stdscr.box() self._stdscr.refresh() self._amsn_core.idlerAdd(self.__on_show) - + def hide(self): - curses.nocbreak() - self._stdscr.keypad(0) - curses.echo() - curses.endwin() - + curses.nocbreak() + self._stdscr.keypad(0) + curses.echo() + curses.endwin() + def __on_show(self): self._amsn_core.mainWindowShown() - + + def setTitle(self,title): + self._title = title + + def setMenu(self,menu): + pass + diff --git a/amsn2/gui/front_ends/curses/main_loop.py b/amsn2/gui/front_ends/curses/main_loop.py index 215ef1f4..ac239541 100644 --- a/amsn2/gui/front_ends/curses/main_loop.py +++ b/amsn2/gui/front_ends/curses/main_loop.py @@ -5,6 +5,7 @@ class aMSNMainLoop(base.aMSNMainLoop): def __init__(self, amsn_core): self._amsn_core = amsn_core + def run(self): self._mainloop = gobject.MainLoop(is_running=True) @@ -14,7 +15,6 @@ def run(self): except KeyboardInterrupt: self.quit() - def idlerAdd(self, func): gobject.idle_add(func) @@ -24,9 +24,9 @@ def timerAdd(self, delay, func): def quit(self): import curses stdscr = self._amsn_core.getMainWindow()._stdscr - curses.nocbreak() - stdscr.keypad(0) - curses.echo() - curses.endwin() + curses.nocbreak() + stdscr.keypad(0) + curses.echo() + curses.endwin() self._mainloop.quit() - + diff --git a/amsn2/gui/front_ends/curses/splash.py b/amsn2/gui/front_ends/curses/splash.py new file mode 100644 index 00000000..fd6e4962 --- /dev/null +++ b/amsn2/gui/front_ends/curses/splash.py @@ -0,0 +1,26 @@ +from amsn2.gui import base + +class aMSNSplashScreen(base.aMSNSplashScreen): + """This is the splashscreen for ncurses""" + def __init__(self, amsn_core, parent): + """Initialize the interface. You should store the reference to the core in here + as well as a reference to the window where you will show the splash screen + """ + # Is it needed in ncurses? + pass + + def show(self): + """ Draw the splashscreen """ + pass + + def hide(self): + """ Hide the splashscreen """ + pass + + def setText(self, text): + """ Shows a different text inside the splashscreen """ + pass + + def setImage(self, image): + """ Set the image to show in the splashscreen. This is an ImageView object """ + pass From 88a867960ae558e8be28dd51152e1c7e3f23e5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Fri, 3 Apr 2009 23:26:42 +0100 Subject: [PATCH 003/147] Implemented password hiding during login --- amsn2.py | 2 ++ amsn2/gui/front_ends/curses/login.py | 38 ++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/amsn2.py b/amsn2.py index bfb9f7d8..69aa6994 100644 --- a/amsn2.py +++ b/amsn2.py @@ -3,6 +3,8 @@ import os import optparse sys.path.insert(0, "./pymsn") +import locale +locale.setlocale(locale.LC_ALL, '') from amsn2.core import aMSNCore diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index 8af7a4b7..d4ba38a5 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -10,14 +10,43 @@ def __init__(self, win, y, x, txt): self._txtbox.stripspaces = True if txt is not None: - for x in txt: - self._txtbox.do_command(x) + self._insert(txt) def edit(self): return self._txtbox.edit() def value(self): return self._txtbox.gather() + + def _insert(self, txt): + for ch in txt: + self._txtbox.do_command(ch) + +class PasswordBox(TextBox): + def __init__(self, win, y, x, txt): + super(PasswordBox, self).__init__(win, y, x, txt) + self._password = '' + + def edit(self, cb=None): + return self._txtbox.edit(self._validateInput) + + def value(self): + return self._password + + def _validateInput(self, ch): + if ch in (curses.KEY_BACKSPACE, curses.ascii.BEL): + self._password = self._password[0:-1] + return ch + elif curses.ascii.isprint(ch): + self._password += chr(ch) + return '*' + else: + return ch + + def _insert(self, str): + for ch in str: + self._password += ch + self._txtbox.do_command('*') class aMSNLoginWindow(object): def __init__(self, amsn_core, parent): @@ -27,11 +56,12 @@ def __init__(self, amsn_core, parent): self._win = curses.newwin(20, 100, 5, 5) def show(self): + self._win.border() self._win.addstr(5, 5, "Account : ", curses.A_BOLD) self._username_t = TextBox(self._win, 5, 17, self._username) self._win.addstr(8, 5, "Password : ", curses.A_BOLD) - self._password_t = TextBox(self._win, 8, 17, self._password) + self._password_t = PasswordBox(self._win, 8, 17, self._password) self._win.refresh() @@ -106,5 +136,3 @@ def onSynchronized(self): self._win.addstr(10, 25, "Synchronized!", curses.A_BOLD | curses.A_STANDOUT) self._win.refresh() - - From cd959b451ff18e4f73a4a62a0d4ec5a5abb7af14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sun, 5 Apr 2009 22:20:39 +0100 Subject: [PATCH 004/147] Fixed exception at login with some kind of OIMs. Needs further adjustments. --- pymsn/pymsn/service/OfflineIM/offline_messages_box.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py b/pymsn/pymsn/service/OfflineIM/offline_messages_box.py index b497b458..a06d3927 100644 --- a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py +++ b/pymsn/pymsn/service/OfflineIM/offline_messages_box.py @@ -253,9 +253,17 @@ def __parse_metadata(self, xml_data): network = (m.findtext('T','int'), m.findtext('S','int')) if network == (11,6): network_id = NetworkID.MSN + elif network == (11,7): + # FIXME: What does 11 and 7 mean? We should find out... + # FIXME: My first guess would be Windows Live Communication Server + network_id = NetworkID.LCS elif network == (13,7): network_id = NetworkID.EXTERNAL - + else: + # This is to ensure that network_id is always set, even if we get unknown values for network + # FIXME: Is NetworkID.MSN the best choice as default value? + network_id = NetworkID.MSN + account = m.findtext('./E') try: From 288dabe31d91196d657a8e067f3117a8462f504c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 6 Apr 2009 19:32:42 +0100 Subject: [PATCH 005/147] Added the note about the TERM setting for the ncurses front-end --- README | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README b/README index 066e5763..05c19b83 100644 --- a/README +++ b/README @@ -30,3 +30,7 @@ If you have the following error with the qt4 front-end: self._loop = self._gui.gui.aMSNMainLoop(self) AttributeError: 'NoneType' object has no attribute 'aMSNMainLoop' try moving into the amsn2/gui/front_ends/qt4 directory and calling generateFiles.sh + +If the backspace is not working as expected with the ncurses front-end it is probably because your TERM is not set to the correct value. Try to launch amsn2 with some other setting for TERM, like: +TERM=konsole ./amsn2.py -f curses +If it works with some other value it means that your terminal emulator is not set correctly. From 3c137b38ef50fb297e3ea81157be91bbd3eef2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 6 Apr 2009 21:50:36 +0100 Subject: [PATCH 006/147] Removed internal pymsn to include it as submodule --- pymsn/AUTHORS | 3 - pymsn/COPYING | 340 -------- pymsn/MANIFEST.in | 5 - pymsn/NEWS | 46 - pymsn/README | 13 - pymsn/doc.py | 75 -- pymsn/doc/user-api.conf | 13 - pymsn/pymsn/__init__.py | 41 - pymsn/pymsn/client.py | 404 --------- pymsn/pymsn/conversation.py | 447 ---------- pymsn/pymsn/event/__init__.py | 74 -- pymsn/pymsn/event/address_book.py | 54 -- pymsn/pymsn/event/client.py | 114 --- pymsn/pymsn/event/contact.py | 91 -- pymsn/pymsn/event/conversation.py | 120 --- pymsn/pymsn/event/invite.py | 41 - pymsn/pymsn/event/offline_messages.py | 50 -- pymsn/pymsn/event/profile.py | 57 -- pymsn/pymsn/gnet/__init__.py | 27 - pymsn/pymsn/gnet/constants.py | 53 -- pymsn/pymsn/gnet/io/__init__.py | 27 - pymsn/pymsn/gnet/io/abstract.py | 189 ---- pymsn/pymsn/gnet/io/iochannel.py | 161 ---- pymsn/pymsn/gnet/io/sock.py | 98 --- pymsn/pymsn/gnet/io/ssl_socket.py | 117 --- pymsn/pymsn/gnet/io/ssl_tcp.py | 47 - pymsn/pymsn/gnet/io/tcp.py | 47 - pymsn/pymsn/gnet/message/HTTP.py | 161 ---- pymsn/pymsn/gnet/message/SOAP.py | 154 ---- pymsn/pymsn/gnet/message/__init__.py | 21 - pymsn/pymsn/gnet/parser.py | 190 ----- pymsn/pymsn/gnet/protocol/HTTP.py | 154 ---- pymsn/pymsn/gnet/protocol/HTTPS.py | 60 -- pymsn/pymsn/gnet/protocol/__init__.py | 35 - pymsn/pymsn/gnet/proxy/HTTPConnect.py | 110 --- pymsn/pymsn/gnet/proxy/SOCKS4.py | 132 --- pymsn/pymsn/gnet/proxy/__init__.py | 23 - pymsn/pymsn/gnet/proxy/abstract.py | 48 -- pymsn/pymsn/gnet/proxy/proxy_infos.py | 120 --- pymsn/pymsn/gnet/proxy/proxyfiable.py | 37 - pymsn/pymsn/gnet/resolver.py | 98 --- pymsn/pymsn/msnp/__init__.py | 29 - pymsn/pymsn/msnp/base.py | 114 --- pymsn/pymsn/msnp/challenge.py | 78 -- pymsn/pymsn/msnp/command.py | 221 ----- pymsn/pymsn/msnp/constants.py | 27 - pymsn/pymsn/msnp/message.py | 111 --- pymsn/pymsn/msnp/notification.py | 627 -------------- pymsn/pymsn/msnp/switchboard.py | 296 ------- pymsn/pymsn/msnp2p/SLP.py | 306 ------- pymsn/pymsn/msnp2p/__init__.py | 27 - pymsn/pymsn/msnp2p/constants.py | 49 -- pymsn/pymsn/msnp2p/exceptions.py | 59 -- pymsn/pymsn/msnp2p/session.py | 211 ----- pymsn/pymsn/msnp2p/session_manager.py | 188 ---- pymsn/pymsn/msnp2p/test.py | 140 --- pymsn/pymsn/msnp2p/transport/TLP.py | 248 ------ pymsn/pymsn/msnp2p/transport/__init__.py | 23 - pymsn/pymsn/msnp2p/transport/base.py | 154 ---- pymsn/pymsn/msnp2p/transport/switchboard.py | 84 -- .../msnp2p/transport/transport_manager.py | 130 --- pymsn/pymsn/p2p.py | 264 ------ pymsn/pymsn/profile.py | 805 ------------------ pymsn/pymsn/service/AddressBook/__init__.py | 22 - pymsn/pymsn/service/AddressBook/ab.py | 429 ---------- .../pymsn/service/AddressBook/address_book.py | 701 --------------- pymsn/pymsn/service/AddressBook/common.py | 33 - pymsn/pymsn/service/AddressBook/constants.py | 51 -- .../service/AddressBook/scenario/__init__.py | 26 - .../service/AddressBook/scenario/base.py | 50 -- .../AddressBook/scenario/contacts/__init__.py | 35 - .../scenario/contacts/accept_invite.py | 117 --- .../scenario/contacts/block_contact.py | 63 -- .../scenario/contacts/check_pending_invite.py | 51 -- .../scenario/contacts/contact_delete.py | 54 -- .../contacts/contact_update_properties.py | 59 -- .../scenario/contacts/decline_invite.py | 68 -- .../scenario/contacts/email_contact_add.py | 77 -- .../scenario/contacts/external_contact_add.py | 84 -- .../contacts/messenger_contact_add.py | 90 -- .../scenario/contacts/mobile_contact_add.py | 80 -- .../scenario/contacts/unblock_contact.py | 63 -- .../scenario/contacts/update_memberships.py | 144 ---- .../AddressBook/scenario/groups/__init__.py | 25 - .../AddressBook/scenario/groups/group_add.py | 52 -- .../scenario/groups/group_contact_add.py | 53 -- .../scenario/groups/group_contact_delete.py | 54 -- .../scenario/groups/group_delete.py | 52 -- .../scenario/groups/group_rename.py | 55 -- .../AddressBook/scenario/sync/__init__.py | 20 - .../AddressBook/scenario/sync/initial_sync.py | 95 --- pymsn/pymsn/service/AddressBook/sharing.py | 221 ----- .../pymsn/service/ContentRoaming/__init__.py | 21 - .../pymsn/service/ContentRoaming/constants.py | 36 - .../service/ContentRoaming/content_roaming.py | 224 ----- .../ContentRoaming/scenario/__init__.py | 26 - .../service/ContentRoaming/scenario/base.py | 39 - .../scenario/get_stored_profile.py | 74 -- .../ContentRoaming/scenario/store_profile.py | 102 --- pymsn/pymsn/service/ContentRoaming/storage.py | 169 ---- pymsn/pymsn/service/OfflineIM/__init__.py | 22 - pymsn/pymsn/service/OfflineIM/constants.py | 42 - .../service/OfflineIM/offline_messages_box.py | 416 --------- pymsn/pymsn/service/OfflineIM/oim.py | 112 --- pymsn/pymsn/service/OfflineIM/rsi.py | 94 -- .../service/OfflineIM/scenario/__init__.py | 28 - .../pymsn/service/OfflineIM/scenario/base.py | 32 - .../OfflineIM/scenario/delete_messages.py | 50 -- .../OfflineIM/scenario/fetch_messages.py | 57 -- .../OfflineIM/scenario/send_message.py | 75 -- .../OfflineIM/scenario/sync_headers.py | 47 - pymsn/pymsn/service/SOAPService.py | 263 ------ pymsn/pymsn/service/SOAPUtils.py | 62 -- pymsn/pymsn/service/SingleSignOn.py | 270 ------ pymsn/pymsn/service/Spaces/__init__.py | 22 - .../service/Spaces/contactcardservice.py | 133 --- .../pymsn/service/Spaces/scenario/__init__.py | 25 - pymsn/pymsn/service/Spaces/scenario/base.py | 32 - .../Spaces/scenario/get_contact_card.py | 49 -- pymsn/pymsn/service/Spaces/spaces.py | 105 --- pymsn/pymsn/service/__init__.py | 27 - pymsn/pymsn/service/description/AB/ABAdd.py | 52 -- .../service/description/AB/ABContactAdd.py | 190 ----- .../service/description/AB/ABContactDelete.py | 52 -- .../service/description/AB/ABContactUpdate.py | 186 ---- .../pymsn/service/description/AB/ABFindAll.py | 61 -- .../service/description/AB/ABGroupAdd.py | 73 -- .../description/AB/ABGroupContactAdd.py | 69 -- .../description/AB/ABGroupContactDelete.py | 59 -- .../service/description/AB/ABGroupDelete.py | 52 -- .../service/description/AB/ABGroupUpdate.py | 63 -- .../pymsn/service/description/AB/__init__.py | 39 - pymsn/pymsn/service/description/AB/common.py | 35 - .../pymsn/service/description/AB/constants.py | 71 -- pymsn/pymsn/service/description/OIM/Store2.py | 70 -- .../pymsn/service/description/OIM/__init__.py | 25 - .../service/description/RSI/DeleteMessages.py | 49 -- .../service/description/RSI/GetMessage.py | 52 -- .../service/description/RSI/GetMetadata.py | 46 - .../pymsn/service/description/RSI/__init__.py | 27 - pymsn/pymsn/service/description/RSI/common.py | 33 - .../SchematizedStore/CreateDocument.py | 82 -- .../SchematizedStore/CreateRelationships.py | 60 -- .../SchematizedStore/DeleteRelationships.py | 66 -- .../SchematizedStore/FindDocuments.py | 85 -- .../SchematizedStore/GetProfile.py | 81 -- .../SchematizedStore/UpdateProfile.py | 61 -- .../description/SchematizedStore/__init__.py | 32 - .../description/SchematizedStore/common.py | 37 - .../service/description/Sharing/AddMember.py | 80 -- .../description/Sharing/DeleteMember.py | 76 -- .../description/Sharing/FindMembership.py | 88 -- .../service/description/Sharing/__init__.py | 27 - .../service/description/Sharing/common.py | 37 - .../RequestMultipleSecurityTokens.py | 121 --- .../description/SingleSignOn/__init__.py | 27 - .../service/description/Spaces/GetXmlFeed.py | 82 -- .../service/description/Spaces/__init__.py | 25 - pymsn/pymsn/service/description/__init__.py | 30 - pymsn/pymsn/storage.py | 211 ----- pymsn/pymsn/switchboard_manager.py | 366 -------- pymsn/pymsn/transport.py | 487 ----------- pymsn/pymsn/util/__init__.py | 20 - pymsn/pymsn/util/debug.py | 46 - pymsn/pymsn/util/decorator.py | 121 --- pymsn/pymsn/util/element_tree.py | 152 ---- pymsn/pymsn/util/guid.py | 32 - pymsn/pymsn/util/iso8601/LICENSE | 20 - pymsn/pymsn/util/iso8601/README | 26 - pymsn/pymsn/util/iso8601/__init__.py | 1 - pymsn/pymsn/util/iso8601/iso8601.py | 102 --- pymsn/pymsn/util/iso8601/test_iso8601.py | 106 --- pymsn/pymsn/util/queue.py | 64 -- pymsn/pymsn/util/string_io.py | 26 - pymsn/pymsn/util/weak.py | 35 - pymsn/setup.py | 89 -- pymsn/test.py | 145 ---- 177 files changed, 18316 deletions(-) delete mode 100644 pymsn/AUTHORS delete mode 100644 pymsn/COPYING delete mode 100644 pymsn/MANIFEST.in delete mode 100644 pymsn/NEWS delete mode 100644 pymsn/README delete mode 100644 pymsn/doc.py delete mode 100644 pymsn/doc/user-api.conf delete mode 100644 pymsn/pymsn/__init__.py delete mode 100644 pymsn/pymsn/client.py delete mode 100644 pymsn/pymsn/conversation.py delete mode 100644 pymsn/pymsn/event/__init__.py delete mode 100644 pymsn/pymsn/event/address_book.py delete mode 100644 pymsn/pymsn/event/client.py delete mode 100644 pymsn/pymsn/event/contact.py delete mode 100644 pymsn/pymsn/event/conversation.py delete mode 100644 pymsn/pymsn/event/invite.py delete mode 100644 pymsn/pymsn/event/offline_messages.py delete mode 100644 pymsn/pymsn/event/profile.py delete mode 100644 pymsn/pymsn/gnet/__init__.py delete mode 100644 pymsn/pymsn/gnet/constants.py delete mode 100644 pymsn/pymsn/gnet/io/__init__.py delete mode 100644 pymsn/pymsn/gnet/io/abstract.py delete mode 100644 pymsn/pymsn/gnet/io/iochannel.py delete mode 100644 pymsn/pymsn/gnet/io/sock.py delete mode 100644 pymsn/pymsn/gnet/io/ssl_socket.py delete mode 100644 pymsn/pymsn/gnet/io/ssl_tcp.py delete mode 100644 pymsn/pymsn/gnet/io/tcp.py delete mode 100644 pymsn/pymsn/gnet/message/HTTP.py delete mode 100644 pymsn/pymsn/gnet/message/SOAP.py delete mode 100644 pymsn/pymsn/gnet/message/__init__.py delete mode 100644 pymsn/pymsn/gnet/parser.py delete mode 100644 pymsn/pymsn/gnet/protocol/HTTP.py delete mode 100644 pymsn/pymsn/gnet/protocol/HTTPS.py delete mode 100644 pymsn/pymsn/gnet/protocol/__init__.py delete mode 100644 pymsn/pymsn/gnet/proxy/HTTPConnect.py delete mode 100644 pymsn/pymsn/gnet/proxy/SOCKS4.py delete mode 100644 pymsn/pymsn/gnet/proxy/__init__.py delete mode 100644 pymsn/pymsn/gnet/proxy/abstract.py delete mode 100644 pymsn/pymsn/gnet/proxy/proxy_infos.py delete mode 100644 pymsn/pymsn/gnet/proxy/proxyfiable.py delete mode 100644 pymsn/pymsn/gnet/resolver.py delete mode 100644 pymsn/pymsn/msnp/__init__.py delete mode 100644 pymsn/pymsn/msnp/base.py delete mode 100644 pymsn/pymsn/msnp/challenge.py delete mode 100644 pymsn/pymsn/msnp/command.py delete mode 100644 pymsn/pymsn/msnp/constants.py delete mode 100644 pymsn/pymsn/msnp/message.py delete mode 100644 pymsn/pymsn/msnp/notification.py delete mode 100644 pymsn/pymsn/msnp/switchboard.py delete mode 100644 pymsn/pymsn/msnp2p/SLP.py delete mode 100644 pymsn/pymsn/msnp2p/__init__.py delete mode 100644 pymsn/pymsn/msnp2p/constants.py delete mode 100644 pymsn/pymsn/msnp2p/exceptions.py delete mode 100644 pymsn/pymsn/msnp2p/session.py delete mode 100644 pymsn/pymsn/msnp2p/session_manager.py delete mode 100644 pymsn/pymsn/msnp2p/test.py delete mode 100644 pymsn/pymsn/msnp2p/transport/TLP.py delete mode 100644 pymsn/pymsn/msnp2p/transport/__init__.py delete mode 100644 pymsn/pymsn/msnp2p/transport/base.py delete mode 100644 pymsn/pymsn/msnp2p/transport/switchboard.py delete mode 100644 pymsn/pymsn/msnp2p/transport/transport_manager.py delete mode 100644 pymsn/pymsn/p2p.py delete mode 100644 pymsn/pymsn/profile.py delete mode 100644 pymsn/pymsn/service/AddressBook/__init__.py delete mode 100644 pymsn/pymsn/service/AddressBook/ab.py delete mode 100644 pymsn/pymsn/service/AddressBook/address_book.py delete mode 100644 pymsn/pymsn/service/AddressBook/common.py delete mode 100644 pymsn/pymsn/service/AddressBook/constants.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/__init__.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/base.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py delete mode 100644 pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py delete mode 100644 pymsn/pymsn/service/AddressBook/sharing.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/__init__.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/constants.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/content_roaming.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/scenario/__init__.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/scenario/base.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py delete mode 100644 pymsn/pymsn/service/ContentRoaming/storage.py delete mode 100644 pymsn/pymsn/service/OfflineIM/__init__.py delete mode 100644 pymsn/pymsn/service/OfflineIM/constants.py delete mode 100644 pymsn/pymsn/service/OfflineIM/offline_messages_box.py delete mode 100644 pymsn/pymsn/service/OfflineIM/oim.py delete mode 100644 pymsn/pymsn/service/OfflineIM/rsi.py delete mode 100644 pymsn/pymsn/service/OfflineIM/scenario/__init__.py delete mode 100644 pymsn/pymsn/service/OfflineIM/scenario/base.py delete mode 100644 pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py delete mode 100644 pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py delete mode 100644 pymsn/pymsn/service/OfflineIM/scenario/send_message.py delete mode 100644 pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py delete mode 100644 pymsn/pymsn/service/SOAPService.py delete mode 100644 pymsn/pymsn/service/SOAPUtils.py delete mode 100644 pymsn/pymsn/service/SingleSignOn.py delete mode 100644 pymsn/pymsn/service/Spaces/__init__.py delete mode 100644 pymsn/pymsn/service/Spaces/contactcardservice.py delete mode 100644 pymsn/pymsn/service/Spaces/scenario/__init__.py delete mode 100644 pymsn/pymsn/service/Spaces/scenario/base.py delete mode 100644 pymsn/pymsn/service/Spaces/scenario/get_contact_card.py delete mode 100644 pymsn/pymsn/service/Spaces/spaces.py delete mode 100644 pymsn/pymsn/service/__init__.py delete mode 100644 pymsn/pymsn/service/description/AB/ABAdd.py delete mode 100644 pymsn/pymsn/service/description/AB/ABContactAdd.py delete mode 100644 pymsn/pymsn/service/description/AB/ABContactDelete.py delete mode 100644 pymsn/pymsn/service/description/AB/ABContactUpdate.py delete mode 100644 pymsn/pymsn/service/description/AB/ABFindAll.py delete mode 100644 pymsn/pymsn/service/description/AB/ABGroupAdd.py delete mode 100644 pymsn/pymsn/service/description/AB/ABGroupContactAdd.py delete mode 100644 pymsn/pymsn/service/description/AB/ABGroupContactDelete.py delete mode 100644 pymsn/pymsn/service/description/AB/ABGroupDelete.py delete mode 100644 pymsn/pymsn/service/description/AB/ABGroupUpdate.py delete mode 100644 pymsn/pymsn/service/description/AB/__init__.py delete mode 100644 pymsn/pymsn/service/description/AB/common.py delete mode 100644 pymsn/pymsn/service/description/AB/constants.py delete mode 100644 pymsn/pymsn/service/description/OIM/Store2.py delete mode 100644 pymsn/pymsn/service/description/OIM/__init__.py delete mode 100644 pymsn/pymsn/service/description/RSI/DeleteMessages.py delete mode 100644 pymsn/pymsn/service/description/RSI/GetMessage.py delete mode 100644 pymsn/pymsn/service/description/RSI/GetMetadata.py delete mode 100644 pymsn/pymsn/service/description/RSI/__init__.py delete mode 100644 pymsn/pymsn/service/description/RSI/common.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/GetProfile.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/__init__.py delete mode 100644 pymsn/pymsn/service/description/SchematizedStore/common.py delete mode 100644 pymsn/pymsn/service/description/Sharing/AddMember.py delete mode 100644 pymsn/pymsn/service/description/Sharing/DeleteMember.py delete mode 100644 pymsn/pymsn/service/description/Sharing/FindMembership.py delete mode 100644 pymsn/pymsn/service/description/Sharing/__init__.py delete mode 100644 pymsn/pymsn/service/description/Sharing/common.py delete mode 100644 pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py delete mode 100644 pymsn/pymsn/service/description/SingleSignOn/__init__.py delete mode 100644 pymsn/pymsn/service/description/Spaces/GetXmlFeed.py delete mode 100644 pymsn/pymsn/service/description/Spaces/__init__.py delete mode 100644 pymsn/pymsn/service/description/__init__.py delete mode 100644 pymsn/pymsn/storage.py delete mode 100644 pymsn/pymsn/switchboard_manager.py delete mode 100644 pymsn/pymsn/transport.py delete mode 100644 pymsn/pymsn/util/__init__.py delete mode 100644 pymsn/pymsn/util/debug.py delete mode 100644 pymsn/pymsn/util/decorator.py delete mode 100644 pymsn/pymsn/util/element_tree.py delete mode 100644 pymsn/pymsn/util/guid.py delete mode 100644 pymsn/pymsn/util/iso8601/LICENSE delete mode 100644 pymsn/pymsn/util/iso8601/README delete mode 100644 pymsn/pymsn/util/iso8601/__init__.py delete mode 100644 pymsn/pymsn/util/iso8601/iso8601.py delete mode 100644 pymsn/pymsn/util/iso8601/test_iso8601.py delete mode 100644 pymsn/pymsn/util/queue.py delete mode 100644 pymsn/pymsn/util/string_io.py delete mode 100644 pymsn/pymsn/util/weak.py delete mode 100644 pymsn/setup.py delete mode 100755 pymsn/test.py diff --git a/pymsn/AUTHORS b/pymsn/AUTHORS deleted file mode 100644 index 24ae6c67..00000000 --- a/pymsn/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -Ali Sabil علي سبيل -Johann Prieur -Ole André Vadla Ravnås diff --git a/pymsn/COPYING b/pymsn/COPYING deleted file mode 100644 index 623b6258..00000000 --- a/pymsn/COPYING +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/pymsn/MANIFEST.in b/pymsn/MANIFEST.in deleted file mode 100644 index 6e1fabbf..00000000 --- a/pymsn/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include test.py -include setup.py -include doc.py -include doc/user-api.conf -include README COPYING AUTHORS NEWS ChangeLog diff --git a/pymsn/NEWS b/pymsn/NEWS deleted file mode 100644 index d5372d4b..00000000 --- a/pymsn/NEWS +++ /dev/null @@ -1,46 +0,0 @@ -pymsn-0.3.2 (Green Moustache) -=============================== - - * List of changes between 0.3.2 and 0.3.3 - o Removed an assert statement causing pymsn to fail with the newest CVR response - o Removed the python-adns dependency - o Fixed a DNS resolution issue - o Fixed a small error in the decorator module - - * List of changes between 0.3.1 and 0.3.2 - o Fixed MSNObject handling in Profile - o Added Profile events support - o Fixed typo in the EmailContactAdd scenario - o Fixed Unicode problems in Contact.attributes - o Fixed a ContactMembershipsUpdate bug - o Removed the unused SLPTransferRequestBody check, this still not supported by our msnp2p stack - o Fixed the msnp2p test file - o Fixed iterating over the switchboard handlers and changing the handlers set (Stéphan Kochen) - o Fixed a missing AddressBookError import (Stéphan Kochen) - - * List of changes between 0.3.0 and 0.3.1 - o Fixed adding contacts already in Memberships list but not with isMessengerUser - - * List of changes between 0.2.2 and 0.3.0 - o Complete rewrite - o MSNP15 support - - -pymsn-0.2.2 (Yellow Ivy) -========================== - - * List of changes between 0.2.2 and 0.2.1 - o Added display pictures request in Client class - o Enhanced HTTPPollConnection, now supporting http proxy, and nasty - proxy not honoring the Keep-Alive - o Added ability to choose the transport in the Client - o Added Storage system to enable pymsn to cache object across sessions - o Added display picture publishing - - * List of bugs fixed between 0.2.2 and 0.2.1 - o Fixed https redirection while authentication - (encountered with @msn.com accounts) - o Fixed pymsn msnp2p bug causing second msnslp session to fail - o Fixed a bug showing wrong representation of Messages in debug messages - o Fixed display-picture-updated signal being emitted before data update - o Fixed transport in Conversation not being given the correct proxy diff --git a/pymsn/README b/pymsn/README deleted file mode 100644 index 6128dc99..00000000 --- a/pymsn/README +++ /dev/null @@ -1,13 +0,0 @@ -pymsn - Python msn client library -================================= - -pymsn is an MSN client library, that tries to abstract the MSN protocol -gory details. - -Dependencies -============ -python (>= 2.4) -python-gobject (>=2.10) -ElementTree (>=1.2.0) or cElementTree (>=1.0.5) or python (>= 2.5) -pyOpenSSL (>=0.6) -pyCrypto (>=2.0.0) diff --git a/pymsn/doc.py b/pymsn/doc.py deleted file mode 100644 index 63471558..00000000 --- a/pymsn/doc.py +++ /dev/null @@ -1,75 +0,0 @@ - - -"""this module was based on setup.py from pygame-ctype branch. -author: Alex Holkner """ - -import os -from os.path import join, abspath, dirname, splitext -import sys -import subprocess - -from distutils.cmd import Command - - -# A "do-everything" command class for building any type of documentation. -class BuildDocCommand(Command): - user_options = [('doc-dir=', None, 'directory to build documentation'), - ('epydoc=', None, 'epydoc executable')] - - def initialize_options(self): - self.doc_dir = join(abspath(dirname(sys.argv[0])), 'doc') - self.epydoc = 'epydoc' - - def finalize_options(self): - pass - - def run(self): - if 'pre' in self.doc: - subprocess.call(self.doc['pre'], shell=True) - - prev_dir = os.getcwd() - if 'chdir' in self.doc: - dir = abspath(join(self.doc_dir, self.doc['chdir'])) - try: - os.makedirs(dir) - except: - pass - os.chdir(dir) - - if 'config' in self.doc: - cmd = [self.epydoc, - '--no-private', - '--no-frames', - '--config "%s"' % self.doc['config']] - subprocess.call(' '.join(cmd), shell=True) - - os.chdir(prev_dir) - - if 'post' in self.doc: - subprocess.call(self.doc['post'], shell=True) - -# Fudge a command class given a dictionary description -def make_doc_command(**kwargs): - class c(BuildDocCommand): - doc = dict(**kwargs) - description = 'build %s' % doc['description'] - c.__name__ = 'build_doc_%s' % c.doc['name'].replace('-', '_') - return c - -# This command does nothing but run all the other doc commands. -# (sub_commands are set later) -class BuildAllDocCommand(Command): - description = 'build all documentation' - user_options = [] - sub_commands = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - diff --git a/pymsn/doc/user-api.conf b/pymsn/doc/user-api.conf deleted file mode 100644 index 4dbd1ac4..00000000 --- a/pymsn/doc/user-api.conf +++ /dev/null @@ -1,13 +0,0 @@ -[epydoc] - -# Information about the project. -name: pymsn -url: http://telepathy.freedesktop.org/wiki/Pymsn - -modules: pymsn.client pymsn.profile pymsn.conversation pymsn.event pymsn.p2p - -output: html -target: doc/user-api/ - -private: no - diff --git a/pymsn/pymsn/__init__.py b/pymsn/pymsn/__init__.py deleted file mode 100644 index 0eda876e..00000000 --- a/pymsn/pymsn/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""An implementation of the MSN Messenger Protocol - -pymsn is a library, written in Python, for accessing the MSN -instant messaging service. - - @group High Level Interface: client, profile, conversation, event - @group Low Level Interface: msnp, msnp2p, service - @group Network Layer: gnet -""" - -__version__ = "0.3.3" -__author__ = "Ali Sabil " -__url__ = "http://telepathy.freedesktop.org/wiki/Pymsn" -__license__ = "GNU GPL" - -from client import * -from conversation import * -from profile import NetworkID, Presence, Privacy, Membership, Contact, Group -import event - -import gnet.proxy -Proxy = gnet.proxy.ProxyFactory -ProxyInfos = gnet.proxy.ProxyInfos diff --git a/pymsn/pymsn/client.py b/pymsn/pymsn/client.py deleted file mode 100644 index a77d684e..00000000 --- a/pymsn/pymsn/client.py +++ /dev/null @@ -1,404 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2006-2007 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Client - -This module contains the main class used to login into the MSN Messenger -network. The following example demonstrates a simple client. - - >>> import pymsn - >>> - >>> server = ('messenger.hotmail.com', 1863) - >>> account = ('pymsn@hotmail.com', 'pymsn is great !') - >>> - >>> client = pymsn.Client(server) - >>> client.login(*account) - >>> - >>> if __name__ == "__main__": - ... import gobject - ... import logging - ... logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug - ... - ... mainloop = gobject.MainLoop() - ... mainloop.run() - -This client will try to login, but will probably fail because of the wrong -password, so let's enhance this client so that it displays an error if the -password was wrong, this will lead us to use the L{pymsn.event} interfaces: - - >>> import pymsn - >>> import pymsn.event - >>> - >>> class ClientEventHandler(pymsn.event.ClientEventInterface): - ... def on_client_error(self, error_type, error): - ... if error_type == pymsn.event.ClientErrorType.AUTHENTICATION: - ... print "" - ... print "********************************************************" - ... print "* You bummer ! you did input a wrong username/password *" - ... print "********************************************************" - ... else: - ... print "ERROR :", error_type, " ->", error - >>> - >>> - >>> server = ('messenger.hotmail.com', 1863) - >>> account = ('pymsn@hotmail.com', 'pymsn is great !') - >>> - >>> client = pymsn.Client(server) - >>> client_events_handler = ClientEventHandler(client) - >>> - >>> client.login(*account) - >>> - >>> if __name__ == "__main__": - ... import gobject - ... import logging - ... - ... logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug - ... - ... mainloop = gobject.MainLoop() - ... mainloop.run() - -""" - -import pymsn.profile as profile -import pymsn.msnp as msnp - -import pymsn.service.SingleSignOn as SSO -import pymsn.service.AddressBook as AB -import pymsn.service.OfflineIM as OIM -import pymsn.service.Spaces as Spaces - -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.conversation import SwitchboardConversation, \ - ExternalNetworkConversation -from pymsn.event import ClientState, ClientErrorType, \ - AuthenticationError, EventsDispatcher - -import logging - -__all__ = ['Client'] - -logger = logging.getLogger('client') - -class Client(EventsDispatcher): - """This class provides way to connect to the notification server as well - as methods to manage the contact list, and the personnal settings. - @sort: __init__, login, logout, state, profile, address_book, - msn_object_store, oim_box, spaces""" - - def __init__(self, server, proxies={}, transport_class=DirectConnection): - """Initializer - - @param server: the Notification server to connect to. - @type server: tuple(host, port) - - @param proxies: proxies that we can use to connect - @type proxies: {type: string => L{gnet.proxy.ProxyInfos}} - - @param transport_class: the transport class to use for the network - connection - @type transport_class: L{pymsn.transport.AbstractTransport}""" - EventsDispatcher.__init__(self) - - self.__state = ClientState.CLOSED - - self._proxies = proxies - self._transport_class = transport_class - self._proxies = proxies - - self._transport = transport_class(server, ServerType.NOTIFICATION, - self._proxies) - self._protocol = msnp.NotificationProtocol(self, self._transport, - self._proxies) - - self._switchboard_manager = SwitchboardManager(self) - self._switchboard_manager.register_handler(SwitchboardConversation) - - self._p2p_session_manager = P2PSessionManager(self) - self._msn_object_store = MSNObjectStore(self) - - self._external_conversations = {} - - self._sso = None - - self._profile = None - self._address_book = None - self._oim_box = None - - self.__die = False - self.__connect_transport_signals() - self.__connect_protocol_signals() - self.__connect_switchboard_manager_signals() - - ### public: - @property - def msn_object_store(self): - """The MSNObjectStore instance associated with this client. - @type: L{MSNObjectStore}""" - return self._msn_object_store - - @property - def profile(self): - """The profile of the current user - @type: L{User}""" - return self._profile - - @property - def address_book(self): - """The address book of the current user - @type: L{AddressBook}""" - return self._address_book - - @property - def oim_box(self): - """The offline IM for the current user - @type: L{OfflineIM}""" - return self._oim_box - - @property - def spaces(self): - """The MSN Spaces of the current user - @type: L{Spaces}""" - return self._spaces - - @property - def state(self): - """The state of this Client - @type: L{pymsn.event.ClientState}""" - return self.__state - - def login(self, account, password): - """Login to the server. - - @param account: the account to use for authentication. - @type account: utf-8 encoded string - - @param password: the password needed to authenticate to the account - @type password: utf-8 encoded string - """ - if (self._state != ClientState.CLOSED): - logger.warning('login already in progress') - self.__die = False - self._profile = profile.Profile((account, password), self._protocol) - self.__connect_profile_signals() - self._transport.establish_connection() - self._state = ClientState.CONNECTING - - def logout(self): - """Logout from the server.""" - if self.__state != ClientState.OPEN: # FIXME: we need something better - return - self.__die = True - self._protocol.signoff() - self._switchboard_manager.close() - self.__state = ClientState.CLOSED - - ### protected: - @rw_property - def _state(): - def fget(self): - return self.__state - def fset(self, state): - self.__state = state - self._dispatch("on_client_state_changed", state) - return locals() - - def _register_external_conversation(self, conversation): - for contact in conversation.participants: - break - - if contact in self._external_conversations: - logger.warning("trying to register an external conversation twice") - return - self._external_conversations[contact] = conversation - - def _unregister_external_conversation(self, conversation): - for contact in conversation.participants: - break - del self._external_conversations[contact] - - ### private: - def __connect_profile_signals(self): - """Connect profile signals""" - def property_changed(profile, pspec): - method_name = "on_profile_%s_changed" % pspec.name.replace("-", "_") - self._dispatch(method_name) - - self.profile.connect("notify::presence", property_changed) - self.profile.connect("notify::display-name", property_changed) - self.profile.connect("notify::personal-message", property_changed) - self.profile.connect("notify::current-media", property_changed) - self.profile.connect("notify::msn-object", property_changed) - - def __connect_contact_signals(self, contact): - """Connect contact signals""" - def event(contact, *args): - event_name = args[-1] - event_args = args[:-1] - method_name = "on_contact_%s" % event_name.replace("-", "_") - self._dispatch(method_name, contact, *event_args) - - def property_changed(contact, pspec): - method_name = "on_contact_%s_changed" % pspec.name.replace("-", "_") - self._dispatch(method_name, contact) - - contact.connect("notify::memberships", property_changed) - contact.connect("notify::presence", property_changed) - contact.connect("notify::display-name", property_changed) - contact.connect("notify::personal-message", property_changed) - contact.connect("notify::current-media", property_changed) - contact.connect("notify::msn-object", property_changed) - contact.connect("notify::client-capabilities", property_changed) - - def connect_signal(name): - contact.connect(name, event, name) - connect_signal("infos-changed") - - def __connect_transport_signals(self): - """Connect transport signals""" - def connect_success(transp): - self._sso = SSO.SingleSignOn(self.profile.account, - self.profile.password, - self._proxies) - self._address_book = AB.AddressBook(self._sso, self._proxies) - self.__connect_addressbook_signals() - self._oim_box = OIM.OfflineMessagesBox(self._sso, self, self._proxies) - self.__connect_oim_box_signals() - self._spaces = Spaces.Spaces(self._sso, self._proxies) - - self._state = ClientState.CONNECTED - - def connect_failure(transp, reason): - self._dispatch("on_client_error", ClientErrorType.NETWORK, reason) - self._state = ClientState.CLOSED - - def disconnected(transp, reason): - if not self.__die: - self._dispatch("on_client_error", ClientErrorType.NETWORK, reason) - self.__die = False - self._state = ClientState.CLOSED - - self._transport.connect("connection-success", connect_success) - self._transport.connect("connection-failure", connect_failure) - self._transport.connect("connection-lost", disconnected) - - def __connect_protocol_signals(self): - """Connect protocol signals""" - def state_changed(proto, param): - state = proto.state - if state == msnp.ProtocolState.AUTHENTICATING: - self._state = ClientState.AUTHENTICATING - elif state == msnp.ProtocolState.AUTHENTICATED: - self._state = ClientState.AUTHENTICATED - elif state == msnp.ProtocolState.SYNCHRONIZING: - self._state = ClientState.SYNCHRONIZING - elif state == msnp.ProtocolState.SYNCHRONIZED: - self._state = ClientState.SYNCHRONIZED - elif state == msnp.ProtocolState.OPEN: - self._state = ClientState.OPEN - im_contacts = self.address_book.contacts - for contact in im_contacts: - self.__connect_contact_signals(contact) - - def authentication_failed(proto): - self._dispatch("on_client_error", ClientErrorType.AUTHENTICATION, - AuthenticationError.INVALID_USERNAME_OR_PASSWORD) - self.__die = True - self._transport.lose_connection() - - def unmanaged_message_received(proto, sender, message): - if sender in self._external_conversations: - conversation = self._external_conversations[sender] - conversation._on_message_received(message) - else: - conversation = ExternalNetworkConversation(self, [sender]) - self._register_external_conversation(conversation) - if self._dispatch("on_invite_conversation", conversation) == 0: - logger.warning("No event handler attached for conversations") - conversation._on_message_received(message) - - self._protocol.connect("notify::state", state_changed) - self._protocol.connect("authentication-failed", authentication_failed) - self._protocol.connect("unmanaged-message-received", unmanaged_message_received) - - def __connect_switchboard_manager_signals(self): - """Connect Switchboard Manager signals""" - def handler_created(switchboard_manager, handler_class, handler): - if handler_class is SwitchboardConversation: - if self._dispatch("on_invite_conversation", handler) == 0: - logger.warning("No event handler attached for conversations") - else: - logger.warning("Unknown Switchboard Handler class %s" % handler_class) - - self._switchboard_manager.connect("handler-created", handler_created) - - def __connect_addressbook_signals(self): - """Connect AddressBook signals""" - def event(address_book, *args): - event_name = args[-1] - event_args = args[:-1] - if event_name == "messenger-contact-added": - self.__connect_contact_signals(event_args[0]) - method_name = "on_addressbook_%s" % event_name.replace("-", "_") - self._dispatch(method_name, *event_args) - def error(address_book, error_code): - self._dispatch("on_client_error", ClientErrorType.ADDRESSBOOK, error_code) - self.__die = True - self._transport.lose_connection() - - self.address_book.connect('error', error) - - def connect_signal(name): - self.address_book.connect(name, event, name) - - connect_signal("messenger-contact-added") - connect_signal("contact-deleted") - connect_signal("contact-blocked") - connect_signal("contact-unblocked") - connect_signal("group-added") - connect_signal("group-deleted") - connect_signal("group-renamed") - connect_signal("group-contact-added") - connect_signal("group-contact-deleted") - - def __connect_oim_box_signals(self): - """Connect Offline IM signals""" - def event(oim_box, *args): - method_name = "on_oim_%s" % args[-1].replace("-", "_") - self._dispatch(method_name, *args[:-1]) - def state_changed(oim_box, pspec): - self._dispatch("on_oim_state_changed", oim_box.state) - def error(oim_box, error_code): - self._dispatch("on_client_error", ClientErrorType.OFFLINE_MESSAGES, error_code) - - self.oim_box.connect("notify::state", state_changed) - self.oim_box.connect('error', error) - - def connect_signal(name): - self.oim_box.connect(name, event, name) - connect_signal("messages-received") - connect_signal("messages-fetched") - connect_signal("message-sent") - connect_signal("messages-deleted") diff --git a/pymsn/pymsn/conversation.py b/pymsn/pymsn/conversation.py deleted file mode 100644 index b47b1d18..00000000 --- a/pymsn/pymsn/conversation.py +++ /dev/null @@ -1,447 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Conversation - -This module contains the classes needed to have a conversation with a -contact.""" - -import msnp -import p2p -from switchboard_manager import SwitchboardClient -from pymsn.event import EventsDispatcher -from pymsn.profile import NetworkID - -import logging -import gobject -from urllib import quote, unquote - -__all__ = ['Conversation', 'ConversationInterface', 'ConversationMessage', 'TextFormat'] - -logger = logging.getLogger('conversation') - - -def Conversation(client, contacts): - """Factory function used to create the appropriate conversation with the - given contacts. - - This is the method you need to use to start a conversation with both MSN - users and Yahoo! users. - @attention: you can only talk to one Yahoo! contact at a time, and you - cannot have multi-user conversations with both MSN and Yahoo! contacts. - - @param contacts: The list of contacts to invite into the conversation - @type contacts: [L{Contact}, ...] - - @returns: a Conversation object implementing L{ConversationInterface} - @rtype: L{ConversationInterface} - """ - msn_contacts = set([contact for contact in contacts \ - if contact.network_id == NetworkID.MSN]) - external_contacts = set(contacts) - msn_contacts - - if len(external_contacts) == 0: - return SwitchboardConversation(client, contacts) - elif len(msn_contacts) != 0: - raise NotImplementedError("The protocol doesn't allow mixing " \ - "contacts from different networks in a single conversation") - elif len(external_contacts) > 1: - raise NotImplementedError("The protocol doesn't allow having " \ - "more than one external contact in a conversation") - elif len(external_contacts) == 1: - return ExternalNetworkConversation(client, contacts) - - -class ConversationInterface(object): - """Interface implemented by all the Conversation objects, a Conversation - object allows the user to communicate with one or more peers""" - - def send_text_message(self, message): - """Send a message to all persons in this conversation. - - @param message: the message to send to the users on this conversation - @type message: L{Contact}""" - raise NotImplementedError - - def send_nudge(self): - """Sends a nudge to the contacts on this conversation.""" - raise NotImplementedError - - def send_typing_notification(self): - """Sends an user typing notification to the contacts on this - conversation.""" - raise NotImplementedError - - def invite_user(self, contact): - """Request a contact to join in the conversation. - - @param contact: the contact to invite. - @type contact: L{Contact}""" - raise NotImplementedError - - def leave(self): - """Leave the conversation.""" - raise NotImplementedError - - -class ConversationMessage(object): - """A Conversation message sent or received - - @ivar display_name: the display name to show for the sender of this message - @type display_name: utf-8 encoded string - - @ivar content: the content of the message - @type content: utf-8 encoded string - - @ivar formatting: the formatting for this message - @type formatting: L{TextFormat} - - @ivar msn_objects: a dictionary mapping smileys - to an L{MSNObject} - @type msn_objects: {smiley: string => L{MSNObject}} - """ - def __init__(self, content, formatting=None, msn_objects={}): - """Initializer - - @param content: the content of the message - @type content: utf-8 encoded string - - @param formatting: the formatting for this message - @type formatting: L{TextFormat} - - @param msn_objects: a dictionary mapping smileys - to an L{MSNObject} - @type msn_objects: {smiley: string => L{MSNObject}}""" - self.display_name = None - self.content = content - self.formatting = formatting - self.msn_objects = msn_objects - -class TextFormat(object): - - DEFAULT_FONT = 'MS Sans Serif' - - # effects - NO_EFFECT = 0 - BOLD = 1 - ITALIC = 2 - UNDERLINE = 4 - STRIKETHROUGH = 8 - - # charset - ANSI_CHARSET = '0' - DEFAULT_CHARSET = '1' - SYMBOL_CHARSET = '2' - MAC_CHARSETLT = '4d' - SHIFTJIS_CHARSET = '80' - HANGEUL_CHARSET = '81' - JOHAB_CHARSET = '82' - GB2312_CHARSET = '86' - CHINESEBIG5_CHARSET = '88' - GREEK_CHARSET = 'a1' - TURKISH_CHARSET = 'a2' - VIETNAMESE_CHARSET = 'a3' - HEBREW_CHARSET = 'b1' - ARABIC_CHARSET = 'b2' - BALTIC_CHARSET = 'ba' - RUSSIAN_CHARSET_DEFAULT = 'cc' - THAI_CHARSET = 'de' - EASTEUROPE_CHARSET = 'ee' - OEM_DEFAULT = 'ff' - - # family - FF_DONTCARE = 0 - FF_ROMAN = 1 - FF_SWISS = 2 - FF_MODERN = 3 - FF_SCRIPT = 4 - FF_DECORATIVE = 5 - - # pitch - DEFAULT_PITCH = 0 - FIXED_PITCH = 1 - VARIABLE_PITCH = 2 - - @staticmethod - def parse(format): - text_format = TextFormat() - text_format.__parse(format) - return text_format - - @property - def font(self): - return self._font - - @property - def style(self): - return self._style - - @property - def color(self): - return self._color - - @property - def right_alignment(self): - return self._right_alignment - - @property - def charset(self): - return self._charset - - @property - def pitch(self): - return self._pitch - - @property - def family(self): - return self._family - - def __init__(self, font=DEFAULT_FONT, style=NO_EFFECT, color='0', - charset=DEFAULT_CHARSET, family=FF_DONTCARE, - pitch=DEFAULT_PITCH, right_alignment=False): - self._font = font - self._style = style - self._color = color - self._charset = charset - self._pitch = pitch - self._family = family - self._right_alignment = right_alignment - - def __parse(self, format): - for property in format.split(';'): - key, value = [p.strip(' \t|').upper() \ - for p in property.split('=', 1)] - if key == 'FN': - # Font - self._font = unquote(value) - elif key == 'EF': - # Effects - if 'B' in value: self._style |= TextFormat.BOLD - if 'I' in value: self._style |= TextFormat.ITALIC - if 'U' in value: self._style |= TextFormat.UNDERLINE - if 'S' in value: self._style |= TextFormat.STRIKETHROUGH - elif key == 'CO': - # Color - value = value.zfill(6) - self._color = ''.join((value[4:6], value[2:4], value[0:2])) - elif key == 'CS': - # Charset - self._charset = value - elif key == 'PF': - # Family and pitch - value = value.zfill(2) - self._family = int(value[0]) - self._pitch = int(value[1]) - elif key == 'RL': - # Right alignment - if value == '1': self._right_alignement = True - - def __str__(self): - style = '' - if self._style & TextFormat.BOLD == TextFormat.BOLD: - style += 'B' - if self._style & TextFormat.ITALIC == TextFormat.ITALIC: - style += 'I' - if self._style & TextFormat.UNDERLINE == TextFormat.UNDERLINE: - style += 'U' - if self._style & TextFormat.STRIKETHROUGH == TextFormat.STRIKETHROUGH: - style += 'S' - - color = '%s%s%s' % (self._color[4:6], self._color[2:4], self._color[0:2]) - - format = 'FN=%s; EF=%s; CO=%s; CS=%s; PF=%d%d' % (quote(self._font), - style, color, - self._charset, - self._family, - self._pitch) - if self._right_alignment: format += '; RL=1' - - return format - - def __repr__(self): - return __str__(self) - - -class AbstractConversation(ConversationInterface, EventsDispatcher): - def __init__(self, client): - self._client = client - ConversationInterface.__init__(self) - EventsDispatcher.__init__(self) - - self.__last_received_msn_objects = {} - - def send_text_message(self, message): - if len(message.msn_objects) > 0: - body = [] - for alias, msn_object in message.msn_objects.iteritems(): - self._client._msn_object_store.publish(msn_object) - body.append(alias.encode("utf-8")) - body.append(str(msn_object)) - # FIXME : we need to distinguish animemoticon and emoticons - # and send the related msn objects in separated messages - self._send_message(("text/x-mms-animemoticon",), '\t'.join(body)) - - content_type = ("text/plain","utf-8") - body = message.content.encode("utf-8") - ack = msnp.MessageAcknowledgement.HALF - headers = {} - if message.formatting is not None: - headers["X-MMS-IM-Format"] = str(message.formatting) - - self._send_message(content_type, body, headers, ack) - - def send_nudge(self): - content_type = "text/x-msnmsgr-datacast" - body = "ID: 1\r\n\r\n".encode('UTF-8') #FIXME: we need to figure out the datacast objects :D - ack = msnp.MessageAcknowledgement.NONE - self._send_message(content_type, body, ack=ack) - - def send_typing_notification(self): - content_type = "text/x-msmsgscontrol" - body = "\r\n\r\n".encode('UTF-8') - headers = { "TypingUser" : self._client.profile.account.encode('UTF_8') } - ack = msnp.MessageAcknowledgement.NONE - self._send_message(content_type, body, headers, ack) - - def invite_user(self, contact): - raise NotImplementedError - - def leave(self): - raise NotImplementedError - - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - raise NotImplementedError - - def _on_contact_joined(self, contact): - self._dispatch("on_conversation_user_joined", contact) - - def _on_contact_left(self, contact): - self._dispatch("on_conversation_user_left", contact) - - def _on_message_received(self, message): - sender = message.sender - message_type = message.content_type[0] - message_encoding = message.content_type[1] - try: - message_formatting = message.get_header('X-MMS-IM-Format') - except KeyError: - message_formatting = '=' - - if message_type == 'text/plain': - msg = ConversationMessage(unicode(message.body, message_encoding), - TextFormat.parse(message_formatting), - self.__last_received_msn_objects) - try: - display_name = message.get_header('P4-Context') - except KeyError: - display_name = sender.display_name - msg.display_name = display_name - self._dispatch("on_conversation_message_received", sender, msg) - self.__last_received_msn_objects = {} - elif message_type == 'text/x-msmsgscontrol': - self._dispatch("on_conversation_user_typing", sender) - elif message_type in ['text/x-mms-emoticon', - 'text/x-mms-animemoticon']: - msn_objects = {} - parts = message.body.split('\t') - logger.debug(parts) - for i in [i for i in range(len(parts)) if not i % 2]: - if parts[i] == '': break - msn_objects[parts[i]] = p2p.MSNObject.parse(self._client, - parts[i+1]) - self.__last_received_msn_objects = msn_objects - elif message_type == 'text/x-msnmsgr-datacast' and \ - message.body.strip() == "ID: 1": - self._dispatch("on_conversation_nudge_received", sender) - - def _on_message_sent(self, message): - pass - - def _on_error(self, error_type, error): - self._dispatch("on_conversation_error", error_type, error) - - -class ExternalNetworkConversation(AbstractConversation): - def __init__(self, client, contacts): - AbstractConversation.__init__(self, client) - self.participants = set(contacts) - client._register_external_conversation(self) - gobject.idle_add(self._open) - - def _open(self): - for contact in self.participants: - self._on_contact_joined(contact) - return False - - def invite_user(self, contact): - raise NotImplementedError("The protocol doesn't allow multiuser " \ - "conversations for external contacts") - - def leave(self): - self._client._unregister_external_conversation(self) - - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - if content_type[0] in ['text/x-mms-emoticon', - 'text/x-mms-animemoticon']: - return - message = msnp.Message(self._client.profile) - for key, value in headers.iteritems(): - message.add_header(key, value) - message.content_type = content_type - message.body = body - for contact in self.participants: - self._client._protocol.\ - send_unmanaged_message(contact, message) - - -class SwitchboardConversation(AbstractConversation, SwitchboardClient): - def __init__(self, client, contacts): - SwitchboardClient.__init__(self, client, contacts, priority=0) - AbstractConversation.__init__(self, client) - - @staticmethod - def _can_handle_message(message, switchboard_client=None): - content_type = message.content_type[0] - if switchboard_client is None: - return content_type in ('text/plain', 'text/x-msnmsgr-datacast') - # FIXME : we need to not filter those 'text/x-mms-emoticon', 'text/x-mms-animemoticon' - return content_type in ('text/plain', 'text/x-msmsgscontrol', - 'text/x-msnmsgr-datacast', 'text/x-mms-emoticon', - 'text/x-mms-animemoticon') - - def invite_user(self, contact): - """Request a contact to join in the conversation. - - @param contact: the contact to invite. - @type contact: L{profile.Contact}""" - SwitchboardClient._invite_user(self, contact) - - def leave(self): - """Leave the conversation.""" - SwitchboardClient._leave(self) - - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - SwitchboardClient._send_message(self, content_type, body, headers, ack) - - diff --git a/pymsn/pymsn/event/__init__.py b/pymsn/pymsn/event/__init__.py deleted file mode 100644 index 0c9b6049..00000000 --- a/pymsn/pymsn/event/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""pymsn event interfaces. - -Defines the interfaces that the client can implement to benefit from the -client event notifications.""" - -from pymsn.util.weak import WeakSet - -class EventsDispatcher(object): - """Abstract object from which all the objects generating events inherit""" - - def __init__(self): - self._events_handlers = WeakSet() - - ### Callbacks - def register_events_handler(self, events_handler): - """Registers an event handler with this dispatcher - @param events_handler: an instance with methods as code of callbacks - @type events_handler: L{pymsn.event.BaseEventInterface} - """ - self._events_handlers.add(events_handler) - - def _dispatch(self, name, *args): - count = 0 - for event_handler in list(self._events_handlers): - if event_handler._dispatch_event(name, *args): - count += 1 - return count - -import weakref -class BaseEventInterface(object): - """Event handler interface, implemented by all the event handlers""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: an object implementing L{EventsDispatcher}""" - self._client = weakref.proxy(client) - client.register_events_handler(self) - - def _dispatch_event(self, event_name, *params): - try: - handler = getattr(self, event_name) - except Exception, e: - return False - - handler(*params) - return True - -from client import * -from conversation import * -from profile import * -from contact import * -from address_book import * -from offline_messages import * -from invite import * diff --git a/pymsn/pymsn/event/address_book.py b/pymsn/pymsn/event/address_book.py deleted file mode 100644 index dac2454e..00000000 --- a/pymsn/pymsn/event/address_book.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from pymsn.event import BaseEventInterface - -__all__ = ["AddressBookEventInterface"] - -class AddressBookEventInterface(BaseEventInterface): - def __init__(self, client): - BaseEventInterface.__init__(self, client) - - def on_addressbook_messenger_contact_added(self, contact): - pass - - def on_addressbook_contact_deleted(self, contact): - pass - - def on_addressbook_contact_blocked(self, contact): - pass - - def on_addressbook_contact_unblocked(self, contact): - pass - - def on_addressbook_group_added(self, group): - pass - - def on_addressbook_group_deleted(self, group): - pass - - def on_addressbook_group_renamed(self, group): - pass - - def on_addressbook_group_contact_added(self, group, contact): - pass - - def on_addressbook_group_contact_deleted(self, group, contact): - pass - diff --git a/pymsn/pymsn/event/client.py b/pymsn/pymsn/event/client.py deleted file mode 100644 index f2378f22..00000000 --- a/pymsn/pymsn/event/client.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Client event interfaces - -The interfaces defined in this module allow receiving core notification events -from the client. - - @sort: ClientEventInterface - @group Enums: ClientState, ClientErrorType - @group Error Enums: NetworkError, AuthenticationError, ProtocolError, - AddressBookError, OfflineMessagesBoxError""" - -from pymsn.event import BaseEventInterface - -import pymsn.gnet -import pymsn.service.AddressBook.constants -import pymsn.service.OfflineIM.constants -import pymsn.msnp - -__all__ = [ "ClientState", "ClientErrorType", - "NetworkError", "AuthenticationError", "ProtocolError", - "AddressBookError", "OfflineMessagesBoxError", - "ClientEventInterface" ] - -class ClientState(object): - "L{Client} states" - CLOSED = 0 - CONNECTING = 1 - CONNECTED = 2 - AUTHENTICATING = 3 - AUTHENTICATED = 4 - SYNCHRONIZING = 5 - SYNCHRONIZED = 6 - OPEN = 7 - -class ClientErrorType(object): - """L{Client} error types - @see: L{ClientEventInterface.on_client_error}""" - - NETWORK = 0 - "Network related errors" - AUTHENTICATION = 1 - "Authentication related errors" - PROTOCOL = 2 - "Protocol related errors" - ADDRESSBOOK = 3 - "Address book related errors" - OFFLINE_MESSAGES = 4 - "Offline IM related errors" - -NetworkError = pymsn.gnet.IoError -"Network related errors" - -class AuthenticationError(object): - "Authentication related errors" - UNKNOWN = 0 - INVALID_USERNAME = 1 - INVALID_PASSWORD = 2 - INVALID_USERNAME_OR_PASSWORD = 3 - -class ProtocolError(object): - "Protocol related errors" - UNKNOWN = 0 - -AddressBookError = pymsn.service.AddressBook.constants.AddressBookError -OfflineMessagesBoxError = pymsn.service.OfflineIM.constants.OfflineMessagesBoxError - - -class ClientEventInterface(BaseEventInterface): - """Interface allowing the user to get notified about the - L{Client} events""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_client_state_changed(self, state): - """Called when the state of the L{Client} changes. - @param state: the new state of the client - @type state: L{ClientState}""" - pass - - def on_client_error(self, type, error): - """Called when an error occurs in the L{Client}. - - @param type: the error type - @type type: L{ClientErrorType} - - @param error: the error code - @type error: L{NetworkError} or L{AuthenticationError} or - L{ProtocolError} or L{AddressBookError} or - L{OfflineMessagesBoxError}""" - pass - diff --git a/pymsn/pymsn/event/contact.py b/pymsn/pymsn/event/contact.py deleted file mode 100644 index 893c5cb6..00000000 --- a/pymsn/pymsn/event/contact.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Contact event interfaces - -The interfaces defined in this module allow receiving notification events -from the contacts.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["ContactEventInterface"] - -class ContactEventInterface(BaseEventInterface): - """Interface allowing the user to get notified about the - L{Contact}s events""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_contact_memberships_changed(self, contact): - """Called when the memberships of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact} - @see: L{Memberships}""" - pass - - def on_contact_presence_changed(self, contact): - """Called when the presence of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_display_name_changed(self, contact): - """Called when the display name of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_personal_message_changed(self, contact): - """Called when the personal message of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_current_media_changed(self, contact): - """Called when the current media of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_infos_changed(self, contact, infos): - """Called when the infos of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_client_capabilities_changed(self, contact): - """Called when the client capabilities of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_msn_object_changed(self, contact): - """Called when the MSNObject of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact} - - @see: L{MSNObjectStore}, - L{MSNObject}""" - pass - diff --git a/pymsn/pymsn/event/conversation.py b/pymsn/pymsn/event/conversation.py deleted file mode 100644 index 7cff5d37..00000000 --- a/pymsn/pymsn/event/conversation.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Conversation event interfaces - -The interfaces defined in this module allow receiving notification events -from a L{Conversation} object.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["ConversationEventInterface", "ConversationErrorType", - "ContactInviteError", "MessageError"] - - -class ConversationErrorType(object): - """L{Client} error types - @see: L{ClientEventInterface.on_client_error}""" - - NETWORK = 0 - "Network related errors" - AUTHENTICATION = 1 - "Authentication related errors" - PROTOCOL = 2 - "Protocol related errors" - CONTACT_INVITE = 3 - "Contact invitation related errors" - MESSAGE = 4 - "Message sending related errors" - - -class ContactInviteError(object): - "Contact invitation related errors" - UNKNOWN = 0 - - NOT_AVAILABLE = 1 - - -class MessageError(object): - "Message related errors" - UNKNOWN = 0 - - DELIVERY_FAILED = 1 - - -class ConversationEventInterface(BaseEventInterface): - """interfaces allowing the user to get notified about events - from a L{Conversation} object.""" - - def __init__(self, conversation): - """Initializer - @param conversation: the conversation we want to be notified for its events - @type conversation: L{Conversation}""" - BaseEventInterface.__init__(self, conversation) - - def on_conversation_state_changed(self, state): - """@attention: not implemented""" - pass - - def on_conversation_error(self, type, error): - """Called when an error occurs in the L{Client}. - - @param type: the error type - @type type: L{ClientErrorType} - - @param error: the error code - @type error: L{NetworkError} or L{AuthenticationError} or - L{ProtocolError} or L{ContactInviteError} or - L{MessageError}""" - pass - - def on_conversation_user_joined(self, contact): - """Called when a user joins the conversation. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_conversation_user_left(self, contact): - """Called when a user leaved the conversation. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_conversation_user_typing(self, contact): - """Called when a user is typing. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_conversation_message_received(self, sender, message): - """Called when a user sends a message. - @param sender: the contact who sent the message - @type sender: L{Contact} - - @param message: the message - @type message: L{ConversationMessage}""" - pass - - def on_conversation_nudge_received(self, sender): - """Called when a user sends a nudge. - @param sender: the contact who sent the nudge - @type sender: L{Contact}""" - pass - diff --git a/pymsn/pymsn/event/invite.py b/pymsn/pymsn/event/invite.py deleted file mode 100644 index 37cdd247..00000000 --- a/pymsn/pymsn/event/invite.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Invite event interfaces - -The interfaces defined in this module allow receiving notification events when -we get invited into an activity with other users.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["InviteEventInterface"] - -class InviteEventInterface(BaseEventInterface): - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_invite_conversation(self, conversation): - """Called when we get invited into a conversation - @param conversation: the conversation - @type conversation: L{Conversation}""" - pass - diff --git a/pymsn/pymsn/event/offline_messages.py b/pymsn/pymsn/event/offline_messages.py deleted file mode 100644 index f7f5b5e3..00000000 --- a/pymsn/pymsn/event/offline_messages.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Offline IM event interfaces - -The interfaces defined in this module allow receiving notification events about -Offline messages.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["OfflineMessagesEventInterface"] - -class OfflineMessagesEventInterface(BaseEventInterface): - """interfaces allowing the user to get notified about events from the - Offline IM box.""" - - def __init__(self, client): - BaseEventInterface.__init__(self, client) - - def on_oim_state_changed(self, state): - pass - - def on_oim_messages_received(self, messages): - pass - - def on_oim_messages_fetched(self, messages): - pass - - def on_oim_messages_deleted(self): - pass - - def on_oim_message_sent(self, recipient, message): - pass - diff --git a/pymsn/pymsn/event/profile.py b/pymsn/pymsn/event/profile.py deleted file mode 100644 index 89e844e1..00000000 --- a/pymsn/pymsn/event/profile.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2008 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Profile event interfaces - -The interfaces defined in this module allow receiving notification events when -the user's profile has been effectively changed on the server.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["ProfileEventInterface"] - -class ProfileEventInterface(BaseEventInterface): - """Interface allowing the user to get notified about - L{Profile}s events""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_profile_presence_changed(self): - """Called when the presence changes.""" - pass - - def on_profile_display_name_changed(self): - """Called when the display name changes.""" - pass - - def on_profile_personal_message_changed(self): - """Called when the personal message changes.""" - pass - - def on_profile_current_media_changed(self): - """Called when the current media changes.""" - pass - - def on_profile_msn_object_changed(self): - """Called when the MSNObject changes.""" - pass diff --git a/pymsn/pymsn/gnet/__init__.py b/pymsn/pymsn/gnet/__init__.py deleted file mode 100644 index e699431d..00000000 --- a/pymsn/pymsn/gnet/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""GNet - glib based asynchronous networking library. - -The GNet library was designed as a replacement for the python asyncore -and asynchat modules that easily integrate with the glib main loop. -""" -from constants import IoStatus, IoError -import io diff --git a/pymsn/pymsn/gnet/constants.py b/pymsn/pymsn/gnet/constants.py deleted file mode 100644 index 80154efe..00000000 --- a/pymsn/pymsn/gnet/constants.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Constants used in GNet.""" -from socket import AF_INET, AF_INET6, \ - SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET -try: - from socket import AF_UNIX -except ImportError: - pass - -class GNet(object): - NAME = "gnet" - VERSION = "0.1" - -class IoStatus(object): - """Various networking status""" - CLOSING = 0 - CLOSED = 1 - OPENING = 2 - OPEN = 3 - -class IoError(object): - """I/O error codes""" - UNKNOWN = 0 - - CONNECTION_FAILED = 1 - CONNECTION_TIMED_OUT = 2 - - SSL_CONNECTION_FAILED = 10 - SSL_PROTOCOL_ERROR = 11 - - PROXY_CONNECTION_FAILED = 20 - PROXY_AUTHENTICATION_REQUIRED = 21 - PROXY_FORBIDDEN = 22 - diff --git a/pymsn/pymsn/gnet/io/__init__.py b/pymsn/pymsn/gnet/io/__init__.py deleted file mode 100644 index 8b9109d1..00000000 --- a/pymsn/pymsn/gnet/io/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""GNet IO layer, provide most commonly used IO transports to be used -by higher level classes""" - -from abstract import * -from sock import * -from tcp import * -from ssl_socket import * -from ssl_tcp import * diff --git a/pymsn/pymsn/gnet/io/abstract.py b/pymsn/pymsn/gnet/io/abstract.py deleted file mode 100644 index d860dbbb..00000000 --- a/pymsn/pymsn/gnet/io/abstract.py +++ /dev/null @@ -1,189 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.gnet.constants import * - -import gobject - -__all__ = ['AbstractClient'] - -class AbstractClient(gobject.GObject): - """Abstract client base class. - All network client classes implements this interface. - - @sort: __init__, open, send, close - @undocumented: do_*, _configure, _pre_open, _post_open - - @since: 0.1""" - - __gproperties__ = { - "host": (gobject.TYPE_STRING, - "Remote Host", - "The remote host to connect to.", - "", - gobject.PARAM_READWRITE), - - "port": (gobject.TYPE_INT, - "Remote Port", - "The remote port to connect to.", - -1, 65535, -1, - gobject.PARAM_READWRITE), - - "status": (gobject.TYPE_INT, - "Connection Status", - "The status of this connection.", - 0, 3, IoStatus.CLOSED, - gobject.PARAM_READABLE), - } - - __gsignals__ = { - "error": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (gobject.TYPE_ULONG,)), - - "received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, gobject.TYPE_ULONG)), - - "sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, gobject.TYPE_ULONG)), - } - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - """Initializer - - @param host: the hostname to connect to. - @type host: string - - @param port: a port number to connect to - @type port: integer > 0 and < 65536 - - @param domain: the communication domain. - @type domain: integer - @see socket module - - @param type: the communication semantics - @type type: integer - @see socket module - """ - gobject.GObject.__init__(self) - self._host = host - self._port = port - self._domain = domain - self._type = type - self._transport = None - self.__status = IoStatus.CLOSED - - def __del__(self): - self.close() - - # opening state methods - def _configure(self): - if len(self._host) == 0 or self._port < 0 or self._port > 65535: - raise ValueError("Wrong host or port number : (%s, %d)" % \ - (self._host, self._port) ) - if self.status in (IoStatus.OPENING, IoStatus.OPEN): - return False - assert(self.status == IoStatus.CLOSED) - return True - - def _pre_open(self, io_object=None): - self._status = IoStatus.OPENING - - def _post_open(self): - pass - - # public API - def open(self): - """Open the connection.""" - raise NotImplementedError - - - def close(self): - """Close the connection.""" - raise NotImplementedError - - def send(self, buffer, callback=None, *args): - """Send data to the server. - - @param buffer: data buffer. - @type buffer: string - - @param callback: a callback method that would be called when the - data is atually sent to the server. - @type callback: callback - - @param args: callback arguments to be passed to the callback. - """ - raise NotImplementedError - - # properties - def __get_host(self): - return self._host - def __set_host(self, host): - self.set_property("host", host) - host = property(__get_host, __set_host) - - def __get_port(self): - return self._port - def __set_port(self, port): - self.set_property("port", port) - port = property(__get_port, __set_port) - - @property - def domain(self): - return self._domain - - @property - def type(self): - return self._type - - def __get_status(self): - return self.__status - def __set_status(self, new_status): - if self.__status != new_status: - self.__status = new_status - self.notify("status") - _status = property(__get_status, __set_status) - status = property(__get_status) - - def do_get_property(self, pspec): - if pspec.name == "host": - return self._host - elif pspec.name == "port": - return self._port - elif pspec.name == "status": - return self.__status - else: - raise AttributeError, "unknown property %s" % pspec.name - - def do_set_property(self, pspec, value): - if pspec.name == "host": - if len(value) == 0: - raise ValueError("Wrong host %s" % self._host) - self._host = value - elif pspec.name == "port": - if self._port < 0 or self._port > 65535: - raise ValueError("Wrong port %d" % self._port) - self._port = value - else: - raise AttributeError, "unknown property %s" % pspec.name -gobject.type_register(AbstractClient) diff --git a/pymsn/pymsn/gnet/io/iochannel.py b/pymsn/pymsn/gnet/io/iochannel.py deleted file mode 100644 index 8139f5d2..00000000 --- a/pymsn/pymsn/gnet/io/iochannel.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.gnet.constants import * -from pymsn.gnet.resolver import * -from abstract import AbstractClient - -import gobject -from errno import * - -__all__ = ['GIOChannelClient'] - -class OutgoingPacket(object): - """Represents a packet to be sent over the IO channel""" - def __init__(self, buffer, size, callback=None, *cb_args): - self.buffer = buffer - self.size = size - self._sent = 0 - self._callback = callback - self._callback_args = cb_args - - def read(self, size=2048): - if size is not None: - return self.buffer[self._sent:][0:size] - return self.buffer[self._sent:] - - def sent(self, size): - """update how many bytes have been sent""" - self._sent += size - - def is_complete(self): - """return whether this packet was completely transmitted or not""" - return self.size == self._sent - - def callback(self): - """Run the callback function if supplied""" - if self._callback is not None: - self._callback(*self._callback_args) - - -class GIOChannelClient(AbstractClient): - """Base class for clients using GIOChannel facilities - - @sort: __init__, open, send, close - @undocumented: do_*, _configure, _pre_open, _post_open - - @since: 0.1""" - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - AbstractClient.__init__(self, host, port, domain, type) - - def _pre_open(self, io_object): - io_object.setblocking(False) - channel = gobject.IOChannel(io_object.fileno()) - channel.set_flags(channel.get_flags() | gobject.IO_FLAG_NONBLOCK) - channel.set_encoding(None) - channel.set_buffered(False) - - self._transport = io_object - self._channel = channel - - self._source_id = None - self._source_condition = 0 - self._outgoing_queue = [] - AbstractClient._pre_open(self) - - def _post_open(self): - AbstractClient._post_open(self) - self._watch_remove() - - def _open(self, host, port): - resolver = HostnameResolver() - resolver.query(host, (self.__open, host, port)) - - def __open(self, resolve_response, host, port): - if resolve_response.status != 0: - self.emit("error", IoError.CONNECTION_FAILED) - self._transport.close() - return - else: - host = resolve_response.answer[0][1] - err = self._transport.connect_ex((host, port)) - self._watch_set_cond(gobject.IO_PRI | gobject.IO_IN | gobject.IO_OUT | - gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL, - lambda chan, cond: self._post_open()) - if err in (0, EINPROGRESS, EALREADY, EWOULDBLOCK, EISCONN): - return - elif err in (EHOSTUNREACH, EHOSTDOWN, ECONNREFUSED, ECONNABORTED, - ENETUNREACH, ENETDOWN): - self.emit("error", IoError.CONNECTION_FAILED) - self._transport.close() - - # convenience methods - def _watch_remove(self): - if self._source_id is not None: - gobject.source_remove(self._source_id) - self._source_id = None - self._source_condition = 0 - - def _watch_set_cond(self, cond, handler=None): - self._watch_remove() - self._source_condition = cond - if handler is None: - handler = self._io_channel_handler - self._source_id = self._channel.add_watch(cond, handler) - - def _watch_add_cond(self, cond): - if self._source_condition & cond == cond: - return - self._source_condition |= cond - self._watch_set_cond(self._source_condition) - - def _watch_remove_cond(self, cond): - if self._source_condition & cond == 0: - return - self._source_condition ^= cond - self._watch_set_cond(self._source_condition) - - # public API - def open(self): - if not self._configure(): - return - self._pre_open() - self._open(self._host, self._port) - - def close(self): - if self._status in (IoStatus.CLOSING, IoStatus.CLOSED): - return - self._status = IoStatus.CLOSING - self._watch_remove() - try: - self._channel.close() - self._transport.shutdown(socket.SHUT_RDWR) - except: - pass - self._transport.close() - self._status = IoStatus.CLOSED - - def send(self, buffer, callback=None, *args): - assert(self._status == IoStatus.OPEN), self._status - self._outgoing_queue.append(OutgoingPacket(buffer, len(buffer), - callback, *args)) - self._watch_add_cond(gobject.IO_OUT) -gobject.type_register(GIOChannelClient) diff --git a/pymsn/pymsn/gnet/io/sock.py b/pymsn/pymsn/gnet/io/sock.py deleted file mode 100644 index a588957f..00000000 --- a/pymsn/pymsn/gnet/io/sock.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.gnet.constants import * -from iochannel import GIOChannelClient - -import gobject -import socket - - -__all__ = ['SocketClient'] - -class SocketClient(GIOChannelClient): - """Asynchronous Socket client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - GIOChannelClient.__init__(self, host, port, domain, type) - - - def _pre_open(self, sock=None): - if sock is None: - sock = socket.socket(self._domain, self._type) - try: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - except AttributeError: - pass - GIOChannelClient._pre_open(self, sock) - - def _post_open(self): - GIOChannelClient._post_open(self) - opts = self._transport.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - if opts == 0: - self._watch_set_cond(gobject.IO_IN | gobject.IO_PRI | - gobject.IO_ERR | gobject.IO_HUP) - self._status = IoStatus.OPEN - else: - self.emit("error", IoError.CONNECTION_FAILED) - self._status = IoStatus.CLOSED - return False - - def _io_channel_handler(self, chan, cond): - if self._status == IoStatus.CLOSED: - return False - - if cond & (gobject.IO_IN | gobject.IO_PRI): - buf = "" - try: - buf = self._channel.read(2048) - except gobject.GError: - pass - if buf == "": - self.close() - return False - self.emit("received", buf, len(buf)) - - # Check for error/EOF - if cond & (gobject.IO_ERR | gobject.IO_HUP): - self.close() - return False - - if cond & gobject.IO_OUT: - if len(self._outgoing_queue) > 0: # send next item - item = self._outgoing_queue[0] - item.sent(self._channel.write(item.read())) - if item.is_complete(): # sent item - self.emit("sent", item.buffer, item.size) - item.callback() - del self._outgoing_queue[0] - del item - if len(self._outgoing_queue) == 0: - self._watch_remove_cond(gobject.IO_OUT) - else: - self._watch_remove_cond(gobject.IO_OUT) - - return True -gobject.type_register(SocketClient) diff --git a/pymsn/pymsn/gnet/io/ssl_socket.py b/pymsn/pymsn/gnet/io/ssl_socket.py deleted file mode 100644 index bf8ebc07..00000000 --- a/pymsn/pymsn/gnet/io/ssl_socket.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.gnet.constants import * -from iochannel import GIOChannelClient - -import gobject -import socket -import OpenSSL.SSL as OpenSSL - -__all__ = ['SSLSocketClient'] - -class SSLSocketClient(GIOChannelClient): - """Asynchronous Socket client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - GIOChannelClient.__init__(self, host, port, domain, type) - - def _pre_open(self, sock=None): - if sock is None: - sock = socket.socket(self._domain, self._type) - try: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - except AttributeError: - pass - context = OpenSSL.Context(OpenSSL.SSLv23_METHOD) - ssl_sock = OpenSSL.Connection(context, sock) - GIOChannelClient._pre_open(self, ssl_sock) - - def _post_open(self): - GIOChannelClient._post_open(self) - if self._transport.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) == 0: - self._watch_set_cond(gobject.IO_IN | gobject.IO_PRI | gobject.IO_OUT | - gobject.IO_ERR | gobject.IO_HUP) - else: - self.emit("error", IoError.CONNECTION_FAILED) - self._status = IoStatus.CLOSED - return False - - def _io_channel_handler(self, chan, cond): - if self._status == IoStatus.CLOSED: - return False - if self._status == IoStatus.OPENING: - try: - self._transport.do_handshake() - except (OpenSSL.WantX509LookupError, - OpenSSL.WantReadError, OpenSSL.WantWriteError): - return True - except (OpenSSL.ZeroReturnError, OpenSSL.SysCallError): - self.emit("error", IoError.SSL_CONNECTION_FAILED) - self.close() - return False - else: - self._status = IoStatus.OPEN - elif self._status == IoStatus.OPEN: - if cond & (gobject.IO_IN | gobject.IO_PRI): - try: - buf = self._transport.recv(2048) - except (OpenSSL.WantX509LookupError, - OpenSSL.WantReadError, OpenSSL.WantWriteError): - return True - except (OpenSSL.ZeroReturnError, OpenSSL.SysCallError): - self.close() - return False - self.emit("received", buf, len(buf)) - - if cond & (gobject.IO_ERR | gobject.IO_HUP): - self.close() - return False - - if cond & gobject.IO_OUT: - if len(self._outgoing_queue) > 0: # send next item - item = self._outgoing_queue[0] - try: - ret = self._transport.send(item.read()) - except (OpenSSL.WantX509LookupError, - OpenSSL.WantReadError, OpenSSL.WantWriteError): - return True - except (OpenSSL.ZeroReturnError, OpenSSL.SysCallError): - self.close() - return False - item.sent(ret) - if item.is_complete(): # sent item - self.emit("sent", item.buffer, item.size) - item.callback() - del self._outgoing_queue[0] - del item - if len(self._outgoing_queue) == 0: - self._watch_remove_cond(gobject.IO_OUT) - else: - self._watch_remove_cond(gobject.IO_OUT) - - return True - -gobject.type_register(SSLSocketClient) diff --git a/pymsn/pymsn/gnet/io/ssl_tcp.py b/pymsn/pymsn/gnet/io/ssl_tcp.py deleted file mode 100644 index ed36c6e9..00000000 --- a/pymsn/pymsn/gnet/io/ssl_tcp.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.gnet.constants import * -from pymsn.gnet.proxy.proxyfiable import ProxyfiableClient -from ssl_socket import SSLSocketClient - -import gobject - -__all__ = ['SSLTCPClient'] - -class SSLTCPClient(SSLSocketClient, ProxyfiableClient): - """Asynchronous TCP client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port): - """initializer - - @param host: the hostname to connect to. - @type host: string - - @param port: the port number to connect to. - @type port: integer > 0 and < 65536""" - SSLSocketClient.__init__(self, host, port, AF_INET, SOCK_STREAM) - ProxyfiableClient.__init__(self) -gobject.type_register(SSLTCPClient) diff --git a/pymsn/pymsn/gnet/io/tcp.py b/pymsn/pymsn/gnet/io/tcp.py deleted file mode 100644 index bd530ba4..00000000 --- a/pymsn/pymsn/gnet/io/tcp.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.gnet.constants import * -from pymsn.gnet.proxy.proxyfiable import ProxyfiableClient -from sock import SocketClient - -import gobject - -__all__ = ['TCPClient'] - -class TCPClient(SocketClient, ProxyfiableClient): - """Asynchronous TCP client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port): - """initializer - - @param host: the hostname to connect to. - @type host: string - - @param port: the port number to connect to. - @type port: integer > 0 and < 65536""" - SocketClient.__init__(self, host, port, AF_INET, SOCK_STREAM) - ProxyfiableClient.__init__(self) -gobject.type_register(TCPClient) diff --git a/pymsn/pymsn/gnet/message/HTTP.py b/pymsn/pymsn/gnet/message/HTTP.py deleted file mode 100644 index a36d21e5..00000000 --- a/pymsn/pymsn/gnet/message/HTTP.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""HTTP Messages structures.""" -from UserDict import DictMixin - -from pymsn.gnet.constants import * - -__all__ = ['HTTPMessage', 'HTTPResponse', 'HTTPRequest'] - -class odict(DictMixin): - def __init__(self, dict=None): - self._keys = [] - self.data = dict or {} - - def __getitem__(self, key): - return self.data[key] - - def __delitem__(self, key): - del self.data[key] - self._keys.remove(key) - - def __setitem__(self, key, item): - self.data[key] = item - if key not in self._keys: - self._keys.append(key) - - def keys(self): - return self._keys[:] - - -class HTTPMessage(object): - """HTTP style message abstraction - - @ivar headers: HTTP style headers of the message - @type headers: dict() - - @ivar body: HTTP Message Body - @type body: string - """ - def __init__(self): - self.clear() - - def add_header(self, name, value): - """Add the header with the given name to the set of headers of - this message - - @param name: name of the header - @param value: value of the header""" - value = str(value) - self.headers[name] = value - - def get_header(self, name): - """Returns the value of a given header""" - return self.headers[name] - - def clear(self): - """Empties the HTTP message""" - self.headers = odict() - self.body = "" - - def parse(self, chunk): - """Parses a given chunk of data and fill in the current object - - @param chunk: the chunk of data to parse - @type chunk: string""" - self.clear() - - lines = chunk.split("\r\n") - for i, line in enumerate(lines): - if line.strip() == "": - self.body = "\r\n".join(lines[i+1:]) - break - name, value = line.split(":", 1) - self.add_header(name.rstrip(), value.lstrip()) - - def __str__(self): - result = [] - body = str(self.body) - for name in self.headers: - result.append(": ".join((name, str(self.headers[name])))) - #if "Content-Length" not in self.headers: - # result.append("Content-Length: %d" % len(body)) - result.append("") - result.append(str(self.body)) - return "\r\n".join(result) - - -class HTTPResponse(HTTPMessage): - def __init__(self, headers=None, body="", status=200, reason="OK", version="1.0"): - if headers is None: - headers = {} - HTTPMessage.__init__(self) - for header, value in headers.iteritems(): - self.add_header(header, value) - self.body = body - self.status = status - self.reason = reason - self.version = version - - def parse(self, chunk): - start_line, message = chunk.split("\r\n", 1) - - version, status, reason = start_line.split(" ", 2) - self.status = int(status) - self.reason = reason - self.version = version.split("/",1)[1] - - HTTPMessage.parse(self, message) - - def __str__(self): - message = HTTPMessage.__str__(self) - start_line = "HTTP/%s %d %s" % (self.version, self.status, self.reason) - return start_line + "\r\n" + message - - -class HTTPRequest(HTTPMessage): - def __init__(self, headers=None, body="", method="GET", resource="/", version="1.0"): - if headers is None: - headers = {} - HTTPMessage.__init__(self) - for header, value in headers.iteritems(): - self.add_header(header, value) - self.body = body - self.method = method - self.resource = resource - self.version = version - - def parse(self, chunk): - start_line, message = chunk.split("\r\n", 1) - - method, resource, version = start_line.split(" ") - self.method = method - self.resource = resource - self.version = version.split("/",1)[1] - - HTTPMessage.parse(self, message) - - def __str__(self): - message = HTTPMessage.__str__(self) - start_line = "%s %s HTTP/%s" % (self.method, - self.resource, self.version) - return start_line + "\r\n" + message - diff --git a/pymsn/pymsn/gnet/message/SOAP.py b/pymsn/pymsn/gnet/message/SOAP.py deleted file mode 100644 index 53564dd9..00000000 --- a/pymsn/pymsn/gnet/message/SOAP.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""SOAP Messages structures.""" - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO - -__all__=['SOAPRequest', 'SOAPResponse'] - -class NameSpace: - SOAP_ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/" - SOAP_ENCODING = "http://schemas.xmlsoap.org/soap/encoding/" - XML_SCHEMA = "http://www.w3.org/1999/XMLSchema" - XML_SCHEMA_INSTANCE = "http://www.w3.org/1999/XMLSchema-instance" - -class Encoding: - SOAP = "http://schemas.xmlsoap.org/soap/encoding/" - -class _SOAPSection: - ENVELOPE = "{" + NameSpace.SOAP_ENVELOPE + "}Envelope" - HEADER = "{" + NameSpace.SOAP_ENVELOPE + "}Header" - BODY = "{" + NameSpace.SOAP_ENVELOPE + "}Body" - - -class _SOAPElement(object): - def __init__(self, element): - self.element = element - - def append(self, tag, namespace=None, type=None, attrib={}, value=None, **kwargs): - if namespace is not None: - tag = "{" + namespace + "}" + tag - if type: - if isinstance(type, str): - type = ElementTree.QName(type, NameSpace.XML_SCHEMA) - else: - type = ElementTree.QName(type[1], type[0]) - attrib["{" + NameSpace.XML_SCHEMA_INSTANCE + "}type"] = type - - child = ElementTree.SubElement(self.element, tag, attrib, **kwargs) - child.text = value - return _SOAPElement(child) - - def __str__(self): - return ElementTree.tostring(self.element, "utf-8") - - -class SOAPRequest(object): - """Abstracts a SOAP Request to be sent to the server""" - - def __init__(self, method, namespace=None, encoding_style=Encoding.SOAP, **attr): - """Initializer - - @param method: the method to be called - @type method: string - - @param namespace: the namespace that the method belongs to - @type namespace: URI - - @param encoding_style: the encoding style for this method - @type encoding: URI - - @param **attr: attributes to be attached to the method""" - self.header = ElementTree.Element(_SOAPSection.HEADER) - if namespace is not None: - method = "{" + namespace + "}" + method - self.method = ElementTree.Element(method) - if encoding_style is not None: - self.method.set("{" + NameSpace.SOAP_ENVELOPE + "}encodingStyle", encoding_style) - - for attr_key, attr_value in attr.iteritems(): - self.method.set(attr_key, attr_value) - - def add_argument(self, name, namespace=None, type=None, attrib=None, value=None, **kwargs): - if namespace is not None: - name = "{" + namespace + "}" + name - return self._add_element(self.method, name, type, attrib, value, **kwargs) - - def add_header(self, name, namespace=None, attrib=None, value=None, **kwargs): - if namespace is not None: - name = "{" + namespace + "}" + name - return self._add_element(self.header, name, None, attrib, value, **kwargs) - - def _add_element(self, parent, name, type=None, attributes=None, value=None, **kwargs): - elem = ElementTree.SubElement(parent, name) - if attributes is None: - attributes = {} - attributes.update(kwargs) - if type: - type = self._qname(type, NameSpace.XML_SCHEMA) - elem.set("{" + NameSpace.XML_SCHEMA_INSTANCE + "}type", type) - for attr_key, attr_value in attributes.iteritems(): - elem.set(attr_key, attr_value) - elem.text = value - return _SOAPElement(elem) - - def _qname(self, name, default_ns): - if name[0] != "{": - return ElementTree.QName(default_ns, name) - return ElementTree.QName(name) - - def __str__(self): - envelope = ElementTree.Element(_SOAPSection.ENVELOPE) - if len(self.header) > 0: - envelope.append(self.header) - body = ElementTree.SubElement(envelope, _SOAPSection.BODY) - body.append(self.method) - return "" +\ - ElementTree.tostring(envelope, "utf-8") - - def __repr__(self): - return "" % self.method.tag - - -class SOAPResponse(object): - def __init__(self, data): - self.tree = self._parse(data) - self.header = self.tree.find(_SOAPSection.HEADER) - self.body = self.tree.find(_SOAPSection.BODY) - - def find(self, path): - return self.tree.find(path) - - def _parse(self, data): - events = ("start", "end", "start-ns", "end-ns") - ns = [] - data = StringIO.StringIO(data) - context = ElementTree.iterparse(data, events=events) - for event, elem in context: - if event == "start-ns": - ns.append(elem) - elif event == "end-ns": - ns.pop() - elif event == "start": - elem.set("(xmlns)", tuple(ns)) - data.close() - return context.root - diff --git a/pymsn/pymsn/gnet/message/__init__.py b/pymsn/pymsn/gnet/message/__init__.py deleted file mode 100644 index dd8ad840..00000000 --- a/pymsn/pymsn/gnet/message/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""GNet messages types, those types are an abstract representation -of a known message type, like the HTTP messages or the SOAP messages""" diff --git a/pymsn/pymsn/gnet/parser.py b/pymsn/pymsn/gnet/parser.py deleted file mode 100644 index b59a03bb..00000000 --- a/pymsn/pymsn/gnet/parser.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Incomming data parsers.""" - -from constants import * -from message.HTTP import HTTPResponse - -import gobject - -__all__ = ['AbstractParser', 'DelimiterParser'] - -class AbstractParser(gobject.GObject): - """Base class for all stateful parsers. - - @since: 0.1""" - __gsignals__ = { - "received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - def __init__(self, transport, connect_signals=True): - """Initializer - - @param transport: the transport used to receive data - @type transport: an object derived from - L{io.AbstractClient}""" - gobject.GObject.__init__(self) - if connect_signals: - transport.connect("received", self._on_received) - transport.connect("notify::status", self._on_status_change) - self._transport = transport - self._reset_state() - - def _reset_state(self): - """Needs to be overriden in order to implement the default - parser state.""" - raise NotImplementedError - - def _on_status_change(self, transport, param): - status = transport.get_property("status") - if status == IoStatus.OPEN: - self._reset_state() - - def _on_received(self, transport, buf, length): - raise NotImplementedError - - -class DelimiterParser(AbstractParser): - """Receiver class that emit received signal when a chunk of data is - received. - - A chunk is defined by a delimiter which is either a string or an integer. - - @since: 0.1""" - - def __init__(self, transport): - """Initializer - - @param transport: the transport used to receive data - @type transport: L{io.AbstractClient}""" - AbstractParser.__init__(self, transport) - self._chunk_delimiter = "\n" - - def _reset_state(self): - self._recv_cache = "" - - def _on_received(self, transport, buf, length): - self._recv_cache += buf - self._process_recv_cache() - - def _process_recv_cache(self): - if len(self._recv_cache) == 0: - return - if self._chunk_delimiter is None or self._chunk_delimiter == "": - self.emit("received", self._recv_cache) - self._recv_cache = "" - return - - previous_length = len(self._recv_cache) - while len(self._recv_cache) != 0: - if isinstance(self._chunk_delimiter, int): - available = len(self._recv_cache) - required = self._chunk_delimiter - if required <= available: - self.emit ("received", self._recv_cache[:required]) - self._recv_cache = self._recv_cache[required:] - else: - s = self._recv_cache.split(self._chunk_delimiter, 1) - if len(s) > 1: - self.emit("received", s[0]) - self._recv_cache = s[1] - else: - self._recv_cache = s[0] - if len(self._recv_cache) == previous_length: # noting got consumed, exit - return - previous_length = len(self._recv_cache) - - def _set_chunk_delimiter(self, delimiter): - self._chunk_delimiter = delimiter - def _get_chunk_delimiter(self): - return self._chunk_delimiter - delimiter = property(_get_chunk_delimiter, - _set_chunk_delimiter, - doc="""The chunk delimiter, can be either a string or - an integer that specify the number of bytes for each chunk""") -gobject.type_register(DelimiterParser) - - -class HTTPParser(AbstractParser): - """Receiver class that emit received signal when an HTTP response is - received. - - @since: 0.1""" - - CHUNK_START_LINE = 0 - CHUNK_HEADERS = 1 - CHUNK_BODY = 2 - - def __init__(self, transport): - self._parser = DelimiterParser(transport) - self._parser.connect("received", self._on_chunk_received) - transport.connect("notify::status", self._on_status_change) - AbstractParser.__init__(self, transport, connect_signals=False) - - def _reset_state(self): - self._next_chunk = self.CHUNK_START_LINE - self._receive_buffer = "" - self._content_length = None - self._parser.delimiter = "\r\n" - - def _on_status_change(self, transport, param): - status = transport.get_property("status") - if status == IoStatus.OPEN: - self._reset_state() - elif status == IoStatus.CLOSING: - self._receive_buffer += self._parser._recv_cache - self.__emit_result() - - def _on_chunk_received(self, parser, chunk): - complete = False - if self._next_chunk == self.CHUNK_START_LINE: - self._receive_buffer += chunk + "\r\n" - self._next_chunk = self.CHUNK_HEADERS - elif self._next_chunk == self.CHUNK_HEADERS: - self._receive_buffer += chunk + "\r\n" - if chunk == "": - if self._content_length == 0: - complete = True - else: - self._parser.delimiter = self._content_length or 0 - self._next_chunk = self.CHUNK_BODY - else: - header, value = chunk.split(":", 1) - header, value = header.strip(), value.strip() - if header == "Content-Length": - self._content_length = int(value) - elif self._next_chunk == self.CHUNK_BODY: - self._receive_buffer += chunk - if self._content_length is not None: - complete = True - - if complete: - self.__emit_result() - - def __emit_result(self): - if self._receive_buffer == "": - return - response = HTTPResponse() - response.parse(self._receive_buffer) - self.emit("received", response) - self._reset_state() - diff --git a/pymsn/pymsn/gnet/protocol/HTTP.py b/pymsn/pymsn/gnet/protocol/HTTP.py deleted file mode 100644 index d857f9b7..00000000 --- a/pymsn/pymsn/gnet/protocol/HTTP.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from pymsn.gnet.constants import * -from pymsn.gnet.proxy import ProxyInfos -from pymsn.gnet.message.HTTP import HTTPRequest -from pymsn.gnet.io import TCPClient -from pymsn.gnet.parser import HTTPParser - -import gobject -import base64 -import platform - -__all__ = ['HTTP'] - - -class HTTP(gobject.GObject): - """HTTP protocol client class.""" - - __gsignals__ = { - "error" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (gobject.TYPE_ULONG,)), - - "response-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), # HTTPResponse - - "request-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), # HTTPRequest - } - - def __init__(self, host, port=80, proxy=None): - """Connection initialization - - @param host: the host to connect to. - @type host: string - - @param port: the port number to connect to - @type port: integer - - @param proxy: proxy that we can use to connect - @type proxy: L{gnet.proxy.ProxyInfos}""" - gobject.GObject.__init__(self) - assert(proxy is None or proxy.type == 'http') # TODO: add support for other proxies (socks4 and 5) - self._host = host - self._port = port - self.__proxy = proxy - self._transport = None - self._http_parser = None - self._outgoing_queue = [] - self._waiting_response = False - - def _setup_transport(self): - if self._transport is None: - if self.__proxy is not None: - self._transport = TCPClient(self.__proxy.host, self.__proxy.port) - else: - self._transport = TCPClient(self._host, self._port) - self._http_parser = HTTPParser(self._transport) - self._http_parser.connect("received", self._on_response_received) - self._transport.connect("notify::status", self._on_status_change) - self._transport.connect("error", self._on_error) - self._transport.connect("sent", self._on_request_sent) - - if self._transport.get_property("status") != IoStatus.OPEN: - self._transport.open() - - def _on_status_change(self, transport, param): - if transport.get_property("status") == IoStatus.OPEN: - self._process_queue() - elif transport.get_property("status") == IoStatus.CLOSED and\ - (self._waiting_response or len(self._outgoing_queue) > 0): - self._waiting_response = False - self._setup_transport() - - def _on_request_sent(self, transport, request, length): - assert(str(self._outgoing_queue[0]) == request) - self._waiting_response = True - self.emit("request-sent", self._outgoing_queue[0]) - - def _on_response_received(self, parser, response): - if response.status >= 100 and response.status < 200: - return - #if response.status in (301, 302): # UNTESTED: please test - # location = response.headers['Location'] - - # location = location.rsplit("://", 1) - # if len(location) == 2: - # scheme = location[0] - # location = location[1] - # if scheme == "http": - # location = location.rsplit(":", 1) - # self._host = location[0] - # if len(location) == 2: - # self._port = int(location[1]) - # self._outgoing_queue[0].headers['Host'] = response.headers['Location'] - # self._setup_transport() - # return - self._outgoing_queue.pop(0) # pop the request from the queue - self.emit("response-received", response) - self._waiting_response = False - self._process_queue() # next request ? - - def _on_error(self, transport, error): - self.emit("error", error) - - def _process_queue(self): - if len(self._outgoing_queue) == 0 or \ - self._waiting_response: # no pipelining - return - if self._transport is None or \ - self._transport.get_property("status") != IoStatus.OPEN: - self._setup_transport() - return - self._transport.send(str(self._outgoing_queue[0])) - - def request(self, resource='/', headers=None, data='', method='GET'): - if headers is None: - headers = {} - headers['Host'] = self._host + ':' + str(self._port) - headers['Content-Length'] = str(len(data)) - if 'User-Agent' not in headers: - user_agent = GNet.NAME, GNet.VERSION, platform.system(), platform.machine() - headers['User-Agent'] = "%s/%s (%s %s)" % user_agent - - if self.__proxy is not None: - url = 'http://%s:%d%s' % (self._host, self._port, resource) - if self.__proxy.user: - auth = self.__proxy.user + ':' + self.__proxy.password - credentials = base64.encodestring(auth).strip() - headers['Proxy-Authorization'] = 'Basic ' + credentials - else: - url = resource - request = HTTPRequest(headers, data, method, url) - self._outgoing_queue.append(request) - self._process_queue() diff --git a/pymsn/pymsn/gnet/protocol/HTTPS.py b/pymsn/pymsn/gnet/protocol/HTTPS.py deleted file mode 100644 index 2dff86bf..00000000 --- a/pymsn/pymsn/gnet/protocol/HTTPS.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from pymsn.gnet.constants import * -from pymsn.gnet.io import SSLTCPClient -from pymsn.gnet.proxy.HTTPConnect import HTTPConnectProxy -from pymsn.gnet.parser import HTTPParser -from HTTP import HTTP - -__all__ = ['HTTPS'] - -class HTTPS(HTTP): - """HTTP protocol client class.""" - def __init__(self, host, port=443, proxy=None): - """Connection initialization - - @param host: the host to connect to. - @type host: string - - @param port: the port number to connect to - @type port: integer - - @param proxy: proxy that we can use to connect - @type proxy: L{gnet.proxy.ProxyInfos}""" - HTTP.__init__(self, host, port) - assert(proxy is None or proxy.type == 'https') - self.__proxy = proxy - - def _setup_transport(self): - if self._transport is None: - transport = SSLTCPClient(self._host, self._port) - if self.__proxy is not None: - print 'Using proxy : ', repr(self.__proxy) - self._transport = HTTPConnectProxy(transport, self.__proxy) - else: - self._transport = transport - self._http_parser = HTTPParser(self._transport) - self._http_parser.connect("received", self._on_response_received) - self._transport.connect("notify::status", self._on_status_change) - self._transport.connect("error", self._on_error) - self._transport.connect("sent", self._on_request_sent) - - if self._transport.get_property("status") != IoStatus.OPEN: - self._transport.open() diff --git a/pymsn/pymsn/gnet/protocol/__init__.py b/pymsn/pymsn/gnet/protocol/__init__.py deleted file mode 100644 index 6db2cfcd..00000000 --- a/pymsn/pymsn/gnet/protocol/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""GNect protocol support""" - -from HTTP import * -from HTTPS import * - -def ProtocolFactory(protocol, host, port=None, proxy=None): - if protocol == "http": - klass = HTTP - elif protocol == "https": - klass = HTTPS - - if port is None: - return klass(host, proxy=proxy) - else: - return klass(host, port, proxy=proxy) - diff --git a/pymsn/pymsn/gnet/proxy/HTTPConnect.py b/pymsn/pymsn/gnet/proxy/HTTPConnect.py deleted file mode 100644 index d5af8f95..00000000 --- a/pymsn/pymsn/gnet/proxy/HTTPConnect.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from abstract import AbstractProxy -from pymsn.gnet.io import TCPClient -from pymsn.gnet.constants import * -from pymsn.gnet.parser import HTTPParser - -import gobject -import base64 - -__all__ = ['HTTPConnectProxy'] - -class HTTPConnectProxy(AbstractProxy): - def __init__(self, client, proxy_infos): - assert(proxy_infos.type in ('http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" - assert(client.domain == AF_INET), "HTTP CONNECT only handles INET address family" - assert(client.type == SOCK_STREAM), "HTTP CONNECT only handles SOCK_STREAM" - assert(client.status == IoStatus.CLOSED), "HTTPConnectProxy expects a closed client" - AbstractProxy.__init__(self, client, proxy_infos) - - self._transport = TCPClient(self._proxy.host, self._proxy.port) - self._transport.connect("notify::status", self._on_transport_status) - self._transport.connect("error", self._on_transport_error) - self._http_parser = HTTPParser(self._transport) - self._http_parser.connect("received", self._on_proxy_response) - - # opening state methods - def _pre_open(self, io_object=None): - AbstractProxy._pre_open(self) - - def _post_open(self): - AbstractProxy._post_open(self) - host = self._client.get_property("host") - port = self._client.get_property("port") - proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (host, port) - proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' - proxy_protocol += 'Pragma: no-cache\r\n' - proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) - if self._proxy.user: - auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() - proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' - proxy_protocol += '\r\n' - self._transport.send(proxy_protocol) - - # public API - def open(self): - """Open the connection.""" - if not self._configure(): - return - self._pre_open() - try: - self._transport.open() - except: - pass - - def close(self): - """Close the connection.""" - self._client._proxy_closed() - - def send(self, buffer, callback=None, *args): - self._client.send(buffer, callback, *args) - - # callbacks and signal handlers - def _on_transport_status(self, transport, param): - if transport.status == IoStatus.OPEN: - self._post_open() - elif transport.status == IoStatus.OPENING: - self._client._proxy_opening(self._transport._transport) - self._status = transport.status - else: - self._status = transport.status - - def _on_transport_error(self, transport, error_code): - if error_code == IoError.CONNECTION_FAILED: - error_code = IoError.PROXY_CONNECTION_FAILED - self.close() - self.emit("error", error_code) - - def _on_proxy_response(self, parser, response): - if self.status == IoStatus.OPENING: - if response.status == 200: - del self._http_parser - self._transport._watch_remove() # HACK: ok this is ugly ! - self._client._proxy_open() - elif response.status == 100: - return True - elif response.status == 407: - self.close() - self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) - else: - raise NotImplementedError("Unknown Proxy response code") - return False -gobject.type_register(HTTPConnectProxy) diff --git a/pymsn/pymsn/gnet/proxy/SOCKS4.py b/pymsn/pymsn/gnet/proxy/SOCKS4.py deleted file mode 100644 index ffdf7d42..00000000 --- a/pymsn/pymsn/gnet/proxy/SOCKS4.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from abstract import AbstractProxy -from pymsn.gnet.io import TCPClient -from pymsn.gnet.constants import * -from pymsn.gnet.parser import DelimiterParser - -import gobject -import struct - -__all__ = ['SOCKS4Proxy'] - -class SOCKS4Proxy(AbstractProxy): - - PROTOCOL_VERSION = 4 - CONNECT_COMMAND = 1 - - """Proxy class used to communicate with SOCKS4 proxies.""" - def __init__(self, client, proxy_infos): - assert(proxy_infos.type == 'socks4'), \ - "SOCKS4Proxy expects a socks4 proxy description" - # TODO : implement version 4a of the protocol to allow proxy-side name resolution - assert(client.domain == AF_INET), \ - "SOCKS4 CONNECT only handles INET address family" - assert(client.type == SOCK_STREAM), \ - "SOCKS4 CONNECT only handles SOCK_STREAM" - assert(client.status == IoStatus.CLOSED), \ - "SOCKS4Proxy expects a closed client" - AbstractProxy.__init__(self, client, proxy_infos) - - self._transport = TCPClient(self._proxy.host, self._proxy.port) - self._transport.connect("notify::status", self._on_transport_status) - self._transport.connect("error", self._on_transport_error) - - self._delimiter_parser = DelimiterParser(self._transport) - self._delimiter_parser.delimiter = 8 - self._delimiter_parser.connect("received", self._on_proxy_response) - - # Opening state methods - def _pre_open(self, io_object=None): - AbstractProxy._pre_open(self) - - def _post_open(self): - AbstractProxy._post_open(self) - host = self._client.get_property("host") - port = self._client.get_property("port") - user = self._proxy.user - - proxy_protocol = struct.pack('!BBH', SOCKS4Proxy.PROTOCOL_VERSION, - SOCKS4Proxy.CONNECT_COMMAND, port) - - for part in host.split('.'): - proxy_protocol += struct.pack('B', int(part)) - - proxy_protocol += user - proxy_protocol += struct.pack('B', 0) - - self._transport.send(proxy_protocol) - - # Public API - def open(self): - """Open the connection.""" - if not self._configure(): - return - self._pre_open() - try: - self._transport.open() - except: - pass - - def close(self): - """Close the connection.""" - self._client._proxy_closed() - - def send(self, buffer, callback=None, *args): - self._client.send(buffer, callback, *args) - - # Callbacks - def _on_transport_status(self, transport, param): - if transport.status == IoStatus.OPEN: - self._post_open() - elif transport.status == IoStatus.OPENING: - self._client._proxy_opening(self._transport._transport) - self._status = transport.status - else: - self._status = transport.status - - def _on_transport_error(self, transport, error_code): - if error_code == IoError.CONNECTION_FAILED: - error_code = IoError.PROXY_CONNECTION_FAILED - self.close() - self.emit("error", error_code) - - def _on_proxy_response(self, parser, response): - version, response_code = struct.unpack('BB', response[0:2]) - assert(version == 0) - if self.status == IoStatus.OPENING: - if response_code == 90: - del self._delimiter_parser - self._transport._watch_remove() # HACK: ok this is ugly ! - self._client._proxy_open() - elif response_code == 91: - self.close() - self.emit("error", IoError.PROXY_CONNECTION_FAILED) - elif response_code == 92: - self.close() - self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) - elif response_code == 93: - self.close() - self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) - else: - raise NotImplementedError("Unknow Proxy response code") - return False - -gobject.type_register(SOCKS4Proxy) diff --git a/pymsn/pymsn/gnet/proxy/__init__.py b/pymsn/pymsn/gnet/proxy/__init__.py deleted file mode 100644 index 11488c54..00000000 --- a/pymsn/pymsn/gnet/proxy/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""GNet proxy support, provides proxy classes to be used with proxifiable -IO clients""" - -from proxy_infos import * diff --git a/pymsn/pymsn/gnet/proxy/abstract.py b/pymsn/pymsn/gnet/proxy/abstract.py deleted file mode 100644 index 9a1489dc..00000000 --- a/pymsn/pymsn/gnet/proxy/abstract.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from pymsn.gnet.io import AbstractClient -from pymsn.gnet.constants import IoStatus - -import gobject - -__all__ = ['AbstractProxy'] - -class AbstractProxy(AbstractClient): - def __init__(self, client, proxy_infos): - self._client = client - self._proxy = proxy_infos - self._client.connect("sent", self._on_client_sent) - self._client.connect("received", self._on_client_received) - self._client.connect("notify::status", self._on_client_status) - AbstractClient.__init__(self, self._proxy.host, self._proxy.port) - - def _on_client_status(self, client, param): - status = client.get_property("status") - if status == IoStatus.OPEN: - self._status = IoStatus.OPEN - elif status == IoStatus.CLOSED: - self._status = IoStatus.CLOSED - - def _on_client_sent(self, client, data, length): - self.emit("sent", data, length) - - def _on_client_received(self, client, data, length): - self.emit("received", data, length) -gobject.type_register(AbstractProxy) diff --git a/pymsn/pymsn/gnet/proxy/proxy_infos.py b/pymsn/pymsn/gnet/proxy/proxy_infos.py deleted file mode 100644 index 48a1a3d1..00000000 --- a/pymsn/pymsn/gnet/proxy/proxy_infos.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -import base64 -import urlparse - -__all__ = ['ProxyInfos', 'ProxyFactory'] - -class ProxyInfos(object): - """Contain informations needed to make use of a proxy. - - @ivar host: hostname of the proxy server. - @ivar port: port used to connect to server. - @ivar type: proxy type - @ivar user: username to use for authentication. - @ivar password: password to use for authentication. - @undocumented __get_*, __set_* - - @since: 0.1""" - - def __init__(self, host='', port=0, type='http', user=None, password=None): - """Initializer - - @param host: the hostname of the proxy server. - @type host: string - - @param port: the port used to connect to server. - @type port: integer >= 0 and < 65536 - - @param type: proxy type - @type type: string in ('http', 'https', 'socks4', 'socks5') - - @param user: the username to use for authentication. - @type user: string - - @param password: the password to use for authentication. - @type password: string""" - self.host = host - self.port = port - self.type = type - self.user = user - self.password = password - - @staticmethod - def from_string(url, default_type='http'): - """Builds a new L{ProxyInfos} instance from a given proxy url string - @param url: the proxy url string - @type url: string - - @param default_type: the default proxy type - @type default_type: string in ('http', 'https', 'socks4', 'socks5') - - @return L{ProxyInfos} instance filled with the infos given in the - url""" - # scheme://netloc/path;parameters?query#fragment - # (scheme, netloc, path;parameters, query, fragment) - url = urlparse.urlsplit(url, default_type) - proxy_type = url[0] - location = url[1] - location = location.rsplit('@',1) - if len(location) == 1: - auth = ('','') - host = location[0] - else: - auth = location[0].split(':',1) - host = location[1] - host = host.split(':',1) - if len(host) == 1: - port = 8080 - else: - port = int(host[1]) - host = host[0] - return ProxyInfos(host, port, proxy_type, auth[0], auth[1]) - - def __get_port(self): - return self._port - def __set_port(self, port): - self._port = int(port) - assert(self._port >= 0 and self._port <= 65535) - port = property(__get_port, __set_port, doc="Port used to connect to server.") - - def __get_type(self): - return self._type - def __set_type(self, type): - assert(type in ('http', 'https', 'socks4', 'socks5')) - self._type = type - type = property(__get_type, __set_type, doc="Proxy type.") - - def __str__(self): - host = '%s:%u' % (self.host, self.port) - if self.user: - auth = '%s:%s' % (self.user, self.password) - host = auth + '@' + host - return self.type + '://' + host + '/' - - def __repr__(self): - host = '%s:%u' % (self.host, self.port) - if self.user: - auth = '%s:%s' % (self.user, "*" * len(self.password)) - host = auth + '@' + host - return self.type + '://' + host + '/' - -ProxyFactory = ProxyInfos.from_string - diff --git a/pymsn/pymsn/gnet/proxy/proxyfiable.py b/pymsn/pymsn/gnet/proxy/proxyfiable.py deleted file mode 100644 index ee851304..00000000 --- a/pymsn/pymsn/gnet/proxy/proxyfiable.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ['ProxyfiableClient'] - -class ProxyfiableClient(object): - """All proxifiable clients must inherit from this class - to enable the Proxy object to manipulate them""" - - def __init__(self): - pass - - def _proxy_opening(self, sock): - if not self._configure(): return - self._pre_open(sock) - - def _proxy_open(self): - self._post_open() - - def _proxy_closed(self): - self.close() diff --git a/pymsn/pymsn/gnet/resolver.py b/pymsn/pymsn/gnet/resolver.py deleted file mode 100644 index 49e72a70..00000000 --- a/pymsn/pymsn/gnet/resolver.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""GNet dns resolver""" - -import socket - -import gobject - -from pymsn.util.decorator import async - -__all__ = ['HostnameResolver'] - -class HostnameResponse(object): - def __init__(self, response): - self._response = response - - @property - def status(self): - return self._response[0] - - @property - def cname(self): - return self._response[1] - - @property - def expires(self): - return self._response[2] - - @property - def answer(self): - return self._response[3] - - def __repr__(self): - return repr(self._response) - -class HostnameResolver(object): - def __init__(self): - self._queries = {} - - def query(self, host, callback): - result = socket.getaddrinfo(host, None, socket.AF_INET, socket.SOCK_STREAM) - if len(result) == 0: - status = 1 - cname = '' - expires = 0 - addresses = () - else: - status = 0 - cname = result[0][3] - expires = 0 - addresses = ((socket.AF_INET, result[0][4][0]),) - self._emit_response(callback, (status, cname, expires, addresses)) - - @async - def _emit_response(self, callback, response): - callback[0](HostnameResponse(response), *callback[1:]) - return False - - -if __name__ == "__main__": - mainloop = gobject.MainLoop(is_running=True) - def print_throbber(): - print "*" - return True - - def hostname_resolved(result): - print result - mainloop.quit() - - def resolve_hostname(resolver, host): - print "Resolving" - resolver.query(host, (hostname_resolved,)) - return False - - resolver = HostnameResolver() - - gobject.timeout_add(10, print_throbber) - gobject.timeout_add(100, resolve_hostname, resolver, 'www.google.com') - #gobject.timeout_add(100, resolve_hostname, resolver, '209.85.129.104') - - mainloop.run() diff --git a/pymsn/pymsn/msnp/__init__.py b/pymsn/pymsn/msnp/__init__.py deleted file mode 100644 index c9b8ca13..00000000 --- a/pymsn/pymsn/msnp/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""MSN Core protocol client implementation. -Contains a set of class abstracting the MSNP protocol used to communicate -with the Notification Server as well as the Switchboard Server""" - -from command import * -from message import * -from constants import * -from notification import * -from switchboard import * -from base import ProtocolState diff --git a/pymsn/pymsn/msnp/base.py b/pymsn/pymsn/msnp/base.py deleted file mode 100644 index b43fd9fd..00000000 --- a/pymsn/pymsn/msnp/base.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Base classes used by the specific classes of the Core Protocol""" - -import logging - -__all__ = ['BaseProtocol'] - -logger = logging.getLogger('protocol') - -class ProtocolState(object): - CLOSED = 0 - OPENING = 1 - AUTHENTICATING = 2 - AUTHENTICATED = 3 - SYNCHRONIZING = 4 - SYNCHRONIZED = 5 - OPEN = 6 - - -class BaseProtocol(object): - """Base class used to implement the Notification protocol as well - as the Switchboard protocol - @group Handlers: _handle_*, _default_handler, _error_handler - - @ivar _client: the parent instance of L{client.Client} - @type _client: L{client.Client} - - @ivar _transport: the transport instance - @type _transport: L{transport.BaseTransport} - - @ivar _proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - """ - - def __init__(self, client, transport, proxies={}): - """Initializer - - @param client: the parent instance of L{client.Client} - @type client: L{client.Client} - - @param transport: The transport to use to speak the protocol - @type transport: L{transport.BaseTransport} - - @param proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - @type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}} - """ - transport.connect("command-received", self._dispatch_command) - transport.connect("connection-success", self._connect_cb) - transport.connect("connection-failure", self._disconnect_cb) - transport.connect("connection-lost", self._disconnect_cb) - - self._client = client - self._transport = transport - self._proxies = proxies - - def _send_command(self, command, arguments=(), payload=None, - increment=True, callback=None, *cb_args): - self._transport.send_command_ex(command, arguments, payload, increment, - callback, *cb_args) - - # default handlers - def _default_handler(self, command): - """ - Default handler used when no handler is defined - - @param command: the received command - @type command: L{command.Command} - """ - logger.warning('Notification unhandled command :' + repr(command)) - - def _error_handler(self, error): - """Handles errors - - @param error: an error command object - @type error: L{command.Command} - """ - logger.error('Notification got error :' + repr(error)) - - # callbacks - def _dispatch_command(self, connection, command): - if not command.is_error(): - handler = getattr(self, - '_handle_' + command.name, - self._default_handler) - handler(command) - else: - self._error_handler(command) - - def _connect_cb(self, transport): - pass - - def _disconnect_cb(self, transport, reason): - pass diff --git a/pymsn/pymsn/msnp/challenge.py b/pymsn/pymsn/msnp/challenge.py deleted file mode 100644 index 30a81ef4..00000000 --- a/pymsn/pymsn/msnp/challenge.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from constants import ProtocolConstant - -def _msn_challenge(data): - """ - Compute an answer for MSN Challenge from a given data - - @param data: the challenge string sent by the server - @type data: string - """ - import struct - import md5 - def little_endify(value, c_type="L"): - """Transform the given value into little endian""" - return struct.unpack(">" + c_type, struct.pack("<" + c_type, value))[0] - - md5_digest = md5.md5(data + ProtocolConstant.PRODUCT_KEY).digest() - # Make array of md5 string ints - md5_integers = struct.unpack("QQ", md5_digest)] - longs = [little_endify(x, "Q") for x in longs] - longs = [x ^ key for x in longs] - longs = [little_endify(abs(x), "Q") for x in longs] - out = "" - for value in longs: - value = hex(long(value)) - value = value[2:-1] - value = value.zfill(16) - out += value.lower() - return out - diff --git a/pymsn/pymsn/msnp/command.py b/pymsn/pymsn/msnp/command.py deleted file mode 100644 index 4f0b83e7..00000000 --- a/pymsn/pymsn/msnp/command.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""MSN protocol commands.""" - -from urllib import quote, unquote - -from pymsn.msnp.message import Message - -__all__ = ['Command'] - -class CommandPrinter(object): - def __init__(self, command): - self.command = command - - def __repr__(self): - printer = getattr(self, "_print_" + self.command.name, - self._print_default) - return printer() - - def _print_MSG(self): - command = self.command - - result = command.name - if command.transaction_id is not None: - result += ' ' + str(command.transaction_id) - - if command.arguments is not None and len(command.arguments) > 0: - result += ' ' + ' '.join(command.arguments) - - if command.payload is not None: - result += "\n" + repr(Message(None, str(command.payload))) - return result - - def _print_QRY(self): - command = self.command - - result = command.name - if command.transaction_id is not None: - result += ' ' + str(command.transaction_id) - - if command.arguments is not None and len(command.arguments) > 0: - arguments = [str(argument) for argument in command.arguments] - result += ' ' + ' '.join(arguments) - - if command.payload is not None: - payload = repr(command.payload) - length = len(payload) - if length > 0: - result += ' ' + str(length) + '\r\n' - result += payload - return result - - def _print_default(self): - command = self.command - - result = command.name - if command.transaction_id is not None: - result += ' ' + str(command.transaction_id) - - if command.arguments is not None and len(command.arguments) > 0: - arguments = [str(argument) for argument in command.arguments] - result += ' ' + ' '.join(arguments) - - if command.payload is not None: - payload = repr(command.payload) - length = len(payload) - if length > 0: - result += ' ' + str(length) + '\r\n' - if not command.is_error(): - result += '\t[payload]' - else: - result += payload - return result - - -class Command(object): - """Abstraction of MSN commands, this class enables parsing and construction - of commands. - - @ivar name: the 3 uppercase letters name of the command - @type name: string - - @ivar transaction_id: the transaction id of the command or None - @type transaction_id: integer - - @ivar arguments: the arguments of the command - @type arguments: tuple() - - @ivar payload: the payload of the command - @type payload: string or None""" - - OUTGOING_NO_TRID = ('OUT', 'PNG') - INCOMING_NO_TRID = ( - # NS commands - 'QNG', 'IPG', 'NOT', 'NLN', 'FLN', 'GCF', - 'QRY', 'SBS', 'UBN', 'UBM', 'UBX', - # SW commands - 'RNG', 'JOI', 'BYE', 'MSG') - - OUTGOING_PAYLOAD = ( - 'QRY', 'SDC', 'PGD', 'ADL', 'RML', 'UUN', - 'UUM', 'UUX', 'MSG', 'FQY') - - INCOMING_PAYLOAD = ( - 'GCF', 'MSG', 'UBN', 'UBM', 'UBX', 'IPG', - 'NOT', 'ADL', 'RML', 'FQY', - - '241', '509') - - def __init__(self): - self._reset() - - def _reset(self): - """Resets the object values""" - self.name = '' - self.transaction_id = None - self.arguments = None - self.payload = None - - ### public methods - def build(self, name, transaction_id, payload=None, *arguments): - """Updates the command with the given parameters - - @param name: the command name (3 letters) (e.g. MSG NLN ...) - @type name: string - - @param transaction_id: the transaction ID - @type transaction_id: integer - - @param *arguments: the command arguments - @type *arguments: string, ... - - @param payload: is the data to send with the command - @type payload: string - """ - self.name = name - self.transaction_id = transaction_id - self.arguments = arguments - self.payload = payload - - def parse(self, buf): - """Fills the Command object according parsing a string. - - @param buf: the data to parse - @type buf: string""" - self._reset() - lines = buf.split('\r\n', 1) - self.__parse_command(lines[0]) - if len(lines) > 1: # payload - self.payload = lines[1] - # remove the last argument as it is the data length - self.arguments = self.arguments[:-1] - - def is_error(self): - """Tells if the current command is an error code - - @rtype: bool""" - try: - int(self.name) - except ValueError: - return False - else: - return True - - def is_payload(self): - """Tells if the current comment is a payload command - - @rtype: bool""" - return self.payload is not None - - ### private and special methods - def __str__(self): - result = self.name[:] - if self.transaction_id is not None: - result += ' ' + str(self.transaction_id) - - if self.arguments is not None and len(self.arguments) > 0: - arguments = [str(arg) for arg in self.arguments] - result += ' ' + ' '.join(arguments) - - if self.payload is not None: - payload = str(self.payload) - length = len(payload) - if length > 0: - result += ' ' + str(length) + '\r\n' + payload - return result - - return result + '\r\n' - - def __repr__(self): - return repr(CommandPrinter(self)) - #return pymsn.util.debug.raw_cmd_to_debug(self.__str__()) - - def __parse_command(self, buf): - words = buf.split() - self.name, pos = words[0], 1 - if (words[0] not in self.INCOMING_NO_TRID) and\ - (words[0] not in self.OUTGOING_NO_TRID) and\ - len(words) > pos: - self.transaction_id = int(words[pos]) - pos += 1 - if len(words) > pos: - self.arguments = words[pos:] diff --git a/pymsn/pymsn/msnp/constants.py b/pymsn/pymsn/msnp/constants.py deleted file mode 100644 index f0e21d30..00000000 --- a/pymsn/pymsn/msnp/constants.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ["ProtocolConstant"] - -class ProtocolConstant(object): - VER = ('MSNP15', 'MSNP14', 'MSNP13', 'CVR0') - CVR = ('0x0409', 'winnt', '5.1', 'i386', 'MSNMSGR', '8.1.0178', 'msmsgs') - PRODUCT_ID = "PROD0114ES4Z%Q5W" - PRODUCT_KEY = "PK}_A_0N_K%O?A9S" - CHL_MAGIC_NUM = 0x0E79A9C1 diff --git a/pymsn/pymsn/msnp/message.py b/pymsn/pymsn/msnp/message.py deleted file mode 100644 index cacdca4f..00000000 --- a/pymsn/pymsn/msnp/message.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""MSN protocol special command : MSG""" - -from pymsn.gnet.message.HTTP import HTTPMessage -import pymsn.util.debug as debug - -from urllib import quote, unquote -import struct - -__all__ = ['MessageAcknowledgement', 'Message'] - - -class MessageAcknowledgement(object): - """Message Acknowledgement""" - FULL = 'A' - """Acknowledgement required for both delivery success and failure""" - MSNC = 'D' - """Direct connection, no acknowledgment required from the server""" - HALF = 'N' - """Acknowledgment on delivery failures""" - NONE = 'U' - """No Acknowledgment""" - -class Message(HTTPMessage): - """Base Messages class. - - @ivar sender: sender - @type sender: profile.Contact - - @ivar body: message body - @type body: string - - @ivar headers: message headers - @type headers: {header_name: string => header_value:string} - - @ivar content_type: the message content type - @type content_type: tuple(mime_type, encoding)""" - - def __init__(self, sender=None, message=""): - """Initializer - - @param body: The body of the message, it is put after the headers - @type body: string""" - HTTPMessage.__init__(self) - self.sender = sender - if message: - self.parse(message) - - def __repr__(self): - """Represents the payload of the message""" - message = '' - for header_name, header_value in self.headers.iteritems(): - message += '\t%s: %s\\r\\n\n' % (header_name, header_value) - message += '\t\\r\\n\n' - if self.headers['Content-Type'] != "application/x-msnmsgrp2p": - message += '\t' + debug.escape_string(self.body).\ - replace("\r\n", "\\r\\n\n\t") - else: - tlp_header = self.body[:48] - tlp_footer = self.body[-4:] - tlp_flags = struct.unpack(" 0: - message += "\n\t" + "[%d bytes of data]" % len(body) - message += "\n\t" + debug.hexify_string(tlp_footer) - - return message.rstrip("\n\t") - - def __get_content_type(self): - if 'Content-Type' in self.headers: - content_type = self.headers['Content-Type'].split(';', 1) - if len(content_type) == 1: - return (content_type[0].strip(), 'UTF-8') - mime_type = content_type[0].strip() - encoding = content_type[1].split('=', 1)[1].strip() - return (mime_type, encoding) - return ('text/plain', 'UTF-8') - - def __set_content_type(self, content_type): - if not isinstance(content_type, str): - content_type = '; charset='.join(content_type) - self.headers['Content-Type'] = content_type - - content_type = property(__get_content_type, __set_content_type, - doc="a tuple specifying the content type") - diff --git a/pymsn/pymsn/msnp/notification.py b/pymsn/pymsn/msnp/notification.py deleted file mode 100644 index 615e92bf..00000000 --- a/pymsn/pymsn/msnp/notification.py +++ /dev/null @@ -1,627 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Notification protocol Implementation -Implements the protocol used to communicate with the Notification Server.""" - -from base import BaseProtocol, ProtocolState -from message import Message -from constants import ProtocolConstant -from challenge import _msn_challenge - -import pymsn -from pymsn.gnet.message.HTTP import HTTPMessage -from pymsn.util.queue import PriorityQueue, LastElementQueue -from pymsn.util.decorator import throttled -import pymsn.util.element_tree as ElementTree -import pymsn.profile as profile -import pymsn.service.SingleSignOn as SSO -import pymsn.service.AddressBook as AB -import pymsn.service.OfflineIM as OIM - -import logging -import urllib -import gobject -import xml.sax.saxutils as xml_utils - -__all__ = ['NotificationProtocol'] - -logger = logging.getLogger('protocol:notification') - - -class NotificationProtocol(BaseProtocol, gobject.GObject): - """Protocol used to communicate with the Notification Server - - @undocumented: do_get_property, do_set_property - @group Handlers: _handle_*, _default_handler, _error_handler - - @ivar state: the current protocol state - @type state: integer - @see L{base.ProtocolState}""" - __gsignals__ = { - "authentication-failed" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ()), - - "mail-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "switchboard-invitation-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)), - - "unmanaged-message-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)), - } - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the communication with the server.", - 0, 6, ProtocolState.CLOSED, - gobject.PARAM_READABLE) - } - - def __init__(self, client, transport, proxies={}): - """Initializer - - @param client: the parent instance of L{client.Client} - @type client: L{client.Client} - - @param transport: The transport to use to speak the protocol - @type transport: L{transport.BaseTransport} - - @param proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - @type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}} - """ - BaseProtocol.__init__(self, client, transport, proxies) - gobject.GObject.__init__(self) - self.__state = ProtocolState.CLOSED - self._protocol_version = 0 - - # Properties ------------------------------------------------------------ - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - def do_get_property(self, pspec): - if pspec.name == "state": - return self.__state - else: - raise AttributeError, "unknown property %s" % pspec.name - - def do_set_property(self, pspec, value): - raise AttributeError, "unknown property %s" % pspec.name - - # Public API ------------------------------------------------------------- - @throttled(2000, LastElementQueue()) - def set_presence(self, presence, client_id=0, msn_object=None): - """Publish the new user presence. - - @param presence: the new presence - @type presence: string L{profile.Presence}""" - if msn_object == None: - msn_object = "" - - if presence == profile.Presence.OFFLINE: - self.signoff() - else: - if msn_object: - self._client._msn_object_store.publish(msn_object) - self._send_command('CHG', - (presence, str(client_id), urllib.quote(str(msn_object)))) - - @throttled(2000, LastElementQueue()) - def set_display_name(self, display_name): - """Sets the new display name - - @param friendly_name: the new friendly name - @type friendly_name: string""" - self._send_command('PRP', - ('MFN', urllib.quote(display_name))) - - @throttled(2000, LastElementQueue()) - def set_personal_message(self, personal_message='', current_media=None): - """Sets the new personal message - - @param personal_message: the new personal message - @type personal_message: string""" - cm = '' - if current_media is not None: - cm ='\\0Music\\01\\0{0} - {1}\\0%s\\0%s\\0\\0' % \ - (xml_utils.escape(current_media[0]), - xml_utils.escape(current_media[1])) - - message = xml_utils.escape(personal_message) - pm = ''\ - '%s'\ - '%s'\ - '{CAFEBABE-DEAD-BEEF-BAAD-FEEDDEADC0DE}'\ - '' % (message, cm) - self._send_command('UUX', payload=pm) - self._client.profile._server_property_changed("personal-message", - personal_message) - if current_media is not None: - self._client.profile._server_property_changed("current-media", - current_media) - - def signoff(self): - """Logout from the server""" - self._send_command('OUT') - self._transport.lose_connection() - - @throttled(7600, list()) - def request_switchboard(self, priority, callback, *callback_args): - self.__switchboard_callbacks.add((callback, callback_args), priority) - self._send_command('XFR', ('SB',)) - - def add_contact_to_membership(self, account, - network_id=profile.NetworkID.MSN, - membership=profile.Membership.FORWARD): - """Add a contact to a given membership. - - @param account: the contact identifier - @type account: string - - @param network_id: the contact network - @type network_id: integer - @see L{pymsn.profile.NetworkID} - - @param membership: the list to be added to - @type membership: integer - @see L{pymsn.profile.Membership}""" - - if network_id == profile.NetworkID.MOBILE: - payload = '' % \ - (contact, membership) - self._send_command("ADL", payload=payload) - else: - contact, domain = account.split("@", 1) - payload = '' % \ - (domain, contact, membership, network_id) - self._send_command("ADL", payload=payload) - - def remove_contact_from_membership(self, account, - network_id=profile.NetworkID.MSN, - membership=profile.Membership.FORWARD): - """Remove a contact from a given membership. - - @param account: the contact identifier - @type account: string - - @param network_id: the contact network - @type network_id: integer - @see L{pymsn.profile.NetworkID} - - @param membership: the list to be added to - @type membership: integer - @see L{pymsn.profile.Membership}""" - - if network_id == profile.NetworkID.MOBILE: - payload = '' % \ - (contact, membership) - self._send_command("RML", payload=payload) - else: - contact, domain = account.split("@", 1) - payload = '' % \ - (domain, contact, membership, network_id) - self._send_command("RML", payload=payload) - - def send_unmanaged_message(self, contact, message): - content_type = message.content_type[0] - if content_type == 'text/x-msnmsgr-datacast': - message_type = 3 - elif content_type == 'text/x-msmsgscontrol': - message_type = 2 - else: - message_type = 1 - self._send_command('UUM', - (contact.account, contact.network_id, message_type), - payload=message) - - # Handlers --------------------------------------------------------------- - # --------- Connection --------------------------------------------------- - def _handle_VER(self, command): - self._protocol_version = int(command.arguments[0].lstrip('MSNP')) - self._send_command('CVR', - ProtocolConstant.CVR + (self._client.profile.account,)) - - def _handle_CVR(self, command): - if self._protocol_version >= 15: - method = 'SSO' - else: - method = 'TWN' - self._send_command('USR', - (method, 'I', self._client.profile.account)) - - def _handle_XFR(self, command): - if command.arguments[0] == 'NS': - try: - host, port = command.arguments[1].split(":", 1) - port = int(port) - except ValueError: - host = command.arguments[1] - port = self._transport.server[1] - logger.debug("<-> Redirecting to " + command.arguments[1]) - self._transport.reset_connection((host,port)) - else: # connect to a switchboard - try: - host, port = command.arguments[1].split(":", 1) - port = int(port) - except ValueError: - host = command.arguments[1] - port = self._transport.server[1] - session_id = command.arguments[3] - callback, callback_args = self.__switchboard_callbacks.pop(0) - callback(((host, port), session_id, None), *callback_args) - - def _handle_USR(self, command): - args_len = len(command.arguments) - - # MSNP15 have only 4 params for final USR - assert(args_len == 3 or args_len == 4), \ - "Received USR with invalid number of params : " + str(command) - - if command.arguments[0] == "OK": - self._state = ProtocolState.AUTHENTICATED - - # we need to authenticate with a passport server - elif command.arguments[1] == "S": - self._state = ProtocolState.AUTHENTICATING - if command.arguments[0] == "SSO": - self._client._sso.RequestMultipleSecurityTokens( - (self._sso_cb, command.arguments[3]), - (lambda *args: self.emit("authentication-failed"),), - SSO.LiveService.MESSENGER_CLEAR) - - self._client.address_book.connect("notify::state", - self._address_book_state_changed_cb) - - self._client.address_book.connect("messenger-contact-added", - self._address_book_contact_added_cb) - self._client.address_book.connect("contact-accepted", - self._address_book_contact_accepted_cb) - self._client.address_book.connect("contact-rejected", - self._address_book_contact_rejected_cb) - self._client.address_book.connect("contact-deleted", - self._address_book_contact_deleted_cb) - self._client.address_book.connect("contact-blocked", - self._address_book_contact_blocked_cb) - self._client.address_book.connect("contact-unblocked", - self._address_book_contact_unblocked_cb) - - elif command.arguments[0] == "TWN": - raise NotImplementedError, "Missing Implementation, please fix" - - def _handle_SBS(self, command): # unknown command - pass - - def _handle_OUT(self, command): - pass - - # --------- Presence & Privacy ------------------------------------------- - def _handle_BLP(self, command): - self._client.profile._server_property_changed("privacy", - command.arguments[0]) - - def _handle_CHG(self, command): - self._client.profile._server_property_changed("presence", - command.arguments[0]) - if len(command.arguments) > 2: - if command.arguments[2] != '0': - msn_object = pymsn.p2p.MSNObject.parse(self._client, - urllib.unquote(command.arguments[2])) - else: - msn_object = None - self._client.profile._server_property_changed("msn_object", msn_object) - else: - self._client.profile._server_property_changed("msn_object", None) - - def _handle_ILN(self,command): - self._handle_NLN(command) - - def _handle_FLN(self,command): - network_id = int(command.arguments[1]) - account = command.arguments[0] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - - for contact in contacts: - contact._server_property_changed("presence", - profile.Presence.OFFLINE) - - def _handle_NLN(self,command): - network_id = int(command.arguments[2]) - account = command.arguments[1] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - for contact in contacts: - presence = command.arguments[0] - display_name = urllib.unquote(command.arguments[3]) - capabilities = int(command.arguments[4]) - contact._server_property_changed("presence", presence) - contact._server_property_changed("display-name", display_name) - contact._server_property_changed("client-capabilities", capabilities) - if len(command.arguments) >= 6: - if command.arguments[5] != '0': - msn_object = pymsn.p2p.MSNObject.parse(self._client, - urllib.unquote(command.arguments[5])) - contact._server_property_changed("msn-object", msn_object) - elif command.arguments[5] == '0': - contact._server_property_changed("msn-object", None) - elif len(command.arguments) > 6: - icon_url = command.arguments[6] - contact._server_attribute_changed('icon_url', icon_url) - - # --------- Display name and co ------------------------------------------ - def _handle_PRP(self, command): - ctype = command.arguments[0] - if len(command.arguments) < 2: return - if ctype == 'MFN': - self._client.profile._server_property_changed('display-name', - urllib.unquote(command.arguments[1])) - # TODO: add support for other stuff - - def _handle_UUX(self, command): - pass - - def _handle_UBN(self,command): # contact infos - if not command.payload: - return - print "RECEIVED UBN : %s\n%s" % (command, command.payload) - - def _handle_UBX(self,command): # contact infos - if not command.payload: - return - - network_id = int(command.arguments[1]) - account = command.arguments[0] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - for contact in contacts: - cm = ElementTree.fromstring(command.payload).find("./CurrentMedia") - if cm is not None and cm.text is not None: - parts = cm.text.split('\\0') - if parts[1] == 'Music' and parts[2] == '1': - cm = (parts[4].encode("utf-8"), parts[5].encode("utf-8")) - contact._server_property_changed("current-media", cm) - continue - elif parts[2] == '0': - contact._server_property_changed("current-media", None) - else: - contact._server_property_changed("current-media", None) - pm = ElementTree.fromstring(command.payload).find("./PSM") - if pm is not None and pm.text is not None: - pm = pm.text.encode("utf-8") - else: - pm = "" - contact._server_property_changed("personal-message", pm) - # --------- Contact List ------------------------------------------------- - def _handle_ADL(self, command): - if command.transaction_id == 0: # incoming ADL from the server - self._client.address_book.check_pending_invitations() - if len(command.arguments) > 0 and command.arguments[0] == "OK": - if self._state != ProtocolState.OPEN: # Initial ADL - self._state = ProtocolState.OPEN - self._transport.enable_ping() - else: # contact Added - pass - - # --------- Messages ----------------------------------------------------- - def _handle_MSG(self, command): - message = Message(None, command.payload) - content_type = message.content_type - if content_type[0] == 'text/x-msmsgsprofile': - self._client.profile._server_property_changed("profile", - command.payload) - - if self._protocol_version < 15: - #self._send_command('SYN', ('0', '0')) - raise NotImplementedError, "Missing Implementation, please fix" - else: - self._send_command("BLP", - (self._client.profile.privacy,)) - self._state = ProtocolState.SYNCHRONIZING - self._client.address_book.sync() - elif content_type[0] in \ - ('text/x-msmsgsinitialemailnotification', \ - 'text/x-msmsgsemailnotification'): - self.emit("mail-received", message) - elif content_type[0] in \ - ('text/x-msmsgsinitialmdatanotification', \ - 'text/x-msmsgsoimnotification'): - if self._client.oim_box is not None: - self._client.oim_box._state = \ - OIM.OfflineMessagesBoxState.NOT_SYNCHRONIZED - m = HTTPMessage() - m.parse(message.body) - mail_data = m.get_header('Mail-Data').strip() - if mail_data == 'too-large': - mail_data = None - self._client.oim_box.sync(mail_data) - elif content_type[0] == 'text/x-msmsgsactivemailnotification': - pass - - def _handle_UBM(self, command): - network_id = int(command.arguments[1]) - account = command.arguments[0] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - else: - contact = contacts[0] - message = Message(contact, command.payload) - self.emit("unmanaged-message-received", contact, message) - - # --------- Invitation --------------------------------------------------- - def _handle_RNG(self,command): - session_id = command.arguments[0] - host, port = command.arguments[1].split(':',1) - port = int(port) - key = command.arguments[3] - account = command.arguments[4] - display_name = urllib.unquote(command.arguments[5]) - - session = ((host, port), session_id, key) - inviter = (account, display_name) - self.emit("switchboard-invitation-received", session, inviter) - - # --------- Challenge ---------------------------------------------------- - def _handle_QNG(self,command): - pass - - def _handle_QRY(self,command): - pass - - def _handle_CHL(self,command): - response = _msn_challenge(command.arguments[0]) - self._send_command('QRY', - (ProtocolConstant.PRODUCT_ID,), payload=response) - - # callbacks -------------------------------------------------------------- - def _connect_cb(self, transport): - self.__switchboard_callbacks = PriorityQueue() - self._state = ProtocolState.OPENING - self._send_command('VER', ProtocolConstant.VER) - - def _disconnect_cb(self, transport, reason): - self._state = ProtocolState.CLOSED - - def _sso_cb(self, tokens, nonce): - clear_token = tokens[SSO.LiveService.MESSENGER_CLEAR] - blob = clear_token.mbi_crypt(nonce) - - self._send_command("USR", - ("SSO", "S", clear_token.security_token, blob)) - - def _address_book_state_changed_cb(self, address_book, pspec): - MAX_PAYLOAD_SIZE = 7500 - if address_book.state != AB.AddressBookState.SYNCHRONIZED: - return - self._client.profile._server_property_changed("display-name", - address_book.profile.display_name) - - contacts = address_book.contacts\ - .search_by_memberships(profile.Membership.FORWARD)\ - .group_by_domain() - - payloads = [''] - mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) - for domain, contacts in contacts.iteritems(): - payloads[-1] += '' % domain - for contact in contacts: - user = contact.account.split("@", 1)[0] - lists = contact.memberships & mask - network_id = contact.network_id - node = '' % (user, lists, network_id) - size = len(payloads[-1]) + len(node) + len('') - if size >= MAX_PAYLOAD_SIZE: - payloads[-1] += '' - payloads.append('' % domain) - payloads[-1] += node - payloads[-1] += '' - payloads[-1] += '' - - for payload in payloads: - self._send_command("ADL", payload=payload) - self._state = ProtocolState.SYNCHRONIZED - - def _address_book_contact_added_cb(self, address_book, contact): - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.FORWARD) - - if contact.network_id != profile.NetworkID.MOBILE: - account, domain = contact.account.split('@', 1) - payload = ''% \ - (domain, account) - self._send_command("FQY", payload=payload) - - def _address_book_contact_deleted_cb(self, address_book, contact): - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.BLOCK) - - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.FORWARD) - - def _address_book_contact_accepted_cb(self, address_book, contact): - mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) - memberships = contact.memberships & mask - if memberships: - self.add_contact_to_membership(contact.account, contact.network_id, - memberships) - - def _address_book_contact_rejected_cb(self, address_book, contact): - mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) - memberships = contact.memberships & mask - if memberships: - self.add_contact_to_membership(contact.account, contact.network_id, - memberships) - - def _address_book_contact_blocked_cb(self, address_book, contact): - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.BLOCK) - - def _address_book_contact_unblocked_cb(self, address_book, contact): - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.BLOCK) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) diff --git a/pymsn/pymsn/msnp/switchboard.py b/pymsn/pymsn/msnp/switchboard.py deleted file mode 100644 index 0d339397..00000000 --- a/pymsn/pymsn/msnp/switchboard.py +++ /dev/null @@ -1,296 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Switchboard protocol Implementation -Implements the protocol used to communicate with the Switchboard Server.""" - -from base import BaseProtocol, ProtocolState -from message import Message -import pymsn.profile - -import logging -import urllib -import gobject - -__all__ = ['SwitchboardProtocol'] - -logger = logging.getLogger('protocol:switchboard') - - -class SwitchboardProtocol(BaseProtocol, gobject.GObject): - """Protocol used to communicate with the Switchboard Server - - @undocumented: do_get_property, do_set_property - @group Handlers: _handle_*, _default_handler, _error_handler - - @ivar _state: the current protocol state - @type _state: integer - @see L{ProtocolState}""" - __gsignals__ = { - "message-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "message-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "message-delivered": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "message-undelivered": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "user-joined": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "user-left": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "user-invitation-failed": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,))} - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the communication with the server.", - 0, 6, ProtocolState.CLOSED, - gobject.PARAM_READABLE), - - "inviting": (gobject.TYPE_BOOLEAN, - "Inviting", - "True if an invite was sent, and the contact didn't join yet", - False, - gobject.PARAM_READABLE) - } - - def __init__(self, client, transport, session_id, key=None, proxies={}): - """Initializer - - @param client: the Client instance - - @param transport: The transport to use to speak the protocol - @type transport: L{transport.BaseTransport} - - @param session_id: the session to join if any - @type session_id: string - - @param key: the key used to authenticate to server when connecting - @type key: string - - @param proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - @type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}} - """ - BaseProtocol.__init__(self, client, transport, proxies) - gobject.GObject.__init__(self) - self.participants = {} - self.__session_id = session_id - self.__key = key - self.__state = ProtocolState.CLOSED - self.__inviting = False - - self.__invitations = {} - - # Properties ------------------------------------------------------------ - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - def __get_inviting(self): - return self.__inviting - def __set_inviting(self, value): - if self.__inviting != value: - self.__inviting = value - self.notify("inviting") - inviting = property(__get_inviting) - _inviting = property(__get_inviting, __set_inviting) - - def do_get_property(self, pspec): - if pspec.name == "state": - return self.__state - elif pspec.name == "inviting": - return self.__inviting - else: - raise AttributeError, "unknown property %s" % pspec.name - - def do_set_property(self, pspec, value): - raise AttributeError, "unknown property %s" % pspec.name - - # Public API ------------------------------------------------------------- - def invite_user(self, contact): - """Invite user to join in the conversation - - @param contact: the contact to invite - @type contact: L{profile.Contact}""" - assert(self.state == ProtocolState.OPEN) - self.__invitations[self._transport.transaction_id] = contact - self._inviting = True - self._send_command('CAL', (contact.account,) ) - - def send_message(self, message, ack, callback=None, cb_args=()): - """Send a message to all contacts in this switchboard - - @param message: the message to send - @type message: L{message.Message}""" - assert(self.state == ProtocolState.OPEN) - self._send_command('MSG', - (ack,), - message, - True, - self.__on_message_sent, - message, callback, cb_args) - - def __on_message_sent(self, message, user_callback, user_cb_args): - self.emit("message-sent", message) - if user_callback: - user_callback(*user_cb_args) - - def leave(self): - """Leave the conversation""" - assert(self.state == ProtocolState.OPEN) - self._send_command('OUT') - # Handlers --------------------------------------------------------------- - # --------- Authentication ----------------------------------------------- - def _handle_ANS(self, command): - if command.arguments[0] == 'OK': - self._state = ProtocolState.SYNCHRONIZED - self._state = ProtocolState.OPEN - else: - self._state = ProtocolState.AUTHENTICATED - self._state = ProtocolState.SYNCHRONIZING - - def _handle_USR(self, command): - self._state = ProtocolState.AUTHENTICATED - self._state = ProtocolState.SYNCHRONIZING - self._state = ProtocolState.SYNCHRONIZED - self._state = ProtocolState.OPEN - - def _handle_OUT(self, command): - pass - # --------- Invitation --------------------------------------------------- - def __participant_join(self, account, display_name, client_id): - contacts = self._client.address_book.contacts.\ - search_by_account(account) - if len(contacts) == 0: - contact = pymsn.profile.Contact(id=0, - network_id=pymsn.profile.NetworkID.MSN, - account=account, - display_name=display_name) - else: - contact = contacts[0] - contact._server_property_changed("client-capabilities", client_id) - self.participants[account] = contact - self.emit("user-joined", contact) - - def _handle_IRO(self, command): - account = command.arguments[2] - display_name = urllib.unquote(command.arguments[3]) - client_id = int(command.arguments[4]) - self.__participant_join(account, display_name, client_id) - - def _handle_JOI(self, command): - account = command.arguments[0] - display_name = urllib.unquote(command.arguments[1]) - client_id = int(command.arguments[2]) - self.__participant_join(account, display_name, client_id) - if len(self.__invitations) == 0: - self._inviting = False - - def _handle_CAL(self, command): - # this should be followed by a JOI, so we only change - # the self._inviting state until we get the actual JOI - del self.__invitations[command.transaction_id] - - def _handle_BYE(self, command): - if len(command.arguments) == 1: - account = command.arguments[0] - self.emit("user-left", self.participants[account]) - del self.participants[account] - else: - self._state = ProtocolState.CLOSED - self.participants = {} - - # --------- Messenging --------------------------------------------------- - def _handle_MSG(self, command): - account = command.arguments[0] - display_name = urllib.unquote(command.arguments[1]) - contacts = self._client.address_book.contacts.\ - search_by_account(account) - if len(contacts) == 0: - contact = pymsn.profile.Contact(id=0, - network_id=pymsn.profile.NetworkID.MSN, - account=account, - display_name=display_name) - else: - contact = contacts[0] - message = Message(contact, command.payload) - self.emit("message-received", message) - - def _handle_ACK(self, command): - self.emit("message-delivered", command) - - def _handle_NAK(self, command): - self.emit("message-undelivered", command) - - def _error_handler(self, error): - """Handles errors - - @param error: an error command object - @type error: L{command.Command} - """ - if error.name in ('208', '215', '216', '217', '713'): - try: - contact = self.__invitations[error.transaction_id] - self.emit("user-invitation-failed", contact) - del self.__invitations[error.transaction_id] - if len(self.__invitations) == 0: - self._inviting = False - except: - pass - else: - logger.error('Notification got error :' + repr(error)) - # callbacks -------------------------------------------------------------- - def _connect_cb(self, transport): - self._state = ProtocolState.OPENING - account = self._client.profile.account - if self.__key is not None: - arguments = (account, self.__key, self.__session_id) - self._send_command('ANS', arguments) - else: - arguments = (account, self.__session_id) - self._send_command('USR', arguments) - self._state = ProtocolState.AUTHENTICATING - - def _disconnect_cb(self, transport, reason): - logger.info("Disconnected") - self._state = ProtocolState.CLOSED - diff --git a/pymsn/pymsn/msnp2p/SLP.py b/pymsn/pymsn/msnp2p/SLP.py deleted file mode 100644 index c285f83b..00000000 --- a/pymsn/pymsn/msnp2p/SLP.py +++ /dev/null @@ -1,306 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.gnet.message.HTTP import HTTPMessage -from pymsn.msnp2p.exceptions import ParseError -from pymsn.msnp2p.constants import SLPContentType - -import base64 - -__all__ = ['SLPMessage', 'SLPRequestMessage', 'SLPResponseMessage', - 'SLPMessageBody', 'SLPNullBody', 'SLPSessionRequestBody', - 'SLPSessionCloseBody', 'SLPSessionFailureResponseBody'] - - -class SLPMessage(HTTPMessage): - STD_HEADERS = ["To", "From", "Via", "CSeq", "Call-ID", "Max-Forwards"] - - def __init__(self, to="", frm="", branch="", cseq=0, call_id="", max_forwards=0): - HTTPMessage.__init__(self) - self.add_header("To", "" % to) - self.add_header("From", "" % frm) - if branch: - self.add_header("Via", "MSNSLP/1.0/TLP ;branch=%s" % branch) - self.add_header("CSeq", str(cseq)) - if call_id: - self.add_header("Call-ID", call_id) - self.add_header("Max-Forwards", str(max_forwards)) - - # Make the body a SLP Message wih "null" content type - self.body = SLPNullBody() - - @property - def to(self): - to = self.get_header("To") - return to.split(":", 1)[1][:-1] - - @property - def frm(self): - frm = self.get_header("From") - return frm.split(":", 1)[1][:-1] - - @property - def branch(self): - try: - via = self.get_header("Via") - params = via.split(";", 1)[1:] - - for param in params: - key, value = param.split('=') - if key.strip() == "branch": - return value.strip() - return "" - except KeyError: - return "" - - @property - def cseq(self): - return int(self.get_header("CSeq")) - - @property - def call_id(self): - try: - return self.get_header("Call-ID") - except KeyError: - return "" - - def parse(self, chunk): - HTTPMessage.parse(self, chunk) - - content_type = self.headers.get("Content-Type", "null") - - raw_body = self.body - self.body = SLPMessageBody.build(content_type, raw_body) - - def __str__(self): - if self.body is None: - self.add_header("Content-Type", "null") - self.add_header("Content-Length", 0) - else: - self.add_header("Content-Type", self.body.content_type) - self.add_header("Content-Length", len(str(self.body))) - - return HTTPMessage.__str__(self) - - @staticmethod - def build(raw_message): - if raw_message.find("MSNSLP/1.0") < 0: - raise ParseError("message doesn't seem to be an MSNSLP/1.0 message") - start_line, content = raw_message.split("\r\n", 1) - start_line = start_line.split(" ") - - if start_line[0].strip() == "MSNSLP/1.0": - status = int(start_line[1].strip()) - reason = " ".join(start_line[2:]).strip() - slp_message = SLPResponseMessage(status, reason) - else: - method = start_line[0].strip() - resource = start_line[1].strip() - slp_message = SLPRequestMessage(method, resource) - slp_message.parse(content) - - return slp_message - - -class SLPRequestMessage(SLPMessage): - def __init__(self, method, resource, *args, **kwargs): - SLPMessage.__init__(self, *args, **kwargs) - self.method = method - self.resource = resource - - self._to = resource.split(":", 1)[1] - - def __get_to(self): - return self._to - def __set_to(self, to): - self._to = to - self.resource = "MSNMSGR:" + to - self.add_header("To", "" % to) - to = property(__get_to, __set_to) - - def __str__(self): - message = SLPMessage.__str__(self) - start_line = "%s %s MSNSLP/1.0" % (self.method, self.resource) - return start_line + "\r\n" + message - - -class SLPResponseMessage(SLPMessage): - STATUS_MESSAGE = { - 200 : "OK", - 404 : "Not Found", - 500 : "Internal Error", - 603 : "Decline", - 606 : "Unacceptable"} - - def __init__(self, status, reason=None, *args, **kwargs): - SLPMessage.__init__(self, *args, **kwargs) - self.status = int(status) - self.reason = reason - - def __str__(self): - message = SLPMessage.__str__(self) - - if self.reason is None: - reason = SLPResponseMessage.STATUS_MESSAGE[self.status] - else: - reason = self.reason - - start_line = "MSNSLP/1.0 %d %s" % (self.status, reason) - return start_line + "\r\n" + message - - -class SLPMessageBody(HTTPMessage): - content_classes = {} - - def __init__(self, content_type, session_id=None, s_channel_state=0, capabilities_flags=1): - HTTPMessage.__init__(self) - self.content_type = content_type - - if session_id is not None: - self.add_header("SessionID", session_id) - if s_channel_state is not None: - self.add_header("SChannelState", s_channel_state) - if capabilities_flags is not None: - self.add_header("Capabilities-Flags", capabilities_flags) - - - @property - def session_id(self): - try: - return int(self.get_header("SessionID")) - except (KeyError, ValueError): - return 0 - - @property - def s_channel_state(self): - try: - return int(self.get_header("SChannelState")) - except (KeyError, ValueError): - return 0 - - @property - def capabilities_flags(self): - try: - return int(self.get_header("Capabilities-Flags")) - except (KeyError, ValueError): - return 0 - - def parse(self, data): - if len(data) == 0: - return - data.rstrip('\x00') - HTTPMessage.parse(self, data) - - def __str__(self): - return HTTPMessage.__str__(self) + "\x00" - - @staticmethod - def register_content(content_type, cls): - SLPMessageBody.content_classes[content_type] = cls - - @staticmethod - def build(content_type, content): - if content_type in SLPMessageBody.content_classes.keys(): - cls = SLPMessageBody.content_classes[content_type] - body = cls(); - else: - body = SLPMessageBody(content_type) - - body.parse(content) - return body - - -class SLPNullBody(SLPMessageBody): - def __init__(self): - SLPMessageBody.__init__(self, SLPContentType.NULL) -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, - session_id, s_channel_state, capabilities_flags) - - if euf_guid is not None: - self.add_header("EUF-GUID", euf_guid) - if app_id is not None: - self.add_header("AppID", app_id) - if context is not None: - self.add_header("Context", base64.b64encode(context)) - - @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.SESSION_REQUEST, SLPSessionRequestBody) - - -class SLPSessionCloseBody(SLPMessageBody): - def __init__(self, context=None, session_id=None, s_channel_state=0, - capabilities_flags=1): - SLPMessageBody.__init__(self, SLPContentType.SESSION_CLOSE, - session_id, s_channel_state, capabilities_flags) - - if context is not None: - self.add_header("Context", base64.b64encode(context)); - - @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 - -SLPMessageBody.register_content(SLPContentType.SESSION_CLOSE, SLPSessionCloseBody) - - -class SLPSessionFailureResponseBody(SLPMessageBody): - def __init__(self, session_id=None, s_channel_state=0, capabilities_flags=1): - SLPMessageBody.__init__(self, SLPContentType.SESSION_FAILURE, - session_id, s_channel_state, capabilities_flags) - -SLPMessageBody.register_content(SLPContentType.SESSION_FAILURE, SLPSessionFailureResponseBody) - diff --git a/pymsn/pymsn/msnp2p/__init__.py b/pymsn/pymsn/msnp2p/__init__.py deleted file mode 100644 index 46db9b89..00000000 --- a/pymsn/pymsn/msnp2p/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""MSNP2P protocol implementation. -Contains a set of class enabling the communication using the MSNP2P -protocol used to transfer data between peers, such as transfer of files -and display pictures.""" - -from session_manager import P2PSessionManager -from session import OutgoingP2PSession -from constants import EufGuid, ApplicationID diff --git a/pymsn/pymsn/msnp2p/constants.py b/pymsn/pymsn/msnp2p/constants.py deleted file mode 100644 index 9b423765..00000000 --- a/pymsn/pymsn/msnp2p/constants.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -__all__ = ['EufGuid', 'ApplicationID', 'SLPContentType', 'SLPRequestMethod'] - -class EufGuid(object): - MSN_OBJECT = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}" - FILE_TRANSFER = "{5D3E02AB-6190-11D3-BBBB-00C04F795683}" - MEDIA_RECEIVE_ONLY = "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}" - MEDIA_SESSION = "{4BD96FC0-AB17-4425-A14A-439185962DC8}" - -class ApplicationID(object): - CUSTOM_EMOTICON_TRANSFER = 11 - DISPLAY_PICTURE_TRANSFER = 12 - -class SLPContentType(object): - """MSNSLP content types""" - SESSION_REQUEST = "application/x-msnmsgr-sessionreqbody" - SESSION_FAILURE = "application/x-msnmsgr-session-failure-respbody" - SESSION_CLOSE = "application/x-msnmsgr-sessionclosebody" - - TRANSFER_REQUEST = "application/x-msnmsgr-transreqbody" - TRANSFER_RESPONSE = "application/x-msnmsgr-transrespbody" - - TRANS_UDP_SWITCH = "application/x-msnmsgr-transudpswitch" - - NULL = "null" - -class SLPRequestMethod(object): - INVITE = 'INVITE' - BYE = 'BYE' - ACK = 'ACK' diff --git a/pymsn/pymsn/msnp2p/exceptions.py b/pymsn/pymsn/msnp2p/exceptions.py deleted file mode 100644 index 0a34255f..00000000 --- a/pymsn/pymsn/msnp2p/exceptions.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -__all__ = ['ProtocolError', 'ParseError', 'SLPError', 'SLPSessionError'] - -class Error(Exception): - """Generic exception type""" - def __init__(self, code, message): - Exception.__init__(self) - self.code = code - self.message = message - - def __str__(self): - return str(self.code) + ':' + str(self.message) - -class ProtocolError(Error): - """Protocol Error, it should never be thrown""" - def __init__(self, message): - Error.__init__(self, 0, message) - - def __str__(self): - return self.message - -class ParseError(Error): - """Parsing Error""" - def __init__(self, message, infos=''): - Error.__init__(self, 1, message) - self.infos = infos - - def __str__(self): - return self.message - -class SLPError(Error): - """MSNSLP error, used by the msnp2p protocol""" - def __init__(self, message): - Error.__init__(self, 2, message) - -class SLPSessionError(Error): - """SLP Session error, used by the msnp2p protocol""" - def __init__(self, message): - Error.__init__(self, 2, message) - diff --git a/pymsn/pymsn/msnp2p/session.py b/pymsn/pymsn/msnp2p/session.py deleted file mode 100644 index 63325190..00000000 --- a/pymsn/pymsn/msnp2p/session.py +++ /dev/null @@ -1,211 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.msnp2p.constants import * -from pymsn.msnp2p.SLP import * -from pymsn.msnp2p.transport import * -from pymsn.msnp2p.exceptions import * - -import pymsn.util.guid as guid - -import gobject -import base64 -import random - -__all__ = ['OutgoingP2PSession'] - -MAX_INT32 = 0x7fffffff -MAX_INT16 = 0x7fff - -def _generate_id(max=MAX_INT32): - """ - Returns a random ID. - - @return: a random integer between 1000 and sys.maxint - @rtype: integer - """ - return random.randint(1000, max) - - -class P2PSession(gobject.GObject): - __gsignals__ = { - "transfer-completed" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - def __init__(self, session_manager, peer, euf_guid="", application_id=0): - gobject.GObject.__init__(self) - self._session_manager = session_manager - self._peer = peer - - self._id = _generate_id() - self._call_id = "{%s}" % guid.generate_guid() - - self._euf_guid = euf_guid - self._application_id = application_id - - self._cseq = 0 - self._branch = "{%s}" % guid.generate_guid() - self._session_manager._register_session(self) - - @property - def id(self): - return self._id - - @property - def call_id(self): - return self._call_id - - @property - def peer(self): - return self._peer - - def _close(self): - body = SLPSessionCloseBody() - - self._cseq = 0 - self._branch = "{%s}" % guid.generate_guid() - message = SLPRequestMessage(SLPRequestMethod.BYE, - "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._send_p2p_data(message) - self._session_manager._unregister_session(self) - - def _send_p2p_data(self, data_or_file): - if isinstance(data_or_file, SLPMessage): - session_id = 0 - data = str(data_or_file) - total_size = len(data) - else: - session_id = self._id - data = data_or_file - total_size = None - - blob = MessageBlob(self._application_id, - data, total_size, session_id) - self._session_manager._transport_manager.send(self.peer, blob) - - def _on_blob_sent(self, blob): - if blob.session_id == 0: - # FIXME: handle the signaling correctly - return - - if blob.total_size == 4 and \ - blob.data.read() == ('\x00' * 4): - self._on_data_preparation_blob_sent(blob) - else: - self._on_data_blob_sent(blob) - - def _on_blob_received(self, blob): - if blob.session_id == 0: - # FIXME: handle the signaling correctly - return - - if blob.total_size == 4 and \ - blob.data.read() == ('\x00' * 4): - self._on_data_preparation_blob_received(blob) - else: - self._on_data_blob_received(blob) - self._close() - - def _on_data_preparation_blob_received(self, blob): - pass - - def _on_data_preparation_blob_sent(self, blob): - pass - - def _on_data_blob_sent(self, blob): - blob.data.seek(0, 0) - self.emit("transfer-completed", blob.data) - - def _on_data_blob_received(self, blob): - blob.data.seek(0, 0) - self.emit("transfer-completed", blob.data) - -gobject.type_register(P2PSession) - - -class IncomingP2PSession(P2PSession): - def __init__(self, session_manager, peer, id, message): - P2PSession.__init__(self, session_manager, peer, - message.body.euf_guid, message.body.application_id) - self._id = id - self._call_id = message.call_id - - self._cseq = message.cseq - self._branch = message.branch - try: - self._context = message.body.context.strip('\x00') - except AttributeError: - raise SLPError("Incoming INVITE without context") - - def accept(self, data_file): - gobject.idle_add(self._start_transfer, data_file) - - def reject(self): - self._respond(603) - - def _respond(self, status_code): - body = SLPSessionRequestBody(session_id=self._id) - - 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 _start_transfer(self, data_file): - self._respond(200) - self._send_p2p_data("\x00" * 4) - self._send_p2p_data(data_file) - return False - - -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)) - - def _invite(self, context): - self._session_manager._register_session(self) - body = SLPSessionRequestBody(self._euf_guid, self._application_id, - context, 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._send_p2p_data(message) - return False - diff --git a/pymsn/pymsn/msnp2p/session_manager.py b/pymsn/pymsn/msnp2p/session_manager.py deleted file mode 100644 index 6f9b0277..00000000 --- a/pymsn/pymsn/msnp2p/session_manager.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.msnp2p.transport import * -from pymsn.msnp2p.exceptions import * -from pymsn.msnp2p.SLP import * -from pymsn.msnp2p.session import IncomingP2PSession -from pymsn.msnp2p.constants import SLPContentType, SLPRequestMethod - -import pymsn.profile - -import gobject -import weakref -import logging - -__all__ = ['P2PSessionManager'] - -logger = logging.getLogger('msnp2p:session-manager') - -class P2PSessionManager(gobject.GObject): - __gsignals__ = { - "incoming-session" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - - def __init__(self, client): - """Initializer""" - gobject.GObject.__init__(self) - - self._client = client - self._sessions = weakref.WeakValueDictionary() # session_id => session - 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_session(self, session): - self._sessions[session.id] = session - - def _unregister_session(self, session): - del self._sessions[session.id] - - def _blob_to_session(self, blob): - # Check to see if it's a signaling message - if blob.session_id == 0: - blob.data.seek(0, 0) - slp_data = blob.data.read() - blob.data.seek(0, 0) - try: - message = SLPMessage.build(slp_data) - except ParseError: - logger.warning('Received blob with SessionID=0 and non SLP data') - raise SLPError("Non SLP data for blob with null sessionID") - session_id = message.body.session_id - - # Backward compatible with older clients that use the call-id - # for responses - if session_id == 0: - call_id = message.call_id - for session in self._sessions.itervalues(): - if session.call_id == call_id: - return session - # Couldn't find a session for the call id we received - return None - if session_id in self._sessions: - return self._sessions[session_id] - # Session doesn't exist - return None - else: - session_id = blob.session_id - if session_id in self._sessions: - return self._sessions[blob.session_id] - else: - raise SLPSessionError("Unknown session") - - def _on_blob_received(self, blob): - try: - session = self._blob_to_session(blob) - except SLPError: - # If the blob has a null session id but a badly formed SLP - # Then we should do nothing. The official client doesn't answer. - # We can't send a '500 Internal Error' response since we can't - # parse the SLP, so we don't know who to send it to, or the call-id, etc... - return - except SLPSessionError: - # 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 - return - - new_session = session is None - - # The session could not be found, create a new one if necessary - if session is None: - # Parse the SLP message. We know it's an SLP because if it was a data packet - # we would have received a ProtocolError exception - blob.data.seek(0, 0) - slp_data = blob.data.read() - blob.data.seek(0, 0) - - # No need to 'try', if it was invalid, we would have received an SLPError - message = SLPMessage.build(slp_data) - session_id = message.body.session_id - - logger.info("blob has SLP (%d):\n%s" % (session_id, message)) - - # Make sure the SLP has a session_id, otherwise, it means it's invite - # if it's a signaling SLP and the call-id could not be matched to - # an existing session - if session_id == 0: - # TODO send a 500 internal error - return - - # If there was no session then create one only if it's an INVITE - if isinstance(message, SLPRequestMessage) and \ - message.method == SLPRequestMethod.INVITE: - # Find the contact we received the message from - contacts = self._client.address_book.contacts.\ - search_by_network_id(pymsn.profile.NetworkID.MSN).\ - search_by_account(message.frm) - if len(contacts) == 0: - peer = pymsn.profile.Contact(id=0, - network_id=pymsn.profile.NetworkID.MSN, - account=message.frm, - display_name=message.frm) - else: - peer = contacts[0] - - # Create the session depending on the type of the message - if isinstance(message.body, SLPSessionRequestBody): - try: - session = IncomingP2PSession(self, peer, session_id, message) - except SLPError: - #TODO: answer with a 603 Decline ? - return - #elif isinstance(message.body, SLPTransferRequestBody): - # pass - else: - logger.warning('Received initial blob with SessionID=0 and non INVITE SLP data') - #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: - session = self._blob_to_session(blob) - except SLPError, e: - # Something is fishy.. we shouldn't have to send anything abnormal.. - logger.warning("Sent a bad message : %s" % (e)) - session = None - except SLPSessionError, e: - # May happen when we close the session - pass - - if session is None: - return - session._on_blob_sent(blob) - -gobject.type_register(P2PSessionManager) diff --git a/pymsn/pymsn/msnp2p/test.py b/pymsn/pymsn/msnp2p/test.py deleted file mode 100644 index 6c2ef7fa..00000000 --- a/pymsn/pymsn/msnp2p/test.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python - -import pymsn -import pymsn.event -from pymsn.msnp2p.session_manager import * -from pymsn.msnp2p.session import * -from pymsn.msnp2p.constants import EufGuid - -import pymsn.util.string_io as StringIO - -import logging -import gobject - -logging.basicConfig(level=logging.DEBUG) - -finished = False - -def get_proxies(): - import urllib - proxies = urllib.getproxies() - result = {} - if 'https' not in proxies and \ - 'http' in proxies: - url = proxies['http'].replace("http://", "https://") - result['https'] = pymsn.Proxy(url) - for type, url in proxies.items(): - if type == 'no': continue - if type == 'https' and url.startswith('http://'): - url = url.replace('http://', 'https://', 1) - result[type] = pymsn.Proxy(url) - return result - - -class ClientEvents(pymsn.event.ClientEventInterface): - def on_client_state_changed(self, state): - if state == pymsn.event.ClientState.CLOSED: - self._client.quit() - elif state == pymsn.event.ClientState.OPEN: - self._client.profile.display_name = "Kimbix" - self._client.profile.personal_message = "Testing pymsn, and freeing the pandas!" - -# path = '/home/jprieur/projects/pymsn.rewrite/pymsn/service/ContentRoaming/test.jpeg' -# f = open(path, 'r') -# old_pos = f.tell() -# f.seek(0, 2) -# size = f.tell() -# f.seek(old_pos,0) - -# msn_object = \ -# pymsn.p2p.MSNObject(self._client.profile, -# size, pymsn.p2p.MSNObjectType.DISPLAY_PICTURE, -# 0, "lalala") -# msn_object._data = StringIO.StringIO(f.read()) - - self._client.profile.presence_msn_object = pymsn.Presence.ONLINE, None - self._client.profile.personal_message_current_media = "yo!", None - - gobject.timeout_add(5000, self._client.request_display_picture) - - def on_client_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - -class Client(pymsn.Client): - def __init__(self, account, quit, http_mode=False): - server = ('messenger.hotmail.com', 1863) - self.quit = quit - self.account = account - if http_mode: - from pymsn.transport import HTTPPollConnection - pymsn.Client.__init__(self, server, get_proxies(), HTTPPollConnection) - else: - pymsn.Client.__init__(self, server, proxies = get_proxies()) - self.client_event_handler = ClientEvents(self) - self._p2p_session_manager = P2PSessionManager(self) - gobject.idle_add(self._connect) - - def _connect(self): - self.login(*self.account) - return False - - def request_display_picture(self): - contacts = self.address_book.contacts.\ - search_by_presence(pymsn.Presence.OFFLINE) - contacts = self.address_book.contacts - contacts - if len(contacts) == 0: - print "No online contacts" - return True - else: - contact = contacts[0] - print "CONTACT : ", contact.account, str(contact.msn_object) - if not contact.msn_object: - return True - self._msn_object_store.request(contact.msn_object, (self.__request_display_picture_callback,)) - return False - - def __request_display_picture_callback(self, msn_object): - print "Received %s" % str(msn_object) - - -def main(): - import sys - import getpass - import signal - - if "--http" in sys.argv: - http_mode = True - sys.argv.remove('--http') - else: - http_mode = False - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - passwd = getpass.getpass('Password: ') - else: - passwd = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - def quit(): - mainloop.quit() - - def sigterm_cb(): - gobject.idle_add(quit) - - signal.signal(signal.SIGTERM, sigterm_cb) - - n = Client((account, passwd), quit, http_mode) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - quit() - -if __name__ == '__main__': - main() diff --git a/pymsn/pymsn/msnp2p/transport/TLP.py b/pymsn/pymsn/msnp2p/transport/TLP.py deleted file mode 100644 index be4ac18d..00000000 --- a/pymsn/pymsn/msnp2p/transport/TLP.py +++ /dev/null @@ -1,248 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import pymsn.util.string_io as StringIO - -import struct -import random -import logging - -__all__ = ['MessageBlob'] - -MAX_INT32 = 2147483647 - -logger = logging.getLogger('msnp2p:transport') - -def _generate_id(max=MAX_INT32): - """ - Returns a random ID. - - @return: a random integer between 1000 and sys.maxint - @rtype: integer - """ - return random.randint(1000, max) - -_previous_chunk_id = _generate_id(MAX_INT32 - 1) -def _chunk_id(): - global _previous_chunk_id - _previous_chunk_id += 1 - if _previous_chunk_id == MAX_INT32: - _previous_chunk_id = 1 - return _previous_chunk_id - -class TLPHeader(object): - SIZE = 48 - - def __init__(self, *header): - header = list(header) - header[len(header):] = [0] * (9 - len(header)) - - self.session_id = header[0] - self.blob_id = header[1] - self.blob_offset = header[2] - self.blob_size = header[3] - self.chunk_size = header[4] - self.flags = header[5] - self.dw1 = header[6] - self.dw2 = header[7] - self.qw1 = header[8] - - def __str__(self): - return struct.pack(" 0: - total_size = len(data) - data = StringIO.StringIO(data) - else: - data = StringIO.StringIO() - - if total_size is None: - data.seek(0, 2) # relative to the end - total_size = data.tell() - data.seek(0, 0) - else: - total_size = 0 - - self.data = data - self.current_size = 0 - self.total_size = total_size - self.application_id = application_id - if session_id is None: - session_id = _generate_id() - self.session_id = session_id - self.id = blob_id or _generate_id() - - def __del__(self): - #if self.data is not None: - # self.data.close() - pass - - def __str__(self): - return repr(self) - - def __repr__(self): - return """""" % (self.id, self.id, - self.session_id, - self.current_size, - self.total_size, - self.application_id, - str(self.data)) - - @property - def transferred(self): - return self.current_size - - def is_complete(self): - return self.transferred == self.total_size - - def is_control_blob(self): - return False - - def get_chunk(self, max_size): - blob_offset = self.transferred - - if self.data is not None: - self.data.seek(blob_offset, 0) - data = self.data.read(max_size - TLPHeader.SIZE) - assert len(data) > 0, "Trying to read more data than available" - else: - data = "" - - header = TLPHeader() - header.session_id = self.session_id - header.blob_id = self.id - header.blob_offset = blob_offset - header.blob_size = self.total_size - header.chunk_size = len(data) - header.dw1 = _chunk_id() - if self.session_id != 0 and self.total_size != 4 and data != '\x00' * 4: - header.flags = TLPFlag.EACH - - chunk = MessageChunk(header, data) - chunk.application_id = self.application_id - self.current_size += header.chunk_size - return chunk - - def append_chunk(self, chunk): - assert self.data is not None, "Trying to write to a Read Only blob" - assert self.session_id == chunk.header.session_id, "Trying to append a chunk to the wrong blob" - assert self.id == chunk.header.blob_id, "Trying to append a chunk to the wrong blob" - self.data.seek(chunk.header.blob_offset, 0) - self.data.write(chunk.body) - self.data.seek(0, 2) - self.current_size = self.data.tell() - - -class ControlBlob(MessageBlob): - def __init__(self, session_id, flags, dw1=0, dw2=0, qw1=0): - MessageBlob.__init__(self, 0, None) - header = TLPHeader(session_id, self.id, 0, 0, 0, - flags, dw1, dw2, qw1) - self.chunk = MessageChunk(header, "") - - def __repr__(self): - return "" % (self.id, self.session_id) - - def get_chunk(self, max_size): - return self.chunk - - def is_control_blob(self): - return True - diff --git a/pymsn/pymsn/msnp2p/transport/__init__.py b/pymsn/pymsn/msnp2p/transport/__init__.py deleted file mode 100644 index b71ebb3d..00000000 --- a/pymsn/pymsn/msnp2p/transport/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""MSNP2P transport layer""" - -from TLP import * -from transport_manager import * diff --git a/pymsn/pymsn/msnp2p/transport/base.py b/pymsn/pymsn/msnp2p/transport/base.py deleted file mode 100644 index 31ac3f70..00000000 --- a/pymsn/pymsn/msnp2p/transport/base.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.msnp2p.transport.TLP import TLPFlag, MessageChunk, ControlBlob - -import gobject -import logging -import weakref - -__all__ = ['BaseP2PTransport'] - -logger = logging.getLogger('msnp2p:transport') - -class BaseP2PTransport(gobject.GObject): - __gsignals__ = { - "chunk-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "chunk-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - } - - def __init__(self, transport_manager, name): - gobject.GObject.__init__(self) - self._transport_manager = weakref.proxy(transport_manager) - self._client = transport_manager._client - self._name = name - - self._transport_manager._register_transport(self) - self._reset() - - @property - def name(self): - return self._name - - @property - def peer(self): - raise NotImplementedError - - @property - def rating(self): - raise NotImplementedError - - @property - def max_chunk_size(self): - raise NotImplementedError - - def send(self, blob, callback=None, errback=None): - if blob.is_control_blob(): - self._control_blob_queue.append((blob, callback, errback)) - else: - self._data_blob_queue.append((blob, callback, errback)) - gobject.timeout_add(200, self._process_send_queues) - self._process_send_queues() - - def close(self): - self._transport_manager._unregister_transport(self) - - def _send_chunk(self, chunk): - raise NotImplementedError - - # Helper methods - def _reset(self): - self._control_blob_queue = [] - self._data_blob_queue = [] - self._pending_ack = {} # blob_id : [blob_offset1, blob_offset2 ...] - - def _add_pending_ack(self, blob_id, chunk_id=0): - if blob_id not in self._pending_ack: - self._pending_ack[blob_id] = set() - self._pending_ack[blob_id].add(chunk_id) - - def _del_pending_ack(self, blob_id, chunk_id=0): - if blob_id not in self._pending_ack: - return - self._pending_ack[blob_id].discard(chunk_id) - - if len(self._pending_ack[blob_id]) == 0: - del self._pending_ack[blob_id] - - def _on_chunk_received(self, chunk): - if chunk.require_ack(): - self._send_ack(chunk) - - if chunk.header.flags & TLPFlag.ACK: - self._del_pending_ack(chunk.header.dw1, chunk.header.dw2) - - #FIXME: handle all the other flags - - if not chunk.is_control_chunk(): - self.emit("chunk-received", chunk) - - self._process_send_queues() - - def _on_chunk_sent(self, chunk): - self.emit("chunk-sent", chunk) - self._process_send_queues() - - def _process_send_queues(self): - if len(self._control_blob_queue) > 0: - queue = self._control_blob_queue - elif len(self._data_blob_queue) > 0: - queue = self._data_blob_queue - else: - return False - - blob, callback, errback = queue[0] - chunk = blob.get_chunk(self.max_chunk_size) - if blob.is_complete(): - # FIXME: we should keep it in the queue until we receive the ACK - queue.pop(0) - if callback: - callback[0](*callback[1:]) - - if chunk.require_ack() : - self._add_pending_ack(chunk.header.blob_id, chunk.header.dw1) - self._send_chunk(chunk) - return True - - def _send_ack(self, received_chunk): - flags = received_chunk.header.flags - - flags = TLPFlag.ACK - if received_chunk.header.flags & TLPFlag.RAK: - flags |= TLPFlag.RAK - - ack_blob = ControlBlob(0, flags, - dw1 = received_chunk.header.blob_id, - dw2 = received_chunk.header.dw1, - qw1 = received_chunk.header.blob_size) - - self.send(ack_blob) - -gobject.type_register(BaseP2PTransport) - diff --git a/pymsn/pymsn/msnp2p/transport/switchboard.py b/pymsn/pymsn/msnp2p/transport/switchboard.py deleted file mode 100644 index 98ecfe88..00000000 --- a/pymsn/pymsn/msnp2p/transport/switchboard.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.msnp.message import MessageAcknowledgement -from pymsn.msnp2p.transport.TLP import MessageChunk -from pymsn.msnp2p.transport.base import BaseP2PTransport -from pymsn.switchboard_manager import SwitchboardClient - -import gobject -import struct -import logging - -__all__ = ['SwitchboardP2PTransport'] - -logger = logging.getLogger('msnp2p:transport') - - -class SwitchboardP2PTransport(BaseP2PTransport, SwitchboardClient): - def __init__(self, client, contacts, transport_manager): - SwitchboardClient.__init__(self, client, contacts) - BaseP2PTransport.__init__(self, transport_manager, "switchboard") - - def close(self): - BaseP2PTransport.close(self) - self._leave() - - @staticmethod - def _can_handle_message(message, switchboard_client=None): - content_type = message.content_type[0] - return content_type == 'application/x-msnmsgrp2p' - - @property - def peer(self): - for peer in self.total_participants: - return peer - return None - - @property - def rating(self): - return 0 - - @property - def max_chunk_size(self): - return 1250 # length of the chunk including the header but not the footer - - def _send_chunk(self, chunk): - headers = {'P2P-Dest': self.peer.account} - content_type = 'application/x-msnmsgrp2p' - body = str(chunk) + struct.pack('>L', chunk.application_id) - self._send_message(content_type, body, headers, MessageAcknowledgement.MSNC) - - def _on_message_received(self, message): - chunk = MessageChunk.parse(message.body[:-4]) - chunk.application_id = struct.unpack('>L', message.body[-4:])[0] - self._on_chunk_received(chunk) - - def _on_message_sent(self, message): - chunk = MessageChunk.parse(message.body[:-4]) - chunk.application_id = struct.unpack('>L', message.body[-4:])[0] - self._on_chunk_sent(chunk) - - def _on_contact_joined(self, contact): - pass - - def _on_contact_left(self, contact): - self.close() - diff --git a/pymsn/pymsn/msnp2p/transport/transport_manager.py b/pymsn/pymsn/msnp2p/transport/transport_manager.py deleted file mode 100644 index d62d7f35..00000000 --- a/pymsn/pymsn/msnp2p/transport/transport_manager.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.msnp2p.transport.switchboard import * -from pymsn.msnp2p.transport.TLP import MessageBlob - -import gobject -import struct -import logging - -__all__ = ['P2PTransportManager'] - -logger = logging.getLogger('msnp2p:transport') - - -class P2PTransportManager(gobject.GObject): - __gsignals__ = { - "blob-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "blob-sent" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - - def __init__(self, client): - gobject.GObject.__init__(self) - - self._client = client - switchboard_manager = self._client._switchboard_manager - switchboard_manager.register_handler(SwitchboardP2PTransport, self) - self._default_transport = \ - lambda transport_mgr, peer : \ - SwitchboardP2PTransport(client, (peer,), transport_mgr) - self._transports = set() - self._transport_signals = {} - self._signaling_blobs = {} # blob_id => blob - self._data_blobs = {} # session_id => blob - - def _register_transport(self, transport): - assert transport not in self._transports, "Trying to register transport twice" - self._transports.add(transport) - signals = [] - signals.append(transport.connect("chunk-received", self._on_chunk_received)) - signals.append(transport.connect("chunk-sent", self._on_chunk_sent)) - self._transport_signals[transport] = signals - - def _unregister_transport(self, transport): - self._transports.discard(transport) - signals = self._transport_signals[transport] - for signal in signals: - transport.disconnect(signal) - del self._transport_signals[transport] - - def _get_transport(self, peer): - for transport in self._transports: - if transport.peer == peer: - return transport - return self._default_transport(self, peer) - - def _on_chunk_received(self, transport, chunk): - session_id = chunk.header.session_id - blob_id = chunk.header.blob_id - - if session_id == 0: # signaling blob - if blob_id in self._signaling_blobs: - blob = self._signaling_blobs[blob_id] - else: - # create an in-memory blob - blob = MessageBlob(chunk.application_id, "", - chunk.header.blob_size, - session_id, chunk.header.blob_id) - self._signaling_blobs[blob_id] = blob - else: # data blob - if session_id in self._data_blobs: - blob = self._data_blobs[session_id] - if blob.transferred == 0: - blob.id = chunk.header.blob_id - else: - # create an in-memory blob - blob = MessageBlob(chunk.application_id, "", - chunk.header.blob_size, - session_id, chunk.header.blob_id) - self._data_blobs[session_id] = blob - - blob.append_chunk(chunk) - if blob.is_complete(): - blob.data.seek(0, 0) - self.emit("blob-received", blob) - if session_id == 0: - del self._signaling_blobs[blob_id] - else: - del self._data_blobs[session_id] - - def _on_chunk_sent(self, transport, chunk): - pass - - def _on_blob_sent(self, transport, blob): - self.emit("blob-sent", blob) - - def send(self, peer, blob): - transport = self._get_transport(peer) - transport.send(blob, (self._on_blob_sent, transport, blob)) - - def register_writable_blob(self, blob): - if blob.session_id in self._data_blobs: - logger.warning("registering already registered blob "\ - "with session_id=" + str(session_id)) - return - self._data_blobs[blob.session_id] = blob - -gobject.type_register(P2PTransportManager) diff --git a/pymsn/pymsn/p2p.py b/pymsn/pymsn/p2p.py deleted file mode 100644 index d6aa1da6..00000000 --- a/pymsn/pymsn/p2p.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""P2P -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 import OutgoingP2PSession, EufGuid, ApplicationID -from msnp2p.exceptions import ParseError -from profile import NetworkID - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO - -import xml.sax.saxutils as xml -import urllib -import base64 -import sha -import logging - -__all__ = ['MSNObjectType', 'MSNObject', 'MSNObjectStore'] - -logger = logging.getLogger('p2p') - -class MSNObjectType(object): - """Represent the various MSNObject types""" - - CUSTOM_EMOTICON = 2 - "Custom smiley" - DISPLAY_PICTURE = 3 - "Display picture" - BACKGROUND_PICTURE = 5 - "Background picture" - DYNAMIC_DISPLAY_PICTURE = 7 - "Dynamic display picture" - WINK = 8 - "Wink" - VOICE_CLIP = 11 - "Void clip" - SAVED_STATE_PROPERTY = 12 - "Saved state property" - LOCATION = 14 - "Location" - -class MSNObject(object): - "Represents an MSNObject." - def __init__(self, creator, size, type, location, friendly, - shad=None, shac=None, data=None): - """Initializer - - @param creator: the creator of this MSNObject - @type creator: utf-8 encoded string representing the account - - @param size: the total size of the data represented by this MSNObject - @type size: int - - @param type: the type of the data - @type type: L{MSNObjectType} - - @param location: a filename for the MSNObject - @type location: utf-8 encoded string - - @param friendly: a friendly name for the MSNObject - @type friendly: utf-8 encoded string - - @param shad: sha1 digest of the data - - @param shac: sha1 digest of the MSNObject itself - - @param data: file object to the data represented by this MSNObject - @type data: File - """ - self._creator = creator - self._size = size - self._type = type - self._location = location - self._friendly = friendly - self._checksum_sha = shac - - if shad is None: - if data is None: - raise NotImplementedError - shad = self.__compute_data_hash(data) - self._data_sha = shad - self.__data = data - self._repr = None - - def __ne__(self, other): - return not (self == other) - - def __eq__(self, other): - if other == None: - return False - return other._type == self._type and \ - other._data_sha == self._data_sha - - def __hash__(self): - return hash(str(self._type) + self._data_sha) - - def __set_data(self, data): - if self._data_sha != self.__compute_data_hash(data): - logger.warning("Received data doesn't match the MSNObject data hash.") - return - - old_pos = data.tell() - data.seek(0, 2) - self._size = data.tell() - data.seek(old_pos, 0) - - self.__data = data - self._checksum_sha = self.__compute_checksum() - def __get_data(self): - return self.__data - _data = property(__get_data, __set_data) - - @staticmethod - def parse(client, xml_data): - data = StringIO.StringIO(xml_data) - try: - element = ElementTree.parse(data).getroot().attrib - except: - raise ParseError('Invalid MSNObject') - - try: - creator = client.address_book.contacts.\ - search_by_account(element["Creator"]).\ - search_by_network_id(NetworkID.MSN)[0] - except IndexError: - creator = None - - size = int(element["Size"]) - type = int(element["Type"]) - location = xml.unescape(element["Location"]) - friendly = base64.b64decode(xml.unescape(element["Friendly"])) - shad = element.get("SHA1D", None) - if shad is not None: - shad = base64.b64decode(shad) - shac = element.get("SHA1C", None) - if shac is not None: - shac = base64.b64decode(shac) - - result = MSNObject(creator, size, type, location, \ - friendly, shad, shac) - result._repr = xml_data - return result - - def __compute_data_hash(self, data): - digest = sha.new() - data.seek(0, 0) - read_data = data.read(1024) - while len(read_data) > 0: - digest.update(read_data) - read_data = data.read(1024) - data.seek(0, 0) - return digest.digest() - - def __compute_checksum(self): - input = "Creator%sSize%sType%sLocation%sFriendly%sSHA1D%s" % \ - (self._creator.account, str(self._size), str(self._type),\ - str(self._location), base64.b64encode(self._friendly), \ - base64.b64encode(self._data_sha)) - return sha.new(input).hexdigest() - - def __str__(self): - return self.__repr__() - - def __repr__(self): - if self._repr is not None: - return self._repr - dump = "" % \ - (xml.quoteattr(self._creator.account), - xml.quoteattr(str(self._type)), - xml.quoteattr(base64.b64encode(self._data_sha)), - xml.quoteattr(str(self._size)), - xml.quoteattr(str(self._location)), - xml.quoteattr(base64.b64encode(self._friendly))) - return dump - - -class MSNObjectStore(object): - - def __init__(self, client): - self._client = client - 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) - - def request(self, msn_object, callback, errback=None): - if msn_object._data is not None: - callback[0](msn_object, *callback[1:]) - - if msn_object._type == MSNObjectType.CUSTOM_EMOTICON: - application_id = ApplicationID.CUSTOM_EMOTICON_TRANSFER - elif msn_object._type == MSNObjectType.DISPLAY_PICTURE: - application_id = ApplicationID.DISPLAY_PICTURE_TRANSFER - else: - raise NotImplementedError - - session = OutgoingP2PSession(self._client._p2p_session_manager, - msn_object._creator, msn_object, - EufGuid.MSN_OBJECT, application_id) - handle_id = session.connect("transfer-completed", - self._outgoing_session_transfer_completed) - self._outgoing_sessions[session] = \ - (handle_id, callback, errback, msn_object) - - def publish(self, msn_object): - if msn_object._data is None: - logger.warning("Trying to publish an empty MSNObject") - else: - self._published_objects.add(msn_object) - - def _outgoing_session_transfer_completed(self, session, data): - handle_id, callback, errback, msn_object = self._outgoing_sessions[session] - session.disconnect(handle_id) - msn_object._data = data - - callback[0](msn_object, *callback[1:]) - del self._outgoing_sessions[session] - - def _incoming_session_received(self, session_manager, session): - if session._euf_guid != EufGuid.MSN_OBJECT: - return - handle_id = session.connect("transfer-completed", - self._incoming_session_transfer_completed) - self._incoming_sessions[session] = handle_id - try: - msn_object = MSNObject.parse(self._client, session._context) - except ParseError: - session.reject() - return - for obj in self._published_objects: - if obj._data_sha == msn_object._data_sha: - session.accept(obj._data) - return - session.reject() - - def _incoming_session_transfer_completed(self, session, data): - handle_id = self._incoming_sessions[session] - session.disconnect(handle_id) - del self._incoming_sessions[session] - diff --git a/pymsn/pymsn/profile.py b/pymsn/pymsn/profile.py deleted file mode 100644 index 83f5fb74..00000000 --- a/pymsn/pymsn/profile.py +++ /dev/null @@ -1,805 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Profile of the User connecting to the service, as well as the profile of -contacts in his/her contact list. - - @sort: Profile, Contact, Group, ClientCapabilities - @group Enums: Presence, Membership, Privacy, NetworkID - @sort: Presence, Membership, Privacy, NetworkID""" - -from pymsn.util.decorator import rw_property - -import gobject - -__all__ = ['Profile', 'Contact', 'Group', - 'Presence', 'Membership', 'ContactType', 'Privacy', 'NetworkID', 'ClientCapabilities'] - - -class ClientCapabilities(object): - """Capabilities of the client. This allow adverstising what the User Agent - is capable of, for example being able to receive video stream, and being - able to receive nudges... - - @ivar is_bot: is the client a bot - @type is_bot: bool - - @ivar is_mobile_device: is the client running on a mobile device - @type is_mobile_device: bool - - @ivar is_msn_mobile: is the client an MSN Mobile device - @type is_msn_mobile: bool - - @ivar is_msn_direct_device: is the client an MSN Direct device - @type is_msn_direct_device: bool - - @ivar is_media_center_user: is the client running on a Media Center - @type is_media_center_user: bool - - @ivar is_msn8_user: is the client using WLM 8 - @type is_msn8_user: bool - - @ivar is_web_client: is the client web based - @type is_web_client: bool - - @ivar is_tgw_client: is the client a gateway - @type is_tgw_client: bool - - @ivar has_space: does the user has a space account - @type has_space: bool - - @ivar has_webcam: does the user has a webcam plugged in - @type has_webcam: bool - - @ivar has_onecare: does the user has the OneCare service - @type has_onecare: bool - - @ivar renders_gif: can the client render gif (for ink) - @type renders_gif: bool - - @ivar renders_isf: can the client render ISF (for ink) - @type renders_isf: bool - - @ivar supports_chunking: does the client supports chunking messages - @type supports_chunking: bool - - @ivar supports_direct_im: does the client supports direct IM - @type supports_direct_im: bool - - @ivar supports_winks: does the client supports Winks - @type supports_winks: bool - - @ivar supports_shared_search: does the client supports Shared Search - @type supports_shared_search: bool - - @ivar supports_voice_im: does the client supports voice clips - @type supports_voice_im: bool - - @ivar supports_secure_channel: does the client supports secure channels - @type supports_secure_channel: bool - - @ivar supports_sip_invite: does the client supports SIP - @type supports_sip_invite: bool - - @ivar supports_shared_drive: does the client supports File sharing - @type supports_shared_drive: bool - - @ivar p2p_supports_turn: does the client supports TURN for p2p transfer - @type p2p_supports_turn: bool - - @ivar p2p_bootstrap_via_uun: is the client able to use and understand UUN commands - @type p2p_bootstrap_via_uun: bool - - @undocumented: __getattr__, __setattr__, __str__ - """ - - _CAPABILITIES = { - 'is_bot': 0x00020000, - 'is_mobile_device': 0x00000001, - 'is_msn_mobile': 0x00000040, - 'is_msn_direct_device': 0x00000080, - - 'is_media_center_user': 0x00002000, - 'is_msn8_user': 0x00000002, - - 'is_web_client': 0x00000200, - 'is_tgw_client': 0x00000800, - - 'has_space': 0x00001000, - 'has_webcam': 0x00000010, - 'has_onecare': 0x01000000, - - 'renders_gif': 0x00000004, - 'renders_isf': 0x00000008, - - 'supports_chunking': 0x00000020, - 'supports_direct_im': 0x00004000, - 'supports_winks': 0x00008000, - 'supports_shared_search': 0x00010000, - 'supports_voice_im': 0x00040000, - 'supports_secure_channel': 0x00080000, - 'supports_sip_invite': 0x00100000, - 'supports_shared_drive': 0x00400000, - - 'p2p_supports_turn': 0x02000000, - 'p2p_bootstrap_via_uun': 0x04000000 - } - - def __init__(self, msnc=0, client_id=0): - """Initializer - - @param msnc: The MSNC version - @type msnc: integer < 8 and >= 0 - - @param client_id: the full client ID""" - MSNC = (0x0, # MSNC0 - 0x10000000, # MSNC1 - 0x20000000, # MSNC2 - 0x30000000, # MSNC3 - 0x40000000, # MSNC4 - 0x50000000, # MSNC5 - 0x60000000, # MSNC6 - 0x70000000) # MSNC7 - object.__setattr__(self, 'client_id', MSNC[msnc] | client_id) - - def __getattr__(self, name): - if name == "p2p_aware": - mask = 0xf0000000 - elif name in self._CAPABILITIES: - mask = self._CAPABILITIES[name] - else: - raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name) - return (self.client_id & mask != 0) - - def __setattr__(self, name, value): - if name in self._CAPABILITIES: - mask = self._CAPABILITIES[name] - if value: - object.__setattr__(self, 'client_id', self.client_id | mask) - else: - object.__setattr__(self, 'client_id', self.client_id ^ mask) - else: - raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name) - - def __str__(self): - return str(self.client_id) - - -class NetworkID(object): - """Refers to the contact Network ID""" - - MSN = 1 - """Microsoft Network""" - - LCS = 2 - """Microsoft Live Communication Server""" - - MOBILE = 4 - """Mobile phones""" - - EXTERNAL = 32 - """External IM etwork, currently Yahoo!""" - - -class Presence(object): - """Presence states. - - The members of this class are used to identify the Presence that a user - wants to advertise to the contacts on his/her contact list. - - @cvar ONLINE: online - @cvar BUSY: busy - @cvar IDLE: idle - @cvar AWAY: away - @cvar BE_RIGHT_BACK: be right back - @cvar ON_THE_PHONE: on the phone - @cvar OUT_TO_LUNCH: out to lunch - @cvar INVISIBLE: status hidden from contacts - @cvar OFFLINE: offline""" - ONLINE = 'NLN' - BUSY = 'BSY' - IDLE = 'IDL' - AWAY = 'AWY' - BE_RIGHT_BACK = 'BRB' - ON_THE_PHONE = 'PHN' - OUT_TO_LUNCH = 'LUN' - INVISIBLE = 'HDN' - OFFLINE = 'FLN' - - -class Privacy(object): - """User privacy, defines the default policy concerning contacts not - belonging to the ALLOW list nor to the BLOCK list. - - @cvar ALLOW: allow by default - @cvar BLOCK: block by default""" - ALLOW = 'AL' - BLOCK = 'BL' - - -class Membership(object): - """Contact Membership""" - - NONE = 0 - """Contact doesn't belong to the contact list, but belongs to the address book""" - - FORWARD = 1 - """Contact belongs to our contact list""" - - ALLOW = 2 - """Contact is explicitely allowed to see our presence regardless of the - currently set L{Privacy}""" - - BLOCK = 4 - """Contact is explicitely forbidden from seeing our presence regardless of - the currently set L{Privacy}""" - - REVERSE = 8 - """We belong to the FORWARD list of the contact""" - - PENDING = 16 - """Contact pending""" - - -class ContactType(object): - """Automatic update status flag""" - - ME = "Me" - """Contact is the user so there's no automatic update relationship""" - - EXTERNAL = "Messenger2" - """Contact is part of an external messenger service so there's no automatic - update relationship with the user""" - - REGULAR = "Regular" - """Contact has no automatic update relationship with the user""" - - LIVE = "Live" - """Contact has an automatic update relationship with the user and an - automatic update already occured""" - - LIVE_PENDING = "LivePending" - """Contact was requested automatic update from the user and didn't - give its authorization yet""" - - LIVE_REJECTED = "LiveRejected" - """Contact was requested automatic update from the user and rejected - the request""" - - LIVE_DROPPED = "LiveDropped" - """Contact had an automatic update relationship with the user but - the contact dropped it""" - - -class Profile(gobject.GObject): - """Profile of the User connecting to the service - - @undocumented: __gsignals__, __gproperties__, do_get_property""" - - __gproperties__ = { - "display-name": (gobject.TYPE_STRING, - "Friendly name", - "A nickname that the user chooses to display to others", - "", - gobject.PARAM_READABLE), - - "personal-message": (gobject.TYPE_STRING, - "Personal message", - "The personal message that the user wants to display", - "", - gobject.PARAM_READABLE), - - "current-media": (gobject.TYPE_PYOBJECT, - "Current media", - "The current media that the user wants to display", - gobject.PARAM_READABLE), - - "profile": (gobject.TYPE_STRING, - "Profile", - "the text/x-msmsgsprofile sent by the server", - "", - gobject.PARAM_READABLE), - - "presence": (gobject.TYPE_STRING, - "Presence", - "The presence to show to others", - Presence.OFFLINE, - gobject.PARAM_READABLE), - - "privacy": (gobject.TYPE_STRING, - "Privacy", - "The privacy policy to use", - Privacy.BLOCK, - gobject.PARAM_READABLE), - - "msn-object": (gobject.TYPE_STRING, - "MSN Object", - "MSN Object attached to the user, this generally represent " - "its display picture", - "", - gobject.PARAM_READABLE), - } - - def __init__(self, account, ns_client): - gobject.GObject.__init__(self) - self._ns_client = ns_client - self._account = account[0] - self._password = account[1] - - self._profile = "" - self._display_name = self._account.split("@", 1)[0] - self._presence = Presence.OFFLINE - self._privacy = Privacy.BLOCK - self._personal_message = "" - self._current_media = None - - self.client_id = ClientCapabilities(7) - #self.client_id.supports_sip_invite = True - #FIXME: this should only be advertised when a webcam is plugged - #self.client_id.has_webcam = True - - self._msn_object = None - - self.__pending_set_presence = [self._presence, self.client_id, self._msn_object] - self.__pending_set_personal_message = [self._personal_message, self._current_media] - - @property - def account(self): - """The user account - @type: utf-8 encoded string""" - return self._account - - @property - def password(self): - """The user password - @type: utf-8 encoded string""" - return self._password - - @property - def profile(self): - """The user profile retrieved from the MSN servers - @type: utf-8 encoded string""" - return self._profile - - @property - def id(self): - """The user identifier in a GUID form - @type: GUID string""" - return "00000000-0000-0000-0000-000000000000" - - @rw_property - def display_name(): - """The display name shown to you contacts - @type: utf-8 encoded string""" - def fset(self, display_name): - if not display_name: - return - self._ns_client.set_display_name(display_name) - def fget(self): - return self._display_name - return locals() - - @rw_property - def presence(): - """The presence displayed to you contacts - @type: L{Presence}""" - def fset(self, presence): - if presence == self._presence: - return - self.__pending_set_presence[0] = presence - self._ns_client.set_presence(*self.__pending_set_presence) - def fget(self): - return self._presence - return locals() - - @rw_property - def privacy(): - """The default privacy, can be either Privacy.ALLOW or Privacy.BLOCK - @type: L{Privacy}""" - def fset(self, privacy): - pass #FIXME: set the privacy setting - def fget(self): - return self._privacy - return locals() - - @rw_property - def personal_message(): - """The personal message displayed to you contacts - @type: utf-8 encoded string""" - def fset(self, personal_message): - if personal_message == self._personal_message: - return - self.__pending_set_personal_message[0] = personal_message - self._ns_client.set_personal_message(*self.__pending_set_personal_message) - def fget(self): - return self._personal_message - return locals() - - @rw_property - def current_media(): - """The current media displayed to you contacts - @type: (artist: string, track: string)""" - def fset(self, current_media): - if current_media == self._current_media: - return - self.__pending_set_personal_message[1] = current_media - self._ns_client.set_personal_message(*self.__pending_set_personal_message) - def fget(self): - return self._current_media - return locals() - - @rw_property - def msn_object(): - """The MSNObject attached to your contact, this MSNObject represents the - display picture to be shown to your peers - @type: L{MSNObject}""" - def fset(self, msn_object): - if msn_object == self._msn_object: - return - self.__pending_set_presence[2] = msn_object - self._ns_client.set_presence(*self.__pending_set_presence) - def fget(self): - return self._msn_object - return locals() - - @rw_property - def presence_msn_object(): - def fset(self, args): - presence, msn_object = args - if presence == self._presence and msn_object == self._msn_object: - return - self.__pending_set_presence[0] = presence - self.__pending_set_presence[2] = msn_object - self._ns_client.set_presence(*self.__pending_set_presence) - def fget(self): - return self._presence, self._msn_object - return locals() - - @rw_property - def personal_message_current_media(): - def fset(self, args): - personal_message, current_media = args - if personal_message == self._personal_message and \ - current_media == self._current_media: - return - self.__pending_set_personal_message[0] = personal_message - self.__pending_set_personal_message[1] = current_media - self._ns_client.set_personal_message(*self.__pending_set_personal_message) - def fget(self): - return self._personal_message, self._current_media - return locals() - - def _server_property_changed(self, name, value): - attr_name = "_" + name.lower().replace("-", "_") - if attr_name == "_msn_object" and value is not None: - value = self.__pending_set_presence[2] - old_value = getattr(self, attr_name) - if value != old_value: - setattr(self, attr_name, value) - self.notify(name) - - def do_get_property(self, pspec): - name = pspec.name.lower().replace("-", "_") - return getattr(self, name) -gobject.type_register(Profile) - - -class Contact(gobject.GObject): - """Contact related information - @undocumented: __gsignals__, __gproperties__, do_get_property""" - - __gsignals__ = { - "infos-changed": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - } - - __gproperties__ = { - "memberships": (gobject.TYPE_UINT, - "Memberships", - "Membership relation with the contact.", - 0, 15, 0, gobject.PARAM_READABLE), - - "display-name": (gobject.TYPE_STRING, - "Friendly name", - "A nickname that the user chooses to display to others", - "", - gobject.PARAM_READWRITE), - - "personal-message": (gobject.TYPE_STRING, - "Personal message", - "The personal message that the user wants to display", - "", - gobject.PARAM_READABLE), - - "current-media": (gobject.TYPE_PYOBJECT, - "Current media", - "The current media that the user wants to display", - gobject.PARAM_READABLE), - - "presence": (gobject.TYPE_STRING, - "Presence", - "The presence to show to others", - Presence.OFFLINE, - gobject.PARAM_READABLE), - - "groups": (gobject.TYPE_PYOBJECT, - "Groups", - "The groups the contact belongs to", - gobject.PARAM_READABLE), - - "infos": (gobject.TYPE_PYOBJECT, - "Informations", - "The contact informations", - gobject.PARAM_READABLE), - - "contact-type": (gobject.TYPE_PYOBJECT, - "Contact type", - "The contact automatic update status flag", - gobject.PARAM_READABLE), - - "client-capabilities": (gobject.TYPE_UINT64, - "Client capabilities", - "The client capabilities of the contact 's client", - 0, 0xFFFFFFFF, 0, - gobject.PARAM_READABLE), - - "msn-object": (gobject.TYPE_STRING, - "MSN Object", - "MSN Object attached to the contact, this generally represent " - "its display picture", - "", - gobject.PARAM_READABLE), - } - - def __init__(self, id, network_id, account, display_name, cid=None, - memberships=Membership.NONE, contact_type=ContactType.REGULAR): - """Initializer""" - gobject.GObject.__init__(self) - self._id = id - self._cid = cid or "00000000-0000-0000-0000-000000000000" - self._network_id = network_id - self._account = account - - self._display_name = display_name - self._presence = Presence.OFFLINE - self._personal_message = "" - self._current_media = None - self._groups = set() - - self._memberships = memberships - self._contact_type = contact_type - self._client_capabilities = ClientCapabilities() - self._msn_object = None - self._infos = {} - self._attributes = {'icon_url' : None} - - def __repr__(self): - def memberships_str(): - m = [] - memberships = self._memberships - if memberships & Membership.FORWARD: - m.append('FORWARD') - if memberships & Membership.ALLOW: - m.append('ALLOW') - if memberships & Membership.BLOCK: - m.append('BLOCK') - if memberships & Membership.REVERSE: - m.append('REVERSE') - if memberships & Membership.PENDING: - m.append('PENDING') - return " | ".join(m) - template = "" - return template % (self._id, self._network_id, self._account, memberships_str()) - - @property - def id(self): - """Contact identifier in a GUID form - @type: GUID string""" - return self._id - - @property - def attributes(self): - """Contact attributes - @type: {key: string => value: string}""" - return self._attributes.copy() - - @property - def cid(self): - """Contact ID - @type: GUID string""" - return self._cid - - @property - def network_id(self): - """Contact network ID - @type: L{NetworkID}""" - return self._network_id - - @property - def account(self): - """Contact account - @type: utf-8 encoded string""" - return self._account - - @property - def presence(self): - """Contact presence - @type: L{Presence}""" - return self._presence - - @property - def display_name(self): - """Contact display name - @type: utf-8 encoded string""" - return self._display_name - - @property - def personal_message(self): - """Contact personal message - @type: utf-8 encoded string""" - return self._personal_message - - @property - def current_media(self): - """Contact current media - @type: (artist: string, track: string)""" - return self._current_media - - @property - def groups(self): - """Contact list of groups - @type: set(L{Group}...)""" - return self._groups - - @property - def infos(self): - """Contact informations - @type: {key: string => value: string}""" - return self._infos - - @property - def memberships(self): - """Contact membership value - @type: bitmask of L{Membership}s""" - return self._memberships - - @property - def contact_type(self): - """Contact automatic update status flag - @type: L{ContactType}""" - return self._contact_type - - @property - def client_capabilities(self): - """Contact client capabilities - @type: L{ClientCapabilities}""" - return self._client_capabilities - - @property - def msn_object(self): - """Contact MSN Object - @type: L{MSNObject}""" - return self._msn_object - - @property - def domain(self): - """Contact domain, which is basically the part after @ in the account - @type: utf-8 encoded string""" - result = self._account.split('@', 1) - if len(result) > 1: - return result[1] - else: - return "" - - ### membership management - def is_member(self, memberships): - """Determines if this contact belongs to the specified memberships - @type memberships: bitmask of L{Membership}s""" - return (self.memberships & memberships) == memberships - - def _set_memberships(self, memberships): - self._memberships = memberships - self.notify("memberships") - - def _add_membership(self, membership): - self._memberships |= membership - self.notify("memberships") - - def _remove_membership(self, membership): - """removes the given membership from the contact - - @param membership: the membership to remove - @type membership: int L{Membership}""" - self._memberships ^= membership - self.notify("memberships") - - def _server_property_changed(self, name, value): #FIXME, should not be used for memberships - if name == "client-capabilities": - value = ClientCapabilities(client_id=value) - attr_name = "_" + name.lower().replace("-", "_") - old_value = getattr(self, attr_name) - if value != old_value: - setattr(self, attr_name, value) - self.notify(name) - - def _server_attribute_changed(self, name, value): - self._attributes[name] = value - - def _server_infos_changed(self, updated_infos): - self._infos.update(updated_infos) - self.emit("infos-changed", updated_infos) - self.notify("infos") - - ### group management - def _add_group_ownership(self, group): - self._groups.add(group) - - def _delete_group_ownership(self, group): - self._groups.discard(group) - - def do_get_property(self, pspec): - name = pspec.name.lower().replace("-", "_") - return getattr(self, name) -gobject.type_register(Contact) - - -class Group(gobject.GObject): - """Group - @undocumented: __gsignals__, __gproperties__, do_get_property""" - - __gproperties__ = { - "name": (gobject.TYPE_STRING, - "Group name", - "Name that the user chooses for the group", - "", - gobject.PARAM_READABLE) - } - - def __init__(self, id, name): - """Initializer""" - gobject.GObject.__init__(self) - self._id = id - self._name = name - - @property - def id(self): - """Group identifier in a GUID form - @type: GUID string""" - return self._id - - @property - def name(self): - """Group name - @type: utf-8 encoded string""" - return self._name - - def _server_property_changed(self, name, value): - attr_name = "_" + name.lower().replace("-", "_") - old_value = getattr(self, attr_name) - if value != old_value: - setattr(self, attr_name, value) - self.notify(name) - - def do_get_property(self, pspec): - name = pspec.name.lower().replace("-", "_") - return getattr(self, name) -gobject.type_register(Group) diff --git a/pymsn/pymsn/service/AddressBook/__init__.py b/pymsn/pymsn/service/AddressBook/__init__.py deleted file mode 100644 index 0a673205..00000000 --- a/pymsn/pymsn/service/AddressBook/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from constants import * -from address_book import * diff --git a/pymsn/pymsn/service/AddressBook/ab.py b/pymsn/pymsn/service/AddressBook/ab.py deleted file mode 100644 index 81841b49..00000000 --- a/pymsn/pymsn/service/AddressBook/ab.py +++ /dev/null @@ -1,429 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.service.SOAPService import SOAPService -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * -from pymsn.service.AddressBook.common import * - -from pymsn.service.description.AB.constants import ContactGeneral - -__all__ = ['AB'] - -class ABResult(object): - """ABFindAll Result object""" - def __init__(self, ab, contacts, groups): - self.ab = ab - self.contacts = contacts - self.groups = groups - -class Group(object): - def __init__(self, group): - self.Id = group.findtext("./ab:groupId") - - group_info = group.find("./ab:groupInfo") - - self.Type = group_info.findtext("./ab:groupType") - self.Name = group_info.findtext("./ab:name") - self.IsNotMobileVisible = group_info.findtext("./ab:IsNotMobileVisible", "bool") - self.IsPrivate = group_info.findtext("./ab:IsPrivate", "bool") - - self.Annotations = annotations_to_dict(group_info.find("./ab:Annotations")) - - self.PropertiesChanged = [] #FIXME: implement this - self.Deleted = group.findtext("./ab:fDeleted", "bool") - self.LastChanged = group.findtext("./ab:lastChange", "bool") - - def __hash__(self): - return hash(self.Id) - - def __eq__(self, other): - return self.Id == other.Id - - def __repr__(self): - return "" % self.Id - -class ContactEmail(object): - def __init__(self, email): - self.Type = email.findtext("./ab:contactEmailType") - self.Email = email.findtext("./ab:email") - self.IsMessengerEnabled = email.findtext("./ab:isMessengerEnabled", "bool") - self.Capability = email.findtext("./ab:Capability", "int") - self.MessengerEnabledExternally = email.findtext("./ab:MessengerEnabledExternally", "bool") - -class ContactPhone(object): - def __init__(self, phone): - self.Type = phone.findtext("./ab:contactPhoneType") - self.Number = phone.findtext("./ab:number") - self.IsMessengerEnabled = phone.findtext("./ab:isMessengerEnabled", "bool") - self.PropertiesChanged = phone.findtext("./ab:propertiesChanged").split(' ') - -class ContactLocation(object): - def __init__(self, location): - self.Type = location.findtext("./ab:contactLocationType") - self.Name = location.findtext("./ab:name") - self.City = location.findtext("./ab:city") - self.Country = location.findtext("./ab:country") - self.PostalCode = location.findtext("./ab:postalcode") - self.Changes = location.findtext("./ab:Changes").split(' ') - -class Contact(object): - def __init__(self, contact): - self.Id = contact.findtext("./ab:contactId") - - contact_info = contact.find("./ab:contactInfo") - - self.Groups = [] - groups = contact_info.find("./ab:groupIds") - if groups is not None: - for group in groups: - self.Groups.append(group.text) - - self.Type = contact_info.findtext("./ab:contactType") - self.QuickName = contact_info.findtext("./ab:quickName") - self.PassportName = contact_info.findtext("./ab:passportName") - self.DisplayName = contact_info.findtext("./ab:displayName") - self.IsPassportNameHidden = contact_info.findtext("./ab:IsPassportNameHidden", "bool") - - self.FirstName = contact_info.findtext("./ab:firstName") - self.LastName = contact_info.findtext("./ab:lastName") - - self.PUID = contact_info.findtext("./ab:puid", "int") - self.CID = contact_info.findtext("./ab:CID", "int") - - self.IsNotMobileVisible = contact_info.findtext("./ab:IsNotMobileVisible", "bool") - self.IsMobileIMEnabled = contact_info.findtext("./ab:isMobileIMEnabled", "bool") - self.IsMessengerUser = contact_info.findtext("./ab:isMessengerUser", "bool") - self.IsFavorite = contact_info.findtext("./ab:isFavorite", "bool") - self.IsSmtp = contact_info.findtext("./ab:isSmtp", "bool") - self.HasSpace = contact_info.findtext("./ab:hasSpace", "bool") - - self.SpotWatchState = contact_info.findtext("./ab:spotWatchState") - self.Birthdate = contact_info.findtext("./ab:birthdate", "datetime") - - self.PrimaryEmailType = contact_info.findtext("./ab:primaryEmailType") - self.PrimaryLocation = contact_info.findtext("./ab:PrimaryLocation") - self.PrimaryPhone = contact_info.findtext("./ab:primaryPhone") - - self.IsPrivate = contact_info.findtext("./ab:IsPrivate", "bool") - self.Gender = contact_info.findtext("./ab:Gender") - self.TimeZone = contact_info.findtext("./ab:TimeZone") - - self.Annotations = annotations_to_dict(contact_info.find("./ab:annotations")) - - self.Emails = [] - emails = contact_info.find("./ab:emails") or [] - for contact_email in emails: - self.Emails.append(ContactEmail(contact_email)) - - self.PropertiesChanged = [] #FIXME: implement this - self.Deleted = contact.findtext("./ab:fDeleted", "bool") - self.LastChanged = contact.findtext("./ab:lastChanged", "datetime") - - -class AB(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "AB", proxies) - - self._last_changes = "0001-01-01T00:00:00.0000000-08:00" - - @RequireSecurityTokens(LiveService.CONTACTS) - def Add(self, callback, errback, scenario, account): - """Creates the address book on the server. - - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param scenario: "Initial" - @param account: the owner account""" - self.__soap_request(self._service.ABAll, scenario, (account,), - callback, errback) - - def _HandleABAddResponse(self, callback, errback, response, user_data): - return None - - @RequireSecurityTokens(LiveService.CONTACTS) - def FindAll(self, callback, errback, scenario, deltas_only): - """Requests the contact list. - @param scenario: "Initial" | "ContactSave" ... - @param deltas_only: True if the method should only check changes - since last_change, otherwise False - @param last_change: an ISO 8601 timestamp - (previously sent by the server), or - 0001-01-01T00:00:00.0000000-08:00 to get the whole list - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - self.__soap_request(self._service.ABFindAll, scenario, - (XMLTYPE.bool.encode(deltas_only), self._last_changes), - callback, errback) - - def _HandleABFindAllResponse(self, callback, errback, response, user_data): - last_changes = response[0].find("./ab:lastChange") - if last_changes is not None: - self._last_changes = last_changes.text - - groups = [] - contacts = [] - for group in response[1]: - groups.append(Group(group)) - - for contact in response[2]: - contacts.append(Contact(contact)) - - address_book = ABResult(None, contacts, groups) #FIXME: add support for the ab param - callback[0](address_book, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def ContactAdd(self, callback, errback, scenario, - contact_info, invite_info, auto_manage_allow_list=True): - """Adds a contact to the contact list. - - @param scenario: "ContactSave" | "ContactMsgrAPI" - @param contact_info: info dict concerning the new contact - @param invite_info: info dict concerning the sent invite - @param auto_manage_allow_list: whether to auto add to Allow role or not - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - is_messenger_user = contact_info.get('is_messenger_user', None) - if is_messenger_user is not None: - is_messenger_user = XMLTYPE.bool.encode(is_messenger_user) - self.__soap_request(self._service.ABContactAdd, scenario, - (contact_info.get('passport_name', None), - is_messenger_user, - contact_info.get('contact_type', None), - contact_info.get('first_name', None), - contact_info.get('last_name', None), - contact_info.get('birth_date', None), - contact_info.get('email', None), - contact_info.get('phone', None), - contact_info.get('location', None), - contact_info.get('web_site', None), - contact_info.get('annotation', None), - contact_info.get('comment', None), - contact_info.get('anniversary', None), - invite_info.get('display_name', ''), - invite_info.get('invite_message', ''), - contact_info.get('capability', None), - auto_manage_allow_list), - callback, errback) - - def _HandleABContactAddResponse(self, callback, errback, response, user_data): - callback[0](response.text, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def ContactDelete(self, callback, errback, scenario, - contact_id): - """Deletes a contact from the contact list. - - @param scenario: "Timer" | ... - @param contact_id: the contact id (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABContactDelete, scenario, - (contact_id,), callback, errback) - - def _HandleABContactDeleteResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def ContactUpdate(self, callback, errback, - scenario, contact_id, contact_info, - enable_allow_list_management=False): - """Updates a contact informations. - - @param scenario: "ContactSave" | "Timer" | ... - @param contact_id: the contact id (a GUID) - @param contact_info: info dict - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - if 'is_messenger_user' in contact_info: - contact_info['is_messenger_user'] = \ - XMLTYPE.bool.encode(contact_info['is_messenger_user']) - - self.__soap_request(self._service.ABContactUpdate, scenario, - (contact_id, - contact_info.get('display_name', None), - contact_info.get('is_messenger_user', None), - contact_info.get('contact_type', None), - contact_info.get(ContactGeneral.FIRST_NAME, None), - contact_info.get(ContactGeneral.LAST_NAME, None), - contact_info.get(ContactGeneral.BIRTH_DATE, None), - contact_info.get(ContactGeneral.EMAILS, None), - contact_info.get(ContactGeneral.PHONES, None), - contact_info.get(ContactGeneral.LOCATIONS, None), - contact_info.get(ContactGeneral.WEBSITES, None), - contact_info.get(ContactGeneral.ANNOTATIONS, None), - contact_info.get(ContactGeneral.COMMENT, None), - contact_info.get(ContactGeneral.ANNIVERSARY, None), - contact_info.get('has_space', None), - enable_allow_list_management), - callback, errback) - - def _HandleABContactUpdateResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupAdd(self, callback, errback, scenario, - group_name): - """Adds a group to the address book. - - @param scenario: "GroupSave" | ... - @param group_name: the name of the group - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupAdd, scenario, - (group_name,), - callback, errback) - - def _HandleABGroupAddResponse(self, callback, errback, response, user_data): - callback[0](response.text, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupDelete(self, callback, errback, scenario, - group_id): - """Deletes a group from the address book. - - @param scenario: "Timer" | ... - @param group_id: the id of the group (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupDelete, scenario, - (group_id,), callback, errback) - - def _HandleABGroupDeleteResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupUpdate(self, callback, errback, scenario, - group_id, group_name): - """Updates a group name. - - @param scenario: "GroupSave" | ... - @param group_id: the id of the group (a GUID) - @param group_name: the new name for the group - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupUpdate, scenario, - (group_id, group_name), callback, errback) - - def _HandleABGroupUpdateResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupContactAdd(self, callback, errback, scenario, - group_id, contact_id): - """Adds a contact to a group. - - @param scenario: "GroupSave" | ... - @param group_id: the id of the group (a GUID) - @param contact_id: the id of the contact to add to the - group (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupContactAdd, scenario, - (group_id, contact_id), callback, errback) - - def _HandleABGroupContactAddResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupContactDelete(self, callback, errback, scenario, - group_id, contact_id): - """Deletes a contact from a group. - - @param scenario: "GroupSave" | ... - @param group_id: the id of the group (a GUID) - @param contact_id: the id of the contact to delete from the - group (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupContactDelete, scenario, - (group_id, contact_id), callback, errback) - - def _HandleABGroupContactDeleteResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def __soap_request(self, method, scenario, args, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(scenario, token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, - self._service.url, - soap_header, soap_body, soap_action, - callback, errback, - http_headers) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](soap_response.fault.faultcode, *errback[1:]) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def ab_callback(contacts, groups): - print contacts - print groups - - sso = SingleSignOn(account, password) - ab = AB(sso) - ab.FindAll((ab_callback,), None, 'Initial', False) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/AddressBook/address_book.py b/pymsn/pymsn/service/AddressBook/address_book.py deleted file mode 100644 index a592ca1f..00000000 --- a/pymsn/pymsn/service/AddressBook/address_book.py +++ /dev/null @@ -1,701 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007-2008 Johann Prieur -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -import ab -import sharing -import scenario - -import pymsn -import pymsn.profile as profile -from pymsn.profile import NetworkID -from pymsn.util.decorator import rw_property -from pymsn.profile import ContactType -from pymsn.service.AddressBook.constants import * -from pymsn.service.description.AB.constants import * -from pymsn.service.AddressBook.scenario.contacts import * - -import gobject - -__all__ = ['AddressBook', 'AddressBookState'] - -class AddressBookStorage(set): - def __init__(self, initial_set=()): - set.__init__(self, initial_set) - - def __repr__(self): - return "AddressBook : %d contact(s)" % len(self) - - def __getitem__(self, key): - i = 0 - for contact in self: - if i == key: - return contact - i += 1 - raise IndexError("Index out of range") - - def __getattr__(self, name): - if name.startswith("search_by_"): - field = name[10:] - def search_by_func(criteria): - return self.search_by(field, criteria) - search_by_func.__name__ = name - return search_by_func - elif name.startswith("group_by_"): - field = name[9:] - def group_by_func(): - return self.group_by(field) - group_by_func.__name__ = name - return group_by_func - else: - raise AttributeError, name - - def search_by_memberships(self, memberships): - result = [] - for contact in self: - if contact.is_member(memberships): - result.append(contact) - # Do not break here, as the account - # might exist in multiple networks - return AddressBookStorage(result) - - def search_by_groups(self, *groups): - result = [] - groups = set(groups) - for contact in self: - if groups <= contact.groups: - result.append(contact) - return AddressBookStorage(result) - - def group_by_group(self): - result = {} - for contact in self: - groups = contact.groups - for group in groups: - if group not in result: - result[group] = set() - result[group].add(contact) - return result - - def search_by_predicate(self, predicate): - result = [] - for contact in self: - if predicate(contact): - result.append(contact) - return AddressBookStorage(result) - - def search_by(self, field, value): - result = [] - if isinstance(value, basestring): - value = value.lower() - for contact in self: - contact_field_value = getattr(contact, field) - if isinstance(contact_field_value, basestring): - contact_field_value = contact_field_value.lower() - if contact_field_value == value: - result.append(contact) - # Do not break here, as the account - # might exist in multiple networks - return AddressBookStorage(result) - - def group_by(self, field): - result = {} - for contact in self: - value = getattr(contact, field) - if value not in result: - result[value] = AddressBookStorage() - result[value].add(contact) - return result - - -class AddressBook(gobject.GObject): - - __gsignals__ = { - "error" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "messenger-contact-added" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), -# "email-contact-added" : (gobject.SIGNAL_RUN_FIRST, -# gobject.TYPE_NONE, -# (object,)), -# "mobile-contact-added" : (gobject.SIGNAL_RUN_FIRST, -# gobject.TYPE_NONE, -# (object,)), - - "contact-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - # FIXME: those signals will be removed in the future and will be - # moved to profile.Contact - "contact-accepted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "contact-rejected" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "contact-blocked" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "contact-unblocked" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "group-added" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "group-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "group-renamed" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "group-contact-added" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)), - "group-contact-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)) - } - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the addressbook.", - 0, 2, AddressBookState.NOT_SYNCHRONIZED, - gobject.PARAM_READABLE) - } - - def __init__(self, sso, proxies=None): - """The address book object.""" - gobject.GObject.__init__(self) - - self._ab = ab.AB(sso, proxies) - self._sharing = sharing.Sharing(sso, proxies) - - self.__state = AddressBookState.NOT_SYNCHRONIZED - - self.groups = set() - self.contacts = AddressBookStorage() - self._profile = None - - # Properties - @property - def state(self): - return self.__state - - @rw_property - def _state(): - def fget(self): - return self.__state - def fset(self, state): - self.__state = state - self.notify("state") - return locals() - - @property - def profile(self): - return self._profile - - def sync(self): - if self._state != AddressBookState.NOT_SYNCHRONIZED: - return - self._state = AddressBookState.SYNCHRONIZING - - def callback(address_book, memberships): - ab = address_book.ab - contacts = address_book.contacts - groups = address_book.groups - for group in groups: - g = profile.Group(group.Id, group.Name.encode("utf-8")) - self.groups.add(g) - for contact in contacts: - c = self.__build_contact(contact) - if c is None: - continue - if contact.Type == ContactType.ME: - self._profile = c - else: - self.contacts.add(c) - self.__update_memberships(memberships) - self._state = AddressBookState.SYNCHRONIZED - - initial_sync = scenario.InitialSyncScenario(self._ab, self._sharing, - (callback,), - (self.__common_errback,)) - initial_sync() - - # Public API - def accept_contact_invitation(self, pending_contact, add_to_contact_list=True): - def callback(contact_infos, memberships): - pending_contact.freeze_notify() - pending_contact._id = contact_infos.Id - pending_contact._cid = contact_infos.CID - pending_contact._set_memberships(memberships) - pending_contact.thaw_notify() - self.emit('contact-accepted', pending_contact) - ai = scenario.AcceptInviteScenario(self._ab, self._sharing, - (callback,), - (self.__common_errback,)) - ai.account = pending_contact.account - ai.network = pending_contact.network_id - ai.memberships = pending_contact.memberships - ai.add_to_contact_list = add_to_contact_list - ai() - - def decline_contact_invitation(self, pending_contact, block=True): - def callback(memberships): - pending_contact._set_memberships(memberships) - self.emit('contact-rejected', pending_contact) - di = scenario.DeclineInviteScenario(self._sharing, - (callback,), - (self.__common_errback,)) - di.account = pending_contact.account - di.network = pending_contact.network_id - di.memberships = pending_contact.memberships - di.block = block - di() - - def add_messenger_contact(self, account, invite_display_name='', - invite_message='', groups=[], network_id=NetworkID.MSN): - def callback(contact_guid, address_book_delta): - contacts = address_book_delta.contacts - for contact in contacts: - if contact.Id != contact_guid: - continue - try: - c = self.contacts.search_by_account(contact.PassportName).\ - search_by_network_id(NetworkID.MSN)[0] - c.freeze_notify() - c._id = contact.Id - c._cid = contact.CID - c._display_name = contact.DisplayName - for group in self.groups: - if group.id in contact.Groups: - c._add_group_ownership(group) - c._add_membership(profile.Membership.FORWARD) - c._add_membership(profile.Membership.ALLOW) - - annotations = contact.Annotations - for key in annotations: - annotations[key] = annotations[key].encode("utf-8") - contact_infos = {ContactGeneral.ANNOTATIONS : annotations} - - c._server_infos_changed(contact_infos) - c.thaw_notify() - self.unblock_contact(c) - except IndexError: - c = self.__build_contact(contact) - if c.is_member(profile.Membership.FORWARD): - c._add_membership(profile.Membership.ALLOW) - if c is None: - continue - self.contacts.add(c) - self.emit('messenger-contact-added', c) - self.unblock_contact(c) - for group in groups: - self.add_contact_to_group(group, c) - - try: - contact = self.contacts.search_by_account(account).\ - search_by_network_id(NetworkID.MSN)[0] - if not contact.is_member(profile.Membership.FORWARD) and \ - contact.id != "00000000-0000-0000-0000-000000000000": - self.__upgrade_mail_contact(contact, groups) - elif contact.id == "00000000-0000-0000-0000-000000000000": - raise IndexError - else: - return - except IndexError: - if network_id == NetworkID.MSN: - scenario_class = MessengerContactAddScenario - elif network_id == NetworkID.EXTERNAL: - scenario_class = ExternalContactAddScenario - s = scenario_class(self._ab, (callback,), (self.__common_errback,)) - s.account = account - s.invite_display_name = invite_display_name - s.invite_message = invite_message - s() - - def __upgrade_mail_contact(self, contact, groups=[]): - def callback(): - contact._add_memberships(profile.Membership.ALLOW) - for group in groups: - self.add_contact_to_group(group, contact) - - up = scenario.ContactUpdatePropertiesScenario(self._ab, - (callback,), (self.__common_errback,)) - up.contact_guid = contact.id - up.contact_properties = { 'is_messenger_user' : True } - up.enable_allow_list_management = True - up() - -# def add_email_contact(self, email_address): -# ae = scenario.EmailContactAddScenario(self._ab, -# (self.__add_email_contact_cb,), -# (self.__common_errback,)) -# ae.email_address = email_address -# ae() - -# def add_mobile_contact(self, phone_number): -# am = scenario.MobileContactAddScenario(self._ab, -# (self.__add_mobile_contact_cb,), -# (self.__common_errback,)) -# am.phone_number = phone_number -# am() - - def delete_contact(self, contact): - def callback(): - self.contacts.discard(contact) - self.emit('contact-deleted', contact) - dc = scenario.ContactDeleteScenario(self._ab, - (callback,), - (self.__common_errback,)) - dc.contact_guid = contact.id - dc() - - def update_contact_infos(self, contact, infos): - def callback(): - contact._server_infos_changed(infos) - up = scenario.ContactUpdatePropertiesScenario(self._ab, - (callback,), - (self.__common_errback,)) - up.contact_guid = contact.id - up.contact_properties = infos - up() - - def block_contact(self, contact): - def callback(memberships): - contact._set_memberships(memberships) - self.emit('contact-blocked', contact) - bc = scenario.BlockContactScenario(self._sharing, - (callback,), - (self.__common_errback,)) - bc.account = contact.account - bc.network = contact.network_id - bc.membership = contact.memberships - bc() - - def unblock_contact(self, contact): - def callback(memberships): - contact._set_memberships(memberships) - self.emit('contact-unblocked', contact) - uc = scenario.UnblockContactScenario(self._sharing, - (callback,), - (self.__common_errback,)) - uc.account = contact.account - uc.network = contact.network_id - uc.membership = contact.memberships - uc() - - def add_group(self, group_name): - def callback(group_id): - group = profile.Group(group_id, group_name) - self.groups.add(group) - self.emit('group-added', group) - ag = scenario.GroupAddScenario(self._ab, - (callback,), - (self.__common_errback,)) - ag.group_name = group_name - ag() - - def delete_group(self, group): - def callback(): - for contact in self.contacts: - contact._delete_group_ownership(group) - self.groups.discard(group) - self.emit('group-deleted', group) - dg = scenario.GroupDeleteScenario(self._ab, - (callback,), - (self.__common_errback,)) - dg.group_guid = group.id - dg() - - def rename_group(self, group, new_name): - def callback(): - group._name = group_name - self.emit('group-renamed', group) - rg = scenario.GroupRenameScenario(self._ab, - (callback,), - (self.__common_errback,)) - rg.group_guid = group.id - rg.group_name = new_name - rg() - - def add_contact_to_group(self, group, contact): - def callback(): - contact._add_group_ownership(group) - self.emit('group-contact-added', group, contact) - ac = scenario.GroupContactAddScenario(self._ab, - (callback,), - (self.__common_errback,)) - ac.group_guid = group.id - ac.contact_guid = contact.id - ac() - - def delete_contact_from_group(self, group, contact): - def callback(): - contact._delete_group_ownership(group) - self.emit('group-contact-deleted', group, contact) - dc = scenario.GroupContactDeleteScenario(self._ab, - (callback,), - (self.__common_errback,)) - dc.group_guid = group.id - dc.contact_guid = contact.id - dc() - # End of public API - - def check_pending_invitations(self): - cp = scenario.CheckPendingInviteScenario(self._sharing, - (self.__update_memberships,), - (self.__common_errback,)) - cp() - - def __build_contact(self, contact): - external_email = None - for email in contact.Emails: - if email.Type == ContactEmailType.EXTERNAL: - external_email = email - break - - if (not contact.IsMessengerUser) and (external_email is not None): - display_name = contact.DisplayName - if display_name == "": - display_name = external_email.Email - - annotations = contact.Annotations - for key in annotations: - annotations[key] = annotations[key].encode("utf-8") - contact_infos = { ContactGeneral.ANNOTATIONS : annotations } - - if contact.IsMessengerUser: - memberships = profile.Membership.FORWARD - else: - memberships = profile.Membership.NONE - c = profile.Contact(contact.Id, - NetworkID.EXTERNAL, - external_email.Email.encode("utf-8"), - display_name.encode("utf-8"), - contact.CID, - memberships, - contact.Type) - c._server_infos_changed(contact_infos) - - for group in self.groups: - if group.id in contact.Groups: - c._add_group_ownership(group) - - return c - - elif contact.PassportName == "": - # FIXME : mobile phone and mail contacts here - return None - else: - display_name = contact.DisplayName - if display_name == "": - display_name = contact.QuickName - if display_name == "": - display_name = contact.PassportName - - annotations = contact.Annotations - for key in annotations: - annotations[key] = annotations[key].encode("utf-8") - contact_infos = {ContactGeneral.ANNOTATIONS : annotations} - - if contact.IsMessengerUser: - memberships = profile.Membership.FORWARD - else: - memberships = profile.Membership.NONE - c = profile.Contact(contact.Id, - NetworkID.MSN, - contact.PassportName.encode("utf-8"), - display_name.encode("utf-8"), - contact.CID, - memberships) - c._server_infos_changed(contact_infos) - - for group in self.groups: - if group.id in contact.Groups: - c._add_group_ownership(group) - - return c - return None - - def __update_memberships(self, memberships): - for member in memberships: - if isinstance(member, sharing.PassportMember): - network = NetworkID.MSN - elif isinstance(member, sharing.EmailMember): - network = NetworkID.EXTERNAL - else: - continue - - try: - contact = self.contacts.search_by_account(member.Account).\ - search_by_network_id(network)[0] - except IndexError: - contact = None - - new_contact = False - if contact is None: - new_contact = True - try: - cid = member.CID - except AttributeError: - cid = None - msg = member.Annotations.get('MSN.IM.InviteMessage', u'') - c = profile.Contact("00000000-0000-0000-0000-000000000000", - network, - member.Account.encode("utf-8"), - member.DisplayName.encode("utf-8"), - cid) - c._server_attribute_changed('invite_message', msg.encode("utf-8")) - self.contacts.add(c) - contact = c - - for role in member.Roles: - if role == "Allow": - membership = profile.Membership.ALLOW - elif role == "Block": - membership = profile.Membership.BLOCK - elif role == "Reverse": - membership = profile.Membership.REVERSE - elif role == "Pending": - membership = profile.Membership.PENDING - else: - raise NotImplementedError("Unknown Membership Type : " + membership) - contact._add_membership(membership) - - if new_contact and self.state == AddressBookState.SYNCHRONIZED: - self.emit('messenger-contact-added', contact) - - # Callbacks - def __common_errback(self, error_code, *args): - self.emit('error', error_code) - -gobject.type_register(AddressBook) - -if __name__ == '__main__': - def get_proxies(): - import urllib - proxies = urllib.getproxies() - result = {} - if 'https' not in proxies and \ - 'http' in proxies: - url = proxies['http'].replace("http://", "https://") - result['https'] = pymsn.Proxy(url) - for type, url in proxies.items(): - if type == 'no': continue - if type == 'https' and url.startswith('http://'): - url = url.replace('http://', 'https://', 1) - result[type] = pymsn.Proxy(url) - return result - - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.description.AB.constants import ContactGeneral - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def address_book_state_changed(address_book, pspec): - if address_book.state == AddressBookState.SYNCHRONIZED: - for group in address_book.groups: - print "Group : %s " % group.name - - for contact in address_book.contacts: - print "Contact : %s (%s) %s" % \ - (contact.account, - contact.display_name, - contact.network_id) - - print address_book.contacts[0].account - address_book.update_contact_infos(address_book.contacts[0], {ContactGeneral.FIRST_NAME : "lolibouep"}) - - #address_book._check_pending_invitations() - #address_book.accept_contact_invitation(address_book.pending_contacts.pop()) - #print address_book.pending_contacts.pop() - #address_book.accept_contact_invitation(address_book.pending_contacts.pop()) - #address_book.add_group("ouch2") - #address_book.add_group("callback test6") - #group = address_book.groups.values()[0] - #address_book.delete_group(group) - #address_book.delete_group(group) - #address_book.rename_group(address_book.groups.values()[0], "ouch") - #address_book.add_contact_to_group(address_book.groups.values()[1], - # address_book.contacts[0]) - #contact = address_book.contacts[0] - #address_book.delete_contact_from_group(address_book.groups.values()[0], - # contact) - #address_book.delete_contact_from_group(address_book.groups.values()[0], - # contact) - #address_book.block_contact(address_book.contacts.search_by_account('pymsn.rewrite@yahoo.com')[0]) - #address_book.block_contact(address_book.contacts.search_by_account('pymsn.rewrite@yahoo.com')[0]) - #address_book.unblock_contact(address_book.contacts[0]) - #address_book.block_contact(address_book.contacts[0]) - #contact = address_book.contacts[2] - #address_book.delete_contact(contact) - #address_book.delete_contact(contact) - #g=list(address_book.groups) - #address_book.add_messenger_contact("wikipedia-bot@hotmail.com",groups=g) - - #for i in range(5): - # address_book.delete_contact(address_book.contacts[i]) - #address_book.add_messenger_contact("johanssn.prieur@gmail.com") - - def messenger_contact_added(address_book, contact): - print "Added contact : %s (%s) %s %s" % (contact.account, - contact.display_name, - contact.network_id, - contact.memberships) - - sso = SingleSignOn(account, password, proxies=get_proxies()) - address_book = AddressBook(sso, proxies=get_proxies()) - address_book.connect("notify::state", address_book_state_changed) - address_book.connect("messenger-contact-added", messenger_contact_added) - address_book.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/AddressBook/common.py b/pymsn/pymsn/service/AddressBook/common.py deleted file mode 100644 index 6e7f6bd2..00000000 --- a/pymsn/pymsn/service/AddressBook/common.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -__all__ = ['annotations_to_dict'] - -def annotations_to_dict(annotations): - if annotations is None: - return {} - - result = {} - for annotation in annotations: - key = annotation.findtext("./ab:Name") - value = annotation.findtext("./ab:Value") - result[key] = value - return result - diff --git a/pymsn/pymsn/service/AddressBook/constants.py b/pymsn/pymsn/service/AddressBook/constants.py deleted file mode 100644 index 27d913cc..00000000 --- a/pymsn/pymsn/service/AddressBook/constants.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -__all__ = ['AddressBookError', 'AddressBookState'] - -class AddressBookError(object): - "Address book related errors" - UNKNOWN = 0 - - CONTACT_ALREADY_EXISTS = 1 - CONTACT_DOES_NOT_EXIST = 2 - INVALID_CONTACT_ADDRESS = 3 - - GROUP_ALREADY_EXISTS = 4 - GROUP_DOES_NOT_EXIST = 5 - CONTACT_NOT_IN_GROUP = 6 - - MEMBER_ALREADY_EXISTS = 7 - MEMBER_DOES_NOT_EXIST = 8 - - -class AddressBookState(object): - """Addressbook synchronization state. - - An adressbook is said to be synchronized when it - matches the addressbook stored on the server.""" - - NOT_SYNCHRONIZED = 0 - """The addressbook is not synchronized yet""" - SYNCHRONIZING = 1 - """The addressbook is being synchronized""" - SYNCHRONIZED = 2 - """The addressbook is already synchronized""" - diff --git a/pymsn/pymsn/service/AddressBook/scenario/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/__init__.py deleted file mode 100644 index bc483322..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "scenario" -description = "" - -from sync import * -from contacts import * -from groups import * - diff --git a/pymsn/pymsn/service/AddressBook/scenario/base.py b/pymsn/pymsn/service/AddressBook/scenario/base.py deleted file mode 100644 index 9b06504d..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/base.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ['BaseScenario', 'Scenario'] - -class BaseScenario(object): - def __init__(self, partner_scenario, callback, errback): - self._scenario = partner_scenario - self._callback = callback - self._errback = errback - - def __set_scenario(self, scenario): - self._scenario = scenario - def __get_scenario(self): - return self._scenario - scenario = property(__get_scenario, __set_scenario) - - def execute(self): - pass - - def __call__(self): - return self.execute() - -class Scenario(object): - """Scenario label""" - - INITIAL = "Initial" - TIMER = "Timer" - CONTACT_SAVE = "ContactSave" - GROUP_SAVE = "GroupSave" - BLOCK_UNBLOCK = "BlockUnblock" - CONTACT_MSGR_API = "ContactMsgrAPI" - MOBILE_CONTACT_MSGR_API = "MobileContactMsgrAPI" - MESSENGER_PENDING_LIST = "MessengerPendingList" diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py deleted file mode 100644 index 683c0a93..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from accept_invite import * -from decline_invite import * -from check_pending_invite import * - -from update_memberships import * -from block_contact import * -from unblock_contact import * - -from contact_update_properties import * -from contact_delete import * - -from email_contact_add import * -from messenger_contact_add import * -from external_contact_add import * -from mobile_contact_add import * - diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py deleted file mode 100644 index 4fabb297..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from messenger_contact_add import MessengerContactAddScenario -from external_contact_add import ExternalContactAddScenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.service.AddressBook.constants import * -from pymsn.profile import NetworkID, Membership - -__all__ = ['AcceptInviteScenario'] - -class AcceptInviteScenario(BaseScenario): - def __init__(self, ab, sharing, callback, errback, - account='', - memberships=Membership.NONE, - network=NetworkID.MSN, - state='Accepted'): - """Accepts an invitation. - - @param ab: the address book service - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.CONTACT_MSGR_API, callback, errback) - self.__ab = ab - self.__sharing = sharing - - self.add_to_contact_list = True - - self.account = account - self.memberships = memberships - self.network = network - self.state = state - - def execute(self): - if self.add_to_contact_list and not (self.memberships & Membership.FORWARD): - if self.network == NetworkID.MSN: - am = MessengerContactAddScenario(self.__ab, - (self.__add_contact_callback,), - (self.__add_contact_errback,), - self.account) - am() - elif self.network == NetworkID.EXTERNAL: - em = ExternalContactAddScenario(self.__ab, - (self.__add_contact_callback,), - (self.__add_contact_errback,), - self.account) - em() - else: - # FIXME: maybe raise an exception ? - self.__update_memberships() - else: - self.__update_memberships() - - def __update_memberships(self): - new_membership = (self.memberships & ~Membership.PENDING) | \ - Membership.ALLOW | Membership.REVERSE - um = UpdateMembershipsScenario(self.__sharing, - (self.__update_memberships_callback,), - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.memberships, - new_membership) - um() - - def __add_contact_callback(self, contact_guid, address_book_delta): - contacts = address_book_delta.contacts - self.memberships |= Membership.ALLOW | Membership.FORWARD - for contact in contacts: - if contact.Id != contact_guid: - continue - self._added_contact = contact - break - self.__update_memberships() - - def __add_contact_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactAlreadyExists': - errcode = AddressBookError.CONTACT_ALREADY_EXISTS - elif error_code == 'InvalidPassportUser': - errcode = AddressBookError.INVALID_CONTACT_ADDRESS - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __update_memberships_callback(self, memberships): - self.memberships = memberships - contact = self._added_contact - callback[0](contact, memberships, *callback[1:]) - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py deleted file mode 100644 index 604453f7..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.profile import Membership -from pymsn.profile import NetworkID - -__all__ = ['BlockContactScenario'] - -class BlockContactScenario(BaseScenario): - def __init__(self, sharing, callback, errback, account='', - network=NetworkID.MSN, membership=Membership.NONE, - state='Accepted'): - """Blocks a contact. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.BLOCK_UNBLOCK, callback, errback) - self.__sharing = sharing - - self.account = account - self.network = network - self.membership = membership - self.state = state - - def execute(self): - new_membership = self.membership & ~Membership.ALLOW | Membership.BLOCK - um = UpdateMembershipsScenario(self.__sharing, - self._callback, - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.membership, - new_membership) - um() - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py deleted file mode 100644 index f418949f..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * - -__all__ = ['CheckPendingInviteScenario'] - -class CheckPendingInviteScenario(BaseScenario): - def __init__(self, sharing, callback, errback): - """Checks the pending invitations. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.MESSENGER_PENDING_LIST, callback, errback) - self.__sharing = sharing - - def execute(self): - self.__sharing.FindMembership((self.__membership_findall_callback,), - (self.__membership_findall_errback,), - self._scenario, ['Messenger'], True) - - def __membership_findall_callback(self, result): - callback = self._callback - callback[0](result, *callback[1:]) - - def __membership_findall_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py deleted file mode 100644 index 3a1621f2..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * - -__all__ = ['ContactDeleteScenario'] - -class ContactDeleteScenario(BaseScenario): - def __init__(self, ab, callback, errback, contact_guid=''): - """Deletes a contact from the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param contact_guid: the guid of the contact to delete""" - BaseScenario.__init__(self, Scenario.TIMER, callback, errback) - self.__ab = ab - - self.contact_guid = contact_guid - - def execute(self): - self.__ab.ContactDelete((self.__contact_delete_callback,), - (self.__contact_delete_errback,), - self._scenario, self.contact_guid) - - def __contact_delete_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __contact_delete_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactDoesNotExist': - errcode = AddressBookError.CONTACT_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py deleted file mode 100644 index 09f38d0c..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * - -__all__ = ['ContactUpdatePropertiesScenario'] - -class ContactUpdatePropertiesScenario(BaseScenario): - def __init__(self, ab, callback, errback, contact_guid='', - contact_properties={}): - """Updates a contact properties - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param contact_guid: the guid of the contact to update""" - BaseScenario.__init__(self, Scenario.CONTACT_SAVE, callback, errback) - self.__ab = ab - - self.contact_guid = contact_guid - self.contact_properties = contact_properties - self.enable_allow_list_management = False - - def execute(self): - self.__ab.ContactUpdate((self.__contact_update_callback,), - (self.__contact_update_errback,), - self._scenario, self.contact_guid, - self.contact_properties, - self.enable_allow_list_management) - - def __contact_update_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __contact_update_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactDoesNotExist': - errcode = AddressBookError.CONTACT_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py deleted file mode 100644 index 9b6c2b82..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.constants import AddressBookError -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.profile import Membership -from pymsn.profile import NetworkID - -__all__ = ['DeclineInviteScenario'] - -class DeclineInviteScenario(BaseScenario): - def __init__(self, sharing, callback, errback, account='', - network=NetworkID.MSN, memberships=Membership.NONE, - state='Accepted', block=True): - """Declines an invitation. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.TIMER, callback, errback) - self.__sharing = sharing - - self.account = account - self.network = network - self.memberships = memberships - self.state = state - self.block = block - - def execute(self): - new_memberships = self.memberships & ~Membership.PENDING - if self.block: - new_memberships |= Membership.BLOCK - um = UpdateMembershipsScenario(self.__sharing, - self._callback, - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.memberships, - new_memberships) - um() - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py deleted file mode 100644 index ca2b329c..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -__all__ = ['EmailContactAddScenario'] - -class EmailContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, email_address="", contact_info={}): - """Adds a mail contact and updates the address book. - - @param ab: the adress book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.CONTACT_SAVE, callback, errback) - self.__ab = ab - - self.__email_address = email_address - self.__contact_info = contact_info - - def __set_email_address(self, email_address): - self.__email_address = email_address - def __get_email_address(self): - return self.__email_address - email_address = property(__get_email_address, __set_email_address, - doc="The mail address of the contact") - - def __set_contact_info(self, contact_info): - self.__contact_info = contact_info - def __get_contact_info(self): - return self.__contact_info - contact_info = property(__get_contact_info, __set_contact_info, - doc="A dict which contains addressbook " \ - "information about the contact") - - def execute(self): - contact_info['passport_name'] = self.__email_address - contact_info['is_messenger_user'] = False - self.__ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self.__scenario, self.__contact_info, {}) - - def __contact_add_callback(self, stuff): - self._callback(stuff) - # TODO : get the cached lastchanged date to make a delta findall - # or directly call a sync scenario - self.__ab.FindAll(self.__scenario, True, None, - self.__find_all_callback, self.__find_all_errback) - - def __contact_add_errback(self, reason): - # TODO : analyse the reason, and maybe call execute again - # instead of transmitting it via _errback. - self._errback(reason) - - def __find_all_callback(self): - # TODO : complete the contact list in the client, need to access to - # the local address book storage, not the service.. - pass - - def __find_all_errback(self, reason): - self._errback(reason) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py deleted file mode 100644 index 30661d16..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * -from pymsn.service.description.AB.constants import ContactEmailType -from pymsn.profile import NetworkID - -__all__ = ['ExternalContactAddScenario'] - -class ExternalContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, account='', - network_id=NetworkID.EXTERNAL, contact_info={}, - invite_display_name='', invite_message=''): - """Adds an external messenger contact and updates the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.CONTACT_MSGR_API, callback, errback) - - self._ab = ab - - self.account = account - self.network_id = network_id - self.contact_info = contact_info - - self.invite_display_name = invite_display_name - self.invite_message = invite_message - - def execute(self): - invite_info = { 'display_name' : self.invite_display_name , - 'invite_message' : self.invite_message } - - if self.contact_info.get('email', None) is None: - self.contact_info['email'] = \ - { ContactEmailType.EXTERNAL : self.account } - else: - self.contact_info['email'][ContactEmailType.EXTERNAL] = self.account - self.contact_info['capability'] = self.network_id - self._ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self._scenario, - self.contact_info, - invite_info) - - def __contact_add_callback(self, contact_guid): - self._ab.FindAll((self.__find_all_callback, contact_guid), - (self.__find_all_errback, contact_guid), - self._scenario, True) - - def __contact_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __find_all_callback(self, delta, contact_guid): - callback = self._callback - callback[0](contact_guid, delta, *callback[1:]) - - def __find_all_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py deleted file mode 100644 index d8b525d0..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * -from pymsn.profile import ContactType - -__all__ = ['MessengerContactAddScenario'] - -class MessengerContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, - account='', - contact_type=ContactType.REGULAR, - contact_info={}, - invite_display_name='', - invite_message=''): - """Adds a messenger contact and updates the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.CONTACT_SAVE, callback, errback) - - self._ab = ab - - self.account = account - - self.contact_type = contact_type - self.contact_info = contact_info - - self.invite_display_name = invite_display_name - self.invite_message = invite_message - self.auto_manage_allow_list = True - - def execute(self): - invite_info = { 'display_name' : self.invite_display_name , - 'invite_message' : self.invite_message } - - self.contact_info['passport_name'] = self.account - self.contact_info['contact_type'] = self.contact_type - self.contact_info['is_messenger_user'] = True - self._ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self._scenario, - self.contact_info, - invite_info, - self.auto_manage_allow_list) - - def __contact_add_callback(self, contact_guid): - self._ab.FindAll((self.__find_all_callback, contact_guid), - (self.__find_all_errback, contact_guid), - self._scenario, True) - - def __contact_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactAlreadyExists': - errcode = AddressBookError.CONTACT_ALREADY_EXISTS - elif error_code == 'InvalidPassportUser': - errcode = AddressBookError.INVALID_CONTACT_ADDRESS - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __find_all_callback(self, address_book_delta, contact_guid): - callback = self._callback - callback[0](contact_guid, address_book_delta, *callback[1:]) - - def __find_all_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py deleted file mode 100644 index ab9af251..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.description.AB import ContactPhoneType - -__all__ = ['MobileContactAddScenario'] - -class MobileContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, - phone_number="", contact_info={}): - """Adds a mobile contact and updates the address book. - - @param ab: the adress book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.MOBILE_CONTACT_MSGR_API, callback, errback) - self.__ab = ab - - self.__phone_number = phone_number - self.__contact_info = contact_info - - def __set_phone_number(self, phone_number): - self.__phone_number = phone_number - def __get_phone_number(self): - return self.__phone_number - phone_number = property(__get_phone_number, __set_phone_number) - - def __set_contact_info(self, contact_info): - self.__contact_info = contact_info - def __get_contact_info(self): - return self.__contact_info - contact_info = property(__get_contact_info, __set_contact_info) - - def execute(self): - phones = self.__contact_info.get('phone', {}) - phones[ContactPhoneType.MOBILE] = self.__phone_number - # self.__contact_info['phone'] = phones - self.__ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self.__scenario, - self.__contact_info, - {}) - - def __contact_add_callback(self, stuff): - self._callback(stuff) - # TODO : get the cached lastchanged date to make a delta findall - # or directly call a sync scenario - self.__ab.FindAll(self.__scenario, True, None, - self.__find_all_callback, self.__find_all_errback) - - def __contact_add_errback(self, reason): - # TODO : analyse the reason, and maybe call execute again - # instead of transmitting it via _errback. - self._errback(reason) - - def __find_all_callback(self): - # TODO : complete the contact list in the client, need to access to - # the local address book storage, not the service.. - pass - - def __find_all_errback(self, reason): - self._errback(reason) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py deleted file mode 100644 index 904e6bb3..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.profile import Membership -from pymsn.profile import NetworkID - -__all__ = ['UnblockContactScenario'] - -class UnblockContactScenario(BaseScenario): - def __init__(self, sharing, callback, errback, account='', - network=NetworkID.MSN, membership=Membership.NONE, - state='Accepted'): - """Unblocks a contact. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.BLOCK_UNBLOCK, callback, errback) - self.__sharing = sharing - - self.account = account - self.network = network - self.membership = membership - self.state = state - - def execute(self): - new_membership = self.membership & ~Membership.BLOCK | Membership.ALLOW - um = UpdateMembershipsScenario(self.__sharing, - self._callback, - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.membership, - new_membership) - um() - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py deleted file mode 100644 index 821ce4cd..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2008 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.constants import * - -from pymsn.profile import NetworkID -from pymsn.profile import Membership - -__all__ = ['UpdateMembershipsScenario'] - -class UpdateMembershipsScenario(BaseScenario): - """Scenario used to update contact memberships in a safe way. - @undocumented: __membership_mapping, __contact_type""" - - __mapping = { Membership.FORWARD: "Forward", - Membership.ALLOW: "Allow", - Membership.BLOCK: "Block", - Membership.REVERSE: "Reverse", - Membership.PENDING: "Pending" } - - __contact_type = { NetworkID.MSN: "Passport", - NetworkID.EXTERNAL: "Email" } - - def __init__(self, sharing, callback, errback, scenario, - account, network, state, old_membership, new_membership): - """Updates contact memberships. - - @type scenario: L{Scenario} - @type network: L{NetworkID} - @type old_memberships: bitmask of L{Membership} - @type new_memberships: bitmask of L{Membership} - """ - BaseScenario.__init__(self, scenario, callback, errback) - self.__sharing = sharing - - self.account = account - self.contact_type = UpdateMembershipsScenario.__contact_type[network] - self.old = old_membership - self.new = new_membership - self.state = state - - # We keep a trace of what changes are actually done to pass it through - # the callback or the errback so that the executor of the scenario can - # update the memberships property of the contact. - self.__done = old_membership - - # Subscription to the REVERSE or ALLOW lists can only occur when the - # contact is member of the PENDING list, so when a subscription to the - # REVERSE or ALLOW membership is detected, we delay the eventual deletion - # from the PENDING membership list. - self.__late_pending_delete = False - - def _change(self, membership): - return (membership & (self.old ^ self.new)) - - def _add(self, membership): - return (self._change(membership) and (membership & self.new)) - - def _delete(self, membership): - return (self._change(membership) and (membership & self.old)) - - def execute(self): - if (self._add(Membership.REVERSE) or self._add(Membership.ALLOW)) and \ - self._delete(Membership.PENDING): - self.__late_pending_delete = True - - self.__process_delete(UpdateMembershipsScenario.__mapping.keys(), - Membership.NONE) - - def __process_delete(self, memberships, last): - self.__done &= ~last - - if memberships == []: - self.__process_add(UpdateMembershipsScenario.__mapping.keys(), - Membership.NONE) - return - - current = memberships.pop() - if self._delete(current) and not (current == Membership.PENDING and \ - self.__late_pending_delete): - membership = UpdateMembershipsScenario.__mapping[current] - self.__sharing.DeleteMember((self.__process_delete, memberships, current), - (self.__common_errback, self.__done, current), - self._scenario, membership, - self.contact_type, self.state, - self.account) - else: - self.__process_delete(memberships, Membership.NONE) - - def __process_add(self, memberships, last): - self.__done |= last - - if memberships == []: - if self.__late_pending_delete: - membership = UpdateMembershipsScenario.__mapping[Membership.PENDING] - callback = list(self._callback) - callback.insert(1, self.__done) - self.__sharing.DeleteMember(callback, - (self.__common_errback, self.__done, - Membership.PENDING), - self._scenario, membership, - self.contact_type, self.state, - self.account) - else: - callback = self._callback - callback[0](self.__done, *callback[1:]) - return - - current = memberships.pop() - if self._add(current): - membership = UpdateMembershipsScenario.__mapping[current] - self.__sharing.AddMember((self.__process_add, memberships, current), - (self.__common_errback, self.__done, current), - self._scenario, membership, - self.contact_type, self.state, - self.account) - else: - self.__process_add(memberships, Membership.NONE) - - def __common_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - if error_code == 'MemberAlreadyExists': - errcode = AddressBookError.MEMBER_ALREADY_EXISTS - elif error_code == 'MemberDoesNotExist': - errcode = AddressBookError.MEMBER_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, done, failed, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py deleted file mode 100644 index 492af99a..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from group_add import * -from group_contact_add import * -from group_contact_delete import * -from group_delete import * -from group_rename import * - diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py deleted file mode 100644 index e2fc00a4..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupAddScenario'] - -class GroupAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_name=''): - """Adds a group to the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_name: the name of the new group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_name = group_name - - def execute(self): - self.__ab.GroupAdd((self.__group_add_callback,), - (self.__group_add_errback,), - self._scenario, self.group_name) - - def __group_add_callback(self, group_guid): - callback = self._callback - callback[0](group_guid, *callback[1:]) - - def __group_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'GroupAlreadyExists': - errcode = AddressBookError.GROUP_ALREADY_EXISTS - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py deleted file mode 100644 index 64e5efdd..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupContactAddScenario'] - -class GroupContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid='', contact_guid=''): - """Adds a contact to a group. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group - @param contact_guid: the guid of the contact to add to the group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - self.contact_guid = contact_guid - - def execute(self): - self.__ab.GroupContactAdd((self.__group_contact_add_callback,), - (self.__group_contact_add_errback,), - self._scenario, self.group_guid, - self.contact_guid) - - def __group_contact_add_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_contact_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py deleted file mode 100644 index 80d2986a..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario - -__all__ = ['GroupContactDeleteScenario'] - -class GroupContactDeleteScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid='', contact_guid=''): - """Deletes a contact to a group. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group - @param contact_guid: the guid of the contact to delete from the group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - self.contact_guid = contact_guid - - def execute(self): - self.__ab.GroupContactDelete((self.__group_contact_delete_callback,), - (self.__group_contact_delete_errback,), - self._scenario, self.group_guid, - self.contact_guid) - - def __group_contact_delete_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_contact_delete_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactDoesNotExist': - errcode = AddressBookError.CONTACT_NOT_IN_GROUP - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py deleted file mode 100644 index 548dce0f..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupDeleteScenario'] - -class GroupDeleteScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid=''): - """Deletes a group from the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group to delete""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - - def execute(self): - self.__ab.GroupDelete((self.__group_delete_callback,), - (self.__group_delete_errback,), - self._scenario, self.group_guid) - - def __group_delete_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_delete_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'GroupDoesNotExist': - errcode = AddressBookError.GROUP_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py deleted file mode 100644 index 06cf01ef..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupRenameScenario'] - -class GroupRenameScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid='', group_name=''): - """Renames a group to the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group to rename - @param group_name: the new name for the group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - self.group_name = group_name - - def execute(self): - self.__ab.GroupUpdate((self.__group_rename_callback,), - (self.__group_rename_errback,), - self._scenario, self.group_guid, - self.group_name) - - def __group_rename_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_rename_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'GroupAlreadyExists': - errcode = AddressBookError.GROUP_ALREADY_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py deleted file mode 100644 index 37bc43cd..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from initial_sync import * diff --git a/pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py b/pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py deleted file mode 100644 index b906673c..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.constants import * - -__all__ = ['InitialSyncScenario'] - -class InitialSyncScenario(BaseScenario): - def __init__(self, address_book, membership, callback, errback, account=''): - """Synchronizes the membership content when logging in. - - @param membership: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, 'Initial', callback, errback) - self.__membership = membership - self.__address_book = address_book - - self.__membership_response = None - self.__ab_response = None - - # FIXME : get the real account for 'Me' - self.__account = account - - def execute(self): - self.__address_book.FindAll((self.__ab_findall_callback,), - (self.__ab_findall_errback,), - self._scenario, False) - self.__membership.FindMembership((self.__membership_findall_callback,), - (self.__membership_findall_errback,), - self._scenario, ['Messenger'], - False) - - def __membership_findall_callback(self, result): - self.__membership_response = result - if self.__ab_response is not None: - callback = self._callback - callback[0](self.__ab_response, - self.__membership_response, *callback[1:]) - self.__membership_response = None - self.__ab_response = None - - def __ab_findall_callback(self, result): - self.__ab_response = result - if self.__membership_response is not None: - callback = self._callback - callback[0](self.__ab_response, - self.__membership_response, *callback[1:]) - self.__membership_response = None - self.__ab_response = None - - def __membership_findall_errback(self, error_code): - self.__sync_errback(error_code) - - def __ab_findall_errback(self, error_code): - self.__sync_errback(error_code) - - def __sync_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ABDoesNotExist': - self.__ab.ABAdd((self.__ab_add_callback,), - (self.__ab_add_errback,), - self._scenario, - self._account) - return - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __ab_add_callback(self, *args): - self.execute() - - def __ab_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/AddressBook/sharing.py b/pymsn/pymsn/service/AddressBook/sharing.py deleted file mode 100644 index b5785f23..00000000 --- a/pymsn/pymsn/service/AddressBook/sharing.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.service.SOAPService import SOAPService -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * -from pymsn.service.AddressBook.common import * - -__all__ = ['Sharing'] - -class Member(object): - def __init__(self, member): - self.Roles = {} - self.Account = "" - self.MembershipId = member.findtext("./ab:MembershipId") - self.Type = member.findtext("./ab:Type") - self.DisplayName = member.findtext("./ab:DisplayName") - self.State = member.findtext("./ab:State") - - self.Deleted = member.findtext("./ab:Deleted", "bool") - self.LastChanged = member.findtext("./ab:LastChanged", "datetime") - self.Changes = [] # FIXME: extract the changes - self.Annotations = annotations_to_dict(member.find("./ab:Annotations")) - - def __hash__(self): - return hash(self.Type) ^ hash(self.Account) - - def __eq__(self, other): - return (self.Type == other.Type) and (self.Account == other.Account) - - def __repr__(self): - return "<%sMember account=%s roles=%r>" % (self.Type, self.Account, self.Roles) - - @staticmethod - def new(member): - type = member.findtext("./ab:Type") - if type == "Passport": - return PassportMember(member) - elif type == "Email": - return EmailMember(member) - elif type == "Phone": - return PhoneMember(member) - else: - raise NotImplementedError("Member type not implemented : " + type) - - -class PassportMember(Member): - def __init__(self, member): - Member.__init__(self, member) - self.Id = member.findtext("./ab:PassportId", "int") - self.PassportName = member.findtext("./ab:PassportName") - self.IsPassportNameHidden = member.findtext("./ab:IsPassportNameHidden", "bool") - self.CID = member.findtext("./ab:CID", "int") - self.Changes = [] # FIXME: extract the changes - - self.Account = self.PassportName - -class EmailMember(Member): - def __init__(self, member): - Member.__init__(self, member) - self.Email = member.findtext("./ab:Email") - - self.Account = self.Email - -class PhoneMember(Member): - def __init__(self, member): - Member.__init__(self, member) - self.PhoneNumber = member.findtext("./ab:PhoneNumber") - - -class Sharing(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "Sharing", proxies) - - self._last_changes = "0001-01-01T00:00:00.0000000-08:00" - - @RequireSecurityTokens(LiveService.CONTACTS) - def FindMembership(self, callback, errback, scenario, services, deltas_only): - """Requests the membership list. - - @param scenario: 'Initial' | ... - @param services: a list containing the services to check in - ['Messenger', 'Invitation', 'SocialNetwork', - 'Space', 'Profile' ] - @param deltas_only: True if the method should only check changes - since last_change, False else - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.FindMembership, scenario, - (services, deltas_only, self._last_changes), callback, errback) - - def _HandleFindMembershipResponse(self, callback, errback, response, user_data): - if response[1] is not None: - self._last_changes = response[1].text - - memberships = {} - for role, members in response[0].iteritems(): - for member in members: - membership_id = XMLTYPE.int.decode(member.find("./ab:MembershipId").text) - member_obj = Member.new(member) - member_id = hash(member_obj) - if member_id in memberships: - memberships[member_id].Roles[role] = membership_id - else: - member_obj.Roles[role] = membership_id - memberships[member_id] = member_obj - callback[0](memberships.values(), *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def AddMember(self, callback, errback, scenario, member_role, type, - state, account): - """Adds a member to a membership list. - - @param scenario: 'Timer' | 'BlockUnblock' | ... - @param member_role: 'Allow' | ... - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.AddMember, scenario, - (member_role, type, state, account), callback, errback) - - def _HandleAddMemberResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def DeleteMember(self, callback, errback, scenario, member_role, type, - state, account): - """Deletes a member from a membership list. - - @param scenario: 'Timer' | 'BlockUnblock' | ... - @param member_role: 'Block' | ... - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.DeleteMember, scenario, - (member_role, type, state, account), - callback, errback) - - def _HandleDeleteMemberResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def __soap_request(self, method, scenario, args, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(scenario, token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, - self._service.url, - soap_header, soap_body, soap_action, - callback, errback, - http_headers) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](soap_response.fault.faultcode, *errback[1:]) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def sharing_callback(memberships): - print "Memberships :" - for member in memberships: - print member - - sso = SingleSignOn(account, password) - sharing = Sharing(sso) - sharing.FindMembership((sharing_callback,), None, 'Initial', - ['Messenger', 'Invitation'], False) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/ContentRoaming/__init__.py b/pymsn/pymsn/service/ContentRoaming/__init__.py deleted file mode 100644 index 9feb5121..00000000 --- a/pymsn/pymsn/service/ContentRoaming/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from content_roaming import * diff --git a/pymsn/pymsn/service/ContentRoaming/constants.py b/pymsn/pymsn/service/ContentRoaming/constants.py deleted file mode 100644 index ef28a654..00000000 --- a/pymsn/pymsn/service/ContentRoaming/constants.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -__all__ = ["ContentRoamingError", "ContentRoamingState"] - -class ContentRoamingError(object): - UNKNOWN = 0 - -class ContentRoamingState(object): - """Content roaming service synchronization state. - - The service is said to be synchronized when it - matches the stuff stored on the server.""" - - NOT_SYNCHRONIZED = 0 - """The service is not synchronized yet""" - SYNCHRONIZING = 1 - """The service is being synchronized""" - SYNCHRONIZED = 2 - """The service is already synchronized""" - diff --git a/pymsn/pymsn/service/ContentRoaming/content_roaming.py b/pymsn/pymsn/service/ContentRoaming/content_roaming.py deleted file mode 100644 index abc7baae..00000000 --- a/pymsn/pymsn/service/ContentRoaming/content_roaming.py +++ /dev/null @@ -1,224 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import storage -import scenario - -from pymsn.service.ContentRoaming.constants import * -from pymsn.service.ContentRoaming.scenario import * - -import gobject -import imghdr - -__all__ = ['ContentRoaming', 'ContentRoamingState', 'ContentRoamingError'] - -class ContentRoaming(gobject.GObject): - - __gproperties__ = { - "state" : (gobject.TYPE_INT, - "State", - "The state of the addressbook.", - 0, 2, ContentRoamingState.NOT_SYNCHRONIZED, - gobject.PARAM_READABLE), - - "display-name" : (gobject.TYPE_STRING, - "Display name", - "The user's display name on storage", - "", - gobject.PARAM_READABLE), - - "personal-message" : (gobject.TYPE_STRING, - "Personal message", - "The user's personal message on storage", - "", - gobject.PARAM_READABLE), - - "display-picture" : (gobject.TYPE_PYOBJECT, - "Display picture", - "The user's display picture on storage", - gobject.PARAM_READABLE) - } - - def __init__(self, sso, ab, proxies=None): - """The content roaming object""" - gobject.GObject.__init__(self) - - self._storage = storage.Storage(sso, proxies) - self._ab = ab - - self.__state = ContentRoamingState.NOT_SYNCHRONIZED - - self.__display_name = '' - self.__personal_message = '' - self.__display_picture = None - - self._profile_id = None - self._expression_profile_id = None - self._display_picture_id = None - - # Properties - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - @property - def display_name(self): - return self.__display_name - - @property - def personal_message(self): - return self.__personal_message - - @property - def display_picture(self): - return self.__display_picture - - def sync(self): - if self._state != ContentRoamingState.NOT_SYNCHRONIZED: - return - self._state = ContentRoamingState.SYNCHRONIZING - - gp = GetStoredProfileScenario(self._storage, - (self.__get_dn_and_pm_cb,), - (self.__get_display_picture_cb,), - (self.__common_errback,)) - gp.cid = self._ab.profile.cid - gp() - - # Public API - def store(self, display_name=None, personal_message=None, - display_picture=None): - if display_name is None: - display_name = self.__display_name - if personal_message is None: - personal_message = self.__personal_message - - if display_picture is not None: - type = imghdr.what('', display_picture) - if type is None: type = 'png' - display_picture = ('image/%s' % type, display_picture) - - def store_profile_cb(): - self.__display_name = display_name - self.__personal_message = personal_message - self.__display_picture = display_picture - - up = StoreProfileScenario(self._storage, - (store_profile_cb,), - (self.__common_errback,), - self._ab.profile.cid, - self._profile_id, - self._expression_profile_id, - self._display_picture_id) - - up.display_name = display_name - up.personal_message = personal_message - up.display_picture = display_picture - - up() - # End of public API - - # Callbacks - def __get_dn_and_pm_cb(self, profile_id, expression_profile_id, - display_name, personal_message, display_picture_id): - self._profile_id = profile_id - self._expression_profile_id = expression_profile_id - self._display_picture_id = display_picture_id - - self.__display_name = display_name - self.notify("display-name") - - self.__personal_message = personal_message - self.notify("personal-message") - - if self._display_picture_id is None: - self._state = ContentRoamingState.SYNCHRONIZED - - def __get_display_picture_cb(self, type, data): - self.__display_picture = (type, data) - self.notify("display-picture") - - self._state = ContentRoamingState.SYNCHRONIZED - - def __common_errback(self, error_code, *args): - print "The content roaming service got the error (%s)" % error_code - -gobject.type_register(ContentRoaming) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.AddressBook import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def address_book_state_changed(address_book, pspec, sso): - if address_book.state == AddressBookState.SYNCHRONIZED: - - def content_roaming_state_changed(cr, pspec): - if cr.state == ContentRoamingState.SYNCHRONIZED: - print "Content roaming service is now synchronized" - -# print cr.display_picture -# type, data = cr.display_picture -# path = '/home/jprieur/projects/pymsn.rewrite/pymsn/service/ContentRoaming/argh.%s' % type.split('/')[1] -# f = open(path, 'w') -# f.write(data) - -# path = '/home/jprieur/projects/pymsn.rewrite/pymsn/service/ContentRoaming/test.jpeg' -# f = open(path, 'r') -# cr.store("Pouet pouet", "Brainy lala brainy...", f.read()) - - cr = ContentRoaming(sso, address_book) - cr.connect("notify::state", content_roaming_state_changed) - cr.sync() - - sso = SingleSignOn(account, password) - - address_book = AddressBook(sso) - address_book.connect("notify::state", address_book_state_changed, sso) - address_book.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/__init__.py b/pymsn/pymsn/service/ContentRoaming/scenario/__init__.py deleted file mode 100644 index 3254c2d1..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "scenario" -description = "" - -import base - -from get_stored_profile import * -from store_profile import * diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/base.py b/pymsn/pymsn/service/ContentRoaming/scenario/base.py deleted file mode 100644 index 91d57e43..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/base.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ['BaseScenario'] - -class BaseScenario(object): - def __init__(self, partner_scenario, callback, errback): - self._scenario = partner_scenario - self._callback = callback - self._errback = errback - - def __set_scenario(self, scenario): - self._scenario = scenario - def __get_scenario(self): - return self._scenario - scenario = property(__get_scenario, __set_scenario) - - def execute(self): - pass - - def __call__(self): - return self.execute() - diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py b/pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py deleted file mode 100644 index 4d36e39b..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from base import * - -from pymsn.service.ContentRoaming.constants import * - -__all__ = ['GetStoredProfileScenario'] - -class GetStoredProfileScenario(BaseScenario): - def __init__(self, storage, callback, dp_callback, errback, cid=''): - """Gets the roaming profile stored on the server - - @param storage: the storage service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, 'Initial', callback, errback) - self.__storage = storage - self.__dp_callback = dp_callback - - self.cid = cid - - def execute(self): - self.__storage.GetProfile((self.__get_profile_callback,), - (self.__get_profile_errback,), - self._scenario, self.cid, - True, True, True, True, True, True, - True, True, True, True, True) - - def __get_profile_callback(self, profile_rid, expression_profile_rid, - display_name, personal_msg, photo_rid, - photo_mime_type, photo_data_size, photo_url): - callback = self._callback - callback[0](profile_rid, expression_profile_rid, display_name, - personal_msg, photo_rid, *callback[1:]) - - if photo_rid is not None: - self.__storage.get_display_picture(photo_url, - (self.__get_display_picture_callback,), - (self.__get_display_picture_errback,)) - - def __get_profile_errback(self, error_code): - errcode = ContentRoamingError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __get_display_picture_callback(self, type, data): - callback = self.__dp_callback - callback[0](type, data, *callback[1:]) - - def __get_display_picture_errback(self, error_code): - # TODO : adapt this to the transport way of handling errors - errcode = ContentRoamingError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py b/pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py deleted file mode 100644 index 38fd9168..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from base import * - -from pymsn.service.ContentRoaming.constants import * - -__all__ = ['StoreProfileScenario'] - -class StoreProfileScenario(BaseScenario): - def __init__(self, storage, callback, errback, - cid, profile_id, expression_profile_id, display_picture_id, - display_name='', personal_message='', display_picture=''): - """Updates the roaming profile stored on the server - - @param storage: the storage service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, 'RoamingIdentityChanged', callback, errback) - self.__storage = storage - - self.__cid = cid - self.__profile_id = profile_id - self.__expression_profile_id = expression_profile_id - self.__display_picture_id = display_picture_id - - print self.__cid - print self.__profile_id - print self.__expression_profile_id - print self.__display_picture_id - - self.display_name = display_name - self.personal_message = personal_message - self.display_picture = display_picture - - def execute(self): - self.__storage.UpdateProfile((self.__update_profile_callback,), - (self.__store_profile_errback,), - self._scenario, self.__profile_id, - self.display_name, self.personal_message, - 0) - - def __update_profile_callback(self): - if self.display_picture is not None: - self.__storage.DeleteRelationships( - (self.__delete_relationship_profile_callback,), - (self.__store_profile_errback,), - self._scenario, - self.__display_picture_id, - self.__cid, None) - else: - callback = self._callback - callback[0](*callback[1:]) - - def __delete_relationship_profile_callback(self): - self.__storage.DeleteRelationships( - (self.__delete_relationship_expression_callback,), - (self.__store_profile_errback,), - self._scenario, self.__display_picture_id, - None, self.__expression_profile_id) - - def __delete_relationship_expression_callback(self): - # FIXME : add support for dp name - self.__storage.CreateDocument( - (self.__create_document_callback,), - (self.__store_profile_errback,), - self._scenario, self.__cid, - "roaming", self.display_picture[0], - self.display_picture[1].encode('base64')) - - def __create_document_callback(self, document_rid): - self.__storage.CreateRelationships( - (self.__create_relationship_callback,), - (self.__store_profile_errback,), - self._scenario, self.__expression_profile_id, document_rid) - - def __create_relationship_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __store_profile_errback(self, error_code): - errcode = ContentRoamingError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/ContentRoaming/storage.py b/pymsn/pymsn/service/ContentRoaming/storage.py deleted file mode 100644 index eafd3863..00000000 --- a/pymsn/pymsn/service/ContentRoaming/storage.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.service.SOAPService import SOAPService, url_split -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * - -from pymsn.gnet.protocol import ProtocolFactory - -__all__ = ['Storage'] - -class Storage(SOAPService): - - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "SchematizedStore", proxies) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GetProfile(self, callback, errback, scenario, cid, profile_rid, - p_date_modified, expression_rid, e_date_modified, - display_name, dn_last_modified, personal_status, - ps_last_modified, user_tile_url, photo, flags): - self.__soap_request(self._service.GetProfile, scenario, - (cid, - XMLTYPE.bool.encode(profile_rid), - XMLTYPE.bool.encode(p_date_modified), - XMLTYPE.bool.encode(expression_rid), - XMLTYPE.bool.encode(e_date_modified), - XMLTYPE.bool.encode(display_name), - XMLTYPE.bool.encode(dn_last_modified), - XMLTYPE.bool.encode(personal_status), - XMLTYPE.bool.encode(ps_last_modified), - XMLTYPE.bool.encode(user_tile_url), - XMLTYPE.bool.encode(photo), - XMLTYPE.bool.encode(flags)), - callback, errback) - - def _HandleGetProfileResponse(self, callback, errback, response, user_date): - profile_rid = response.findtext('./st:ResourceID') - - expression_profile = response.find('./st:ExpressionProfile') - expression_profile_rid = expression_profile.findtext('./st:ResourceID') - - display_name = expression_profile.findtext('./st:DisplayName') - personal_msg = expression_profile.findtext('./st:PersonalStatus') - - photo = expression_profile.find('./st:Photo') - if photo is not None: - photo_rid = photo.findtext('./st:ResourceID') - document_stream = photo.find('./st:DocumentStreams/st:DocumentStream') - photo_mime_type = document_stream.findtext('./st:MimeType') - photo_data_size = document_stream.findtext('./st:DataSize', "int") - photo_url = document_stream.findtext('./st:PreAuthURL') - else: - photo_rid = photo_mime_type = photo_data_size = photo_url = None - - callback[0](profile_rid, expression_profile_rid, display_name, personal_msg, - photo_rid, photo_mime_type, photo_data_size, photo_url, - *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def UpdateProfile(self, callback, errback, scenario, profile_rid, - display_name, personal_status, flags): - self.__soap_request(self._service.UpdateProfile, scenario, - (profile_rid, display_name, personal_status, flags), - callback, errback) - - def _HandleUpdateProfileResponse(self, callback, errback, response, user_date): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def CreateRelationships(self, callback, errback, scenario, - source_rid, target_rid): - self.__soap_request(self._service.CreateRelationships, scenario, - (source_rid, target_rid), - callback, errback) - - def _HandleCreateRelationshipsResponse(self, callback, errback, response, user_date): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def DeleteRelationships(self, callback, errback, scenario, - target_id, cid=None, source_id=None): - self.__soap_request(self._service.DeleteRelationships, scenario, - (cid, source_id, target_id), callback, errback) - - def _HandleDeleteRelationshipsResponse(self, callback, errback, response, user_date): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def CreateDocument(self, callback, errback, scenario, cid, photo_name, - photo_mime_type, photo_data): - self.__soap_request(self._service.CreateDocument, scenario, - (cid, photo_name, photo_mime_type, photo_data), - callback, errback) - - def _HandleCreateDocumentResponse(self, callback, errback, response, user_date): - document_rid = response.text - callback[0](document_rid, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def FindDocuments(self, callback, errback, scenario, cid): - self.__soap_request(self._service.FindDocuments, scenario, - (cid,), callback, errback) - - def _HandleFindDocumentsResponse(self, callback, errback, response, user_date): - document = response.find('./st:Document') - - document_rid = response.findtext('./st:ResourceID') - document_name = response.findtext('./st:Name') - callback[0](document_rid, document_name, *callback[1:]) - - def __soap_request(self, method, scenario, args, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(scenario, token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, - self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers) - - @RequireSecurityTokens(LiveService.CONTACTS) - def get_display_picture(self, pre_auth_url, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - scheme = 'http' - host = 'byfiles.storage.msn.com' - port = 80 - resource = '?'.join([pre_auth_url, token.split('&')[0]]) - - def request_callback(transport, http_response): - type = http_response.get_header('Content-Type')#.split('/')[1] - data = http_response.body - callback[0](type, data, *callback[1:]) - - http_headers = {} - http_headers["Accept"] = "*/*" - http_headers["Proxy-Connection"] = "Keep-Alive" - http_headers["Connection"] = "Keep-Alive" - - proxy = self._proxies.get(scheme, None) - transport = ProtocolFactory(scheme, host, port, proxy=proxy) - transport.connect("response-received", request_callback) - transport.connect("request-sent", self._request_handler) - transport.connect("error", errback[0], *errback[1:]) - - transport.request(resource, http_headers, method='GET') diff --git a/pymsn/pymsn/service/OfflineIM/__init__.py b/pymsn/pymsn/service/OfflineIM/__init__.py deleted file mode 100644 index 7fde863a..00000000 --- a/pymsn/pymsn/service/OfflineIM/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from offline_messages_box import * -from constants import * diff --git a/pymsn/pymsn/service/OfflineIM/constants.py b/pymsn/pymsn/service/OfflineIM/constants.py deleted file mode 100644 index 56cce948..00000000 --- a/pymsn/pymsn/service/OfflineIM/constants.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ["OfflineMessagesBoxState", "OfflineMessagesBoxError"] - -class OfflineMessagesBoxState(object): - """Offline messages box synchronization state. - - The box is said to be synchronized when it - owns the references to all the new messages on the server.""" - - NOT_SYNCHRONIZED = 0 - """The box is not synchronized yet""" - SYNCHRONIZING = 1 - """The box is being synchronized""" - SYNCHRONIZED = 2 - """The box is already synchronized""" - -class OfflineMessagesBoxError(object): - "Offline IM related errors" - UNKNOWN = 0 - AUTHENTICATION_FAILED = 1 - SYSTEM_UNAVAILABLE = 2 - SENDER_THROTTLE_LIMIT_EXCEEDED = 3 - - diff --git a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py b/pymsn/pymsn/service/OfflineIM/offline_messages_box.py deleted file mode 100644 index a06d3927..00000000 --- a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py +++ /dev/null @@ -1,416 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -import rsi -import oim -import scenario - -from pymsn.service.SOAPUtils import * -from pymsn.service.OfflineIM.constants import * - -from pymsn.profile import NetworkID - -from pymsn.util.decorator import throttled - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO -import pymsn.util.guid as guid -import pymsn.util.iso8601 as iso8601 - -import datetime -import gobject - -import logging - -__all__ = ['OfflineMessagesBox', 'OfflineMessage'] - -logger = logging.getLogger('Service') - -class OfflineMessagesStorage(list): - def __init__(self, initial_set=()): - list.__init__(self, initial_set) - -# def __repr__(self): -# return "OfflineMessagesStorage : %d message(s)" % len(self) - - def add(self, message): - self.append(message) - - def __getattr__(self, name): - if name.startswith("search_by_"): - field = name[10:] - def search_by_func(criteria): - return self.search_by(field, criteria) - search_by_func.__name__ = name - return search_by_func - elif name.startswith("group_by_"): - field = name[9:] - def group_by_func(): - return self.group_by(field) - group_by_func.__name__ = name - return group_by_func - else: - raise AttributeError, name - - def search_by(self, field, value): - result = [] - for contact in self: - if getattr(contact, field) == value: - result.append(contact) - return OfflineMessagesStorage(result) - - def group_by(self, field): - result = {} - for contact in self: - value = getattr(contact, field) - if value not in result: - result[value] = OfflineMessagesStorage() - result[value].add(contact) - return result - -class OfflineMessage(object): - - def __init__(self, id, sender, display_name='', date=None): - self._id = id - self._sender = sender - self._display_name = display_name - - if date is None: - self._date = datetime.datetime.utcnow() - else: - date = iso8601.parse_date(date) - self._date = date.replace(tzinfo=None) # FIXME: do not disable the timezone - - self.__text = '' - self.__run_id = '' - self.__sequence_num = -1 - self.__is_mobile = False - - @property - def id(self): - return self._id - - @property - def sender(self): - return self._sender - - @property - def display_name(self): - return self._display_name - - @property - def date(self): - return self._date - - def __get_text(self): - return self.__text - def __set_text(self, text): - self.__text = text - text = property(__get_text) - _text = property(__get_text, __set_text) - - def __get_run_id(self): - return self.__run_id - def __set_run_id(self, run_id): - self.__run_id = run_id - run_id = property(__get_run_id) - _run_id = property(__get_run_id, __set_run_id) - - def __get_sequence_num(self): - return self.__sequence_num - def __set_sequence_num(self, sequence_num): - self.__sequence_num = sequence_num - sequence_num = property(__get_sequence_num) - _sequence_num = property(__get_sequence_num, __set_sequence_num) - - def __get_is_mobile(self): - return self.__is_mobile - def __set_is_mobile(self, is_mobile): - self.__is_mobile = is_mobile - is_mobile = property(__get_is_mobile) - _is_mobile = property(__get_is_mobile, __set_is_mobile) - - def __str__(self): - return self.__text - - def __repr__(self): - return "" % (self.run_id, self.sequence_num) - - def __cmp__(self, other): - if self.run_id == other.run_id: - return self.sequence_num - other.sequence_num - elif self.date >= other.date: - return 1 - return -1 - -class Metadata(ElementTree.XMLResponse): - def __init__(self, metadata): - ElementTree.XMLResponse.__init__(self, metadata) - if self.tree is None: - logger.warning("Metadata: Invalid metadata") - - def is_valid(self): - return self.tree is not None - - def _parse(self, data): - data = StringIO.StringIO(data) - return ElementTree.parse(data) - -class OfflineMessagesBox(gobject.GObject): - - __gsignals__ = { - "error" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "messages-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "messages-fetched" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "messages-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ()), - "message-sent" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, str)) - } - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the offline messages box.", - 0, 2, OfflineMessagesBoxState.NOT_SYNCHRONIZED, - gobject.PARAM_READABLE), - "messages" : (gobject.TYPE_PYOBJECT, - "Offline messages", - "The fetched offline messages.", - gobject.PARAM_READABLE) - } - - def __init__(self, sso, client, proxies=None): - gobject.GObject.__init__(self) - - self._client = client - self._rsi = rsi.RSI(sso, proxies) - self._oim = oim.OIM(sso, proxies) - - self.__state = OfflineMessagesBoxState.NOT_SYNCHRONIZED - self.__messages = OfflineMessagesStorage() - - self.__conversations = {} - - # Properties - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - def __get_messages(self): - return self.__messages - def __set_messages(self, messages): - self.__messages = messages - self.notify("messages") - messages = property(__get_messages) - _messages = property(__get_messages, - __set_messages) - - def sync(self, xml_data=None): - if self._state != OfflineMessagesBoxState.NOT_SYNCHRONIZED: - return - self._state = OfflineMessagesBoxState.SYNCHRONIZING - if xml_data is None: - sh = scenario.SyncHeadersScenario(self._rsi, - (self.__parse_metadata,), - (self.__common_errback,)) - sh() - else: - self.__parse_metadata(xml_data) - - def __parse_metadata(self, xml_data): - metadata = Metadata(xml_data) - for m in metadata.findall('./M'): - id = m.findtext('./I') - network = (m.findtext('T','int'), m.findtext('S','int')) - if network == (11,6): - network_id = NetworkID.MSN - elif network == (11,7): - # FIXME: What does 11 and 7 mean? We should find out... - # FIXME: My first guess would be Windows Live Communication Server - network_id = NetworkID.LCS - elif network == (13,7): - network_id = NetworkID.EXTERNAL - else: - # This is to ensure that network_id is always set, even if we get unknown values for network - # FIXME: Is NetworkID.MSN the best choice as default value? - network_id = NetworkID.MSN - - account = m.findtext('./E') - - try: - sender = self._client.address_book.contacts.\ - search_by_account(account).\ - search_by_network_id(network_id)[0] - except IndexError: - sender = None - - if network_id == NetworkID.MSN: - name = m.findtext('./N').replace(' ','').\ - split('?')[3].decode('base64').encode('utf-8') - elif network_id == NetworkID.EXTERNAL: - name = m.findtext('./N').encode('utf-8') - - date = m.find('./RT') - if date is not None: - date = date.text - - self.__messages.add(OfflineMessage(id, sender, name, date)) - - self._state = OfflineMessagesBoxState.SYNCHRONIZED - - if len(self.__messages) > 0: - self.emit('messages-received', self.__messages) - - # Public API - def fetch_messages(self, messages=None): - if messages is None: - messages = self.messages - - if len(messages) == 0: - return - - fm = scenario.FetchMessagesScenario(self._rsi, - (self.__fetch_message_cb,), - (self.__common_errback,), - (self.__fetch_messages_cb, messages)) - fm.message_ids = [m.id for m in messages] - fm() - - @throttled(1000, list()) - def send_message(self, recipient, message): - if recipient.network_id == NetworkID.EXTERNAL: - return - - convo = self.__conversations.get(recipient, None) - if convo is None: - run_id = guid.generate_guid() - sequence_num = 1 - self.__conversations[recipient] = [run_id, sequence_num] - else: - (run_id, sequence_num) = convo - convo[1] = convo[1] + 1 - - sm = scenario.SendMessageScenario(self._oim, - self._client, recipient, message, - (self.__send_message_cb, recipient, message), - (self.__common_errback,)) - - sm.run_id = run_id - sm.sequence_num = sequence_num - sm() - - def delete_messages(self, messages=None): - if messages is None: - messages = self.messages - - if len(messages) == 0: - return - - dm = scenario.DeleteMessagesScenario(self._rsi, - (self.__delete_messages_cb, messages), - (self.__common_errback,)) - dm.message_ids = [m.id for m in messages] - dm() - - # Callbacks - def __fetch_message_cb(self, id, run_id, sequence_num, text): - message = self._messages.search_by_id(id)[0] - message._run_id = run_id - message._sequence_num = sequence_num - message._text = text - - def __fetch_messages_cb(self, messages): - messages.sort() - self.emit('messages-fetched', messages) - - def __send_message_cb(self, recipient, message): - self.emit('message-sent', recipient, message) - - def __delete_messages_cb(self, messages): - for message in messages: - try: - self._messages.remove(message) - except ValueError: - pass - self.emit('messages-deleted') - - def __common_callback(self, signal, *args): - self.emit(signal, *args) - - def __common_errback(self, error_code, *args): - self.emit('error', error_code) - -gobject.type_register(OfflineMessagesBox) - - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.AddressBook import AddressBook - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def sso_callback(arg): - print arg - - def sso_errback(): - pass - - sso = SingleSignOn(account, password) - - box = OfflineMessagesBox(sso) - box.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/OfflineIM/oim.py b/pymsn/pymsn/service/OfflineIM/oim.py deleted file mode 100644 index 73fd7806..00000000 --- a/pymsn/pymsn/service/OfflineIM/oim.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.service.OfflineIM.constants import * -from pymsn.service.SOAPService import SOAPService -from pymsn.msnp.notification import ProtocolConstant -from pymsn.service.SingleSignOn import * - -__all__ = ['OIM'] - -class OIM(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - self.__lock_key = "" - SOAPService.__init__(self, "OIM", proxies) - - def set_lock_key(self, lock_key): - self.__lock_key = lock_key - - @RequireSecurityTokens(LiveService.MESSENGER_SECURE) - def Store2(self, callback, errback, from_member_name, friendly_name, - to_member_name, session_id, message_number, message_type, message_content): - import base64 - token = str(self._tokens[LiveService.MESSENGER_SECURE]) - fname = "=?utf-8?B?%s?=" % base64.b64encode(friendly_name) - - content = self.__build_mail_data(session_id, message_number, message_content) - - self.__soap_request(self._service.Store2, - (from_member_name, fname, - ProtocolConstant.CVR[4], - ProtocolConstant.VER[0], - ProtocolConstant.CVR[5], - to_member_name, - message_number, - token, - ProtocolConstant.PRODUCT_ID, - self.__lock_key), - (message_type, content), - callback, errback) - - def _HandleStore2Response(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def _HandleStore2Fault(self, callback, errback, soap_response, user_data): - error_code = OfflineMessagesBoxError.UNKNOWN - auth_policy = None - lock_key_challenge = None - - if soap_response.fault.faultcode.endswith("AuthenticationFailed"): - error_code = OfflineMessagesBoxError.AUTHENTICATION_FAILED - auth_policy = soap_response.fault.detail.findtext("./oim:RequiredAuthPolicy") - lock_key_challenge = soap_response.fault.detail.findtext("./oim:LockKeyChallenge") - - if auth_policy == "": - auth_policy = None - if lock_key_challenge == "": - lock_key_challenge = None - - #print "Authentication failed - policy = %s - lockkey = %s" % (auth_policy, lock_key_challenge) - elif soap_response.fault.faultcode.endswith("SystemUnavailable"): - error_code = OfflineMessagesBoxError.SYSTEM_UNAVAILABLE - elif soap_response.fault.faultcode.endswith("SenderThrottleLimitExceeded"): - error_code = OfflineMessagesBoxError.SENDER_THROTTLE_LIMIT_EXCEEDED - - errback[0](error_code, auth_policy, lock_key_challenge, *errback[1:]) - - def __build_mail_data(self, run_id, sequence_number, content): - import base64 - mail_data = 'MIME-Version: 1.0\r\n' - mail_data += 'Content-Type: text/plain; charset=UTF-8\r\n' - mail_data += 'Content-Transfer-Encoding: base64\r\n' - mail_data += 'X-OIM-Message-Type: OfflineMessage\r\n' - mail_data += 'X-OIM-Run-Id: {%s}\r\n' % run_id - mail_data += 'X-OIM-Sequence-Num: %s\r\n\r\n' % sequence_number - mail_data += base64.b64encode(content) - return mail_data - - def __soap_request(self, method, header_args, body_args, - callback, errback, user_data=None): - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(*header_args) - soap_body = method.soap_body(*body_args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers, user_data) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](None, *errback[1:]) diff --git a/pymsn/pymsn/service/OfflineIM/rsi.py b/pymsn/pymsn/service/OfflineIM/rsi.py deleted file mode 100644 index 97b54eaa..00000000 --- a/pymsn/pymsn/service/OfflineIM/rsi.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * -from pymsn.service.SOAPService import SOAPService - -import email - -import logging - -__all__ = ['RSI'] - -logger = logging.getLogger('Service') - -class RSI(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "RSI", proxies) - - @RequireSecurityTokens(LiveService.MESSENGER) - def GetMetadata(self, callback, errback): - self.__soap_request(self._service.GetMetadata, (), - callback, errback) - - def _HandleGetMetadataResponse(self, callback, errback, response, user_data): - callback[0](response.text, *callback[1:]) - - @RequireSecurityTokens(LiveService.MESSENGER) - def GetMessage(self, callback, errback, message_id, mark_as_read): - self.__soap_request(self._service.GetMessage, - (message_id, XMLTYPE.bool.encode(mark_as_read)), - callback, errback) - - def _HandleGetMessageResponse(self, callback, errback, response, user_data): - m = email.message_from_string(response.text) - run_id = m.get('X-OIM-Run-Id')[1:-1] - seq_num = int(m.get('X-OIM-Sequence-Num')) - if m.get_content_type().split('/')[1] == 'vnd.ms-msnipg': - # FIXME : process the IPG data - # http://www.amsn-project.net/forums/viewtopic.php?p=21744 - # set a mobile sender flag - return - callback[0](run_id, seq_num, m.get_payload().decode('base64'), - *callback[1:]) - - @RequireSecurityTokens(LiveService.MESSENGER) - def DeleteMessages(self, callback, errback, message_ids): - self.__soap_request(self._service.DeleteMessages, (message_ids,), - callback, errback) - - def _HandleDeleteMessagesResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def __soap_request(self, method, args, callback, errback): - token = str(self._tokens[LiveService.MESSENGER]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](None, *errback[1:]) - - - - - diff --git a/pymsn/pymsn/service/OfflineIM/scenario/__init__.py b/pymsn/pymsn/service/OfflineIM/scenario/__init__.py deleted file mode 100644 index 750164fe..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "scenario" -description = "" - -import base - -from sync_headers import * -from fetch_messages import * -from send_message import * -from delete_messages import * diff --git a/pymsn/pymsn/service/OfflineIM/scenario/base.py b/pymsn/pymsn/service/OfflineIM/scenario/base.py deleted file mode 100644 index 03b56ed0..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/base.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ['BaseScenario'] - -class BaseScenario(object): - def __init__(self, callback, errback): - self._callback = callback - self._errback = errback - - def execute(self): - pass - - def __call__(self): - return self.execute() - diff --git a/pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py b/pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py deleted file mode 100644 index 08f0692a..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario - -__all__ = ['DeleteMessagesScenario'] - -class DeleteMessagesScenario(BaseScenario): - def __init__(self, rsi, callback, errback, message_ids=[]): - """Accepts an invitation. - - @param rsi: the rsi service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__rsi = rsi - - self.message_ids = message_ids - - def execute(self): - self.__rsi.DeleteMessages((self.__delete_messages_callback,), - (self.__delete_messages_errback,), - self.message_ids) - - def __delete_messages_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __delete_messages_errback(self, error_code): - errcode = OfflineMessagesBoxError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py b/pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py deleted file mode 100644 index ad7cfef8..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario - -__all__ = ['FetchMessagesScenario'] - -class FetchMessagesScenario(BaseScenario): - def __init__(self, rsi, callback, errback, global_callback, message_ids=[]): - """Accepts an invitation. - - @param rsi: the rsi service - @param message_ids: id list of messages to fetch - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__rsi = rsi - self.__global_callback = global_callback - - self.message_ids = message_ids - - def execute(self): - for message_id in self.message_ids: - self.__rsi.GetMessage((self.__get_message_callback, message_id), - (self.__get_message_errback,), - message_id, False) - - def __get_message_callback(self, run_id, seq_num, message, id): - callback = self._callback - callback[0](id, run_id, seq_num, message, *callback[1:]) - self.message_ids.remove(id) - if self.message_ids == []: - global_callback = self.__global_callback - global_callback[0](*global_callback[1:]) - - def __get_message_errback(self, error_code): - errcode = OfflineMessagesBoxError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/OfflineIM/scenario/send_message.py b/pymsn/pymsn/service/OfflineIM/scenario/send_message.py deleted file mode 100644 index f23cd30a..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/send_message.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario -from pymsn.msnp.challenge import _msn_challenge - -__all__ = ['SendMessageScenario'] - -class SendMessageScenario(BaseScenario): - def __init__(self, oim, client, recipient, message, callback, errback): - """Accepts an invitation. - - @param oim: the oim service - @param client: the client object sending the OIM - @param recipient: the contact to send the OIM to - @param message: the message to send - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__oim = oim - self.__client = client - self.__from = client.profile - self.__to = recipient - - self.run_id = "" - self.sequence_num = -1 - - self.__msg = message - - def execute(self): - self.__oim.Store2((self.__store2_callback,), - (self.__store2_errback,), - self.__from.account, - self.__from.display_name, - self.__to.account, - self.run_id, - self.sequence_num, - "text", - self.__msg) - - def __store2_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __store2_errback(self, error_code, auth_policy, lock_key_challenge): - if error_code == OfflineMessagesBoxError.AUTHENTICATION_FAILED: - if lock_key_challenge != None: - self.__oim.set_lock_key(_msn_challenge(lock_key_challenge)) - if auth_policy != None: - self._client._sso.DiscardSecurityTokens([LiveService.CONTACTS]) - self._client._sso.RequestMultipleSecurityTokens((self.execute, ), None, LiveService.CONTACTS) - return - - self.execute() - return - - errback = self._errback - errback[0](error_code, *errback[1:]) diff --git a/pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py b/pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py deleted file mode 100644 index 19d7ad79..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario - -__all__ = ['SyncHeadersScenario'] - -class SyncHeadersScenario(BaseScenario): - def __init__(self, rsi, callback, errback): - """Accepts an invitation. - - @param rsi: the rsi service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__rsi = rsi - - def execute(self): - self.__rsi.GetMetadata((self.__get_metadata_callback,), - (self.__get_metadata_errback,)) - - def __get_metadata_callback(self, metadata): - callback = self._callback - callback[0](metadata, *callback[1:]) - - def __get_metadata_errback(self, error_code): - errcode = OfflineMessagesBoxError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/SOAPService.py b/pymsn/pymsn/service/SOAPService.py deleted file mode 100644 index 7c31a61d..00000000 --- a/pymsn/pymsn/service/SOAPService.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import description -from SOAPUtils import * - -import pymsn.gnet.protocol -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO -import re -import logging - -__all__ = ['SOAPService', 'SOAPResponse'] - -logger = logging.getLogger('Service') - -def url_split(url, default_scheme='http'): - from urlparse import urlsplit, urlunsplit - if "://" not in url: # fix a bug in urlsplit - url = default_scheme + "://" + url - protocol, host, path, query, fragment = urlsplit(url) - if path == "": path = "/" - try: - host, port = host.rsplit(":", 1) - port = int(port) - except: - port = None - resource = urlunsplit(('', '', path, query, fragment)) - return protocol, host, port, resource - -def compress_xml(xml_string): - space_regex = [(re.compile('>\s+<'), '><'), - (re.compile('>\s+'), '>'), - (re.compile('\s+<'), '<')] - - for regex, replacement in space_regex: - xml_string = regex.sub(replacement, xml_string) - return xml_string - -soap_template = """ - - - %s - - - %s - -""" - -class SOAPFault(object): - def __init__(self, tree): - self.tree = tree - self.faultcode = None - self.faultstring = None - self.faultactor = None - self.detail = None - - if tree is not None: - self.faultcode = tree.findtext("./faultcode") - self.faultstring = tree.findtext("./faultstring") - self.faultactor = tree.findtext("./faultactor") - self.detail = tree.find("./detail") - - def is_fault(self): - return self.tree is not None - - def __repr__(self): - return """ fault code : %s - fault string : %s - fault actor : %s - detail : %s""" % ( - self.faultcode, self.faultstring, self.faultactor, self.detail) - - def __str__(self): - return self.__repr__() - - -class SOAPResponse(ElementTree.XMLResponse): - NS_SHORTHANDS = {"soap" : XMLNS.SOAP.ENVELOPE, - "xmlenc" : XMLNS.ENCRYPTION.BASE, - "wsse" : XMLNS.WS.SECEXT, - "wst" : XMLNS.WS.TRUST, - "wsa" : XMLNS.WS.ADDRESSING, - "wsp" : XMLNS.WS.POLICY, - "wsi" : XMLNS.WS.ISSUE, - "wsu" : XMLNS.WS.UTILITY, - "ps" : XMLNS.MICROSOFT.PASSPORT, - "psf" : XMLNS.MICROSOFT.PASSPORT_FAULT, - "ab" : XMLNS.MICROSOFT.LIVE.ADDRESSBOOK, - "st" : XMLNS.MICROSOFT.LIVE.STORAGE, - "oim" : XMLNS.MICROSOFT.LIVE.OIM, - "rsi" : XMLNS.MICROSOFT.LIVE.RSI, - "spaces" : XMLNS.MICROSOFT.LIVE.SPACES } - - def __init__(self, soap_data): - ElementTree.XMLResponse.__init__(self, soap_data, self.NS_SHORTHANDS) - try: - self.header = self.tree.find("./soap:Header") - self.body = self.tree.find("./soap:Body") - try: - self.fault = SOAPFault(self.body.find("./soap:Fault")) - except: - self.fault = SOAPFault(self.tree.find("./soap:Fault")) - except: - self.tree = None - self.header = None - self.body = None - self.fault = None - logger.warning("SOAPResponse: Invalid xml+soap data : %s" % soap_data) - - def is_fault(self): - return self.fault.is_fault() - - def is_valid(self): - return ((self.header is not None) or \ - (self.fault is not None) or \ - (self.body is not None)) \ - and self.tree is not None - - def _parse(self, data): - events = ("start", "end", "start-ns", "end-ns") - ns = [] - data = StringIO.StringIO(data) - context = ElementTree.iterparse(data, events=events) - for event, elem in context: - if event == "start-ns": - ns.append(elem) - elif event == "end-ns": - ns.pop() - elif event == "start": - elem.set("(xmlns)", tuple(ns)) - data.close() - return context.root - -class SOAPService(object): - - def __init__(self, name, proxies=None): - self._name = name - self._service = getattr(description, self._name) - self._active_transports = {} - self._proxies = proxies or {} - - def _send_request(self, name, url, soap_header, soap_body, soap_action, - callback, errback=None, transport_headers={}, user_data=None): - - scheme, host, port, resource = url_split(url) - http_headers = transport_headers.copy() - if soap_action is not None: - http_headers["SOAPAction"] = str(soap_action) - http_headers["Content-Type"] = "text/xml; charset=utf-8" - http_headers["Cache-Control"] = "no-cache" - if "Accept" not in http_headers: - http_headers["Accept"] = "text/*" - http_headers["Proxy-Connection"] = "Keep-Alive" - http_headers["Connection"] = "Keep-Alive" - - request = compress_xml(soap_template % (soap_header, soap_body)) - - transport = self._get_transport(name, scheme, host, port, - callback, errback, user_data) - transport.request(resource, http_headers, request, 'POST') - - def _response_handler(self, transport, http_response): - logger.debug("<<< " + str(http_response)) - soap_response = SOAPResponse(http_response.body) - request_id, callback, errback, user_data = self._unref_transport(transport) - - if not soap_response.is_valid(): - logger.warning("Invalid SOAP Response") - return #FIXME: propagate the error up - - if not soap_response.is_fault(): - handler = getattr(self, - "_Handle" + request_id + "Response", - None) - method = getattr(self._service, request_id) - response = method.process_response(soap_response) - - if handler is not None: - handler(callback, errback, response, user_data) - else: - self._HandleUnhandledResponse(request_id, callback, errback, - response, user_data) - else: - handler = getattr(self, - "_Handle" + request_id + "Fault", - None) - if handler is not None: - handler(callback, errback, soap_response, user_data) - else: - self._HandleSOAPFault(request_id, callback, errback, soap_response, - user_data) - - def _request_handler(self, transport, http_request): - logger.debug(">>> " + str(http_request)) - - def _error_handler(self, transport, error): - logger.warning("Transport Error :" + str(error)) - request_id, callback, errback, user_data = self._unref_transport(transport) - return request_id, callback, errback #FIXME: do something sensible here - - # Handlers - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - logger.warning("Unhandled SOAPFault to %s" % request_id) - - def _HandleUnhandledResponse(self, request_id, callback, errback, - response, user_data): - logger.warning("Unhandled Response to %s" % request_id) - - # Transport management - def _get_transport(self, request_id, scheme, host, port, - callback, errback, user_data): - key = (scheme, host, port) - if key in self._active_transports: - trans = self._active_transports[key] - transport = trans[0] - trans[1].append((request_id, callback, errback, user_data)) - else: - proxy = self._proxies.get(scheme, None) - transport = pymsn.gnet.protocol.ProtocolFactory(scheme, - host, port, proxy=proxy) - handler_id = [transport.connect("response-received", - self._response_handler), - transport.connect("request-sent", self._request_handler), - transport.connect("error", self._error_handler)] - - trans = [transport, [(request_id, callback, errback, user_data)], handler_id] - self._active_transports[key] = trans - return transport - - def _unref_transport(self, transport): - for key, trans in self._active_transports.iteritems(): - if trans[0] == transport: - response = trans[1].pop(0) - - if len(trans[1]) != 0: - return response - - for handle in trans[2]: - transport.disconnect(handle) - del self._active_transports[key] - return response - return None - diff --git a/pymsn/pymsn/service/SOAPUtils.py b/pymsn/pymsn/service/SOAPUtils.py deleted file mode 100644 index 3720f507..00000000 --- a/pymsn/pymsn/service/SOAPUtils.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -__all__ = ['XMLNS'] - -class XMLNS(object): - - class SOAP(object): - ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/" - ENCODING = "http://schemas.xmlsoap.org/soap/encoding/" - ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next" - - class SCHEMA(object): - XSD1 = "http://www.w3.org/1999/XMLSchema" - XSD2 = "http://www.w3.org/2000/10/XMLSchema" - XSD3 = "http://www.w3.org/2001/XMLSchema" - - XSI1 = "http://www.w3.org/1999/XMLSchema-instance" - XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" - XSI3 = "http://www.w3.org/2001/XMLSchema-instance" - - class ENCRYPTION(object): - BASE = "http://www.w3.org/2001/04/xmlenc#" - - class WS: - SECEXT = "http://schemas.xmlsoap.org/ws/2003/06/secext" - TRUST = "http://schemas.xmlsoap.org/ws/2004/04/trust" - ADDRESSING = "http://schemas.xmlsoap.org/ws/2004/03/addressing" - POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy" - ISSUE = "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" - UTILITY = "http://docs.oasis-open.org/wss/2004/01/" + \ - "oasis-200401-wss-wssecurity-utility-1.0.xsd" - - class MICROSOFT: - PASSPORT = "http://schemas.microsoft.com/Passport/SoapServices/PPCRL" - PASSPORT_FAULT = "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault" - - class LIVE: - ADDRESSBOOK = "http://www.msn.com/webservices/AddressBook" - STORAGE = "http://www.msn.com/webservices/storage/w10" - OIM = "http://messenger.msn.com/ws/2004/09/oim/" - RSI = "http://www.hotmail.msn.com/ws/2004/09/oim/rsi" - SPACES = "http://www.msn.com/webservices/spaces/v1/" diff --git a/pymsn/pymsn/service/SingleSignOn.py b/pymsn/pymsn/service/SingleSignOn.py deleted file mode 100644 index 54282ad3..00000000 --- a/pymsn/pymsn/service/SingleSignOn.py +++ /dev/null @@ -1,270 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from SOAPService import * -from description.SingleSignOn.RequestMultipleSecurityTokens import LiveService -import pymsn.storage - -import base64 -import struct -import time -import datetime -import sys -import Crypto.Util.randpool as randpool -from Crypto.Hash import HMAC, SHA -from Crypto.Cipher import DES3 - -__all__ = ['SingleSignOn', 'LiveService', 'RequireSecurityTokens'] - -class SecurityToken(object): - - def __init__(self): - self.type = "" - self.service_address = "" - self.lifetime = [0, 0] - self.security_token = "" - self.proof_token = "" - - def is_expired(self): - return datetime.datetime.utcnow() + datetime.timedelta(seconds=60) \ - >= self.lifetime[1] - - def mbi_crypt(self, nonce): - WINCRYPT_CRYPT_MODE_CBC = 1 - WINCRYPT_CALC_3DES = 0x6603 - WINCRYPT_CALC_SHA1 = 0x8004 - - # Read key and generate two derived keys - key1 = base64.b64decode(self.proof_token) - key2 = self._derive_key(key1, "WS-SecureConversationSESSION KEY HASH") - key3 = self._derive_key(key1, "WS-SecureConversationSESSION KEY ENCRYPTION") - - # Create a HMAC-SHA-1 hash of nonce using key2 - hash = HMAC.new(key2, nonce, SHA).digest() - - # - # Encrypt nonce with DES3 using key3 - # - - # IV (Initialization Vector): 8 bytes of random data - iv = randpool.RandomPool().get_bytes(8) - obj = DES3.new(key3, DES3.MODE_CBC, iv) - - # XXX: win32's Crypt API seems to pad the input with 0x08 bytes - # to align on 72/36/18/9 boundary - ciph = obj.encrypt(nonce + "\x08\x08\x08\x08\x08\x08\x08\x08") - - blob = struct.pack("" % \ - (self.type, self.service_address, str(self.lifetime)) - - -class RequireSecurityTokens(object): - def __init__(self, *tokens): - assert(len(tokens) > 0) - self._tokens = tokens - - def __call__(self, func): - def sso_callback(tokens, object, user_callback, user_errback, - user_args, user_kwargs): - object._tokens.update(tokens) - func(object, user_callback, user_errback, *user_args, **user_kwargs) - - def method(object, callback, errback, *args, **kwargs): - callback = (sso_callback, object, callback, errback, args, kwargs) - object._sso.RequestMultipleSecurityTokens(callback, - None, *self._tokens) - method.__name__ = func.__name__ - method.__doc__ = func.__doc__ - method.__dict__.update(func.__dict__) - return method - - -class SingleSignOn(SOAPService): - def __init__(self, username, password, proxies=None): - self.__credentials = (username, password) - self.__storage = pymsn.storage.get_storage(username, password, - "security-tokens") - - self.__pending_response = False - self.__pending_requests = [] - SOAPService.__init__(self, "SingleSignOn", proxies) - self.url = self._service.url - - def RequestMultipleSecurityTokens(self, callback, errback, *services): - """Requests multiple security tokens from the single sign on service. - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param services: one or more L{LiveService}""" - - #FIXME: we should instead check what are the common requested tokens - # and if some tokens are not common then do a parallel request, needs - # to be fixed later, for now, we just queue the requests - if self.__pending_response: - self.__pending_requests.append((callback, errback, services)) - return - - method = self._service.RequestMultipleSecurityTokens - - response_tokens = {} - - requested_services = services - services = list(services) - for service in services: # filter already available tokens - service_url = service[0] - if service_url in self.__storage: - try: - token = self.__storage[service_url] - except pymsn.storage.DecryptError: - continue - if not token.is_expired(): - services.remove(service) - response_tokens[service] = token - - if len(services) == 0: - self._HandleRequestMultipleSecurityTokensResponse(callback, - errback, [], (requested_services, response_tokens)) - return - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(*self.__credentials) - soap_body = method.soap_body(*services) - - self.__pending_response = True - self._send_request("RequestMultipleSecurityTokens", self.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers, (requested_services, response_tokens)) - - def _HandleRequestMultipleSecurityTokensResponse(self, callback, errback, - response, user_data): - requested_services, response_tokens = user_data - result = {} - for node in response: - token = SecurityToken() - token.type = node.findtext("./wst:TokenType") - token.service_address = node.findtext("./wsp:AppliesTo" - "/wsa:EndpointReference/wsa:Address") - token.lifetime[0] = node.findtext("./wst:LifeTime/wsu:Created", "datetime") - token.lifetime[1] = node.findtext("./wst:LifeTime/wsu:Expires", "datetime") - - try: - token.security_token = node.findtext("./wst:RequestedSecurityToken" - "/wsse:BinarySecurityToken") - except AttributeError: - token.security_token = node.findtext("./wst:RequestedSecurityToken" - "/xmlenc:EncryptedData/xmlenc:CipherData" - "/xmlenc:CipherValue") - - try: - token.proof_token = node.findtext("./wst:RequestedProofToken/wst:BinarySecret") - except AttributeError: - pass - - service = LiveService.url_to_service(token.service_address) - assert(service != None), "Unknown service URL : " + \ - token.service_address - self.__storage[token.service_address] = token - result[service] = token - result.update(response_tokens) - - self.__pending_response = False - - if callback is not None: - callback[0](result, *callback[1:]) - - if len(self.__pending_requests): - callback, errback, services = self.__pending_requests.pop(0) - self.RequestMultipleSecurityTokens(callback, errback, *services) - - def DiscardSecurityTokens(self, services): - for service in services: - del self.__storage[service[0]] - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - if soap_response.fault.faultcode.endswith("FailedAuthentication"): - errback[0](*errback[1:]) - elif soap_response.fault.faultcode.endswith("Redirect"): - requested_services, response_tokens = user_data - self.url = soap_response.fault.tree.findtext("psf:redirectUrl") - self.__pending_response = False - self.RequestMultipleSecurityTokens(callback, errback, *requested_services) - - - - - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - - def sso_cb(tokens): - print "Received tokens : " - for token in tokens: - print "token %s : %s" % (token, str(tokens[token])) - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - sso = SingleSignOn(account, password) - sso.RequestMultipleSecurityTokens((sso_cb,), None, LiveService.VOICE) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() - diff --git a/pymsn/pymsn/service/Spaces/__init__.py b/pymsn/pymsn/service/Spaces/__init__.py deleted file mode 100644 index de9565e2..00000000 --- a/pymsn/pymsn/service/Spaces/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from spaces import * diff --git a/pymsn/pymsn/service/Spaces/contactcardservice.py b/pymsn/pymsn/service/Spaces/contactcardservice.py deleted file mode 100644 index d3e6c1ce..00000000 --- a/pymsn/pymsn/service/Spaces/contactcardservice.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from pymsn.service.SOAPService import SOAPService -from pymsn.service.SingleSignOn import * - -__all__ = ['ContactCardService'] - -class ContactCardSubElement(object): - def __init__(self, xml): - self.type = xml.element.attrib["type"] - self.last_updated = xml.element.attrib["lastUpdated"] - self.description = xml.findtext("./spaces:description") - self.title = xml.findtext("./spaces:title") - self.tooltip = xml.findtext("./spaces:tooltip") - self.url = xml.findtext("./spaces:url") - - if self.type == "Photo": - self.thumbnail_url = xml.findtext("./spaces:thumbnailUrl") - self.web_ready_url = xml.findtext("./spaces:webReadyUrl") - self.album_name = xml.findtext("./spaces:albumName") - # FIXME The "xsi:type" attribute is missing.. but do we need it ? what is it ? - - def __str__(self): - ret = " SubElement type %s - title %s - url %s - tooltip %s\n" % (self.type, self.title, self.url, self.tooltip) - try: - ret += " album name %s - thumbnail url %s - web ready url %s\n" % (self.album_name, self.thumbnail_url, self.web_ready_url) - except AttributeError: - pass - - return ret - -class ContactCardElement(object): - def __init__(self, xml): - self.type = xml.element.attrib["type"] - self.title = xml.findtext("./spaces:title") - self.url = xml.findtext("./spaces:url") - self.total_new_items = xml.findtext("./spaces:totalNewItems") - - self.sub_elements = [] - all_subelements = xml.findall("./spaces:subElement") - for e in all_subelements: - self.sub_elements.append(ContactCardSubElement(e)) - - def __str__(self): - ret = " Element type %s - title %s - url %s\n" % (self.type, self.title, self.url) - for s in self.sub_elements: - ret += "%s\n" % str(s) - - return ret - -class ContactCard(object): - def __init__(self, xml): - self.storage_auth_cache = xml.findtext("./spaces:storageAuthCache") - self.last_update = xml.findtext("./spaces:lastUpdate") - - elements_xml = xml.find("./spaces:elements") - self.returned_matches = elements_xml.element.attrib["returnedMatches"]; - self.total_matches = elements_xml.element.attrib["totalMatches"]; - try: - self.display_name = elements_xml.element.attrib["displayName"]; - except KeyError: - self.display_name = "" - - try: - self.display_picture_url = elements_xml.element.attrib["displayPictureUrl"]; - except KeyError: - self.display_picture_url = "" - - self.elements = [] - all_elements = xml.findall("./spaces:elements/spaces:element") - for e in all_elements: - self.elements.append(ContactCardElement(e)) - - def __str__(self): - ret = "matches %s\n" % self.returned_matches - for s in self.elements: - ret += "%s\n" % str(s) - return ret - -class ContactCardService(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "Spaces", proxies) - - - @RequireSecurityTokens(LiveService.SPACES) - def GetXmlFeed(self, callback, errback, contact): - self.__soap_request(self._service.GetXmlFeed, - (contact.cid), - callback, errback) - - def _HandleGetXmlFeedResponse(self, callback, errback, response, user_data): - if response is not None: - callback[0](ContactCard(response.find("./spaces:contactCard")), *callback[1:]) - else: - callback[0](None, *callback[1:]) - - def __soap_request(self, method, cid, - callback, errback, user_data=None): - token = str(self._tokens[LiveService.SPACES]) - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(token) - soap_body = method.soap_body(cid) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers, user_data) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](None, *errback[1:]) diff --git a/pymsn/pymsn/service/Spaces/scenario/__init__.py b/pymsn/pymsn/service/Spaces/scenario/__init__.py deleted file mode 100644 index d7e79048..00000000 --- a/pymsn/pymsn/service/Spaces/scenario/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "scenario" -description = "" - -import base - -from get_contact_card import * diff --git a/pymsn/pymsn/service/Spaces/scenario/base.py b/pymsn/pymsn/service/Spaces/scenario/base.py deleted file mode 100644 index 180d0879..00000000 --- a/pymsn/pymsn/service/Spaces/scenario/base.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -__all__ = ['BaseScenario'] - -class BaseScenario(object): - def __init__(self, callback, errback): - self._callback = callback - self._errback = errback - - def execute(self): - pass - - def __call__(self): - return self.execute() - diff --git a/pymsn/pymsn/service/Spaces/scenario/get_contact_card.py b/pymsn/pymsn/service/Spaces/scenario/get_contact_card.py deleted file mode 100644 index 9b42acd7..00000000 --- a/pymsn/pymsn/service/Spaces/scenario/get_contact_card.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -from pymsn.service.Spaces.scenario.base import BaseScenario - -__all__ = ['GetContactCardScenario'] - -class GetContactCardScenario(BaseScenario): - def __init__(self, ccard, contact, callback, errback): - """Accepts an invitation. - - @param ccard: the contactcard service - @param contact: the contact to fetch his CCard - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__ccard = ccard - self.__contact = contact - - def execute(self): - self.__ccard.GetXmlFeed((self.__get_xml_feed_callback,), - (self.__get_xml_feed_errback,), - self.__contact) - pass - - def __get_xml_feed_callback(self, ccard): - callback = self._callback - callback[0](ccard, *callback[1:]) - - def __get_xml_feed_errback(self, error_code): - errback = self._errback[0] - args = self._errback[1:] - errback(error_code, *args) diff --git a/pymsn/pymsn/service/Spaces/spaces.py b/pymsn/pymsn/service/Spaces/spaces.py deleted file mode 100644 index d4081354..00000000 --- a/pymsn/pymsn/service/Spaces/spaces.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -import contactcardservice -import scenario - -from pymsn.service.SOAPUtils import * - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO -import gobject - -import logging - -__all__ = ['Spaces'] - -class Spaces(gobject.GObject): - - __gsignals__ = { - "contact-card-retrieved" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)) - } - - def __init__(self, sso, proxies=None): - gobject.GObject.__init__(self) - - self._ccard = contactcardservice.ContactCardService(sso, proxies) - - # Public API - def get_contact_card(self, contact): - ccs = scenario.GetContactCardScenario(self._ccard, contact, - (self.__get_contact_card_cb, contact), - (self.__common_errback,)) - ccs() - # End of public API - - # Callbacks - def __get_contact_card_cb(self, ccard, contact): - print "Contact card retrieved : \n%s\n" % str(ccard) - self.emit('contact-card-retrieved', contact, ccard) - - def __common_errback(self, error_code, *args): - print "The fetching of the contact card returned an error (%s)" % error_code - - -gobject.type_register(Spaces) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.AddressBook import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def address_book_state_changed(address_book, pspec, sso): - if address_book.state == AddressBookState.SYNCHRONIZED: - pass - - sso = SingleSignOn(account, password) - - address_book = AddressBook(sso) - address_book.connect("notify::state", address_book_state_changed, sso) - address_book.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/__init__.py b/pymsn/pymsn/service/__init__.py deleted file mode 100644 index 796f8a0d..00000000 --- a/pymsn/pymsn/service/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Windows Live Services clients. -Contains a set of clients to connect and interact with Windows Live services. -""" - -# import AddressBook -# import OfflineIM -# import ContentRoaming diff --git a/pymsn/pymsn/service/description/AB/ABAdd.py b/pymsn/pymsn/service/description/AB/ABAdd.py deleted file mode 100644 index 22ffe74b..00000000 --- a/pymsn/pymsn/service/description/AB/ABAdd.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * -from constants import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABAdd" - -def soap_body(account): - return """ - - - 0 - - %s - - true - - """ % account - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./ab:ABAddResponse/ab:ABAddResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/AB/ABContactAdd.py b/pymsn/pymsn/service/description/AB/ABContactAdd.py deleted file mode 100644 index 92cf14fb..00000000 --- a/pymsn/pymsn/service/description/AB/ABContactAdd.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * -from constants import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABContactAdd" - -def soap_body(passport_name, is_messenger_user, contact_type, first_name, - last_name, birth_date, email, phone, location, web_site, - annotation, comment, anniversary, display_name, invite_message, - capability, enable_allow_list_management=True): - """Returns the SOAP xml body - - @param passport_name: the passport adress if the contact to add - @param is_messenger_user: True if this is a messenger contact, - otherwise False (only a Live mail contact) - @param contact_type:: 'Me' | 'Regular' | 'Messenger' | 'Messenger2' - @param first_name: string - @param last_name: string - @param birth_date: an ISO 8601 timestamp - @param email: { ContactEmailType : well-formed email string } - @param phone: { ContactPhoneType : well-formed phone string } - @param location: { ContactLocation.Type : { ContactLocation : string } } - @param web_site: { ContactWebSite : url string } - @param annotation: { ContactAnnotations : string } - @param comment: string - @param anniversary: yyyy/mm/dd - @param display_name: display name used in the invitation - @param invite_message: message sent for the invitation""" - - contact_info = '' - if passport_name is not None: - contact_info += "%s" % passport_name - - if is_messenger_user is not None: - contact_info += "%s" % is_messenger_user - - if contact_type is not None: - contact_info += "%s" % contact_type - - if first_name is not None: - contact_info += "%s" % xml.escape(first_name) - - if last_name is not None: - contact_info += "%s" % xml.escape(last_name) - - if birth_date is not None: - contact_info += "%s" % birth_date - - if email is not None: - emails = "" - for type, email in email.iteritems(): - yahoo_tags = changed = "" - if type == ContactEmailType.EXTERNAL: - yahoo_tags = """ - true - - - %s - """ % capability - changed = " IsMessengerEnabled Capability" - emails += """ - %s - %s - %s - Email%s - """ % (type, email, yahoo_tags, changed) - contact_info += "%s" % emails - - if phone is not None: - phones = "" - for type, number in phone.iteritems(): - phones += """ - %s - %s - Number - """ % (type, number) - contact_info += "%s" % phones - - if location is not None: - locations = "" - for type, parts in location.iteritems(): - items = changes = "" - for item, value in parts.iteritems(): - items += "<%s>%s" % (item, value, item) - changes += " %s%s" % (item[0].upper(), item[1:len(item)]) - locations += """ - %s - %s - %s - """ % (type, items, changes) - contact_info += "%s" % locations - - if web_site is not None: - web_sites = "" - for type, url in web_site.iteritems(): - websites += """ - %s - %s - """ % (type, xml.escape(url)) - contact_info += "%s" % web_sites - - if annotation is not None: - annotations = "" - for name, value in annotation.iteritems(): - annotations += """ - %s - %s - """ % (name, xml.escape(value)) - contact_info += "%s" % annotations - - if comment is not None: - contact_info += "%s" % xml.escape(comment) - - if anniversary is not None: - contact_info += "%s" % anniversary - - invite_info = '' - invite_info += """ - - - - MSN.IM.InviteMessage - - - %(invite_message)s - - - - - %(display_name)s - - """ % { 'invite_message' : xml.escape(invite_message), - 'display_name' : xml.escape(display_name) } - - return """ - - 00000000-0000-0000-0000-000000000000 - - - - %(contact_info)s - %(invite_info)s - - - - - - %(allow_list_management)s - - - """ % { 'contact_info' : contact_info, - 'invite_info' : invite_info, - 'allow_list_management' : str(enable_allow_list_management).lower()} - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./ab:ABContactAddResponse/ab:ABContactAddResult/ab:guid") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/AB/ABContactDelete.py b/pymsn/pymsn/service/description/AB/ABContactDelete.py deleted file mode 100644 index 086cbd10..00000000 --- a/pymsn/pymsn/service/description/AB/ABContactDelete.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABContactDelete" - -def soap_body(contact_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(contact_id)s - - - - """ % { 'contact_id' : contact_id } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABContactUpdate.py b/pymsn/pymsn/service/description/AB/ABContactUpdate.py deleted file mode 100644 index 9682d280..00000000 --- a/pymsn/pymsn/service/description/AB/ABContactUpdate.py +++ /dev/null @@ -1,186 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * -from constants import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABContactUpdate" - -def soap_body(contact_id, display_name, is_messenger_user, contact_type, - first_name, last_name, birth_date, email, phone, location, - web_site, annotation, comment, anniversary, has_space, - enable_allow_list_management=False): - """Returns the SOAP xml body - - @param contact_id: a contact GUID string - @param display_name: string - @param is_messenger_user: "true" if messenger user | - "false" if live mail contact only - @param contact_type: 'Me' | 'Regular' | 'Messenger' | 'Messenger2' - @param first_name: string - @param last_name: string - @param birth_date: an ISO 8601 timestamp - @param email: { ContactEmailType : well-formed email string } - @param phone: { ContactPhoneType : well-formed phone string } - @param location: { ContactLocation.Type : { ContactLocation : string } } - @param web_site: { ContactWebSite : url string } - @param annotation: { ContactAnnotations : string } - @param comment: string - @param anniversary: yyyy/mm/dd - @param has_space: "true" | "false" """ - - contact_info = "" - properties_changed = "" - - if display_name is not None: - contact_info += "%s" % xml.escape(display_name) - properties_changed += " DisplayName" - - if has_space is not None: - contact_info += "%s" % has_space - properties_changed += " HasSpace" - - if is_messenger_user is not None: - contact_info += "%s" % is_messenger_user - properties_changed += " IsMessengerUser" - - if contact_type is not None: - contact_info += "%s" % contact_type - - if first_name is not None: - contact_info += "%s" % xml.escape(first_name) - properties_changed += " ContactFirstName" - - if last_name is not None: - contact_info += "%s" % xml.escape(last_name) - properties_changed += " ContactLastName" - - if birth_date is not None: - contact_info += "%s" % birth_date - properties_changed += " ContactBirthDate" - - if email is not None: - emails = "" - for type, email in email.iteritems(): - emails += """ - %s - %s - Email - """ % (type, email) - contact_info += "%s" % emails - properties_changed += " ContactEmail" - - if phone is not None: - phones = "" - for type, number in phone.iteritems(): - phones += """ - %s - %s - Number - """ % (type, number) - contact_info += "%s" % phones - properties_changed += " ContactPhone" - - if location is not None: - locations = "" - for type, parts in location.iteritems(): - items = changes = "" - for item, value in parts.iteritems(): - items += "<%s>%s" % (item, value, item) - changes += " %s%s" % (item[0].upper(), item[1:len(item)]) - locations += """ - %s - %s - %s - """ % (type, items, changes) - contact_info += "%s" % locations - properties_changed += " ContactLocation" - - if web_site is not None: - web_sites = "" - for type, url in web_site.iteritems(): - websites += """ - %s - %s - """ % (type, xml.escape(url)) - contact_info += "%s" % web_sites - properties_changed += " ContactWebSite" - - if annotation is not None: - annotations = "" - for name, value in annotation.iteritems(): - if value == None or value == "": - value = "" - else: - value = "%s" % value - annotations += """ - %s - %s - """ % (name, value) - contact_info += "%s" % annotations - properties_changed += " Annotation" - - if comment is not None: - contact_info += "%s" % xml.escape(comment) - properties_changed += " Comment" - - if anniversary is not None: - contact_info += "%s" % anniversary - properties_changed += " Anniversary" - - return """ - - 00000000-0000-0000-0000-000000000000 - - - - %(contact_id)s - - - %(contact_info)s - - - %(properties_changed)s - - - - - - %(allow_list_management)s - - - """ % { 'contact_id' : contact_id, - 'contact_info' : contact_info, - 'properties_changed' : properties_changed, - 'allow_list_management' : str(enable_allow_list_management).lower() } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABFindAll.py b/pymsn/pymsn/service/description/AB/ABFindAll.py deleted file mode 100644 index 7ac3ab63..00000000 --- a/pymsn/pymsn/service/description/AB/ABFindAll.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABFindAll" - -def soap_body(deltas_only, last_change): - """Returns the SOAP xml body""" - - return """ - - 00000000-0000-0000-0000-000000000000 - Full - %(deltas_only)s - %(last_change)s - """ % {'deltas_only' : deltas_only, - 'last_change' : last_change} - -def process_response(soap_response): - find_all_result = soap_response.body.\ - find("./ab:ABFindAllResponse/ab:ABFindAllResult") - - path = "./ab:groups/ab:Group" - groups = find_all_result.findall(path) - - path = "./ab:contacts/ab:Contact" - contacts = find_all_result.findall(path) - - path = "./ab:ab" - ab = find_all_result.find(path) - - return (ab, groups, contacts) diff --git a/pymsn/pymsn/service/description/AB/ABGroupAdd.py b/pymsn/pymsn/service/description/AB/ABGroupAdd.py deleted file mode 100644 index 0bf3b30e..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupAdd.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupAdd" - -def soap_body(group_name): - """Returns the SOAP xml body""" - - return """ - - 00000000-0000-0000-0000-000000000000 - - false - - - - - %(group_name)s - - - C8529CE2-6EAD-434d-881F-341E17DB3FF8 - - - false - - - - - MSN.IM.Display - - - 1 - - - - - - """ % { 'group_name' : xml.escape(group_name) } - -def process_response(soap_response): - guid = soap_response.body.find("./ab:ABGroupAddResponse/" - "ab:ABGroupAddResult/ab:guid") - return guid diff --git a/pymsn/pymsn/service/description/AB/ABGroupContactAdd.py b/pymsn/pymsn/service/description/AB/ABGroupContactAdd.py deleted file mode 100644 index dfb74ea0..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupContactAdd.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * -from constants import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupContactAdd" - -# TODO : complete the method to be able to add a contact (like ABContactAdd) -# directly when adding him to his original group - -def soap_body(group_id, contact_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(group_id)s - - - - - - - %(contact_id)s - - - - """ % { 'group_id' : group_id, - 'contact_id' : contact_id } - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./ab:ABGroupContactAddResponse/" \ - "ab:ABGroupContactAddResult/ab:guid").text - except: - return None diff --git a/pymsn/pymsn/service/description/AB/ABGroupContactDelete.py b/pymsn/pymsn/service/description/AB/ABGroupContactDelete.py deleted file mode 100644 index 693883b8..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupContactDelete.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupContactDelete" - -def soap_body(group_id, contact_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %s - - - - - - - %s - - - - """ % (contact_id, group_id) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABGroupDelete.py b/pymsn/pymsn/service/description/AB/ABGroupDelete.py deleted file mode 100644 index 90b8b2f4..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupDelete.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupDelete" - -def soap_body(group_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(group_id)s - - - - """ % { 'group_id' : group_id } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABGroupUpdate.py b/pymsn/pymsn/service/description/AB/ABGroupUpdate.py deleted file mode 100644 index f17f21a8..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupUpdate.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupUpdate" - -def soap_body(group_id, group_name): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(group_id)s - - - - %(group_name)s - - - - GroupName - - - - """ % { 'group_id' : group_id, - 'group_name' : xml.escape(group_name) } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/__init__.py b/pymsn/pymsn/service/description/AB/__init__.py deleted file mode 100644 index 48af7e24..00000000 --- a/pymsn/pymsn/service/description/AB/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "AB" -description = "Hotmail address book service" - -url = "http://contacts.msn.com/abservice/abservice.asmx" - -from constants import * - -import ABAdd - -import ABFindAll - -import ABContactAdd -import ABContactDelete -import ABContactUpdate - -import ABGroupAdd -import ABGroupDelete -import ABGroupUpdate -import ABGroupContactAdd -import ABGroupContactDelete diff --git a/pymsn/pymsn/service/description/AB/common.py b/pymsn/pymsn/service/description/AB/common.py deleted file mode 100644 index 5e17e3e6..00000000 --- a/pymsn/pymsn/service/description/AB/common.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import xml.sax.saxutils as xml - -def soap_header(scenario, security_token): - """Returns the SOAP xml header""" - - return """ - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - %s - - - false - %s - """ % (xml.escape(scenario), xml.escape(security_token)) diff --git a/pymsn/pymsn/service/description/AB/constants.py b/pymsn/pymsn/service/description/AB/constants.py deleted file mode 100644 index 6cce72cf..00000000 --- a/pymsn/pymsn/service/description/AB/constants.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -__all__ = ['ContactAnnotations', 'ContactEmailType', 'ContactPhoneType', - 'ContactLocation', 'ContactWebSiteType', 'ContactGeneral'] - -class ContactGeneral(object): - FIRST_NAME = 0 - LAST_NAME = 1 - BIRTH_DATE = 2 - EMAILS = 3 - PHONES = 4 - LOCATIONS = 5 - WEBSITES = 6 - ANNOTATIONS = 7 - COMMENT = 8 - ANNIVERSARY = 9 - -class ContactAnnotations(object): - NICKNAME = "AB.NickName" - JOB_TITLE = "AB.JobTitle" - SPOUSE = "AB.Spouse" - -class ContactEmailType(object): - BUSINESS = "ContactEmailBusiness" - MESSENGER = "ContactEmailMessenger" - OTHER = "ContactEmailOther" - PERSONAL = "ContactEmailPersonal" - EXTERNAL = "Messenger2" - -class ContactPhoneType(object): - BUSINESS = "ContactPhoneBusiness" - FAX = "ContactPhoneFax" - MOBILE = "ContactPhoneMobile" - OTHER = "ContactPhoneOther" - PAGER = "ContactPhonePager" - PERSONAL = "ContactPhonePersonal" - -class ContactLocation(object): - class Type(object): - BUSINESS = "ContactLocationBusiness" - PERSONAL = "ContactLocationPersonal" - - NAME = "name" - STREET = "street" - CITY = "city" - STATE = "state" - COUNTRY = "country" - POSTAL_CODE = "postalCode" - -class ContactWebSiteType(object): - BUSINESS = "ContactWebSiteBusiness" - PERSONAL = "ContactWebSitePersonal" - diff --git a/pymsn/pymsn/service/description/OIM/Store2.py b/pymsn/pymsn/service/description/OIM/Store2.py deleted file mode 100644 index ac8437f4..00000000 --- a/pymsn/pymsn/service/description/OIM/Store2.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -import xml.sax.saxutils as xml -def soap_header(from_member_name, friendly_name, proxy, msnp_ver, build_ver, - to_member_name, message_number, security_token, app_id, - lock_key): - """Returns the SOAP xml header""" - - # FIXME : escape the parameters - - return """ - - - - - http://messenger.msn.com - - %(message_number)s - """ % { 'from_member_name' : from_member_name, - 'friendly_name' : friendly_name, - 'proxy' : proxy, - 'msnp_ver' : msnp_ver, - 'build_ver' : build_ver, - 'to_member_name' : to_member_name, - 'passport' : xml.escape(security_token), - 'app_id' : app_id, - 'lock_key' : lock_key, - 'message_number' : message_number } - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://messenger.live.com/ws/2006/09/oim/Store2" - -def soap_body(message_type, message_content): - """Returns the SOAP xml body""" - - return """ - %s - - - %s - """ % (message_type, message_content) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/OIM/__init__.py b/pymsn/pymsn/service/description/OIM/__init__.py deleted file mode 100644 index 8ab4b7f5..00000000 --- a/pymsn/pymsn/service/description/OIM/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "OIM" -description = "Offline messages service" - -url = "https://ows.messenger.msn.com/OimWS/oim.asmx" - -import Store2 diff --git a/pymsn/pymsn/service/description/RSI/DeleteMessages.py b/pymsn/pymsn/service/description/RSI/DeleteMessages.py deleted file mode 100644 index 080ee9b1..00000000 --- a/pymsn/pymsn/service/description/RSI/DeleteMessages.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages" - -def soap_body(message_ids): - """Returns the SOAP xml body""" - - ids = "" - for message_id in message_ids: - ids += "%s" % message_id - - return """ - - - %s - - """ % ids - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/RSI/GetMessage.py b/pymsn/pymsn/service/description/RSI/GetMessage.py deleted file mode 100644 index 12dd0e05..00000000 --- a/pymsn/pymsn/service/description/RSI/GetMessage.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage" - -def soap_body(message_id, also_mark_as_read): - """Returns the SOAP xml body - - @param message_id: the id of the message to get (guid) - @param also_mark_as_read: "true if the message should be marked as read - "false else - """ - return """ - - %s - %s - """ % (message_id, also_mark_as_read) - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./rsi:GetMessageResponse/rsi:GetMessageResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/RSI/GetMetadata.py b/pymsn/pymsn/service/description/RSI/GetMetadata.py deleted file mode 100644 index e85302f9..00000000 --- a/pymsn/pymsn/service/description/RSI/GetMetadata.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata" - -def soap_body(): - """Returns the SOAP xml body""" - - return """ - """ - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./rsi:GetMetadataResponse") - except AttributeError: - return None - diff --git a/pymsn/pymsn/service/description/RSI/__init__.py b/pymsn/pymsn/service/description/RSI/__init__.py deleted file mode 100644 index 2365cda2..00000000 --- a/pymsn/pymsn/service/description/RSI/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "RSI" -description = "Offline message service" - -url = "https://rsi.hotmail.com/rsi/rsi.asmx" - -import GetMetadata -import GetMessage -import DeleteMessages diff --git a/pymsn/pymsn/service/description/RSI/common.py b/pymsn/pymsn/service/description/RSI/common.py deleted file mode 100644 index 75e5d895..00000000 --- a/pymsn/pymsn/service/description/RSI/common.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import xml.sax.saxutils as xml - -def soap_header(security_token): - """Returns the SOAP xml header""" - - t, p = security_token.split('&') - - return """ - - %s -

%s

-
""" % (xml.escape(t[2:]), - xml.escape(p[2:])) diff --git a/pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py b/pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py deleted file mode 100644 index 9e09bcac..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/CreateDocument" - -def soap_body(cid, photo_name, photo_mime_type, photo_data): - """Returns the SOAP xml body - """ - return """ - - - /UserTiles - - - - %s - - - MyCidStuff - - - - - - %s - - - - - UserTileStatic - - - %s - - - %s - - - 0 - - - - - - Messenger User Tile - - """ % (cid, photo_name, photo_mime_type, photo_data) - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./st:CreateDocumentResponse/st:CreateDocumentResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py b/pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py deleted file mode 100644 index 05dce82b..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/CreateRelationships" - -def soap_body(source_rid, target_rid): - """Returns the SOAP xml body - """ - return """ - - - - %s - - - SubProfile - - - %s - - - Photo - - - ProfilePhoto - - - - """ % (source_rid, target_rid) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py b/pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py deleted file mode 100644 index d9d086e7..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/DeleteRelationships" - -def soap_body(cid, source_rid, target_rid): - """Returns the SOAP xml body - """ - if cid is not None: - source_handle = """ - /UserTiles - - - - %s - - - MyCidStuff - - """ % cid - else: - source_handle = "%s" % source_rid - - return """ - - %s - - - - - %s - - - - """ % (source_handle, target_rid) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py b/pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py deleted file mode 100644 index 0c0591ab..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/FindDocument" - -def soap_body(cid): - """Returns the SOAP xml body - """ - return """ - - - /UserTiles - - - - %s - - - MyCidStuff - - - - - - true - - - true - - - - - None - - - - - DateModified - - - - - Default - - - 25 - - - """ % cid - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./st:FindDocumentsResponse/st:FindDocumentsResult") - except AttributeError: - return None - diff --git a/pymsn/pymsn/service/description/SchematizedStore/GetProfile.py b/pymsn/pymsn/service/description/SchematizedStore/GetProfile.py deleted file mode 100644 index 2d07b9a0..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/GetProfile.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/GetProfile" - -def soap_body(cid, profile_rid, p_date_modified, expression_rid, - e_date_modified, display_name, dn_last_modified, - personal_status, ps_last_modified, user_tile_url, - photo, flags): - """Returns the SOAP xml body - """ - return """ - - - %(cid)s - MyCidStuff - - MyProfile - - - %(profile_rid)s - %(p_date_modified)s - - %(expression_rid)s - %(e_date_modified)s - %(display_name)s - %(dn_last_modified)s - %(personal_status)s - %(ps_last_modified)s - %(user_tile_url)s - %(photo)s - %(flags)s - - - """ % { 'cid' : cid, - 'profile_rid' : profile_rid, - 'p_date_modified' : p_date_modified, - 'expression_rid' : expression_rid, - 'e_date_modified' : e_date_modified, - 'display_name' : display_name, - 'dn_last_modified' : dn_last_modified, - 'personal_status' : personal_status, - 'ps_last_modified' : ps_last_modified, - 'user_tile_url' : user_tile_url, - 'photo' : photo, - 'flags' : flags } - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./st:GetProfileResponse/st:GetProfileResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py b/pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py deleted file mode 100644 index 1719d235..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/UpdateProfile" - -def soap_body(profile_rid, display_name, personal_status, flags=0): - """Returns the SOAP xml body - """ - return """ - - - - %s - - - - Update - - - %s - - - %s - - - %s - - - - """ % (profile_rid, display_name, personal_status, flags) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/__init__.py b/pymsn/pymsn/service/description/SchematizedStore/__init__.py deleted file mode 100644 index 4cc4a721..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "SchematizedStore" -description = "MSN Storage service" - -url = "https://storage.msn.com/storageservice/SchematizedStore.asmx" - -import GetProfile -import UpdateProfile - -import DeleteRelationships -import CreateRelationships - -import CreateDocument -import FindDocuments diff --git a/pymsn/pymsn/service/description/SchematizedStore/common.py b/pymsn/pymsn/service/description/SchematizedStore/common.py deleted file mode 100644 index dddb8b19..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/common.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import xml.sax.saxutils as xml - -def soap_header(scenario, security_token): - """Returns the SOAP xml header""" - - return """ - Messenger Client 8.0 - - %s - - - - 0 - - %s - - """ % (xml.escape(scenario), xml.escape(security_token)) diff --git a/pymsn/pymsn/service/description/Sharing/AddMember.py b/pymsn/pymsn/service/description/Sharing/AddMember.py deleted file mode 100644 index 278ea9a0..00000000 --- a/pymsn/pymsn/service/description/Sharing/AddMember.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/AddMember" - -def soap_body(member_role, type, state, account): - """Returns the SOAP xml body""" - stuff = "" - - if type == 'Passport': - stuff = "%s" % account - elif type == 'Email': - stuff = """%s - - MSN.IM.BuddyType - 32: - """ % account - - return """ - - - - 0 - - - Messenger - - - - - - - - %s - - - - - %s - - - %s - - %s - - - - - """ % (member_role, type, type, state, stuff) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/Sharing/DeleteMember.py b/pymsn/pymsn/service/description/Sharing/DeleteMember.py deleted file mode 100644 index 5e1669c4..00000000 --- a/pymsn/pymsn/service/description/Sharing/DeleteMember.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/DeleteMember" - -def soap_body(member_role, type, state, account): - """Returns the SOAP xml body""" - address = "" - - if account is not None: - if type == 'Passport': - address = "%s" % account - elif type == 'Email': - address = "%s" % account - - member = """ - %s - %s - %s - """ % (type, type, state, address) - - return """ - - - - 0 - - - Messenger - - - - - - - - %(member_role)s - - - %(member)s - - - - """ % { 'member_role' : member_role, - 'member' : member } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/Sharing/FindMembership.py b/pymsn/pymsn/service/description/Sharing/FindMembership.py deleted file mode 100644 index 31901412..00000000 --- a/pymsn/pymsn/service/description/Sharing/FindMembership.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -from common import * -from pymsn.profile import Membership - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/FindMembership" - -def soap_body(services_types, deltas_only, last_change): - """Returns the SOAP xml body""" - - services = '' - for service in services_types: - services += """ - %s - """ % xml.escape(service) - - deltas = '' - if deltas_only: - deltas = """ - Full - - - true - - - %s - """ % last_change - - return """ - - - - %(services)s - - - %(deltas)s - """ % {'services' : services, 'deltas' : deltas} - -def process_response(soap_response): - # FIXME: don't pick the 1st service only, we need to extract them all - result = {} - service = soap_response.body.find("./ab:FindMembershipResponse/" - "ab:FindMembershipResult/ab:Services/" - "ab:Service") - if service is not None: - memberships = service.find("./ab:Memberships") - for membership in memberships: - role = membership.find("./ab:MemberRole") - members = membership.findall("./ab:Members/ab:Member") - if role is None or len(members) == 0: - continue - result[role.text] = members - last_changes = service.find("./ab:LastChange") - else: - last_changes = {'text':"0-0-0T0:0:0.0-0:0"} - result = {'Allow':{},'Block':{},'Reverse':{},'Pending':{}} - return (result, last_changes) - - diff --git a/pymsn/pymsn/service/description/Sharing/__init__.py b/pymsn/pymsn/service/description/Sharing/__init__.py deleted file mode 100644 index 20423c95..00000000 --- a/pymsn/pymsn/service/description/Sharing/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -name = "Sharing" -description = "Membership address book service" - -url = "http://contacts.msn.com/abservice/SharingService.asmx" - -import FindMembership -import AddMember -import DeleteMember diff --git a/pymsn/pymsn/service/description/Sharing/common.py b/pymsn/pymsn/service/description/Sharing/common.py deleted file mode 100644 index 74b95b5c..00000000 --- a/pymsn/pymsn/service/description/Sharing/common.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import xml.sax.saxutils as xml - -__all__ = ['soap_header'] - -def soap_header(scenario, security_token): - """Returns the SOAP xml header""" - - return """ - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - %s - - - false - %s - """ % (xml.escape(scenario), xml.escape(security_token)) diff --git a/pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py b/pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py deleted file mode 100644 index 6fc7cc73..00000000 --- a/pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import xml.sax.saxutils as xml - -class LiveService(object): - CONTACTS = ("contacts.msn.com", "?fs=1&id=24000&kv=7&rn=93S9SWWw&tw=0&ver=2.1.6000.1") - MESSENGER = ("messenger.msn.com", "?id=507") - MESSENGER_CLEAR = ("messengerclear.live.com", "MBI_KEY_OLD") - MESSENGER_SECURE = ("messengersecure.live.com", "MBI_SSL") - SPACES = ("spaces.live.com", "MBI") - TB = ("http://Passport.NET/tb", None) - VOICE = ("voice.messenger.msn.com", "?id=69264") - - @classmethod - def url_to_service(cls, url): - for attr_name in dir(cls): - if attr_name.startswith('_'): - continue - attr = getattr(cls, attr_name) - if isinstance(attr, tuple) and attr[0] == url: - return attr - return None - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return None - -def soap_header(account, password): - """Returns the SOAP xml header""" - - return """ - - {7108E71A-9926-4FCB-BCC9-9A9D3F32E423} - 4 - 1 - - AQAAAAIAAABsYwQAAAAxMDMz - - - - %(account)s - %(password)s - - """ % {'account': xml.escape(account), - 'password': xml.escape(password)} - -def soap_body(*tokens): - """Returns the SOAP xml body""" - - token_template = """ - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - %(address)s - - - %(policy_reference)s - """ - policy_reference_template = """ - """ - - tokens = list(tokens) - if LiveService.TB in tokens: - tokens.remove(LiveService.TB) - - assert(len(tokens) >= 1) - - body = token_template % \ - {'id': 0, - 'address': xml.escape(LiveService.TB[0]), - 'policy_reference': ''} - - for id, token in enumerate(tokens): - if token[1] is not None: - policy_reference = policy_reference_template % \ - {'uri': xml.quoteattr(token[1])} - else: - policy_reference = "" - - t = token_template % \ - {'id': id + 1, - 'address': xml.escape(token[0]), - 'policy_reference': policy_reference} - body += t - - return '%s' % body - -def process_response(soap_response): - body = soap_response.body - return body.findall("./wst:RequestSecurityTokenResponseCollection/" \ - "wst:RequestSecurityTokenResponse") - diff --git a/pymsn/pymsn/service/description/SingleSignOn/__init__.py b/pymsn/pymsn/service/description/SingleSignOn/__init__.py deleted file mode 100644 index 06f59a8c..00000000 --- a/pymsn/pymsn/service/description/SingleSignOn/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -name = "SingleSignOn" -description = "Microsoft Passport single sign on service." - -url = "https://login.live.com/RST.srf" - -import RequestMultipleSecurityTokens diff --git a/pymsn/pymsn/service/description/Spaces/GetXmlFeed.py b/pymsn/pymsn/service/description/Spaces/GetXmlFeed.py deleted file mode 100644 index 2eecc9d8..00000000 --- a/pymsn/pymsn/service/description/Spaces/GetXmlFeed.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -import xml.sax.saxutils as xml - -#from pymsn.util.element_tree import XMLTYPE - -def soap_header(security_token): - """Returns the SOAP xml header""" - - return """ - - %s - - """ % (xml.escape(security_token)) - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/spaces/v1/GetXmlFeed" - -def soap_body(cid, market = "en-US", max_elements = 2, max_chars = 200, max_images = 6): - """Returns the SOAP xml body""" - # , last_viewed, app_id = "Messenger Client 8.0", update_access_time = True, is_active_contact = False - return """ - - - %(cid)s - - %(market)s - - %(max_element_count)d - %(max_character_count)d - %(max_image_count)d - - - """ % {'cid' : cid, - 'market' : market, - 'max_element_count' : max_elements, - 'max_character_count' : max_chars, - 'max_image_count' : max_images} - -# %(application_id)s -# %(update_accessed_time)s -# %(space_last_viewed)s -# %(is_active_contact)s - -# 'appliation_id' : app_id, -# 'update_accessed_time' : XMLTYPE.boolean.encode(update_access_time), -# 'space_last_viewed' : last_viewed, -# 'is_active_contact' : XMLTYPE.boolean.encode(is_active_contact) - -def process_response(soap_response): - body = soap_response.body - try: - response = body.find("./spaces:GetXmlFeedResponse/spaces:GetXmlFeedResult") - return response - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/Spaces/__init__.py b/pymsn/pymsn/service/description/Spaces/__init__.py deleted file mode 100644 index e957d013..00000000 --- a/pymsn/pymsn/service/description/Spaces/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import SingleSignOn - -import AB -import Sharing - -import SchematizedStore - -import RSI -import OIM -import Spaces diff --git a/pymsn/pymsn/storage.py b/pymsn/pymsn/storage.py deleted file mode 100644 index b4c76a48..00000000 --- a/pymsn/pymsn/storage.py +++ /dev/null @@ -1,211 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import pymsn.util.string_io as StringIO -try: - from cPickle import Pickler, Unpickler -except ImportError: - from pickle import Pickler, Unpickler - -import UserDict -import anydbm -import random -from Crypto.Hash import SHA -from Crypto.Cipher import Blowfish - -__all__ = ['MemoryStorage', 'DbmStorage', 'DecryptError'] - -_storage = None - -def set_storage(klass): - global _storage - _storage = klass - -def get_storage(*args): - global _storage - if _storage is None: - _storage = MemoryStorage - if len(args) > 0: - return _storage(*args) - else: - return _storage - - -class BlowfishCipher: - def __init__(self, key): - self._cipher = Blowfish.new(key) - - def encrypt(self, data): - return self._cipher.encrypt(self.__add_padding(data)) - - def decrypt(self, data): - return self.__remove_padding(self._cipher.decrypt(data)) - - def __add_padding(self, data): - padding_length = 8 - (len(data) % 8) - for i in range(padding_length - 1): - data += chr(random.randrange(0, 256)) - data += chr(padding_length) - return data - - def __remove_padding(self, data): - padding_length = ord(data[-1]) % 8 - if padding_length == 0: - padding_length = 8 - return data[:-padding_length] - - -class DecryptError(Exception): - pass - - -class AbstractStorage(UserDict.DictMixin): - """Base class for storage objects, storage objects are - a way to let pymsn and the client agree on how data may - be stored. This data included security tokens, cached - display pictures ...""" - - def __init__(self, account, password, identifier): - """Initializer - - @param identifier: the identifier of this storage instance - @type identifier: string""" - self.account = account - self.cipher = BlowfishCipher(SHA.new(password).digest()) - self.storage_id = identifier - - def keys(self): - raise NotImplementedError("Abstract method call") - - def has_key(self, key): - return key in self.keys() - - def get(self, key, default=None): - if self.has_key(key): - return self[key] - return default - - def __len__(self): - return len(self.keys) - - def __contains__(self, key): - return self.has_key(key) - - def __getitem__(self, key): - raise NotImplementedError("Abstract method call") - - def __setitem__(self, key, value): - raise NotImplementedError("Abstract method call") - - def __delitem__(self, key): - raise NotImplementedError("Abstract method call") - - def __del__(self): - raise NotImplementedError("Abstract method call") - - def close(self): - pass - - # Helper functions - def _pickle_encrypt(self, value): - f = StringIO.StringIO() - pickler = Pickler(f, -1) - pickler.dump(value) - data = self.account + f.getvalue() # prepend a known value to check decrypt - return self.cipher.encrypt(data) - - def _unpickle_decrypt(self, data): - data = self.cipher.decrypt(data) - - if not data.startswith(self.account): - raise DecryptError() - data = data[len(self.account):] - return Unpickler(StringIO.StringIO(data)).load() - - -_MemoryStorageDict = {} -class MemoryStorage(AbstractStorage): - """In memory storage type""" - - def __init__(self, account, password, identifier): - AbstractStorage.__init__(self, account, password, identifier) - if account + "/" + identifier not in _MemoryStorageDict: - _MemoryStorageDict[account + "/" + identifier] = {} - self._dict = _MemoryStorageDict[self.account + "/" + self.storage_id] - - def keys(self): - return self._dict.keys() - - def __getitem__(self, key): - return self._unpickle_decrypt(self._dict[key]) - - def __setitem__(self, key, value): - self._dict[key] = self._pickle_encrypt(value) - - def __delitem__(self, key): - del self._dict[key] - - def __del__(self): - pass - - def close(self): - pass - - -class DbmStorage(AbstractStorage): - STORAGE_PATH = "~/.pymsn" - - def __init__(self, account, password, identifier): - import os.path - AbstractStorage.__init__(self, account, password, identifier) - - storage_path = os.path.expanduser(self.STORAGE_PATH) - - file_dir = os.path.join(storage_path, self.account) - file_path = os.path.join(file_dir, self.storage_id) - try: - import os - os.makedirs(file_dir) - except: - pass - self._dict = anydbm.open(file_path, 'c') - - def keys(self): - return self._dict.keys() - - def __getitem__(self, key): - return self._unpickle_decrypt(self._dict[str(key)]) # some dbm don't support int keys - - def __setitem__(self, key, value): - self._dict[str(key)] = self._pickle_encrypt(value) - if hasattr(self._dict, 'sync'): - self._dict.sync() - - def __delitem__(self, key): - del self._dict[str(key)] - - def __del__(self): - self.close() - - def close(self): - if hasattr(self._dict, 'sync'): - self._dict.sync() - self._dict.close() - diff --git a/pymsn/pymsn/switchboard_manager.py b/pymsn/pymsn/switchboard_manager.py deleted file mode 100644 index 4f76dc5b..00000000 --- a/pymsn/pymsn/switchboard_manager.py +++ /dev/null @@ -1,366 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Switchboard Manager -The switchboard manager is responsible for managing all the switchboards in -use, it simplifies the complexity of the switchboard crack.""" - -import logging -import gobject -import weakref - -import pymsn.msnp as msnp -from pymsn.transport import ServerType -from pymsn.util.weak import WeakSet -from pymsn.event import ConversationErrorType, ContactInviteError, MessageError - -__all__ = ['SwitchboardManager'] - -logger = logging.getLogger('protocol:switchboard_manager') - -class SwitchboardClient(object): - def __init__(self, client, contacts, priority=99): - self._client = client - self._switchboard_manager = weakref.proxy(self._client._switchboard_manager) - self.__switchboard = None - self._switchboard_requested = False - self._switchboard_priority = priority - - self._pending_invites = set(contacts) - self._pending_messages = [] - - self.participants = set() - self._process_pending_queues() - - @staticmethod - def _can_handle_message(message, switchboard_client=None): - return False - - # properties - @property - def total_participants(self): - return self.participants | self._pending_invites - - def __get_switchboard(self): - return self.__switchboard - def __set_switchboard(self, switchboard): - self.__switchboard = weakref.proxy(switchboard) - self._switchboard_requested = False - self.participants = set(switchboard.participants.values()) - - self.switchboard.connect("notify::inviting", - lambda sb, pspec: self.__on_user_inviting_changed()) - self.switchboard.connect("user-joined", - lambda sb, contact: self.__on_user_joined(contact)) - self.switchboard.connect("user-left", - lambda sb, contact: self.__on_user_left(contact)) - self.switchboard.connect("user-invitation-failed", - lambda sb, contact: self.__on_user_invitation_failed(contact)) - self.switchboard.connect("message-undelivered", - lambda sb, command: self.__on_message_undelivered(command)) - logger.info("New switchboard attached") - def process_pending_queues(): - self._process_pending_queues() - return False - gobject.idle_add(process_pending_queues) - - _switchboard = property(__get_switchboard, __set_switchboard) - switchboard = property(__get_switchboard) - - # protected - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - message = msnp.Message(self._client.profile) - for key, value in headers.iteritems(): - message.add_header(key, value) - message.content_type = content_type - message.body = body - - self._pending_messages.append((message, ack)) - self._process_pending_queues() - - def _invite_user(self, contact): - self._pending_invites.add(contact) - self._process_pending_queues() - - def _leave(self): - self._switchboard_manager.close_handler(self) - - # callbacks - def _on_message_received(self, message): - raise NotImplementedError - - def _on_message_sent(self, message): - raise NotImplementedError - - def _on_contact_joined(self, contact): - raise NotImplementedError - - def _on_contact_left(self, contact): - raise NotImplementedError - - def _on_error(self, error_type, error): - raise NotImplementedError - - # private - def __on_user_inviting_changed(self): - if not self.switchboard.inviting: - self._process_pending_queues() - - def __on_user_joined(self, contact): - self.participants.add(contact) - self._pending_invites.discard(contact) - self._on_contact_joined(contact) - - def __on_user_left(self, contact): - self._on_contact_left(contact) - self.participants.remove(contact) - if len(self.participants) == 0: - self._pending_invites.add(contact) - try: - self._switchboard.leave() - except: - pass - - def __on_user_invitation_failed(self, contact): - self._pending_invites.discard(contact) - self._on_error(ConversationErrorType.CONTACT_INVITE, - ContactInviteError.NOT_AVAILABLE) - - def __on_message_undelivered(self, command): - self._on_error(ConversationErrorType.MESSAGE, - MessageError.DELIVERY_FAILED) - - # Helper functions - def _process_pending_queues(self): - if len(self._pending_invites) == 0 and \ - len(self._pending_messages) == 0: - return - - if self._request_switchboard(): - return - - for contact in self._pending_invites: - if contact not in self.participants: - self.switchboard.invite_user(contact) - self._pending_invites = set() - - if not self.switchboard.inviting: - for message, ack in self._pending_messages: - self.switchboard.send_message(message, ack) - self._pending_messages = [] - - def _request_switchboard(self): - if (self.switchboard is not None) and \ - self.switchboard.state == msnp.ProtocolState.OPEN: - return False - if self._switchboard_requested: - return True - logger.info("requesting new switchboard") - self._switchboard_requested = True - self._pending_invites |= self.participants - self.participants = set() - self._switchboard_manager.request_switchboard(self, self._switchboard_priority) # may set the switchboard immediatly - return self._switchboard_requested - - -class SwitchboardManager(gobject.GObject): - """Switchboard management - - @undocumented: do_get_property, do_set_property - @group Handlers: _handle_*, _default_handler, _error_handler""" - __gsignals__ = { - "handler-created": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)) - } - - def __init__(self, client): - """Initializer - - @param client: the main Client instance""" - gobject.GObject.__init__(self) - self._client = weakref.proxy(client) - - self._handlers_class = set() - self._orphaned_handlers = WeakSet() - self._switchboards = {} - self._orphaned_switchboards = set() - self._pending_switchboards = {} - - self._client._protocol.connect("switchboard-invitation-received", - self._ns_switchboard_invite) - - def close(self): - for switchboard in self._orphaned_switchboards: - switchboard.leave() - for switchboard in self._pending_switchboards: - switchboard.leave() - for switchboard in self._switchboards: - switchboard.leave() - - def register_handler(self, handler_class, *extra_arguments): - self._handlers_class.add((handler_class, extra_arguments)) - - def request_switchboard(self, handler, priority=99): - handler_participants = handler.total_participants - - # If the Handler was orphan, then it is no more - self._orphaned_handlers.discard(handler) - - # Check already open switchboards - for switchboard in self._switchboards.keys(): - switchboard_participants = set(switchboard.participants.values()) - if handler_participants == switchboard_participants: - self._switchboards[switchboard].add(handler) - handler._switchboard = switchboard - return - - # Check Orphaned switchboards - for switchboard in list(self._orphaned_switchboards): - switchboard_participants = set(switchboard.participants.values()) - if handler_participants == switchboard_participants: - self._switchboards[switchboard] = set([handler]) #FIXME: WeakSet ? - self._orphaned_switchboards.discard(switchboard) - handler._switchboard = switchboard - return - - # Check being requested switchboards - for switchboard, handlers in self._pending_switchboards.iteritems(): - pending_handler = handlers.pop() - handlers.add(pending_handler) - switchboard_participants = pending_handler.total_participants - if handler_participants == switchboard_participants: - self._pending_switchboards[switchboard].add(handler) - return - - self._client._protocol.\ - request_switchboard(priority, self._ns_switchboard_request_response, handler) - - def close_handler(self, handler): - self._orphaned_handlers.discard(handler) - for switchboard in self._switchboards.keys(): - handlers = self._switchboards[switchboard] - handlers.discard(handler) - if len(handlers) == 0: - switchboard.leave() - del self._switchboards[switchboard] - self._orphaned_switchboards.add(switchboard) - - for switchboard in self._pending_switchboards.keys(): - handlers = self._pending_switchboards[switchboard] - handlers.discard(handler) - if len(handlers) == 0: - del self._pending_switchboards[switchboard] - self._orphaned_switchboards.add(switchboard) - - def _ns_switchboard_request_response(self, session, handler): - switchboard = self._build_switchboard(session) - self._pending_switchboards[switchboard] = set([handler]) #FIXME: WeakSet ? - - def _ns_switchboard_invite(self, protocol, session, inviter): - switchboard = self._build_switchboard(session) - self._orphaned_switchboards.add(switchboard) - - def _build_switchboard(self, session): - server, session_id, key = session - client = self._client - proxies = client._proxies - - transport_class = client._transport_class - transport = transport_class(server, ServerType.SWITCHBOARD, proxies) - switchboard = msnp.SwitchboardProtocol(client, transport, - session_id, key, proxies) - switchboard.connect("notify::state", self._sb_state_changed) - switchboard.connect("message-received", self._sb_message_received) - transport.establish_connection() - return switchboard - - def _sb_state_changed(self, switchboard, param_spec): - state = switchboard.state - if state == msnp.ProtocolState.OPEN: - self._switchboards[switchboard] = set() #FIXME: WeakSet ? - - # Requested switchboards - if switchboard in self._pending_switchboards: - handlers = self._pending_switchboards[switchboard] - while True: - try: - handler = handlers.pop() - self._switchboards[switchboard].add(handler) - handler._switchboard = switchboard - except KeyError: - break - del self._pending_switchboards[switchboard] - - # Orphaned Handlers - for handler in list(self._orphaned_handlers): - switchboard_participants = set(switchboard.participants.values()) - handler_participants = handler.total_participants - if handler_participants == switchboard_participants: - self._switchboards[switchboard].add(handler) - self._orphaned_handlers.discard(handler) - self._orphaned_switchboards.discard(switchboard) - handler._switchboard = switchboard - - # no one wants it, it is an orphan - if len(self._switchboards[switchboard]) == 0: - del self._switchboards[switchboard] - self._orphaned_switchboards.add(switchboard) - - elif state == msnp.ProtocolState.CLOSED: - if switchboard in self._switchboards.keys(): - for handler in self._switchboards[switchboard]: - self._orphaned_handlers.add(handler) - del self._switchboards[switchboard] - self._orphaned_switchboards.discard(switchboard) - - def _sb_message_received(self, switchboard, message): - switchboard_participants = set(switchboard.participants.values()) - if switchboard in self._switchboards.keys(): - handlers = self._switchboards[switchboard] - handlers_class = [type(handler) for handler in handlers] - for handler in list(handlers): - if not handler._can_handle_message(message, handler): - continue - handler._on_message_received(message) - for handler_class, extra_args in self._handlers_class: - if handler_class in handlers_class: - continue - if not handler_class._can_handle_message(message): - continue - handler = handler_class(self._client, (), *extra_args) - handlers.add(handler) - handler._switchboard = switchboard - self.emit("handler-created", handler_class, handler) - handler._on_message_received(message) - - if switchboard in list(self._orphaned_switchboards): - for handler_class, extra_args in self._handlers_class: - if not handler_class._can_handle_message(message): - continue - handler = handler_class(self._client, (), *extra_args) - self._switchboards[switchboard] = set([handler]) #FIXME: WeakSet ? - self._orphaned_switchboards.discard(switchboard) - handler._switchboard = switchboard - self.emit("handler-created", handler_class, handler) - handler._on_message_received(message) - diff --git a/pymsn/pymsn/transport.py b/pymsn/pymsn/transport.py deleted file mode 100644 index 214f885d..00000000 --- a/pymsn/pymsn/transport.py +++ /dev/null @@ -1,487 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# Copyright (C) 2006 Johann Prieur -# Copyright (C) 2006 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Network Transport Layer - -This module provides an abstraction of the transport to be used to communicate -with the MSN servers, actually MSN servers can communicate either through -direct connection using TCP/1863 or using TCP/80 by tunelling the protocol -inside HTTP POST requests. - -The classes of this module are structured as follow: -G{classtree BaseTransport}""" - -import gnet -import gnet.protocol -import msnp - -import logging -import gobject - -__all__=['ServerType', 'DirectConnection'] - -logger = logging.getLogger('Transport') - -class ServerType(object): - """""" - SWITCHBOARD = 'SB' - NOTIFICATION = 'NS' - -TransportError = gnet.IoError - -class BaseTransport(gobject.GObject): - """Abstract Base Class that modelize a connection to the MSN service, this - abstraction is used to build various transports that expose the same - interface, basically a transport is created using its constructor then it - simply emits signals when network events (or even abstracted network events) - occur, for example a Transport that successfully connected to the MSN - service will emit a connection-success signal, and when that transport - received a meaningful message it would emit a command-received signal. - - @ivar server: the server being used to connect to - @type server: tuple(host, port) - - @ivar server_type: the server that we are connecting to, either - Notification or switchboard. - @type server_type: L{ServerType} - - @ivar proxies: proxies that we can use to connect - @type proxies: dict(type => L{gnet.proxy.ProxyInfos}) - - @ivar transaction_id: the current transaction ID - @type transaction_id: integer - - - @cvar connection-failure: signal emitted when the connection fails - @type connection-failure: () - - @cvar connection-success: signal emitted when the connection succeed - @type connection-success: () - - @cvar connection-reset: signal emitted when the connection is being - reset - @type connection-reset: () - - @cvar connection-lost: signal emitted when the connection was lost - @type connection-lost: () - - @cvar command-received: signal emitted when a command is received - @type command-received: FIXME - - @cvar command-sent: signal emitted when a command was successfully - transmitted to the server - @type command-sent: FIXME - - @undocumented: __gsignals__""" - - __gsignals__ = { - "connection-failure" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "connection-success" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ()), - - "connection-reset" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ()), - - "connection-lost" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "command-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "command-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - } - - def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}): - """Connection initialization - - @param server: the server to connect to. - @type server: (host: string, port: integer) - - @param server_type: the server that we are connecting to, either - Notification or switchboard. - @type server_type: L{ServerType} - - @param proxies: proxies that we can use to connect - @type proxies: {type: string => L{gnet.network.ProxyInfos}}""" - gobject.GObject.__init__(self) - self.server = server - self.server_type = server_type - self.proxies = proxies - self._transaction_id = 0 - - @property - def transaction_id(self): - return self._transaction_id - - # Connection - def establish_connection(self): - """Connect to the server server""" - raise NotImplementedError - - def lose_connection(self): - """Disconnect from the server""" - raise NotImplementedError - - def reset_connection(self, server=None): - """Reset the connection - - @param server: when set, reset the connection and - connect to this new server - @type server: tuple(host, port)""" - raise NotImplementedError - - # Command Sending - def send_command(self, command, increment=True, callback=None, *cb_args): - """ - Sends a L{msnp.Command} to the server. - - @param command: command to send - @type command: L{msnp.Command} - - @param increment: if False, the transaction ID is not incremented - @type increment: bool - - @param callback: callback to be used when the command has been - transmitted - @type callback: callable - - @param cb_args: callback arguments - @type cb_args: Any, ... - """ - raise NotImplementedError - - def send_command_ex(self, command, arguments=(), payload=None, - increment=True, callback=None, *cb_args): - """ - Builds a command object then send it to the server. - - @param command: the command name, must be a 3 letters - uppercase string. - @type command: string - - @param arguments: command arguments - @type arguments: (string, ...) - - @param payload: payload data - @type payload: string - - @param increment: if False, the transaction ID is not incremented - @type increment: bool - - @param callback: callback to be used when the command has been - transmitted - @type callback: callable - - @param cb_args: callback arguments - @type cb_args: tuple - """ - cmd = msnp.Command() - cmd.build(command, self._transaction_id, payload, *arguments) - self.send_command(cmd, increment, callback, *cb_args) - return cmd - - def enable_ping(self): - pass - - def _increment_transaction_id(self): - """Increments the Transaction ID then return it. - - @rtype: integer""" - self._transaction_id += 1 - return self._transaction_id -gobject.type_register(BaseTransport) - - -class DirectConnection(BaseTransport): - """Implements a direct connection to the net using TCP/1863""" - - def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}): - BaseTransport.__init__(self, server, server_type, proxies) - - transport = gnet.io.TCPClient(server[0], server[1]) - transport.connect("notify::status", self.__on_status_change) - transport.connect("error", self.__on_error) - - receiver = gnet.parser.DelimiterParser(transport) - receiver.connect("received", self.__on_received) - - self._receiver = receiver - self._receiver.delimiter = "\r\n" - self._transport = transport - self.__pending_chunk = None - self.__resetting = False - self.__error = False - self.__png_timeout = None - - __init__.__doc__ = BaseTransport.__init__.__doc__ - - ### public commands - - def establish_connection(self): - logger.debug('<-> Connecting to %s:%d' % self.server ) - self._transport.open() - - def lose_connection(self): - self._transport.close() - if self.__png_timeout is not None: - gobject.source_remove(self.__png_timeout) - self.__png_timeout = None - - def reset_connection(self, server=None): - if server: - self._transport.set_property("host", server[0]) - self._transport.set_property("port", server[1]) - self.server = server - self.__resetting = True - if self.__png_timeout is not None: - gobject.source_remove(self.__png_timeout) - self.__png_timeout = None - self._transport.close() - self._transport.open() - - def send_command(self, command, increment=True, callback=None, *cb_args): - logger.debug('>>> ' + repr(command)) - our_cb_args = (command, callback, cb_args) - self._transport.send(str(command), self.__on_command_sent, *our_cb_args) - if increment: - self._increment_transaction_id() - - def enable_ping(self): - cmd = msnp.Command() - cmd.build("PNG", None) - self.send_command(cmd, False) - self.__png_timeout = None - return False - - def __on_command_sent(self, command, user_callback, user_cb_args): - self.emit("command-sent", command) - if user_callback: - user_callback(*user_cb_args) - - def __handle_ping_reply(self, command): - timeout = int(command.arguments[0]) - self.__png_timeout = gobject.timeout_add(timeout * 1000, self.enable_ping) - - ### callbacks - def __on_status_change(self, transport, param): - status = transport.get_property("status") - if status == gnet.IoStatus.OPEN: - if self.__resetting: - self.emit("connection-reset") - self.__resetting = False - self.emit("connection-success") - elif status == gnet.IoStatus.CLOSED: - if not self.__resetting and not self.__error: - self.emit("connection-lost", None) - self.__error = False - - def __on_error(self, transport, reason): - status = transport.get_property("status") - self.__error = True - if status == gnet.IoStatus.OPEN: - self.emit("connection-lost", reason) - else: - self.emit("connection-failure", reason) - - def __on_received(self, receiver, chunk): - cmd = msnp.Command() - if self.__pending_chunk: - chunk = self.__pending_chunk + "\r\n" + chunk - cmd.parse(chunk) - self.__pending_chunk = None - self._receiver.delimiter = "\r\n" - else: - cmd.parse(chunk) - if cmd.name in msnp.Command.INCOMING_PAYLOAD or \ - (cmd.is_error() and (cmd.arguments is not None) and len(cmd.arguments) > 0): - try: - payload_len = int(cmd.arguments[-1]) - except: - payload_len = 0 - if payload_len > 0: - self.__pending_chunk = chunk - self._receiver.delimiter = payload_len - return - logger.debug('<<< ' + repr(cmd)) - if cmd.name == 'QNG': - self.__handle_ping_reply(cmd) - else: - self.emit("command-received", cmd) -gobject.type_register(DirectConnection) - - -class HTTPPollConnection(BaseTransport): - """Implements an HTTP polling transport, basically it encapsulates the MSNP - commands into an HTTP request, and receive responses by polling a specific - url""" - def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}): - self._target_server = server - server = ("gateway.messenger.hotmail.com", 80) - BaseTransport.__init__(self, server, server_type, proxies) - self._setup_transport() - - self._command_queue = [] - self._waiting_for_response = False # are we waiting for a response - self._session_id = None - self.__error = False - - def _setup_transport(self): - server = self.server - proxies = self.proxies - if 'http' in proxies: - transport = gnet.protocol.HTTP(server[0], server[1], proxies['http']) - else: - transport = gnet.protocol.HTTP(server[0], server[1]) - transport.connect("response-received", self.__on_received) - transport.connect("request-sent", self.__on_sent) - transport.connect("error", self.__on_error) - self._transport = transport - - def establish_connection(self): - logger.debug('<-> Connecting to %s:%d' % self.server) - self._polling_source_id = gobject.timeout_add(5000, self._poll) - self.emit("connection-success") - - def lose_connection(self): - gobject.source_remove(self._polling_source_id) - del self._polling_source_id - if not self.__error: - self.emit("connection-lost", None) - self.__error = False - - def reset_connection(self, server=None): - if server: - self._target_server = server - self.emit("connection-reset") - - def send_command(self, command, increment=True, callback=None, *cb_args): - self._command_queue.append((command, increment, callback, cb_args)) - self._send_command() - - def _send_command(self): - if len(self._command_queue) == 0 or self._waiting_for_response: - return - command, increment = self._command_queue[0][0:2] - resource = "/gateway/gateway.dll" - headers = { - "Accept": "*/*", - "Accept-Language": "en-us", - #"User-Agent": "MSMSGS", - "Connection": "Keep-Alive", - "Pragma": "no-cache", - "Content-Type": "application/x-msn-messenger", - "Proxy-Connection": "Keep-Alive" - } - - str_command = str(command) - if self._session_id is None: - resource += "?Action=open&Server=%s&IP=%s" % (self.server_type, - self._target_server[0]) - elif command == None:# Polling the server for queued messages - resource += "?Action=poll&SessionID=%s" % self._session_id - str_command = "" - else: - resource += "?SessionID=%s" % self._session_id - - self._transport.request(resource, headers, str_command, "POST") - self._waiting_for_response = True - - if command is not None: - logger.debug('>>> ' + repr(command)) - - if increment: - self._increment_transaction_id() - - def _poll(self): - if not self._waiting_for_response: - self.send_command(None) - return True - - def __on_error(self, transport, reason): - self.__error = True - self.emit("connection-lost", reason) - - def __on_received(self, transport, http_response): - if http_response.status == 403: - self.emit("connection-lost", TransportError.PROXY_FORBIDDEN) - self.lose_connection() - if 'X-MSN-Messenger' in http_response.headers: - data = http_response.headers['X-MSN-Messenger'].split(";") - for elem in data: - key, value = [p.strip() for p in elem.split('=', 1)] - if key == 'SessionID': - self._session_id = value - elif key == 'GW-IP': - self.server = (value, self.server[1]) - self._setup_transport() - elif key == 'Session'and value == 'close': - #self.lose_connection() - pass - - self._waiting_for_response = False - - commands = http_response.body - while len(commands) != 0: - commands = self.__extract_command(commands) - - self._send_command() - - def __on_sent(self, transport, http_request): - if len(self._command_queue) == 0: - return - command, increment, callback, cb_args = self._command_queue.pop(0) - if command is not None: - if callback: - callback(*cb_args) - self.emit("command-sent", command) - - def __extract_command(self, data): - first, rest = data.split('\r\n', 1) - cmd = msnp.Command() - cmd.parse(first.strip()) - if cmd.name in msnp.Command.INCOMING_PAYLOAD or \ - (cmd.is_error() and (cmd.arguments is not None) and len(cmd.arguments) > 0): - try: - payload_len = int(cmd.arguments[-1]) - except: - payload_len = 0 - if payload_len > 0: - cmd.payload = rest[:payload_len].strip() - logger.debug('<<< ' + repr(cmd)) - self.emit("command-received", cmd) - return rest[payload_len:] - else: - logger.debug('<<< ' + repr(cmd)) - self.emit("command-received", cmd) - return rest - - - diff --git a/pymsn/pymsn/util/__init__.py b/pymsn/pymsn/util/__init__.py deleted file mode 100644 index a27e2007..00000000 --- a/pymsn/pymsn/util/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Convenience import modules""" diff --git a/pymsn/pymsn/util/debug.py b/pymsn/pymsn/util/debug.py deleted file mode 100644 index 3ec5bcbf..00000000 --- a/pymsn/pymsn/util/debug.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import struct - -"""Utility functions used for debug output processing""" - -def escape_string(string): - result = "" - for c in string: - v = ord(c) - if (v >= 32 and v <= 127) or c in ("\t", "\r", "\n"): - result += c - else: - result += "\\x%02x" % ord(c) - - return result - -def hexify_string(string): - result = "" - for i, c in enumerate(string): - if result: - if i % 16 != 0: - result += " " - else: - result += "\r\n" - result += "%02x" % ord(c) - return result - diff --git a/pymsn/pymsn/util/decorator.py b/pymsn/pymsn/util/decorator.py deleted file mode 100644 index 0d66c79f..00000000 --- a/pymsn/pymsn/util/decorator.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Useful decorators""" - -import sys -import warnings -import time - -import gobject - - -def decorator(function): - """decorator to be used on decorators, it preserves the docstring and - function attributes of functions to which it is applied.""" - def new_decorator(f): - g = function(f) - g.__name__ = f.__name__ - g.__doc__ = f.__doc__ - g.__dict__.update(f.__dict__) - return g - new_decorator.__name__ = function.__name__ - new_decorator.__doc__ = function.__doc__ - new_decorator.__dict__.update(function.__dict__) - return new_decorator - - -def rw_property(function): - """This decorator implements read/write properties, as follow: - - @rw_property - def my_property(): - "Documentation" - def fget(self): - return self._my_property - def fset(self, value): - self._my_property = value - return locals() - """ - return property(**function()) - -@decorator -def deprecated(func): - """This is a decorator which can be used to mark functions as deprecated. - It will result in a warning being emitted when the function is used.""" - def new_function(*args, **kwargs): - warnings.warn("Call to deprecated function %s." % func.__name__, - category=DeprecationWarning) - return func(*args, **kwargs) - return new_function - -@decorator -def unstable(func): - """This is a decorator which can be used to mark functions as unstable API - wise. It will result in a warning being emitted when the function is used.""" - def new_function(*args, **kwargs): - warnings.warn("Call to unstable API function %s." % func.__name__, - category=FutureWarning) - return func(*args, **kwargs) - return new_function - -@decorator -def async(func): - """Make a function mainloop friendly. the function will be called at the - next mainloop idle state.""" - def new_function(*args, **kwargs): - def async_function(): - func(*args, **kwargs) - return False - gobject.idle_add(async_function) - return new_function - -class throttled(object): - """Throttle the calls to a function by queueing all the calls that happen - before the minimum delay.""" - - def __init__(self, min_delay, queue): - self._min_delay = min_delay - self._queue = queue - self._last_call_time = None - - def __call__(self, func): - def process_queue(): - if len(self._queue) != 0: - func, args, kwargs = self._queue.pop(0) - self._last_call_time = time.time() * 1000 - func(*args, **kwargs) - return False - - def new_function(*args, **kwargs): - now = time.time() * 1000 - if self._last_call_time is None or \ - now - self._last_call_time >= self._min_delay: - self._last_call_time = now - func(*args, **kwargs) - else: - self._queue.append((func, args, kwargs)) - last_call_delta = now - self._last_call_time - process_queue_timeout = int(self._min_delay * len(self._queue) - last_call_delta) - gobject.timeout_add(process_queue_timeout, process_queue) - - new_function.__name__ = func.__name__ - new_function.__doc__ = func.__doc__ - new_function.__dict__.update(func.__dict__) - return new_function diff --git a/pymsn/pymsn/util/element_tree.py b/pymsn/pymsn/util/element_tree.py deleted file mode 100644 index 0e83c907..00000000 --- a/pymsn/pymsn/util/element_tree.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""ElementTree independent from the available distribution""" - -try: - from xml.etree.cElementTree import * -except ImportError: - try: - from cElementTree import * - except ImportError: - from elementtree.ElementTree import * - -__all__ = ["XMLTYPE", "XMLResponse"] - -import iso8601 - -class XMLTYPE(object): - - class bool(object): - @staticmethod - def encode(boolean): - if boolean: - return "true" - return "false" - - @staticmethod - def decode(boolean_str): - false_set = ("false", "f", "no", "n", "0", "") - if str(boolean_str).strip().lower() not in false_set: - return True - return False - - class int(object): - @staticmethod - def encode(integer): - return str(integer) - - @staticmethod - def decode(integer_str): - try: - return int(integer_str) - except ValueError: - return 0 - - class datetime(object): - @staticmethod - def encode(datetime): - return datetime.isoformat() - - @staticmethod - def decode(date_str): - result = iso8601.parse_date(date_str.strip()) - return result.replace(tzinfo=None) # FIXME: do not disable the timezone - -class _Element(object): - def __init__(self, element, ns_shorthands): - self.element = element - self.ns_shorthands = ns_shorthands.copy() - - def __getattr__(self, name): - return getattr(self.element, name) - - def __getitem__(self, name): - return self.element[name] - - def __iter__(self): - for node in self.element: - yield _Element(node, self.ns_shorthands) - - def __contains__(self, node): - return node in self.element - - def __repr__(self): - return "" % (self.element.tag,) - - def _process_path(self, path): - for sh, ns in self.ns_shorthands.iteritems(): - path = path.replace("/%s:" % sh, "/{%s}" % ns) - if path.startswith("%s:" % sh): - path = path.replace("%s:" % sh, "{%s}" % ns, 1) - return path - - def find(self, path): - path = self._process_path(path) - node = self.element.find(path) - if node is None: - return None - return _Element(node, self.ns_shorthands) - - def findall(self, path): - path = self._process_path(path) - - result = [] - nodes = self.element.findall(path) - for node in nodes: - result.append(_Element(node, self.ns_shorthands)) - return result - - def findtext(self, path, type=None): - result = self.find(path) - if result is None: - return "" - result = result.text - - if type is None: - return result - return getattr(XMLTYPE, type).decode(result) - -class XMLResponse(object): - - def __init__(self, data, ns_shorthands={}): - try: - tree = self._parse(data) - self.tree = _Element(tree, ns_shorthands) - except: - self.tree = None - - def __getitem__(self, name): - return self.tree[name] - - def find(self, path): - return self.tree.find(path) - - def findall(self, path): - return self.tree.findall(path) - - def findtext(self, path, type=None): - return self.tree.findtext(path, type) - - def is_valid(self): - return self.tree is not None - - def _parse(self, data): - pass diff --git a/pymsn/pymsn/util/guid.py b/pymsn/pymsn/util/guid.py deleted file mode 100644 index b9cd20b3..00000000 --- a/pymsn/pymsn/util/guid.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -import random - -def generate_guid(): - bytes = [random.randrange(256) for i in range(16)] - - data1 = ("%02X" * 4) % tuple(bytes[0:4]) - data2 = ("%02X" * 2) % tuple(bytes[4:6]) - data3 = ("%02X" * 2) % tuple(bytes[6:8]) - data4 = ("%02X" * 2) % tuple(bytes[8:10]) - data5 = ("%02X" * 6) % tuple(bytes[10:]) - - data3 = "4" + data3[1:] - - return "%s-%s-%s-%s-%s" % (data1, data2, data3, data4, data5) diff --git a/pymsn/pymsn/util/iso8601/LICENSE b/pymsn/pymsn/util/iso8601/LICENSE deleted file mode 100644 index 5ca93dae..00000000 --- a/pymsn/pymsn/util/iso8601/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2007 Michael Twomey - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pymsn/pymsn/util/iso8601/README b/pymsn/pymsn/util/iso8601/README deleted file mode 100644 index 5ec9d455..00000000 --- a/pymsn/pymsn/util/iso8601/README +++ /dev/null @@ -1,26 +0,0 @@ -A simple package to deal with ISO 8601 date time formats. - -ISO 8601 defines a neutral, unambiguous date string format, which also -has the property of sorting naturally. - -e.g. YYYY-MM-DDTHH:MM:SSZ or 2007-01-25T12:00:00Z - -Currently this covers only the most common date formats encountered, not -all of ISO 8601 is handled. - -Currently the following formats are handled: - -* 2006-01-01T00:00:00Z -* 2006-01-01T00:00:00[+-]00:00 - -I'll add more as I encounter them in my day to day life. Patches with -new formats and tests will be gratefully accepted of course :) - -References: - -* http://www.cl.cam.ac.uk/~mgk25/iso-time.html - simple overview - -* http://hydracen.com/dx/iso8601.htm - more detailed enumeration of - valid formats. - -See the LICENSE file for the license this package is released under. diff --git a/pymsn/pymsn/util/iso8601/__init__.py b/pymsn/pymsn/util/iso8601/__init__.py deleted file mode 100644 index e72e3563..00000000 --- a/pymsn/pymsn/util/iso8601/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from iso8601 import * diff --git a/pymsn/pymsn/util/iso8601/iso8601.py b/pymsn/pymsn/util/iso8601/iso8601.py deleted file mode 100644 index 68ed05a0..00000000 --- a/pymsn/pymsn/util/iso8601/iso8601.py +++ /dev/null @@ -1,102 +0,0 @@ -"""ISO 8601 date time string parsing - -Basic usage: ->>> import iso8601 ->>> iso8601.parse_date("2007-01-25T12:00:00Z") -datetime.datetime(2007, 1, 25, 12, 0, tzinfo=) ->>> - -""" - -from datetime import datetime, timedelta, tzinfo -import re - -__all__ = ["parse_date", "ParseError"] - -# Adapted from http://delete.me.uk/2005/03/iso8601.html -ISO8601_REGEX = re.compile(r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" - r"((?P.)(?P[0-9]{2}):(?P[0-9]{2})(:(?P[0-9]{2})(\.(?P[0-9]+))?)?" - r"(?PZ|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" -) -TIMEZONE_REGEX = re.compile("(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})") - -class ParseError(Exception): - """Raised when there is a problem parsing a date string""" - -# Yoinked from python docs -ZERO = timedelta(0) -class Utc(tzinfo): - """UTC - - """ - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO -UTC = Utc() - -class FixedOffset(tzinfo): - """Fixed offset in hours and minutes from UTC - - """ - def __init__(self, offset_hours, offset_minutes, name): - self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - def __repr__(self): - return "" % self.__name - -def parse_timezone(tzstring, default_timezone=UTC): - """Parses ISO 8601 time zone specs into tzinfo offsets - - """ - if tzstring == "Z": - return default_timezone - # This isn't strictly correct, but it's common to encounter dates without - # timezones so I'll assume the default (which defaults to UTC). - # Addresses issue 4. - if tzstring is None: - return default_timezone - m = TIMEZONE_REGEX.match(tzstring) - prefix, hours, minutes = m.groups() - hours, minutes = int(hours), int(minutes) - if prefix == "-": - hours = -hours - minutes = -minutes - return FixedOffset(hours, minutes, tzstring) - -def parse_date(datestring, default_timezone=UTC): - """Parses ISO 8601 dates into datetime objects - - The timezone is parsed from the date string. However it is quite common to - have dates without a timezone (not strictly correct). In this case the - default timezone specified in default_timezone is used. This is UTC by - default. - """ - if not isinstance(datestring, basestring): - raise ParseError("Expecting a string %r" % datestring) - m = ISO8601_REGEX.match(datestring) - if not m: - raise ParseError("Unable to parse date string %r" % datestring) - groups = m.groupdict() - tz = parse_timezone(groups["timezone"], default_timezone=UTC) - if groups["fraction"] is None: - groups["fraction"] = 0 - frac = int(groups["fraction"]) - groups["fraction"] = int (frac / 10 ** (len(str(frac)) - 6)) - return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]), - int(groups["hour"]), int(groups["minute"]), int(groups["second"]), - int(groups["fraction"]), tz) diff --git a/pymsn/pymsn/util/iso8601/test_iso8601.py b/pymsn/pymsn/util/iso8601/test_iso8601.py deleted file mode 100644 index 9534f959..00000000 --- a/pymsn/pymsn/util/iso8601/test_iso8601.py +++ /dev/null @@ -1,106 +0,0 @@ -import iso8601 - -def test_iso8601_regex(): - assert iso8601.ISO8601_REGEX.match("2006-10-11T00:14:33Z") - -def test_timezone_regex(): - assert iso8601.TIMEZONE_REGEX.match("+01:00") - assert iso8601.TIMEZONE_REGEX.match("+00:00") - assert iso8601.TIMEZONE_REGEX.match("+01:20") - assert iso8601.TIMEZONE_REGEX.match("-01:00") - -def test_parse_date(): - d = iso8601.parse_date("2006-10-20T15:34:56Z") - assert d.year == 2006 - assert d.month == 10 - assert d.day == 20 - assert d.hour == 15 - assert d.minute == 34 - assert d.second == 56 - assert d.tzinfo == iso8601.UTC - -def test_parse_date_fraction(): - d = iso8601.parse_date("2006-10-20T15:34:56.123Z") - assert d.year == 2006 - assert d.month == 10 - assert d.day == 20 - assert d.hour == 15 - assert d.minute == 34 - assert d.second == 56 - assert d.microsecond == 123 - assert d.tzinfo == iso8601.UTC - -def test_parse_date_fraction_2(): - """From bug 6 - - """ - d = iso8601.parse_date("2007-5-7T11:43:55.328Z'") - assert d.year == 2007 - assert d.month == 5 - assert d.day == 7 - assert d.hour == 11 - assert d.minute == 43 - assert d.second == 55 - assert d.microsecond == 328 - assert d.tzinfo == iso8601.UTC - -def test_parse_date_tz(): - d = iso8601.parse_date("2006-10-20T15:34:56.123+02:30") - assert d.year == 2006 - assert d.month == 10 - assert d.day == 20 - assert d.hour == 15 - assert d.minute == 34 - assert d.second == 56 - assert d.microsecond == 123 - assert d.tzinfo.tzname(None) == "+02:30" - offset = d.tzinfo.utcoffset(None) - assert offset.days == 0 - assert offset.seconds == 60 * 60 * 2.5 - -def test_parse_invalid_date(): - try: - iso8601.parse_date(None) - except iso8601.ParseError: - pass - else: - assert 1 == 2 - -def test_parse_invalid_date2(): - try: - iso8601.parse_date("23") - except iso8601.ParseError: - pass - else: - assert 1 == 2 - -def test_parse_no_timezone(): - """issue 4 - Handle datetime string without timezone - - This tests what happens when you parse a date with no timezone. While not - strictly correct this is quite common. I'll assume UTC for the time zone - in this case. - """ - d = iso8601.parse_date("2007-01-01T08:00:00") - assert d.year == 2007 - assert d.month == 1 - assert d.day == 1 - assert d.hour == 8 - assert d.minute == 0 - assert d.second == 0 - assert d.microsecond == 0 - assert d.tzinfo == iso8601.UTC - -def test_space_separator(): - """Handle a separator other than T - - """ - d = iso8601.parse_date("2007-06-23 06:40:34.00Z") - assert d.year == 2007 - assert d.month == 6 - assert d.day == 23 - assert d.hour == 6 - assert d.minute == 40 - assert d.second == 34 - assert d.microsecond == 0 - assert d.tzinfo == iso8601.UTC diff --git a/pymsn/pymsn/util/queue.py b/pymsn/pymsn/util/queue.py deleted file mode 100644 index 88a3ffeb..00000000 --- a/pymsn/pymsn/util/queue.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Useful queues""" - -__all__ = ['PriorityQueue', 'LastElementQueue'] - -import bisect - -class PriorityQueue(object): - def __init__(self, iterable=()): - self.queue = list(iterable) - - def add(self, item, priority=0): - bisect.insort(self.queue, (priority, item)) - - def append(self, item): - self.add(item) - - def pop(self, n): - return self.queue.pop(n)[1] - - def __len__(self): - return len(self.queue) - - @property - def empty(self): - return len(self.queue) == 0 - -class LastElementQueue(object): - def __init__(self, iterable=()): - self.queue = list(iterable)[-1:] - - def append(self, item): - self.queue = [item] - - def pop(self, n): - return self.queue.pop(n) - - def __len__(self): - return len(self.queue) - - @property - def empty(self): - return len(self.queue) == 0 - - - diff --git a/pymsn/pymsn/util/string_io.py b/pymsn/pymsn/util/string_io.py deleted file mode 100644 index ec775a21..00000000 --- a/pymsn/pymsn/util/string_io.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Imports the fastest StringIO available""" - -try: - from cStringIO import * -except ImportError: - from StringIO import * diff --git a/pymsn/pymsn/util/weak.py b/pymsn/pymsn/util/weak.py deleted file mode 100644 index 6e612513..00000000 --- a/pymsn/pymsn/util/weak.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# - -"""Some missing weak refs""" - -from weakref import WeakKeyDictionary -from sets import Set - -__all__ = ['WeakSet'] - -class WeakSet(Set): - def __init__(self, iterable=None): - Set.__init__(self) - self._data = WeakKeyDictionary() - if iterable is not None: - self._update(iterable) - - def __hash__(self): - raise TypeError, "Can't hash a WeakSet." diff --git a/pymsn/setup.py b/pymsn/setup.py deleted file mode 100644 index fbe57165..00000000 --- a/pymsn/setup.py +++ /dev/null @@ -1,89 +0,0 @@ -from distutils.core import setup -from doc import make_doc_command, BuildAllDocCommand -import os - -import pymsn - - -# Metadata -NAME = "pymsn" -VERSION = pymsn.__version__ -DESCRIPTION = "Python msn client library" -AUTHOR = "Ali Sabil" -AUTHOR_EMAIL = "ali.sabil@gmail.com" -URL = pymsn.__url__ -LICENSE = pymsn.__license__ - -# Documentation -doc_commands = { - 'doc_user_api': make_doc_command( - name='user_api', - description='the pymsn user API documentation', - config='doc/user-api.conf'), - 'doc': BuildAllDocCommand -} -for name in doc_commands.keys(): - if name != 'doc': - BuildAllDocCommand.sub_commands.append((name, None)) - -# Compile the list of packages available, because distutils doesn't have -# an easy way to do this. -def path_split(path, result=None): - """ - Split a pathname into components (the opposite of os.path.join) in a - platform-neutral way. - """ - if result is None: - result = [] - head, tail = os.path.split(path) - if head == '': - return [tail] + result - if head == path: - return result - return path_split(head, [tail] + result) - -packages, data_files = [], [] -root_dir = os.path.dirname(__file__) -pieces = path_split(root_dir) -if pieces[-1] == '': - len_root_dir = len(pieces) - 1 -else: - len_root_dir = len(pieces) - -pymsn_dir = os.path.join(root_dir, 'pymsn') -for dirpath, dirnames, filenames in os.walk(pymsn_dir): - # Ignore dirnames that start with '.' - for i, dirname in enumerate(dirnames): - if dirname.startswith('.'): - del dirnames[i] - if '__init__.py' in filenames: - packages.append('.'.join(path_split(dirpath)[len_root_dir:])) - elif filenames: - data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) - -# Setup -setup(name=NAME, - version=VERSION, - description=DESCRIPTION, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - url=URL, - license=LICENSE, - platforms=["any"], - packages=packages, - cmdclass=doc_commands, - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Telecommunications Industry', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Operating System :: POSIX', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python', - 'Topic :: Communications :: Chat', - 'Topic :: Communications :: Telephony', - 'Topic :: Internet', - 'Topic :: Software Development :: Libraries :: Python Modules' - ]) diff --git a/pymsn/test.py b/pymsn/test.py deleted file mode 100755 index ba5fffd1..00000000 --- a/pymsn/test.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python - -import pymsn -import pymsn.event - - -import logging -import gobject - -logging.basicConfig(level=logging.DEBUG) - -finished = False - -def get_proxies(): - import urllib - proxies = urllib.getproxies() - result = {} - if 'https' not in proxies and \ - 'http' in proxies: - url = proxies['http'].replace("http://", "https://") - result['https'] = pymsn.Proxy(url) - for type, url in proxies.items(): - if type == 'no': continue - if type == 'https' and url.startswith('http://'): - url = url.replace('http://', 'https://', 1) - result[type] = pymsn.Proxy(url) - return result - - -class ClientEvents(pymsn.event.ClientEventInterface): - def on_client_state_changed(self, state): - if state == pymsn.event.ClientState.CLOSED: - self._client.quit() - elif state == pymsn.event.ClientState.OPEN: - self._client.profile.display_name = "Kimbix" - self._client.profile.presence = pymsn.Presence.ONLINE - self._client.profile.current_media = ("I listen to", "Nothing") - for contact in self._client.address_book.contacts: - print contact - #self._client.profile.personal_message = "Testing pymsn, and freeing the pandas!" - gobject.timeout_add(5000, self._client.start_conversation) - - def on_client_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - -class AnnoyingConversation(pymsn.event.ConversationEventInterface): - def on_conversation_user_joined(self, contact): - gobject.timeout_add(5000, self.annoy_user) - - def annoy_user(self): - msg = "Let's free the pandas ! (testing pymsn)" - formatting = pymsn.TextFormat("Comic Sans MS", - pymsn.TextFormat.UNDERLINE | pymsn.TextFormat.BOLD, - 'FF0000') - self._client.send_text_message(pymsn.ConversationMessage(msg, formatting)) -# self._client.send_nudge() -# self._client.send_typing_notification() - return True - - def on_conversation_user_typing(self, contact): - pass - - def on_conversation_message_received(self, sender, message): - pass - - def on_conversation_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - -class Client(pymsn.Client): - def __init__(self, account, quit, http_mode=False): - server = ('messenger.hotmail.com', 1863) - self.quit = quit - self.account = account - if http_mode: - from pymsn.transport import HTTPPollConnection - pymsn.Client.__init__(self, server, get_proxies(), HTTPPollConnection) - else: - pymsn.Client.__init__(self, server, proxies = get_proxies()) - self._event_handler = ClientEvents(self) - gobject.idle_add(self._connect) - - def _connect(self): - self.login(*self.account) - return False - - def start_conversation(self): - contacts = self.address_book.contacts.\ - search_by_presence(pymsn.Presence.ONLINE) - for c in self.address_book.contacts: - if c.account == "@hotmail.com": - print "Fetching space of : %s with cid %s\n" % (c.display_name, c.cid) - self.spaces_service.get_contact_card(c) - - if len(contacts) == 0: - print "No online contacts" - return True - else: - for contact in contacts: - if contact.account == "johann.prieur@gmail.com": - print "Inviting %s for a conversation" % contact.display_name - self.conv = pymsn.Conversation(self, [contact]) - self._convo_events = AnnoyingConversation(self.conv) - return False - -def main(): - import sys - import getpass - import signal - - if "--http" in sys.argv: - http_mode = True - sys.argv.remove('--http') - else: - http_mode = False - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - passwd = getpass.getpass('Password: ') - else: - passwd = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - def quit(): - mainloop.quit() - - def sigterm_cb(): - gobject.idle_add(quit) - - signal.signal(signal.SIGTERM, sigterm_cb) - - n = Client((account, passwd), quit, http_mode) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - quit() - -if __name__ == '__main__': - main() From 2ce0b54dbb1010207d2a135ace7251bdbef2b98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 6 Apr 2009 21:53:11 +0100 Subject: [PATCH 007/147] Added pymsn as submodule --- .gitmodules | 3 +++ pymsn | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 pymsn diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..782d18e5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pymsn"] + path = pymsn + url = git://git.collabora.co.uk/git/user/kakaroto/pymsn.git diff --git a/pymsn b/pymsn new file mode 160000 index 00000000..ede8699d --- /dev/null +++ b/pymsn @@ -0,0 +1 @@ +Subproject commit ede8699d1e7ebe17afe8fd9435ad1df21a5102e1 From ffc428e72195da1bb6cb7545075bd81267822994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 6 Apr 2009 22:59:56 +0100 Subject: [PATCH 008/147] Modified pymsn submodule to point to github instead of collabora --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 782d18e5..953fa459 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "pymsn"] path = pymsn - url = git://git.collabora.co.uk/git/user/kakaroto/pymsn.git + url = git@github.com:Kjir/pymsn.git From bf0d4e8ca914c14fb8a57da72533d69b7d015530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 6 Apr 2009 23:08:58 +0100 Subject: [PATCH 009/147] Corrected setup of submodule --- pymsn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymsn b/pymsn index ede8699d..d4bf6745 160000 --- a/pymsn +++ b/pymsn @@ -1 +1 @@ -Subproject commit ede8699d1e7ebe17afe8fd9435ad1df21a5102e1 +Subproject commit d4bf6745c2f71e6e5c88d434bfe5a3e5fe0e6499 From 95579d942f9d6de3e95d1e1cce28bbae26b00243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 7 Apr 2009 01:45:18 +0100 Subject: [PATCH 010/147] Update reference to pymsn submodule --- pymsn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymsn b/pymsn index d4bf6745..290d4151 160000 --- a/pymsn +++ b/pymsn @@ -1 +1 @@ -Subproject commit d4bf6745c2f71e6e5c88d434bfe5a3e5fe0e6499 +Subproject commit 290d4151f178891d55233e9f48c2ad233a186176 From c7064516df47df485ad0b81b3e3ffba49670a397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 7 Apr 2009 02:15:56 +0100 Subject: [PATCH 011/147] Display the progress of the login process --- amsn2/gui/front_ends/curses/login.py | 44 ++-------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index d4ba38a5..59eef646 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -34,7 +34,7 @@ def value(self): return self._password def _validateInput(self, ch): - if ch in (curses.KEY_BACKSPACE, curses.ascii.BEL): + if ch in (curses.KEY_BACKSPACE, curses.ascii.BS): self._password = self._password[0:-1] return ch elif curses.ascii.isprint(ch): @@ -94,45 +94,5 @@ def onConnecting(self, progress, message): self._password_t = None self._win.clear() - self._win.addstr(10, 25, "Connecting...", curses.A_BOLD | curses.A_STANDOUT) - self._win.refresh() - - def onConnected(self): - self._username_t = None - self._password_t = None - self._win.clear() - - self._win.addstr(10, 25, "Connected...", curses.A_BOLD | curses.A_STANDOUT) - self._win.refresh() - - def onAuthenticating(self): - self._username_t = None - self._password_t = None - self._win.clear() - - self._win.addstr(10, 25, "Authenticating...", curses.A_BOLD | curses.A_STANDOUT) - self._win.refresh() - - def onAuthenticated(self): - self._username_t = None - self._password_t = None - self._win.clear() - - self._win.addstr(10, 25, "Authenticated...", curses.A_BOLD | curses.A_STANDOUT) - self._win.refresh() - - def onSynchronizing(self): - self._username_t = None - self._password_t = None - self._win.clear() - - self._win.addstr(10, 25, "Fetching contact list...", curses.A_BOLD | curses.A_STANDOUT) - self._win.refresh() - - def onSynchronized(self): - self._username_t = None - self._password_t = None - self._win.clear() - - self._win.addstr(10, 25, "Synchronized!", curses.A_BOLD | curses.A_STANDOUT) + self._win.addstr(10, 25, message, curses.A_BOLD | curses.A_STANDOUT) self._win.refresh() From 52f975a76f51f47d45ad0155b360ca9927887fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 7 Apr 2009 02:16:25 +0100 Subject: [PATCH 012/147] Initialize colors for ncurses --- amsn2/gui/front_ends/curses/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index 84239167..036311ac 100644 --- a/amsn2/gui/front_ends/curses/main.py +++ b/amsn2/gui/front_ends/curses/main.py @@ -8,6 +8,7 @@ def __init__(self, amsn_core): def show(self): self._stdscr = curses.initscr() + curses.start_color() curses.noecho() curses.cbreak() self._stdscr.keypad(1) From e6db32e32b29a765076b3c7d974b4a32c904028c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 7 Apr 2009 02:41:22 +0100 Subject: [PATCH 013/147] curses: Login mask is now relative to window width and height --- amsn2/gui/front_ends/curses/login.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index 59eef646..3c40377b 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -53,10 +53,17 @@ def __init__(self, amsn_core, parent): self._amsn_core = amsn_core self.switch_to_profile(None) self._stdscr = parent._stdscr - self._win = curses.newwin(20, 100, 5, 5) + + (y, x) = self._stdscr.getmaxyx() + wy = int(y * 0.8) + wx = int(x * 0.8) + sy = int((y - wy)/2) + sx = int((x - wx)/2) + self._win = curses.newwin(wy, wx, sy, sx) def show(self): self._win.border() + self._win.standout() self._win.addstr(5, 5, "Account : ", curses.A_BOLD) self._username_t = TextBox(self._win, 5, 17, self._username) From 8fc791b44ec895669edcdde21afb22d8815523a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 7 Apr 2009 02:42:37 +0100 Subject: [PATCH 014/147] core: Corrected spelling of login messages --- amsn2/core/amsn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index a8f412b1..2971e184 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -137,10 +137,10 @@ def connectionStateChanged(self, profile, state): { pymsn.event.ClientState.CONNECTING : 'Connecting to server...', pymsn.event.ClientState.CONNECTED : 'Connected', - pymsn.event.ClientState.AUTHENTICATING : 'Authentificating...', + pymsn.event.ClientState.AUTHENTICATING : 'Authenticating...', pymsn.event.ClientState.AUTHENTICATED : 'Password accepted', pymsn.event.ClientState.SYNCHRONIZING : 'Please wait while your contact list\nis being downloaded...', - pymsn.event.ClientState.SYNCHRONIZED : 'Contact list downloaded successfully\nHappy Chatting' + pymsn.event.ClientState.SYNCHRONIZED : 'Contact list downloaded successfully.\nHappy Chatting' } if state in status_str: From 052c633b89208a9aaeaaad7c6afce7bbaeec0fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 7 Apr 2009 03:16:46 +0100 Subject: [PATCH 015/147] curses: Added colors to the login windows --- amsn2/gui/front_ends/curses/login.py | 3 ++- amsn2/gui/front_ends/curses/main.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index 3c40377b..c39e0d37 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -5,6 +5,7 @@ class TextBox(object): def __init__(self, win, y, x, txt): self._win = win.derwin(1, 30, y, x) + self._win.bkgd(' ', curses.color_pair(0)) self._win.clear() self._txtbox = curses.textpad.Textbox(self._win) self._txtbox.stripspaces = True @@ -63,7 +64,7 @@ def __init__(self, amsn_core, parent): def show(self): self._win.border() - self._win.standout() + self._win.bkgd(' ', curses.color_pair(1)) self._win.addstr(5, 5, "Account : ", curses.A_BOLD) self._username_t = TextBox(self._win, 5, 17, self._username) diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index 036311ac..b3b73628 100644 --- a/amsn2/gui/front_ends/curses/main.py +++ b/amsn2/gui/front_ends/curses/main.py @@ -8,7 +8,7 @@ def __init__(self, amsn_core): def show(self): self._stdscr = curses.initscr() - curses.start_color() + self.__init_colors() curses.noecho() curses.cbreak() self._stdscr.keypad(1) @@ -31,3 +31,8 @@ def setTitle(self,title): def setMenu(self,menu): pass + def __init_colors(self): + curses.start_color() + curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) + curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_WHITE) + From 5641a40af8e4912b48f322a8903762adc3f02319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Wed, 8 Apr 2009 01:14:20 +0100 Subject: [PATCH 016/147] Started to display contact list --- amsn2/gui/front_ends/curses/contact_list.py | 42 +++++++++++++++++++-- amsn2/gui/front_ends/curses/login.py | 6 ++- amsn2/gui/front_ends/curses/main.py | 1 - 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index 4a7b87a1..6e23fef6 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -45,13 +45,18 @@ def __init__(self, amsn_core, parent): self.groups = {} self.contacts = {} self._stdscr = parent._stdscr - self._win = curses.newwin(100, 100, 3, 3) + (y,x) = self._stdscr.getmaxyx() + # TODO: Use a pad instead + self._win = curses.newwin(y, int(0.2*x), 0, 0) + self._win.bkgd(curses.color_pair(1)) + self._clwidget = aMSNContactListWidget(amsn_core, self) def show(self): - pass + self._win.refresh() def hide(self): - pass + self._stdscr.clear() + self._stdscr.refresh() def contactStateChange(self, contact): for group in contact.groups: @@ -128,3 +133,34 @@ def __update_view(self): self._win.refresh() +class aMSNContactListWidget(base.aMSNContactListWidget): + + def __init__(self, amsn_core, parent): + super(aMSNContactListWidget, self).__init__(amsn_core, parent) + self._groups = {} + self._win = parent._win + self._stdscr = parent._stdscr + + def contactListUpdated(self, clView): + # TODO: Implement it to sort groups and handle add/delete + for g in clView.group_ids: + self._groups[g] = None + + def groupUpdated(self, gView): + self._groups[gView.uid] = gView + self.__updateGroups() + # TODO: Implement something useful + import sys + print >> sys.stderr, gView.name + + def contactUpdated(self, cView): + pass + + def __updateGroups(self): + self._win.clear() + self._win.move(0,0) + for g in self._groups: + if self._groups[g] is not None: + self._win.insstr(str(self._groups[g].name)) + self._win.insertln() + self._win.refresh() diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index c39e0d37..849e032e 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -76,13 +76,15 @@ def show(self): self._username_t.edit() self._password_t.edit() + curses.curs_set(0) self.signin() def hide(self): self._username_t = None self._password_t = None - self._win.clear() - self._win.refresh() + self._win = None + self._stdscr.clear() + self._stdscr.refresh() def switch_to_profile(self, profile): self.current_profile = profile diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index b3b73628..a133cefb 100644 --- a/amsn2/gui/front_ends/curses/main.py +++ b/amsn2/gui/front_ends/curses/main.py @@ -33,6 +33,5 @@ def setMenu(self,menu): def __init_colors(self): curses.start_color() - curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_WHITE) From b6b856737d83b9b5b13146445115b6926a8f6e69 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 13 Apr 2009 16:06:45 +0200 Subject: [PATCH 017/147] Merge commit 'origin/master'; commit 'Kjir/master' From 3bd523eac9bb751d0bc7c31fdc35bcc977059e37 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 13 Apr 2009 16:57:54 +0200 Subject: [PATCH 018/147] fix submodule path --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 953fa459..127d57a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "pymsn"] path = pymsn - url = git@github.com:Kjir/pymsn.git + url = git://github.com/Kjir/pymsn.git From c41567a9083a12127f3828c7d4592f3d7bec223c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 13 Apr 2009 20:41:17 +0100 Subject: [PATCH 019/147] Updated README with instructions for the pymsn submodule --- README | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README b/README index 05c19b83..22f4a6f4 100644 --- a/README +++ b/README @@ -5,6 +5,9 @@ python-pyopenssl python-crypto maybe some other stuff... +Before launching amsn2 you have to fetch the submodules (pymsn). Instructions can be found on the aMSN forum: +http://www.amsn-project.net/forums/viewtopic.php?t=5994 + If you launch ./amsn2.py and it gives an error, that error probably tells you which dependency you need... You can type ./amsn2.py --help for more info... and the front ends can be selected with -f : ./amsn2.py -f efl From 5476ecdbdb207cd5fb6a06bc919aa894d7e895d4 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 13 Apr 2009 17:17:02 +0200 Subject: [PATCH 020/147] remove trailing ; --- amsn2/gui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/gui/gui.py b/amsn2/gui/gui.py index a3e87094..970f7182 100644 --- a/amsn2/gui/gui.py +++ b/amsn2/gui/gui.py @@ -27,7 +27,7 @@ def registerFrontEnd(name, module): @staticmethod def listFrontEnds(): - return GUIManager.front_ends.keys(); + return GUIManager.front_ends.keys() @staticmethod def frontEndExists(front_end): From fa38c005114b84d2073b7072954f7ca50957c068 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 13 Apr 2009 21:49:55 +0200 Subject: [PATCH 021/147] make amsn2.py executable --- amsn2.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 amsn2.py diff --git a/amsn2.py b/amsn2.py old mode 100644 new mode 100755 From 80eae6e46f623e15a981ba7fec99cd290f28538c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 14 Apr 2009 03:41:17 +0800 Subject: [PATCH 022/147] Updated README with instructions for the pymsn submodule Signed-off-by: Boris Faure --- README | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README b/README index 05c19b83..22f4a6f4 100644 --- a/README +++ b/README @@ -5,6 +5,9 @@ python-pyopenssl python-crypto maybe some other stuff... +Before launching amsn2 you have to fetch the submodules (pymsn). Instructions can be found on the aMSN forum: +http://www.amsn-project.net/forums/viewtopic.php?t=5994 + If you launch ./amsn2.py and it gives an error, that error probably tells you which dependency you need... You can type ./amsn2.py --help for more info... and the front ends can be selected with -f : ./amsn2.py -f efl From 1711a2c0578079ae52f49e43b0a2e00f9b64e476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 13 Apr 2009 22:50:40 +0100 Subject: [PATCH 023/147] curses: Added a new color_pair --- amsn2/gui/front_ends/curses/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index a133cefb..e308bb58 100644 --- a/amsn2/gui/front_ends/curses/main.py +++ b/amsn2/gui/front_ends/curses/main.py @@ -34,4 +34,5 @@ def setMenu(self,menu): def __init_colors(self): curses.start_color() curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_WHITE) + curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_BLUE) From 7b2ac44b7ce02e01a091a1bf88b616257395b7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Mon, 13 Apr 2009 22:50:56 +0100 Subject: [PATCH 024/147] Implemented visualization of contacts --- amsn2/gui/front_ends/curses/contact_list.py | 160 +++++--------------- 1 file changed, 42 insertions(+), 118 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index 6e23fef6..8843a25d 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -1,54 +1,15 @@ from amsn2.gui import base -import pymsn import curses -class Contact(object): - def __init__(self, name, presence): - self.name = name - self.presence = presence - self.p2s = {pymsn.Presence.ONLINE:"online", - pymsn.Presence.BUSY:"busy", - pymsn.Presence.IDLE:"idle", - pymsn.Presence.AWAY:"away", - pymsn.Presence.BE_RIGHT_BACK:"brb", - pymsn.Presence.ON_THE_PHONE:"phone", - pymsn.Presence.OUT_TO_LUNCH:"lunch", - pymsn.Presence.INVISIBLE:"hidden", - pymsn.Presence.OFFLINE:"offline"} - - - def is_online(self): - return self.presence != pymsn.Presence.OFFLINE - - def status(self): - return self.p2s[self.presence] - -class Group(object): - def __init__(self, name): - self.contacts = {} - self.name = name - - def count(self): - online = 0 - total = 0 - for c in self.contacts: - total += 1 - if self.contacts[c].is_online(): - online +=1 - - return (online, total) - - class aMSNContactListWindow(base.aMSNContactListWindow): def __init__(self, amsn_core, parent): self._amsn_core = amsn_core - self.groups = {} - self.contacts = {} self._stdscr = parent._stdscr (y,x) = self._stdscr.getmaxyx() # TODO: Use a pad instead - self._win = curses.newwin(y, int(0.2*x), 0, 0) - self._win.bkgd(curses.color_pair(1)) + self._win = curses.newwin(y, int(0.25*x), 0, 0) + self._win.bkgd(curses.color_pair(0)) + self._win.border() self._clwidget = aMSNContactListWidget(amsn_core, self) def show(self): @@ -58,109 +19,72 @@ def hide(self): self._stdscr.clear() self._stdscr.refresh() - def contactStateChange(self, contact): - for group in contact.groups: - self.groups[group.id].contacts[contact.id].presence = contact.presence - - self.__update_view() - - def contactNickChange(self, contact): - pass - - def contactPSMChange(self, contact): - pass - - def contactAlarmChange(self, contact): - pass - - def contactDisplayPictureChange(self, contact): - pass - - def contactSpaceChange(self, contact): - pass - - def contactSpaceFetched(self, contact): - pass - - def contactBlocked(self, contact): - pass - - def contactUnblocked(self, contact): - pass - - def contactMoved(self, from_group, to_group, contact): - pass - - def contactAdded(self, group, contact): - self.groups[group.id].contacts[contact.id] = Contact(contact.display_name, contact.presence) - self.__update_view() - - def contactRemoved(self, group, contact): - pass - - def contactRenamed(self, contact): - pass - - def groupRenamed(self, group): - pass - - def groupAdded(self, group): - self.groups[group.id] = Group(group.name) - self.__update_view() - - def groupRemoved(self, group): - pass - def configure(self, option, value): pass def cget(self, option, value): pass - - def __update_view(self): - self._win.clear() - row = 0 - for g in self.groups: - count = self.groups[g].count() - self._win.addstr(row, 0, "%s (%d/%d)" % (self.groups[g].name, count[0], count[1]), curses.A_BOLD | curses.A_UNDERLINE) - row += 1 - for c in self.groups[g].contacts: - self._win.addstr(row, 2, "%s (%s)" % (self.groups[g].contacts[c].name, self.groups[g].contacts[c].status()), curses.A_BOLD) - row += 1 - row += 1 - - self._win.refresh() - - class aMSNContactListWidget(base.aMSNContactListWidget): def __init__(self, amsn_core, parent): super(aMSNContactListWidget, self).__init__(amsn_core, parent) self._groups = {} + self._contacts = {} self._win = parent._win self._stdscr = parent._stdscr def contactListUpdated(self, clView): - # TODO: Implement it to sort groups and handle add/delete + # TODO: Implement it to sort groups + for g in self._groups: + if g not in clView.group_ids: + self._groups.delete(g) for g in clView.group_ids: - self._groups[g] = None + if not self._groups.has_key(g): + self._groups[g] = None def groupUpdated(self, gView): + if not self._groups.has_key(gView.uid): + return + + if self._groups[gView.uid] is not None: + #Delete contacts + for c in self._groups[gView.uid].contact_ids: + if c not in gView.contact_ids: + if self._contacts[c]['refs'] == 1: + self._contacts.delete(c) + else: + self._contacts[c]['refs'] -= 1 + #Add contacts + for c in gView.contact_ids: + if not self._contacts.has_key(c): + self._contacts[c] = {'cView': None, 'refs': 1} + continue + #If contact wasn't already there, increment reference count + if self._groups[gView.uid] is None or c not in self._groups[gView.uid].contact_ids: + self._contacts[c]['refs'] += 1 self._groups[gView.uid] = gView self.__updateGroups() - # TODO: Implement something useful - import sys - print >> sys.stderr, gView.name def contactUpdated(self, cView): - pass + if not self._contacts.has_key(cView.uid): + return + self._contacts[cView.uid]['cView'] = cView + self.__updateGroups() def __updateGroups(self): self._win.clear() self._win.move(0,0) for g in self._groups: if self._groups[g] is not None: - self._win.insstr(str(self._groups[g].name)) + self._win.insstr(self._groups[g].name.toString()) + self._win.insch(curses.ACS_LLCORNER) self._win.insertln() + for c in self._groups[g].contact_ids: + if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: + self._win.insstr(self._contacts[c]['cView'].name.toString()) + self._win.insch(curses.ACS_HLINE) + self._win.insch(curses.ACS_HLINE) + self._win.insch(curses.ACS_LLCORNER) + self._win.insertln() self._win.refresh() From ec54cabb41e7846fbe5a75c2485a5e240ad2d778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Fri, 17 Apr 2009 17:23:01 +0100 Subject: [PATCH 025/147] Updated instructions to build efl frontend --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 22f4a6f4..a931a4bf 100644 --- a/README +++ b/README @@ -20,9 +20,9 @@ If you want to install the EFL, it's easy go to http://omicron.homeip.net/projec http://omicron.homeip.net/projects/easy_e17/easy_e17.sh Then do ./easy_e17.sh -i It will download the CVS, automake, configure, make and make install everything into /opt/e17 (so it doesn't 'contaminate' your system). -Once done, go to ./e17_cvs/proto/python-efl and type ./buildall.sh /usr (this will build and install the python extensions into /usr/python2.X/...) -Then in amsn2 directory, go to python-etk and build that too and install it... +Once done, go to ./e17_src/BINDINGS/python and type ./buildall.sh /usr (this will build and install the python extensions into /usr/python2.X/...). See README.txt in that directory for requirements. Then the efl front end should become available... +If it's not, fire up a python shell and try to import the ecore module; diagnose from there the problem. If you have the following error with the qt4 front-end: From 40373513c7125f7b71909cdf88fcf6a1f8428d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Fri, 17 Apr 2009 23:27:22 +0100 Subject: [PATCH 026/147] curses: Optimized CL redrawing, made *Updated methods threadsafe --- amsn2/gui/front_ends/curses/contact_list.py | 64 ++++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index 8843a25d..0877c4c5 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -1,5 +1,8 @@ from amsn2.gui import base import curses +from threading import Thread +from threading import Condition +import time class aMSNContactListWindow(base.aMSNContactListWindow): def __init__(self, amsn_core, parent): @@ -33,8 +36,19 @@ def __init__(self, amsn_core, parent): self._contacts = {} self._win = parent._win self._stdscr = parent._stdscr + self._mod_lock = Condition() + self._modified = False + self._thread = Thread(target=self.__thread_run) + self._thread.daemon = True + self._thread.setDaemon(True) + import sys + print >> sys.stderr, self._thread.isDaemon() + self._thread.start() def contactListUpdated(self, clView): + # Acquire the lock to do modifications + self._mod_lock.acquire() + # TODO: Implement it to sort groups for g in self._groups: if g not in clView.group_ids: @@ -42,8 +56,17 @@ def contactListUpdated(self, clView): for g in clView.group_ids: if not self._groups.has_key(g): self._groups[g] = None + self._modified = True + + # Notify waiting threads that we modified something + self._mod_lock.notify() + # Release the lock + self._mod_lock.release() def groupUpdated(self, gView): + # Acquire the lock to do modifications + self._mod_lock.acquire() + if not self._groups.has_key(gView.uid): return @@ -64,15 +87,33 @@ def groupUpdated(self, gView): if self._groups[gView.uid] is None or c not in self._groups[gView.uid].contact_ids: self._contacts[c]['refs'] += 1 self._groups[gView.uid] = gView - self.__updateGroups() + self._modified = True + + # Notify waiting threads that we modified something + self._mod_lock.notify() + # Release the lock + self._mod_lock.release() def contactUpdated(self, cView): + # Acquire the lock to do modifications + self._mod_lock.acquire() + if not self._contacts.has_key(cView.uid): return self._contacts[cView.uid]['cView'] = cView - self.__updateGroups() + self._modified = True + + # Notify waiting threads that we modified something + self._mod_lock.notify() + # Release the lock + self._mod_lock.release() + + def __repaint(self): + import sys + print >> sys.stderr, "Repainting" + # Acquire the lock to do modifications + self._mod_lock.acquire() - def __updateGroups(self): self._win.clear() self._win.move(0,0) for g in self._groups: @@ -88,3 +129,20 @@ def __updateGroups(self): self._win.insch(curses.ACS_LLCORNER) self._win.insertln() self._win.refresh() + self._modified = False + + # Notify waiting threads that we modified something + self._mod_lock.notify() + # Release the lock + self._mod_lock.release() + + def __thread_run(self): + while True: + self._mod_lock.acquire() + t = time.time() + # We don't want to work before at least half a second has passed + while t - time.time() < 0.5 and not self._modified: + self._mod_lock.wait(0.5) + self.__repaint() + t = time.time() + self._mod_lock.release() From 6688da4c0b7cc16c0fe04d6d6018bc1b16d992a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sat, 18 Apr 2009 01:07:58 +0100 Subject: [PATCH 027/147] curses: Fixed groups/contacts display order, added prints for debugging --- amsn2/gui/front_ends/curses/contact_list.py | 37 +++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index 0877c4c5..aede087d 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -32,6 +32,7 @@ class aMSNContactListWidget(base.aMSNContactListWidget): def __init__(self, amsn_core, parent): super(aMSNContactListWidget, self).__init__(amsn_core, parent) + self._groups_order = [] self._groups = {} self._contacts = {} self._win = parent._win @@ -41,8 +42,6 @@ def __init__(self, amsn_core, parent): self._thread = Thread(target=self.__thread_run) self._thread.daemon = True self._thread.setDaemon(True) - import sys - print >> sys.stderr, self._thread.isDaemon() self._thread.start() def contactListUpdated(self, clView): @@ -50,12 +49,13 @@ def contactListUpdated(self, clView): self._mod_lock.acquire() # TODO: Implement it to sort groups - for g in self._groups: + for g in self._groups_order: if g not in clView.group_ids: self._groups.delete(g) for g in clView.group_ids: - if not self._groups.has_key(g): + if not g in self._groups_order: self._groups[g] = None + self._groups_order = clView.group_ids self._modified = True # Notify waiting threads that we modified something @@ -116,33 +116,42 @@ def __repaint(self): self._win.clear() self._win.move(0,0) - for g in self._groups: + gso = self._groups_order + gso.reverse() + for g in gso: if self._groups[g] is not None: - self._win.insstr(self._groups[g].name.toString()) - self._win.insch(curses.ACS_LLCORNER) - self._win.insertln() - for c in self._groups[g].contact_ids: + cids = self._groups[g].contact_ids + cids.reverse() + for c in cids: if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: - self._win.insstr(self._contacts[c]['cView'].name.toString()) + self._win.insstr(" " + self._contacts[c]['cView'].name.toString()) self._win.insch(curses.ACS_HLINE) self._win.insch(curses.ACS_HLINE) self._win.insch(curses.ACS_LLCORNER) self._win.insertln() + self._win.insstr(self._groups[g].name.toString()) + self._win.insch(curses.ACS_LLCORNER) + self._win.insertln() self._win.refresh() self._modified = False - # Notify waiting threads that we modified something - self._mod_lock.notify() # Release the lock self._mod_lock.release() + print >> sys.stderr, "Repainted" def __thread_run(self): while True: + import sys + print >> sys.stderr, "at loop start" self._mod_lock.acquire() t = time.time() # We don't want to work before at least half a second has passed - while t - time.time() < 0.5 and not self._modified: - self._mod_lock.wait(0.5) + while time.time() - t < 0.5 or not self._modified: + print >> sys.stderr, "Going to sleep\n" + self._mod_lock.wait(timeout=1) + print >> sys.stderr, "Ok time to see if we must repaint" self.__repaint() t = time.time() self._mod_lock.release() + print >> sys.stderr, "at loop end" + self._mod_lock.acquire() From bda6829d0c7d50a1cbf1188cdfa3789c4b7967c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sat, 18 Apr 2009 13:32:14 +0100 Subject: [PATCH 028/147] curses: Cleaned method and lock, used with statement --- amsn2/gui/front_ends/curses/contact_list.py | 168 +++++++++----------- 1 file changed, 79 insertions(+), 89 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index aede087d..d29ab896 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -1,3 +1,4 @@ +from __future__ import with_statement from amsn2.gui import base import curses from threading import Thread @@ -46,112 +47,101 @@ def __init__(self, amsn_core, parent): def contactListUpdated(self, clView): # Acquire the lock to do modifications - self._mod_lock.acquire() - - # TODO: Implement it to sort groups - for g in self._groups_order: - if g not in clView.group_ids: - self._groups.delete(g) - for g in clView.group_ids: - if not g in self._groups_order: - self._groups[g] = None - self._groups_order = clView.group_ids - self._modified = True - - # Notify waiting threads that we modified something - self._mod_lock.notify() - # Release the lock - self._mod_lock.release() + with self._mod_lock: + # TODO: Implement it to sort groups + for g in self._groups_order: + if g not in clView.group_ids: + self._groups.delete(g) + for g in clView.group_ids: + if not g in self._groups_order: + self._groups[g] = None + self._groups_order = clView.group_ids + self._modified = True + + # Notify waiting threads that we modified something + import sys + print >> sys.stderr, "Notify from contactListUpdated" + self._mod_lock.notify() def groupUpdated(self, gView): # Acquire the lock to do modifications - self._mod_lock.acquire() - - if not self._groups.has_key(gView.uid): - return - - if self._groups[gView.uid] is not None: - #Delete contacts - for c in self._groups[gView.uid].contact_ids: - if c not in gView.contact_ids: - if self._contacts[c]['refs'] == 1: - self._contacts.delete(c) - else: - self._contacts[c]['refs'] -= 1 - #Add contacts - for c in gView.contact_ids: - if not self._contacts.has_key(c): - self._contacts[c] = {'cView': None, 'refs': 1} - continue - #If contact wasn't already there, increment reference count - if self._groups[gView.uid] is None or c not in self._groups[gView.uid].contact_ids: - self._contacts[c]['refs'] += 1 - self._groups[gView.uid] = gView - self._modified = True - - # Notify waiting threads that we modified something - self._mod_lock.notify() - # Release the lock - self._mod_lock.release() + with self._mod_lock: + if self._groups.has_key(gView.uid): + if self._groups[gView.uid] is not None: + #Delete contacts + for c in self._groups[gView.uid].contact_ids: + if c not in gView.contact_ids: + if self._contacts[c]['refs'] == 1: + self._contacts.delete(c) + else: + self._contacts[c]['refs'] -= 1 + #Add contacts + for c in gView.contact_ids: + if not self._contacts.has_key(c): + self._contacts[c] = {'cView': None, 'refs': 1} + continue + #If contact wasn't already there, increment reference count + if self._groups[gView.uid] is None or c not in self._groups[gView.uid].contact_ids: + self._contacts[c]['refs'] += 1 + self._groups[gView.uid] = gView + self._modified = True + + # Notify waiting threads that we modified something + import sys + print >> sys.stderr, "Notify from groupUpdated" + self._mod_lock.notify() def contactUpdated(self, cView): # Acquire the lock to do modifications - self._mod_lock.acquire() - - if not self._contacts.has_key(cView.uid): - return - self._contacts[cView.uid]['cView'] = cView - self._modified = True + with self._mod_lock: + if self._contacts.has_key(cView.uid): + self._contacts[cView.uid]['cView'] = cView + self._modified = True - # Notify waiting threads that we modified something - self._mod_lock.notify() - # Release the lock - self._mod_lock.release() + # Notify waiting threads that we modified something + import sys + print >> sys.stderr, "Notify from contactUpdated" + self._mod_lock.notify() def __repaint(self): import sys print >> sys.stderr, "Repainting" # Acquire the lock to do modifications - self._mod_lock.acquire() - - self._win.clear() - self._win.move(0,0) - gso = self._groups_order - gso.reverse() - for g in gso: - if self._groups[g] is not None: - cids = self._groups[g].contact_ids - cids.reverse() - for c in cids: - if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: - self._win.insstr(" " + self._contacts[c]['cView'].name.toString()) - self._win.insch(curses.ACS_HLINE) - self._win.insch(curses.ACS_HLINE) - self._win.insch(curses.ACS_LLCORNER) - self._win.insertln() - self._win.insstr(self._groups[g].name.toString()) - self._win.insch(curses.ACS_LLCORNER) - self._win.insertln() - self._win.refresh() - self._modified = False + with self._mod_lock: + self._win.clear() + self._win.move(0,0) + gso = self._groups_order + gso.reverse() + for g in gso: + if self._groups[g] is not None: + cids = self._groups[g].contact_ids + cids.reverse() + for c in cids: + if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: + self._win.insstr(" " + self._contacts[c]['cView'].name.toString()) + self._win.insch(curses.ACS_HLINE) + self._win.insch(curses.ACS_HLINE) + self._win.insch(curses.ACS_LLCORNER) + self._win.insertln() + self._win.insstr(self._groups[g].name.toString()) + self._win.insch(curses.ACS_LLCORNER) + self._win.insertln() + self._win.refresh() + self._modified = False - # Release the lock - self._mod_lock.release() print >> sys.stderr, "Repainted" def __thread_run(self): while True: import sys print >> sys.stderr, "at loop start" - self._mod_lock.acquire() - t = time.time() - # We don't want to work before at least half a second has passed - while time.time() - t < 0.5 or not self._modified: - print >> sys.stderr, "Going to sleep\n" - self._mod_lock.wait(timeout=1) - print >> sys.stderr, "Ok time to see if we must repaint" - self.__repaint() - t = time.time() - self._mod_lock.release() + with self._mod_lock: + t = time.time() + # We don't want to work before at least half a second has passed + while time.time() - t < 0.5 or not self._modified: + print >> sys.stderr, "Going to sleep\n" + self._mod_lock.wait(timeout=1) + print >> sys.stderr, "Ok time to see if we must repaint" + self.__repaint() + t = time.time() print >> sys.stderr, "at loop end" - self._mod_lock.acquire() From 96ca75419bd924381586008907f91fd2ec1f785d Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Sun, 19 Apr 2009 21:13:52 +0200 Subject: [PATCH 029/147] WIP: a window is shown!! --- amsn2/gui/front_ends/efl/__init__.py | 6 +- amsn2/gui/front_ends/efl/efl.py | 6 +- amsn2/gui/front_ends/efl/main.py | 7 +- amsn2/gui/front_ends/efl/main_loop.py | 10 +-- amsn2/gui/front_ends/efl/splash.py | 6 +- amsn2/gui/front_ends/efl/window.py | 98 ++++----------------------- 6 files changed, 31 insertions(+), 102 deletions(-) diff --git a/amsn2/gui/front_ends/efl/__init__.py b/amsn2/gui/front_ends/efl/__init__.py index b8ea9d94..ad32ed00 100644 --- a/amsn2/gui/front_ends/efl/__init__.py +++ b/amsn2/gui/front_ends/efl/__init__.py @@ -20,10 +20,10 @@ def load(): imp.find_module("evas") imp.find_module("edje") imp.find_module("ecore") - imp.find_module("etk") + imp.find_module("elementary") gui.GUIManager.registerFrontEnd("efl", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/efl/efl.py b/amsn2/gui/front_ends/efl/efl.py index 49737726..16f060ba 100644 --- a/amsn2/gui/front_ends/efl/efl.py +++ b/amsn2/gui/front_ends/efl/efl.py @@ -1,8 +1,8 @@ from main_loop import * from main import * -from contact_list import * -from login import * +#from contact_list import * +#from login import * from image import * from splash import * from skins import * -from chat_window import * +#from chat_window import * diff --git a/amsn2/gui/front_ends/efl/main.py b/amsn2/gui/front_ends/efl/main.py index fd1b3da0..fee6299b 100644 --- a/amsn2/gui/front_ends/efl/main.py +++ b/amsn2/gui/front_ends/efl/main.py @@ -2,7 +2,6 @@ import ecore import ecore.evas import ecore.x -import etk import skins import window from amsn2.gui import base @@ -11,9 +10,9 @@ class aMSNMainWindow(window.aMSNWindow, base.aMSNMainWindow): def __init__(self, amsn_core): window.aMSNWindow.__init__(self, amsn_core) - self.on_destroyed(self.__on_delete_request) - self.on_shown(self.__on_show) - self.on_key_down(self.__on_key_down) + self.destroy = self.__on_delete_request + self.on_show_add(self.__on_show) + self.on_key_down_add(self.__on_key_down) """ Private methods thoses methods shouldn't be called by outside or by an inherited class diff --git a/amsn2/gui/front_ends/efl/main_loop.py b/amsn2/gui/front_ends/efl/main_loop.py index caa4efc3..e760c0c1 100644 --- a/amsn2/gui/front_ends/efl/main_loop.py +++ b/amsn2/gui/front_ends/efl/main_loop.py @@ -2,11 +2,12 @@ from amsn2.gui import base import gobject import ecore +import elementary class aMSNMainLoop(base.aMSNMainLoop): def __init__(self, amsn_core): - pass - + elementary.init() + def run(self): mainloop = gobject.MainLoop(is_running=True) context = mainloop.get_context() @@ -21,8 +22,9 @@ def glib_context_iterate(): # to allow the protocol context loop to work ecore.timer_add(0.1, glib_context_iterate) + #equals elementary.run() ecore.main_loop_begin() - + def idlerAdd(self, func): ecore.idler_add(func) @@ -31,4 +33,4 @@ def timerAdd(self, delay, func): def quit(self): ecore.main_loop_quit() - + diff --git a/amsn2/gui/front_ends/efl/splash.py b/amsn2/gui/front_ends/efl/splash.py index 569f9052..190a7506 100644 --- a/amsn2/gui/front_ends/efl/splash.py +++ b/amsn2/gui/front_ends/efl/splash.py @@ -7,12 +7,12 @@ def __init__(self, amsn_core, parent): def show(self): pass - + def hide(self): pass - + def setText(self, text): pass - + def setImage(self, image): pass diff --git a/amsn2/gui/front_ends/efl/window.py b/amsn2/gui/front_ends/efl/window.py index bc5d5686..2a2ebd70 100644 --- a/amsn2/gui/front_ends/efl/window.py +++ b/amsn2/gui/front_ends/efl/window.py @@ -3,115 +3,43 @@ import ecore import ecore.evas import ecore.x -import etk +import elementary from amsn2.gui import base from amsn2.core.views import MenuView, MenuItemView -class aMSNWindow(etk.Window, base.aMSNWindow): +class aMSNWindow(elementary.Window, base.aMSNWindow): def __init__(self, amsn_core): self._amsn_core = amsn_core - self._vbox = etk.VBox() - etk.Window.__init__(self,title="aMSN", - size_request=(MIN_WIDTH,MIN_HEIGHT), - child=self._vbox) - self.on_key_down(self._on_key_down) - self.fullscreen = False - self.wmclass_set(WM_NAME, WM_CLASS) + elementary.Window.__init__(self, "aMSN", elementary.ELM_WIN_BASIC) self.resize(WIDTH, HEIGHT) - self._has_menu = False - self._vbox.show() + self.on_key_down_add(self._on_key_down) + self.fullscreen = False + self.name_class_set = (WM_NAME, WM_CLASS) + #self._has_menu = False @property def _evas(self): - return self.toplevel_evas_get() - - def show(self): - self.show_all() + return self.canvas def hide(self): - self.hide() + pass def setTitle(self, text): self.title_set(text) def setMenu(self, menu): - if menu is None: - if self._has_menu: - #Remove the menubar - menu_bar = self._vbox.child_get_at(etk.VBox.START, 0) - menu_bar.parent = None - self._has_menu = False - else: - if self._has_menu: - menu_bar = self._vbox.child_get_at(etk.VBox.START, 0) - #Clear the menubar: - for menu_item in menu_bar.items_get: - menu_bar.remove(menu_item) - else: - menu_bar = etk.MenuBar() - self._vbox.prepend(menu_bar, etk.VBox.START, etk.VBox.FILL, 0) - self._has_menu = True - createEtkMenuFromMenuView(menu.items, menu_bar) - + pass def setChild(self, child): - obj = self.getChild() - if obj is not None: - obj.parent = None - self._vbox.append(child, etk.VBox.START, etk.VBox.EXPAND_FILL, 0) + pass def getChild(self): - if self._has_menu: - pos = 1 - else: - pos = 0 pass - return self._vbox.child_get_at(etk.VBox.START, pos) def toggleMenu(self): - if self._has_menu: - menu_bar = self._vbox.child_get_at(etk.VBox.START, 0) - if menu_bar.is_visible(): - menu_bar.hide() - else: - menu_bar.show() + pass def _on_key_down(self, obj, event): - if (event.keyname == "F6" or - (event.keyname is "f" and event.modifiers is - etk.c_etk.EventEnums.MODIFIER_CTRL)): - self.fullscreen = not self.fullscreen - elif (event.keyname == "F5" or - (event.keyname is "b" and event.modifiers is - etk.c_etk.EventEnums.MODIFIER_CTRL)): - self.decorated = not self.decorated - elif (event.keyname == "m" and - event.modifiers == etk.c_etk.EventEnums.MODIFIER_CTRL): - self.toggleMenu() - -def createEtkMenuFromMenuView(items, etkmenu): - for item in items: - if item.type is MenuItemView.CASCADE_MENU: - m = etk.Menu() - mi = etk.MenuItem(label=item.label) - createEtkMenuFromMenuView(item.items, m) - mi.submenu = m - etkmenu.append(mi) - elif item.type is MenuItemView.COMMAND: - if item.icon is None: - mi = etk.MenuItem(label=item.label) - else: - #TODO: icon - mi = etk.MenuItemImage(label=item.label) - #TODO: define the prototype of item.command - #=> for the moment, it's item.command() i.e. no argument - def cb(obj): - item.command() - mi.on_activated(cb) - etkmenu.append(mi) - elif item.type is MenuItemView.SEPARATOR: - mi = etk.MenuItemSeparator() - etkmenu.append(mi) + pass - #TODO: CHECKBUTTON, RADIOBUTTON, RADIOBUTTONGROUP From 834041f961e60fdd6280b1a44c7fe2a8e2e56598 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Sun, 19 Apr 2009 22:20:01 +0200 Subject: [PATCH 030/147] Login is shown with user/pass field and a signin button Clicking on the button makes a weird segfault... --- amsn2/gui/front_ends/efl/efl.py | 2 +- amsn2/gui/front_ends/efl/login.py | 69 +++++++++++++++---------------- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/amsn2/gui/front_ends/efl/efl.py b/amsn2/gui/front_ends/efl/efl.py index 16f060ba..5da512ce 100644 --- a/amsn2/gui/front_ends/efl/efl.py +++ b/amsn2/gui/front_ends/efl/efl.py @@ -1,7 +1,7 @@ from main_loop import * from main import * +from login import * #from contact_list import * -#from login import * from image import * from splash import * from skins import * diff --git a/amsn2/gui/front_ends/efl/login.py b/amsn2/gui/front_ends/efl/login.py index 14b10223..d0ef654e 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -2,7 +2,7 @@ import edje import ecore import ecore.x -import etk +import elementary from amsn2.gui import base @@ -14,43 +14,37 @@ def __init__(self, amsn_core, parent): edje.frametime_set(1.0 / 30) - mainChild = etk.EvasObject() - try: self._edje = edje.Edje(self._evas, file=THEME_FILE, group="login_screen") except edje.EdjeLoadError, e: raise SystemExit("error loading %s: %s" % (THEME_FILE, e)) - mainChild.evas_object = self._edje + self._parent.resize_object_add(self._edje) + self._edje.size_hint_weight_set(1.0, 1.0) + self.show() - self.password = etk.Entry() - embed = etk.Embed(self._evas) - embed.add(self.password) - embed.show_all() - self.password.password_mode = True - self._edje.part_swallow("login_screen.password", embed.object) + self.password = elementary.Entry(self._edje) + self.password.single_line_set(1) + self.password.password_set(1) + self.password.size_hint_weight_set(1.0, 1.0) + self.password.show() + self._edje.part_swallow("login_screen.password", self.password) + self.password.show() - self.status = etk.Entry() - embed = etk.Embed(self._evas) - embed.add(self.status) - embed.show_all() - self._edje.part_swallow("login_screen.status", embed.object) + #TODO: login_screen.status - self.username = etk.Entry() - embed = etk.Embed(self._evas) - embed.add(self.username) - embed.show_all() - self._edje.part_swallow("login_screen.username", embed.object) + self.username = elementary.Entry(self._edje) + self.username.single_line_set(1) + self.username.show() + self._edje.part_swallow("login_screen.username", self.username) if self._edje.part_exists("login_screen.signin"): - self.signin_b = etk.Button() - embed = etk.Embed(self._evas) - embed.add(self.signin_b) - embed.show_all() - self._edje.part_swallow("login_screen.signin", embed.object) - self.signin_b.label = "Sign in" - self.signin_b.connect("clicked", self.__signin_button_cb) + self.signin_b = elementary.Button(self._edje) + self.signin_b.label_set("Sign in") + self.signin_b.clicked = self.__signin_button_cb + self.signin_b.show() + self._edje.part_swallow("login_screen.signin", self.signin_b) else: self._edje.signal_callback_add("signin", "*", self.__signin_cb) @@ -58,7 +52,6 @@ def __init__(self, amsn_core, parent): # We start with no profile set up, we let the Core set our starting profile self.switch_to_profile(None) - parent.setChild(mainChild) def show(self): self._edje.show() @@ -79,15 +72,20 @@ def hide(self): def switch_to_profile(self, profile): self.current_profile = profile if self.current_profile is not None: - self.username.text = self.current_profile.username - self.password.text = self.current_profile.password + if self.current_profile.username is None: + self.username.entry_set("") + else: + self.username.entry_set(self.current_profile.username) + if self.current_profile.password is None: + self.password.entry_set("") + else: + self.password.entry_set(self.current_profile.password) def signin(self): - # TODO : get/set the username/password and other options from the login screen - self.current_profile.username = self.username.text - self.current_profile.email = self.username.text - self.current_profile.password = self.password.text + self.current_profile.username = self.username.entry_get() + self.current_profile.email = self.username.entry_get() + self.current_profile.password = self.password.entry_get() self._amsn_core.signinToAccount(self, self.current_profile) def onConnecting(self, progress, message): @@ -110,6 +108,5 @@ def onConnecting(self, progress, message): def __signin_cb(self, edje_obj, signal, source): self.signin() - def __signin_button_cb(self, button): - print "clicked %s - %s" % (self, button) + def __signin_button_cb(self, button, event): self.signin() From f14d024dc2c9252ab68ce8c1b59f0ca70bdc1cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sun, 19 Apr 2009 22:52:17 +0100 Subject: [PATCH 031/147] curses: Fixed bug with threads, removed print statements --- amsn2/gui/front_ends/curses/contact_list.py | 21 +++------------------ amsn2/gui/front_ends/curses/main_loop.py | 1 + 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index d29ab896..53fa12e5 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -59,8 +59,6 @@ def contactListUpdated(self, clView): self._modified = True # Notify waiting threads that we modified something - import sys - print >> sys.stderr, "Notify from contactListUpdated" self._mod_lock.notify() def groupUpdated(self, gView): @@ -87,8 +85,6 @@ def groupUpdated(self, gView): self._modified = True # Notify waiting threads that we modified something - import sys - print >> sys.stderr, "Notify from groupUpdated" self._mod_lock.notify() def contactUpdated(self, cView): @@ -99,13 +95,9 @@ def contactUpdated(self, cView): self._modified = True # Notify waiting threads that we modified something - import sys - print >> sys.stderr, "Notify from contactUpdated" self._mod_lock.notify() def __repaint(self): - import sys - print >> sys.stderr, "Repainting" # Acquire the lock to do modifications with self._mod_lock: self._win.clear() @@ -129,19 +121,12 @@ def __repaint(self): self._win.refresh() self._modified = False - print >> sys.stderr, "Repainted" def __thread_run(self): while True: - import sys - print >> sys.stderr, "at loop start" + # We don't want to repaint too often, once every half second is cool + time.sleep(0.5) with self._mod_lock: - t = time.time() - # We don't want to work before at least half a second has passed - while time.time() - t < 0.5 or not self._modified: - print >> sys.stderr, "Going to sleep\n" + while not self._modified: self._mod_lock.wait(timeout=1) - print >> sys.stderr, "Ok time to see if we must repaint" self.__repaint() - t = time.time() - print >> sys.stderr, "at loop end" diff --git a/amsn2/gui/front_ends/curses/main_loop.py b/amsn2/gui/front_ends/curses/main_loop.py index ac239541..26f0ddbe 100644 --- a/amsn2/gui/front_ends/curses/main_loop.py +++ b/amsn2/gui/front_ends/curses/main_loop.py @@ -1,6 +1,7 @@ from amsn2.gui import base import gobject +gobject.threads_init() class aMSNMainLoop(base.aMSNMainLoop): def __init__(self, amsn_core): From 292f3530407f3aa823e2474a397c0b4460f40f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sun, 19 Apr 2009 22:53:12 +0100 Subject: [PATCH 032/147] Merge branch 'master'; commit 'origin/master' From b69cd0e9324a77d6134a1a5a67c42bc6774404fd Mon Sep 17 00:00:00 2001 From: Boris billiob Faure Date: Tue, 21 Apr 2009 23:13:18 +0200 Subject: [PATCH 033/147] Fix signin button issue --- amsn2/gui/front_ends/efl/login.py | 10 +++++----- amsn2/gui/front_ends/efl/main.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/amsn2/gui/front_ends/efl/login.py b/amsn2/gui/front_ends/efl/login.py index d0ef654e..b94bc604 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -58,7 +58,7 @@ def show(self): def hide(self): self._edje.hide() - #FIXME: those are not hidden by self._edje.hide() + #FIXME: those are not hidden by self._edje.hide() self.password.hide() self.status.hide() self.username.hide() @@ -83,9 +83,9 @@ def switch_to_profile(self, profile): def signin(self): - self.current_profile.username = self.username.entry_get() - self.current_profile.email = self.username.entry_get() - self.current_profile.password = self.password.entry_get() + self.current_profile.username = elementary.Entry.markup_to_utf8(self.username.entry_get()) + self.current_profile.email = self.current_profile.username + self.current_profile.password = elementary.Entry.markup_to_utf8(self.password.entry_get()) self._amsn_core.signinToAccount(self, self.current_profile) def onConnecting(self, progress, message): @@ -108,5 +108,5 @@ def onConnecting(self, progress, message): def __signin_cb(self, edje_obj, signal, source): self.signin() - def __signin_button_cb(self, button, event): + def __signin_button_cb(self, button, event, data): self.signin() diff --git a/amsn2/gui/front_ends/efl/main.py b/amsn2/gui/front_ends/efl/main.py index fee6299b..f282abe8 100644 --- a/amsn2/gui/front_ends/efl/main.py +++ b/amsn2/gui/front_ends/efl/main.py @@ -21,7 +21,7 @@ def __init__(self, amsn_core): def __on_show(self, evas_obj): self._amsn_core.mainWindowShown() - def __on_delete_request(self, evas_obj): + def __on_delete_request(self, evas_obj, emission, data): self._amsn_core.quit() def __on_key_down(self, obj, event): From f171143fe0237fe649b8498203e67d06e1cd0146 Mon Sep 17 00:00:00 2001 From: Boris billiob Faure Date: Tue, 21 Apr 2009 23:54:17 +0200 Subject: [PATCH 034/147] Strip text from efl entry --- amsn2/core/amsn.py | 2 +- amsn2/gui/front_ends/efl/login.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 2971e184..69250283 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -125,7 +125,7 @@ def addProfile(self, account): return self._profile_manager.addProfile(account) def signinToAccount(self, login_window, profile): - print "Signing in to account %s" % (profile.email) + print "Signing in to account '%s'" % (profile.email) profile.login = login_window profile.client = protocol.Client(self, profile) self._profile = profile diff --git a/amsn2/gui/front_ends/efl/login.py b/amsn2/gui/front_ends/efl/login.py index b94bc604..82763746 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -83,9 +83,11 @@ def switch_to_profile(self, profile): def signin(self): - self.current_profile.username = elementary.Entry.markup_to_utf8(self.username.entry_get()) + self.current_profile.username = \ + elementary.Entry.markup_to_utf8(self.username.entry_get()).strip() self.current_profile.email = self.current_profile.username - self.current_profile.password = elementary.Entry.markup_to_utf8(self.password.entry_get()) + self.current_profile.password = \ + elementary.Entry.markup_to_utf8(self.password.entry_get()).strip() self._amsn_core.signinToAccount(self, self.current_profile) def onConnecting(self, progress, message): From e070851f9ae57e8649ebef7a0e6eb7b85ae59d7d Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 13 Apr 2009 17:38:03 +0200 Subject: [PATCH 035/147] Add loadUI function --- amsn2/core/amsn.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 69250283..3ad9ebe8 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -41,13 +41,14 @@ def __init__(self, options): options.front_end = the front end's name to use options.debug = whether or not to enable debug output """ - self._profile_manager = profile.aMSNProfileManager() self._options = options - self._gui_name = self._options.front_end - self._gui = gui.GUIManager(self, self._gui_name) - self._loop = self._gui.gui.aMSNMainLoop(self) - self._main = self._gui.gui.aMSNMainWindow(self) - self._skin_manager = self._gui.gui.SkinManager(self) + + self._gui_name = None + self._gui = None + self._loop = None + self._main = None + self.loadUI(self._options.front_end) + self._theme_manager = aMSNThemeManager() self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) @@ -71,6 +72,16 @@ def run(self): self._main.show(); self._loop.run(); + def loadUI(self, ui_name): + self._gui_name = ui_name + self._gui = gui.GUIManager(self, self._gui_name) + self._loop = self._gui.gui.aMSNMainLoop(self) + self._main = self._gui.gui.aMSNMainWindow(self) + self._skin_manager = self._gui.gui.SkinManager(self) + + def switchToUI(self, ui_name): + #TODO: unloadUI + stop loops??? + loadUI + run + pass def mainWindowShown(self): # TODO : load the profiles from disk and all settings From bbc79d80c066b05377ad68807bb9bdd34e6710de Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 13 Apr 2009 21:49:10 +0200 Subject: [PATCH 036/147] WIP, improve profile management --- amsn2/core/amsn.py | 34 +-- amsn2/core/profile.py | 449 +++----------------------------- amsn2/core/views/__init__.py | 1 + amsn2/core/views/profileview.py | 12 + 4 files changed, 59 insertions(+), 437 deletions(-) create mode 100644 amsn2/core/views/profileview.py diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 3ad9ebe8..d351f239 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -18,11 +18,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import profile from amsn2 import gui from amsn2 import protocol import pymsn from views import * +from profile import * from contactlist_manager import * from conversation_manager import * from oim_manager import * @@ -49,6 +49,8 @@ def __init__(self, options): self._main = None self.loadUI(self._options.front_end) + self._profile_manager = aMSNProfileManager(options) + self._profile = None self._theme_manager = aMSNThemeManager() self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) @@ -100,27 +102,7 @@ def mainWindowShown(self): login = self._gui.gui.aMSNLoginWindow(self, self._main) - profile = None - if self._options.account is not None: - if self._profile_manager.profileExists(self._options.account): - profile = self._profile_manager.getProfile(self._options.account) - else: - profile = self._profile_manager.addProfile(self._options.account) - profile.save = False - if self._options.password is not None: - profile.password = self._options.password - - else: - for prof in self._profile_manager.getAllProfiles(): - if prof.isLocked() is False: - profile = prof - break - - if profile is None: - profile = self._profile_manager.addProfile("") - profile.password = "" - - login.switch_to_profile(profile) + login.setProfiles(self._profile_manager.getAvailableProfileViews()) splash.hide() self._main.setTitle("aMSN 2 - Login") @@ -132,11 +114,9 @@ def mainWindowShown(self): def getMainWindow(self): return self._main - def addProfile(self, account): - return self._profile_manager.addProfile(account) - - def signinToAccount(self, login_window, profile): - print "Signing in to account '%s'" % (profile.email) + def signinToAccount(self, login_window, profileview): + print "Signing in to account %s" % (profileview.email) + self.profileself.profile profile.login = login_window profile.client = protocol.Client(self, profile) self._profile = profile diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py index 72f385df..be3e8196 100644 --- a/amsn2/core/profile.py +++ b/amsn2/core/profile.py @@ -4,440 +4,69 @@ import xml.parsers.expat import __builtin__ -class aMSNProfilesList(object): - """ aMSNProfilesList : Object representing profiles.xml """ - def __init__(self, profiles_dir): - self.path = os.path.join(profiles_dir, "profiles.xml") - self.updateProfilesList() - - def updateProfilesList(self): - """ Reads the content of profiles.xml """ - if os.access(self.path, os.F_OK): - profiles_file = file(self.path, "r") - - self.tree = xml.etree.ElementTree.ElementTree(file=profiles_file) - - profiles_file.close() - - self.root_element = self.tree.getroot() - else : - self.root_element = xml.etree.ElementTree.Element("aMSNProfilesList") - - self.tree = xml.etree.ElementTree.ElementTree(element = - self.root_element) - - def setProfileKey(self, profile_name, key, value): - self.updateProfilesList() - if self.ProfileIsListed(profile_name): - profile_element = self.root_element.find(profile_name.replace("@", "_")) - self.root_element.remove(profile_element) - profile_dict = elementToDict(profile_element) - - if profile_dict.has_key(key): - profile_dict[key] = value - profile_element = dictToElement(profile_name.replace("@", "_"), profile_dict) - self.root_element.append(profile_element) - self.saveProfilesList() - else: - raise ProfileKeyNotListedError - - def getProfileKey(self, profile_name, key, default=None): - self.updateProfilesList() - if self.ProfileIsListed(profile_name): - profile_element = self.root_element.find(profile_name.replace("@", "_")) - profile_dict = elementToDict(profile_element) - - try: - return profile_dict[key] - except KeyError: - return default - else: - return default - - def addProfile(self, profile, attributes_dict): - """ Adds a profile section in profiles.xml """ - self.updateProfilesList() - if self.ProfileIsListed(profile.email) is False: - profile_element = dictToElement(profile.email.replace("@","_"), attributes_dict) - self.root_element.append(profile_element) - self.saveProfilesList() - - def deleteProfile(self, profile): - """ Deletes a profile section from profiles.xml """ - self.updateProfilesList() - for profile_element in self.root_element : - if profile_element.tag == profile.email.replace("@","_"): - self.root_element.remove(profile_element) - self.saveProfilesList() - - def getAllProfilesNames(self): - """ Returns a list of all profiles' email addresses """ - self.updateProfilesList() - names_list = [] - - for profile_element in self.root_element: - profile_dict = elementToDict(profile_element) - names_list.append(profile_dict["email"]) - - return names_list - - def ProfileIsListed(self, profile_name): - """ Returns True if the profile is in profiles.xml """ - return (profile_name in self.getAllProfilesNames()) - - def saveProfilesList(self): - """ Dumps aMSNProfilesList XML tree into profiles.xml """ - return self.tree.write(self.path) - -class aMSNProfileConfiguration(object): - def __init__(self, profile): - self._profile = profile - self.config = {"ns_server":'messenger.hotmail.com', - "ns_port":1863, - } - - def getKey(self, key, default = None): - try: - return self.config[key] - except KeyError: - return default - - def setKey(self, key, value): - self.config[key] = value - -class aMSNPluginConfiguration(object): - """ aMSNProfilePlugin : A plugin object for profiles.""" - def __init__(self, profile, plugin, config_dict): - """ config_dict must be a dictionary of configurations ("option": value, ...) """ - self._profile = profile - self._plugin = plugin - self.config = config_dict - - def getKey(self, key, default = None): - try: - return self.config[key] - except KeyError: - return default - - def setKey(self, key, value): - self.config[key] = value - -class aMSNSmileyConfiguration(object): - """ aMSNSmileyCinfiguration : A smiley object for profiles.""" - def __init__(self, profile, smiley, config_dict): - """ config_dict must be a dictionary of configurations ("option": value, ...), - the "shortcut" and "image" keys are mandatory.""" - self._profile = profile - self._smiley = smiley - self.config = config_dict - self.config["valid"] = self.smileyIsValid() - - def smileyIsValid(self): - """ Checks whether a shortcut and a image file are defined - and if the image file is accessible. Accordingly sets the "valid" key""" - if self.config.has_key("shortcut") and self.config.has_key("image") : - self.image_path = os.path.join(self._profile.directory, "smileys", self.config["image"]) - if os.access(self.image_path, os.R_OK) : - return True - return False - - def getKey(self, key, default = None): - try: - return self.config[key] - except KeyError: - return default - - def setKey(self, key, value): - self.config[key] = value class aMSNProfile(object): """ aMSNProfile : a Class to represent an aMSN profile This class will contain all settings relative to a profile - and will store the protocol and GUI objects + and will store the protocol and GUI objects """ - def __init__(self, email, profiles_dir): - self.email = email - self.username = self.email - self.alias = self.email - self.password = None - self.account = None - self.directory = os.path.join(profiles_dir, self.email) - self.config = aMSNProfileConfiguration(self) - ###self.plugins_configs must be a dictionary like {"Plugin1_name":aMSNPluginConfiguration(self,"Plugin1_name",{"option1":123}), - ### "Plugin2_name":aMSNPluginConfiguration(self,"Plugin2_name",{"option1":"sdda","option2":345}) - ### } - self.plugins_configs = {} - ###self.smileys_configs must be a dictionary like {"Smiley1_name":aMSNSmileyConfiguration(self,"Smiley1_name",{"shortcut":";D", "image":image_file_name}), - ### "Smiley2_name":..... - ### } - self.smileys_configs = {} - - def isLocked(self): - """ Returns whether the profile is locked or not""" - return False - - def lock(self): - """ Locks a profile to avoid concurrent access to it from multiple instances """ - pass - - def unlock(self): - """ Unlocks a profile to allow other instances to acces it """ - pass - - def remove_dir(self): - """ Removes profile's directory from disk """ - - for root, dirs, files in os.walk(self.directory, topdown=False): - for name in files: - os.remove(os.path.join(root, name)) - for name in dirs: - os.rmdir(os.path.join(root, name)) - try: - os.rmdir(self.directory) - except: - pass - - def getConfigKey(self, key, default = None): - return self.config.getKey(key, default) - - def setConfigKey(self, key, value): - return self.config.setKey(key, value) - - def getPluginKey(self, plugin_name, key, default = None): - try: - return self.plugins_configs[plugin_name].getKey(key, default) - except KeyError: - return default - - def setPluginKey(self, plugin_name, key, value): - try: - self.plugins_configs[plugin_name].setKey(key, default) - return True - except KeyError: - return False - - def addPlugin(self, plugin_name, plugin_config_dict={}): - self.plugins_configs[plugin_name] = \ - aMSNPluginConfiguration(self, plugin_name, plugin_config_dict) - - def removePlugin(self, plugin_name): - return self.plugins_configs.pop(plugin_name, None) - - def getSmileyKey(self, plugin_name, key, default = None): - try: - return self.plugins_configs[plugin_name].getKey(key, default) - except KeyError: - return default - - def setSmileyKey(self, plugin_name, key, value): - try: - self.plugins_configs[plugin_name].setKey(key, default) - return True - except KeyError: - return False - - def addSmiley(self, smiley_name, smiley_config_dict={"shortcut":"dummy", "image":"dummy"}): - self.smileys_configs[smiley_name] = \ - aMSNSmileyConfiguration(self, smiley_name, smiley_config_dict) - - def removeSmiley(self, smiley_name): - return self.smileys_configs.pop(smiley_name, None) - + def __init__(self, profileview, core, profiles_dir): + self.view = profileview + #TODO class aMSNProfileManager(object): """ aMSNProfileManager : The profile manager that takes care of storing and retreiving all the profiles for our users. """ - def __init__(self): + def __init__(self, options): if os.name == "posix": self._profiles_dir = os.path.join(os.environ['HOME'], ".amsn2") elif os.name == "nt": self._profiles_dir = os.path.join(os.environ['USERPROFILE'], "amsn2") else: self._profiles_dir = os.path.join(os.curdir, "amsn2") - + try : os.makedirs(self._profiles_dir, 0777) except : pass - - self.profiles_list = aMSNProfilesList(self._profiles_dir) - - self.profiles = {} - self.loadAllProfiles() - - def profileExists(self, email): - """ Checks whether a profile exists """ - return self.getProfile(email) is not None - - def getProfile(self, email): - """ Get a profile object by email """ - try: - profile = self.profiles[email] - except KeyError: - profile = None - return profile - - def getAllProfiles(self): - return self.profiles.values() - def addProfile(self, email): - """ Adds a profile to the current running instance of aMSN """ - if self.profileExists(email) is False: - new_profile = aMSNProfile(email, self._profiles_dir) - self.profiles[email] = new_profile - - return self.getProfile(email) + self.profileviews = [] + self.reload() - def createProfile(self, email): - """ Creates a profile and stores it on disk and adds it to the current instance """ - new_profile = self.addProfile(email) - self.saveProfile(new_profile) - return self.getProfile(email) + if options.account is not None: + #TODO: check if the profile is not already in the list + pv = ProfileView() + pv.email = options.account + pv.password = options.password + self.profileviews.insert(0, pv) - def removeProfile(self, profile, and_delete=False): - """ Removes a profile from the current instance of aMSN """ - if self.profileExists(profile.email): - del self.profiles[profile.email] - - self.profiles_list.deleteProfile(profile) - - if and_delete == True: - self.deleteProfile(profile) - - def deleteProfile(self, profile): - """ Removes a profile from the current instance of aMSN and deletes it from disk """ - profile.remove_dir() - - return self.removeProfile(profile) - - def saveProfile(self, profile): - """ Stores a profile on disk """ - - config = profile.config.config - - config_section = dictToElement("Configurations", config) - - settings = {"email":profile.email, - "username":profile.username, - "alias":profile.alias, - "password":profile.password, - "account":profile.account - } - - if profile.password == None : - settings["password"] = "" - if profile.account == None : - settings["account"] = "" - - settings_section = dictToElement("Settings", settings) - - plugins = profile.plugins_configs - plugins_section = xml.etree.ElementTree.Element("Plugins") + def reload(self): + self.profileviews = [] + #TODO + pass - for plugin_name, plugin in plugins.iteritems(): - plugin_section = dictToElement(plugin_name, plugin.config) - plugins_section.append(plugin_section) + def getAllProfileViews(self): + #TODO + pass - smileys = profile.smileys_configs - smileys_section = xml.etree.ElementTree.Element("Smileys") - - for smiley_name, smiley in smileys.iteritems(): - smiley_section = dictToElement(smiley_name, smiley.config) - smileys_section.append(smiley_section) - - root_section = xml.etree.ElementTree.Element("aMSNProfile") - - settings_section.append(config_section) - settings_section.append(plugins_section) - settings_section.append(smileys_section) - root_section.append(settings_section) - - xml_tree = xml.etree.ElementTree.ElementTree(root_section) - - profile_dir = os.path.join(self._profiles_dir, profile.email) - - try : - os.makedirs(profile_dir, 0777) - os.makedirs(os.path.join(profile_dir, "smileys"), 0777) - os.makedirs(os.path.join(profile_dir, "displaypics"), 0777) - os.makedirs(os.path.join(profile_dir, "logs"), 0777) - ## Other directories here - except : - pass - - profile_path = os.path.join(self._profiles_dir, profile.email, "config.xml") - profile_file = file(profile_path, "w") - xml_tree.write(profile_file) - profile_file.close() - - list_opts={"email":profile.email, - "auto_connect":False - } - self.profiles_list.addProfile(profile, list_opts) - - def saveAllProfiles(self): - for profile in self.getAllProfiles(): - self.saveProfile(profile) - - def loadAllProfiles(self): - """ Loads all profiles from disk """ - profiles_names = self.profiles_list.getAllProfilesNames() - - for profile_name in profiles_names : - profile_file_path = os.path.join(self._profiles_dir, \ - profile_name, \ - "config.xml") - if os.path.exists(profile_file_path) is False: - continue + def getAvailableProfileViews(self): + #TODO + return self.profileviews + pass - ### Prepares XML Elements - root_tree = xml.etree.ElementTree.parse(profile_file_path) - settings = root_tree.find("Settings") - settings_tree = xml.etree.ElementTree.ElementTree(settings) - configs = settings_tree.find("Configurations") - settings.remove(configs) - plugins = settings_tree.find("Plugins") - settings.remove(plugins) - smileys = settings_tree.find("Smileys") - settings.remove(smileys) - - ### Loads Settings - settings_dict = elementToDict(settings) - profile = aMSNProfile(settings_dict['email'], self._profiles_dir) - profile.username = settings_dict['username'] - profile.alias = settings_dict['alias'] - profile.password = settings_dict['password'] - profile.account = settings_dict['account'] - profile.config = aMSNProfileConfiguration(profile) + def signingToProfile(self, profileview): + #TODO + pass - if profile.password == "" : - profile.password = None - if profile.account == "" : - profile.account = None - - ### Loads Configurations - configs_dict = elementToDict(configs) - profile.config.config = configs_dict - - ### Loads Plugins - plugins_dict = {} - for plugin_element in plugins: - plugins_dict[plugin_element.tag] = elementToDict(plugin_element) - - for plugin_name, plugin_config_dict in plugins_dict.iteritems(): - profile.addPlugin(plugin_name, plugin_config_dict) + def loadProfile(self, profileview, core): + #TODO + return aMSNProfile(profileview, core) - ### Loads Smileys - smileys_dict = {} - for smiley_element in smileys: - smileys_dict[smiley_element.tag] = elementToDict(smiley_element) - - for smiley_name, smiley_config_dict in smileys_dict.iteritems(): - profile.addSmiley(smiley_name, smiley_config_dict) + def unloadProfile(self, amsnprofile): + #TODO: unlock the profile + pass - ### Finally loads the Profile - self.profiles[profile.email] = profile def elementToDict(element): @@ -450,21 +79,21 @@ def dictToTuple(name, dict): value = int(dict['value']) else: value = dict['value'] - + config_pair = (key,eval(type)(value)) - + config_pair_list.append(config_pair) - + config_pair_list = [] for entry in element : entry_str = xml.etree.ElementTree.tostring(entry) - + parser=xml.parsers.expat.ParserCreate() parser.StartElementHandler = dictToTuple parser.Parse(entry_str, 1) del parser - + config_dict = dict(config_pair_list) return config_dict diff --git a/amsn2/core/views/__init__.py b/amsn2/core/views/__init__.py index 4dbfb176..d652173d 100644 --- a/amsn2/core/views/__init__.py +++ b/amsn2/core/views/__init__.py @@ -5,3 +5,4 @@ from tooltipview import * from messageview import * from imageview import * +from profileview import * diff --git a/amsn2/core/views/profileview.py b/amsn2/core/views/profileview.py new file mode 100644 index 00000000..eae57e77 --- /dev/null +++ b/amsn2/core/views/profileview.py @@ -0,0 +1,12 @@ +from imageview import * +from stringview import * + +class ProfileView: + def __init__(self): + self.email = Stringview() + self.password = Stringview() + self.nick = Stringview() + self.status = pymsn.Presence.ONLINE + self.dp = Imageview() + self.saveprofile = False + #TODO: preferred UI ? From aacf7d72eb3c1a65493572f7d2b696ef393dd8a6 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Sun, 19 Apr 2009 14:15:27 +0200 Subject: [PATCH 037/147] WIP --- amsn2/core/amsn.py | 9 ++++----- amsn2/core/profile.py | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index d351f239..036493c5 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -116,11 +116,10 @@ def getMainWindow(self): def signinToAccount(self, login_window, profileview): print "Signing in to account %s" % (profileview.email) - self.profileself.profile - profile.login = login_window - profile.client = protocol.Client(self, profile) - self._profile = profile - profile.client.connect() + self.profile = self._profile_manager.signinToAccount(profileview) + self.profile.login = login_window + self.profile.client = protocol.Client(self, profile) + self.profile.client.connect() def connectionStateChanged(self, profile, state): diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py index be3e8196..f9d241f7 100644 --- a/amsn2/core/profile.py +++ b/amsn2/core/profile.py @@ -68,7 +68,6 @@ def unloadProfile(self, amsnprofile): pass - def elementToDict(element): """ Converts an XML Element into a proper profile dictionary """ def dictToTuple(name, dict): From 33018bddc3222685de96e7a9eb6f95159bc54f5c Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 26 Apr 2009 16:34:45 +0200 Subject: [PATCH 038/147] Add setProfiles method to login window --- amsn2/core/profile.py | 16 ++++++++++------ amsn2/core/views/profileview.py | 9 +++++---- amsn2/gui/base/login.py | 10 +++++++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py index f9d241f7..d95f3dcf 100644 --- a/amsn2/core/profile.py +++ b/amsn2/core/profile.py @@ -3,7 +3,8 @@ import xml.etree.ElementTree import xml.parsers.expat import __builtin__ - +from views import ProfileView +from views import StringView class aMSNProfile(object): """ aMSNProfile : a Class to represent an aMSN profile @@ -24,7 +25,7 @@ def __init__(self, options): elif os.name == "nt": self._profiles_dir = os.path.join(os.environ['USERPROFILE'], "amsn2") else: - self._profiles_dir = os.path.join(os.curdir, "amsn2") + self._profiles_dir = os.path.join(os.curdir, "amsn2_profiles") try : os.makedirs(self._profiles_dir, 0777) @@ -35,10 +36,13 @@ def __init__(self, options): self.reload() if options.account is not None: - #TODO: check if the profile is not already in the list - pv = ProfileView() - pv.email = options.account - pv.password = options.password + pv = [p for p in self.profileviews if p.email == options.account] + if pv: + self.profileviews.remove(pv[0]) + else: + pv = ProfileView() + pv.email = options.account + pv.password = options.password self.profileviews.insert(0, pv) def reload(self): diff --git a/amsn2/core/views/profileview.py b/amsn2/core/views/profileview.py index eae57e77..a5a5cf14 100644 --- a/amsn2/core/views/profileview.py +++ b/amsn2/core/views/profileview.py @@ -1,12 +1,13 @@ from imageview import * from stringview import * +import pymsn class ProfileView: def __init__(self): - self.email = Stringview() - self.password = Stringview() - self.nick = Stringview() + self.email = StringView() + self.password = StringView() + self.nick = StringView() self.status = pymsn.Presence.ONLINE - self.dp = Imageview() + self.dp = ImageView() self.saveprofile = False #TODO: preferred UI ? diff --git a/amsn2/gui/base/login.py b/amsn2/gui/base/login.py index 7c71af6f..af75dc7d 100644 --- a/amsn2/gui/base/login.py +++ b/amsn2/gui/base/login.py @@ -8,13 +8,17 @@ def __init__(self, amsn_core, parent): def show(self): """ Draw the login window """ raise NotImplementedError - + def hide(self): """ Hide the login window """ raise NotImplementedError - def switch_to_profile(self, profile): - """ This method will be called when the core needs the login window to switch to a different profile """ + def setProfiles(self, profileviews): + """ This method will be called when the core needs the login window to + let the user select among some profiles. + @profileviews: list of profileviews describing profiles + The first one in the list + should be considered as default. """ raise NotImplementedError def signin(self): From d6663b965de6b7871067f04925b96d8fddb75220 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 26 Apr 2009 19:28:10 +0200 Subject: [PATCH 039/147] Try to fix the EFL CL, does not work yet --- amsn2/gui/front_ends/efl/contact_list.py | 24 ++++++++++++------------ amsn2/gui/front_ends/efl/efl.py | 2 +- amsn2/gui/front_ends/efl/image.py | 2 +- amsn2/gui/front_ends/efl/login.py | 3 ++- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/amsn2/gui/front_ends/efl/contact_list.py b/amsn2/gui/front_ends/efl/contact_list.py index 3c2486d1..65e8ef54 100644 --- a/amsn2/gui/front_ends/efl/contact_list.py +++ b/amsn2/gui/front_ends/efl/contact_list.py @@ -4,7 +4,7 @@ import edje import ecore import ecore.evas -import etk +import elementary from image import * @@ -12,14 +12,17 @@ from amsn2.gui import base import pymsn -class aMSNContactListWindow(base.aMSNContactListWindow): +class aMSNContactListWindow(elementary.Box, base.aMSNContactListWindow): def __init__(self, amsn_core, parent): self._core = amsn_core self._evas = parent._evas self._parent = parent self._skin = amsn_core._skin_manager.skin + elementary.Box.__init__(self, parent) self._clwidget = aMSNContactListWidget(amsn_core, self) - parent.setChild(self._clwidget) + self._parent.resize_object_add(self) + self.size_hint_weight_set(1.0, 1.0) + self.pack_start(self._clwidget) self._clwidget.show() def show(self): @@ -38,15 +41,13 @@ def myInfoUpdated(self, view): pass #TODO -class aMSNContactListWidget(etk.ScrolledView, base.aMSNContactListWidget): +class aMSNContactListWidget(elementary.Scroller, base.aMSNContactListWidget): def __init__(self, amsn_core, parent): base.aMSNContactListWidget.__init__(self, amsn_core, parent) self._core = amsn_core self._evas = parent._evas self._skin = parent._skin - - self._etk_evas_object = etk.EvasObject() - etk.ScrolledView.__init__(self) + elementary.Scroller.__init__(self, parent) edje.frametime_set(1.0 / 30) try: @@ -58,13 +59,12 @@ def __init__(self, amsn_core, parent): self.group_holder = GroupHolder(self._evas, self) - self._etk_evas_object.evas_object = self._edje - self.add_with_viewport(self._etk_evas_object) self._edje.part_swallow("groups", self.group_holder); - + #elementary.Scroller.resize_object_add(self._edje) + self._edje.size_hint_weight_set(1.0, 1.0) + self.content_set(self._edje) self._edje.show() - self._etk_evas_object.show() def contactUpdated(self, contact): @@ -83,7 +83,7 @@ def contactListUpdated(self, clview): def size_request_set(self, w,h): - self._etk_evas_object.size_request_set(w,h) + self.size_hint_request_set(w,h) class ContactHolder(evas.SmartObject): diff --git a/amsn2/gui/front_ends/efl/efl.py b/amsn2/gui/front_ends/efl/efl.py index 5da512ce..e517c436 100644 --- a/amsn2/gui/front_ends/efl/efl.py +++ b/amsn2/gui/front_ends/efl/efl.py @@ -1,7 +1,7 @@ from main_loop import * from main import * from login import * -#from contact_list import * +from contact_list import * from image import * from splash import * from skins import * diff --git a/amsn2/gui/front_ends/efl/image.py b/amsn2/gui/front_ends/efl/image.py index ce79dd9f..e0cc9023 100644 --- a/amsn2/gui/front_ends/efl/image.py +++ b/amsn2/gui/front_ends/efl/image.py @@ -62,7 +62,7 @@ def _loadFromFileObject(self, fileobject, pos=0, view=None, i=0): self._loadFromFilename(tf, pos, view, i) - def _loadFromSkin(self, resource_name, pos=0, view=None, i=0): + def _loadFromTheme(self, resource_name, pos=0, view=None, i=0): res = self._skin.getKey(resource_name) if res is not None: (type, value) = res diff --git a/amsn2/gui/front_ends/efl/login.py b/amsn2/gui/front_ends/efl/login.py index 82763746..d26c870d 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -54,13 +54,14 @@ def __init__(self, amsn_core, parent): def show(self): + self._parent.resize_object_add(self._edje) self._edje.show() def hide(self): + self._parent.resize_object_del(self._edje) self._edje.hide() #FIXME: those are not hidden by self._edje.hide() self.password.hide() - self.status.hide() self.username.hide() try: getattr(self, "signin_b") From 3842d49bab736813c63afeeafdd1a7ba9897ba2b Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Thu, 30 Apr 2009 02:04:56 +0200 Subject: [PATCH 040/147] [Gtk] Fix login with username and password set from command line --- amsn2/gui/front_ends/gtk/login.py | 6 +++--- amsn2/gui/front_ends/gtk/main.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 6907e052..7856844b 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -28,7 +28,7 @@ from image import * from amsn2.core.views import ImageView -class aMSNLoginWindow(gtk.VBox): +class aMSNLoginWindow(gtk.VBox, base.aMSNLoginWindow): def __init__(self, amsn_core, parent): @@ -191,9 +191,9 @@ def switch_to_profile(self, profile): self.current_profile = profile if profile is not None: self._username = self.current_profile.username + self.user.get_children()[0].set_text(self._username) self._password = self.current_profile.password - - + self.password.set_text(self._password) def signin(self): self.current_profile.username = self.user.get_active_text() diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index a0924140..d1820cbd 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -18,7 +18,7 @@ def __on_show(self): self._amsn_core.mainWindowShown() def __on_close(self, widget, event): - exit(0) + self._amsn_core.quit() def show(self): self.main_win.show() From f3e15f7f96d013eadc421703b9bbe5df2871cd4c Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Tue, 28 Apr 2009 23:53:31 +0800 Subject: [PATCH 041/147] [Gtk] Allow setting username and password from command line Signed-off-by: jonte --- amsn2/gui/front_ends/gtk/login.py | 4 ++-- pymsn | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 6907e052..4cd7376c 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -191,9 +191,9 @@ def switch_to_profile(self, profile): self.current_profile = profile if profile is not None: self._username = self.current_profile.username + self.user.get_children()[0].set_text(self._username) self._password = self.current_profile.password - - + self.password.set_text(self._password) def signin(self): self.current_profile.username = self.user.get_active_text() diff --git a/pymsn b/pymsn index 290d4151..48cd26a8 160000 --- a/pymsn +++ b/pymsn @@ -1 +1 @@ -Subproject commit 290d4151f178891d55233e9f48c2ad233a186176 +Subproject commit 48cd26a8e674a8f9d97f5c227da33609641e28f9 From 7b4bfcc573e2e225641eea51336c66186d6f8c80 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Wed, 29 Apr 2009 07:38:49 +0800 Subject: [PATCH 042/147] [Gtk] Added menu to the main window Signed-off-by: jonte --- amsn2/gui/front_ends/gtk/login.py | 4 +- amsn2/gui/front_ends/gtk/main.py | 64 +++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 4cd7376c..46687728 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -28,7 +28,7 @@ from image import * from amsn2.core.views import ImageView -class aMSNLoginWindow(gtk.VBox): +class aMSNLoginWindow(gtk.VBox, base.aMSNLoginWindow): def __init__(self, amsn_core, parent): @@ -181,7 +181,7 @@ def __login_clicked(self, *args): self.signin() def show(self): - pass + self.show_all() def hide(self): if (self.timer is not None): diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index a0924140..8007be98 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -1,5 +1,6 @@ from amsn2.gui import base +from amsn2.core.views import MenuItemView import skins import gtk @@ -12,13 +13,17 @@ def __init__(self, amsn_core): self.main_win = gtk.Window() self.main_win.set_default_size(250, 500) self.main_win.connect('delete-event', self.__on_close) + self.main_menu = gtk.MenuBar() + inner = gtk.VBox() + inner.pack_start(self.main_menu, False, False) + self.main_win.add(inner) self.view = None def __on_show(self): self._amsn_core.mainWindowShown() def __on_close(self, widget, event): - exit(0) + self._amsn_core.quit() def show(self): self.main_win.show() @@ -34,14 +39,57 @@ def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView """ - pass + chldn = self.main_menu.get_children() + if len(chldn) is not 0: + for chl in chldn: + self.main_menu.remove(chl) + self._createMenuItemsFromView(self.main_menu, menu.items) + self.main_menu.show() + #if self.view is not None: + # self.view.pack_start(self.main_menu, False, False) + # self.view.reorder_child(self.main_menu, 0) + # self.view.show() + #self.main_win.add(self.view) + #self.main_win.show_all() + def _createMenuItemsFromView(self, menu, items): + # TODO: images & radio groups, for now only basic representation + for item in items: + if item.type is MenuItemView.COMMAND: + it = gtk.MenuItem(item.label) + it.connect("activate", lambda i, item: item.command(), item ) + it.show() + menu.append(it) + elif item.type is MenuItemView.CASCADE_MENU: + men = gtk.Menu() + it = gtk.MenuItem(item.label) + self._createMenuItemsFromView(men, item.items) + it.set_submenu(men) + it.show() + menu.append(it) + elif item.type is MenuItemView.SEPARATOR: + it = gtk.SeperatorMenuItem() + it.show() + menu.append(it) + elif item.type is MenuItemView.CHECKBUTTON: + it = gtk.CheckMenuItem(item.label) + if item.checkbox: + it.set_active() + it.show() + menu.append(it) + elif item.type is MenuItemView.RADIOBUTTON: + it = gtk.RadioMenuItem(item.label) + it.show() + menu.append(it) + elif item.type is MenuItemView.RADIOBUTTONGROUP: + pass def set_view(self, view): - current = self.main_win.get_child() - - if current: - self.main_win.remove(current) - - self.main_win.add(view) + inner = self.main_win.get_child() + chldn = inner.get_children() + for c in chldn: + if isinstance(c, base.aMSNLoginWindow) or isinstance(c, base.aMSNContactListWindow): + inner.remove(c) + + inner.pack_start(view) self.main_win.show_all() From 9653eb135700157320b4772df5ca9d32ee367f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Mon, 4 May 2009 21:26:13 +0200 Subject: [PATCH 043/147] Added cosmetic changes to the login window. Statuses are capitalized, default status is 'online'. --- amsn2/gui/front_ends/gtk/login.py | 5 ++++- pymsn | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 46687728..84bc9e72 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -24,6 +24,7 @@ import os import gtk import gobject +import string from image import * from amsn2.core.views import ImageView @@ -95,7 +96,9 @@ def __init__(self, amsn_core, parent): _, path = self._theme_manager.get_statusicon("buddy_%s" % name) if (name == 'offline'): continue icon = gtk.gdk.pixbuf_new_from_file(path) + name = string.capitalize(name) status_list.append([icon, name, key]) + iconCell = gtk.CellRendererPixbuf() iconCell.set_property('xalign', 0.0) @@ -105,7 +108,7 @@ def __init__(self, amsn_core, parent): # status combobox self.statusCombo = gtk.ComboBox() self.statusCombo.set_model(status_list) - self.statusCombo.set_active(0) + self.statusCombo.set_active(4) # Set status to 'online' self.statusCombo.pack_start(iconCell, False) self.statusCombo.pack_start(txtCell, False) self.statusCombo.add_attribute(iconCell, 'pixbuf',0) diff --git a/pymsn b/pymsn index 48cd26a8..290d4151 160000 --- a/pymsn +++ b/pymsn @@ -1 +1 @@ -Subproject commit 48cd26a8e674a8f9d97f5c227da33609641e28f9 +Subproject commit 290d4151f178891d55233e9f48c2ad233a186176 From 07835fe30e3f56947a67bae097c2a7f9e746385b Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Tue, 5 May 2009 01:11:56 +0200 Subject: [PATCH 044/147] [core] added basic implementation of status manager & status view They will manage the info of the profile such as nickname, image, etc; the gui can use the callbacks in the status view in order to apply the changes to the profile --- amsn2/core/__init__.py | 1 + amsn2/core/amsn.py | 5 ++ amsn2/core/status_manager.py | 63 ++++++++++++++++++++++++ amsn2/core/views/__init__.py | 1 + amsn2/core/views/statusview.py | 37 ++++++++++++++ amsn2/gui/front_ends/gtk/contact_list.py | 8 ++- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 amsn2/core/status_manager.py create mode 100644 amsn2/core/views/statusview.py diff --git a/amsn2/core/__init__.py b/amsn2/core/__init__.py index 6a102930..7bf6aba8 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -4,3 +4,4 @@ from views import * from lang import * from contactlist_manager import * +from status_manager import * diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 69250283..4536353d 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -27,6 +27,7 @@ from conversation_manager import * from oim_manager import * from theme_manager import * +from status_manager import * class aMSNCore(object): @@ -52,6 +53,7 @@ def __init__(self, options): self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) self._conversation_manager = aMSNConversationManager(self) + self._status_manager = aMSNStatusManager(self) self.p2s = {pymsn.Presence.ONLINE:"online", pymsn.Presence.BUSY:"busy", @@ -153,6 +155,9 @@ def connectionStateChanged(self, profile, state): self._main.setTitle("aMSN 2") profile.clwin.show() profile.login = None + self._status_manager.set_profile(profile.client.profile) + # need something better to update info, can't always access clwin + clwin.myInfoUpdated(self._status_manager._statusview) self._contactlist_manager.onCLDownloaded(profile.client.address_book) diff --git a/amsn2/core/status_manager.py b/amsn2/core/status_manager.py new file mode 100644 index 00000000..7d4b8587 --- /dev/null +++ b/amsn2/core/status_manager.py @@ -0,0 +1,63 @@ +from views import * + +class aMSNStatusManager(): + def __init__(self, core): + self._core = core + self._statusview = None + self._pymsn_profile = None + + def set_profile(self, pymsn_profile): + self._pymsn_profile = pymsn_profile + self._statusview = StatusView(self._core, pymsn_profile) + # TODO: update the contactlist gui from the core + + """ Actions from ourselves """ + def onNickUpdated(self, new_nick): + # TODO: parsing + self._pymsn_profile.display_name = new_nick + + def onPMUpdated(self, new_pm): + self._pymsn_profile.personal_message = new_pm + + def onDPUpdated(self, new_dp): + # TODO: manage msn_objects + pass + + def onPresenceUpdated(self, new_presence): + self._pymsn_profile.presence = presence + + """ actions from the core """ + def onCurrentMediaUpdated(self, new_media): + # TODO: update the contactlist gui from the core + pass + + # TODO: connect to pymsn signals + """ Actions from outside """ + def onNewMail(self, info): + pass + + def onOIM(self, oims): + pass + + +# necessary???? +class aMSNStatus(): + def __init__(self, core, pymsn_profile): + # TODO: parse fields for smileys, format, etc + self.nickname = StringView() + self.nickname.appendText(pymsn_profile.display_name) + self.psm = StringView() + self.psm.appendText(pymsn_profile.personal_message) + self.current_media = StringView() + if pymsn_profile.current_media is not None: + self.current_media.appendText(pymsn_profile.current_media[0]) + self.current_media.appendText(pymsn_profile.current_media[1]) + # TODO: How do I get the profile image? + self.image = ImageView() + #self.image.load(pymsn_profile.msn_object) + self.presence = core.p2s[pymsn_profile.presence] + # TODO: get more info, how to manage webcams and mail + self.webcam = None + self.mail_unread = None + + diff --git a/amsn2/core/views/__init__.py b/amsn2/core/views/__init__.py index 4dbfb176..36bfd58b 100644 --- a/amsn2/core/views/__init__.py +++ b/amsn2/core/views/__init__.py @@ -5,3 +5,4 @@ from tooltipview import * from messageview import * from imageview import * +from statusview import * diff --git a/amsn2/core/views/statusview.py b/amsn2/core/views/statusview.py new file mode 100644 index 00000000..30022b46 --- /dev/null +++ b/amsn2/core/views/statusview.py @@ -0,0 +1,37 @@ +from stringview import * +from imageview import * + +class StatusView(object): + def __init__(self, core, pymsn_profile): + # TODO: parse fields for smileys, format, etc + self.nickname = StringView() + self.nickname.appendText(pymsn_profile.display_name) + self.psm = StringView() + self.psm.appendText(pymsn_profile.personal_message) + self.current_media = StringView() + if pymsn_profile.current_media is not None: + self.current_media.appendText(pymsn_profile.current_media[0]) + self.current_media.appendText(pymsn_profile.current_media[1]) + # TODO: How do I get the profile image? + self.image = ImageView() + #self.image.load(pymsn_profile.msn_object) + self.presence = core.p2s[pymsn_profile.presence] + + # callbacks + def update_nick_cb(nickv): + core.status_manager.onNickUpdated(nickv) + self.update_nick = update_nick_cb + def update_presence_cb(presencev): + core.status_manager.onpresenceUpdated(presencev) + self.update_presence = update_presence_cb + def update_pm_cb(pmv): + core.status_manager.onPMUpdated(pmv) + self.update_pm = update_pm_cb + def update_dp_cb(dpv): + core.status_manager.onDPUpdated(dpv) + self.update_dp = update_dp_cb + # TODO: get more info, how to manage webcams and mail + self.webcam = None + self.mail_unread = None + + diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 9495561b..2ad0fb64 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -33,6 +33,7 @@ from amsn2.core.views import GroupView from amsn2.core.views import ContactView from amsn2.core.views import ImageView +from amsn2.core.views import StatusView from amsn2.gui import base import common @@ -181,9 +182,12 @@ def setMenu(self, menu): def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the contactView of the ourself (contains DP, nick, psm, + @view: the StatusView of the ourself (contains DP, nick, psm, currentMedia,...)""" - print view.name.toString() + self.nicklabel.set_markup(view.nickname.toString()) + self.psmlabel.set_markup(''+view.psm.toString()+'') + print 'nickname: '+view.nickname.toString() + print 'psm: '+view.psm.toString() class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): From 58a130312e17aa83bf7e3bc536dd89fa70d1b12f Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Tue, 5 May 2009 17:10:48 +0200 Subject: [PATCH 045/147] [core] fixed callbacks calling in status_manager [gtk] added support to changing presence status Remains some troubles setting the status 'offline' perhaps due to a bug in pymsn On gtk, needs to implement returning to login screen on disconnect --- amsn2/core/status_manager.py | 27 +++++------------------- amsn2/core/views/statusview.py | 8 +++---- amsn2/gui/front_ends/gtk/contact_list.py | 23 ++++++++++++++++---- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/amsn2/core/status_manager.py b/amsn2/core/status_manager.py index 7d4b8587..68cedc61 100644 --- a/amsn2/core/status_manager.py +++ b/amsn2/core/status_manager.py @@ -17,6 +17,7 @@ def onNickUpdated(self, new_nick): self._pymsn_profile.display_name = new_nick def onPMUpdated(self, new_pm): + # TODO: parsing self._pymsn_profile.personal_message = new_pm def onDPUpdated(self, new_dp): @@ -24,7 +25,10 @@ def onDPUpdated(self, new_dp): pass def onPresenceUpdated(self, new_presence): - self._pymsn_profile.presence = presence + for key in self._core.p2s: + if self._core.p2s[key] == new_presence: + break + self._pymsn_profile.presence = key """ actions from the core """ def onCurrentMediaUpdated(self, new_media): @@ -40,24 +44,3 @@ def onOIM(self, oims): pass -# necessary???? -class aMSNStatus(): - def __init__(self, core, pymsn_profile): - # TODO: parse fields for smileys, format, etc - self.nickname = StringView() - self.nickname.appendText(pymsn_profile.display_name) - self.psm = StringView() - self.psm.appendText(pymsn_profile.personal_message) - self.current_media = StringView() - if pymsn_profile.current_media is not None: - self.current_media.appendText(pymsn_profile.current_media[0]) - self.current_media.appendText(pymsn_profile.current_media[1]) - # TODO: How do I get the profile image? - self.image = ImageView() - #self.image.load(pymsn_profile.msn_object) - self.presence = core.p2s[pymsn_profile.presence] - # TODO: get more info, how to manage webcams and mail - self.webcam = None - self.mail_unread = None - - diff --git a/amsn2/core/views/statusview.py b/amsn2/core/views/statusview.py index 30022b46..84ea8e33 100644 --- a/amsn2/core/views/statusview.py +++ b/amsn2/core/views/statusview.py @@ -19,16 +19,16 @@ def __init__(self, core, pymsn_profile): # callbacks def update_nick_cb(nickv): - core.status_manager.onNickUpdated(nickv) + core._status_manager.onNickUpdated(nickv) self.update_nick = update_nick_cb def update_presence_cb(presencev): - core.status_manager.onpresenceUpdated(presencev) + core._status_manager.onPresenceUpdated(presencev) self.update_presence = update_presence_cb def update_pm_cb(pmv): - core.status_manager.onPMUpdated(pmv) + core._status_manager.onPMUpdated(pmv) self.update_pm = update_pm_cb def update_dp_cb(dpv): - core.status_manager.onDPUpdated(dpv) + core._status_manager.onDPUpdated(dpv) self.update_dp = update_dp_cb # TODO: get more info, how to manage webcams and mail self.webcam = None diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 2ad0fb64..14019539 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -58,6 +58,7 @@ def __init__(self, amsn_core, parent): self.show_all() self.__setup_window() + self._onStatusChanged = lambda *args: args def __create_controls(self): ###self.psmlabel.modify_font(common.GUI_FONT) @@ -89,11 +90,13 @@ def __create_controls(self): self.btnPsm.set_alignment(0,0) # status list + self.status_values = {} status_list = gtk.ListStore(gtk.gdk.Pixbuf, str, str) for key in self._amsn_core.p2s: name = self._amsn_core.p2s[key] + self.status_values[name] = self._amsn_core.p2s.values().index(name) _, path = self._theme_manager.get_statusicon("buddy_%s" % name) - if (name == 'offline'): continue + #if (name == 'offline'): continue #iv = ImageView("Skin", "buddy_%s" % name) #img = Image(self._skin, iv) #icon = img.to_pixbuf(28) @@ -114,6 +117,7 @@ def __create_controls(self): self.status.pack_start(txtCell, False) self.status.add_attribute(iconCell, 'pixbuf',0) self.status.add_attribute(txtCell, 'markup',1) + self.status.connect('changed', self.onStatusChanged) def __create_box(self): frameDisplay = gtk.Frame() @@ -184,10 +188,21 @@ def myInfoUpdated(self, view): ourself, such as DP, nick, psm, the current media being played,... @view: the StatusView of the ourself (contains DP, nick, psm, currentMedia,...)""" + # TODO: image, ... self.nicklabel.set_markup(view.nickname.toString()) - self.psmlabel.set_markup(''+view.psm.toString()+'') - print 'nickname: '+view.nickname.toString() - print 'psm: '+view.psm.toString() + message = view.psm.toString()+' '+view.current_media.toString() + self.psmlabel.set_markup(''+message+'') + self.status.set_active(self.status_values[view.presence]) + self._onStatusChanged = view.update_presence + + def onStatusChanged(self, combobox): + status = combobox.get_active() + for key in self.status_values: + if self.status_values[key] == status: + break + # FIXME: changing status to 'offline' will disconnect, so return to login window + # also fix pymsn, gives an error on setting 'offline' + self._onStatusChanged(key) class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): From bd3104003f8d43be9dd6766938be19951562e459 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Wed, 29 Apr 2009 07:40:31 +0800 Subject: [PATCH 046/147] Deleted old lines Signed-off-by: jonte --- amsn2/gui/front_ends/gtk/main.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index 8007be98..a15b6267 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -45,12 +45,6 @@ def setMenu(self, menu): self.main_menu.remove(chl) self._createMenuItemsFromView(self.main_menu, menu.items) self.main_menu.show() - #if self.view is not None: - # self.view.pack_start(self.main_menu, False, False) - # self.view.reorder_child(self.main_menu, 0) - # self.view.show() - #self.main_win.add(self.view) - #self.main_win.show_all() def _createMenuItemsFromView(self, menu, items): # TODO: images & radio groups, for now only basic representation From ed6adf7b87b1388382a2b2375296996fce2d5ffb Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Tue, 5 May 2009 07:11:56 +0800 Subject: [PATCH 047/147] [core] added basic implementation of status manager & status view They will manage the info of the profile such as nickname, image, etc; the gui can use the callbacks in the status view in order to apply the changes to the profile Signed-off-by: jonte --- amsn2/core/__init__.py | 1 + amsn2/core/amsn.py | 5 ++ amsn2/core/status_manager.py | 63 ++++++++++++++++++++++++ amsn2/core/views/__init__.py | 1 + amsn2/core/views/statusview.py | 37 ++++++++++++++ amsn2/gui/front_ends/gtk/contact_list.py | 8 ++- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 amsn2/core/status_manager.py create mode 100644 amsn2/core/views/statusview.py diff --git a/amsn2/core/__init__.py b/amsn2/core/__init__.py index 6a102930..7bf6aba8 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -4,3 +4,4 @@ from views import * from lang import * from contactlist_manager import * +from status_manager import * diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 69250283..4536353d 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -27,6 +27,7 @@ from conversation_manager import * from oim_manager import * from theme_manager import * +from status_manager import * class aMSNCore(object): @@ -52,6 +53,7 @@ def __init__(self, options): self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) self._conversation_manager = aMSNConversationManager(self) + self._status_manager = aMSNStatusManager(self) self.p2s = {pymsn.Presence.ONLINE:"online", pymsn.Presence.BUSY:"busy", @@ -153,6 +155,9 @@ def connectionStateChanged(self, profile, state): self._main.setTitle("aMSN 2") profile.clwin.show() profile.login = None + self._status_manager.set_profile(profile.client.profile) + # need something better to update info, can't always access clwin + clwin.myInfoUpdated(self._status_manager._statusview) self._contactlist_manager.onCLDownloaded(profile.client.address_book) diff --git a/amsn2/core/status_manager.py b/amsn2/core/status_manager.py new file mode 100644 index 00000000..7d4b8587 --- /dev/null +++ b/amsn2/core/status_manager.py @@ -0,0 +1,63 @@ +from views import * + +class aMSNStatusManager(): + def __init__(self, core): + self._core = core + self._statusview = None + self._pymsn_profile = None + + def set_profile(self, pymsn_profile): + self._pymsn_profile = pymsn_profile + self._statusview = StatusView(self._core, pymsn_profile) + # TODO: update the contactlist gui from the core + + """ Actions from ourselves """ + def onNickUpdated(self, new_nick): + # TODO: parsing + self._pymsn_profile.display_name = new_nick + + def onPMUpdated(self, new_pm): + self._pymsn_profile.personal_message = new_pm + + def onDPUpdated(self, new_dp): + # TODO: manage msn_objects + pass + + def onPresenceUpdated(self, new_presence): + self._pymsn_profile.presence = presence + + """ actions from the core """ + def onCurrentMediaUpdated(self, new_media): + # TODO: update the contactlist gui from the core + pass + + # TODO: connect to pymsn signals + """ Actions from outside """ + def onNewMail(self, info): + pass + + def onOIM(self, oims): + pass + + +# necessary???? +class aMSNStatus(): + def __init__(self, core, pymsn_profile): + # TODO: parse fields for smileys, format, etc + self.nickname = StringView() + self.nickname.appendText(pymsn_profile.display_name) + self.psm = StringView() + self.psm.appendText(pymsn_profile.personal_message) + self.current_media = StringView() + if pymsn_profile.current_media is not None: + self.current_media.appendText(pymsn_profile.current_media[0]) + self.current_media.appendText(pymsn_profile.current_media[1]) + # TODO: How do I get the profile image? + self.image = ImageView() + #self.image.load(pymsn_profile.msn_object) + self.presence = core.p2s[pymsn_profile.presence] + # TODO: get more info, how to manage webcams and mail + self.webcam = None + self.mail_unread = None + + diff --git a/amsn2/core/views/__init__.py b/amsn2/core/views/__init__.py index 4dbfb176..36bfd58b 100644 --- a/amsn2/core/views/__init__.py +++ b/amsn2/core/views/__init__.py @@ -5,3 +5,4 @@ from tooltipview import * from messageview import * from imageview import * +from statusview import * diff --git a/amsn2/core/views/statusview.py b/amsn2/core/views/statusview.py new file mode 100644 index 00000000..30022b46 --- /dev/null +++ b/amsn2/core/views/statusview.py @@ -0,0 +1,37 @@ +from stringview import * +from imageview import * + +class StatusView(object): + def __init__(self, core, pymsn_profile): + # TODO: parse fields for smileys, format, etc + self.nickname = StringView() + self.nickname.appendText(pymsn_profile.display_name) + self.psm = StringView() + self.psm.appendText(pymsn_profile.personal_message) + self.current_media = StringView() + if pymsn_profile.current_media is not None: + self.current_media.appendText(pymsn_profile.current_media[0]) + self.current_media.appendText(pymsn_profile.current_media[1]) + # TODO: How do I get the profile image? + self.image = ImageView() + #self.image.load(pymsn_profile.msn_object) + self.presence = core.p2s[pymsn_profile.presence] + + # callbacks + def update_nick_cb(nickv): + core.status_manager.onNickUpdated(nickv) + self.update_nick = update_nick_cb + def update_presence_cb(presencev): + core.status_manager.onpresenceUpdated(presencev) + self.update_presence = update_presence_cb + def update_pm_cb(pmv): + core.status_manager.onPMUpdated(pmv) + self.update_pm = update_pm_cb + def update_dp_cb(dpv): + core.status_manager.onDPUpdated(dpv) + self.update_dp = update_dp_cb + # TODO: get more info, how to manage webcams and mail + self.webcam = None + self.mail_unread = None + + diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 9495561b..2ad0fb64 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -33,6 +33,7 @@ from amsn2.core.views import GroupView from amsn2.core.views import ContactView from amsn2.core.views import ImageView +from amsn2.core.views import StatusView from amsn2.gui import base import common @@ -181,9 +182,12 @@ def setMenu(self, menu): def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the contactView of the ourself (contains DP, nick, psm, + @view: the StatusView of the ourself (contains DP, nick, psm, currentMedia,...)""" - print view.name.toString() + self.nicklabel.set_markup(view.nickname.toString()) + self.psmlabel.set_markup(''+view.psm.toString()+'') + print 'nickname: '+view.nickname.toString() + print 'psm: '+view.psm.toString() class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): From fd8e0a89647dea5883f143a5d5d5dfa909242056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Tue, 5 May 2009 23:36:58 +0200 Subject: [PATCH 048/147] [GTK] Added possibility to change nickname by clicking nickname button --- amsn2/gui/front_ends/gtk/contact_list.py | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 2ad0fb64..82ef2635 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -75,6 +75,7 @@ def __create_controls(self): self.btnNickname.set_relief(gtk.RELIEF_NONE) self.btnNickname.add(self.nicklabel) self.btnNickname.set_alignment(0,0) + self.btnNickname.connect("clicked",self.__on_btnNicknameClicked) self.psm = gtk.Entry() @@ -188,6 +189,29 @@ def myInfoUpdated(self, view): self.psmlabel.set_markup(''+view.psm.toString()+'') print 'nickname: '+view.nickname.toString() print 'psm: '+view.psm.toString() + + def __on_btnNicknameClicked(self, source): + self.__switchToNickInput() + + def __switchToNickInput(self): + """ Switches the nick button into a text area for editing of the nick + name.""" + #label = self.btnNickname.get_child() + self.btnNickname.get_child().destroy() + entry = gtk.Entry() + self.btnNickname.add(entry) + entry.show() + entry.connect("activate", self.__switchFromNickInput) + + def __switchFromNickInput(self, source): + """ When in the editing state of nickname, change back to the uneditable + label state. + """ + self._amsn_core._status_manager.onNickUpdated(source.get_text()); + self.btnNickname.get_child().destroy() + entry = self.nicklabel + self.btnNickname.add(entry) + entry.show() class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): @@ -242,7 +266,7 @@ def __on_contact_dblclick(self, widget, path, column): contactview = model.get_value(row, 1) contactview.on_click(contactview.uid) - + def __search_by_id(self, id): parent = self._model.get_iter_first() From e276b04a738cf36ef04974dca16bebab95b82c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Tue, 5 May 2009 22:48:07 +0100 Subject: [PATCH 049/147] Renamed pymsn to papyon --- .gitmodules | 6 +-- README | 2 +- amsn2.py | 2 +- amsn2/core/amsn.py | 34 ++++++------ amsn2/core/contactlist_manager.py | 54 ++++++++++---------- amsn2/core/conversation.py | 12 ++--- amsn2/gui/front_ends/console/contact_list.py | 22 ++++---- amsn2/gui/front_ends/efl/contact_list.py | 2 +- amsn2/gui/front_ends/gtk/chat_window.py | 22 ++++---- amsn2/gui/front_ends/gtk/contact_list.py | 2 +- amsn2/protocol/addressbook.py | 10 ++-- amsn2/protocol/client.py | 12 ++--- amsn2/protocol/contact.py | 8 +-- amsn2/protocol/conversation.py | 8 +-- amsn2/protocol/invite.py | 8 +-- amsn2/protocol/oim.py | 10 ++-- amsn2/protocol/protocol.py | 8 +-- papyon | 1 + pymsn | 1 - setupCocoa.py | 2 +- 20 files changed, 113 insertions(+), 113 deletions(-) create mode 160000 papyon delete mode 160000 pymsn diff --git a/.gitmodules b/.gitmodules index 127d57a4..01e1b92c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "pymsn"] - path = pymsn - url = git://github.com/Kjir/pymsn.git +[submodule "papyon"] + path = papyon + url = git://github.com/Kjir/papyon.git diff --git a/README b/README index a931a4bf..afe1ab00 100644 --- a/README +++ b/README @@ -5,7 +5,7 @@ python-pyopenssl python-crypto maybe some other stuff... -Before launching amsn2 you have to fetch the submodules (pymsn). Instructions can be found on the aMSN forum: +Before launching amsn2 you have to fetch the submodules (papyon). Instructions can be found on the aMSN forum: http://www.amsn-project.net/forums/viewtopic.php?t=5994 If you launch ./amsn2.py and it gives an error, that error probably tells you which dependency you need... diff --git a/amsn2.py b/amsn2.py index 69aa6994..05381f77 100755 --- a/amsn2.py +++ b/amsn2.py @@ -2,7 +2,7 @@ import sys import os import optparse -sys.path.insert(0, "./pymsn") +sys.path.insert(0, "./papyon") import locale locale.setlocale(locale.LC_ALL, '') diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 69250283..74a7e6d3 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -21,7 +21,7 @@ import profile from amsn2 import gui from amsn2 import protocol -import pymsn +import papyon from views import * from contactlist_manager import * from conversation_manager import * @@ -53,15 +53,15 @@ def __init__(self, options): self._oim_manager = aMSNOIMManager(self) self._conversation_manager = aMSNConversationManager(self) - self.p2s = {pymsn.Presence.ONLINE:"online", - pymsn.Presence.BUSY:"busy", - pymsn.Presence.IDLE:"idle", - pymsn.Presence.AWAY:"away", - pymsn.Presence.BE_RIGHT_BACK:"brb", - pymsn.Presence.ON_THE_PHONE:"phone", - pymsn.Presence.OUT_TO_LUNCH:"lunch", - pymsn.Presence.INVISIBLE:"hidden", - pymsn.Presence.OFFLINE:"offline"} + self.p2s = {papyon.Presence.ONLINE:"online", + papyon.Presence.BUSY:"busy", + papyon.Presence.IDLE:"idle", + papyon.Presence.AWAY:"away", + papyon.Presence.BE_RIGHT_BACK:"brb", + papyon.Presence.ON_THE_PHONE:"phone", + papyon.Presence.OUT_TO_LUNCH:"lunch", + papyon.Presence.INVISIBLE:"hidden", + papyon.Presence.OFFLINE:"offline"} if self._options.debug: import logging @@ -135,17 +135,17 @@ def connectionStateChanged(self, profile, state): status_str = \ { - pymsn.event.ClientState.CONNECTING : 'Connecting to server...', - pymsn.event.ClientState.CONNECTED : 'Connected', - pymsn.event.ClientState.AUTHENTICATING : 'Authenticating...', - pymsn.event.ClientState.AUTHENTICATED : 'Password accepted', - pymsn.event.ClientState.SYNCHRONIZING : 'Please wait while your contact list\nis being downloaded...', - pymsn.event.ClientState.SYNCHRONIZED : 'Contact list downloaded successfully.\nHappy Chatting' + papyon.event.ClientState.CONNECTING : 'Connecting to server...', + papyon.event.ClientState.CONNECTED : 'Connected', + papyon.event.ClientState.AUTHENTICATING : 'Authenticating...', + papyon.event.ClientState.AUTHENTICATED : 'Password accepted', + papyon.event.ClientState.SYNCHRONIZING : 'Please wait while your contact list\nis being downloaded...', + papyon.event.ClientState.SYNCHRONIZED : 'Contact list downloaded successfully.\nHappy Chatting' } if state in status_str: profile.login.onConnecting((state + 1)/ 7., status_str[state]) - elif state == pymsn.event.ClientState.OPEN: + elif state == papyon.event.ClientState.OPEN: clwin = self._gui.gui.aMSNContactListWindow(self, self._main) clwin.profile = profile profile.clwin = clwin diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index 9fadbe35..a9e381b2 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -1,7 +1,7 @@ from views import * import os import tempfile -import pymsn +import papyon class aMSNContactListManager: @@ -18,7 +18,7 @@ def __init__(self, core): self._contacts = {} self._groups = {} - self._pymsn_addressbook = None + self._papyon_addressbook = None #TODO: sorting contacts & groups @@ -44,10 +44,10 @@ def unregister(self, event, callback): - def onContactPresenceChanged(self, pymsn_contact): + def onContactPresenceChanged(self, papyon_contact): #1st/ update the aMSNContact object - c = self.getContact(pymsn_contact.id, pymsn_contact) - c.fill(self._core, pymsn_contact) + c = self.getContact(papyon_contact.id, papyon_contact) + c.fill(self._core, papyon_contact) #2nd/ update the ContactView cv = ContactView(self._core, c) self.emit(self.CONTACTVIEW_UPDATED, cv) @@ -55,14 +55,14 @@ def onContactPresenceChanged(self, pymsn_contact): #TODO: update the group view #Request the DP... - if (pymsn_contact.presence is not pymsn.Presence.OFFLINE and - pymsn_contact.msn_object): - self._core._profile.client._msn_object_store.request(pymsn_contact.msn_object, + if (papyon_contact.presence is not papyon.Presence.OFFLINE and + papyon_contact.msn_object): + self._core._profile.client._msn_object_store.request(papyon_contact.msn_object, (self.onDPdownloaded, - pymsn_contact.id)) + papyon_contact.id)) def onCLDownloaded(self, address_book): - self._pymsn_addressbook = address_book + self._papyon_addressbook = address_book grpviews = [] cviews = [] clv = ContactListView() @@ -80,7 +80,7 @@ def onCLDownloaded(self, address_book): grpviews.append(gv) clv.group_ids.append(group.id) - contacts = address_book.contacts.search_by_memberships(pymsn.Membership.FORWARD) + contacts = address_book.contacts.search_by_memberships(papyon.Membership.FORWARD) no_group_ids= [] for contact in contacts: if len(contact.groups) == 0: @@ -116,13 +116,13 @@ def onDPdownloaded(self, msn_object, uid): self.emit(self.CONTACTVIEW_UPDATED, cv) - def getContact(self, cid, pymsn_contact=None): + def getContact(self, cid, papyon_contact=None): #TODO: should raise UnknownContact or sthg like that try: return self._contacts[cid] except KeyError: - if pymsn_contact is not None: - c = aMSNContact(self._core, pymsn_contact) + if papyon_contact is not None: + c = aMSNContact(self._core, papyon_contact) self._contacts[cid] = c self.emit(self.AMSNCONTACT_UPDATED, c) return c @@ -135,29 +135,29 @@ def getContact(self, cid, pymsn_contact=None): everytime """ class aMSNContact(): - def __init__(self, core, pymsn_contact): - self.uid = pymsn_contact.id - self.fill(core, pymsn_contact) + def __init__(self, core, papyon_contact): + self.uid = papyon_contact.id + self.fill(core, papyon_contact) - def fill(self, core, pymsn_contact): + def fill(self, core, papyon_contact): self.icon = ImageView() - self.icon.load("Theme","buddy_" + core.p2s[pymsn_contact.presence]) + self.icon.load("Theme","buddy_" + core.p2s[papyon_contact.presence]) self.dp = ImageView() #TODO: for the moment, use default dp self.dp.load("Theme", "dp_nopic") self.emblem = ImageView() - self.emblem.load("Theme", "emblem_" + core.p2s[pymsn_contact.presence]) + self.emblem.load("Theme", "emblem_" + core.p2s[papyon_contact.presence]) #TODO: PARSE ONLY ONCE self.nickname = StringView() - self.nickname.appendText(pymsn_contact.display_name) + self.nickname.appendText(papyon_contact.display_name) self.personal_message = StringView() - self.personal_message.appendText(pymsn_contact.personal_message) + self.personal_message.appendText(papyon_contact.personal_message) self.current_media = StringView() - self.current_media.appendText(pymsn_contact.current_media) + self.current_media.appendText(papyon_contact.current_media) self.status = StringView() - self.status.appendText(core.p2s[pymsn_contact.presence]) - #for the moment, we store the pymsn_contact object, but we shouldn't have to - #TODO: getPymsnContact(self, core...) or _pymsn_contact? - self._pymsn_contact = pymsn_contact + self.status.appendText(core.p2s[papyon_contact.presence]) + #for the moment, we store the papyon_contact object, but we shouldn't have to + #TODO: getPymsnContact(self, core...) or _papyon_contact? + self._papyon_contact = papyon_contact diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index 1f121f04..8e2800c1 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -21,7 +21,7 @@ from amsn2.protocol import conversation from amsn2.core.contactlist_manager import * from amsn2.core.views import * -import pymsn +import papyon class aMSNConversation: def __init__(self, core, conv_manager, conv = None, contacts_uid = None): @@ -33,10 +33,10 @@ def __init__(self, core, conv_manager, conv = None, contacts_uid = None): self._contacts_uid = contacts_uid if conv is None: #New conversation - pymsn_contacts = [core._contactlist_manager.getContact(uid) for uid in contacts_uid] - pymsn_contacts = [c._pymsn_contact for c in pymsn_contacts if c is not None] + papyon_contacts = [core._contactlist_manager.getContact(uid) for uid in contacts_uid] + papyon_contacts = [c._papyon_contact for c in papyon_contacts if c is not None] #if c was None.... wtf? - self._conv = pymsn.Conversation(self._core._profile.client, pymsn_contacts) + self._conv = papyon.Conversation(self._core._profile.client, papyon_contacts) else: #From an existing conversation self._conv = conv @@ -90,7 +90,7 @@ def sendMessage(self, msg, formatting=None): # for the moment, no formatting, no smiley substitution... (TODO) # peacey: Added formatting of styles self.onMessageReceived(msg, formatting=formatting) - message = pymsn.ConversationMessage(msg.toString(), formatting) + message = papyon.ConversationMessage(msg.toString(), formatting) self._conv.send_text_message(message) def sendNudge(self): @@ -105,6 +105,6 @@ def leave(self): def inviteContact(self, contact_uid): """ contact_uid is the Id of the contact to invite """ c = self._core._contactlist_manager.getContact(contact_uid) - self._conv.invite_user(contact.pymsn_contact) + self._conv.invite_user(contact.papyon_contact) #TODO: ... diff --git a/amsn2/gui/front_ends/console/contact_list.py b/amsn2/gui/front_ends/console/contact_list.py index f90ca22c..fdcdeb58 100644 --- a/amsn2/gui/front_ends/console/contact_list.py +++ b/amsn2/gui/front_ends/console/contact_list.py @@ -1,23 +1,23 @@ from amsn2.gui import base -import pymsn +import papyon class Contact(object): def __init__(self, name, presence): self.name = name self.presence = presence - self.p2s = {pymsn.Presence.ONLINE:"online", - pymsn.Presence.BUSY:"busy", - pymsn.Presence.IDLE:"idle", - pymsn.Presence.AWAY:"away", - pymsn.Presence.BE_RIGHT_BACK:"brb", - pymsn.Presence.ON_THE_PHONE:"phone", - pymsn.Presence.OUT_TO_LUNCH:"lunch", - pymsn.Presence.INVISIBLE:"hidden", - pymsn.Presence.OFFLINE:"offline"} + self.p2s = {papyon.Presence.ONLINE:"online", + papyon.Presence.BUSY:"busy", + papyon.Presence.IDLE:"idle", + papyon.Presence.AWAY:"away", + papyon.Presence.BE_RIGHT_BACK:"brb", + papyon.Presence.ON_THE_PHONE:"phone", + papyon.Presence.OUT_TO_LUNCH:"lunch", + papyon.Presence.INVISIBLE:"hidden", + papyon.Presence.OFFLINE:"offline"} def is_online(self): - return self.presence != pymsn.Presence.OFFLINE + return self.presence != papyon.Presence.OFFLINE def status(self): return self.p2s[self.presence] diff --git a/amsn2/gui/front_ends/efl/contact_list.py b/amsn2/gui/front_ends/efl/contact_list.py index 3c2486d1..cfdbeefb 100644 --- a/amsn2/gui/front_ends/efl/contact_list.py +++ b/amsn2/gui/front_ends/efl/contact_list.py @@ -10,7 +10,7 @@ from amsn2.core.views import StringView from amsn2.gui import base -import pymsn +import papyon class aMSNContactListWindow(base.aMSNContactListWindow): def __init__(self, amsn_core, parent): diff --git a/amsn2/gui/front_ends/gtk/chat_window.py b/amsn2/gui/front_ends/gtk/chat_window.py index 557b1e81..b5c8cdc3 100644 --- a/amsn2/gui/front_ends/gtk/chat_window.py +++ b/amsn2/gui/front_ends/gtk/chat_window.py @@ -30,7 +30,7 @@ from amsn2.gui import base from amsn2.core.views import StringView import gtk_extras -import pymsn +import papyon class aMSNChatWindow(base.aMSNChatWindow, gtk.Window): def __init__(self, amsn_core): @@ -247,14 +247,14 @@ def __on_chat_send(self, entry, event_keyval, event_keymod): color = self.button_color.get_color() hex8 = "%.2x%.2x%.2x" % ((color.red/0x101), (color.green/0x101), (color.blue/0x101)) - style = pymsn.TextFormat.NO_EFFECT - if self.button_bold.get_active(): style |= pymsn.TextFormat.BOLD - if self.button_italic.get_active(): style |= pymsn.TextFormat.ITALIC - if self.button_underline.get_active(): style |= pymsn.TextFormat.UNDERLINE - if self.button_strikethrough.get_active(): style |= pymsn.TextFormat.STRIKETHROUGH + style = papyon.TextFormat.NO_EFFECT + if self.button_bold.get_active(): style |= papyon.TextFormat.BOLD + if self.button_italic.get_active(): style |= papyon.TextFormat.ITALIC + if self.button_underline.get_active(): style |= papyon.TextFormat.UNDERLINE + if self.button_strikethrough.get_active(): style |= papyon.TextFormat.STRIKETHROUGH font_name = self.button_font.get_font_name() font_family = pango.FontDescription(font_name).get_family() - format = pymsn.TextFormat(font=font_family, color=hex8, style=style) + format = papyon.TextFormat(font=font_family, color=hex8, style=style) strv = StringView() strv.appendText(msg) self._amsn_conversation.sendMessage(strv, format) @@ -308,13 +308,13 @@ def onMessageReceived(self, messageview, formatting=None): fmsg += "font-family: %s;" % formatting.font if formatting.color: fmsg += "color: %s;" % ("#"+formatting.color) - if formatting.style & pymsn.TextFormat.BOLD == pymsn.TextFormat.BOLD: + if formatting.style & papyon.TextFormat.BOLD == papyon.TextFormat.BOLD: fmsg += "font-weight: bold;" - if formatting.style & pymsn.TextFormat.ITALIC == pymsn.TextFormat.ITALIC: + if formatting.style & papyon.TextFormat.ITALIC == papyon.TextFormat.ITALIC: fmsg += "font-style: italic;" - if formatting.style & pymsn.TextFormat.UNDERLINE == pymsn.TextFormat.UNDERLINE: + if formatting.style & papyon.TextFormat.UNDERLINE == papyon.TextFormat.UNDERLINE: fmsg += "text-decoration: underline;" - if formatting.style & pymsn.TextFormat.STRIKETHROUGH == pymsn.TextFormat.STRIKETHROUGH: + if formatting.style & papyon.TextFormat.STRIKETHROUGH == papyon.TextFormat.STRIKETHROUGH: fmsg += "text-decoration: line-through;" if formatting.right_alignment: fmsg += "text-align: right;" diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 9495561b..9bc6a37d 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -27,7 +27,7 @@ import pango import gobject -#import pymsn +#import papyon from image import * from amsn2.core.views import StringView from amsn2.core.views import GroupView diff --git a/amsn2/protocol/addressbook.py b/amsn2/protocol/addressbook.py index add86648..7566102a 100644 --- a/amsn2/protocol/addressbook.py +++ b/amsn2/protocol/addressbook.py @@ -18,13 +18,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import pymsn -import pymsn.event +import papyon +import papyon.event -class AddressBookEvents(pymsn.event.AddressBookEventInterface): +class AddressBookEvents(papyon.event.AddressBookEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core - pymsn.event.AddressBookEventInterface.__init__(self, client) + papyon.event.AddressBookEventInterface.__init__(self, client) def on_addressbook_messenger_contact_added(self, contact): pass @@ -52,4 +52,4 @@ def on_addressbook_group_contact_added(self, group, contact): def on_addressbook_group_contact_deleted(self, group, contact): pass - \ No newline at end of file + diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 3684ac32..8f4f51d7 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -1,19 +1,19 @@ -import pymsn -import pymsn.event +import papyon +import papyon.event -class ClientEvents(pymsn.event.ClientEventInterface): +class ClientEvents(papyon.event.ClientEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core - pymsn.event.ClientEventInterface.__init__(self, client) + papyon.event.ClientEventInterface.__init__(self, client) def on_client_state_changed(self, state): self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) - if state == pymsn.event.ClientState.OPEN: + if state == papyon.event.ClientState.OPEN: self._client.profile.display_name = "aMSN2" - self._client.profile.presence = pymsn.Presence.ONLINE + self._client.profile.presence = papyon.Presence.ONLINE self._client.profile.current_media = ("I listen to", "Nothing") self._client.profile.personal_message = "Testing aMSN2!" diff --git a/amsn2/protocol/contact.py b/amsn2/protocol/contact.py index 83949b68..548b5118 100644 --- a/amsn2/protocol/contact.py +++ b/amsn2/protocol/contact.py @@ -1,12 +1,12 @@ -import pymsn -import pymsn.event +import papyon +import papyon.event -class ContactEvents(pymsn.event.ContactEventInterface): +class ContactEvents(papyon.event.ContactEventInterface): def __init__(self, client, contact_manager): self._contact_manager = contact_manager - pymsn.event.ContactEventInterface.__init__(self, client) + papyon.event.ContactEventInterface.__init__(self, client) def on_contact_presence_changed(self, contact): self._contact_manager.onContactPresenceChanged(contact) diff --git a/amsn2/protocol/conversation.py b/amsn2/protocol/conversation.py index 181ba711..0a487cc2 100644 --- a/amsn2/protocol/conversation.py +++ b/amsn2/protocol/conversation.py @@ -19,15 +19,15 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from amsn2.core.views import * -import pymsn -import pymsn.event +import papyon +import papyon.event -class ConversationEvents(pymsn.event.ConversationEventInterface): +class ConversationEvents(papyon.event.ConversationEventInterface): def __init__(self, amsn_conversation): self._amsn_conversation = amsn_conversation self._conversation = amsn_conversation._conv - pymsn.event.ConversationEventInterface.__init__(self, self._conversation) + papyon.event.ConversationEventInterface.__init__(self, self._conversation) def on_conversation_state_changed(self, state): self._amsn_conversation.onStateChanged(state) diff --git a/amsn2/protocol/invite.py b/amsn2/protocol/invite.py index 276a0783..e155fd0e 100644 --- a/amsn2/protocol/invite.py +++ b/amsn2/protocol/invite.py @@ -1,13 +1,13 @@ -import pymsn -import pymsn.event +import papyon +import papyon.event -class InviteEvents(pymsn.event.InviteEventInterface): +class InviteEvents(papyon.event.InviteEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core - pymsn.event.InviteEventInterface.__init__(self, client) + papyon.event.InviteEventInterface.__init__(self, client) def on_invite_conversation(self, conversation): self._amsn_core._conversation_manager.onInviteConversation(conversation) diff --git a/amsn2/protocol/oim.py b/amsn2/protocol/oim.py index d678d3bc..d13891a4 100644 --- a/amsn2/protocol/oim.py +++ b/amsn2/protocol/oim.py @@ -18,13 +18,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import pymsn -import pymsn.event +import papyon +import papyon.event -class OIMEvents(pymsn.event.OfflineMessagesEventInterface): +class OIMEvents(papyon.event.OfflineMessagesEventInterface): def __init__(self, client, oim_manager): self._oim_manager = oim_manager - pymsn.event.OfflineMessagesEventInterface.__init__(self, client) + papyon.event.OfflineMessagesEventInterface.__init__(self, client) def on_oim_state_changed(self, state): pass @@ -39,4 +39,4 @@ def on_oim_messages_deleted(self): pass def on_oim_message_sent(self, recipient, message): - pass \ No newline at end of file + pass diff --git a/amsn2/protocol/protocol.py b/amsn2/protocol/protocol.py index cf968d54..9cfe4ef5 100644 --- a/amsn2/protocol/protocol.py +++ b/amsn2/protocol/protocol.py @@ -18,21 +18,21 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import pymsn -import pymsn.event +import papyon +import papyon.event from client import * from contact import * from invite import * from oim import * from addressbook import * -class Client(pymsn.Client): +class Client(papyon.Client): def __init__(self, amsn_core, profile): self._amsn_profile = profile self._amsn_core = amsn_core server = (self._amsn_profile.getConfigKey("ns_server", "messenger.hotmail.com"), self._amsn_profile.getConfigKey("ns_port", 1863)) - pymsn.Client.__init__(self, server) + papyon.Client.__init__(self, server) self._client_events_handler = ClientEvents(self, self._amsn_core) self._contact_events_handler = ContactEvents(self, self._amsn_core._contactlist_manager) diff --git a/papyon b/papyon new file mode 160000 index 00000000..dcae9f0d --- /dev/null +++ b/papyon @@ -0,0 +1 @@ +Subproject commit dcae9f0dd4fec82a6847b664174cf7cb00c44ea0 diff --git a/pymsn b/pymsn deleted file mode 160000 index 290d4151..00000000 --- a/pymsn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 290d4151f178891d55233e9f48c2ad233a186176 diff --git a/setupCocoa.py b/setupCocoa.py index d4352efe..d9e7fd3f 100644 --- a/setupCocoa.py +++ b/setupCocoa.py @@ -5,7 +5,7 @@ from dircache import listdir # A list of files to include in the bundle. -files = ['amsn2', 'pymsn'] +files = ['amsn2', 'papyon'] # Nibs need to be appened individually because they they need to be in the root of the bundle. for f in listdir('amsn2/gui/front_ends/cocoa/nibs/files/'): From cf9f904d2bb005a406a6690a1e2bb7a1f224ef13 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Tue, 5 May 2009 23:57:52 +0200 Subject: [PATCH 050/147] [core] setting personal info happens directly from status_manager so deleting methods in protocol.Client also delete OIM method from status_manager, as there is already an OIM manager.. --- amsn2/core/status_manager.py | 6 ++---- amsn2/protocol/protocol.py | 5 ----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/amsn2/core/status_manager.py b/amsn2/core/status_manager.py index 68cedc61..0f627c13 100644 --- a/amsn2/core/status_manager.py +++ b/amsn2/core/status_manager.py @@ -5,6 +5,7 @@ def __init__(self, core): self._core = core self._statusview = None self._pymsn_profile = None + self._amsn_profile = core.profile def set_profile(self, pymsn_profile): self._pymsn_profile = pymsn_profile @@ -35,12 +36,9 @@ def onCurrentMediaUpdated(self, new_media): # TODO: update the contactlist gui from the core pass - # TODO: connect to pymsn signals + # TODO: connect to pymsn event, maybe build a mailbox_manager """ Actions from outside """ def onNewMail(self, info): pass - def onOIM(self, oims): - pass - diff --git a/amsn2/protocol/protocol.py b/amsn2/protocol/protocol.py index cf968d54..e3ecc3e2 100644 --- a/amsn2/protocol/protocol.py +++ b/amsn2/protocol/protocol.py @@ -43,9 +43,4 @@ def __init__(self, amsn_core, profile): def connect(self): self.login(self._amsn_profile.email, self._amsn_profile.password) - def changeNick(self, nick): - self.profile.display_name = nick.toString() - - def changeMessage(self, message): - self.profile.personal_message = message.toString() From ec1813c5294951effa1cc19230b6b606c14dccbb Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Thu, 30 Apr 2009 08:04:56 +0800 Subject: [PATCH 051/147] [Gtk] Fix login with username and password set from command line Signed-off-by: Boris Faure --- amsn2/gui/front_ends/gtk/login.py | 6 +++--- amsn2/gui/front_ends/gtk/main.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 6907e052..7856844b 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -28,7 +28,7 @@ from image import * from amsn2.core.views import ImageView -class aMSNLoginWindow(gtk.VBox): +class aMSNLoginWindow(gtk.VBox, base.aMSNLoginWindow): def __init__(self, amsn_core, parent): @@ -191,9 +191,9 @@ def switch_to_profile(self, profile): self.current_profile = profile if profile is not None: self._username = self.current_profile.username + self.user.get_children()[0].set_text(self._username) self._password = self.current_profile.password - - + self.password.set_text(self._password) def signin(self): self.current_profile.username = self.user.get_active_text() diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index a0924140..d1820cbd 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -18,7 +18,7 @@ def __on_show(self): self._amsn_core.mainWindowShown() def __on_close(self, widget, event): - exit(0) + self._amsn_core.quit() def show(self): self.main_win.show() From 361e92036db86b342ae853e0b4bff25816866492 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Wed, 6 May 2009 01:47:27 +0200 Subject: [PATCH 052/147] [gtk] when changing the nick, display the new instead of the default --- amsn2/gui/front_ends/gtk/contact_list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 05d05f02..436a34c0 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -190,6 +190,7 @@ def myInfoUpdated(self, view): @view: the StatusView of the ourself (contains DP, nick, psm, currentMedia,...)""" # TODO: image, ... + # FIXME: status at login, now seems 'offline' even if we are online self.nicklabel.set_markup(view.nickname.toString()) message = view.psm.toString()+' '+view.current_media.toString() self.psmlabel.set_markup(''+message+'') @@ -222,9 +223,10 @@ def __switchFromNickInput(self, source): """ When in the editing state of nickname, change back to the uneditable label state. """ - self._amsn_core._status_manager.onNickUpdated(source.get_text()); + self._amsn_core._status_manager.onNickUpdated(source.get_text()) self.btnNickname.get_child().destroy() entry = self.nicklabel + entry.set_markup(source.get_text()) self.btnNickname.add(entry) entry.show() From 069cb2bcd37b9e42037dc4b4b4ccdc02b8d942ef Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Wed, 6 May 2009 01:54:07 +0200 Subject: [PATCH 053/147] Fixed name on base gui, maybe need to find a more expressive name for StatusView status_manager --- amsn2/gui/base/contact_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/gui/base/contact_list.py b/amsn2/gui/base/contact_list.py index 341ed0d7..b3662811 100644 --- a/amsn2/gui/base/contact_list.py +++ b/amsn2/gui/base/contact_list.py @@ -38,7 +38,7 @@ def setMenu(self, menu): def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the contactView of the ourself (contains DP, nick, psm, + @view: the StatusView of the ourself (contains DP, nick, psm, currentMedia,...)""" raise NotImplementedError From e5bd1fc3f01e1f784d66f7b004743fe514107ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Wed, 6 May 2009 13:44:06 +0100 Subject: [PATCH 054/147] curses: Fixed behaviour when password is set from command line --- amsn2/gui/front_ends/curses/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index 849e032e..a9c7b1d9 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -25,8 +25,8 @@ def _insert(self, txt): class PasswordBox(TextBox): def __init__(self, win, y, x, txt): - super(PasswordBox, self).__init__(win, y, x, txt) self._password = '' + super(PasswordBox, self).__init__(win, y, x, txt) def edit(self, cb=None): return self._txtbox.edit(self._validateInput) From a5d0c4d0d8399cd2f96b3e331e7b0df0f2374f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Wed, 6 May 2009 17:17:29 +0100 Subject: [PATCH 055/147] curses: Implemented line highlighter in CL --- amsn2/gui/front_ends/curses/contact_list.py | 33 ++++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index 53fa12e5..f084cef1 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -23,12 +23,6 @@ def hide(self): self._stdscr.clear() self._stdscr.refresh() - def configure(self, option, value): - pass - - def cget(self, option, value): - pass - class aMSNContactListWidget(base.aMSNContactListWidget): def __init__(self, amsn_core, parent): @@ -101,23 +95,46 @@ def __repaint(self): # Acquire the lock to do modifications with self._mod_lock: self._win.clear() + (y, x) = self._stdscr.getmaxyx() self._win.move(0,0) - gso = self._groups_order + available = y + gso = [] + for g in self._groups_order: + available -= 1 + available -= len(self._groups[g].contact_ids) + gso.append(g) + if available <= 0: + break gso.reverse() + available = y + i = 0 for g in gso: if self._groups[g] is not None: + available -= 1 cids = self._groups[g].contact_ids + cids = cids[:available] cids.reverse() for c in cids: if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: - self._win.insstr(" " + self._contacts[c]['cView'].name.toString()) + if i == y - 3: + self._win.bkgdset(curses.color_pair(1)) + self._win.insstr(self._contacts[c]['cView'].name.toString()) + self._win.bkgdset(curses.color_pair(0)) + self._win.insch(' ') self._win.insch(curses.ACS_HLINE) self._win.insch(curses.ACS_HLINE) self._win.insch(curses.ACS_LLCORNER) self._win.insertln() + self._win.bkgdset(curses.color_pair(0)) + i += 1 + if i == y - 3: + self._win.bkgdset(curses.color_pair(1)) self._win.insstr(self._groups[g].name.toString()) + self._win.bkgdset(curses.color_pair(0)) + self._win.insch(' ') self._win.insch(curses.ACS_LLCORNER) self._win.insertln() + i += 1 self._win.refresh() self._modified = False From 9be17eba206731729ef67d3f89d0e1ee981bdb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Wed, 6 May 2009 22:02:09 +0100 Subject: [PATCH 056/147] Refactored some names in the protocol module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored some file names in the protocol module, so that it better reflects the class names inside the file. All the events have been moved to a subdirectory and all references have been updated. Signed-off-by: Stéphane Bisinger --- amsn2/core/conversation.py | 2 +- amsn2/protocol/__init__.py | 2 +- amsn2/protocol/client.py | 60 +++++++++++++++------ amsn2/protocol/events/__init__.py | 1 + amsn2/protocol/{ => events}/addressbook.py | 0 amsn2/protocol/events/client.py | 23 ++++++++ amsn2/protocol/{ => events}/contact.py | 0 amsn2/protocol/{ => events}/conversation.py | 0 amsn2/protocol/{ => events}/invite.py | 0 amsn2/protocol/{ => events}/oim.py | 0 amsn2/protocol/protocol.py | 51 ------------------ 11 files changed, 70 insertions(+), 69 deletions(-) create mode 100644 amsn2/protocol/events/__init__.py rename amsn2/protocol/{ => events}/addressbook.py (100%) create mode 100644 amsn2/protocol/events/client.py rename amsn2/protocol/{ => events}/contact.py (100%) rename amsn2/protocol/{ => events}/conversation.py (100%) rename amsn2/protocol/{ => events}/invite.py (100%) rename amsn2/protocol/{ => events}/oim.py (100%) delete mode 100644 amsn2/protocol/protocol.py diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index 8e2800c1..ea8c14ea 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -18,7 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from amsn2.protocol import conversation +from amsn2.protocol.events import conversation from amsn2.core.contactlist_manager import * from amsn2.core.views import * import papyon diff --git a/amsn2/protocol/__init__.py b/amsn2/protocol/__init__.py index 6b5043af..1d20dac0 100644 --- a/amsn2/protocol/__init__.py +++ b/amsn2/protocol/__init__.py @@ -1 +1 @@ -from protocol import * +from client import * diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 8f4f51d7..d971a0c9 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -1,23 +1,51 @@ +# -*- coding: utf-8 -*- +# +# amsn - a python client for the WLM Network +# +# Copyright (C) 2008 Dario Freddi +# +# 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import papyon import papyon.event +from events.client import * +from events.contact import * +from events.invite import * +from events.oim import * +from events.addressbook import * - -class ClientEvents(papyon.event.ClientEventInterface): - def __init__(self, client, amsn_core): +class Client(papyon.Client): + def __init__(self, amsn_core, profile): + self._amsn_profile = profile self._amsn_core = amsn_core - papyon.event.ClientEventInterface.__init__(self, client) + server = (self._amsn_profile.getConfigKey("ns_server", "messenger.hotmail.com"), + self._amsn_profile.getConfigKey("ns_port", 1863)) + papyon.Client.__init__(self, server) + + self._client_events_handler = ClientEvents(self, self._amsn_core) + self._contact_events_handler = ContactEvents(self, self._amsn_core._contactlist_manager) + self._invite_events_handler = InviteEvents(self, self._amsn_core) + self._oim_events_handler = OIMEvents(self, self._amsn_core._oim_manager) + self._addressbook_events_handler = AddressBookEvents(self, self._amsn_core) + + def connect(self): + self.login(self._amsn_profile.email, self._amsn_profile.password) + + def changeNick(self, nick): + self.profile.display_name = nick.toString() - def on_client_state_changed(self, state): - self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) - - if state == papyon.event.ClientState.OPEN: - self._client.profile.display_name = "aMSN2" - self._client.profile.presence = papyon.Presence.ONLINE - self._client.profile.current_media = ("I listen to", "Nothing") - self._client.profile.personal_message = "Testing aMSN2!" + def changeMessage(self, message): + self.profile.personal_message = message.toString() - def on_client_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - - diff --git a/amsn2/protocol/events/__init__.py b/amsn2/protocol/events/__init__.py new file mode 100644 index 00000000..792d6005 --- /dev/null +++ b/amsn2/protocol/events/__init__.py @@ -0,0 +1 @@ +# diff --git a/amsn2/protocol/addressbook.py b/amsn2/protocol/events/addressbook.py similarity index 100% rename from amsn2/protocol/addressbook.py rename to amsn2/protocol/events/addressbook.py diff --git a/amsn2/protocol/events/client.py b/amsn2/protocol/events/client.py new file mode 100644 index 00000000..8f4f51d7 --- /dev/null +++ b/amsn2/protocol/events/client.py @@ -0,0 +1,23 @@ + +import papyon +import papyon.event + + +class ClientEvents(papyon.event.ClientEventInterface): + def __init__(self, client, amsn_core): + self._amsn_core = amsn_core + papyon.event.ClientEventInterface.__init__(self, client) + + def on_client_state_changed(self, state): + self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) + + if state == papyon.event.ClientState.OPEN: + self._client.profile.display_name = "aMSN2" + self._client.profile.presence = papyon.Presence.ONLINE + self._client.profile.current_media = ("I listen to", "Nothing") + self._client.profile.personal_message = "Testing aMSN2!" + + def on_client_error(self, error_type, error): + print "ERROR :", error_type, " ->", error + + diff --git a/amsn2/protocol/contact.py b/amsn2/protocol/events/contact.py similarity index 100% rename from amsn2/protocol/contact.py rename to amsn2/protocol/events/contact.py diff --git a/amsn2/protocol/conversation.py b/amsn2/protocol/events/conversation.py similarity index 100% rename from amsn2/protocol/conversation.py rename to amsn2/protocol/events/conversation.py diff --git a/amsn2/protocol/invite.py b/amsn2/protocol/events/invite.py similarity index 100% rename from amsn2/protocol/invite.py rename to amsn2/protocol/events/invite.py diff --git a/amsn2/protocol/oim.py b/amsn2/protocol/events/oim.py similarity index 100% rename from amsn2/protocol/oim.py rename to amsn2/protocol/events/oim.py diff --git a/amsn2/protocol/protocol.py b/amsn2/protocol/protocol.py deleted file mode 100644 index 9cfe4ef5..00000000 --- a/amsn2/protocol/protocol.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# -# amsn - a python client for the WLM Network -# -# Copyright (C) 2008 Dario Freddi -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import papyon -import papyon.event -from client import * -from contact import * -from invite import * -from oim import * -from addressbook import * - -class Client(papyon.Client): - def __init__(self, amsn_core, profile): - self._amsn_profile = profile - self._amsn_core = amsn_core - server = (self._amsn_profile.getConfigKey("ns_server", "messenger.hotmail.com"), - self._amsn_profile.getConfigKey("ns_port", 1863)) - papyon.Client.__init__(self, server) - - self._client_events_handler = ClientEvents(self, self._amsn_core) - self._contact_events_handler = ContactEvents(self, self._amsn_core._contactlist_manager) - self._invite_events_handler = InviteEvents(self, self._amsn_core) - self._oim_events_handler = OIMEvents(self, self._amsn_core._oim_manager) - self._addressbook_events_handler = AddressBookEvents(self, self._amsn_core) - - def connect(self): - self.login(self._amsn_profile.email, self._amsn_profile.password) - - def changeNick(self, nick): - self.profile.display_name = nick.toString() - - def changeMessage(self, message): - self.profile.personal_message = message.toString() - From 1936fd85b6ab1e5d559481fdee9476ee5567e50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Wed, 6 May 2009 22:04:42 +0100 Subject: [PATCH 057/147] Added border around contact list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Bisinger --- amsn2/gui/front_ends/curses/contact_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index f084cef1..ebd61222 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -96,7 +96,7 @@ def __repaint(self): with self._mod_lock: self._win.clear() (y, x) = self._stdscr.getmaxyx() - self._win.move(0,0) + self._win.move(0,1) available = y gso = [] for g in self._groups_order: @@ -135,6 +135,7 @@ def __repaint(self): self._win.insch(curses.ACS_LLCORNER) self._win.insertln() i += 1 + self._win.border() self._win.refresh() self._modified = False From dfa77295c5784b75df445d2b65597697fc8b2af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Thu, 7 May 2009 03:33:00 +0100 Subject: [PATCH 058/147] curses: Started implementation of command line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Started the implementation of the command line, where all input will be catched and redirected to the relevant window. Implemented the function that gets input characters from ncurses, also ensuring that multibyte strings get correctly interpreted. Signed-off-by: Stéphane Bisinger --- amsn2/gui/front_ends/curses/command.py | 48 +++++++++++++++++++++ amsn2/gui/front_ends/curses/contact_list.py | 6 +++ amsn2/gui/front_ends/curses/main.py | 5 +++ 3 files changed, 59 insertions(+) create mode 100644 amsn2/gui/front_ends/curses/command.py diff --git a/amsn2/gui/front_ends/curses/command.py b/amsn2/gui/front_ends/curses/command.py new file mode 100644 index 00000000..df411f7d --- /dev/null +++ b/amsn2/gui/front_ends/curses/command.py @@ -0,0 +1,48 @@ +# -*- encoding: utf-8 -*- +from __future__ import with_statement +import curses +import sys +from threading import Thread +from threading import Condition +import locale + +class CommandLine(object): + def __init__(self, screen, ch_cb): + self._stdscr = screen + self._on_char_cb = ch_cb + self._cb_cond = Condition() + self._thread = Thread(target=self._get_key) + self._thread.daemon = True + self._thread.setDaemon(True) + self._thread.start() + + def setCharCb(self, ch_cb): + with self._cb_cond: + self._on_char_cb = ch_cb + if ch_cb is not None: + self._cb_cond.notify() + + def _get_key(self): + while( True ): + with self._cb_cond: + if self._on_char_cb is None: + self._cb_cond.wait() + print >> sys.stderr, "Waiting for char" + ch = self._stdscr.getkey() + first = True + while True: + try: + ch = ch.decode(locale.getpreferredencoding()) + self._stdscr.nodelay(0) + break + except (UnicodeEncodeError, UnicodeDecodeError), e: + self._stdscr.nodelay(1) + try: + ch += self._stdscr.getkey() + except: + if not first: + ch = None + self._stdscr.nodelay(0) + break + if ch is not None: + self._on_char_cb(ch) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index ebd61222..fef0e4d3 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -9,6 +9,7 @@ class aMSNContactListWindow(base.aMSNContactListWindow): def __init__(self, amsn_core, parent): self._amsn_core = amsn_core self._stdscr = parent._stdscr + parent.setFocusedWindow(self) (y,x) = self._stdscr.getmaxyx() # TODO: Use a pad instead self._win = curses.newwin(y, int(0.25*x), 0, 0) @@ -23,6 +24,11 @@ def hide(self): self._stdscr.clear() self._stdscr.refresh() + def _on_char_cb(self, ch): + import sys + print >> sys.stderr, "Length is %d" % len(ch) + print >> sys.stderr, "Received %s in Contact List" % ch.encode("UTF-8") + class aMSNContactListWidget(base.aMSNContactListWidget): def __init__(self, amsn_core, parent): diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index e308bb58..50f2e8fc 100644 --- a/amsn2/gui/front_ends/curses/main.py +++ b/amsn2/gui/front_ends/curses/main.py @@ -1,5 +1,6 @@ from amsn2.gui import base +import command import curses class aMSNMainWindow(base.aMSNMainWindow): @@ -8,6 +9,7 @@ def __init__(self, amsn_core): def show(self): self._stdscr = curses.initscr() + self._command_line = command.CommandLine(self._stdscr, None) self.__init_colors() curses.noecho() curses.cbreak() @@ -31,6 +33,9 @@ def setTitle(self,title): def setMenu(self,menu): pass + def setFocusedWindow(self, window): + self._command_line.setCharCb(window._on_char_cb) + def __init_colors(self): curses.start_color() curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_WHITE) From 1bdaca1b6b96cf17bde506a0a03d5b1a4743c83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Fri, 8 May 2009 10:53:54 +0200 Subject: [PATCH 059/147] Updated pymsn reperences to papyon references. --- amsn2/core/amsn.py | 34 +++++++-------- amsn2/core/contactlist_manager.py | 54 ++++++++++++------------ amsn2/core/conversation.py | 12 +++--- amsn2/gui/front_ends/gtk/chat_window.py | 22 +++++----- amsn2/gui/front_ends/gtk/contact_list.py | 1 + amsn2/protocol/addressbook.py | 10 ++--- amsn2/protocol/client.py | 12 +++--- amsn2/protocol/contact.py | 8 ++-- amsn2/protocol/conversation.py | 8 ++-- amsn2/protocol/invite.py | 8 ++-- amsn2/protocol/oim.py | 10 ++--- amsn2/protocol/protocol.py | 8 ++-- 12 files changed, 94 insertions(+), 93 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 4536353d..c09e50b7 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -21,7 +21,7 @@ import profile from amsn2 import gui from amsn2 import protocol -import pymsn +import papyon from views import * from contactlist_manager import * from conversation_manager import * @@ -55,15 +55,15 @@ def __init__(self, options): self._conversation_manager = aMSNConversationManager(self) self._status_manager = aMSNStatusManager(self) - self.p2s = {pymsn.Presence.ONLINE:"online", - pymsn.Presence.BUSY:"busy", - pymsn.Presence.IDLE:"idle", - pymsn.Presence.AWAY:"away", - pymsn.Presence.BE_RIGHT_BACK:"brb", - pymsn.Presence.ON_THE_PHONE:"phone", - pymsn.Presence.OUT_TO_LUNCH:"lunch", - pymsn.Presence.INVISIBLE:"hidden", - pymsn.Presence.OFFLINE:"offline"} + self.p2s = {papyon.Presence.ONLINE:"online", + papyon.Presence.BUSY:"busy", + papyon.Presence.IDLE:"idle", + papyon.Presence.AWAY:"away", + papyon.Presence.BE_RIGHT_BACK:"brb", + papyon.Presence.ON_THE_PHONE:"phone", + papyon.Presence.OUT_TO_LUNCH:"lunch", + papyon.Presence.INVISIBLE:"hidden", + papyon.Presence.OFFLINE:"offline"} if self._options.debug: import logging @@ -137,17 +137,17 @@ def connectionStateChanged(self, profile, state): status_str = \ { - pymsn.event.ClientState.CONNECTING : 'Connecting to server...', - pymsn.event.ClientState.CONNECTED : 'Connected', - pymsn.event.ClientState.AUTHENTICATING : 'Authenticating...', - pymsn.event.ClientState.AUTHENTICATED : 'Password accepted', - pymsn.event.ClientState.SYNCHRONIZING : 'Please wait while your contact list\nis being downloaded...', - pymsn.event.ClientState.SYNCHRONIZED : 'Contact list downloaded successfully.\nHappy Chatting' + papyon.event.ClientState.CONNECTING : 'Connecting to server...', + papyon.event.ClientState.CONNECTED : 'Connected', + papyon.event.ClientState.AUTHENTICATING : 'Authenticating...', + papyon.event.ClientState.AUTHENTICATED : 'Password accepted', + papyon.event.ClientState.SYNCHRONIZING : 'Please wait while your contact list\nis being downloaded...', + papyon.event.ClientState.SYNCHRONIZED : 'Contact list downloaded successfully.\nHappy Chatting' } if state in status_str: profile.login.onConnecting((state + 1)/ 7., status_str[state]) - elif state == pymsn.event.ClientState.OPEN: + elif state == papyon.event.ClientState.OPEN: clwin = self._gui.gui.aMSNContactListWindow(self, self._main) clwin.profile = profile profile.clwin = clwin diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index 9fadbe35..a9e381b2 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -1,7 +1,7 @@ from views import * import os import tempfile -import pymsn +import papyon class aMSNContactListManager: @@ -18,7 +18,7 @@ def __init__(self, core): self._contacts = {} self._groups = {} - self._pymsn_addressbook = None + self._papyon_addressbook = None #TODO: sorting contacts & groups @@ -44,10 +44,10 @@ def unregister(self, event, callback): - def onContactPresenceChanged(self, pymsn_contact): + def onContactPresenceChanged(self, papyon_contact): #1st/ update the aMSNContact object - c = self.getContact(pymsn_contact.id, pymsn_contact) - c.fill(self._core, pymsn_contact) + c = self.getContact(papyon_contact.id, papyon_contact) + c.fill(self._core, papyon_contact) #2nd/ update the ContactView cv = ContactView(self._core, c) self.emit(self.CONTACTVIEW_UPDATED, cv) @@ -55,14 +55,14 @@ def onContactPresenceChanged(self, pymsn_contact): #TODO: update the group view #Request the DP... - if (pymsn_contact.presence is not pymsn.Presence.OFFLINE and - pymsn_contact.msn_object): - self._core._profile.client._msn_object_store.request(pymsn_contact.msn_object, + if (papyon_contact.presence is not papyon.Presence.OFFLINE and + papyon_contact.msn_object): + self._core._profile.client._msn_object_store.request(papyon_contact.msn_object, (self.onDPdownloaded, - pymsn_contact.id)) + papyon_contact.id)) def onCLDownloaded(self, address_book): - self._pymsn_addressbook = address_book + self._papyon_addressbook = address_book grpviews = [] cviews = [] clv = ContactListView() @@ -80,7 +80,7 @@ def onCLDownloaded(self, address_book): grpviews.append(gv) clv.group_ids.append(group.id) - contacts = address_book.contacts.search_by_memberships(pymsn.Membership.FORWARD) + contacts = address_book.contacts.search_by_memberships(papyon.Membership.FORWARD) no_group_ids= [] for contact in contacts: if len(contact.groups) == 0: @@ -116,13 +116,13 @@ def onDPdownloaded(self, msn_object, uid): self.emit(self.CONTACTVIEW_UPDATED, cv) - def getContact(self, cid, pymsn_contact=None): + def getContact(self, cid, papyon_contact=None): #TODO: should raise UnknownContact or sthg like that try: return self._contacts[cid] except KeyError: - if pymsn_contact is not None: - c = aMSNContact(self._core, pymsn_contact) + if papyon_contact is not None: + c = aMSNContact(self._core, papyon_contact) self._contacts[cid] = c self.emit(self.AMSNCONTACT_UPDATED, c) return c @@ -135,29 +135,29 @@ def getContact(self, cid, pymsn_contact=None): everytime """ class aMSNContact(): - def __init__(self, core, pymsn_contact): - self.uid = pymsn_contact.id - self.fill(core, pymsn_contact) + def __init__(self, core, papyon_contact): + self.uid = papyon_contact.id + self.fill(core, papyon_contact) - def fill(self, core, pymsn_contact): + def fill(self, core, papyon_contact): self.icon = ImageView() - self.icon.load("Theme","buddy_" + core.p2s[pymsn_contact.presence]) + self.icon.load("Theme","buddy_" + core.p2s[papyon_contact.presence]) self.dp = ImageView() #TODO: for the moment, use default dp self.dp.load("Theme", "dp_nopic") self.emblem = ImageView() - self.emblem.load("Theme", "emblem_" + core.p2s[pymsn_contact.presence]) + self.emblem.load("Theme", "emblem_" + core.p2s[papyon_contact.presence]) #TODO: PARSE ONLY ONCE self.nickname = StringView() - self.nickname.appendText(pymsn_contact.display_name) + self.nickname.appendText(papyon_contact.display_name) self.personal_message = StringView() - self.personal_message.appendText(pymsn_contact.personal_message) + self.personal_message.appendText(papyon_contact.personal_message) self.current_media = StringView() - self.current_media.appendText(pymsn_contact.current_media) + self.current_media.appendText(papyon_contact.current_media) self.status = StringView() - self.status.appendText(core.p2s[pymsn_contact.presence]) - #for the moment, we store the pymsn_contact object, but we shouldn't have to - #TODO: getPymsnContact(self, core...) or _pymsn_contact? - self._pymsn_contact = pymsn_contact + self.status.appendText(core.p2s[papyon_contact.presence]) + #for the moment, we store the papyon_contact object, but we shouldn't have to + #TODO: getPymsnContact(self, core...) or _papyon_contact? + self._papyon_contact = papyon_contact diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index 1f121f04..8e2800c1 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -21,7 +21,7 @@ from amsn2.protocol import conversation from amsn2.core.contactlist_manager import * from amsn2.core.views import * -import pymsn +import papyon class aMSNConversation: def __init__(self, core, conv_manager, conv = None, contacts_uid = None): @@ -33,10 +33,10 @@ def __init__(self, core, conv_manager, conv = None, contacts_uid = None): self._contacts_uid = contacts_uid if conv is None: #New conversation - pymsn_contacts = [core._contactlist_manager.getContact(uid) for uid in contacts_uid] - pymsn_contacts = [c._pymsn_contact for c in pymsn_contacts if c is not None] + papyon_contacts = [core._contactlist_manager.getContact(uid) for uid in contacts_uid] + papyon_contacts = [c._papyon_contact for c in papyon_contacts if c is not None] #if c was None.... wtf? - self._conv = pymsn.Conversation(self._core._profile.client, pymsn_contacts) + self._conv = papyon.Conversation(self._core._profile.client, papyon_contacts) else: #From an existing conversation self._conv = conv @@ -90,7 +90,7 @@ def sendMessage(self, msg, formatting=None): # for the moment, no formatting, no smiley substitution... (TODO) # peacey: Added formatting of styles self.onMessageReceived(msg, formatting=formatting) - message = pymsn.ConversationMessage(msg.toString(), formatting) + message = papyon.ConversationMessage(msg.toString(), formatting) self._conv.send_text_message(message) def sendNudge(self): @@ -105,6 +105,6 @@ def leave(self): def inviteContact(self, contact_uid): """ contact_uid is the Id of the contact to invite """ c = self._core._contactlist_manager.getContact(contact_uid) - self._conv.invite_user(contact.pymsn_contact) + self._conv.invite_user(contact.papyon_contact) #TODO: ... diff --git a/amsn2/gui/front_ends/gtk/chat_window.py b/amsn2/gui/front_ends/gtk/chat_window.py index 557b1e81..b5c8cdc3 100644 --- a/amsn2/gui/front_ends/gtk/chat_window.py +++ b/amsn2/gui/front_ends/gtk/chat_window.py @@ -30,7 +30,7 @@ from amsn2.gui import base from amsn2.core.views import StringView import gtk_extras -import pymsn +import papyon class aMSNChatWindow(base.aMSNChatWindow, gtk.Window): def __init__(self, amsn_core): @@ -247,14 +247,14 @@ def __on_chat_send(self, entry, event_keyval, event_keymod): color = self.button_color.get_color() hex8 = "%.2x%.2x%.2x" % ((color.red/0x101), (color.green/0x101), (color.blue/0x101)) - style = pymsn.TextFormat.NO_EFFECT - if self.button_bold.get_active(): style |= pymsn.TextFormat.BOLD - if self.button_italic.get_active(): style |= pymsn.TextFormat.ITALIC - if self.button_underline.get_active(): style |= pymsn.TextFormat.UNDERLINE - if self.button_strikethrough.get_active(): style |= pymsn.TextFormat.STRIKETHROUGH + style = papyon.TextFormat.NO_EFFECT + if self.button_bold.get_active(): style |= papyon.TextFormat.BOLD + if self.button_italic.get_active(): style |= papyon.TextFormat.ITALIC + if self.button_underline.get_active(): style |= papyon.TextFormat.UNDERLINE + if self.button_strikethrough.get_active(): style |= papyon.TextFormat.STRIKETHROUGH font_name = self.button_font.get_font_name() font_family = pango.FontDescription(font_name).get_family() - format = pymsn.TextFormat(font=font_family, color=hex8, style=style) + format = papyon.TextFormat(font=font_family, color=hex8, style=style) strv = StringView() strv.appendText(msg) self._amsn_conversation.sendMessage(strv, format) @@ -308,13 +308,13 @@ def onMessageReceived(self, messageview, formatting=None): fmsg += "font-family: %s;" % formatting.font if formatting.color: fmsg += "color: %s;" % ("#"+formatting.color) - if formatting.style & pymsn.TextFormat.BOLD == pymsn.TextFormat.BOLD: + if formatting.style & papyon.TextFormat.BOLD == papyon.TextFormat.BOLD: fmsg += "font-weight: bold;" - if formatting.style & pymsn.TextFormat.ITALIC == pymsn.TextFormat.ITALIC: + if formatting.style & papyon.TextFormat.ITALIC == papyon.TextFormat.ITALIC: fmsg += "font-style: italic;" - if formatting.style & pymsn.TextFormat.UNDERLINE == pymsn.TextFormat.UNDERLINE: + if formatting.style & papyon.TextFormat.UNDERLINE == papyon.TextFormat.UNDERLINE: fmsg += "text-decoration: underline;" - if formatting.style & pymsn.TextFormat.STRIKETHROUGH == pymsn.TextFormat.STRIKETHROUGH: + if formatting.style & papyon.TextFormat.STRIKETHROUGH == papyon.TextFormat.STRIKETHROUGH: fmsg += "text-decoration: line-through;" if formatting.right_alignment: fmsg += "text-align: right;" diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 436a34c0..724af75f 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -225,6 +225,7 @@ def __switchFromNickInput(self, source): """ self._amsn_core._status_manager.onNickUpdated(source.get_text()) self.btnNickname.get_child().destroy() + print source.get_text() entry = self.nicklabel entry.set_markup(source.get_text()) self.btnNickname.add(entry) diff --git a/amsn2/protocol/addressbook.py b/amsn2/protocol/addressbook.py index add86648..7566102a 100644 --- a/amsn2/protocol/addressbook.py +++ b/amsn2/protocol/addressbook.py @@ -18,13 +18,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import pymsn -import pymsn.event +import papyon +import papyon.event -class AddressBookEvents(pymsn.event.AddressBookEventInterface): +class AddressBookEvents(papyon.event.AddressBookEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core - pymsn.event.AddressBookEventInterface.__init__(self, client) + papyon.event.AddressBookEventInterface.__init__(self, client) def on_addressbook_messenger_contact_added(self, contact): pass @@ -52,4 +52,4 @@ def on_addressbook_group_contact_added(self, group, contact): def on_addressbook_group_contact_deleted(self, group, contact): pass - \ No newline at end of file + diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 3684ac32..b8c2b66f 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -1,19 +1,19 @@ -import pymsn -import pymsn.event +import papyon +import papyon.event -class ClientEvents(pymsn.event.ClientEventInterface): +class ClientEvents(papyon.event.ClientEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core - pymsn.event.ClientEventInterface.__init__(self, client) + papyon.event.ClientEventInterface.__init__(self, client) def on_client_state_changed(self, state): self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) - if state == pymsn.event.ClientState.OPEN: + if state == papyon.event.ClientState.OPEN: self._client.profile.display_name = "aMSN2" - self._client.profile.presence = pymsn.Presence.ONLINE + self._client.profile.presence = papyon.Presence.ONLINE self._client.profile.current_media = ("I listen to", "Nothing") self._client.profile.personal_message = "Testing aMSN2!" diff --git a/amsn2/protocol/contact.py b/amsn2/protocol/contact.py index 83949b68..548b5118 100644 --- a/amsn2/protocol/contact.py +++ b/amsn2/protocol/contact.py @@ -1,12 +1,12 @@ -import pymsn -import pymsn.event +import papyon +import papyon.event -class ContactEvents(pymsn.event.ContactEventInterface): +class ContactEvents(papyon.event.ContactEventInterface): def __init__(self, client, contact_manager): self._contact_manager = contact_manager - pymsn.event.ContactEventInterface.__init__(self, client) + papyon.event.ContactEventInterface.__init__(self, client) def on_contact_presence_changed(self, contact): self._contact_manager.onContactPresenceChanged(contact) diff --git a/amsn2/protocol/conversation.py b/amsn2/protocol/conversation.py index 181ba711..0a487cc2 100644 --- a/amsn2/protocol/conversation.py +++ b/amsn2/protocol/conversation.py @@ -19,15 +19,15 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from amsn2.core.views import * -import pymsn -import pymsn.event +import papyon +import papyon.event -class ConversationEvents(pymsn.event.ConversationEventInterface): +class ConversationEvents(papyon.event.ConversationEventInterface): def __init__(self, amsn_conversation): self._amsn_conversation = amsn_conversation self._conversation = amsn_conversation._conv - pymsn.event.ConversationEventInterface.__init__(self, self._conversation) + papyon.event.ConversationEventInterface.__init__(self, self._conversation) def on_conversation_state_changed(self, state): self._amsn_conversation.onStateChanged(state) diff --git a/amsn2/protocol/invite.py b/amsn2/protocol/invite.py index 276a0783..e155fd0e 100644 --- a/amsn2/protocol/invite.py +++ b/amsn2/protocol/invite.py @@ -1,13 +1,13 @@ -import pymsn -import pymsn.event +import papyon +import papyon.event -class InviteEvents(pymsn.event.InviteEventInterface): +class InviteEvents(papyon.event.InviteEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core - pymsn.event.InviteEventInterface.__init__(self, client) + papyon.event.InviteEventInterface.__init__(self, client) def on_invite_conversation(self, conversation): self._amsn_core._conversation_manager.onInviteConversation(conversation) diff --git a/amsn2/protocol/oim.py b/amsn2/protocol/oim.py index d678d3bc..d13891a4 100644 --- a/amsn2/protocol/oim.py +++ b/amsn2/protocol/oim.py @@ -18,13 +18,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import pymsn -import pymsn.event +import papyon +import papyon.event -class OIMEvents(pymsn.event.OfflineMessagesEventInterface): +class OIMEvents(papyon.event.OfflineMessagesEventInterface): def __init__(self, client, oim_manager): self._oim_manager = oim_manager - pymsn.event.OfflineMessagesEventInterface.__init__(self, client) + papyon.event.OfflineMessagesEventInterface.__init__(self, client) def on_oim_state_changed(self, state): pass @@ -39,4 +39,4 @@ def on_oim_messages_deleted(self): pass def on_oim_message_sent(self, recipient, message): - pass \ No newline at end of file + pass diff --git a/amsn2/protocol/protocol.py b/amsn2/protocol/protocol.py index e3ecc3e2..495ba803 100644 --- a/amsn2/protocol/protocol.py +++ b/amsn2/protocol/protocol.py @@ -18,21 +18,21 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import pymsn -import pymsn.event +import papyon +import papyon.event from client import * from contact import * from invite import * from oim import * from addressbook import * -class Client(pymsn.Client): +class Client(papyon.Client): def __init__(self, amsn_core, profile): self._amsn_profile = profile self._amsn_core = amsn_core server = (self._amsn_profile.getConfigKey("ns_server", "messenger.hotmail.com"), self._amsn_profile.getConfigKey("ns_port", 1863)) - pymsn.Client.__init__(self, server) + papyon.Client.__init__(self, server) self._client_events_handler = ClientEvents(self, self._amsn_core) self._contact_events_handler = ContactEvents(self, self._amsn_core._contactlist_manager) From d9888822ff5974400a9e26b5eb7973f7627ec08b Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 10 May 2009 18:27:06 +0200 Subject: [PATCH 060/147] s/profile/account --- amsn2/core/__init__.py | 1 + amsn2/core/account_manager.py | 125 ++++++++++++++++++++++++++++++++ amsn2/core/amsn.py | 38 +++++----- amsn2/core/profile.py | 115 +---------------------------- amsn2/core/views/__init__.py | 1 + amsn2/core/views/accountview.py | 14 ++++ amsn2/core/views/profileview.py | 9 +-- 7 files changed, 167 insertions(+), 136 deletions(-) create mode 100644 amsn2/core/account_manager.py create mode 100644 amsn2/core/views/accountview.py diff --git a/amsn2/core/__init__.py b/amsn2/core/__init__.py index 6a102930..4cdd4bba 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -4,3 +4,4 @@ from views import * from lang import * from contactlist_manager import * +from account_manager import * diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py new file mode 100644 index 00000000..f40d88e5 --- /dev/null +++ b/amsn2/core/account_manager.py @@ -0,0 +1,125 @@ +import os +import xml.etree.ElementTree +import xml.parsers.expat +import __builtin__ +from views import AccountView +from views import StringView + +class aMSNAccount(object): + """ aMSNAccount : a Class to represent an aMSN account + This class will contain all settings relative to an account + and will store the protocol and GUI objects + """ + def __init__(self, accountview, core, account_dir): + self.view = profileview + #TODO + +class aMSNAccountManager(object): + """ aMSNAccountManager : The account manager that takes care of storing + and retreiving all the account. + """ + def __init__(self, options): + if os.name == "posix": + self._accounts_dir = os.path.join(os.environ['HOME'], ".amsn2") + elif os.name == "nt": + self._accounts_dir = os.path.join(os.environ['USERPROFILE'], "amsn2") + else: + self._accounts_dir = os.path.join(os.curdir, "amsn2_accounts") + + try : + os.makedirs(self._accounts_dir, 0777) + except : + pass + + self.accountviews = [] + self.reload() + + if options.account is not None: + pv = [p for p in self.accountviews if p.email == options.account] + if pv: + self.accountviews.remove(pv[0]) + else: + pv = AccountView() + pv.email = options.account + pv.password = options.password + self.accountviews.insert(0, pv) + + def reload(self): + self.accountviews = [] + #TODO + pass + + def getAllaccountviews(self): + #TODO + pass + + def getAvailableaccountviews(self): + #TODO + return self.accountviews + pass + + def signingToAccount(self, accountview): + #TODO + pass + + def loadAccount(self, accountview, core): + #TODO + return aMSNAccount(accountview, core) + + def unloadAccount(self, amsnAccount): + #TODO: unlock the Account + pass + + +def elementToDict(element): + """ Converts an XML Element into a proper Account dictionary """ + def dictToTuple(name, dict): + """ Converts a dictionary returned by expat XML parser into a proper tuple and adds it to a list ready for dict() """ + key = dict['name'] + type = dict['type'] + if type == "bool": + value = int(dict['value']) + else: + value = dict['value'] + + config_pair = (key,eval(type)(value)) + + config_pair_list.append(config_pair) + + config_pair_list = [] + + for entry in element : + entry_str = xml.etree.ElementTree.tostring(entry) + + parser=xml.parsers.expat.ParserCreate() + parser.StartElementHandler = dictToTuple + parser.Parse(entry_str, 1) + del parser + + config_dict = dict(config_pair_list) + + return config_dict + +def dictToElement(name, dict): + """ Converts a dictionary into a proper XML Element with tag 'name' """ + keys=[] + types=[] + values=[] + + root_element = xml.etree.ElementTree.Element(name) + + for key, value in dict.iteritems() : + keys.append(key) + type = str(__builtin__.type(value))[7:-2] + types.append(type) + if type == "bool": + int_value = int(value) + values.append(str(int_value)) + else: + values.append(str(value)) + + for key, type, value in zip(keys, types, values) : + element = xml.etree.ElementTree.Element("entry", {"name":key, "type":type, "value":value}) + root_element.append(element) + + return root_element diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index c2317f2e..2a53030a 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -22,7 +22,7 @@ from amsn2 import protocol import papyon from views import * -from profile import * +from account_manager import * from contactlist_manager import * from conversation_manager import * from oim_manager import * @@ -49,8 +49,8 @@ def __init__(self, options): self._main = None self.loadUI(self._options.front_end) - self._profile_manager = aMSNProfileManager(options) - self._profile = None + self._account_manager = aMSNAccountManager(options) + self._account = None self._theme_manager = aMSNThemeManager() self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) @@ -86,7 +86,7 @@ def switchToUI(self, ui_name): pass def mainWindowShown(self): - # TODO : load the profiles from disk and all settings + # TODO : load the accounts from disk and all settings # then show the login window if autoconnect is disabled self._main.setTitle("aMSN 2 - Loading") @@ -102,7 +102,7 @@ def mainWindowShown(self): login = self._gui.gui.aMSNLoginWindow(self, self._main) - login.setProfiles(self._profile_manager.getAvailableProfileViews()) + login.setAccounts(self._account_manager.getAvailableAccountViews()) splash.hide() self._main.setTitle("aMSN 2 - Login") @@ -114,14 +114,14 @@ def mainWindowShown(self): def getMainWindow(self): return self._main - def signinToAccount(self, login_window, profileview): - print "Signing in to account %s" % (profileview.email) - self.profile = self._profile_manager.signinToAccount(profileview) - self.profile.login = login_window - self.profile.client = protocol.Client(self, profile) - self.profile.client.connect() + def signinToAccount(self, login_window, accountview): + print "Signing in to account %s" % (accountview.email) + self._account = self._profile_manager.signinToAccount(accountview) + self._account.login = login_window + self._account.client = protocol.Client(self, self._account) + self._account.client.connect() - def connectionStateChanged(self, profile, state): + def connectionStateChanged(self, account, state): status_str = \ { @@ -136,15 +136,17 @@ def connectionStateChanged(self, profile, state): if state in status_str: profile.login.onConnecting((state + 1)/ 7., status_str[state]) elif state == papyon.event.ClientState.OPEN: + account.login.onConnecting((state + 1)/ 7., status_str[state]) + elif state == pymsn.event.ClientState.OPEN: clwin = self._gui.gui.aMSNContactListWindow(self, self._main) - clwin.profile = profile - profile.clwin = clwin - profile.login.hide() + clwin.account = account + account.clwin = clwin + account.login.hide() self._main.setTitle("aMSN 2") - profile.clwin.show() - profile.login = None + account.clwin.show() + account.login = None - self._contactlist_manager.onCLDownloaded(profile.client.address_book) + self._contactlist_manager.onCLDownloaded(account.client.address_book) def idlerAdd(self, func): self._loop.idlerAdd(func) diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py index d95f3dcf..f59577e5 100644 --- a/amsn2/core/profile.py +++ b/amsn2/core/profile.py @@ -1,126 +1,15 @@ -import os -import xml.etree.ElementTree -import xml.parsers.expat -import __builtin__ from views import ProfileView from views import StringView class aMSNProfile(object): """ aMSNProfile : a Class to represent an aMSN profile - This class will contain all settings relative to a profile - and will store the protocol and GUI objects + This class will contain all functions to get/set nick, PSM, music, ... """ def __init__(self, profileview, core, profiles_dir): self.view = profileview #TODO -class aMSNProfileManager(object): - """ aMSNProfileManager : The profile manager that takes care of storing - and retreiving all the profiles for our users. - """ - def __init__(self, options): - if os.name == "posix": - self._profiles_dir = os.path.join(os.environ['HOME'], ".amsn2") - elif os.name == "nt": - self._profiles_dir = os.path.join(os.environ['USERPROFILE'], "amsn2") - else: - self._profiles_dir = os.path.join(os.curdir, "amsn2_profiles") - - try : - os.makedirs(self._profiles_dir, 0777) - except : - pass - - self.profileviews = [] - self.reload() - - if options.account is not None: - pv = [p for p in self.profileviews if p.email == options.account] - if pv: - self.profileviews.remove(pv[0]) - else: - pv = ProfileView() - pv.email = options.account - pv.password = options.password - self.profileviews.insert(0, pv) - - def reload(self): - self.profileviews = [] - #TODO - pass - - def getAllProfileViews(self): - #TODO - pass - - def getAvailableProfileViews(self): - #TODO - return self.profileviews - pass - - def signingToProfile(self, profileview): - #TODO - pass - - def loadProfile(self, profileview, core): + def toView(self): #TODO - return aMSNProfile(profileview, core) - - def unloadProfile(self, amsnprofile): - #TODO: unlock the profile pass - - -def elementToDict(element): - """ Converts an XML Element into a proper profile dictionary """ - def dictToTuple(name, dict): - """ Converts a dictionary returned by expat XML parser into a proper tuple and adds it to a list ready for dict() """ - key = dict['name'] - type = dict['type'] - if type == "bool": - value = int(dict['value']) - else: - value = dict['value'] - - config_pair = (key,eval(type)(value)) - - config_pair_list.append(config_pair) - - config_pair_list = [] - - for entry in element : - entry_str = xml.etree.ElementTree.tostring(entry) - - parser=xml.parsers.expat.ParserCreate() - parser.StartElementHandler = dictToTuple - parser.Parse(entry_str, 1) - del parser - - config_dict = dict(config_pair_list) - - return config_dict - -def dictToElement(name, dict): - """ Converts a dictionary into a proper XML Element with tag 'name' """ - keys=[] - types=[] - values=[] - - root_element = xml.etree.ElementTree.Element(name) - - for key, value in dict.iteritems() : - keys.append(key) - type = str(__builtin__.type(value))[7:-2] - types.append(type) - if type == "bool": - int_value = int(value) - values.append(str(int_value)) - else: - values.append(str(value)) - - for key, type, value in zip(keys, types, values) : - element = xml.etree.ElementTree.Element("entry", {"name":key, "type":type, "value":value}) - root_element.append(element) - - return root_element diff --git a/amsn2/core/views/__init__.py b/amsn2/core/views/__init__.py index d652173d..92183621 100644 --- a/amsn2/core/views/__init__.py +++ b/amsn2/core/views/__init__.py @@ -6,3 +6,4 @@ from messageview import * from imageview import * from profileview import * +from accountview import * diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py new file mode 100644 index 00000000..a2937e52 --- /dev/null +++ b/amsn2/core/views/accountview.py @@ -0,0 +1,14 @@ + +from imageview import * +from stringview import * +import papyon + +class AccountView: + def __init__(self): + self.email = StringView() + self.password = StringView() + self.nick = StringView() + self.status = papyon.Presence.ONLINE + self.dp = ImageView() + self.saveprofile = False + #TODO: preferred UI ? diff --git a/amsn2/core/views/profileview.py b/amsn2/core/views/profileview.py index a5a5cf14..19524f12 100644 --- a/amsn2/core/views/profileview.py +++ b/amsn2/core/views/profileview.py @@ -1,13 +1,12 @@ from imageview import * from stringview import * -import pymsn +import papyon class ProfileView: def __init__(self): self.email = StringView() - self.password = StringView() self.nick = StringView() - self.status = pymsn.Presence.ONLINE + self.status = papyon.Presence.ONLINE self.dp = ImageView() - self.saveprofile = False - #TODO: preferred UI ? + self.music = StringView() + #TODO From 10ed0c035134c9b32577b32937e3e5c1f97edf21 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Mon, 11 May 2009 19:09:01 +0200 Subject: [PATCH 061/147] Status_Manager, Gtk : implements events --- amsn2/core/amsn.py | 3 +- amsn2/core/status_manager.py | 70 ++++++++++++++----- amsn2/core/views/statusview.py | 89 +++++++++++++++++------- amsn2/gui/base/contact_list.py | 3 +- amsn2/gui/front_ends/gtk/contact_list.py | 19 +++-- amsn2/protocol/client.py | 6 -- 6 files changed, 130 insertions(+), 60 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 4536353d..a6fcb096 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -156,8 +156,7 @@ def connectionStateChanged(self, profile, state): profile.clwin.show() profile.login = None self._status_manager.set_profile(profile.client.profile) - # need something better to update info, can't always access clwin - clwin.myInfoUpdated(self._status_manager._statusview) + self._status_manager._statusview.presence = 'online' self._contactlist_manager.onCLDownloaded(profile.client.address_book) diff --git a/amsn2/core/status_manager.py b/amsn2/core/status_manager.py index 557689f4..0b0b688a 100644 --- a/amsn2/core/status_manager.py +++ b/amsn2/core/status_manager.py @@ -1,6 +1,9 @@ from views import * class aMSNStatusManager(): + STATUS_UPDATED = 0 + _events_cbs = [[]] + def __init__(self, core): self._core = core self._statusview = None @@ -9,35 +12,66 @@ def __init__(self, core): def set_profile(self, pymsn_profile): self._pymsn_profile = pymsn_profile self._statusview = StatusView(self._core, pymsn_profile) - # TODO: update the contactlist gui from the core + self.emit(self.STATUS_UPDATED, self._statusview) + + def emit(self, event, *args): + """ emit the event """ + for cb in self._events_cbs[event]: + #TODO: try except + cb(*args) + + def register(self, event, callback, type='ro'): + """ register a callback for an event """ + #TODO: try except + if type is 'ro': + self._events_cbs[event].append(callback) + elif type is 'rw': + bck_cbs = self._events_cbs[event] + self._events_cbs[event] = [callback] + self._events_cbs[event].extend(bck_cbs) + + def unregister(self, event, callback): + """ unregister a callback for an event """ + #TODO: try except + self._events_cbs[event].remove(callback) + + + """ Actions from ourselves """ - def onNickUpdated(self, new_nick): + def _onNickUpdated(self, new_nick): # TODO: parsing - self._pymsn_profile.display_name = new_nick - - def onPMUpdated(self, new_pm): + self._pymsn_profile.display_name = new_nick.toString() + self.emit(self.STATUS_UPDATED, self._statusview) + + def _onPMUpdated(self, new_pm): # TODO: parsing - self._pymsn_profile.personal_message = new_pm - - def onDPUpdated(self, new_dp): + self._pymsn_profile.personal_message = new_pm.toString() + self.emit(self.STATUS_UPDATED, self._statusview) + + def _onDPUpdated(self, new_dp): # TODO: manage msn_objects - pass - - def onPresenceUpdated(self, new_presence): + self.emit(self.STATUS_UPDATED, self._statusview) + + def _onPresenceUpdated(self, new_presence): for key in self._core.p2s: if self._core.p2s[key] == new_presence: break self._pymsn_profile.presence = key - + self.emit(self.STATUS_UPDATED, self._statusview) + """ actions from the core """ - def onCurrentMediaUpdated(self, new_media): - # TODO: update the contactlist gui from the core - pass + def _onCMUpdated(self, new_media): + self._pymsn_profile.current_media = new_media + self.emit(self.STATUS_UPDATED, self._statusview) - # TODO: connect to pymsn event, maybe build a mailbox_manager + # TODO: connect to papyon event, maybe build a mailbox_manager """ Actions from outside """ - def onNewMail(self, info): - pass + def _onNewMail(self, info): + self.emit(self.STATUS_UPDATED, self._statusview) + + + + diff --git a/amsn2/core/views/statusview.py b/amsn2/core/views/statusview.py index 84ea8e33..4c5e3fe7 100644 --- a/amsn2/core/views/statusview.py +++ b/amsn2/core/views/statusview.py @@ -1,37 +1,74 @@ from stringview import * from imageview import * +def rw_property(f): + return property(**f()) + class StatusView(object): def __init__(self, core, pymsn_profile): # TODO: parse fields for smileys, format, etc - self.nickname = StringView() - self.nickname.appendText(pymsn_profile.display_name) - self.psm = StringView() - self.psm.appendText(pymsn_profile.personal_message) - self.current_media = StringView() + self._nickname = StringView() + self._nickname.appendText(pymsn_profile.display_name) + self._psm = StringView() + self._psm.appendText(pymsn_profile.personal_message) + self._current_media = StringView() if pymsn_profile.current_media is not None: - self.current_media.appendText(pymsn_profile.current_media[0]) - self.current_media.appendText(pymsn_profile.current_media[1]) + self._current_media.appendText(pymsn_profile.current_media[0]) + self._current_media.appendText(pymsn_profile.current_media[1]) # TODO: How do I get the profile image? - self.image = ImageView() + self._image = ImageView() #self.image.load(pymsn_profile.msn_object) - self.presence = core.p2s[pymsn_profile.presence] - - # callbacks - def update_nick_cb(nickv): - core._status_manager.onNickUpdated(nickv) - self.update_nick = update_nick_cb - def update_presence_cb(presencev): - core._status_manager.onPresenceUpdated(presencev) - self.update_presence = update_presence_cb - def update_pm_cb(pmv): - core._status_manager.onPMUpdated(pmv) - self.update_pm = update_pm_cb - def update_dp_cb(dpv): - core._status_manager.onDPUpdated(dpv) - self.update_dp = update_dp_cb + self._presence = core.p2s[pymsn_profile.presence] + self._status_manager = core._status_manager + # TODO: get more info, how to manage webcams and mail - self.webcam = None - self.mail_unread = None - + self._webcam = None + self._mail_unread = None + + @rw_property + def nick(): + def fget(self): + return self._nickname + def fset(self, nick): + self._nickname = nick + self._status_manager._onNickUpdated(nick) + return locals() + + @rw_property + def psm(): + def fget(self): + return self._psm + def fset(self, psm): + self._psm = psm + self._status_manager._onPMUpdated(psm) + return locals() + + @rw_property + def dp(): + def fget(self): + return self._image + def fset(self, imagev): + self._image = imagev + self._status_manager._onDPUpdated(imagev) + return locals() + + @rw_property + def current_media(): + def fget(self): + return self._current_media + def fset(self, artist, song): + # TODO: separators + self._current_media.appendText(artist) + self._current_media.appendText(song) + self._status_manager._onCMUpdated((artist, song)) + return locals() + + @rw_property + def presence(): + def fget(self): + return self._presence + def fset(self, p): + self._presence = p + self._status_manager._onPresenceUpdated(p) + return locals() diff --git a/amsn2/gui/base/contact_list.py b/amsn2/gui/base/contact_list.py index b3662811..34971c08 100644 --- a/amsn2/gui/base/contact_list.py +++ b/amsn2/gui/base/contact_list.py @@ -13,7 +13,8 @@ class aMSNContactListWindow(object): """ def __init__(self, amsn_core, parent): - raise NotImplementedError + sm = amsn_core._status_manager + sm.register(sm.STATUS_UPDATED, self.myInfoUpdated) def show(self): """ Show the contact list window """ diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 436a34c0..ab0808b7 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -43,11 +43,13 @@ class aMSNContactListWindow(base.aMSNContactListWindow, gtk.VBox): def __init__(self, amsn_core, parent): '''Constructor''' gtk.VBox.__init__(self) + base.aMSNContactListWindow.__init__(self, amsn_core, parent) self._amsn_core = amsn_core self._main_win = parent self._skin = amsn_core._skin_manager.skin self._theme_manager = self._amsn_core._theme_manager + self._myview = amsn_core._status_manager._statusview self._clwidget = aMSNContactListWidget(amsn_core, self) @@ -58,7 +60,6 @@ def __init__(self, amsn_core, parent): self.show_all() self.__setup_window() - self._onStatusChanged = lambda *args: args def __create_controls(self): ###self.psmlabel.modify_font(common.GUI_FONT) @@ -191,11 +192,14 @@ def myInfoUpdated(self, view): currentMedia,...)""" # TODO: image, ... # FIXME: status at login, now seems 'offline' even if we are online - self.nicklabel.set_markup(view.nickname.toString()) - message = view.psm.toString()+' '+view.current_media.toString() + self._myview = view + nk = view.nick + self.nicklabel.set_markup(nk.toString()) + psm = view.psm + cm = view.current_media + message = psm.toString()+' '+cm.toString() self.psmlabel.set_markup(''+message+'') self.status.set_active(self.status_values[view.presence]) - self._onStatusChanged = view.update_presence def onStatusChanged(self, combobox): status = combobox.get_active() @@ -204,7 +208,7 @@ def onStatusChanged(self, combobox): break # FIXME: changing status to 'offline' will disconnect, so return to login window # also fix pymsn, gives an error on setting 'offline' - self._onStatusChanged(key) + self._myview.presence = key def __on_btnNicknameClicked(self, source): self.__switchToNickInput() @@ -223,10 +227,11 @@ def __switchFromNickInput(self, source): """ When in the editing state of nickname, change back to the uneditable label state. """ - self._amsn_core._status_manager.onNickUpdated(source.get_text()) + strv = StringView() + strv.appendText(source.get_text()) + self._myview.nick = strv self.btnNickname.get_child().destroy() entry = self.nicklabel - entry.set_markup(source.get_text()) self.btnNickname.add(entry) entry.show() diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 3684ac32..fd8175ce 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -10,12 +10,6 @@ def __init__(self, client, amsn_core): def on_client_state_changed(self, state): self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) - - if state == pymsn.event.ClientState.OPEN: - self._client.profile.display_name = "aMSN2" - self._client.profile.presence = pymsn.Presence.ONLINE - self._client.profile.current_media = ("I listen to", "Nothing") - self._client.profile.personal_message = "Testing aMSN2!" def on_client_error(self, error_type, error): print "ERROR :", error_type, " ->", error From 6cd794d879ea30dfde210513041917868d3615db Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Wed, 13 May 2009 21:26:03 +0200 Subject: [PATCH 062/147] Renamed StatusView and StatusManager to PersonalInfoView and PersonalInfoManager --- amsn2/core/__init__.py | 2 +- amsn2/core/amsn.py | 8 ++--- amsn2/core/contactlist_manager.py | 2 +- ...tus_manager.py => personalinfo_manager.py} | 36 +++++++++---------- amsn2/core/views/__init__.py | 2 +- .../{statusview.py => personalinfoview.py} | 30 ++++++++-------- amsn2/gui/base/contact_list.py | 6 ++-- amsn2/gui/front_ends/gtk/contact_list.py | 8 ++--- 8 files changed, 47 insertions(+), 47 deletions(-) rename amsn2/core/{status_manager.py => personalinfo_manager.py} (58%) rename amsn2/core/views/{statusview.py => personalinfoview.py} (62%) diff --git a/amsn2/core/__init__.py b/amsn2/core/__init__.py index 7bf6aba8..754bef94 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -4,4 +4,4 @@ from views import * from lang import * from contactlist_manager import * -from status_manager import * +from personalinfo_manager import * diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 0901d525..7939a084 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -27,7 +27,7 @@ from conversation_manager import * from oim_manager import * from theme_manager import * -from status_manager import * +from personalinfo_manager import * class aMSNCore(object): @@ -53,7 +53,7 @@ def __init__(self, options): self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) self._conversation_manager = aMSNConversationManager(self) - self._status_manager = aMSNStatusManager(self) + self._personalinfo_manager = aMSNPersonalInfoManager(self) self.p2s = {papyon.Presence.ONLINE:"online", papyon.Presence.BUSY:"busy", @@ -155,8 +155,8 @@ def connectionStateChanged(self, profile, state): self._main.setTitle("aMSN 2") profile.clwin.show() profile.login = None - self._status_manager.set_profile(profile.client.profile) - self._status_manager._statusview.presence = 'online' + self._personalinfo_manager.set_profile(profile.client.profile) + self._personalinfo_manager._personalinfoview.presence = 'online' self._contactlist_manager.onCLDownloaded(profile.client.address_book) diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index a9e381b2..39b20505 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -157,7 +157,7 @@ def fill(self, core, papyon_contact): self.status = StringView() self.status.appendText(core.p2s[papyon_contact.presence]) #for the moment, we store the papyon_contact object, but we shouldn't have to - #TODO: getPymsnContact(self, core...) or _papyon_contact? + #TODO: getPapyonContact(self, core...) or _papyon_contact? self._papyon_contact = papyon_contact diff --git a/amsn2/core/status_manager.py b/amsn2/core/personalinfo_manager.py similarity index 58% rename from amsn2/core/status_manager.py rename to amsn2/core/personalinfo_manager.py index 0b0b688a..6f7faa9a 100644 --- a/amsn2/core/status_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -1,18 +1,18 @@ from views import * -class aMSNStatusManager(): - STATUS_UPDATED = 0 +class aMSNPersonalInfoManager(): + PERSONALINFO_UPDATED = 0 _events_cbs = [[]] def __init__(self, core): self._core = core - self._statusview = None - self._pymsn_profile = None + self._personalinfoview = None + self._papyon_profile = None - def set_profile(self, pymsn_profile): - self._pymsn_profile = pymsn_profile - self._statusview = StatusView(self._core, pymsn_profile) - self.emit(self.STATUS_UPDATED, self._statusview) + def set_profile(self, papyon_profile): + self._papyon_profile = papyon_profile + self._personalinfoview = PersonalInfoView(self._core, papyon_profile) + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) def emit(self, event, *args): """ emit the event """ @@ -41,34 +41,34 @@ def unregister(self, event, callback): """ Actions from ourselves """ def _onNickUpdated(self, new_nick): # TODO: parsing - self._pymsn_profile.display_name = new_nick.toString() - self.emit(self.STATUS_UPDATED, self._statusview) + self._papyon_profile.display_name = new_nick.toString() + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) def _onPMUpdated(self, new_pm): # TODO: parsing - self._pymsn_profile.personal_message = new_pm.toString() - self.emit(self.STATUS_UPDATED, self._statusview) + self._papyon_profile.personal_message = new_pm.toString() + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) def _onDPUpdated(self, new_dp): # TODO: manage msn_objects - self.emit(self.STATUS_UPDATED, self._statusview) + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) def _onPresenceUpdated(self, new_presence): for key in self._core.p2s: if self._core.p2s[key] == new_presence: break - self._pymsn_profile.presence = key - self.emit(self.STATUS_UPDATED, self._statusview) + self._papyon_profile.presence = key + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) """ actions from the core """ def _onCMUpdated(self, new_media): - self._pymsn_profile.current_media = new_media - self.emit(self.STATUS_UPDATED, self._statusview) + self._papyon_profile.current_media = new_media + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) # TODO: connect to papyon event, maybe build a mailbox_manager """ Actions from outside """ def _onNewMail(self, info): - self.emit(self.STATUS_UPDATED, self._statusview) + self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) diff --git a/amsn2/core/views/__init__.py b/amsn2/core/views/__init__.py index 36bfd58b..c6cb5669 100644 --- a/amsn2/core/views/__init__.py +++ b/amsn2/core/views/__init__.py @@ -5,4 +5,4 @@ from tooltipview import * from messageview import * from imageview import * -from statusview import * +from personalinfoview import * diff --git a/amsn2/core/views/statusview.py b/amsn2/core/views/personalinfoview.py similarity index 62% rename from amsn2/core/views/statusview.py rename to amsn2/core/views/personalinfoview.py index 4c5e3fe7..24435d69 100644 --- a/amsn2/core/views/statusview.py +++ b/amsn2/core/views/personalinfoview.py @@ -4,22 +4,22 @@ def rw_property(f): return property(**f()) -class StatusView(object): - def __init__(self, core, pymsn_profile): +class PersonalInfoView(object): + def __init__(self, core, papyon_profile): # TODO: parse fields for smileys, format, etc self._nickname = StringView() - self._nickname.appendText(pymsn_profile.display_name) + self._nickname.appendText(papyon_profile.display_name) self._psm = StringView() - self._psm.appendText(pymsn_profile.personal_message) + self._psm.appendText(papyon_profile.personal_message) self._current_media = StringView() - if pymsn_profile.current_media is not None: - self._current_media.appendText(pymsn_profile.current_media[0]) - self._current_media.appendText(pymsn_profile.current_media[1]) + if papyon_profile.current_media is not None: + self._current_media.appendText(papyon_profile.current_media[0]) + self._current_media.appendText(papyon_profile.current_media[1]) # TODO: How do I get the profile image? self._image = ImageView() - #self.image.load(pymsn_profile.msn_object) - self._presence = core.p2s[pymsn_profile.presence] - self._status_manager = core._status_manager + #self.image.load(papyon_profile.msn_object) + self._presence = core.p2s[papyon_profile.presence] + self._personalinfo_manager = core._personalinfo_manager # TODO: get more info, how to manage webcams and mail self._webcam = None @@ -31,7 +31,7 @@ def fget(self): return self._nickname def fset(self, nick): self._nickname = nick - self._status_manager._onNickUpdated(nick) + self._personalinfo_manager._onNickUpdated(nick) return locals() @rw_property @@ -40,7 +40,7 @@ def fget(self): return self._psm def fset(self, psm): self._psm = psm - self._status_manager._onPMUpdated(psm) + self._personalinfo_manager._onPMUpdated(psm) return locals() @rw_property @@ -49,7 +49,7 @@ def fget(self): return self._image def fset(self, imagev): self._image = imagev - self._status_manager._onDPUpdated(imagev) + self._personalinfo_manager._onDPUpdated(imagev) return locals() @rw_property @@ -60,7 +60,7 @@ def fset(self, artist, song): # TODO: separators self._current_media.appendText(artist) self._current_media.appendText(song) - self._status_manager._onCMUpdated((artist, song)) + self._personalinfo_manager._onCMUpdated((artist, song)) return locals() @rw_property @@ -69,6 +69,6 @@ def fget(self): return self._presence def fset(self, p): self._presence = p - self._status_manager._onPresenceUpdated(p) + self._personalinfo_manager._onPresenceUpdated(p) return locals() diff --git a/amsn2/gui/base/contact_list.py b/amsn2/gui/base/contact_list.py index 34971c08..5ff3bea4 100644 --- a/amsn2/gui/base/contact_list.py +++ b/amsn2/gui/base/contact_list.py @@ -13,8 +13,8 @@ class aMSNContactListWindow(object): """ def __init__(self, amsn_core, parent): - sm = amsn_core._status_manager - sm.register(sm.STATUS_UPDATED, self.myInfoUpdated) + pim = amsn_core._personalinfo_manager + pim.register(pim.PERSONALINFO_UPDATED, self.myInfoUpdated) def show(self): """ Show the contact list window """ @@ -39,7 +39,7 @@ def setMenu(self, menu): def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the StatusView of the ourself (contains DP, nick, psm, + @view: the PersonalInfoView of the ourself (contains DP, nick, psm, currentMedia,...)""" raise NotImplementedError diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index e0926a22..ac3f32eb 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -33,7 +33,7 @@ from amsn2.core.views import GroupView from amsn2.core.views import ContactView from amsn2.core.views import ImageView -from amsn2.core.views import StatusView +from amsn2.core.views import PersonalInfoView from amsn2.gui import base import common @@ -49,7 +49,7 @@ def __init__(self, amsn_core, parent): self._main_win = parent self._skin = amsn_core._skin_manager.skin self._theme_manager = self._amsn_core._theme_manager - self._myview = amsn_core._status_manager._statusview + self._myview = amsn_core._personalinfo_manager._personalinfoview self._clwidget = aMSNContactListWidget(amsn_core, self) @@ -188,7 +188,7 @@ def setMenu(self, menu): def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the StatusView of the ourself (contains DP, nick, psm, + @view: the PersonalInfoView of the ourself (contains DP, nick, psm, currentMedia,...)""" # TODO: image, ... # FIXME: status at login, now seems 'offline' even if we are online @@ -207,7 +207,7 @@ def onStatusChanged(self, combobox): if self.status_values[key] == status: break # FIXME: changing status to 'offline' will disconnect, so return to login window - # also fix pymsn, gives an error on setting 'offline' + # also fix papyon, gives an error on setting 'offline' self._myview.presence = key def __on_btnNicknameClicked(self, source): From 8368ae3c5b88ea6f1d86f80085b51e45b0582109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Wed, 13 May 2009 22:53:31 +0200 Subject: [PATCH 063/147] [GTK] Added focus events for nickname box Now when you click outside the box, or press escape the ord nickname is returned. Also added a nice relief around the box when the nick is being edited. --- amsn2/gui/front_ends/gtk/contact_list.py | 31 +++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 724af75f..d33d9798 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -213,23 +213,42 @@ def __switchToNickInput(self): """ Switches the nick button into a text area for editing of the nick name.""" #label = self.btnNickname.get_child() - self.btnNickname.get_child().destroy() + self.btnNickname.remove(self.btnNickname.get_child()) entry = gtk.Entry() self.btnNickname.add(entry) entry.show() + entry.grab_focus() + self.btnNickname.set_relief(gtk.RELIEF_NORMAL) # Add cool elevated effect entry.connect("activate", self.__switchFromNickInput) - + entry.connect("key-press-event", self.__handleInputNickname) + self.focusOutId = entry.connect("focus-out-event", self.__handleInputNickname) + + def __handleInputNickname(self, source, event): + """ Handle various inputs from the nicknameEntry-box """ + if(event.type == gtk.gdk.FOCUS_CHANGE): #user clickd outside textfield + self.__switchFromNickInput(None) + elif (event.type == gtk.gdk.KEY_PRESS): #user wrote something, esc perhaps? + if event.keyval == gtk.keysyms.Escape: + self.__switchFromNickInput(None) + def __switchFromNickInput(self, source): """ When in the editing state of nickname, change back to the uneditable label state. """ - self._amsn_core._status_manager.onNickUpdated(source.get_text()) - self.btnNickname.get_child().destroy() - print source.get_text() + if(source != None): #Source == None if called from __handleInputNickname + newNick = source.get_text() + self._amsn_core._status_manager.onNickUpdated(newNick) + else: + newNick = self.nicklabel.get_text() # Old nickname + + currWidget = self.btnNickname.get_child() + currWidget.disconnect(self.focusOutId) # Else we trigger focus-out-event; segfault. + self.btnNickname.remove(currWidget) entry = self.nicklabel - entry.set_markup(source.get_text()) + entry.set_markup(newNick) self.btnNickname.add(entry) entry.show() + self.btnNickname.set_relief(gtk.RELIEF_NONE) # remove cool elevated effect class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): From d5149b52cb31e5e2c1ce3b860376fdffdfd15949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Thu, 14 May 2009 00:29:26 +0200 Subject: [PATCH 064/147] [GTK] Merged the code with lucky's Didn't manage to merge some of the code for the nickname box, I still use a label there. --- amsn2/core/amsn.py | 2 -- amsn2/gui/front_ends/gtk/contact_list.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 27f4bcf2..24e0712e 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -53,8 +53,6 @@ def __init__(self, options): self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) self._conversation_manager = aMSNConversationManager(self) - - self._status_manager = aMSNStatusManager(self) self._personalinfo_manager = aMSNPersonalInfoManager(self) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 9a25117b..5fc07bce 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -240,9 +240,9 @@ def __switchFromNickInput(self, source): label state. """ - if(source != None): #Source == None if called from __handleInputNickname + if(source != None): newNick = source.get_text() - self._amsn_core._status_manager.onNickUpdated(newNick) + self._amsn_core._personalinfo_manager._papyon_profile.display_name = newNick else: newNick = self.nicklabel.get_text() # Old nickname From ff5cf03f3242f3a3d39b7b541db8cca58f901514 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Thu, 14 May 2009 01:07:38 +0200 Subject: [PATCH 065/147] [core] Join the management of the events creating an event manager --- amsn2/core/amsn.py | 2 ++ amsn2/core/contactlist_manager.py | 46 ++++++------------------------ amsn2/core/event_manager.py | 40 ++++++++++++++++++++++++++ amsn2/core/personalinfo_manager.py | 44 ++++++---------------------- amsn2/gui/base/contact_list.py | 12 ++++---- 5 files changed, 65 insertions(+), 79 deletions(-) create mode 100644 amsn2/core/event_manager.py diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 7939a084..5d7b8beb 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -28,6 +28,7 @@ from oim_manager import * from theme_manager import * from personalinfo_manager import * +from event_manager import * class aMSNCore(object): @@ -42,6 +43,7 @@ def __init__(self, options): options.front_end = the front end's name to use options.debug = whether or not to enable debug output """ + self._event_manager = aMSNEventManager(self) self._profile_manager = profile.aMSNProfileManager() self._options = options self._gui_name = self._options.front_end diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index 39b20505..1931bf76 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -7,50 +7,20 @@ class aMSNContactListManager: def __init__(self, core): self._core = core - - #TODO: have only one event manager? - #TODO: describe the events: - self.CONTACTVIEW_UPDATED = 0 - self.GROUPVIEW_UPDATED = 1 - self.CLVIEW_UPDATED = 2 - self.AMSNCONTACT_UPDATED = 3 - self._events_cbs = [[], [], [], []] - + self._em = core._event_manager self._contacts = {} self._groups = {} self._papyon_addressbook = None #TODO: sorting contacts & groups - def emit(self, event, *args): - """ emit the event """ - for cb in self._events_cbs[event]: - #TODO: try except - cb(*args) - - def register(self, event, callback, pos=None): - """ register a callback for an event """ - #TODO: try except - if pos is None: - self._events_cbs[event].append(callback) - else: - self._events_cbs[event].insert(pos,callback) - - def unregister(self, event, callback): - """ unregister a callback for an event """ - #TODO: try except - self._events_cbs[event].remove(callback) - - - - def onContactPresenceChanged(self, papyon_contact): #1st/ update the aMSNContact object c = self.getContact(papyon_contact.id, papyon_contact) c.fill(self._core, papyon_contact) #2nd/ update the ContactView cv = ContactView(self._core, c) - self.emit(self.CONTACTVIEW_UPDATED, cv) + self._em.emit(self._em.events.CONTACTVIEW_UPDATED, cv) #TODO: update the group view @@ -95,11 +65,11 @@ def onCLDownloaded(self, address_book): clv.group_ids.append(0) #Emit the events - self.emit(self.CLVIEW_UPDATED, clv) + self._em.emit(self._em.events.CLVIEW_UPDATED, clv) for g in grpviews: - self.emit(self.GROUPVIEW_UPDATED, g) + self._em.emit(self._em.events.GROUPVIEW_UPDATED, g) for c in cviews: - self.emit(self.CONTACTVIEW_UPDATED, c) + self._em.emit(self._em.events.CONTACTVIEW_UPDATED, c) def onDPdownloaded(self, msn_object, uid): #1st/ update the aMSNContact object @@ -110,10 +80,10 @@ def onDPdownloaded(self, msn_object, uid): f.write(msn_object._data.read()) f.close() c.dp.load("Filename", tf) - self.emit(self.AMSNCONTACT_UPDATED, c) + self._em.emit(self._em.events.AMSNCONTACT_UPDATED, c) #2nd/ update the ContactView cv = ContactView(self._core, c) - self.emit(self.CONTACTVIEW_UPDATED, cv) + self._em.emit(self._em.events.CONTACTVIEW_UPDATED, cv) def getContact(self, cid, papyon_contact=None): @@ -124,7 +94,7 @@ def getContact(self, cid, papyon_contact=None): if papyon_contact is not None: c = aMSNContact(self._core, papyon_contact) self._contacts[cid] = c - self.emit(self.AMSNCONTACT_UPDATED, c) + self._em.emit(self._em.events.AMSNCONTACT_UPDATED, c) return c else: raise ValueError diff --git a/amsn2/core/event_manager.py b/amsn2/core/event_manager.py new file mode 100644 index 00000000..2ca16af5 --- /dev/null +++ b/amsn2/core/event_manager.py @@ -0,0 +1,40 @@ +class aMSNEvents: + # ContactList events + CONTACTVIEW_UPDATED = 0 + GROUPVIEW_UPDATED = 1 + CLVIEW_UPDATED = 2 + AMSNCONTACT_UPDATED = 3 + # PersonalInfo events + PERSONALINFO_UPDATED = 4 + +class aMSNEventManager: + def __init__(self, core): + self._core = core + self._events_cbs = [ [] for e in dir(aMSNEvents) if e.isupper()] + self.events = aMSNEvents() + + def emit(self, event, *args): + """ emit the event """ + for cb in self._events_cbs[event]: + #TODO: try except + cb(*args) + + # maybe create a default set of priority, like PLUGIN, CORE... + def register(self, event, callback, type='ro', priority = 0): + """ register a callback for an event """ + #TODO: try except + if type is 'ro': + self._events_cbs[event].append(callback) + elif type is 'rw': + # TODO: insertion depends on priority + bck_cbs = [].extend(self._events_cbs) + self._events_cbs[event] = [callback] + self._events_cbs[event].extend(bck_cbs) + + def unregister(self, event, callback): + """ unregister a callback for an event """ + #TODO: try except + self._events_cbs[event].remove(callback) + + + diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 6f7faa9a..9896fc0c 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -1,74 +1,48 @@ from views import * -class aMSNPersonalInfoManager(): - PERSONALINFO_UPDATED = 0 - _events_cbs = [[]] - +class aMSNPersonalInfoManager: def __init__(self, core): self._core = core + self._em = core._event_manager self._personalinfoview = None self._papyon_profile = None def set_profile(self, papyon_profile): self._papyon_profile = papyon_profile self._personalinfoview = PersonalInfoView(self._core, papyon_profile) - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) - - def emit(self, event, *args): - """ emit the event """ - for cb in self._events_cbs[event]: - #TODO: try except - cb(*args) - - def register(self, event, callback, type='ro'): - """ register a callback for an event """ - #TODO: try except - if type is 'ro': - self._events_cbs[event].append(callback) - elif type is 'rw': - bck_cbs = self._events_cbs[event] - self._events_cbs[event] = [callback] - self._events_cbs[event].extend(bck_cbs) - - def unregister(self, event, callback): - """ unregister a callback for an event """ - #TODO: try except - self._events_cbs[event].remove(callback) - - - + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) """ Actions from ourselves """ def _onNickUpdated(self, new_nick): # TODO: parsing self._papyon_profile.display_name = new_nick.toString() - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) def _onPMUpdated(self, new_pm): # TODO: parsing self._papyon_profile.personal_message = new_pm.toString() - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) def _onDPUpdated(self, new_dp): # TODO: manage msn_objects - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) def _onPresenceUpdated(self, new_presence): for key in self._core.p2s: if self._core.p2s[key] == new_presence: break self._papyon_profile.presence = key - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) """ actions from the core """ def _onCMUpdated(self, new_media): self._papyon_profile.current_media = new_media - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) # TODO: connect to papyon event, maybe build a mailbox_manager """ Actions from outside """ def _onNewMail(self, info): - self.emit(self.PERSONALINFO_UPDATED, self._personalinfoview) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) diff --git a/amsn2/gui/base/contact_list.py b/amsn2/gui/base/contact_list.py index 5ff3bea4..82e7b712 100644 --- a/amsn2/gui/base/contact_list.py +++ b/amsn2/gui/base/contact_list.py @@ -13,8 +13,8 @@ class aMSNContactListWindow(object): """ def __init__(self, amsn_core, parent): - pim = amsn_core._personalinfo_manager - pim.register(pim.PERSONALINFO_UPDATED, self.myInfoUpdated) + em = amsn_core._event_manager + em.register(em.events.PERSONALINFO_UPDATED, self.myInfoUpdated) def show(self): """ Show the contact list window """ @@ -46,10 +46,10 @@ def myInfoUpdated(self, view): class aMSNContactListWidget(object): """ This interface implements the contact list of the UI """ def __init__(self, amsn_core, parent): - clm = amsn_core._contactlist_manager - clm.register(clm.CLVIEW_UPDATED, self.contactListUpdated) - clm.register(clm.GROUPVIEW_UPDATED, self.groupUpdated) - clm.register(clm.CONTACTVIEW_UPDATED, self.contactUpdated) + em = amsn_core._event_manager + em.register(em.events.CLVIEW_UPDATED, self.contactListUpdated) + em.register(em.events.GROUPVIEW_UPDATED, self.groupUpdated) + em.register(em.events.CONTACTVIEW_UPDATED, self.contactUpdated) def show(self): """ Show the contact list widget """ From 3200f1020b54f158b125c525c7e51c90e4b61d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Thu, 14 May 2009 18:28:00 +0200 Subject: [PATCH 066/147] [GTK] Added statusfield. Made code play nice with luckyluke's new pim_manager Now you can set status message in the same way you set your nickname. --- amsn2/gui/front_ends/gtk/contact_list.py | 61 +++++++++++++++--------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 5fc07bce..51a19d21 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -90,6 +90,7 @@ def __create_controls(self): self.btnPsm.add(self.psmlabel) self.btnPsm.set_relief(gtk.RELIEF_NONE) self.btnPsm.set_alignment(0,0) + self.btnPsm.connect("clicked",self.__on_btnPsmClicked) # status list self.status_values = {} @@ -211,49 +212,63 @@ def onStatusChanged(self, combobox): self._myview.presence = key def __on_btnNicknameClicked(self, source): - self.__switchToNickInput() + self.__switchToInput(source) - def __switchToNickInput(self): + def __on_btnPsmClicked(self, source): + self.__switchToInput(source) + + def __switchToInput(self, source): """ Switches the nick button into a text area for editing of the nick name.""" #label = self.btnNickname.get_child() - self.btnNickname.remove(self.btnNickname.get_child()) + source.remove(source.get_child()) entry = gtk.Entry() - self.btnNickname.add(entry) + source.add(entry) entry.show() entry.grab_focus() - self.btnNickname.set_relief(gtk.RELIEF_NORMAL) # Add cool elevated effect - entry.connect("activate", self.__switchFromNickInput) - entry.connect("key-press-event", self.__handleInputNickname) - self.focusOutId = entry.connect("focus-out-event", self.__handleInputNickname) + source.set_relief(gtk.RELIEF_NORMAL) # Add cool elevated effect + entry.connect("activate", self.__switchFromInput, True) + entry.connect("key-press-event", self.__handleInput) + self.focusOutId = entry.connect("focus-out-event", self.__handleInput) - def __handleInputNickname(self, source, event): + def __handleInput(self, source, event): """ Handle various inputs from the nicknameEntry-box """ if(event.type == gtk.gdk.FOCUS_CHANGE): #user clickd outside textfield - self.__switchFromNickInput(None) + self.__switchFromInput(source, False) elif (event.type == gtk.gdk.KEY_PRESS): #user wrote something, esc perhaps? if event.keyval == gtk.keysyms.Escape: - self.__switchFromNickInput(None) + self.__switchFromInput(source, False) - def __switchFromNickInput(self, source): + def __switchFromInput(self, source, isNew): """ When in the editing state of nickname, change back to the uneditable label state. """ - - if(source != None): - newNick = source.get_text() - self._amsn_core._personalinfo_manager._papyon_profile.display_name = newNick + if(isNew): + if(source == self.btnNickname.get_child()): + newText = source.get_text() + strv = StringView() + strv.appendText(newText) + self._myview.nick = strv + elif (source == self.btnPsm.get_child()): + newText = source.get_text() + strv = StringView() + strv.appendText(newText) + self._myview.psm = strv else: - newNick = self.nicklabel.get_text() # Old nickname + if(source == self.btnNickname.get_child()): # User discards input + newText = self.nicklabel.get_text() # Old nickname - currWidget = self.btnNickname.get_child() + parentWidget = source.get_parent() + currWidget = parentWidget.get_child() currWidget.disconnect(self.focusOutId) # Else we trigger focus-out-event; segfault. - self.btnNickname.remove(currWidget) - entry = self.nicklabel - entry.set_markup(newNick) - self.btnNickname.add(entry) + + parentWidget.remove(currWidget) + entry = gtk.Label() + entry.set_markup(newText) + + parentWidget.add(entry) entry.show() - self.btnNickname.set_relief(gtk.RELIEF_NONE) # remove cool elevated effect + parentWidget.set_relief(gtk.RELIEF_NONE) # remove cool elevated effect class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): From d13fe38b293efa5395ae16bb446695a3ef35517c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Thu, 14 May 2009 19:18:09 +0200 Subject: [PATCH 067/147] [GTK] Display picture now clickable, and changeable. --- amsn2/gui/front_ends/gtk/contact_list.py | 33 +++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 51a19d21..3b920ba4 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -67,6 +67,12 @@ def __create_controls(self): self.display = gtk.Image() self.display.set_size_request(64,64) + self.btnDisplay = gtk.Button() + self.btnDisplay.set_relief(gtk.RELIEF_NONE) + self.btnDisplay.add(self.display) + self.btnDisplay.set_alignment(0,0) + self.btnDisplay.connect("clicked", self.__onDisplayClicked) + self.nicklabel = gtk.Label() self.nicklabel.set_alignment(0, 0) self.nicklabel.set_use_markup(True) @@ -124,7 +130,7 @@ def __create_controls(self): def __create_box(self): frameDisplay = gtk.Frame() - frameDisplay.add(self.display) + frameDisplay.add(self.btnDisplay) self.evdisplay = gtk.EventBox() self.evdisplay.add(frameDisplay) @@ -257,6 +263,8 @@ def __switchFromInput(self, source, isNew): else: if(source == self.btnNickname.get_child()): # User discards input newText = self.nicklabel.get_text() # Old nickname + if(source == self.btnPsm.get_child()): + newText = self.psmlabel.get_text() parentWidget = source.get_parent() currWidget = parentWidget.get_child() @@ -269,6 +277,29 @@ def __switchFromInput(self, source, isNew): parentWidget.add(entry) entry.show() parentWidget.set_relief(gtk.RELIEF_NONE) # remove cool elevated effect + + def __onDisplayClicked(self, source): + print "Display clicked!" + chooser = gtk.FileChooserDialog(title=None,action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + + chooser.set_default_response(gtk.RESPONSE_OK) + + filter = gtk.FileFilter() + filter.set_name("All files") + filter.add_pattern("*") + chooser.add_filter(filter) + + response = chooser.run() + if(response == gtk.RESPONSE_OK): + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(chooser.get_filename(), 64, 64) + self.display.set_from_pixbuf(pixbuf) + del pixbuf + gc.collect() + elif (response == gtk.RESPONSE_CANCEL): + pass + chooser.destroy() + class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): From 8423c2ac08d1623417fa5d1b95071f27f53472ae Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 15 May 2009 17:45:33 +0200 Subject: [PATCH 068/147] [personalinfo] allow login with the presence status set in the login window --- amsn2/core/amsn.py | 3 +-- amsn2/core/personalinfo_manager.py | 10 ++++++---- amsn2/gui/front_ends/gtk/login.py | 2 +- amsn2/protocol/events/client.py | 6 ------ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 5d7b8beb..ed4e7a11 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -157,9 +157,8 @@ def connectionStateChanged(self, profile, state): self._main.setTitle("aMSN 2") profile.clwin.show() profile.login = None - self._personalinfo_manager.set_profile(profile.client.profile) - self._personalinfo_manager._personalinfoview.presence = 'online' + self._personalinfo_manager.set_profile(profile) self._contactlist_manager.onCLDownloaded(profile.client.address_book) def idlerAdd(self, func): diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 9896fc0c..050a3588 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -7,10 +7,12 @@ def __init__(self, core): self._personalinfoview = None self._papyon_profile = None - def set_profile(self, papyon_profile): - self._papyon_profile = papyon_profile - self._personalinfoview = PersonalInfoView(self._core, papyon_profile) - self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + def set_profile(self, amsn_profile): + self._papyon_profile = amsn_profile.client.profile + self._personalinfoview = PersonalInfoView(self._core, self._papyon_profile) + + # set login presence and update the gui + self._personalinfoview.presence = amsn_profile.presence """ Actions from ourselves """ def _onNickUpdated(self, new_nick): diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 84bc9e72..340e3a1d 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -203,7 +203,7 @@ def signin(self): self.current_profile.email = self.user.get_active_text() self.current_profile.password = self.password.get_text() i = self.statusCombo.get_active() - self.current_profile.presence = self._amsn_core.p2s.keys()[i] + self.current_profile.presence = self._amsn_core.p2s.values()[i] self._amsn_core.signinToAccount(self, self.current_profile) self.timer = gobject.timeout_add(40, self.__animation) diff --git a/amsn2/protocol/events/client.py b/amsn2/protocol/events/client.py index 8f4f51d7..a475eb7b 100644 --- a/amsn2/protocol/events/client.py +++ b/amsn2/protocol/events/client.py @@ -10,12 +10,6 @@ def __init__(self, client, amsn_core): def on_client_state_changed(self, state): self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) - - if state == papyon.event.ClientState.OPEN: - self._client.profile.display_name = "aMSN2" - self._client.profile.presence = papyon.Presence.ONLINE - self._client.profile.current_media = ("I listen to", "Nothing") - self._client.profile.personal_message = "Testing aMSN2!" def on_client_error(self, error_type, error): print "ERROR :", error_type, " ->", error From 2c3486753abe183be9246aed0ce2fc983a790bf8 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 15 May 2009 21:38:32 +0200 Subject: [PATCH 069/147] Deleted pymsn submodule --- pymsn | 1 - 1 file changed, 1 deletion(-) delete mode 160000 pymsn diff --git a/pymsn b/pymsn deleted file mode 160000 index 290d4151..00000000 --- a/pymsn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 290d4151f178891d55233e9f48c2ad233a186176 From 4c669029950f4e67bca5a1c60eff58e4e66b1807 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 17 May 2009 21:38:38 +0200 Subject: [PATCH 070/147] improve the account manager --- amsn2/core/account_manager.py | 185 +++++++++++++++++++------------- amsn2/core/amsn.py | 4 +- amsn2/core/views/accountview.py | 10 +- amsn2/gui/base/login.py | 6 +- 4 files changed, 124 insertions(+), 81 deletions(-) diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index f40d88e5..b5eb6dcd 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -1,5 +1,5 @@ import os -import xml.etree.ElementTree +from xml.etree.ElementTree import Element, SubElement, ElementTree import xml.parsers.expat import __builtin__ from views import AccountView @@ -10,9 +10,65 @@ class aMSNAccount(object): This class will contain all settings relative to an account and will store the protocol and GUI objects """ - def __init__(self, accountview, core, account_dir): + #TODO: use the personnal info stuff instead of the view + def __init__(self, core, accountview, account_dir): self.view = profileview + self.account_dir = account_dir + self.password_backend = "default" + self.dp_backend = "default" + self.do_save = accountview.save + + self.lock() + #TODO + + def signOut(self): + self.save() + self.unlock() + + def lock(self): + #TODO + pass + + def unlock(self): + #TODO + pass + + def load(self): #TODO + pass + + def save(self): + if self.view is not None and self.view.email is not None: + root_section = Element("aMSNAccount") + #email + emailElmt = SubElement(root_section, "email") + emailElmt.text = self.view.email + #nick + nick = self.view.nick.toString() + nickElmt = SubElement(root_section, "nick") + nickElmt.text = nick + #status + statusElmt = SubElement(root_section, "status") + statusElmt.text = self.view.status + #password + #TODO ask the backend + passwordElmt = SubElement(root_section, "dp", + backend=self.password_backend) + passwordElmt.text = self.view.password + #dp + dpElmt = SubElement(root_section, "dp", + backend=self.dp_backend) + #TODO + + #TODO: save or not, preferred_ui + #TODO: backend for config/logs/... + + if not os.path.isdir(self.account_dir): + os.makedirs(self.account_dir, 0700) + accpath = os.path.join(self.account_dir, "account.xml") + xml_tree = ElementTree(root_section) + xml_tree.write(accpath) + class aMSNAccountManager(object): """ aMSNAccountManager : The account manager that takes care of storing @@ -27,11 +83,10 @@ def __init__(self, options): self._accounts_dir = os.path.join(os.curdir, "amsn2_accounts") try : - os.makedirs(self._accounts_dir, 0777) + os.makedirs(self._accounts_dir, 0700) except : pass - self.accountviews = [] self.reload() if options.account is not None: @@ -46,80 +101,64 @@ def __init__(self, options): def reload(self): self.accountviews = [] - #TODO - pass - - def getAllaccountviews(self): - #TODO - pass - - def getAvailableaccountviews(self): - #TODO + for root, dirs, files in os.walk(self._accounts_dir): + account_dirs = dirs + break + + for account_dir in account_dirs: + self.accountviews.append(self.loadAccount(account_dir)) + + + def loadAccount(self, dir): + accview = None + accpath = os.path.join(dir, "account.xml") + root_tree = parse(accpath) + account = root_tree.find("aMSNAccount") + if account is not None: + accview = Accountview() + #email + emailElt = account.find("email") + accview.email = text + #nick + nickElmt = account.find("nick") + accview.nick.appendText(nickElmt.text) + #TODO: parse... + #status + statusElmt = account.find("status") + accview.status = statusElmt.text + #password + passwordElmt = account.find("password") + accview.password = passwordElmt.text + #TODO: use backend & all + #dp + dpElt = account.find("dp") + #TODO + + #TODO: preferred_ui ? + + accview.save = True + return accview + + + + def getAllAccountViews(self): return self.accountviews + + def getAvailableAccountViews(self): + return [v for v in self.accountviews if not self.isAccountLocked(v)] pass def signingToAccount(self, accountview): - #TODO - pass + accdir = os.path.join(self._accounts_dir, + accountNameToDirName(accountview.email)) + acc = aMSNAccount(self.core, accountview, accdir) + return acc - def loadAccount(self, accountview, core): + def isAccountLocked(self, accountview): #TODO - return aMSNAccount(accountview, core) - - def unloadAccount(self, amsnAccount): - #TODO: unlock the Account - pass - - -def elementToDict(element): - """ Converts an XML Element into a proper Account dictionary """ - def dictToTuple(name, dict): - """ Converts a dictionary returned by expat XML parser into a proper tuple and adds it to a list ready for dict() """ - key = dict['name'] - type = dict['type'] - if type == "bool": - value = int(dict['value']) - else: - value = dict['value'] - - config_pair = (key,eval(type)(value)) - - config_pair_list.append(config_pair) - - config_pair_list = [] - - for entry in element : - entry_str = xml.etree.ElementTree.tostring(entry) - - parser=xml.parsers.expat.ParserCreate() - parser.StartElementHandler = dictToTuple - parser.Parse(entry_str, 1) - del parser - - config_dict = dict(config_pair_list) - - return config_dict - -def dictToElement(name, dict): - """ Converts a dictionary into a proper XML Element with tag 'name' """ - keys=[] - types=[] - values=[] - - root_element = xml.etree.ElementTree.Element(name) - - for key, value in dict.iteritems() : - keys.append(key) - type = str(__builtin__.type(value))[7:-2] - types.append(type) - if type == "bool": - int_value = int(value) - values.append(str(int_value)) - else: - values.append(str(value)) + return False - for key, type, value in zip(keys, types, values) : - element = xml.etree.ElementTree.Element("entry", {"name":key, "type":type, "value":value}) - root_element.append(element) +def accountNameToDirName(acc): + #Having to do that just sucks + str = acc.lower().strip().replace("@","_at_"); - return root_element diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 2a53030a..9455efe8 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -116,7 +116,7 @@ def getMainWindow(self): def signinToAccount(self, login_window, accountview): print "Signing in to account %s" % (accountview.email) - self._account = self._profile_manager.signinToAccount(accountview) + self._account = self._account_manager.signinToAccount(accountview) self._account.login = login_window self._account.client = protocol.Client(self, self._account) self._account.client.connect() @@ -155,6 +155,8 @@ def timerAdd(self, delay, func): self._loop.timerAdd(delay, func) def quit(self): + if self._account is not None: + self._account.signOut() self._loop.quit() def createMainMenuView(self): diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py index a2937e52..8a8f2194 100644 --- a/amsn2/core/views/accountview.py +++ b/amsn2/core/views/accountview.py @@ -5,10 +5,12 @@ class AccountView: def __init__(self): - self.email = StringView() - self.password = StringView() + self.email = None + self.password = None self.nick = StringView() self.status = papyon.Presence.ONLINE self.dp = ImageView() - self.saveprofile = False - #TODO: preferred UI ? + + self.save = False + + self.preferred_ui = None diff --git a/amsn2/gui/base/login.py b/amsn2/gui/base/login.py index af75dc7d..dc4a40cd 100644 --- a/amsn2/gui/base/login.py +++ b/amsn2/gui/base/login.py @@ -13,10 +13,10 @@ def hide(self): """ Hide the login window """ raise NotImplementedError - def setProfiles(self, profileviews): + def setAccounts(self, accountviews): """ This method will be called when the core needs the login window to - let the user select among some profiles. - @profileviews: list of profileviews describing profiles + let the user select among some accounts. + @accountviews: list of accountviews describing accounts The first one in the list should be considered as default. """ raise NotImplementedError From 6be0d8b15d31cec4cf1d1d9b1e16d4fe08e33579 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 17 May 2009 21:39:10 +0200 Subject: [PATCH 071/147] Update the EFL frontend --- amsn2/gui/front_ends/efl/login.py | 41 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/amsn2/gui/front_ends/efl/login.py b/amsn2/gui/front_ends/efl/login.py index d26c870d..049fbd29 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -5,12 +5,14 @@ import elementary from amsn2.gui import base +from amsn2.core.views import accountview class aMSNLoginWindow(base.aMSNLoginWindow): def __init__(self, amsn_core, parent): self._amsn_core = amsn_core self._evas = parent._evas self._parent = parent + self._account_views = [] edje.frametime_set(1.0 / 30) @@ -49,10 +51,6 @@ def __init__(self, amsn_core, parent): self._edje.signal_callback_add("signin", "*", self.__signin_cb) - # We start with no profile set up, we let the Core set our starting profile - self.switch_to_profile(None) - - def show(self): self._parent.resize_object_add(self._edje) self._edje.show() @@ -70,26 +68,27 @@ def hide(self): else: self.signin_b.hide() - def switch_to_profile(self, profile): - self.current_profile = profile - if self.current_profile is not None: - if self.current_profile.username is None: - self.username.entry_set("") - else: - self.username.entry_set(self.current_profile.username) - if self.current_profile.password is None: - self.password.entry_set("") - else: - self.password.entry_set(self.current_profile.password) + + def setAccounts(self, accountviews): + self._account_views = accountviews + if accountviews: + #Only select the first one + acc = accountviews[0] + self.username.entry_set(acc.email) + self.password.entry_set(acc.password) def signin(self): - self.current_profile.username = \ - elementary.Entry.markup_to_utf8(self.username.entry_get()).strip() - self.current_profile.email = self.current_profile.username - self.current_profile.password = \ - elementary.Entry.markup_to_utf8(self.password.entry_get()).strip() - self._amsn_core.signinToAccount(self, self.current_profile) + email = elementary.Entry.markup_to_utf8(self.username.entry_get()).strip() + password = elementary.Entry.markup_to_utf8(self.password.entry_get()).strip() + + accv = [accv for accv in self._account_views if accv.email == email] + if not accv: + accv = AccountView() + accv.email = email + accv.password = password + + self._amsn_core.signinToAccount(self, accv) def onConnecting(self, progress, message): self._edje.signal_emit("connecting", "") From 3fa23d8405f56412839bd602683b02d5f0e2de53 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Mon, 18 May 2009 12:25:01 +0200 Subject: [PATCH 072/147] [core] Added tree-based management of rw callbacks in event_manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Actually dependencies between callback are specified with the callback's name Still need some adjust with error condition inserting/removing a callback, actually just print a message, maybe wait for$ Thanks to Stéphane Bisinger for the idea --- amsn2/core/event_manager.py | 145 ++++++++++++++++++++++++++++++++---- papyon | 2 +- 2 files changed, 133 insertions(+), 14 deletions(-) diff --git a/amsn2/core/event_manager.py b/amsn2/core/event_manager.py index 2ca16af5..e9eb49f5 100644 --- a/amsn2/core/event_manager.py +++ b/amsn2/core/event_manager.py @@ -10,31 +10,150 @@ class aMSNEvents: class aMSNEventManager: def __init__(self, core): self._core = core - self._events_cbs = [ [] for e in dir(aMSNEvents) if e.isupper()] + self._events_cbs = [ [[], []] for e in dir(aMSNEvents) if e.isupper()] + self._events_tree = [aMSNEventTree(None) for e in dir(aMSNEvents) if e.isupper()] self.events = aMSNEvents() def emit(self, event, *args): """ emit the event """ - for cb in self._events_cbs[event]: + # rw callback + for cb in self._events_cbs[event][0]: #TODO: try except cb(*args) - # maybe create a default set of priority, like PLUGIN, CORE... - def register(self, event, callback, type='ro', priority = 0): - """ register a callback for an event """ - #TODO: try except + # ro callback + for cb in self._events_cbs[event][1]: + #TODO: try except + cb(*args) + + def register(self, event, callback, type='ro', deps=[]): + """ + Register a callback for an event: + ro callback: doesn't need to modify the view + rw callback: modify the view, can have dependencies which actually + are the names of the callbacks from which it depends + """ if type is 'ro': - self._events_cbs[event].append(callback) + self._events_cbs[event][1].append(callback) + elif type is 'rw': - # TODO: insertion depends on priority - bck_cbs = [].extend(self._events_cbs) - self._events_cbs[event] = [callback] - self._events_cbs[event].extend(bck_cbs) + if self._events_tree[event].insert(callback, deps): + self._events_cbs[event][0] = self._events_tree[event].getCallbacksSequence() + else: + print 'Failed adding callback '+callback.__name__+' to event '+event+': missing dependencies' def unregister(self, event, callback): """ unregister a callback for an event """ - #TODO: try except - self._events_cbs[event].remove(callback) + if self._events_tree[event].isListed(callback): + self._events_tree[event].remove(callback) + self._events_cbs[event][0] = self._events_tree.getCallbacksSequence() + else: + self._events_cbs[event][1].remove(callback) + + + + +class aMSNEventCallback: + def __init__(self, tree, callback_function, deps): + self.data = callback_function + self.id = callback_function.__name__ + self._deps = set(deps) + self._tree = tree + + def depends(self, cb): + for dep in self._deps: + if cb.id == dep or (\ + cb._tree.right is not None and \ + cb._tree.right.isListed(dep)): + return True + return False + +class aMSNEventTree: + def __init__(self, parent): + self.parent = parent + self.root = None + self.left = None + self.right = None + self._elements = set() + + def remove(self, callback_function): + if self.isListed(callback_function.__name__): + cb_obj = self._find(callback_function.__name__) + + # keep callbacks that do not depend on the one being removed + if cb_obj._tree.parent is not None: + if cb_obj._tree.parent.right is cb_obj._tree: + cb_obj._tree.parent.right = cb_obj._tree.left + else: + cb_obj._tree.parent.left = cb_obj._tree.left + + else: + # remove the root + self.root = self.left.root + self.right = self.left.right + self._elements = self.left._elements + self.left = self.left.left + + else: + print 'Trying to remove missing callback '+callback_function.__name__ + + # FIXME: what if a dependence is not yet in the tree? + def insert(self, callback_function, deps=[]): + cb_obj = aMSNEventCallback(self, callback_function, deps) + if self.isListed(cb_obj.id): + self.remove(callback_function) + print 'Trying to add already added callback '+callback_function.__name__ + + deps_satisfied = [self.isListed(dep) for dep in deps] + + # workaround if there are no dependencies + deps_satisfied.extend([True, True]) + + if reduce(lambda x, y: x and y, deps_satisfied): + self._insert(cb_obj) + return True + else: + # can't satisfy all dependencies + return False + + def isListed(self, item): + return item in self._elements + + def getCallbacksSequence(self): + return self._inorder([]) + + def _insert(self, cb): + self._elements.add(cb.id) + cb._tree = self + if self.root is None: + self.root = cb + + elif cb.depends(self.root): + if self.right is None: + self.right = aMSNEventTree(self) + self.right._insert(cb) + + else: + if self.left is None: + self.left = aMSNEventTree(self) + self.left._insert(cb) + + def _inorder(self, q): + if self.left is not None: + q = self.left._inorder(q) + q.append(self.root.data) + if self.right is not None: + q = self.right._inorder(q) + return q + def _find(self, str_id): + if self.left is not None and self.left.isListed(str_id): + return self.left._find(str_id) + elif self.right is not None and self.right.isListed(str_id): + return self.right._find(str_id) + elif self.root.id == str_id: + return self.root + else: + return None diff --git a/papyon b/papyon index dcae9f0d..894abc93 160000 --- a/papyon +++ b/papyon @@ -1 +1 @@ -Subproject commit dcae9f0dd4fec82a6847b664174cf7cb00c44ea0 +Subproject commit 894abc938e1aa1fea8738156b5bb9392d4ac8ee8 From 64688ac77bc4bd11880e3856a8f132b2263c2167 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Mon, 18 May 2009 12:25:01 +0200 Subject: [PATCH 073/147] [core] Added tree-based management of rw callbacks in event_manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Actually dependencies between callback are specified with the callback's name. Still need some adjust with error condition inserting/removing a callback, actually just print a message, maybe wait for a global error management. Thanks to Stéphane Bisinger for the idea. --- amsn2/core/event_manager.py | 145 ++++++++++++++++++++++++++++++++---- papyon | 2 +- 2 files changed, 133 insertions(+), 14 deletions(-) diff --git a/amsn2/core/event_manager.py b/amsn2/core/event_manager.py index 2ca16af5..e9eb49f5 100644 --- a/amsn2/core/event_manager.py +++ b/amsn2/core/event_manager.py @@ -10,31 +10,150 @@ class aMSNEvents: class aMSNEventManager: def __init__(self, core): self._core = core - self._events_cbs = [ [] for e in dir(aMSNEvents) if e.isupper()] + self._events_cbs = [ [[], []] for e in dir(aMSNEvents) if e.isupper()] + self._events_tree = [aMSNEventTree(None) for e in dir(aMSNEvents) if e.isupper()] self.events = aMSNEvents() def emit(self, event, *args): """ emit the event """ - for cb in self._events_cbs[event]: + # rw callback + for cb in self._events_cbs[event][0]: #TODO: try except cb(*args) - # maybe create a default set of priority, like PLUGIN, CORE... - def register(self, event, callback, type='ro', priority = 0): - """ register a callback for an event """ - #TODO: try except + # ro callback + for cb in self._events_cbs[event][1]: + #TODO: try except + cb(*args) + + def register(self, event, callback, type='ro', deps=[]): + """ + Register a callback for an event: + ro callback: doesn't need to modify the view + rw callback: modify the view, can have dependencies which actually + are the names of the callbacks from which it depends + """ if type is 'ro': - self._events_cbs[event].append(callback) + self._events_cbs[event][1].append(callback) + elif type is 'rw': - # TODO: insertion depends on priority - bck_cbs = [].extend(self._events_cbs) - self._events_cbs[event] = [callback] - self._events_cbs[event].extend(bck_cbs) + if self._events_tree[event].insert(callback, deps): + self._events_cbs[event][0] = self._events_tree[event].getCallbacksSequence() + else: + print 'Failed adding callback '+callback.__name__+' to event '+event+': missing dependencies' def unregister(self, event, callback): """ unregister a callback for an event """ - #TODO: try except - self._events_cbs[event].remove(callback) + if self._events_tree[event].isListed(callback): + self._events_tree[event].remove(callback) + self._events_cbs[event][0] = self._events_tree.getCallbacksSequence() + else: + self._events_cbs[event][1].remove(callback) + + + + +class aMSNEventCallback: + def __init__(self, tree, callback_function, deps): + self.data = callback_function + self.id = callback_function.__name__ + self._deps = set(deps) + self._tree = tree + + def depends(self, cb): + for dep in self._deps: + if cb.id == dep or (\ + cb._tree.right is not None and \ + cb._tree.right.isListed(dep)): + return True + return False + +class aMSNEventTree: + def __init__(self, parent): + self.parent = parent + self.root = None + self.left = None + self.right = None + self._elements = set() + + def remove(self, callback_function): + if self.isListed(callback_function.__name__): + cb_obj = self._find(callback_function.__name__) + + # keep callbacks that do not depend on the one being removed + if cb_obj._tree.parent is not None: + if cb_obj._tree.parent.right is cb_obj._tree: + cb_obj._tree.parent.right = cb_obj._tree.left + else: + cb_obj._tree.parent.left = cb_obj._tree.left + + else: + # remove the root + self.root = self.left.root + self.right = self.left.right + self._elements = self.left._elements + self.left = self.left.left + + else: + print 'Trying to remove missing callback '+callback_function.__name__ + + # FIXME: what if a dependence is not yet in the tree? + def insert(self, callback_function, deps=[]): + cb_obj = aMSNEventCallback(self, callback_function, deps) + if self.isListed(cb_obj.id): + self.remove(callback_function) + print 'Trying to add already added callback '+callback_function.__name__ + + deps_satisfied = [self.isListed(dep) for dep in deps] + + # workaround if there are no dependencies + deps_satisfied.extend([True, True]) + + if reduce(lambda x, y: x and y, deps_satisfied): + self._insert(cb_obj) + return True + else: + # can't satisfy all dependencies + return False + + def isListed(self, item): + return item in self._elements + + def getCallbacksSequence(self): + return self._inorder([]) + + def _insert(self, cb): + self._elements.add(cb.id) + cb._tree = self + if self.root is None: + self.root = cb + + elif cb.depends(self.root): + if self.right is None: + self.right = aMSNEventTree(self) + self.right._insert(cb) + + else: + if self.left is None: + self.left = aMSNEventTree(self) + self.left._insert(cb) + + def _inorder(self, q): + if self.left is not None: + q = self.left._inorder(q) + q.append(self.root.data) + if self.right is not None: + q = self.right._inorder(q) + return q + def _find(self, str_id): + if self.left is not None and self.left.isListed(str_id): + return self.left._find(str_id) + elif self.right is not None and self.right.isListed(str_id): + return self.right._find(str_id) + elif self.root.id == str_id: + return self.root + else: + return None diff --git a/papyon b/papyon index dcae9f0d..894abc93 160000 --- a/papyon +++ b/papyon @@ -1 +1 @@ -Subproject commit dcae9f0dd4fec82a6847b664174cf7cb00c44ea0 +Subproject commit 894abc938e1aa1fea8738156b5bb9392d4ac8ee8 From d18e9b7bdcee8f130d3820bd2f52e4cca5733973 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Mon, 18 May 2009 22:23:11 +0200 Subject: [PATCH 074/147] Remove trailing spaces... --- amsn2/core/lang.py | 44 ++-- amsn2/core/oim_manager.py | 2 +- amsn2/core/personalinfo_manager.py | 8 +- amsn2/core/profile.py | 142 +++++------ amsn2/core/theme_manager.py | 38 +-- amsn2/core/views/keybindingview.py | 8 +- amsn2/core/views/menuview.py | 10 +- amsn2/core/views/tooltipview.py | 1 - amsn2/gui/base/image.py | 8 +- amsn2/gui/base/login.py | 2 +- amsn2/gui/base/main.py | 4 +- amsn2/gui/base/main_loop.py | 2 +- amsn2/gui/base/window.py | 2 +- amsn2/gui/front_ends/cocoa/__init__.py | 6 +- amsn2/gui/front_ends/cocoa/contact_list.py | 14 +- amsn2/gui/front_ends/cocoa/image.py | 6 +- amsn2/gui/front_ends/cocoa/login.py | 16 +- amsn2/gui/front_ends/cocoa/main.py | 14 +- amsn2/gui/front_ends/cocoa/main_loop.py | 24 +- .../cocoa/nibs/CocoaLoggingInView.py | 6 +- .../front_ends/cocoa/nibs/CocoaLoginView.py | 6 +- .../cocoa/nibs/CocoaSplashScreenView.py | 6 +- .../CocoaLoginWindow.py | 4 +- amsn2/gui/front_ends/cocoa/splash.py | 6 +- amsn2/gui/front_ends/console/__init__.py | 4 +- amsn2/gui/front_ends/console/console.py | 2 +- amsn2/gui/front_ends/console/contact_list.py | 16 +- amsn2/gui/front_ends/console/login.py | 4 +- amsn2/gui/front_ends/console/main.py | 4 +- amsn2/gui/front_ends/console/main_loop.py | 4 +- amsn2/gui/front_ends/curses/__init__.py | 4 +- amsn2/gui/front_ends/curses/command.py | 2 +- amsn2/gui/front_ends/curses/login.py | 12 +- amsn2/gui/front_ends/gtk/__init__.py | 4 +- amsn2/gui/front_ends/gtk/chat_window.py | 122 +++++----- amsn2/gui/front_ends/gtk/common.py | 22 +- amsn2/gui/front_ends/gtk/contact_list.py | 122 +++++----- amsn2/gui/front_ends/gtk/gtk_extras.py | 222 +++++++++--------- amsn2/gui/front_ends/gtk/htmltextview.py | 32 +-- amsn2/gui/front_ends/gtk/image.py | 18 +- amsn2/gui/front_ends/gtk/login.py | 8 +- amsn2/gui/front_ends/gtk/main.py | 18 +- amsn2/gui/front_ends/gtk/main_loop.py | 6 +- amsn2/gui/front_ends/gtk/skins.py | 36 +-- amsn2/gui/front_ends/gtk/splash.py | 6 +- amsn2/gui/front_ends/mine/__init__.py | 4 +- amsn2/gui/front_ends/mine/contact_list.py | 2 +- amsn2/gui/front_ends/mine/login.py | 4 +- amsn2/gui/front_ends/mine/main.py | 4 +- amsn2/gui/front_ends/mine/main_loop.py | 6 +- amsn2/gui/front_ends/qt4/__init__.py | 2 +- amsn2/gui/front_ends/qt4/chat_window.py | 64 ++--- amsn2/gui/front_ends/qt4/contact_item.py | 4 +- amsn2/gui/front_ends/qt4/contact_list.py | 52 ++-- amsn2/gui/front_ends/qt4/contact_model.py | 4 +- amsn2/gui/front_ends/qt4/image.py | 16 +- amsn2/gui/front_ends/qt4/main.py | 10 +- amsn2/gui/front_ends/qt4/splash.py | 8 +- amsn2/gui/front_ends/web/__init__.py | 4 +- amsn2/gui/front_ends/web/bend.py | 4 +- amsn2/gui/front_ends/web/login.py | 4 +- amsn2/gui/front_ends/web/main.py | 4 +- amsn2/gui/front_ends/web/main_loop.py | 6 +- amsn2/gui/front_ends/web/window.py | 2 +- amsn2/gui/gui.py | 12 +- amsn2/plugins/developers.py | 38 +-- amsn2/plugins/gui.py | 84 ++++--- amsn2/protocol/events/addressbook.py | 4 +- amsn2/protocol/events/client.py | 4 +- amsn2/protocol/events/contact.py | 2 +- amsn2/protocol/events/invite.py | 2 +- amsn2/protocol/events/oim.py | 2 +- 72 files changed, 707 insertions(+), 692 deletions(-) diff --git a/amsn2/core/lang.py b/amsn2/core/lang.py index 6475d7aa..18f88c3c 100644 --- a/amsn2/core/lang.py +++ b/amsn2/core/lang.py @@ -7,37 +7,37 @@ class aMSNLang(object): lang_dirs = [] base_lang = 'en' lang_code = base_lang - + default_encoding = 'utf-8' - + lineRe = re.compile('\s*([^\s]+)\s+(.+)', re.UNICODE) # whitespace? + key + whitespace + value langRe = re.compile('(.+)-.+', re.UNICODE) # code or code-variant - + def loadLang(self, lang_code, force_reload=False): if self.lang_code is lang_code and force_reload is False: # Don't reload the same lang unless forced. return - + hasVariant = bool(self.langRe.match(lang_code) is not None) - + # Check for lang variants. if hasVariant is True: root = str(self.langRe.split(lang_code)[1]) else: root = str(lang_code) - + if lang_code is self.base_lang: # Clear the keys if we're loading the base lang. self.clearKeys() - + if root is not self.base_lang: # If it's not the default lang, load the base first. self.loadLang(self.base_lang) - + if hasVariant is True: # Then we have a variant, so load the root. self.loadLang(root) - + # Load the langfile from each langdir. fileWasLoaded = False for dir in self.getLangDirs(): @@ -47,26 +47,26 @@ def loadLang(self, lang_code, force_reload=False): except IOError: # file doesn't exist. continue - + line = f.readline() while line: if self.lineRe.match(line) is not None: components = self.lineRe.split(line) self.setKey(unicode(components[1], self.default_encoding), unicode(components[2], self.default_encoding)) - + # Get the next line... line = f.readline() - + f.close() - + # If we've loaded a lang file, set the new lang code. if fileWasLoaded is True: self.lang_code = str(lang_code) - + def addLangDir(self, lang_dir): self.lang_dirs.append(str(lang_dir)) self.reloadKeys() - + def removeLangDir(self, lang_dir): try: # Remove the lang_dir from the lang_dirs list, and reload keys. @@ -76,7 +76,7 @@ def removeLangDir(self, lang_dir): except ValueError: # Dir not in list. return False - + def getLangDirs(self): # Return a copy for them to play with. return self.lang_dirs[:] @@ -90,14 +90,14 @@ def reloadKeys(self): def setKey(self, key, val): self.lang_keys[key] = val - + def getKey(self, key, replacements=[]): try: r = self.lang_keys[key] except KeyError: # Key doesn't exist. return key - + # Perform any replacements necessary. if self._isDict(replacements): # Replace from a dictionary. @@ -109,19 +109,19 @@ def getKey(self, key, replacements=[]): for replacement in replacements: r = r.replace('$' + str(i), replacement) i += 1 - + return r - + def _isDict(self, test): try: test.keys() return True except AttributeError: return False - + def clearKeys(self): self.lang_keys = {} - + def printKeys(self): print self.lang_code print '{' diff --git a/amsn2/core/oim_manager.py b/amsn2/core/oim_manager.py index b97d7a8e..477530af 100644 --- a/amsn2/core/oim_manager.py +++ b/amsn2/core/oim_manager.py @@ -21,4 +21,4 @@ class aMSNOIMManager: def __init__(self, core): self._core = core - \ No newline at end of file + diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 050a3588..b77d71e8 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -19,23 +19,23 @@ def _onNickUpdated(self, new_nick): # TODO: parsing self._papyon_profile.display_name = new_nick.toString() self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) - + def _onPMUpdated(self, new_pm): # TODO: parsing self._papyon_profile.personal_message = new_pm.toString() self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) - + def _onDPUpdated(self, new_dp): # TODO: manage msn_objects self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) - + def _onPresenceUpdated(self, new_presence): for key in self._core.p2s: if self._core.p2s[key] == new_presence: break self._papyon_profile.presence = key self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) - + """ actions from the core """ def _onCMUpdated(self, new_media): self._papyon_profile.current_media = new_media diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py index 72f385df..e6d5e57b 100644 --- a/amsn2/core/profile.py +++ b/amsn2/core/profile.py @@ -9,30 +9,30 @@ class aMSNProfilesList(object): def __init__(self, profiles_dir): self.path = os.path.join(profiles_dir, "profiles.xml") self.updateProfilesList() - + def updateProfilesList(self): """ Reads the content of profiles.xml """ if os.access(self.path, os.F_OK): profiles_file = file(self.path, "r") - + self.tree = xml.etree.ElementTree.ElementTree(file=profiles_file) - + profiles_file.close() - + self.root_element = self.tree.getroot() else : self.root_element = xml.etree.ElementTree.Element("aMSNProfilesList") - + self.tree = xml.etree.ElementTree.ElementTree(element = self.root_element) - + def setProfileKey(self, profile_name, key, value): self.updateProfilesList() if self.ProfileIsListed(profile_name): profile_element = self.root_element.find(profile_name.replace("@", "_")) self.root_element.remove(profile_element) profile_dict = elementToDict(profile_element) - + if profile_dict.has_key(key): profile_dict[key] = value profile_element = dictToElement(profile_name.replace("@", "_"), profile_dict) @@ -40,20 +40,20 @@ def setProfileKey(self, profile_name, key, value): self.saveProfilesList() else: raise ProfileKeyNotListedError - + def getProfileKey(self, profile_name, key, default=None): self.updateProfilesList() if self.ProfileIsListed(profile_name): profile_element = self.root_element.find(profile_name.replace("@", "_")) profile_dict = elementToDict(profile_element) - + try: return profile_dict[key] except KeyError: return default else: return default - + def addProfile(self, profile, attributes_dict): """ Adds a profile section in profiles.xml """ self.updateProfilesList() @@ -61,7 +61,7 @@ def addProfile(self, profile, attributes_dict): profile_element = dictToElement(profile.email.replace("@","_"), attributes_dict) self.root_element.append(profile_element) self.saveProfilesList() - + def deleteProfile(self, profile): """ Deletes a profile section from profiles.xml """ self.updateProfilesList() @@ -69,18 +69,18 @@ def deleteProfile(self, profile): if profile_element.tag == profile.email.replace("@","_"): self.root_element.remove(profile_element) self.saveProfilesList() - + def getAllProfilesNames(self): """ Returns a list of all profiles' email addresses """ self.updateProfilesList() names_list = [] - + for profile_element in self.root_element: profile_dict = elementToDict(profile_element) names_list.append(profile_dict["email"]) - + return names_list - + def ProfileIsListed(self, profile_name): """ Returns True if the profile is in profiles.xml """ return (profile_name in self.getAllProfilesNames()) @@ -95,13 +95,13 @@ def __init__(self, profile): self.config = {"ns_server":'messenger.hotmail.com', "ns_port":1863, } - + def getKey(self, key, default = None): try: return self.config[key] except KeyError: return default - + def setKey(self, key, value): self.config[key] = value @@ -118,14 +118,14 @@ def getKey(self, key, default = None): return self.config[key] except KeyError: return default - + def setKey(self, key, value): self.config[key] = value class aMSNSmileyConfiguration(object): """ aMSNSmileyCinfiguration : A smiley object for profiles.""" def __init__(self, profile, smiley, config_dict): - """ config_dict must be a dictionary of configurations ("option": value, ...), + """ config_dict must be a dictionary of configurations ("option": value, ...), the "shortcut" and "image" keys are mandatory.""" self._profile = profile self._smiley = smiley @@ -146,14 +146,14 @@ def getKey(self, key, default = None): return self.config[key] except KeyError: return default - + def setKey(self, key, value): self.config[key] = value class aMSNProfile(object): """ aMSNProfile : a Class to represent an aMSN profile This class will contain all settings relative to a profile - and will store the protocol and GUI objects + and will store the protocol and GUI objects """ def __init__(self, email, profiles_dir): self.email = email @@ -171,7 +171,7 @@ def __init__(self, email, profiles_dir): ### "Smiley2_name":..... ### } self.smileys_configs = {} - + def isLocked(self): """ Returns whether the profile is locked or not""" return False @@ -179,14 +179,14 @@ def isLocked(self): def lock(self): """ Locks a profile to avoid concurrent access to it from multiple instances """ pass - + def unlock(self): """ Unlocks a profile to allow other instances to acces it """ pass - + def remove_dir(self): """ Removes profile's directory from disk """ - + for root, dirs, files in os.walk(self.directory, topdown=False): for name in files: os.remove(os.path.join(root, name)) @@ -215,14 +215,14 @@ def setPluginKey(self, plugin_name, key, value): return True except KeyError: return False - + def addPlugin(self, plugin_name, plugin_config_dict={}): self.plugins_configs[plugin_name] = \ aMSNPluginConfiguration(self, plugin_name, plugin_config_dict) - + def removePlugin(self, plugin_name): return self.plugins_configs.pop(plugin_name, None) - + def getSmileyKey(self, plugin_name, key, default = None): try: return self.plugins_configs[plugin_name].getKey(key, default) @@ -235,14 +235,14 @@ def setSmileyKey(self, plugin_name, key, value): return True except KeyError: return False - + def addSmiley(self, smiley_name, smiley_config_dict={"shortcut":"dummy", "image":"dummy"}): self.smileys_configs[smiley_name] = \ aMSNSmileyConfiguration(self, smiley_name, smiley_config_dict) - + def removeSmiley(self, smiley_name): return self.smileys_configs.pop(smiley_name, None) - + class aMSNProfileManager(object): """ aMSNProfileManager : The profile manager that takes care of storing @@ -255,21 +255,21 @@ def __init__(self): self._profiles_dir = os.path.join(os.environ['USERPROFILE'], "amsn2") else: self._profiles_dir = os.path.join(os.curdir, "amsn2") - + try : os.makedirs(self._profiles_dir, 0777) except : pass - + self.profiles_list = aMSNProfilesList(self._profiles_dir) - + self.profiles = {} self.loadAllProfiles() - + def profileExists(self, email): """ Checks whether a profile exists """ return self.getProfile(email) is not None - + def getProfile(self, email): """ Get a profile object by email """ try: @@ -286,7 +286,7 @@ def addProfile(self, email): if self.profileExists(email) is False: new_profile = aMSNProfile(email, self._profiles_dir) self.profiles[email] = new_profile - + return self.getProfile(email) def createProfile(self, email): @@ -299,40 +299,40 @@ def removeProfile(self, profile, and_delete=False): """ Removes a profile from the current instance of aMSN """ if self.profileExists(profile.email): del self.profiles[profile.email] - + self.profiles_list.deleteProfile(profile) - + if and_delete == True: self.deleteProfile(profile) - + def deleteProfile(self, profile): """ Removes a profile from the current instance of aMSN and deletes it from disk """ profile.remove_dir() return self.removeProfile(profile) - + def saveProfile(self, profile): - """ Stores a profile on disk """ - + """ Stores a profile on disk """ + config = profile.config.config - + config_section = dictToElement("Configurations", config) - + settings = {"email":profile.email, "username":profile.username, "alias":profile.alias, "password":profile.password, "account":profile.account } - + if profile.password == None : settings["password"] = "" if profile.account == None : settings["account"] = "" - + settings_section = dictToElement("Settings", settings) - - plugins = profile.plugins_configs + + plugins = profile.plugins_configs plugins_section = xml.etree.ElementTree.Element("Plugins") for plugin_name, plugin in plugins.iteritems(): @@ -341,22 +341,22 @@ def saveProfile(self, profile): smileys = profile.smileys_configs smileys_section = xml.etree.ElementTree.Element("Smileys") - + for smiley_name, smiley in smileys.iteritems(): smiley_section = dictToElement(smiley_name, smiley.config) smileys_section.append(smiley_section) - + root_section = xml.etree.ElementTree.Element("aMSNProfile") - + settings_section.append(config_section) settings_section.append(plugins_section) settings_section.append(smileys_section) root_section.append(settings_section) - + xml_tree = xml.etree.ElementTree.ElementTree(root_section) - + profile_dir = os.path.join(self._profiles_dir, profile.email) - + try : os.makedirs(profile_dir, 0777) os.makedirs(os.path.join(profile_dir, "smileys"), 0777) @@ -365,25 +365,25 @@ def saveProfile(self, profile): ## Other directories here except : pass - + profile_path = os.path.join(self._profiles_dir, profile.email, "config.xml") profile_file = file(profile_path, "w") xml_tree.write(profile_file) profile_file.close() - + list_opts={"email":profile.email, "auto_connect":False } self.profiles_list.addProfile(profile, list_opts) - + def saveAllProfiles(self): for profile in self.getAllProfiles(): self.saveProfile(profile) - + def loadAllProfiles(self): - """ Loads all profiles from disk """ + """ Loads all profiles from disk """ profiles_names = self.profiles_list.getAllProfilesNames() - + for profile_name in profiles_names : profile_file_path = os.path.join(self._profiles_dir, \ profile_name, \ @@ -401,7 +401,7 @@ def loadAllProfiles(self): settings.remove(plugins) smileys = settings_tree.find("Smileys") settings.remove(smileys) - + ### Loads Settings settings_dict = elementToDict(settings) profile = aMSNProfile(settings_dict['email'], self._profiles_dir) @@ -415,16 +415,16 @@ def loadAllProfiles(self): profile.password = None if profile.account == "" : profile.account = None - + ### Loads Configurations configs_dict = elementToDict(configs) profile.config.config = configs_dict - + ### Loads Plugins plugins_dict = {} for plugin_element in plugins: plugins_dict[plugin_element.tag] = elementToDict(plugin_element) - + for plugin_name, plugin_config_dict in plugins_dict.iteritems(): profile.addPlugin(plugin_name, plugin_config_dict) @@ -432,7 +432,7 @@ def loadAllProfiles(self): smileys_dict = {} for smiley_element in smileys: smileys_dict[smiley_element.tag] = elementToDict(smiley_element) - + for smiley_name, smiley_config_dict in smileys_dict.iteritems(): profile.addSmiley(smiley_name, smiley_config_dict) @@ -450,21 +450,21 @@ def dictToTuple(name, dict): value = int(dict['value']) else: value = dict['value'] - + config_pair = (key,eval(type)(value)) - + config_pair_list.append(config_pair) - + config_pair_list = [] for entry in element : entry_str = xml.etree.ElementTree.tostring(entry) - + parser=xml.parsers.expat.ParserCreate() parser.StartElementHandler = dictToTuple parser.Parse(entry_str, 1) del parser - + config_dict = dict(config_pair_list) return config_dict diff --git a/amsn2/core/theme_manager.py b/amsn2/core/theme_manager.py index 1c8e94fa..1bbb61c8 100644 --- a/amsn2/core/theme_manager.py +++ b/amsn2/core/theme_manager.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- #=================================================== -# +# # theme_manager.py - This file is part of the amsn2 package # # Copyright (C) 2008 Wil Alvarez @@ -29,16 +29,16 @@ def __init__(self): self._statusicons = {} self._displaypic = {} self._emblems = {} - + self.load() - + def __get(self, var, key): # TODO: evaluate if should be returned None when key is not valid if key in var.keys(): return var[key] else: return (None, None) - + def load(self): # Here aMSNThemeManager should read user's files to know what theme # will be loaded to each aspect @@ -46,7 +46,7 @@ def load(self): self._statusicons = aMSNStatusIconLoader().load('default') self._displaypic = aMSNDisplayPicLoader().load('default') self._emblems = aMSNEmblemLoader().load('default') - + def get_value(self, key): if (key.startswith('button_')): return self.get_button(key) @@ -59,7 +59,7 @@ def get_value(self, key): else: # TODO: This should raise a exception return (None, None) - + def get_button(self, key): return self.__get(self._buttons, key) @@ -81,36 +81,36 @@ def __init__(self, basedir): # Should be initialized after creating the class self._keys = [] self._dict = {} - + def load(self, theme='default'): self.theme = theme self._theme_dir = os.path.join(self._basedir, theme) - + for key in self._keys.keys(): image = self._keys[key] filepath = os.path.join(self._theme_dir, image) - + # Verificating if (not os.path.isfile(filepath)): filepath = os.path.join(self._defaultdir, image) - + self._dict[key] = ("Filename", filepath) - + return self._dict - + class aMSNButtonLoader(aMSNGenericLoader): def __init__(self): aMSNGenericLoader.__init__(self, "buttons") self._keys = { - 'button_nudge': 'nudge.png', + 'button_nudge': 'nudge.png', 'button_smile': 'smile.png', } - + class aMSNStatusIconLoader(aMSNGenericLoader): def __init__(self): aMSNGenericLoader.__init__(self, "status_icons") self._keys = { - 'buddy_online': 'online.png', + 'buddy_online': 'online.png', 'buddy_away': 'away.png', 'buddy_brb': 'away.png', 'buddy_idle': 'away.png', @@ -123,7 +123,7 @@ def __init__(self): 'buddy_blocked_off': 'blocked_off.png', 'buddy_webmsn': 'webmsn.png', } - + class aMSNDisplayPicLoader(aMSNGenericLoader): def __init__(self): aMSNGenericLoader.__init__(self, "displaypic") @@ -134,12 +134,12 @@ def __init__(self): 'dp_male': 'male.png', 'dp_nopic': 'nopic.png', } - + class aMSNEmblemLoader(aMSNGenericLoader): def __init__(self): aMSNGenericLoader.__init__(self, "emblems") self._keys = { - 'emblem_online': 'plain_emblem.png', + 'emblem_online': 'plain_emblem.png', 'emblem_away': 'away_emblem.png', 'emblem_brb': 'away_emblem.png', 'emblem_idle': 'away_emblem.png', @@ -149,4 +149,4 @@ def __init__(self): 'emblem_offline': 'offline_emblem.png', 'emblem_hidden': 'offline_emblem.png', 'emblem_blocked': 'blocked_emblem.png', - } \ No newline at end of file + } diff --git a/amsn2/core/views/keybindingview.py b/amsn2/core/views/keybindingview.py index 542427e7..e023ee31 100644 --- a/amsn2/core/views/keybindingview.py +++ b/amsn2/core/views/keybindingview.py @@ -14,13 +14,13 @@ class KeyBindingView(object): PAGEDOWN = "PageDown" INSERT = "Insert" DELETE = "Delete" - + def __init__(self, key = None, control = False, alt = False, shift = False): self.key = key self.control = control self.alt = alt self.shift = shift - + def __repr__(self): out = "" if self.control: @@ -30,6 +30,6 @@ def __repr__(self): if self.shift: out += "Shift-" out += self.key - + return out - + diff --git a/amsn2/core/views/menuview.py b/amsn2/core/views/menuview.py index 04cb5dc8..e5d4f7fe 100644 --- a/amsn2/core/views/menuview.py +++ b/amsn2/core/views/menuview.py @@ -6,7 +6,7 @@ class MenuItemView(object): RADIOBUTTONGROUP = "radiobuttongroup" SEPARATOR = "separator" COMMAND = "command" - + def __init__(self, type, label = None, icon = None, accelerator = None, radio_value = None, checkbox_value = False, disabled = False, command = None): """ Create a new MenuItemView @@ -48,7 +48,7 @@ def __init__(self, type, label = None, icon = None, accelerator = None, (radio_value is not None or checkbox_value is not False or icon is not None or - command is not None))): + command is not None))): raise ValueError, InvalidArgument new_label = label @@ -71,7 +71,7 @@ def __init__(self, type, label = None, icon = None, accelerator = None, done = True else: done = True - + self.type = type self.label = new_label @@ -84,7 +84,7 @@ def __init__(self, type, label = None, icon = None, accelerator = None, def addItem(self, item): self.items.append(item) - + class MenuView(object): @@ -93,4 +93,4 @@ def __init__(self): def addItem(self, item): self.items.append(item) - + diff --git a/amsn2/core/views/tooltipview.py b/amsn2/core/views/tooltipview.py index 035df832..29536669 100644 --- a/amsn2/core/views/tooltipview.py +++ b/amsn2/core/views/tooltipview.py @@ -4,4 +4,3 @@ def __init__(self): self.name = None self.icon = None - diff --git a/amsn2/gui/base/image.py b/amsn2/gui/base/image.py index a6460951..024eca38 100644 --- a/amsn2/gui/base/image.py +++ b/amsn2/gui/base/image.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- #=================================================== -# +# # image.py - This file is part of the amsn2 package # # Copyright (C) 2008 Wil Alvarez @@ -28,7 +28,7 @@ class aMSNImage(object): def __init__(self, theme_manager, view): self._theme_manager = theme_manager self.load(view) - + def load(self, view): i = 0 for (resource_type, value) in view.imgs: @@ -40,7 +40,7 @@ def load(self, view): else: loadMethod(value, view, i) i += 1 - + def _loadFromFilename(self, filename, view, index): """ Load an image from a path. This method should be reimplemented """ pass @@ -57,4 +57,4 @@ def _loadFromTheme(self, resource_name, view, index): def _loadFromNone(self, resource_name, view=None, index=0): pass - \ No newline at end of file + diff --git a/amsn2/gui/base/login.py b/amsn2/gui/base/login.py index 7c71af6f..659cb2ff 100644 --- a/amsn2/gui/base/login.py +++ b/amsn2/gui/base/login.py @@ -8,7 +8,7 @@ def __init__(self, amsn_core, parent): def show(self): """ Draw the login window """ raise NotImplementedError - + def hide(self): """ Hide the login window """ raise NotImplementedError diff --git a/amsn2/gui/base/main.py b/amsn2/gui/base/main.py index 5607922b..f2f7d514 100644 --- a/amsn2/gui/base/main.py +++ b/amsn2/gui/base/main.py @@ -7,7 +7,7 @@ class aMSNMainWindow(object): def __init__(self, amsn_core): pass - + def show(self): raise NotImplementedError @@ -16,6 +16,6 @@ def hide(self): def setTitle(self,title): raise NotImplementedError - + def setMenu(self,menu): raise NotImplementedError diff --git a/amsn2/gui/base/main_loop.py b/amsn2/gui/base/main_loop.py index 179b38ba..d25c740f 100644 --- a/amsn2/gui/base/main_loop.py +++ b/amsn2/gui/base/main_loop.py @@ -20,4 +20,4 @@ def timerAdd(self, delay, func): def quit(self): """ This will be called when the core wants to exit """ raise NotImplementedError - + diff --git a/amsn2/gui/base/window.py b/amsn2/gui/base/window.py index 768609b7..d7039328 100644 --- a/amsn2/gui/base/window.py +++ b/amsn2/gui/base/window.py @@ -17,7 +17,7 @@ def setTitle(self, text): @text : a string """ raise NotImplementedError - + def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView diff --git a/amsn2/gui/front_ends/cocoa/__init__.py b/amsn2/gui/front_ends/cocoa/__init__.py index dd3f60f4..e5d55c32 100644 --- a/amsn2/gui/front_ends/cocoa/__init__.py +++ b/amsn2/gui/front_ends/cocoa/__init__.py @@ -16,9 +16,9 @@ def load(): imp.find_module('objc') imp.find_module('Foundation') imp.find_module('AppKit') - + gui.GUIManager.registerFrontEnd("cocoa", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/cocoa/contact_list.py b/amsn2/gui/front_ends/cocoa/contact_list.py index f73c45e4..983210ac 100644 --- a/amsn2/gui/front_ends/cocoa/contact_list.py +++ b/amsn2/gui/front_ends/cocoa/contact_list.py @@ -4,11 +4,11 @@ from amsn2.core.views import StringView from amsn2.core.views import GroupView from amsn2.core.views import ContactView - + class aMSNContactList(base.aMSNContactListWindow): def __init__(self, amsn_core, parent): - pass + pass def show(self): pass @@ -17,14 +17,14 @@ def hide(self): pass def contactStateChange(self, contact): - pass + pass def contactNickChange(self, contact): pass - + def contactPSMChange(self, contact): pass - + def contactAlarmChange(self, contact): pass @@ -33,7 +33,7 @@ def contactDisplayPictureChange(self, contact): def contactSpaceChange(self, contact): pass - + def contactSpaceFetched(self, contact): pass @@ -48,7 +48,7 @@ def contactMoved(self, from_group, to_group, contact): def contactAdded(self, group, contact): pass - + def contactRemoved(self, group, contact): pass diff --git a/amsn2/gui/front_ends/cocoa/image.py b/amsn2/gui/front_ends/cocoa/image.py index 772208a9..85eb5c9e 100644 --- a/amsn2/gui/front_ends/cocoa/image.py +++ b/amsn2/gui/front_ends/cocoa/image.py @@ -5,8 +5,8 @@ class Image(object): """ This interface will represent an image to be used by the UI""" def __init__(self, amsn_core, parent): - """Initialize the interface. You should store the reference to the core in here """ - self._img = NSImage.alloc().initWithSize_((1,1)) + """Initialize the interface. You should store the reference to the core in here """ + self._img = NSImage.alloc().initWithSize_((1,1)) def load(self, resource_name, value): """ This method is used to load an image using the name of a resource and a value for that resource @@ -16,7 +16,7 @@ def load(self, resource_name, value): - some more :) """ self._img.release() - + if (resource_name == 'File'): self._img = NSImage.alloc().initWithContentsOfFile_(str(value)) diff --git a/amsn2/gui/front_ends/cocoa/login.py b/amsn2/gui/front_ends/cocoa/login.py index 413ddd11..1d503ed5 100644 --- a/amsn2/gui/front_ends/cocoa/login.py +++ b/amsn2/gui/front_ends/cocoa/login.py @@ -5,30 +5,30 @@ class aMSNLoginWindow(object): loginView = None loggingInView = None - + def __init__(self, amsn_core, parent): self.amsn_core = amsn_core self.parent = parent - + self.switch_to_profile(None) - + # Save the cocoa views that can be loaded in the main window. self.loginView = CocoaLoginView.getView() self.loggingInView = CocoaLoggingInView.getView() - + # Save a call back method for when the cocoa login: message is sent. self.loginView.setParent(self) self.loggingInView.setParent(self) - + def show(self): # Load the login view into the main window. self.parent._loadView(self.loginView) - + # Call back method. def login(self, username, password): self._username = username self._password = password - + # Load loggingInView into main window. self.parent._loadView(self.loggingInView) self.signin() @@ -47,7 +47,7 @@ def signin(self): self.current_profile.email = self._username self.current_profile.password = self._password self.amsn_core.signinToAccount(self, self.current_profile) - + # Set the status message in the login window. def onConnecting(self, progress, message): self.loggingInView.setStatus(message) diff --git a/amsn2/gui/front_ends/cocoa/main.py b/amsn2/gui/front_ends/cocoa/main.py index f775d361..3de79bc1 100644 --- a/amsn2/gui/front_ends/cocoa/main.py +++ b/amsn2/gui/front_ends/cocoa/main.py @@ -4,32 +4,32 @@ class aMSNMainWindow(base.aMSNMainWindow): cocoaWin = None - + def __init__(self, amsn_core): self._amsn_core = amsn_core - + # Load our window. self.cocoaWin = CocoaMainWindow.aMSNCocoaMainWindow.alloc().init() - + def setMenu(self, menu_view): pass - + def setTitle(self, title): self.cocoaWin.setTitle_(title) - + def show(self): self.cocoaWin.makeKeyAndOrderFront_(self.cocoaWin) self._amsn_core.idlerAdd(self.__on_show) def hide(self): self.cocoaWin.orderOut_(self.cocoaWin) - + def _loadView(self, view, resize=False): prevFrame = self.cocoaWin.frame() frame = self.cocoaWin.frameRectForContentRect_(view.frame()) self.cocoaWin.setFrame_display_animate_((prevFrame.origin, frame.size), True, bool(resize)) self.cocoaWin.setContentView_(view) self.cocoaWin.orderFront_(self.cocoaWin) - + def __on_show(self): self._amsn_core.mainWindowShown() diff --git a/amsn2/gui/front_ends/cocoa/main_loop.py b/amsn2/gui/front_ends/cocoa/main_loop.py index 97efc10d..9232cf34 100644 --- a/amsn2/gui/front_ends/cocoa/main_loop.py +++ b/amsn2/gui/front_ends/cocoa/main_loop.py @@ -14,16 +14,16 @@ def __init__(self, amsn_core): def run(self): self._mainloop = gobject.MainLoop(is_running=True) self._context = self._mainloop.get_context() - + self._app = aMSNCocoaNSApplication.sharedApplication() self._app.finishLaunching() - + def glib_context_iterate(): iters = 0 while iters < 10 and self._context.pending(): self._context.iteration() return True - + while True: try: # This hangs for at most 100ms, or until an event is fired. @@ -33,7 +33,7 @@ def glib_context_iterate(): except KeyboardInterrupt: self.quit() - + def idlerAdd(self, func): gobject.idle_add(func) @@ -43,20 +43,20 @@ def timerAdd(self, delay, func): def quit(self): self._mainloop.quit() sys.exit() - + class aMSNCocoaNSApplication(NSApplication): def init(self): super(aMSNCocoaNSApplication, self).init() self.setDelegate_(self) return self - - # Override run so that it doesn't hang. We'll process events ourself thanks! + + # Override run so that it doesn't hang. We'll process events ourself thanks! def run(self): return Null - + # Looks at the events stack and processes the topmost. # return: True - An event was processed. - # False - No events in queue. + # False - No events in queue. def processEvents(self, timeout=100): # Get the next event from the queue. if timeout < 0: @@ -65,19 +65,19 @@ def processEvents(self, timeout=100): eventTimeout = NSDate.distantFuture() else: eventTimeout = NSDate.dateWithTimeIntervalSinceNow_(float(timeout/1000.0)) - + # NSAnyEventMask = 0xffffffff - http://osdir.com/ml/python.pyobjc.devel/2003-10/msg00130.html event = self.nextEventMatchingMask_untilDate_inMode_dequeue_( \ 0xffffffff, \ eventTimeout, \ NSDefaultRunLoopMode , \ True) - + # Process event if we have one. (python None == cocoa nil) if event != None: self.sendEvent_(event) return True - + return False # We call this so that the if someone calls NSApplication.sharedApplication again, they get an aMSNCocoaNSApplication instance rather than a new NSApplication. diff --git a/amsn2/gui/front_ends/cocoa/nibs/CocoaLoggingInView.py b/amsn2/gui/front_ends/cocoa/nibs/CocoaLoggingInView.py index 20de0b02..547658f9 100644 --- a/amsn2/gui/front_ends/cocoa/nibs/CocoaLoggingInView.py +++ b/amsn2/gui/front_ends/cocoa/nibs/CocoaLoggingInView.py @@ -12,15 +12,15 @@ def getView(): class aMSNCocoaLoggingInView(NSView): statusText = IBOutlet('statusText') # Text field with status text. progressIndicator = IBOutlet('progressIndicator') # Spinner. - + def setParent(self, parent): self.parent = parent - + def awakeFromNib(self): global view view = self self.progressIndicator.startAnimation_(self) - + def setStatus(self, newText): self.statusText.setStringValue_(newText) diff --git a/amsn2/gui/front_ends/cocoa/nibs/CocoaLoginView.py b/amsn2/gui/front_ends/cocoa/nibs/CocoaLoginView.py index 8be46202..5171bcd3 100644 --- a/amsn2/gui/front_ends/cocoa/nibs/CocoaLoginView.py +++ b/amsn2/gui/front_ends/cocoa/nibs/CocoaLoginView.py @@ -18,14 +18,14 @@ class aMSNCocoaLoginView(NSView): passwordLabel = IBOutlet('passwordLabel') # Text label next to passwordField. rememberMe = IBOutlet('rememberMe') # Check box for save profile. rememberPassword = IBOutlet('rememberPassword') # Check box for save password. - + def awakeFromNib(self): global loginView loginView = self - + def setParent(self, parent): self.parent = parent - + def login_(self): username = str(self.usernameField.stringValue()) password = str(self.passwordField.stringValue()) diff --git a/amsn2/gui/front_ends/cocoa/nibs/CocoaSplashScreenView.py b/amsn2/gui/front_ends/cocoa/nibs/CocoaSplashScreenView.py index 0a60d29c..8bf93c5f 100644 --- a/amsn2/gui/front_ends/cocoa/nibs/CocoaSplashScreenView.py +++ b/amsn2/gui/front_ends/cocoa/nibs/CocoaSplashScreenView.py @@ -10,12 +10,12 @@ def getView(): class aMSNCocoaSplashScreenView(NSView): statusText = IBOutlet('statusText') # Text field with status text. - + def awakeFromNib(self): global view view = self - + def setStatus(self, text): self.statusText.setStringValue_(text) -NSBundle.loadNibNamed_owner_('aMSNCocoaSplashScreenView', NSApplication.sharedApplication()) \ No newline at end of file +NSBundle.loadNibNamed_owner_('aMSNCocoaSplashScreenView', NSApplication.sharedApplication()) diff --git a/amsn2/gui/front_ends/cocoa/nibs/files/aMSNCocoaLoginView.nib/CocoaLoginWindow.py b/amsn2/gui/front_ends/cocoa/nibs/files/aMSNCocoaLoginView.nib/CocoaLoginWindow.py index ff005e20..e5a22451 100644 --- a/amsn2/gui/front_ends/cocoa/nibs/files/aMSNCocoaLoginView.nib/CocoaLoginWindow.py +++ b/amsn2/gui/front_ends/cocoa/nibs/files/aMSNCocoaLoginView.nib/CocoaLoginWindow.py @@ -6,7 +6,7 @@ from Foundation import * from AppKit import * -#import +#import #NibClassBuilder.extractClasses('aMSNCocoaMainWindow') @@ -16,6 +16,6 @@ def test_(self): pass def awakeFromNib_(self): - print 'hello world' + print 'hello world' NSBundle.loadNibNamed_owner_('aMSNCocoaMainWindow.nib', currentBundle()) diff --git a/amsn2/gui/front_ends/cocoa/splash.py b/amsn2/gui/front_ends/cocoa/splash.py index 15fde27e..5b8fd256 100644 --- a/amsn2/gui/front_ends/cocoa/splash.py +++ b/amsn2/gui/front_ends/cocoa/splash.py @@ -9,13 +9,13 @@ def __init__(self, amsn_core, parent): def show(self): self.parent._loadView(self.view) - + def hide(self): pass - + def setText(self, text): self.view.setStatus(text) - + def setImage(self, image): pass diff --git a/amsn2/gui/front_ends/console/__init__.py b/amsn2/gui/front_ends/console/__init__.py index a30778b9..e126a0c1 100644 --- a/amsn2/gui/front_ends/console/__init__.py +++ b/amsn2/gui/front_ends/console/__init__.py @@ -17,7 +17,7 @@ def load(): # try to find any necessary module # imp.find_module() gui.GUIManager.registerFrontEnd("console", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/console/console.py b/amsn2/gui/front_ends/console/console.py index 6bdd2f25..ebcd9fa2 100644 --- a/amsn2/gui/front_ends/console/console.py +++ b/amsn2/gui/front_ends/console/console.py @@ -2,4 +2,4 @@ from main import * from contact_list import * from login import * - + diff --git a/amsn2/gui/front_ends/console/contact_list.py b/amsn2/gui/front_ends/console/contact_list.py index fdcdeb58..ad494a3e 100644 --- a/amsn2/gui/front_ends/console/contact_list.py +++ b/amsn2/gui/front_ends/console/contact_list.py @@ -14,7 +14,7 @@ def __init__(self, name, presence): papyon.Presence.OUT_TO_LUNCH:"lunch", papyon.Presence.INVISIBLE:"hidden", papyon.Presence.OFFLINE:"offline"} - + def is_online(self): return self.presence != papyon.Presence.OFFLINE @@ -36,7 +36,7 @@ def count(self): online +=1 return (online, total) - + class aMSNContactList(base.aMSNContactList): def __init__(self, amsn_core): @@ -53,15 +53,15 @@ def hide(self): def contactStateChange(self, contact): for group in contact.groups: self.groups[group.id].contacts[contact.id].presence = contact.presence - + self.__update_view() def contactNickChange(self, contact): pass - + def contactPSMChange(self, contact): pass - + def contactAlarmChange(self, contact): pass @@ -70,7 +70,7 @@ def contactDisplayPictureChange(self, contact): def contactSpaceChange(self, contact): pass - + def contactSpaceFetched(self, contact): pass @@ -86,7 +86,7 @@ def contactMoved(self, from_group, to_group, contact): def contactAdded(self, group, contact): self.groups[group.id].contacts[contact.id] = Contact(contact.display_name, contact.presence) self.__update_view() - + def contactRemoved(self, group, contact): pass @@ -111,7 +111,7 @@ def cget(self, option, value): def __cls(self): print "" - + def __update_view(self): self.__cls() for g in self.groups: diff --git a/amsn2/gui/front_ends/console/login.py b/amsn2/gui/front_ends/console/login.py index 0c1b68ed..11892f9d 100644 --- a/amsn2/gui/front_ends/console/login.py +++ b/amsn2/gui/front_ends/console/login.py @@ -3,7 +3,7 @@ class aMSNLoginWindow(object): def __init__(self, amsn_core): self._amsn_core = amsn_core self.switch_to_profile(None) - + def show(self): if self._username is not None and self._username != "": print "Account : %s" % (self._username) @@ -32,7 +32,7 @@ def signin(self): self.current_profile.email = self._username self.current_profile.password = self._password self._amsn_core.signinToAccount(self, self.current_profile) - + def onConnecting(self, progress, message): print "Connecting..." diff --git a/amsn2/gui/front_ends/console/main.py b/amsn2/gui/front_ends/console/main.py index 3cc8e807..bdd0514a 100644 --- a/amsn2/gui/front_ends/console/main.py +++ b/amsn2/gui/front_ends/console/main.py @@ -10,7 +10,7 @@ def show(self): def hide(self): pass - + def __on_show(self): self._amsn_core.mainWindowShown() - + diff --git a/amsn2/gui/front_ends/console/main_loop.py b/amsn2/gui/front_ends/console/main_loop.py index 1ce6eeaf..8bcf40a8 100644 --- a/amsn2/gui/front_ends/console/main_loop.py +++ b/amsn2/gui/front_ends/console/main_loop.py @@ -13,7 +13,7 @@ def run(self): except KeyboardInterrupt: self.quit() - + def idlerAdd(self, func): gobject.idle_add(func) @@ -22,4 +22,4 @@ def timerAdd(self, delay, func): def quit(self): self._mainloop.quit() - + diff --git a/amsn2/gui/front_ends/curses/__init__.py b/amsn2/gui/front_ends/curses/__init__.py index f1f2b7ee..06ac6457 100644 --- a/amsn2/gui/front_ends/curses/__init__.py +++ b/amsn2/gui/front_ends/curses/__init__.py @@ -16,7 +16,7 @@ def load(): imp.find_module("curses") gui.GUIManager.registerFrontEnd("curses", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/curses/command.py b/amsn2/gui/front_ends/curses/command.py index df411f7d..8e2881c3 100644 --- a/amsn2/gui/front_ends/curses/command.py +++ b/amsn2/gui/front_ends/curses/command.py @@ -21,7 +21,7 @@ def setCharCb(self, ch_cb): self._on_char_cb = ch_cb if ch_cb is not None: self._cb_cond.notify() - + def _get_key(self): while( True ): with self._cb_cond: diff --git a/amsn2/gui/front_ends/curses/login.py b/amsn2/gui/front_ends/curses/login.py index a9c7b1d9..03e13e8b 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -48,7 +48,7 @@ def _insert(self, str): for ch in str: self._password += ch self._txtbox.do_command('*') - + class aMSNLoginWindow(object): def __init__(self, amsn_core, parent): self._amsn_core = amsn_core @@ -61,7 +61,7 @@ def __init__(self, amsn_core, parent): sy = int((y - wy)/2) sx = int((x - wx)/2) self._win = curses.newwin(wy, wx, sy, sx) - + def show(self): self._win.border() self._win.bkgd(' ', curses.color_pair(1)) @@ -70,9 +70,9 @@ def show(self): self._win.addstr(8, 5, "Password : ", curses.A_BOLD) self._password_t = PasswordBox(self._win, 8, 17, self._password) - + self._win.refresh() - + self._username_t.edit() self._password_t.edit() @@ -97,12 +97,12 @@ def signin(self): self.current_profile.email = self._username_t.value() self.current_profile.password = self._password_t.value() self._amsn_core.signinToAccount(self, self.current_profile) - + def onConnecting(self, progress, message): self._username_t = None self._password_t = None self._win.clear() - + self._win.addstr(10, 25, message, curses.A_BOLD | curses.A_STANDOUT) self._win.refresh() diff --git a/amsn2/gui/front_ends/gtk/__init__.py b/amsn2/gui/front_ends/gtk/__init__.py index 0fca579c..2eb923d3 100644 --- a/amsn2/gui/front_ends/gtk/__init__.py +++ b/amsn2/gui/front_ends/gtk/__init__.py @@ -16,7 +16,7 @@ def load(): imp.find_module("gtk") gui.GUIManager.registerFrontEnd("gtk", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/gtk/chat_window.py b/amsn2/gui/front_ends/gtk/chat_window.py index b5c8cdc3..ad5accb8 100644 --- a/amsn2/gui/front_ends/gtk/chat_window.py +++ b/amsn2/gui/front_ends/gtk/chat_window.py @@ -41,18 +41,18 @@ def __init__(self, amsn_core): self.set_default_size(550, 450) self.set_position(gtk.WIN_POS_CENTER) self.set_title("aMSN - Chatwindow") - + #leave def addChatWidget(self, chat_widget): print 'addedChatWidget' #if self.child is not None: self.remove(self.child) - #if self.child is not None: + #if self.child is not None: # self.show_all() # return if self.child is None: self.add(chat_widget) self.child = chat_widget - + self.show_all() self.child.entry.grab_focus() @@ -60,7 +60,7 @@ def addChatWidget(self, chat_widget): class aMSNChatWidget(base.aMSNChatWidget, gtk.VBox): def __init__(self, amsn_conversation, parent): gtk.VBox.__init__(self, False, 0) - + self._parent = parent self._amsn_conversation = amsn_conversation self._amsn_core = amsn_conversation._core @@ -71,24 +71,24 @@ def __init__(self, amsn_conversation, parent): self.nickstyle = "color:#555555; margin-left:2px" self.msgstyle = "margin-left:15px" self.infostyle = "margin-left:2px; font-style:italic; color:#6d6d6d" - + self.chatheader = aMSNChatHeader() - + # Middle self.textview = HtmlTextView() tscroll = gtk.ScrolledWindow() tscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) tscroll.set_shadow_type(gtk.SHADOW_ETCHED_IN) tscroll.add(self.textview) - + #self.chat_roster = ChatRoster() - + self.middle_box = gtk.HPaned() self.middle_box.pack1(tscroll, True, True) - + # Bottom self.entry = MessageTextView() - + # Tags for entry tag = self.entry.get_buffer().create_tag("bold") tag.set_property("weight", pango.WEIGHT_BOLD) @@ -102,14 +102,14 @@ def __init__(self, amsn_conversation, parent): tag.set_property("foreground_gdk", gtk.gdk.Color(0,0,0)) tag = self.entry.get_buffer().create_tag("family") tag.set_property("family", "MS Sans Serif") - + escroll = gtk.ScrolledWindow() escroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) escroll.set_placement(gtk.CORNER_TOP_LEFT) escroll.set_shadow_type(gtk.SHADOW_IN) escroll.set_size_request(-1, 40) escroll.add(self.entry) - + # Register button icons as stock icons iconfactory = gtk.IconFactory() icons = ['button_smile', 'button_nudge'] @@ -131,10 +131,10 @@ def __init__(self, amsn_conversation, parent): self.button_color = gtk_extras.ColorToolButton() self.button_font = gtk_extras.FontToolButton() self.button8 = gtk.ToolButton(gtk.STOCK_CLEAR) - + self.button_font.set_show_size(0) self.button_font.set_show_style(0) - + bbox = gtk.Toolbar() bbox.set_style(gtk.TOOLBAR_ICONS) bbox.insert(self.button1, -1) @@ -148,44 +148,44 @@ def __init__(self, amsn_conversation, parent): bbox.insert(self.button_strikethrough, -1) bbox.insert(gtk.SeparatorToolItem(), -1) bbox.insert(self.button8, -1) - + bottom_box = gtk.VBox(False, 0) bottom_box.pack_start(bbox, False, False, 0) bottom_box.pack_start(escroll, True,True, 0) - + self.statusbar = gtk.Statusbar() self.statusbar.set_has_resize_grip(False) self.statusbar.set_spacing(0) - + self.__set_statusbar_text('Welcome to aMSN2') - + vpaned = gtk.VPaned() vpaned.pack1(self.middle_box, True, True) vpaned.pack2(bottom_box, False, True) - + self.pack_start(self.chatheader, False, False, self.padding) self.pack_start(vpaned, True, True, self.padding) self.pack_start(self.statusbar, False, False) - + #Connections #======== - ''' + ''' self.entrytextview.connect('focus-in-event', self.chatman.setUrgencyHint, False) self.entrytextview.get_buffer().connect("changed",self.__updateTextFormat) self.textview.connect("button-press-event", self.__rightClick) - + ''' ''' self.textview.connect("url-clicked", self.__on_url_clicked) - + self.button1.connect("clicked", self.__create_smiles_window) - self.button3.connect("clicked", + self.button3.connect("clicked", self.__on_changed_text_effect, 'bold') - self.button4.connect("clicked", + self.button4.connect("clicked", self.__on_changed_text_effect, 'italic') - self.button5.connect("clicked", + self.button5.connect("clicked", self.__on_changed_text_effect, 'underline') - self.button6.connect("clicked", + self.button6.connect("clicked", self.__on_changed_text_effect, 'strikethrough') self.button7.connect("clicked", self.__on_changed_text_color) ''' @@ -200,31 +200,31 @@ def __init__(self, amsn_conversation, parent): self.button8.connect("clicked", self.__on_clear_textview) self.entry.connect('mykeypress', self.__on_chat_send) self.entry.connect('key-press-event', self.__on_typing_event) - + def __updateTextFormat(self, textbuffer): self.reapply_text_effects(); self.__on_changed_text_color(self.button_color) self.__on_changed_text_font(self.button_font) - + def __on_changed_text_effect(self, button, tag_type): buffer = self.entry.get_buffer(); if button.get_active(): buffer.apply_tag_by_name(tag_type, buffer.get_start_iter(), buffer.get_end_iter()) else: buffer.remove_tag_by_name(tag_type, buffer.get_start_iter(), buffer.get_end_iter()) - + def reapply_text_effects(self): self.__on_changed_text_effect(self.button_bold, "bold") self.__on_changed_text_effect(self.button_italic, "italic") self.__on_changed_text_effect(self.button_underline, "underline") self.__on_changed_text_effect(self.button_strikethrough, "strikethrough") - + def __on_changed_text_color(self, button): buffer = self.entry.get_buffer(); tag = buffer.get_tag_table().lookup("foreground") tag.set_property("foreground_gdk", button.get_color()) buffer.apply_tag_by_name("foreground", buffer.get_start_iter(), buffer.get_end_iter()) - + def __on_changed_text_font(self, button): buffer = self.entry.get_buffer(); font_name = self.button_font.get_font_name() @@ -232,10 +232,10 @@ def __on_changed_text_font(self, button): tag = buffer.get_tag_table().lookup("family") tag.set_property("family", font_family) buffer.apply_tag_by_name("family", buffer.get_start_iter(), buffer.get_end_iter()) - + def __clean_string(self, str): return cgi.escape(str) - + def __on_chat_send(self, entry, event_keyval, event_keymod): if (event_keyval == gtk.keysyms.Return): buffer = entry.get_buffer() @@ -244,7 +244,7 @@ def __on_chat_send(self, entry, event_keyval, event_keymod): entry.clear() entry.grab_focus() if (msg == ''): return False - + color = self.button_color.get_color() hex8 = "%.2x%.2x%.2x" % ((color.red/0x101), (color.green/0x101), (color.blue/0x101)) style = papyon.TextFormat.NO_EFFECT @@ -258,41 +258,41 @@ def __on_chat_send(self, entry, event_keyval, event_keymod): strv = StringView() strv.appendText(msg) self._amsn_conversation.sendMessage(strv, format) - + def __on_clear_textview(self, widget): buffer = self.textview.get_buffer() start = buffer.get_start_iter() end = buffer.get_end_iter() buffer.delete(start, end) - + def __on_typing_event(self, widget, event): self._amsn_conversation.sendTypingNotification() - + def __on_nudge_send(self, widget): self.__print_info('Nudge sent') self._amsn_conversation.sendNudge() - + def __print_chat(self, nick, msg, sender): html = '
' if (self.last_sender != sender): - html += '%s
' % (self.nickstyle, + html += '%s
' % (self.nickstyle, nick) html += '[%s] %s
' % (self.msgstyle, time.strftime('%X'), msg) - + self.textview.display_html(html) self.textview.scroll_to_bottom() - + def __print_info(self, msg): html = '
%s
' % (self.infostyle, msg) self.textview.display_html(html) self.textview.scroll_to_bottom() - + def __set_statusbar_text(self, msg): context = self.statusbar.get_context_id('msg') self.statusbar.pop(context) self.statusbar.push(context, msg) - + def onMessageReceived(self, messageview, formatting=None): text = messageview.toStringView().toHtmlString() text = self.__clean_string(text) @@ -300,7 +300,7 @@ def onMessageReceived(self, messageview, formatting=None): nick = str(nick.replace('\n', '
')) msg = str(msg.replace('\n', '
')) sender = messageview.sender.toString() - + # peacey: Check formatting of styles and perform the required changes if formatting: fmsg = '''''' fmsg += msg fmsg += "" else: fmsg = msg - + self.__print_chat(nick, fmsg, sender) - + self.last_sender = sender - + def onUserJoined(self, contact): print "%s joined the conversation" % (contact,) self.__print_info("%s joined the conversation" % (contact,)) @@ -348,41 +348,41 @@ def nudge(self): class aMSNChatHeader(gtk.EventBox): def __init__(self, cview=None): gtk.EventBox.__init__(self) - + self.buddy_icon = gtk.Image() self.title = gtk.Label() self.dp = gtk.Image() self.title_color = gtk.gdk.color_parse('#dadada') self.psm_color = '#999999' - + self.title.set_use_markup(True) self.title.set_justify(gtk.JUSTIFY_LEFT) self.title.set_ellipsize(pango.ELLIPSIZE_END) self.title.set_alignment(xalign=0, yalign=0.5) self.title.set_padding(xpad=2, ypad=2) - + self.dp.set_size_request(50,50) - + hbox = gtk.HBox(False,0) hbox.pack_start(self.buddy_icon, False,False,0) hbox.pack_start(self.title, True,True,0) hbox.pack_start(self.dp, False,False,0) - + self.modify_bg(gtk.STATE_NORMAL, self.title_color) self.add(hbox) - + self.update(cview) - + def update(self, cview): if cview is None: nickname = 'Me' psm = 'Testing aMSN' - + title = '%s' % (nickname, ) - + if(psm != ''): - title += '\n%s' % ( + title += '\n%s' % ( self.psm_color, psm) - + self.title.set_markup(title) - + diff --git a/amsn2/gui/front_ends/gtk/common.py b/amsn2/gui/front_ends/gtk/common.py index 4ae525fb..c1cfde34 100644 --- a/amsn2/gui/front_ends/gtk/common.py +++ b/amsn2/gui/front_ends/gtk/common.py @@ -5,18 +5,18 @@ GUI_FONT = pango.FontDescription('normal 8') def stringvToHtml(stringv): - out = '' - for x in stringv._elements: - if x.getType() == StringView.TEXT_ELEMENT: - out += x.getValue() - elif x.getType() == StringView.ITALIC_ELEMENT: - if x.getValue(): - out += '' - else: - out += '' - return out + out = '' + for x in stringv._elements: + if x.getType() == StringView.TEXT_ELEMENT: + out += x.getValue() + elif x.getType() == StringView.ITALIC_ELEMENT: + if x.getValue(): + out += '' + else: + out += '' + return out def escape_pango(str): str = gobject.markup_escape_text(str) str = str.replace('\n',' ') - return str \ No newline at end of file + return str diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index ac3f32eb..335315da 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -1,21 +1,21 @@ # -*- coding: utf-8 -*- #=================================================== -# +# # contact_list.py - This file is part of the amsn2 package # # Copyright (C) 2008 Wil Alvarez # # This script is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software +# the terms of the GNU General Public License as published by the Free Software # Foundation; either version 3 of the License, or (at your option) any later # version. # -# This script is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# This script is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # -# You should have received a copy of the GNU General Public License along with +# You should have received a copy of the GNU General Public License along with # this script (see COPYING); if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # @@ -44,53 +44,53 @@ def __init__(self, amsn_core, parent): '''Constructor''' gtk.VBox.__init__(self) base.aMSNContactListWindow.__init__(self, amsn_core, parent) - + self._amsn_core = amsn_core self._main_win = parent self._skin = amsn_core._skin_manager.skin self._theme_manager = self._amsn_core._theme_manager self._myview = amsn_core._personalinfo_manager._personalinfoview - + self._clwidget = aMSNContactListWidget(amsn_core, self) - + self.__create_controls() self.__create_box() - + self._main_win.set_view(self) - + self.show_all() self.__setup_window() - + def __create_controls(self): ###self.psmlabel.modify_font(common.GUI_FONT) # Main Controls self.display = gtk.Image() self.display.set_size_request(64,64) - + self.nicklabel = gtk.Label() self.nicklabel.set_alignment(0, 0) self.nicklabel.set_use_markup(True) self.nicklabel.set_ellipsize(pango.ELLIPSIZE_END) self.nicklabel.set_markup('Loading...') - + self.btnNickname = gtk.Button() self.btnNickname.set_relief(gtk.RELIEF_NONE) self.btnNickname.add(self.nicklabel) self.btnNickname.set_alignment(0,0) self.btnNickname.connect("clicked",self.__on_btnNicknameClicked) - + self.psm = gtk.Entry() - + self.psmlabel = gtk.Label() self.psmlabel.set_use_markup(True) self.psmlabel.set_ellipsize(pango.ELLIPSIZE_END) self.psmlabel.set_markup('<Personal message>') - + self.btnPsm = gtk.Button() self.btnPsm.add(self.psmlabel) self.btnPsm.set_relief(gtk.RELIEF_NONE) self.btnPsm.set_alignment(0,0) - + # status list self.status_values = {} status_list = gtk.ListStore(gtk.gdk.Pixbuf, str, str) @@ -106,12 +106,12 @@ def __create_controls(self): status_list.append([icon, name, key]) del icon gc.collect() - + iconCell = gtk.CellRendererPixbuf() iconCell.set_property('xalign', 0.0) txtCell = gtk.CellRendererText() txtCell.set_property('xalign', 0.0) - + self.status = gtk.ComboBox() self.status.set_model(status_list) self.status.set_active(0) @@ -120,7 +120,7 @@ def __create_controls(self): self.status.add_attribute(iconCell, 'pixbuf',0) self.status.add_attribute(txtCell, 'markup',1) self.status.connect('changed', self.onStatusChanged) - + def __create_box(self): frameDisplay = gtk.Frame() frameDisplay.add(self.display) @@ -146,39 +146,39 @@ def __create_box(self): header = gtk.HBox(False, 1) header.pack_start(headerLeft, False, False, 0) header.pack_start(headerRight, True, True, 0) - + scrollwindow = gtk.ScrolledWindow() scrollwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN) scrollwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrollwindow.add(self._clwidget) - + bottom = gtk.HBox(False, 0) bottom.pack_start(self.status, True, True, 0) - + self.pack_start(header, False, False, 2) self.pack_start(scrollwindow, True, True, 2) self.pack_start(bottom, False, False, 2) - + def __setup_window(self): self.psm.hide() self.btnPsm.show() - + _, filename = self._theme_manager.get_dp('dp_nopic') pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, 64) self.display.set_from_pixbuf(pixbuf) del pixbuf gc.collect() - + def show(self): - pass + pass def hide(self): - pass - + pass + def setTitle(self, text): self._main_win.set_title(text) - + def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView @@ -209,10 +209,10 @@ def onStatusChanged(self, combobox): # FIXME: changing status to 'offline' will disconnect, so return to login window # also fix papyon, gives an error on setting 'offline' self._myview.presence = key - + def __on_btnNicknameClicked(self, source): self.__switchToNickInput() - + def __switchToNickInput(self): """ Switches the nick button into a text area for editing of the nick name.""" @@ -222,7 +222,7 @@ def __switchToNickInput(self): self.btnNickname.add(entry) entry.show() entry.connect("activate", self.__switchFromNickInput) - + def __switchFromNickInput(self, source): """ When in the editing state of nickname, change back to the uneditable label state. @@ -240,47 +240,47 @@ def __init__(self, amsn_core, parent): """Constructor""" base.aMSNContactListWidget.__init__(self, amsn_core, parent) gtk.TreeView.__init__(self) - + self._amsn_core = amsn_core self._cwin = parent self.groups = [] self.contacts = {} - + nick = gtk.CellRendererText() nick.set_property('ellipsize-set',True) nick.set_property('ellipsize', pango.ELLIPSIZE_END) pix = gtk.CellRendererPixbuf() - + column = gtk.TreeViewColumn() column.set_expand(True) column.set_alignment(0.0) column.pack_start(pix, False) column.pack_start(nick, True) - + #column.add_attribute(pix, 'pixbuf', 0) column.set_attributes(pix, pixbuf=0, visible=4) column.add_attribute(nick, 'markup', 2) - + exp_column = gtk.TreeViewColumn() - exp_column.set_max_width(16) - + exp_column.set_max_width(16) + self.append_column(exp_column) self.append_column(column) self.set_expander_column(exp_column) - + self.set_search_column(2) self.set_headers_visible(False) self.set_level_indentation(0) - - # the image (None for groups) the object (group or contact) and + + # the image (None for groups) the object (group or contact) and # the string to display self._model = gtk.TreeStore(gtk.gdk.Pixbuf, object, str, str, bool) self.model = self._model.filter_new(root=None) #self.model.set_visible_func(self._visible_func) - + self.set_model(self.model) self.connect("row-activated", self.__on_contact_dblclick) - + def __on_contact_dblclick(self, widget, path, column): model, row = widget.get_selection().get_selected() if (row is None): return False @@ -288,10 +288,10 @@ def __on_contact_dblclick(self, widget, path, column): contactview = model.get_value(row, 1) contactview.on_click(contactview.uid) - + def __search_by_id(self, id): parent = self._model.get_iter_first() - + while (parent is not None): obj = self._model.get_value(parent, 3) if (obj == id): return parent @@ -301,9 +301,9 @@ def __search_by_id(self, id): if (cobj == id): return child child = self._model.iter_next(child) parent = self._model.iter_next(parent) - + return None - + def show(self): pass @@ -313,64 +313,64 @@ def hide(self): def contactListUpdated(self, clview): guids = self.groups self.groups = [] - + # New groups for gid in clview.group_ids: if (gid == 0): gid = '0' if gid not in guids: self.groups.append(gid) self._model.append(None, [None, None, gid, gid, False]) - + # Remove unused groups for gid in guids: if gid not in self.groups: giter = self.__search_by_id(gid) self._model.remove(giter) self.groups.remove(gid) - + def groupUpdated(self, groupview): if (groupview.uid == 0): groupview.uid = '0' if groupview.uid not in self.groups: return - + giter = self.__search_by_id(groupview.uid) self._model.set_value(giter, 1, groupview) self._model.set_value(giter, 2, '%s' % common.escape_pango( groupview.name.toString())) - + try: cuids = self.contacts[groupview.uid] except: cuids = [] self.contacts[groupview.uid] = [] - + for cid in groupview.contact_ids: if cid not in cuids: giter = self.__search_by_id(groupview.uid) self.contacts[groupview.uid].append(cid) self._model.append(giter, [None, None, cid, cid, True]) - + # Remove unused contacts for cid in cuids: if cid not in self.contacts[groupview.uid]: citer = self.__search_by_id(cid) self._model.remove(citer) self.contacts[groupview.uid].remove(cid) - + def contactUpdated(self, contactview): citer = self.__search_by_id(contactview.uid) if citer is None: return - + # TODO: Verify if DP exist #img = Image(self._cwin._theme_manager, contactview.dp) #dp = img.to_pixbuf(28, 28) img = Image(self._cwin._theme_manager, contactview.icon) dp = img.to_pixbuf(28, 28) - + self._model.set_value(citer, 0, dp) self._model.set_value(citer, 1, contactview) self._model.set_value(citer, 2, common.escape_pango( contactview.name.toString())) del dp gc.collect() - + diff --git a/amsn2/gui/front_ends/gtk/gtk_extras.py b/amsn2/gui/front_ends/gtk/gtk_extras.py index 7fee5f7d..ff88dae0 100644 --- a/amsn2/gui/front_ends/gtk/gtk_extras.py +++ b/amsn2/gui/front_ends/gtk/gtk_extras.py @@ -5,217 +5,217 @@ import gtk import gobject -# This not ideal. It would be better to subclass gtk.ToolButton, however -# the python bindings do not seem to be powerfull enough for that. -# (As we need to change a variable in the class structure.) +# This not ideal. It would be better to subclass gtk.ToolButton, however +# the python bindings do not seem to be powerfull enough for that. +# (As we need to change a variable in the class structure.) class ColorToolButton(gtk.ToolItem): - __gtype_name__ = 'ColorToolButton' - __gsignals__ = { 'color-set' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, tuple())} + __gtype_name__ = 'ColorToolButton' + __gsignals__ = { 'color-set' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, tuple())} def __init__(self, icon_name='color-preview', **kwargs): - self._accelerator = None - self._tooltip = None - #self._palette_invoker = ToolInvoker() - self._palette = None - gobject.GObject.__init__(self, **kwargs) - # The gtk.ToolButton has already added a normal button. - # Replace it with a ColorButton - color_button = gtk.ColorButton() - self.add(color_button) + self._accelerator = None + self._tooltip = None + #self._palette_invoker = ToolInvoker() + self._palette = None + gobject.GObject.__init__(self, **kwargs) + # The gtk.ToolButton has already added a normal button. + # Replace it with a ColorButton + color_button = gtk.ColorButton() + self.add(color_button) # The following is so that the behaviour on the toolbar is correct. - color_button.set_relief(gtk.RELIEF_NONE) - color_button.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR - #self._palette_invoker.attach_tool(self) - # This widget just proxies the following properties to the colorbutton - color_button.connect('notify::color', self.__notify_change) - color_button.connect('notify::icon-name', self.__notify_change) - color_button.connect('notify::icon-size', self.__notify_change) - color_button.connect('notify::title', self.__notify_change) - color_button.connect('color-set', self.__color_set_cb) + color_button.set_relief(gtk.RELIEF_NONE) + color_button.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR + #self._palette_invoker.attach_tool(self) + # This widget just proxies the following properties to the colorbutton + color_button.connect('notify::color', self.__notify_change) + color_button.connect('notify::icon-name', self.__notify_change) + color_button.connect('notify::icon-size', self.__notify_change) + color_button.connect('notify::title', self.__notify_change) + color_button.connect('color-set', self.__color_set_cb) color_button.connect('can-activate-accel', self.__button_can_activate_accel_cb) def __button_can_activate_accel_cb(self, button, signal_id): - # Accept activation via accelerators regardless of this widget's state + # Accept activation via accelerators regardless of this widget's state return True def set_accelerator(self, accelerator): - self._accelerator = accelerator + self._accelerator = accelerator setup_accelerator(self) def get_accelerator(self): return self._accelerator - - accelerator = gobject.property(type=str, setter=set_accelerator, getter=get_accelerator) - + + accelerator = gobject.property(type=str, setter=set_accelerator, getter=get_accelerator) + def create_palette(self): - self._palette = self.get_child().create_palette() + self._palette = self.get_child().create_palette() return self._palette #def get_palette_invoker(self): # return self._palette_invoker #def set_palette_invoker(self, palette_invoker): - # self._palette_invoker.detach() + # self._palette_invoker.detach() # self._palette_invoker = palette_invoker - #palette_invoker = gobject.property( type=object, setter=set_palette_invoker, getter=get_palette_invoker) - + #palette_invoker = gobject.property( type=object, setter=set_palette_invoker, getter=get_palette_invoker) + def set_color(self, color): self.get_child().props.color = color def get_color(self): return self.get_child().props.color - - color = gobject.property(type=object, getter=get_color, setter=set_color) - + + color = gobject.property(type=object, getter=get_color, setter=set_color) + def set_icon_name(self, icon_name): self.get_child().props.icon_name = icon_name def get_icon_name(self): return self.get_child().props.icon_name - - icon_name = gobject.property(type=str, getter=get_icon_name, setter=set_icon_name) - + + icon_name = gobject.property(type=str, getter=get_icon_name, setter=set_icon_name) + def set_icon_size(self, icon_size): self.get_child().props.icon_size = icon_size def get_icon_size(self): return self.get_child().props.icon_size - - icon_size = gobject.property(type=int, getter=get_icon_size, setter=set_icon_size) - + + icon_size = gobject.property(type=int, getter=get_icon_size, setter=set_icon_size) + def set_title(self, title): self.get_child().props.title = title def get_title(self): return self.get_child().props.title - - title = gobject.property(type=str, getter=get_title, setter=set_title) - + + title = gobject.property(type=str, getter=get_title, setter=set_title) + def do_expose_event(self, event): - child = self.get_child() - allocation = self.get_allocation() - if self._palette and self._palette.is_up(): - invoker = self._palette.props.invoker - invoker.draw_rectangle(event, self._palette) - elif child.state == gtk.STATE_PRELIGHT: - child.style.paint_box(event.window, gtk.STATE_PRELIGHT, gtk.SHADOW_NONE, event.area, child, 'toolbutton-prelight', allocation.x, allocation.y, allocation.width, allocation.height) - + child = self.get_child() + allocation = self.get_allocation() + if self._palette and self._palette.is_up(): + invoker = self._palette.props.invoker + invoker.draw_rectangle(event, self._palette) + elif child.state == gtk.STATE_PRELIGHT: + child.style.paint_box(event.window, gtk.STATE_PRELIGHT, gtk.SHADOW_NONE, event.area, child, 'toolbutton-prelight', allocation.x, allocation.y, allocation.width, allocation.height) + gtk.ToolButton.do_expose_event(self, event) def __notify_change(self, widget, pspec): self.notify(pspec.name) def __color_set_cb(self, widget): - self.emit('color-set') + self.emit('color-set') class FontToolButton(gtk.ToolItem): - __gtype_name__ = 'FontToolButton' - __gsignals__ = { 'font-set' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, tuple())} - + __gtype_name__ = 'FontToolButton' + __gsignals__ = { 'font-set' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, tuple())} + def __init__(self, icon_name='font-preview', **kwargs): - self._accelerator = None - self._tooltip = None - #self._palette_invoker = ToolInvoker() - self._palette = None - gobject.GObject.__init__(self, **kwargs) - # The gtk.ToolButton has already added a normal button. - # Replace it with a ColorButton - font_button = gtk.FontButton() - self.add(font_button) + self._accelerator = None + self._tooltip = None + #self._palette_invoker = ToolInvoker() + self._palette = None + gobject.GObject.__init__(self, **kwargs) + # The gtk.ToolButton has already added a normal button. + # Replace it with a ColorButton + font_button = gtk.FontButton() + self.add(font_button) # The following is so that the behaviour on the toolbar is correct. - font_button.set_relief(gtk.RELIEF_NONE) - font_button.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR - #self._palette_invoker.attach_tool(self) - # This widget just proxies the following properties to the colorbutton + font_button.set_relief(gtk.RELIEF_NONE) + font_button.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR + #self._palette_invoker.attach_tool(self) + # This widget just proxies the following properties to the colorbutton font_button.connect('notify::font-name', self.__notify_change) - font_button.connect('notify::show-style', self.__notify_change) - font_button.connect('notify::show-size', self.__notify_change) - font_button.connect('notify::icon-name', self.__notify_change) - font_button.connect('notify::icon-size', self.__notify_change) - font_button.connect('notify::title', self.__notify_change) - font_button.connect('font-set', self.__font_set_cb) + font_button.connect('notify::show-style', self.__notify_change) + font_button.connect('notify::show-size', self.__notify_change) + font_button.connect('notify::icon-name', self.__notify_change) + font_button.connect('notify::icon-size', self.__notify_change) + font_button.connect('notify::title', self.__notify_change) + font_button.connect('font-set', self.__font_set_cb) font_button.connect('can-activate-accel', self.__button_can_activate_accel_cb) def __button_can_activate_accel_cb(self, button, signal_id): - # Accept activation via accelerators regardless of this widget's state + # Accept activation via accelerators regardless of this widget's state return True def set_accelerator(self, accelerator): - self._accelerator = accelerator + self._accelerator = accelerator setup_accelerator(self) def get_accelerator(self): return self._accelerator - - accelerator = gobject.property(type=str, setter=set_accelerator, getter=get_accelerator) - + + accelerator = gobject.property(type=str, setter=set_accelerator, getter=get_accelerator) + def create_palette(self): - self._palette = self.get_child().create_palette() + self._palette = self.get_child().create_palette() return self._palette - + def set_font_name(self, font_name): self.get_child().props.font_name = font_name def get_font_name(self): return self.get_child().props.font_name - - font_name = gobject.property(type=object, getter=get_font_name, setter=set_font_name) - + + font_name = gobject.property(type=object, getter=get_font_name, setter=set_font_name) + def set_show_size(self, show_size): self.get_child().props.show_size = show_size def get_show_size(self): return self.get_child().props.show_size - - show_size = gobject.property(type=object, getter=get_show_size, setter=set_show_size) - + + show_size = gobject.property(type=object, getter=get_show_size, setter=set_show_size) + def set_show_style(self, show_style): self.get_child().props.show_style = show_style def get_show_style(self): return self.get_child().props.show_style - - show_style = gobject.property(type=object, getter=get_show_style, setter=set_show_style) - - + + show_style = gobject.property(type=object, getter=get_show_style, setter=set_show_style) + + def set_icon_name(self, icon_name): self.get_child().props.icon_name = icon_name def get_icon_name(self): return self.get_child().props.icon_name - - icon_name = gobject.property(type=str, getter=get_icon_name, setter=set_icon_name) - + + icon_name = gobject.property(type=str, getter=get_icon_name, setter=set_icon_name) + def set_icon_size(self, icon_size): self.get_child().props.icon_size = icon_size def get_icon_size(self): return self.get_child().props.icon_size - - icon_size = gobject.property(type=int, getter=get_icon_size, setter=set_icon_size) - + + icon_size = gobject.property(type=int, getter=get_icon_size, setter=set_icon_size) + def set_title(self, title): self.get_child().props.title = title def get_title(self): return self.get_child().props.title - - title = gobject.property(type=str, getter=get_title, setter=set_title) - + + title = gobject.property(type=str, getter=get_title, setter=set_title) + def do_expose_event(self, event): - child = self.get_child() - allocation = self.get_allocation() - if self._palette and self._palette.is_up(): - invoker = self._palette.props.invoker - invoker.draw_rectangle(event, self._palette) - elif child.state == gtk.STATE_PRELIGHT: - child.style.paint_box(event.window, gtk.STATE_PRELIGHT, gtk.SHADOW_NONE, event.area, child, 'toolbutton-prelight', allocation.x, allocation.y, allocation.width, allocation.height) - + child = self.get_child() + allocation = self.get_allocation() + if self._palette and self._palette.is_up(): + invoker = self._palette.props.invoker + invoker.draw_rectangle(event, self._palette) + elif child.state == gtk.STATE_PRELIGHT: + child.style.paint_box(event.window, gtk.STATE_PRELIGHT, gtk.SHADOW_NONE, event.area, child, 'toolbutton-prelight', allocation.x, allocation.y, allocation.width, allocation.height) + gtk.ToolButton.do_expose_event(self, event) def __notify_change(self, widget, pspec): self.notify(pspec.name) def __font_set_cb(self, widget): - self.emit('font-set') - + self.emit('font-set') + diff --git a/amsn2/gui/front_ends/gtk/htmltextview.py b/amsn2/gui/front_ends/gtk/htmltextview.py index 31947cf8..f4396df4 100755 --- a/amsn2/gui/front_ends/gtk/htmltextview.py +++ b/amsn2/gui/front_ends/gtk/htmltextview.py @@ -43,10 +43,10 @@ def _parse_css_color(color): return gtk.gdk.Color(r, g, b) else: return gtk.gdk.color_parse(color) - + class HtmlHandler(xml.sax.handler.ContentHandler): - + def __init__(self, textview, startiter): xml.sax.handler.ContentHandler.__init__(self) self.textbuf = textview.get_buffer() @@ -76,9 +76,9 @@ def _get_current_attributes(self): self.iter.get_attributes(attrs) self.iter.forward_char() return attrs - + else: - + ## Workaround http://bugzilla.gnome.org/show_bug.cgi?id=317455 def _get_current_style_attr(self, propname, comb_oper=None): tags = [tag for tag in self.styles if tag is not None] @@ -155,7 +155,7 @@ def _parse_length(self, value, font_relative, callback, *args): else: warnings.warn("Unable to parse length value '%s'" % value) - + def __parse_font_size_cb(length, tag): tag.set_property("size-points", length/display_resolution) __parse_font_size_cb = staticmethod(__parse_font_size_cb) @@ -200,7 +200,7 @@ def _parse_style_font_style(self, tag, value): def __frac_length_tag_cb(length, tag, propname): tag.set_property(propname, length) __frac_length_tag_cb = staticmethod(__frac_length_tag_cb) - + def _parse_style_margin_left(self, tag, value): self._parse_length(value, False, self.__frac_length_tag_cb, tag, "left-margin") @@ -245,7 +245,7 @@ def _parse_style_text_align(self, tag, value): warnings.warn("Invalid text-align:%s requested" % value) else: tag.set_property("justification", align) - + def _parse_style_text_decoration(self, tag, value): if value == "none": tag.set_property("underline", pango.UNDERLINE_NONE) @@ -268,7 +268,7 @@ def _parse_style_text_decoration(self, tag, value): warnings.warn("text-decoration:blink not implemented") else: warnings.warn("text-decoration:%s not implemented" % value) - + ## build a dictionary mapping styles to methods, for greater speed __style_methods = dict() @@ -315,7 +315,7 @@ def _insert_text(self, text): self.textbuf.insert_with_tags(self.iter, text, *tags) else: self.textbuf.insert(self.iter, text) - + def _flush_text(self): if not self.text: return self._insert_text(self.text.replace('\n', '')) @@ -326,7 +326,7 @@ def _anchor_event(self, tag, textview, event, iter, href, type_): self.textview.emit("url-clicked", href, type_) return True return False - + def characters(self, content): if allwhitespace_rx.match(content) is not None: return @@ -351,7 +351,7 @@ def startElement(self, name, attrs): type_ = None tag.connect('event', self._anchor_event, attrs['href'], type_) tag.is_anchor = True - + self._begin_span(style, tag) if name == 'br': @@ -451,7 +451,7 @@ class HtmlTextView(gtk.TextView): __gsignals__ = { 'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type } - + def __init__(self): gtk.TextView.__init__(self) self.set_wrap_mode(gtk.WRAP_CHAR) @@ -472,7 +472,7 @@ def __leave_event(self, widget, event): window = widget.get_window(gtk.TEXT_WINDOW_TEXT) window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) self._changed_cursor = False - + def __motion_notify_event(self, widget, event): x, y, _ = widget.window.get_pointer() x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) @@ -501,10 +501,10 @@ def display_html(self, html): # parser.setFeature(xml.sax.handler.feature_validation, True) parser.setContentHandler(HtmlHandler(self, eob)) parser.parse(StringIO(html)) - + if not eob.starts_line(): buffer.insert(eob, "\n") - + def scroll_to_bottom(self): textbuffer = self.get_buffer() textiter = textbuffer.get_end_iter() @@ -518,7 +518,7 @@ class MessageTextView(gtk.TextView): for chat/groupchat windows''' __gtype_name__ = 'MessageTextView' __gsignals__ = dict(mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (int, gtk.gdk.ModifierType ))) - + def __init__(self): gtk.TextView.__init__(self) diff --git a/amsn2/gui/front_ends/gtk/image.py b/amsn2/gui/front_ends/gtk/image.py index 3968a8e3..553c5579 100644 --- a/amsn2/gui/front_ends/gtk/image.py +++ b/amsn2/gui/front_ends/gtk/image.py @@ -1,21 +1,21 @@ # -*- coding: utf-8 -*- #=================================================== -# +# # image.py - This file is part of the amsn2 package # # Copyright (C) 2008 Wil Alvarez # # This script is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software +# the terms of the GNU General Public License as published by the Free Software # Foundation; either version 3 of the License, or (at your option) any later # version. # -# This script is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# This script is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # -# You should have received a copy of the GNU General Public License along with +# You should have received a copy of the GNU General Public License along with # this script (see COPYING); if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # @@ -24,7 +24,7 @@ import gtk from amsn2.gui import base from amsn2.core.views import imageview - + class Image(gtk.Image, base.aMSNImage): def __init__(self, theme_manager, view): gtk.Image.__init__(self) @@ -40,7 +40,7 @@ def _loadFromFilename(self, filename, view, index): except Exception, e: print e print "Error loading image %s" % filename - + def to_pixbuf(self, width, height): #print 'image.py -> to_pixbuf: filename=%s' % self._filename try: @@ -50,4 +50,4 @@ def to_pixbuf(self, width, height): except: print 'Error converting to pixbuf image %s' % self._filename return None - \ No newline at end of file + diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 340e3a1d..be55761f 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -98,8 +98,8 @@ def __init__(self, amsn_core, parent): icon = gtk.gdk.pixbuf_new_from_file(path) name = string.capitalize(name) status_list.append([icon, name, key]) - - + + iconCell = gtk.CellRendererPixbuf() iconCell.set_property('xalign', 0.0) txtCell = gtk.CellRendererText() @@ -159,9 +159,9 @@ def __init__(self, amsn_core, parent): self.show_all() self._main_win.set_view(self) - self.user.grab_focus() + self.user.grab_focus() #self.switch_to_profile(None) - + def __animation(self): path = os.path.join("amsn2", "themes", "default", "images", "login_screen", "cube") diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index a15b6267..1fc74261 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -7,7 +7,7 @@ class aMSNMainWindow(base.aMSNMainWindow): main_win = None - + def __init__(self, amsn_core): self._amsn_core = amsn_core self.main_win = gtk.Window() @@ -18,23 +18,23 @@ def __init__(self, amsn_core): inner.pack_start(self.main_menu, False, False) self.main_win.add(inner) self.view = None - + def __on_show(self): self._amsn_core.mainWindowShown() - + def __on_close(self, widget, event): self._amsn_core.quit() def show(self): self.main_win.show() self._amsn_core.idlerAdd(self.__on_show) - + def setTitle(self, title): self.main_win.set_title(title) - + def hide(self): self.main_win.hide() - + def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView @@ -45,7 +45,7 @@ def setMenu(self, menu): self.main_menu.remove(chl) self._createMenuItemsFromView(self.main_menu, menu.items) self.main_menu.show() - + def _createMenuItemsFromView(self, menu, items): # TODO: images & radio groups, for now only basic representation for item in items: @@ -83,7 +83,7 @@ def set_view(self, view): for c in chldn: if isinstance(c, base.aMSNLoginWindow) or isinstance(c, base.aMSNContactListWindow): inner.remove(c) - + inner.pack_start(view) self.main_win.show_all() - + diff --git a/amsn2/gui/front_ends/gtk/main_loop.py b/amsn2/gui/front_ends/gtk/main_loop.py index 778fcad8..6c1bbede 100644 --- a/amsn2/gui/front_ends/gtk/main_loop.py +++ b/amsn2/gui/front_ends/gtk/main_loop.py @@ -5,7 +5,7 @@ class aMSNMainLoop(base.aMSNMainLoop): def __init__(self, amsn_core): self._amsn_core = amsn_core - + def run(self): self._mainloop = gobject.MainLoop(is_running=True) @@ -15,7 +15,7 @@ def run(self): except KeyboardInterrupt: self.quit() - + def idlerAdd(self, func): gobject.idle_add(func) @@ -24,4 +24,4 @@ def timerAdd(self, delay, func): def quit(self): self._mainloop.quit() - + diff --git a/amsn2/gui/front_ends/gtk/skins.py b/amsn2/gui/front_ends/gtk/skins.py index 4224656c..6b7b7568 100644 --- a/amsn2/gui/front_ends/gtk/skins.py +++ b/amsn2/gui/front_ends/gtk/skins.py @@ -1,21 +1,21 @@ # -*- coding: utf-8 -*- #=================================================== -# +# # contact_list.py - This file is part of the amsn2 package # # Copyright (C) 2008 Wil Alvarez # # This script is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software +# the terms of the GNU General Public License as published by the Free Software # Foundation; either version 3 of the License, or (at your option) any later # version. # -# This script is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# This script is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # -# You should have received a copy of the GNU General Public License along with +# You should have received a copy of the GNU General Public License along with # this script (see COPYING); if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # @@ -29,41 +29,41 @@ def __init__(self, core, path): self._dict = {} #TODO : remove, it's just here for test purpose #TODO : explain a bit :D - self.setKey("buddy_online", ("Filename", os.path.join("amsn2", + self.setKey("buddy_online", ("Filename", os.path.join("amsn2", "themes", "default", "images", "online.png"))) #self.setKey("emblem_online", ("Filename", "amsn2/themes/default/images/contact_list/plain_emblem.png")) - self.setKey("buddy_away", ("Filename", os.path.join("amsn2", + self.setKey("buddy_away", ("Filename", os.path.join("amsn2", "themes", "default", "images", "away.png"))) - + #self.setKey("emblem_away", ("Filename", "amsn2/themes/default/images/contact_list/away_emblem.png")) - self.setKey("buddy_brb", ("Filename", os.path.join("amsn2", + self.setKey("buddy_brb", ("Filename", os.path.join("amsn2", "themes", "default", "images", "away.png"))) #self.setKey("emblem_brb", ("Filename", "amsn2/themes/default/images/contact_list/away_emblem.png")) - self.setKey("buddy_idle", ("Filename", os.path.join("amsn2", + self.setKey("buddy_idle", ("Filename", os.path.join("amsn2", "themes", "default", "images", "away.png"))) #self.setKey("emblem_idle", ("Filename", "amsn2/themes/default/images/contact_list/away_emblem.png")) - self.setKey("buddy_lunch", ("Filename", os.path.join("amsn2", + self.setKey("buddy_lunch", ("Filename", os.path.join("amsn2", "themes", "default", "images", "away.png"))) #self.setKey("emblem_lunch", ("Filename", "amsn2/themes/default/images/contact_list/away_emblem.png")) # Just to show you can use an image from the edj file - self.setKey("buddy_busy", ("Filename", os.path.join("amsn2", + self.setKey("buddy_busy", ("Filename", os.path.join("amsn2", "themes", "default", "images","busy.png"))) #self.setKey("emblem_busy", ("Filename", "amsn2/themes/default/images/contact_list/busy_emblem.png")) - self.setKey("buddy_phone", ("Filename", os.path.join("amsn2", + self.setKey("buddy_phone", ("Filename", os.path.join("amsn2", "themes", "default", "images", "busy.png"))) #self.setKey("emblem_phone", ("Filename", "amsn2/themes/default/images/contact_list/busy_emblem.png")) - self.setKey("buddy_offline", ("Filename", os.path.join("amsn2", + self.setKey("buddy_offline", ("Filename", os.path.join("amsn2", "themes", "default", "images", "offline.png"))) - + #self.setKey("emblem_offline", ("Filename", "amsn2/themes/default/images/contact_list/offline_emblem.png")) - self.setKey("buddy_hidden", ("Filename", os.path.join("amsn2", + self.setKey("buddy_hidden", ("Filename", os.path.join("amsn2", "themes", "default", "images", "offline.png"))) #self.setKey("emblem_hidden", ("Filename", "amsn2/themes/default/images/contact_list/offline_emblem.png")) - self.setKey("default_dp", ("Filename", os.path.join("amsn2", "themes", + self.setKey("default_dp", ("Filename", os.path.join("amsn2", "themes", "default", "images", "contact_list", "nopic.png"))) def getKey(self, key, default=None): diff --git a/amsn2/gui/front_ends/gtk/splash.py b/amsn2/gui/front_ends/gtk/splash.py index 569f9052..190a7506 100644 --- a/amsn2/gui/front_ends/gtk/splash.py +++ b/amsn2/gui/front_ends/gtk/splash.py @@ -7,12 +7,12 @@ def __init__(self, amsn_core, parent): def show(self): pass - + def hide(self): pass - + def setText(self, text): pass - + def setImage(self, image): pass diff --git a/amsn2/gui/front_ends/mine/__init__.py b/amsn2/gui/front_ends/mine/__init__.py index 9e585598..b7af9103 100644 --- a/amsn2/gui/front_ends/mine/__init__.py +++ b/amsn2/gui/front_ends/mine/__init__.py @@ -15,7 +15,7 @@ def load(): try: import imp gui.GUIManager.registerFrontEnd("mine", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/mine/contact_list.py b/amsn2/gui/front_ends/mine/contact_list.py index 0b7fe2a5..f7367a48 100644 --- a/amsn2/gui/front_ends/mine/contact_list.py +++ b/amsn2/gui/front_ends/mine/contact_list.py @@ -9,7 +9,7 @@ class aMSNContactListWindow(object): """ This interface represents the main Contact List Window - self._clwiget is an aMSNContactListWidget + self._clwiget is an aMSNContactListWidget """ def __init__(self, amsn_core, parent): diff --git a/amsn2/gui/front_ends/mine/login.py b/amsn2/gui/front_ends/mine/login.py index d075efa8..ce7fc761 100644 --- a/amsn2/gui/front_ends/mine/login.py +++ b/amsn2/gui/front_ends/mine/login.py @@ -3,7 +3,7 @@ class aMSNLoginWindow(object): def __init__(self, amsn_core, main): self._amsn_core = amsn_core self.switch_to_profile(None) - + def show(self): if self._username is not None and self._username != "": print "Account : %s" % (self._username) @@ -32,7 +32,7 @@ def signin(self): self.current_profile.email = self._username self.current_profile.password = self._password self._amsn_core.signinToAccount(self, self.current_profile) - + def onConnecting(self,mess): print mess diff --git a/amsn2/gui/front_ends/mine/main.py b/amsn2/gui/front_ends/mine/main.py index 27014cc4..7fa50044 100644 --- a/amsn2/gui/front_ends/mine/main.py +++ b/amsn2/gui/front_ends/mine/main.py @@ -10,7 +10,7 @@ def show(self): def hide(self): pass - + def setTitle(self,title): pass @@ -19,4 +19,4 @@ def setMenu(self,menu): def __on_show(self): self._amsn_core.mainWindowShown() - + diff --git a/amsn2/gui/front_ends/mine/main_loop.py b/amsn2/gui/front_ends/mine/main_loop.py index 778fcad8..6c1bbede 100644 --- a/amsn2/gui/front_ends/mine/main_loop.py +++ b/amsn2/gui/front_ends/mine/main_loop.py @@ -5,7 +5,7 @@ class aMSNMainLoop(base.aMSNMainLoop): def __init__(self, amsn_core): self._amsn_core = amsn_core - + def run(self): self._mainloop = gobject.MainLoop(is_running=True) @@ -15,7 +15,7 @@ def run(self): except KeyboardInterrupt: self.quit() - + def idlerAdd(self, func): gobject.idle_add(func) @@ -24,4 +24,4 @@ def timerAdd(self, delay, func): def quit(self): self._mainloop.quit() - + diff --git a/amsn2/gui/front_ends/qt4/__init__.py b/amsn2/gui/front_ends/qt4/__init__.py index c97afd59..e1f6e045 100644 --- a/amsn2/gui/front_ends/qt4/__init__.py +++ b/amsn2/gui/front_ends/qt4/__init__.py @@ -36,7 +36,7 @@ def load(): try: import imp imp.find_module("PyQt4") - + gui.GUIManager.registerFrontEnd("qt4", sys.modules[__name__]) except ImportError: pass diff --git a/amsn2/gui/front_ends/qt4/chat_window.py b/amsn2/gui/front_ends/qt4/chat_window.py index 67ac1587..521a0c82 100644 --- a/amsn2/gui/front_ends/qt4/chat_window.py +++ b/amsn2/gui/front_ends/qt4/chat_window.py @@ -30,7 +30,7 @@ class InputWidget(QTextEdit): def __init__(self, Parent = None): QTextEdit.__init__(self, Parent) self.setTextInteractionFlags(Qt.TextEditorInteraction) - + def keyPressEvent(self, event): print "key pressed:" + str(event.key()) if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return: @@ -38,21 +38,21 @@ def keyPressEvent(self, event): self.emit(SIGNAL("enterKeyTriggered()")) else: QTextEdit.keyPressEvent(self, event) - + class aMSNChatWindow(QTabWidget, base.aMSNChatWindow): def __init__(self, amsn_core, Parent=None): QTabWidget.__init__(self, Parent) - + self._core = amsn_core - + def addChatWidget(self, chat_widget): self.addTab(chat_widget, "test") - - + + class aMSNChatWidget(QWidget, base.aMSNChatWidget): def __init__(self, amsn_conversation, Parent=None): QWidget.__init__(self, Parent) - + self._amsn_conversation = amsn_conversation self.ui = Ui_ChatWindow() self.ui.setupUi(self) @@ -60,83 +60,83 @@ def __init__(self, amsn_conversation, Parent=None): self.ui.inputLayout.addWidget(self.ui.inputWidget) self._statusBar = QStatusBar(self) self.layout().addWidget(self._statusBar) - + self.loadEmoticonList() - + QObject.connect(self.ui.inputWidget, SIGNAL("textChanged()"), self.processInput) QObject.connect(self.ui.inputWidget, SIGNAL("enterKeyTriggered()"), self.__sendMessage) QObject.connect(self.ui.actionInsert_Emoticon, SIGNAL("triggered()"), self.showEmoticonList) self.enterShortcut = QShortcut(QKeySequence("Enter"), self.ui.inputWidget) self.nudgeShortcut = QShortcut(QKeySequence("Ctrl+G"), self) QObject.connect(self.enterShortcut, SIGNAL("activated()"), self.__sendMessage) - QObject.connect(self.nudgeShortcut, SIGNAL("activated()"), self.__sendNudge) + QObject.connect(self.nudgeShortcut, SIGNAL("activated()"), self.__sendNudge) QObject.connect(self.ui.actionNudge, SIGNAL("triggered()"), self.__sendNudge) - + def processInput(self): """ Here we process what is inside the widget... so showing emoticon and similar stuff""" - + QObject.disconnect(self.ui.inputWidget, SIGNAL("textChanged()"), self.processInput) - + self.text = QString(self.ui.inputWidget.toHtml()) - + for emoticon in self.emoticonList: if self.text.contains(emoticon) == True: print emoticon self.text.replace(emoticon, "") - + self.ui.inputWidget.setHtml(self.text) self.ui.inputWidget.moveCursor(QTextCursor.End) self.__typingNotification() - + QObject.connect(self.ui.inputWidget, SIGNAL("textChanged()"), self.processInput) - + def loadEmoticonList(self): self.emoticonList = QStringList() - + """ TODO: Request emoticon list from amsn core, maybe use a QMap to get the image URL? """ - + """ TODO: Discuss how to handle custom emoticons. We have to provide an option to change the default icon theme, this includes standard emoticons too. Maybe qrc? """ - + #self.emoticonList << ";)" << ":)" << "EmOtIcOn" - #We want :) and ;) to work for now :p - self.emoticonList << "EmOtIcOn" - + #We want :) and ;) to work for now :p + self.emoticonList << "EmOtIcOn" + def showEmoticonList(self): """ Let's popup emoticon selection here """ print "Guess what? No emoticons. But I'll put in a random one for you" self.appendImageAtCursor("throbber.gif") - + def __sendMessage(self): # TODO: Switch to this when implemented """ msg = self.ui.inputWidget.toHtml() self.ui.inputWidget.clear() strv = StringView() strv.appendElementsFromHtml(msg) """ - + msg = self.ui.inputWidget.toPlainText() self.ui.inputWidget.clear() strv = StringView() strv.appendText(unicode(msg)) self._amsn_conversation.sendMessage(strv) self.ui.textEdit.append("/me says:
"+unicode(msg)+"") - + def __sendNudge(self): self._amsn_conversation.sendNudge() self.ui.textEdit.append("/me sent a nudge") - + def __typingNotification(self): self._amsn_conversation.sendTypingNotification() - + def appendTextAtCursor(self, text): self.ui.inputWidget.textCursor().insertHtml(unicode(text)) - + def appendImageAtCursor(self, image): self.ui.inputWidget.textCursor().insertHtml(QString("")) - + def onUserJoined(self, contact): self.ui.textEdit.append(unicode(""+QString.fromUtf8(contact.name.toString())+" "+self.tr("has joined the conversation")+(""))) pass @@ -157,5 +157,5 @@ def onMessageReceived(self, sender, message): def onNudgeReceived(self, sender): self.ui.textEdit.append(unicode(""+sender.name.toString()+" "+self.tr("sent you a nudge!")+(""))) pass - - + + diff --git a/amsn2/gui/front_ends/qt4/contact_item.py b/amsn2/gui/front_ends/qt4/contact_item.py index 8c4c9a81..799fb4d9 100644 --- a/amsn2/gui/front_ends/qt4/contact_item.py +++ b/amsn2/gui/front_ends/qt4/contact_item.py @@ -24,6 +24,6 @@ class ContactItem(QStandardItem): def __init__(self): QStandardItem.__init__(self) - + def setContactName(self, name): - self.setText(name) \ No newline at end of file + self.setText(name) diff --git a/amsn2/gui/front_ends/qt4/contact_list.py b/amsn2/gui/front_ends/qt4/contact_list.py index 262a5f88..ada09631 100644 --- a/amsn2/gui/front_ends/qt4/contact_list.py +++ b/amsn2/gui/front_ends/qt4/contact_list.py @@ -38,7 +38,7 @@ def __init__(self, amsn_core, parent): def show(self): self._clwidget.show() - + def hide(self): self._clwidget.hide() @@ -53,8 +53,8 @@ def topCLUpdated(self, contactView): def myInfoUpdated(self, view): pass #TODO - - + + class aMSNContactListWidget(StyledWidget, base.aMSNContactListWidget): def __init__(self, amsn_core, parent): base.aMSNContactListWidget.__init__(self, amsn_core, parent) @@ -69,10 +69,10 @@ def __init__(self, amsn_core, parent): self._proxyModel.setSourceModel(self._model) self.ui.cList.setModel(self._proxyModel) self._contactDict = dict() - + self._proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self._proxyModel.setFilterKeyColumn(-1) - + self.connect(self.ui.searchLine, SIGNAL('textChanged(QString)'), self._proxyModel, SLOT('setFilterFixedString(QString)')) QObject.connect(self.ui.nickName, SIGNAL('textChanged(QString)'), @@ -83,19 +83,19 @@ def show(self): def hide(self): pass - + def __slotChangeNick(self): sv = StringView() sv.appendText(str(self.ui.nickName.text())) self._amsn_core._profile.client.changeNick(sv) - + def contactListUpdated(self, view): pass - + def contactUpdated(self, contact): print unicode("Contact Updated: " + QString.fromUtf8(contact.name.toString())) l = self._model.findItems("*", Qt.MatchWildcard | Qt.MatchRecursive) - + for itm in l: if itm.data(40).toString() == contact.uid: itm.setText(QString.fromUtf8(contact.name.toString())) @@ -104,34 +104,34 @@ def contactUpdated(self, contact): def groupUpdated(self, group): print "GroupUpdated" l = self._model.findItems("*", Qt.MatchWildcard) - + for itm in l: - + if itm.data(40).toString() == group.uid: - + itm.setText(QString.fromUtf8(group.name.toString())) - + for contact in group.contacts: - + for ent in l: - + if ent.data(40).toString() == contact.uid: itm.setText(QString.fromUtf8(contact.name.toString())) continue - + print " * " + contact.name.toString() - + contactItem = ContactItem() contactItem.setContactName(QString.fromUtf8(contact.name.toString())) contactItem.setData(QVariant(contact.uid), 40) - + itm.appendRow(contactItem) break def groupRemoved(self, group): l = self._model.findItems("", Qt.MatchWildcard) - + for itm in l: if itm.data(40) == group.uid: row = self._model.indexFromItem(itm) @@ -164,23 +164,23 @@ def setContactContextMenu(self, cb): def groupAdded(self, group): print group.name.toString() - + pi = self._model.invisibleRootItem(); - + # Adding Group Item - + groupItem = QStandardItem() groupItem.setText(QString.fromUtf8(group.name.toString())) groupItem.setData(QVariant(group.uid), 40) pi.appendRow(groupItem) - + for contact in group.contacts: print " * " + contact.name.toString() - + contactItem = ContactItem() contactItem.setContactName(QString.fromUtf8(contact.name.toString())) contactItem.setData(QVariant(contact.uid), 40) - + groupItem.appendRow(contactItem) - + self._contactDict[contact.uid] = contact diff --git a/amsn2/gui/front_ends/qt4/contact_model.py b/amsn2/gui/front_ends/qt4/contact_model.py index 56350924..90b99dc3 100644 --- a/amsn2/gui/front_ends/qt4/contact_model.py +++ b/amsn2/gui/front_ends/qt4/contact_model.py @@ -24,6 +24,6 @@ class ContactModel(QStandardItemModel): def __init__(self, parent): QStandardItemModel.__init__(self, parent) - + def test(self): - self.test = "test" + self.test = "test" diff --git a/amsn2/gui/front_ends/qt4/image.py b/amsn2/gui/front_ends/qt4/image.py index 18448c9a..4e2c6336 100644 --- a/amsn2/gui/front_ends/qt4/image.py +++ b/amsn2/gui/front_ends/qt4/image.py @@ -23,7 +23,7 @@ from PyQt4.QtCore import * from PyQt4.QtGui import * from amsn2.core.views import imageview - + class Image(QPixmap): def __init__(self): QPixmap.__init__(self) @@ -37,7 +37,7 @@ def load(self, resource_name, value): """ if resource_name == "File": self._loadFromFilename(value) - + def loadFromImageView(self, view): for (resource_type, value) in view.imgs: try: @@ -46,7 +46,7 @@ def loadFromImageView(self, view): print "From append in qt4/image.py:\n\t(resource_type, value) = (%s, %s)\n\tAttributeError: %s" % (resource_type, value, e) else: loadMethod(value) - + def getAsFilename(self): return self._fileName @@ -63,15 +63,15 @@ def prepend(self, resource_name, value): """ if resource_name == "File": self.load(value) - + def _loadFromFilename(self, filename): QPixmap.load(self, filename) self._fileName = filename - + def _loadFromSkin(self, skin): pass - + def _loadFromFileObject(self, obj): pass - - + + diff --git a/amsn2/gui/front_ends/qt4/main.py b/amsn2/gui/front_ends/qt4/main.py index 31e12bb8..bdd81c03 100644 --- a/amsn2/gui/front_ends/qt4/main.py +++ b/amsn2/gui/front_ends/qt4/main.py @@ -39,7 +39,7 @@ def __init__(self, amsn_core, parent=None): QObject.connect(self.opaqLayer, SIGNAL("fadeInCompleted()"), self.__activateNewWidget) QObject.connect(self.opaqLayer, SIGNAL("fadeOutCompleted()"), self.__fadeIn) self.resize(230, 550) - + def closeEvent(self, event): self._amsn_core.quit() @@ -76,14 +76,14 @@ def setTitle(self, title): def set_view(self, view): print "set_view request" - + def setMenu(self, menu): mb = QMenuBar() - + for item in menu.items: if item.type == "cascade": menu = mb.addMenu(item.label) for subitem in item.items: menu.addAction(subitem.label) - - self.setMenuBar(mb) \ No newline at end of file + + self.setMenuBar(mb) diff --git a/amsn2/gui/front_ends/qt4/splash.py b/amsn2/gui/front_ends/qt4/splash.py index aaaa19f4..02d31e24 100644 --- a/amsn2/gui/front_ends/qt4/splash.py +++ b/amsn2/gui/front_ends/qt4/splash.py @@ -24,7 +24,7 @@ from PyQt4.QtGui import * from fadingwidget import FadingWidget from image import * - + class aMSNSplashScreen(QSplashScreen, base.aMSNSplashScreen): def __init__(self, amsn_core, parent): @@ -33,15 +33,15 @@ def __init__(self, amsn_core, parent): def show(self): self.setVisible(True) qApp.processEvents() - + def hide(self): self.setVisible(False) qApp.processEvents() - + def setText(self, text): self.showMessage(text) qApp.processEvents() - + def setImage(self, image): img = Image() img.loadFromImageView(image) diff --git a/amsn2/gui/front_ends/web/__init__.py b/amsn2/gui/front_ends/web/__init__.py index f20b9d6e..b86ef487 100644 --- a/amsn2/gui/front_ends/web/__init__.py +++ b/amsn2/gui/front_ends/web/__init__.py @@ -14,7 +14,7 @@ def load(): try: import imp gui.GUIManager.registerFrontEnd("web", sys.modules[__name__]) - + except ImportError: pass - + diff --git a/amsn2/gui/front_ends/web/bend.py b/amsn2/gui/front_ends/web/bend.py index da1d2efd..f0de32e1 100644 --- a/amsn2/gui/front_ends/web/bend.py +++ b/amsn2/gui/front_ends/web/bend.py @@ -37,9 +37,9 @@ def checkEvent(self): pass # Return true to continue checking events return True - + def event(self,event,values): - # The JS client sent a message to the backend + # The JS client sent a message to the backend # select the function to call depending on the type of event if self.listeners[event] is not None: for func in self.listeners[event]: diff --git a/amsn2/gui/front_ends/web/login.py b/amsn2/gui/front_ends/web/login.py index 7e775159..ca406137 100644 --- a/amsn2/gui/front_ends/web/login.py +++ b/amsn2/gui/front_ends/web/login.py @@ -3,7 +3,7 @@ def __init__(self, amsn_core, main): self._main = main self._amsn_core = amsn_core self.switch_to_profile(None) - + def show(self): self._main.send("showLogin",[]); self._main.addListener("setUsername",self.setUsername) @@ -30,6 +30,6 @@ def signin(self,listE): self.current_profile.email = self._username self.current_profile.password = self._password self._amsn_core.signinToAccount(self, self.current_profile) - + def onConnecting(self,mess): self._main.send("onConnecting",[mess]) diff --git a/amsn2/gui/front_ends/web/main.py b/amsn2/gui/front_ends/web/main.py index 91bf88e5..0d88ea38 100644 --- a/amsn2/gui/front_ends/web/main.py +++ b/amsn2/gui/front_ends/web/main.py @@ -20,13 +20,13 @@ def __init__(self, amsn_core): Backend.__init__(self,"/tmp/test.in","/tmp/test.out") self._amsn_core = amsn_core self._amsn_core.timerAdd(1,self.checkEvent) - + def show(self): self._amsn_core.idlerAdd(self.__on_show) def hide(self): pass - + def setTitle(self,title): pass diff --git a/amsn2/gui/front_ends/web/main_loop.py b/amsn2/gui/front_ends/web/main_loop.py index 3a26c6ac..5ea562a5 100644 --- a/amsn2/gui/front_ends/web/main_loop.py +++ b/amsn2/gui/front_ends/web/main_loop.py @@ -5,7 +5,7 @@ class aMSNMainLoop(base.aMSNMainLoop): def __init__(self, amsn_core): self._amsn_core = amsn_core - + def run(self): self._mainloop = gobject.MainLoop(is_running=True) while self._mainloop.is_running(): @@ -14,7 +14,7 @@ def run(self): except KeyboardInterrupt: self.quit() - + def idlerAdd(self, func): gobject.idle_add(func) @@ -23,4 +23,4 @@ def timerAdd(self, delay, func): def quit(self): self._mainloop.quit() - + diff --git a/amsn2/gui/front_ends/web/window.py b/amsn2/gui/front_ends/web/window.py index 235c4f1f..503b7426 100644 --- a/amsn2/gui/front_ends/web/window.py +++ b/amsn2/gui/front_ends/web/window.py @@ -17,7 +17,7 @@ def setTitle(self, text): @text : a string """ pass - + def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView diff --git a/amsn2/gui/gui.py b/amsn2/gui/gui.py index 970f7182..22f58894 100644 --- a/amsn2/gui/gui.py +++ b/amsn2/gui/gui.py @@ -10,29 +10,29 @@ def __str__(self): class GUIManager(object): front_ends = {} - + def __init__(self, core, gui_name): self._core = core self._name = gui_name - + if GUIManager.frontEndExists(self._name) is False: raise InvalidFrontEndException("Invalid Front End. Available front ends are : " + str(GUIManager.listFrontEnds())) else: self.gui = GUIManager.front_ends[self._name] self.gui = self.gui.load() - + @staticmethod def registerFrontEnd(name, module): GUIManager.front_ends[name] = module - + @staticmethod def listFrontEnds(): return GUIManager.front_ends.keys() - + @staticmethod def frontEndExists(front_end): return front_end in GUIManager.listFrontEnds() - + diff --git a/amsn2/plugins/developers.py b/amsn2/plugins/developers.py index 62c857a5..90821a92 100755 --- a/amsn2/plugins/developers.py +++ b/amsn2/plugins/developers.py @@ -5,20 +5,24 @@ # To register for an event call self.registerForEvent(event, callback) # To de-register call self.unRegisterForEvent(event) class aMSNPlugin(object): - # These are called when the plugin is loaded or un-loaded. - def load(self): pass - def unload(self): pass - - # Used to access the _name or _dir private variables. - def getName(self): - return str(self._name) - def getDir(self): - return str(self._dir) - - # Used to log data. - def log(self, message): - plugins.log(self._name, message) - - # Used to register/de-register for events. - def registerForEvent(self, event, callback): pass - def unRegisterForEvent(self, event): pass + # These are called when the plugin is loaded or un-loaded. + def load(self): + pass + def unload(self): + pass + + # Used to access the _name or _dir private variables. + def getName(self): + return str(self._name) + def getDir(self): + return str(self._dir) + + # Used to log data. + def log(self, message): + plugins.log(self._name, message) + + # Used to register/de-register for events. + def registerForEvent(self, event, callback): + pass + def unRegisterForEvent(self, event): + pass diff --git a/amsn2/plugins/gui.py b/amsn2/plugins/gui.py index dbb4bb02..403c6fa2 100755 --- a/amsn2/plugins/gui.py +++ b/amsn2/plugins/gui.py @@ -1,41 +1,53 @@ import plugins class aMSNPluginSelectorWindow(object): - def drawWindow(self): pass - def showWindow(self): pass - def closeWindow(self): pass - def getPlugins(self): - return plugins.getPlugins() - def getPluginsWithStatus(self): - return plugins.getPluginsWithStatus() - def loadPlugin(self, plugin_name): pass - def unLoadPlugin(self, plugin_name): pass - def configurePlugin(self, plugin_name): pass + def drawWindow(self): + pass + def showWindow(self): + pass + def closeWindow(self): + pass + def getPlugins(self): + return plugins.getPlugins() + def getPluginsWithStatus(self): + return plugins.getPluginsWithStatus() + def loadPlugin(self, plugin_name): + pass + def unLoadPlugin(self, plugin_name): + pass + def configurePlugin(self, plugin_name): + pass class aMSNPluginConfigurationWindow(object): - # __init__(self, plugin_name) - # Calls plugins.findPlugin(plugin_name) to get a plugin. - # If the plugin is found and is loaded then save an instance of it in self._plugin. - # We cannot configure unloaded plugins so do not show the window if the plugin isn't found. - # Then draw the window and show it. - def __init__(self, plugin_name): pass - - # drawWindow(self) - # Handles pre-loading the window contents before the window is shown. - def drawWindow(self): pass - - # showWindow(self) - # If the window is drawn then simply show the window. - def showWindow(self): pass - - # closeWindow(self) - # Handles closing the window. Shouldn't just hide it. - def closeWindow(self): pass - - # getConfig(self) - # Returns a copy of the plugins config as a keyed array. - def getConfig(self): pass - - # saveConfig(self): pass - # Saves the config via plugins.saveConfig(plugin_name, data) - def saveConfig(self, config): pass + # __init__(self, plugin_name) + # Calls plugins.findPlugin(plugin_name) to get a plugin. + # If the plugin is found and is loaded then save an instance of it in self._plugin. + # We cannot configure unloaded plugins so do not show the window if the plugin isn't found. + # Then draw the window and show it. + def __init__(self, plugin_name): + pass + + # drawWindow(self) + # Handles pre-loading the window contents before the window is shown. + def drawWindow(self): + pass + + # showWindow(self) + # If the window is drawn then simply show the window. + def showWindow(self): + pass + + # closeWindow(self) + # Handles closing the window. Shouldn't just hide it. + def closeWindow(self): + pass + + # getConfig(self) + # Returns a copy of the plugins config as a keyed array. + def getConfig(self): + pass + + # saveConfig(self): pass + # Saves the config via plugins.saveConfig(plugin_name, data) + def saveConfig(self, config): + pass diff --git a/amsn2/protocol/events/addressbook.py b/amsn2/protocol/events/addressbook.py index 7566102a..6f4fc7d7 100644 --- a/amsn2/protocol/events/addressbook.py +++ b/amsn2/protocol/events/addressbook.py @@ -25,7 +25,7 @@ class AddressBookEvents(papyon.event.AddressBookEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core papyon.event.AddressBookEventInterface.__init__(self, client) - + def on_addressbook_messenger_contact_added(self, contact): pass @@ -52,4 +52,4 @@ def on_addressbook_group_contact_added(self, group, contact): def on_addressbook_group_contact_deleted(self, group, contact): pass - + diff --git a/amsn2/protocol/events/client.py b/amsn2/protocol/events/client.py index a475eb7b..86b88c1f 100644 --- a/amsn2/protocol/events/client.py +++ b/amsn2/protocol/events/client.py @@ -13,5 +13,5 @@ def on_client_state_changed(self, state): def on_client_error(self, error_type, error): print "ERROR :", error_type, " ->", error - - + + diff --git a/amsn2/protocol/events/contact.py b/amsn2/protocol/events/contact.py index 548b5118..ace17789 100644 --- a/amsn2/protocol/events/contact.py +++ b/amsn2/protocol/events/contact.py @@ -7,7 +7,7 @@ class ContactEvents(papyon.event.ContactEventInterface): def __init__(self, client, contact_manager): self._contact_manager = contact_manager papyon.event.ContactEventInterface.__init__(self, client) - + def on_contact_presence_changed(self, contact): self._contact_manager.onContactPresenceChanged(contact) diff --git a/amsn2/protocol/events/invite.py b/amsn2/protocol/events/invite.py index e155fd0e..3c63cd35 100644 --- a/amsn2/protocol/events/invite.py +++ b/amsn2/protocol/events/invite.py @@ -8,6 +8,6 @@ class InviteEvents(papyon.event.InviteEventInterface): def __init__(self, client, amsn_core): self._amsn_core = amsn_core papyon.event.InviteEventInterface.__init__(self, client) - + def on_invite_conversation(self, conversation): self._amsn_core._conversation_manager.onInviteConversation(conversation) diff --git a/amsn2/protocol/events/oim.py b/amsn2/protocol/events/oim.py index d13891a4..6ef5c414 100644 --- a/amsn2/protocol/events/oim.py +++ b/amsn2/protocol/events/oim.py @@ -25,7 +25,7 @@ class OIMEvents(papyon.event.OfflineMessagesEventInterface): def __init__(self, client, oim_manager): self._oim_manager = oim_manager papyon.event.OfflineMessagesEventInterface.__init__(self, client) - + def on_oim_state_changed(self, state): pass From 16b61d8d150f9cd4387be174fd2b45e49408c3c5 Mon Sep 17 00:00:00 2001 From: jeromegv Date: Mon, 18 May 2009 23:34:56 -0400 Subject: [PATCH 075/147] now the mac version is loading and fixing bug in the loader --- amsn2/gui/front_ends/cocoa/skins.py | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 amsn2/gui/front_ends/cocoa/skins.py diff --git a/amsn2/gui/front_ends/cocoa/skins.py b/amsn2/gui/front_ends/cocoa/skins.py new file mode 100644 index 00000000..57b53530 --- /dev/null +++ b/amsn2/gui/front_ends/cocoa/skins.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# +# amsn - a python client for the WLM Network +# +# Copyright (C) 2008 Dario Freddi +# +# 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os.path + +class Skin(object): + def __init__(self, core, path): + self._path = path + pass + + def getKey(self, key, default): + pass + + def setKey(self, key, value): + pass + + + +class SkinManager(object): + def __init__(self, core): + self._core = core + self.skin = Skin(core, "skins") + + def setSkin(self, name): + self.skin = Skin(self._core, os.path.join("skins", name)) + + def listSkins(self, path): + pass From eed26f18a5259ad3b436606cd1d3dd0fdab85cde Mon Sep 17 00:00:00 2001 From: jeromegv Date: Mon, 18 May 2009 23:35:55 -0400 Subject: [PATCH 076/147] Now the mac version is loading and fixing bug in the loader --- amsn2.py | 6 +++--- amsn2/gui/gui.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/amsn2.py b/amsn2.py index 05381f77..69990a57 100755 --- a/amsn2.py +++ b/amsn2.py @@ -16,16 +16,16 @@ # Detect graphical toolkit available. # Format - 'default_front_end : module name' # cocoa > efl > qt4 > gtk > console - toolkits = {'cocoa' : '????', + toolkits = {'cocoa' : 'amsn2.gui.front_ends.cocoa', 'elf' : 'ecore', 'qt4' : 'PyQt4.QtGui', 'gtk' : 'gtk', 'console' : None} for toolkit in toolkits: try: - default_front_end = toolkit module_name = toolkits[toolkit] module = __import__(module_name) + default_front_end = toolkit vars()[module_name] = module # Debug # print 'Imported toolkit "%s" with module "%s"' % (toolkit, module) @@ -36,7 +36,7 @@ pass except TypeError: pass - + parser = optparse.OptionParser() parser.add_option("-a", "--account", dest="account", default=None, help="The account's username to use") diff --git a/amsn2/gui/gui.py b/amsn2/gui/gui.py index 22f58894..e6fb67ff 100644 --- a/amsn2/gui/gui.py +++ b/amsn2/gui/gui.py @@ -14,7 +14,7 @@ class GUIManager(object): def __init__(self, core, gui_name): self._core = core self._name = gui_name - + if GUIManager.frontEndExists(self._name) is False: raise InvalidFrontEndException("Invalid Front End. Available front ends are : " + str(GUIManager.listFrontEnds())) else: From ae90b1cf186d691e6ce1805dbb9dd0ecdc265db9 Mon Sep 17 00:00:00 2001 From: jeromegv Date: Tue, 19 May 2009 12:28:14 -0400 Subject: [PATCH 077/147] updated readme for easier instructions --- amsn2/gui/front_ends/cocoa/readme.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/amsn2/gui/front_ends/cocoa/readme.txt b/amsn2/gui/front_ends/cocoa/readme.txt index e09ca922..062ef3ca 100644 --- a/amsn2/gui/front_ends/cocoa/readme.txt +++ b/amsn2/gui/front_ends/cocoa/readme.txt @@ -8,12 +8,13 @@ Building We use NIB files to build our interface, in order to load the NIB's correctly, we require aMSN 2 to be run from inside an application bundle. To build the bundle you need (from macports): python25 -py-openssl (see note) +py25-openssl py25-gobject +py25-crypto +py25-hashlib +py25-py2app-devel py25-pyobjc2-cocoa - - py25-pyobjc2 - - py25-py2app-devel - - py25-py2app (see note) + After they have been installed, you will need to run this inside the root folder of amsn2: $ /opt/local/bin/python2.5 setupCocoa.py py2app -A @@ -23,11 +24,11 @@ $ dist/aMSN2.app/Contents/MacOS/aMSN2 py-openssl - Note ================= -It is recommended to use the 0.7 version of py-openssl. An updated portfile can be found at: http://hosting.notjustanothermacuser.com/macports/py-openssl/Portfiles/0.7_0.tar.gz +It is recommended to use the 0.7 version of py-openssl. Last version of Macports is using version 0.7. You can also use an updated portfile that can be found at: http://hosting.notjustanothermacuser.com/macports/py-openssl/Portfiles/0.7_0.tar.gz py2app - Note ============= -The current py2app on mac ports is out of date, they have 0.3.6, while py25-pyobjc2-cooca requires > 0.4.0. No "official" releases of py2app have been made since 0.3.6, however the current trunk from SVN gives version 0.4.2. If you wish to use macports' python25, and you wish to build the bundle then you will need to update the Portfile for py25-py2app. The updated portfile can be found at: http://hosting.notjustanothermacuser.com/macports/py25-py2app/Portfiles/0.4.2_0.tar.gz +Be sure to use py25-py2app-devel and not py25-py2app from Macports, because py25-py2app is outdated and does not work with pyobjc2-cocoa. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= From 683b66a57e61e3df2b1c0b2b295cb4a25a65b7cd Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Thu, 21 May 2009 14:25:49 +0200 Subject: [PATCH 078/147] [core] Added correct DP request [protocol] Added managing of contact changes --- amsn2/core/contactlist_manager.py | 17 +++++++++++++---- amsn2/protocol/events/contact.py | 25 ++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index 1931bf76..e3a78bc5 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -14,7 +14,7 @@ def __init__(self, core): #TODO: sorting contacts & groups - def onContactPresenceChanged(self, papyon_contact): + def onContactChanged(self, papyon_contact): #1st/ update the aMSNContact object c = self.getContact(papyon_contact.id, papyon_contact) c.fill(self._core, papyon_contact) @@ -24,7 +24,14 @@ def onContactPresenceChanged(self, papyon_contact): #TODO: update the group view + def onContactDPChanged(self, papyon_contact): + #TODO: add local cache for DPs #Request the DP... + c = self.getContact(papyon_contact.id, papyon_contact) + if ("Theme", "dp_nopic") in c.dp.imgs: + c.dp.load("Theme", "dp_loading") + elif papyon_contact is None: + c.dp.load("Theme", "dp_nopic") if (papyon_contact.presence is not papyon.Presence.OFFLINE and papyon_contact.msn_object): self._core._profile.client._msn_object_store.request(papyon_contact.msn_object, @@ -107,14 +114,16 @@ def getContact(self, cid, papyon_contact=None): class aMSNContact(): def __init__(self, core, papyon_contact): self.uid = papyon_contact.id + self.dp = ImageView() + if papyon_contact.msn_object is None: + self.dp.load("Theme", "dp_nopic") + else: + self.dp.load("Theme", "dp_loading") self.fill(core, papyon_contact) def fill(self, core, papyon_contact): self.icon = ImageView() self.icon.load("Theme","buddy_" + core.p2s[papyon_contact.presence]) - self.dp = ImageView() - #TODO: for the moment, use default dp - self.dp.load("Theme", "dp_nopic") self.emblem = ImageView() self.emblem.load("Theme", "emblem_" + core.p2s[papyon_contact.presence]) #TODO: PARSE ONLY ONCE diff --git a/amsn2/protocol/events/contact.py b/amsn2/protocol/events/contact.py index ace17789..0f5f8c87 100644 --- a/amsn2/protocol/events/contact.py +++ b/amsn2/protocol/events/contact.py @@ -9,5 +9,28 @@ def __init__(self, client, contact_manager): papyon.event.ContactEventInterface.__init__(self, client) def on_contact_presence_changed(self, contact): - self._contact_manager.onContactPresenceChanged(contact) + self._contact_manager.onContactChanged(contact) + + def on_contact_display_name_changed(self, contact): + self._contact_manager.onContactChanged(contact) + + def on_contact_personal_message_changed(self, contact): + self._contact_manager.onContactChanged(contact) + + def on_contact_current_media_changed(self, contact): + self._contact_manager.onContactChanged(contact) + + def on_contact_msn_object_changed(self, contact): + # TODO: filter DPs + if contact.msn_object._type is papyon.p2p.MSNObjectType.DISPLAY_PICTURE: + self._contact_manager.onContactDPChanged(contact) + + def on_contact_memberships_changed(self, contact): + pass + + def on_contact_infos_changed(self, contact, infos): + pass + + def on_contact_client_capabilities_changed(self, contact): + pass From 4528d021a2f36e8143de085917db9a77730af5e7 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 22 May 2009 14:52:00 +0200 Subject: [PATCH 079/147] [core] Redifined personal info management to manage papyon's profile events, I think this is the way it should be done. --- amsn2/core/amsn.py | 2 +- amsn2/core/personalinfo_manager.py | 52 ++++++++++++++++++++-------- amsn2/core/views/imageview.py | 2 ++ amsn2/core/views/personalinfoview.py | 34 ++++++------------ amsn2/protocol/client.py | 2 ++ amsn2/protocol/events/contact.py | 2 +- amsn2/protocol/events/profile.py | 25 +++++++++++++ 7 files changed, 79 insertions(+), 40 deletions(-) create mode 100644 amsn2/protocol/events/profile.py diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index ed4e7a11..019d5d08 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -158,7 +158,7 @@ def connectionStateChanged(self, profile, state): profile.clwin.show() profile.login = None - self._personalinfo_manager.set_profile(profile) + self._personalinfo_manager.set_profile_connected(profile) self._contactlist_manager.onCLDownloaded(profile.client.address_book) def idlerAdd(self, func): diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index b77d71e8..cfa800bb 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -4,41 +4,65 @@ class aMSNPersonalInfoManager: def __init__(self, core): self._core = core self._em = core._event_manager - self._personalinfoview = None + self._personalinfoview = PersonalInfoView(self) self._papyon_profile = None - def set_profile(self, amsn_profile): + def set_profile_connected(self, amsn_profile): self._papyon_profile = amsn_profile.client.profile - self._personalinfoview = PersonalInfoView(self._core, self._papyon_profile) # set login presence and update the gui self._personalinfoview.presence = amsn_profile.presence """ Actions from ourselves """ - def _onNickUpdated(self, new_nick): + def _onNickChanged(self, new_nick): # TODO: parsing self._papyon_profile.display_name = new_nick.toString() - self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) - def _onPMUpdated(self, new_pm): + def _onPSMChanged(self, new_psm): # TODO: parsing - self._papyon_profile.personal_message = new_pm.toString() - self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + self._papyon_profile.personal_message = new_psm.toString() - def _onDPUpdated(self, new_dp): + def _onDPChanged(self, new_dp): # TODO: manage msn_objects - self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + self._papyon_profile.msn_object = new_dp - def _onPresenceUpdated(self, new_presence): + def _onPresenceChanged(self, new_presence): for key in self._core.p2s: if self._core.p2s[key] == new_presence: break self._papyon_profile.presence = key - self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) - """ actions from the core """ - def _onCMUpdated(self, new_media): + """ Actions from the core """ + def _onCMChanged(self, new_media): self._papyon_profile.current_media = new_media + + """ Notifications from the server """ + def onNickUpdated(self, nick): + # TODO: parse fields for smileys, format, etc + self._personalinfoview._nickname.reset() + self._personalinfoview._nickname.appendText(nick) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + + def onPSMUpdated(self, psm): + # TODO: parse fields for smileys, format, etc + self._personalinfoview._psm.reset() + self._personalinfoview._psm.appendText(psm) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + + def onDPUpdated(self, dp): + self._personalinfoview._image.reset() + self._personalinfoview._image.load(dp) + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + + def onPresenceUpdated(self, presence): + self._personalinfoview._presence = self._core.p2s[presence] + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + + def onCMUpdated(self, cm): + self._personalinfoview._current_media.reset() + #TODO: insert separators + self._personalinfoview._current_media.apprndText(cm[0]) + self._personalinfoview._current_media.apprndText(cm[1]) self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) # TODO: connect to papyon event, maybe build a mailbox_manager diff --git a/amsn2/core/views/imageview.py b/amsn2/core/views/imageview.py index 2393edd5..a536fad7 100644 --- a/amsn2/core/views/imageview.py +++ b/amsn2/core/views/imageview.py @@ -30,4 +30,6 @@ def appendImageView(self, iv): def prependImageView(self, iv): self.imgs = iv.imgs[:].extend(self.imgs) + def reset(self): + self.imgs = [] diff --git a/amsn2/core/views/personalinfoview.py b/amsn2/core/views/personalinfoview.py index 24435d69..666c2316 100644 --- a/amsn2/core/views/personalinfoview.py +++ b/amsn2/core/views/personalinfoview.py @@ -5,21 +5,14 @@ def rw_property(f): return property(**f()) class PersonalInfoView(object): - def __init__(self, core, papyon_profile): - # TODO: parse fields for smileys, format, etc + def __init__(self, personalinfo_manager): + self._personalinfo_manager = personalinfo_manager + self._nickname = StringView() - self._nickname.appendText(papyon_profile.display_name) self._psm = StringView() - self._psm.appendText(papyon_profile.personal_message) self._current_media = StringView() - if papyon_profile.current_media is not None: - self._current_media.appendText(papyon_profile.current_media[0]) - self._current_media.appendText(papyon_profile.current_media[1]) - # TODO: How do I get the profile image? self._image = ImageView() - #self.image.load(papyon_profile.msn_object) - self._presence = core.p2s[papyon_profile.presence] - self._personalinfo_manager = core._personalinfo_manager + self._presence = 'offline' # TODO: get more info, how to manage webcams and mail self._webcam = None @@ -30,8 +23,7 @@ def nick(): def fget(self): return self._nickname def fset(self, nick): - self._nickname = nick - self._personalinfo_manager._onNickUpdated(nick) + self._personalinfo_manager._onNickChanged(nick) return locals() @rw_property @@ -39,8 +31,7 @@ def psm(): def fget(self): return self._psm def fset(self, psm): - self._psm = psm - self._personalinfo_manager._onPMUpdated(psm) + self._personalinfo_manager._onPSMChanged(psm) return locals() @rw_property @@ -48,8 +39,7 @@ def dp(): def fget(self): return self._image def fset(self, imagev): - self._image = imagev - self._personalinfo_manager._onDPUpdated(imagev) + self._personalinfo_manager._onDPChanged(imagev) return locals() @rw_property @@ -57,18 +47,14 @@ def current_media(): def fget(self): return self._current_media def fset(self, artist, song): - # TODO: separators - self._current_media.appendText(artist) - self._current_media.appendText(song) - self._personalinfo_manager._onCMUpdated((artist, song)) + self._personalinfo_manager._onCMChanged((artist, song)) return locals() @rw_property def presence(): def fget(self): return self._presence - def fset(self, p): - self._presence = p - self._personalinfo_manager._onPresenceUpdated(p) + def fset(self, presence): + self._personalinfo_manager._onPresenceChanged(presence) return locals() diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index d971a0c9..6506e1e8 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -25,6 +25,7 @@ from events.invite import * from events.oim import * from events.addressbook import * +from events.profile import * class Client(papyon.Client): def __init__(self, amsn_core, profile): @@ -39,6 +40,7 @@ def __init__(self, amsn_core, profile): self._invite_events_handler = InviteEvents(self, self._amsn_core) self._oim_events_handler = OIMEvents(self, self._amsn_core._oim_manager) self._addressbook_events_handler = AddressBookEvents(self, self._amsn_core) + self._profile_events_handler = ProfileEvents(self, self._amsn_core._personalinfo_manager) def connect(self): self.login(self._amsn_profile.email, self._amsn_profile.password) diff --git a/amsn2/protocol/events/contact.py b/amsn2/protocol/events/contact.py index 0f5f8c87..f17dfe19 100644 --- a/amsn2/protocol/events/contact.py +++ b/amsn2/protocol/events/contact.py @@ -21,7 +21,7 @@ def on_contact_current_media_changed(self, contact): self._contact_manager.onContactChanged(contact) def on_contact_msn_object_changed(self, contact): - # TODO: filter DPs + # TODO: filter objects if contact.msn_object._type is papyon.p2p.MSNObjectType.DISPLAY_PICTURE: self._contact_manager.onContactDPChanged(contact) diff --git a/amsn2/protocol/events/profile.py b/amsn2/protocol/events/profile.py new file mode 100644 index 00000000..e47c79ce --- /dev/null +++ b/amsn2/protocol/events/profile.py @@ -0,0 +1,25 @@ + +import papyon +import papyon.event + +class ProfileEvents(papyon.event.ProfileEventInterface): + def __init__(self, client, personalinfo_manager): + self._personalinfo_manager = personalinfo_manager + papyon.event.ProfileEventInterface.__init__(self, client) + + def on_profile_presence_changed(self): + self._personalinfo_manager.onPresenceUpdated(self._client.profile.presence) + + def on_profile_display_name_changed(self): + self._personalinfo_manager.onNickUpdated(self._client.profile.display_name) + + def on_profile_personal_message_changed(self): + self._personalinfo_manager.onPMUpdated(self._client.profile.personal_message) + + def on_profile_current_media_changed(self): + self._personalinfo_manager.onCMUpdated(self._client.profile.current_media) + + def on_profile_msn_object_changed(self): + #TODO: filter objects + if self._client.profile.msn_object._type is papyon.p2p.MSNObjectType.DISPLAY_PICTURE: + self._personalinfo_manager.onDPUpdated(self._client.profile.msn_object) From 857985c40cdea91b6bc496fe85d7d254ce3f5bdf Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 22 May 2009 15:00:31 +0200 Subject: [PATCH 080/147] [protocol] Added mailbox handler --- amsn2/protocol/client.py | 2 ++ amsn2/protocol/events/mailbox.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 amsn2/protocol/events/mailbox.py diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 6506e1e8..0bf865b6 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -26,6 +26,7 @@ from events.oim import * from events.addressbook import * from events.profile import * +from events.mailbox import * class Client(papyon.Client): def __init__(self, amsn_core, profile): @@ -41,6 +42,7 @@ def __init__(self, amsn_core, profile): self._oim_events_handler = OIMEvents(self, self._amsn_core._oim_manager) self._addressbook_events_handler = AddressBookEvents(self, self._amsn_core) self._profile_events_handler = ProfileEvents(self, self._amsn_core._personalinfo_manager) + self._mailbox_events_handler = MailboxEvents(self, self._amsn_core) def connect(self): self.login(self._amsn_profile.email, self._amsn_profile.password) diff --git a/amsn2/protocol/events/mailbox.py b/amsn2/protocol/events/mailbox.py new file mode 100644 index 00000000..6dda5e44 --- /dev/null +++ b/amsn2/protocol/events/mailbox.py @@ -0,0 +1,17 @@ + +import papyon +import papyon.event + +class MailboxEvents(papyon.event.MailboxEventInterface): + def __init__(self, client, amsn_core): + self._amsn_core = amsn_core + papyon.event.MailboxEventInterface.__init__(self, client) + + def on_mailbox_unread_mail_count_changed(self, unread_mail_count, + initial=False): + """The number of unread mail messages""" + pass + + def on_mailbox_new_mail_received(self, mail_message): + """New mail message notification""" + pass From a75d7b5cfd08f8f464270ab65e13386076282c8d Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Sun, 24 May 2009 13:16:48 +0200 Subject: [PATCH 081/147] [personalinfo] added methods to set custom status --- amsn2/core/personalinfo_manager.py | 21 +++++++++++++++++---- amsn2/core/views/personalinfoview.py | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index cfa800bb..b2a0a82f 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -9,6 +9,7 @@ def __init__(self, core): def set_profile_connected(self, amsn_profile): self._papyon_profile = amsn_profile.client.profile + #print '%s' % self._papyon_profile._profile # set login presence and update the gui self._personalinfoview.presence = amsn_profile.presence @@ -22,16 +23,28 @@ def _onPSMChanged(self, new_psm): # TODO: parsing self._papyon_profile.personal_message = new_psm.toString() - def _onDPChanged(self, new_dp): - # TODO: manage msn_objects - self._papyon_profile.msn_object = new_dp - def _onPresenceChanged(self, new_presence): + # TODO: manage custom presence for key in self._core.p2s: if self._core.p2s[key] == new_presence: break self._papyon_profile.presence = key + def _onDPChangeRequest(self): + # TODO: tell the core to invoke a file chooser and change DP + pass + + def _onDPChanged(self, new_dp): + # TODO: manage msn_objects + self._papyon_profile.msn_object = new_dp + + def _onPresenceDPChanged(self, new_presence, new_dp): + # TODO: manage msn_objects + self._papyon_profile.presence_msn_object = presence, new_dp + + def _onPSMCMChanged(self, new_psm, new_media): + self._papyon_profile.personal_message_current_media = new_psm, new_media + """ Actions from the core """ def _onCMChanged(self, new_media): self._papyon_profile.current_media = new_media diff --git a/amsn2/core/views/personalinfoview.py b/amsn2/core/views/personalinfoview.py index 666c2316..64f442d2 100644 --- a/amsn2/core/views/personalinfoview.py +++ b/amsn2/core/views/personalinfoview.py @@ -18,6 +18,9 @@ def __init__(self, personalinfo_manager): self._webcam = None self._mail_unread = None + def onDPChangeRequest(self): + self._personalinfo_manager._onDPChangeRequest() + @rw_property def nick(): def fget(self): @@ -58,3 +61,20 @@ def fset(self, presence): self._personalinfo_manager._onPresenceChanged(presence) return locals() + # custom presence + @rw_property + def presence_dp(): + def fget(self): + return (self.presence, self.dp) + def fset(self, presence, dpv): + self._personalinfo_manager._onPresenceDPChanged(presence, dpv) + return locals() + + @rw_property + def psm_current_media(): + def fget(self): + return (self.psm, self.current_media) + def fset(self, psm, artist, song): + self._personalinfo_manager._onPSMCMChanged(psm, (artist, song)) + return locals() + From 5d17c38355c1de4e9930251d4f0fcba8236fd74d Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Wed, 20 May 2009 13:58:57 +0200 Subject: [PATCH 082/147] [gtk] Fix a bug when login status is brb, lunch, online, now select the correct value --- amsn2/gui/front_ends/gtk/login.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index be55761f..1f5c5fbe 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -90,11 +90,15 @@ def __init__(self, amsn_core, parent): passbox.pack_start(self.password, False, False) # status list + self.status_values = {} + status_n = 0 status_list = gtk.ListStore(gtk.gdk.Pixbuf, str, str) for key in self._amsn_core.p2s: name = self._amsn_core.p2s[key] _, path = self._theme_manager.get_statusicon("buddy_%s" % name) if (name == 'offline'): continue + self.status_values[name] = status_n + status_n = status_n +1 icon = gtk.gdk.pixbuf_new_from_file(path) name = string.capitalize(name) status_list.append([icon, name, key]) @@ -202,8 +206,11 @@ def signin(self): self.current_profile.username = self.user.get_active_text() self.current_profile.email = self.user.get_active_text() self.current_profile.password = self.password.get_text() - i = self.statusCombo.get_active() - self.current_profile.presence = self._amsn_core.p2s.values()[i] + status = self.statusCombo.get_active() + for key in self.status_values: + if self.status_values[key] == status: + break + self.current_profile.presence = key self._amsn_core.signinToAccount(self, self.current_profile) self.timer = gobject.timeout_add(40, self.__animation) From e3459945bb742e215195c475eb121e0a8e954096 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Tue, 19 May 2009 14:20:01 +0200 Subject: [PATCH 083/147] [GTK] fix a possible loop when changing presence --- amsn2/gui/front_ends/gtk/contact_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 335315da..9d58b9c2 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -208,7 +208,8 @@ def onStatusChanged(self, combobox): break # FIXME: changing status to 'offline' will disconnect, so return to login window # also fix papyon, gives an error on setting 'offline' - self._myview.presence = key + if key != self._myview.presence: + self._myview.presence = key def __on_btnNicknameClicked(self, source): self.__switchToNickInput() From e6023d31108eeeb889c392405b7ea24ce98523d2 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Sun, 24 May 2009 16:11:42 +0200 Subject: [PATCH 084/147] [personalinfo] set nickname when connected successfully --- amsn2/core/personalinfo_manager.py | 12 +++++++++++- amsn2/gui/front_ends/gtk/contact_list.py | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index b2a0a82f..653f88df 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -11,7 +11,17 @@ def set_profile_connected(self, amsn_profile): self._papyon_profile = amsn_profile.client.profile #print '%s' % self._papyon_profile._profile - # set login presence and update the gui + # set nickname at login + # could be overriden by the one set in the saved profile + # TODO: add setting display picture and saved personal message + strv = StringView() + if amsn_profile.username == amsn_profile.email: + strv.appendText(self._papyon_profile.display_name) + else: + strv.appendText(amsn_profile.username) + self._personalinfoview.nick = strv + + # set login presence, from this moment the client appears to the others self._personalinfoview.presence = amsn_profile.presence """ Actions from ourselves """ diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 9d58b9c2..663c9ef9 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -191,7 +191,6 @@ def myInfoUpdated(self, view): @view: the PersonalInfoView of the ourself (contains DP, nick, psm, currentMedia,...)""" # TODO: image, ... - # FIXME: status at login, now seems 'offline' even if we are online self._myview = view nk = view.nick self.nicklabel.set_markup(nk.toString()) From 40175b1a6612ea0d1419ab40e39761cb6bbf6d9f Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Mon, 25 May 2009 22:47:04 +0200 Subject: [PATCH 085/147] Add pymsn/ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6cba120a..8ef3dd63 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ Debug/ ui_* .*.swp +pymsn/ From 028d63099769316255a9e9dd17982a29706bc7dc Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Mon, 25 May 2009 22:53:42 +0200 Subject: [PATCH 086/147] Remove useless stuff --- amsn2/gui/base/image.py | 60 --------- amsn2/gui/front_ends/console/__init__.py | 23 ---- amsn2/gui/front_ends/console/console.py | 5 - amsn2/gui/front_ends/console/contact_list.py | 123 ------------------- amsn2/gui/front_ends/console/login.py | 54 -------- amsn2/gui/front_ends/console/main.py | 16 --- amsn2/gui/front_ends/console/main_loop.py | 25 ---- amsn2/gui/front_ends/mine/__init__.py | 21 ---- amsn2/gui/front_ends/mine/contact_list.py | 94 -------------- amsn2/gui/front_ends/mine/login.py | 54 -------- amsn2/gui/front_ends/mine/main.py | 22 ---- amsn2/gui/front_ends/mine/main_loop.py | 27 ---- amsn2/gui/front_ends/mine/mine.py | 7 -- amsn2/gui/front_ends/mine/skins.py | 25 ---- amsn2/gui/front_ends/mine/splash.py | 28 ----- 15 files changed, 584 deletions(-) delete mode 100644 amsn2/gui/base/image.py delete mode 100644 amsn2/gui/front_ends/console/__init__.py delete mode 100644 amsn2/gui/front_ends/console/console.py delete mode 100644 amsn2/gui/front_ends/console/contact_list.py delete mode 100644 amsn2/gui/front_ends/console/login.py delete mode 100644 amsn2/gui/front_ends/console/main.py delete mode 100644 amsn2/gui/front_ends/console/main_loop.py delete mode 100644 amsn2/gui/front_ends/mine/__init__.py delete mode 100644 amsn2/gui/front_ends/mine/contact_list.py delete mode 100644 amsn2/gui/front_ends/mine/login.py delete mode 100644 amsn2/gui/front_ends/mine/main.py delete mode 100644 amsn2/gui/front_ends/mine/main_loop.py delete mode 100644 amsn2/gui/front_ends/mine/mine.py delete mode 100644 amsn2/gui/front_ends/mine/skins.py delete mode 100644 amsn2/gui/front_ends/mine/splash.py diff --git a/amsn2/gui/base/image.py b/amsn2/gui/base/image.py deleted file mode 100644 index 024eca38..00000000 --- a/amsn2/gui/base/image.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -#=================================================== -# -# image.py - This file is part of the amsn2 package -# -# Copyright (C) 2008 Wil Alvarez -# -# This script is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. -# -# This script is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for more details. -# -# You should have received a copy of the GNU General Public License along with -# this script (see COPYING); if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -#=================================================== - -class aMSNImage(object): - """ This interface holds the basic methods that must have a Image class in - any front end - """ - def __init__(self, theme_manager, view): - self._theme_manager = theme_manager - self.load(view) - - def load(self, view): - i = 0 - for (resource_type, value) in view.imgs: - try: - loadMethod = getattr(self, "_loadFrom%s" % resource_type) - except AttributeError, e: - print "From load in base/image.py:\n\t(resource_type, value) = " - "(%s, %s)\n\tAttributeError: %s" % (resource_type, value, e) - else: - loadMethod(value, view, i) - i += 1 - - def _loadFromFilename(self, filename, view, index): - """ Load an image from a path. This method should be reimplemented """ - pass - - def _loadFromTheme(self, resource_name, view, index): - """ Load an image from a key stored in aMSNThemeManager""" - try: - _, name = self._theme_manager.get_value(resource_name) - except Exception, e: - print e - print "Error loading resource %s" % resource_name - else: - self._loadFromFilename(name, view, index) - - def _loadFromNone(self, resource_name, view=None, index=0): - pass - diff --git a/amsn2/gui/front_ends/console/__init__.py b/amsn2/gui/front_ends/console/__init__.py deleted file mode 100644 index e126a0c1..00000000 --- a/amsn2/gui/front_ends/console/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ - -from amsn2 import gui -import sys - -# Here we load the actual front end. -# We need to import the front end module and return it -# so the guimanager can access its classes -def load(): - import console - return console - -# Initialize the front end by checking for any -# dependency then register it to the guimanager -try: - import imp - - # try to find any necessary module - # imp.find_module() - gui.GUIManager.registerFrontEnd("console", sys.modules[__name__]) - -except ImportError: - pass - diff --git a/amsn2/gui/front_ends/console/console.py b/amsn2/gui/front_ends/console/console.py deleted file mode 100644 index ebcd9fa2..00000000 --- a/amsn2/gui/front_ends/console/console.py +++ /dev/null @@ -1,5 +0,0 @@ -from main_loop import * -from main import * -from contact_list import * -from login import * - diff --git a/amsn2/gui/front_ends/console/contact_list.py b/amsn2/gui/front_ends/console/contact_list.py deleted file mode 100644 index ad494a3e..00000000 --- a/amsn2/gui/front_ends/console/contact_list.py +++ /dev/null @@ -1,123 +0,0 @@ -from amsn2.gui import base -import papyon - -class Contact(object): - def __init__(self, name, presence): - self.name = name - self.presence = presence - self.p2s = {papyon.Presence.ONLINE:"online", - papyon.Presence.BUSY:"busy", - papyon.Presence.IDLE:"idle", - papyon.Presence.AWAY:"away", - papyon.Presence.BE_RIGHT_BACK:"brb", - papyon.Presence.ON_THE_PHONE:"phone", - papyon.Presence.OUT_TO_LUNCH:"lunch", - papyon.Presence.INVISIBLE:"hidden", - papyon.Presence.OFFLINE:"offline"} - - - def is_online(self): - return self.presence != papyon.Presence.OFFLINE - - def status(self): - return self.p2s[self.presence] - -class Group(object): - def __init__(self, name): - self.contacts = {} - self.name = name - - def count(self): - online = 0 - total = 0 - for c in self.contacts: - total += 1 - if self.contacts[c].is_online(): - online +=1 - - return (online, total) - - -class aMSNContactList(base.aMSNContactList): - def __init__(self, amsn_core): - self._amsn_core = amsn_core - self.groups = {} - self.contacts = {} - - def show(self): - pass - - def hide(self): - pass - - def contactStateChange(self, contact): - for group in contact.groups: - self.groups[group.id].contacts[contact.id].presence = contact.presence - - self.__update_view() - - def contactNickChange(self, contact): - pass - - def contactPSMChange(self, contact): - pass - - def contactAlarmChange(self, contact): - pass - - def contactDisplayPictureChange(self, contact): - pass - - def contactSpaceChange(self, contact): - pass - - def contactSpaceFetched(self, contact): - pass - - def contactBlocked(self, contact): - pass - - def contactUnblocked(self, contact): - pass - - def contactMoved(self, from_group, to_group, contact): - pass - - def contactAdded(self, group, contact): - self.groups[group.id].contacts[contact.id] = Contact(contact.display_name, contact.presence) - self.__update_view() - - def contactRemoved(self, group, contact): - pass - - def contactRenamed(self, contact): - pass - - def groupRenamed(self, group): - pass - - def groupAdded(self, group): - self.groups[group.id] = Group(group.name) - self.__update_view() - - def groupRemoved(self, group): - pass - - def configure(self, option, value): - pass - - def cget(self, option, value): - pass - - def __cls(self): - print "" - - def __update_view(self): - self.__cls() - for g in self.groups: - count = self.groups[g].count() - print "|X| %s (%d/%d)" % (self.groups[g].name, count[0], count[1]) - for c in self.groups[g].contacts: - print " |=> %s (%s)" % (self.groups[g].contacts[c].name, self.groups[g].contacts[c].status()) - print "" - diff --git a/amsn2/gui/front_ends/console/login.py b/amsn2/gui/front_ends/console/login.py deleted file mode 100644 index 11892f9d..00000000 --- a/amsn2/gui/front_ends/console/login.py +++ /dev/null @@ -1,54 +0,0 @@ - -class aMSNLoginWindow(object): - def __init__(self, amsn_core): - self._amsn_core = amsn_core - self.switch_to_profile(None) - - def show(self): - if self._username is not None and self._username != "": - print "Account : %s" % (self._username) - else: - self._username = raw_input("Account : ") - - if self._password is not None and self._password != "": - print "Password : ******" - else: - import getpass - self._password = getpass.getpass('Password: ') - - self.signin() - - def hide(self): - pass - - def switch_to_profile(self, profile): - self.current_profile = profile - if self.current_profile is not None: - self._username = self.current_profile.username - self._password = self.current_profile.password - - def signin(self): - self.current_profile.username = self._username - self.current_profile.email = self._username - self.current_profile.password = self._password - self._amsn_core.signinToAccount(self, self.current_profile) - - - def onConnecting(self, progress, message): - print "Connecting..." - - def onConnected(self): - print "Connected..." - - def onAuthenticating(self): - print "Authenticating..." - - def onAuthenticated(self): - print "Authenticated..." - - def onSynchronizing(self): - print "Fetching contact list..." - - def onSynchronized(self): - print "Synchronized!" - diff --git a/amsn2/gui/front_ends/console/main.py b/amsn2/gui/front_ends/console/main.py deleted file mode 100644 index bdd0514a..00000000 --- a/amsn2/gui/front_ends/console/main.py +++ /dev/null @@ -1,16 +0,0 @@ - -from amsn2.gui import base - -class aMSNMainWindow(base.aMSNMainWindow): - def __init__(self, amsn_core): - self._amsn_core = amsn_core - - def show(self): - self._amsn_core.idlerAdd(self.__on_show) - - def hide(self): - pass - - def __on_show(self): - self._amsn_core.mainWindowShown() - diff --git a/amsn2/gui/front_ends/console/main_loop.py b/amsn2/gui/front_ends/console/main_loop.py deleted file mode 100644 index 8bcf40a8..00000000 --- a/amsn2/gui/front_ends/console/main_loop.py +++ /dev/null @@ -1,25 +0,0 @@ - -from amsn2.gui import base -import gobject - -class aMSNMainLoop(base.aMSNMainLoop): - - def run(self): - self._mainloop = gobject.MainLoop(is_running=True) - - while self._mainloop.is_running(): - try: - self._mainloop.run() - except KeyboardInterrupt: - self.quit() - - - def idlerAdd(self, func): - gobject.idle_add(func) - - def timerAdd(self, delay, func): - gobject.timeout_add(delay, func) - - def quit(self): - self._mainloop.quit() - diff --git a/amsn2/gui/front_ends/mine/__init__.py b/amsn2/gui/front_ends/mine/__init__.py deleted file mode 100644 index b7af9103..00000000 --- a/amsn2/gui/front_ends/mine/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This is a template that has all the basic functions to make a succesfull login. - -from amsn2 import gui -import sys - -# Here we load the actual front end. -# We need to import the front end module and return it -# so the guimanager can access its classes -def load(): - import mine - return mine - -# Initialize the front end by checking for any -# dependency then register it to the guimanager -try: - import imp - gui.GUIManager.registerFrontEnd("mine", sys.modules[__name__]) - -except ImportError: - pass - diff --git a/amsn2/gui/front_ends/mine/contact_list.py b/amsn2/gui/front_ends/mine/contact_list.py deleted file mode 100644 index f7367a48..00000000 --- a/amsn2/gui/front_ends/mine/contact_list.py +++ /dev/null @@ -1,94 +0,0 @@ -"""TODO: - * Let the aMSNContactListWidget be selectable to choose contacts to add to a - conversation... each contact should have a checkbox on front of it - * Drag contacts through groups - * Drag groups - ... -""" - - -class aMSNContactListWindow(object): - """ This interface represents the main Contact List Window - self._clwiget is an aMSNContactListWidget - """ - - def __init__(self, amsn_core, parent): - pass - - def show(self): - """ Show the contact list window """ - pass - - def hide(self): - """ Hide the contact list window """ - pass - - def setTitle(self, text): - """ This will allow the core to change the current window's title - @text : a string - """ - pass - - def setMenu(self, menu): - """ This will allow the core to change the current window's main menu - @menu : a MenuView - """ - pass - - def myInfoUpdated(self, view): - """ This will allow the core to change pieces of information about - ourself, such as DP, nick, psm, the current media being played,... - @view: the contactView of the ourself (contains DP, nick, psm, - currentMedia,...)""" - pass - -class aMSNContactListWidget(object): - """ This interface implements the contact list of the UI """ - def __init__(self, amsn_core, parent): - clm = amsn_core._contactlist_manager - clm.register(clm.CLVIEW_UPDATED, self.contactListUpdated) - clm.register(clm.GROUPVIEW_UPDATED, self.groupUpdated) - clm.register(clm.CONTACTVIEW_UPDATED, self.contactUpdated) - - def show(self): - """ Show the contact list widget """ - pass - - def hide(self): - """ Hide the contact list widget """ - pass - - def contactListUpdated(self, clView): - """ This method will be called when the core wants to notify - the contact list of the groups that it contains, and where they - should be drawn a group should be drawn. - It will be called initially to feed the contact list with the groups - that the CL should contain. - It will also be called to remove any group that needs to be removed. - @cl : a ContactListView containing the list of groups contained in - the contact list which will contain the list of ContactViews - for all the contacts to show in the group.""" - pass - - def groupUpdated(self, groupView): - """ This method will be called to notify the contact list - that a group has been updated. - The contact list should update its icon and name - but also its content (the ContactViews). The order of the contacts - may be changed, in which case the UI should update itself accordingly. - A contact can also be added or removed from a group using this method - """ - pass - - def contactUpdated(self, contactView): - """ This method will be called to notify the contact list - that a contact has been updated. - The contact can be in any group drawn and his icon, - name or DP should be updated accordingly. - The position of the contact will not be changed by a call - to this function. If the position was changed, a groupUpdated - call will be made with the new order of the contacts - in the affects groups. - """ - pass - diff --git a/amsn2/gui/front_ends/mine/login.py b/amsn2/gui/front_ends/mine/login.py deleted file mode 100644 index ce7fc761..00000000 --- a/amsn2/gui/front_ends/mine/login.py +++ /dev/null @@ -1,54 +0,0 @@ - -class aMSNLoginWindow(object): - def __init__(self, amsn_core, main): - self._amsn_core = amsn_core - self.switch_to_profile(None) - - def show(self): - if self._username is not None and self._username != "": - print "Account : %s" % (self._username) - else: - self._username = raw_input("Account : ") - - if self._password is not None and self._password != "": - print "Password : ******" - else: - import getpass - self._password = getpass.getpass('Password: ') - - self.signin() - - def hide(self): - pass - - def switch_to_profile(self, profile): - self.current_profile = profile - if self.current_profile is not None: - self._username = self.current_profile.username - self._password = self.current_profile.password - - def signin(self): - self.current_profile.username = self._username - self.current_profile.email = self._username - self.current_profile.password = self._password - self._amsn_core.signinToAccount(self, self.current_profile) - - - def onConnecting(self,mess): - print mess - - def onConnected(self,mess): - print mess - - def onAuthenticating(self,mess): - print mess - - def onAuthenticated(self,mess): - print mess - - def onSynchronizing(self,mess): - print mess - - def onSynchronized(self,mess): - print mess - diff --git a/amsn2/gui/front_ends/mine/main.py b/amsn2/gui/front_ends/mine/main.py deleted file mode 100644 index 7fa50044..00000000 --- a/amsn2/gui/front_ends/mine/main.py +++ /dev/null @@ -1,22 +0,0 @@ - -from amsn2.gui import base - -class aMSNMainWindow(base.aMSNMainWindow): - def __init__(self, amsn_core): - self._amsn_core = amsn_core - - def show(self): - self._amsn_core.idlerAdd(self.__on_show) - - def hide(self): - pass - - def setTitle(self,title): - pass - - def setMenu(self,menu): - pass - - def __on_show(self): - self._amsn_core.mainWindowShown() - diff --git a/amsn2/gui/front_ends/mine/main_loop.py b/amsn2/gui/front_ends/mine/main_loop.py deleted file mode 100644 index 6c1bbede..00000000 --- a/amsn2/gui/front_ends/mine/main_loop.py +++ /dev/null @@ -1,27 +0,0 @@ - -from amsn2.gui import base -import gobject - -class aMSNMainLoop(base.aMSNMainLoop): - def __init__(self, amsn_core): - self._amsn_core = amsn_core - - def run(self): - self._mainloop = gobject.MainLoop(is_running=True) - - while self._mainloop.is_running(): - try: - self._mainloop.run() - except KeyboardInterrupt: - self.quit() - - - def idlerAdd(self, func): - gobject.idle_add(func) - - def timerAdd(self, delay, func): - gobject.timeout_add(delay, func) - - def quit(self): - self._mainloop.quit() - diff --git a/amsn2/gui/front_ends/mine/mine.py b/amsn2/gui/front_ends/mine/mine.py deleted file mode 100644 index 57e1110f..00000000 --- a/amsn2/gui/front_ends/mine/mine.py +++ /dev/null @@ -1,7 +0,0 @@ -from main_loop import * -from main import * -from contact_list import * -from login import * -from splash import * -from skins import * -from chat_window import * diff --git a/amsn2/gui/front_ends/mine/skins.py b/amsn2/gui/front_ends/mine/skins.py deleted file mode 100644 index 36474730..00000000 --- a/amsn2/gui/front_ends/mine/skins.py +++ /dev/null @@ -1,25 +0,0 @@ -import os.path - -class Skin(object): - def __init__(self, core, path): - self._path = path - pass - - def getKey(self, key, default): - pass - - def setKey(self, key, value): - pass - - - -class SkinManager(object): - def __init__(self, core): - self._core = core - self.skin = Skin(core, "skins") - - def setSkin(self, name): - self.skin = Skin(self._core, os.path.join("skins", name)) - - def listSkins(self, path): - pass diff --git a/amsn2/gui/front_ends/mine/splash.py b/amsn2/gui/front_ends/mine/splash.py deleted file mode 100644 index 8e1f03ab..00000000 --- a/amsn2/gui/front_ends/mine/splash.py +++ /dev/null @@ -1,28 +0,0 @@ - -class aMSNSplashScreen(object): - """ This interface will represent the splashscreen of the UI""" - def __init__(self, amsn_core, parent): - """Initialize the interface. You should store the reference to the core in here - as well as a reference to the window where you will show the splash screen - """ - pass - - def show(self): - """ Draw the splashscreen """ - pass - - def hide(self): - """ Hide the splashscreen """ - pass - - def setText(self, text): - """ Shows a different text inside the splashscreen """ - pass - - def setImage(self, image): - """ Set the image to show in the splashscreen. This is an ImageView object """ - - pass - - - From f0bdefdfa94ed7933423f42f8cc005520cda3107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Thu, 28 May 2009 20:15:01 +0100 Subject: [PATCH 087/147] Updated papyon --- papyon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papyon b/papyon index 894abc93..c16428f2 160000 --- a/papyon +++ b/papyon @@ -1 +1 @@ -Subproject commit 894abc938e1aa1fea8738156b5bb9392d4ac8ee8 +Subproject commit c16428f223e47d7cbda8133b7eb08010438f3811 From 6fa677310ff0caf55987e5eb91bcd02c1b34c3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Thu, 28 May 2009 21:53:33 +0100 Subject: [PATCH 088/147] Corrected initialization of presence The presence attribute was initialized from the aMSNProfile which doesn't have a presence attribute. Presence should be taken from the pymsn_profile instead. --- amsn2/core/personalinfo_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index b77d71e8..ec31a60e 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -12,7 +12,7 @@ def set_profile(self, amsn_profile): self._personalinfoview = PersonalInfoView(self._core, self._papyon_profile) # set login presence and update the gui - self._personalinfoview.presence = amsn_profile.presence + self._personalinfoview.presence = self._papyon_profile.presence """ Actions from ourselves """ def _onNickUpdated(self, new_nick): From e5796982852290f5f45cecff28415a80c5e0435a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Thu, 28 May 2009 21:57:55 +0100 Subject: [PATCH 089/147] Updated reference to papyon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Bisinger --- papyon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papyon b/papyon index c16428f2..ffcfd408 160000 --- a/papyon +++ b/papyon @@ -1 +1 @@ -Subproject commit c16428f223e47d7cbda8133b7eb08010438f3811 +Subproject commit ffcfd4082f5c6b1625e54efb131fb2dfd6720078 From 47e3855e91fa0afe20dd4cb813f67c4d057a7dd6 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 00:11:38 +0200 Subject: [PATCH 090/147] Fix the backend manager --- amsn2/__init__.py | 2 +- amsn2/backend/__init__.py | 4 ++++ amsn2/backend/backend.py | 35 +++++++++++++++++++++++++++++++ amsn2/backend/defaultbackend.py | 16 ++++++++++++++ amsn2/core/account_manager.py | 19 ++++++++--------- amsn2/core/amsn.py | 5 +++-- amsn2/gui/front_ends/efl/login.py | 2 ++ 7 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 amsn2/backend/__init__.py create mode 100644 amsn2/backend/backend.py create mode 100644 amsn2/backend/defaultbackend.py diff --git a/amsn2/__init__.py b/amsn2/__init__.py index 648b9e6c..faac68d9 100644 --- a/amsn2/__init__.py +++ b/amsn2/__init__.py @@ -1,5 +1,5 @@ - import core +import backend import gui import protocol import gui.front_ends diff --git a/amsn2/backend/__init__.py b/amsn2/backend/__init__.py new file mode 100644 index 00000000..5cc3095b --- /dev/null +++ b/amsn2/backend/__init__.py @@ -0,0 +1,4 @@ +from backend import aMSNBackendManager +__all__ = ['aMSNBackendManager', 'defaultbackend'] + +print dir() diff --git a/amsn2/backend/backend.py b/amsn2/backend/backend.py new file mode 100644 index 00000000..b99d450e --- /dev/null +++ b/amsn2/backend/backend.py @@ -0,0 +1,35 @@ +"""ElementTree independent from the available distribution""" +try: + from xml.etree.cElementTree import * +except ImportError: + try: + from cElementTree import * + except ImportError: + from elementtree.ElementTree import * + +class aMSNBackendManager(object): + def __init__(self): + self.setBackendForFunc('setPassword', 'defaultbackend') + + def setBackendForFunc(self, funcname, backendname): + try: + m = __import__(backendname, globals(), locals(), [], -1) + except ImportError: + m = __import__('defaultbackend', globals(), locals(), [], -1) + try: + f = getattr(m, funcname) + except AttributeError: + return + self.__setattr__(funcname, f) + + def getPassword(self, passwdElmt): + backendname = password.Elmt["backend"] + try: + m = __import__(backendname, globals(), locals(), [], -1) + except ImportError: + m = __import__('defaultbackend', globals(), locals(), [], -1) + + return m.getPassword(passwdElmt) + + + diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py new file mode 100644 index 00000000..be07d6fa --- /dev/null +++ b/amsn2/backend/defaultbackend.py @@ -0,0 +1,16 @@ +"""ElementTree independent from the available distribution""" +try: + from xml.etree.cElementTree import * +except ImportError: + try: + from cElementTree import * + except ImportError: + from elementtree.ElementTree import * + +def getPassword(passwdElmt): + return passwdElmt.text + +def setPassword(password, root_section): + elmt = SubElement(root_section, "password", backend='DefaultBackend') + elmt.text = password + return elmt diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index b5eb6dcd..29e67f7a 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -12,11 +12,10 @@ class aMSNAccount(object): """ #TODO: use the personnal info stuff instead of the view def __init__(self, core, accountview, account_dir): - self.view = profileview + self.view = accountview self.account_dir = account_dir - self.password_backend = "default" - self.dp_backend = "default" self.do_save = accountview.save + self.backend_manager = core.backend_manager self.lock() #TODO @@ -51,13 +50,12 @@ def save(self): statusElmt = SubElement(root_section, "status") statusElmt.text = self.view.status #password - #TODO ask the backend - passwordElmt = SubElement(root_section, "dp", - backend=self.password_backend) + passwordElmt = self.backend_manager.setPassword(self.view.password, root_section) passwordElmt.text = self.view.password #dp + #TODO ask the backend dpElmt = SubElement(root_section, "dp", - backend=self.dp_backend) + backend=self.dp_backend) #TODO #TODO: save or not, preferred_ui @@ -74,7 +72,8 @@ class aMSNAccountManager(object): """ aMSNAccountManager : The account manager that takes care of storing and retreiving all the account. """ - def __init__(self, options): + def __init__(self, core, options): + self.core = core if os.name == "posix": self._accounts_dir = os.path.join(os.environ['HOME'], ".amsn2") elif os.name == "nt": @@ -148,7 +147,7 @@ def getAvailableAccountViews(self): return [v for v in self.accountviews if not self.isAccountLocked(v)] pass - def signingToAccount(self, accountview): + def signinToAccount(self, accountview): accdir = os.path.join(self._accounts_dir, accountNameToDirName(accountview.email)) acc = aMSNAccount(self.core, accountview, accdir) @@ -160,5 +159,5 @@ def isAccountLocked(self, accountview): def accountNameToDirName(acc): #Having to do that just sucks - str = acc.lower().strip().replace("@","_at_"); + return acc.lower().strip().replace("@","_at_") diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 9084090d..1df2da09 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -20,6 +20,7 @@ from amsn2 import gui from amsn2 import protocol +from amsn2.backend import aMSNBackendManager import papyon from views import * from account_manager import * @@ -30,7 +31,6 @@ from personalinfo_manager import * from event_manager import * - class aMSNCore(object): def __init__(self, options): """ @@ -52,8 +52,9 @@ def __init__(self, options): self._main = None self.loadUI(self._options.front_end) - self._account_manager = aMSNAccountManager(options) + self._account_manager = aMSNAccountManager(self, options) self._account = None + self._backend_manager = aMSNBackendManager() self._theme_manager = aMSNThemeManager() self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) diff --git a/amsn2/gui/front_ends/efl/login.py b/amsn2/gui/front_ends/efl/login.py index 049fbd29..5dbf9f28 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -86,6 +86,8 @@ def signin(self): if not accv: accv = AccountView() accv.email = email + else: + accv = accv[0] accv.password = password self._amsn_core.signinToAccount(self, accv) From 99684080f75590c76690a91f612511598bdd9559 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 00:15:01 +0200 Subject: [PATCH 091/147] Remove .pyo files when cleaning Also add them in .gitignore --- .gitignore | 1 + clean | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8ef3dd63..04a094d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +*.pyo build/ *~ .project diff --git a/clean b/clean index f249cb7c..31c62132 100755 --- a/clean +++ b/clean @@ -1 +1,2 @@ find . -name "*.pyc" | xargs rm +find . -name "*.pyo" | xargs rm From 156496e5ea0bb32dfa58aa789de9db249ba7e15b Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 00:21:03 +0200 Subject: [PATCH 092/147] Improve clean script --- clean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clean b/clean index 31c62132..c44aa021 100755 --- a/clean +++ b/clean @@ -1,2 +1,2 @@ -find . -name "*.pyc" | xargs rm -find . -name "*.pyo" | xargs rm +find . -name "*.pyc" -exec rm '{}' \; +find . -name "*.pyo" -exec rm '{}' \; From 930ef66a3abb5eceed3adeb31e609cb7f3da275e Mon Sep 17 00:00:00 2001 From: Eduardo Date: Wed, 12 Nov 2008 21:34:32 -0600 Subject: [PATCH 093/147] [WEB] deleted the lines that change your nick...wtf with that? --- amsn2/gui/front_ends/web/chat_window.py | 38 ++++++++++++++++++++---- amsn2/gui/front_ends/web/contact_list.py | 16 ++++++++-- amsn2/gui/front_ends/web/main.py | 4 +++ amsn2/gui/front_ends/web/splash.py | 7 ++++- amsn2/gui/front_ends/web/window.py | 4 +++ amsn2/protocol/client.py | 1 - 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/amsn2/gui/front_ends/web/chat_window.py b/amsn2/gui/front_ends/web/chat_window.py index 8aafb70e..b965fe2d 100644 --- a/amsn2/gui/front_ends/web/chat_window.py +++ b/amsn2/gui/front_ends/web/chat_window.py @@ -1,39 +1,52 @@ +import md5 +import random +from amsn2.core.views import ContactView, StringView class aMSNChatWindow(object): """ This interface will represent a chat window of the UI It can have many aMSNChatWidgets""" def __init__(self, amsn_core): self._amsn_core = amsn_core + self._uid = md5.new(str(random.random())).hexdigest() + self._main = amsn_core._core._main + self._main.send("newChatWindow",[self._uid]) def addChatWidget(self, chat_widget): """ add an aMSNChatWidget to the window """ - pass + self._main.send("addChatWidget",[self._uid,chat_widget._uid]) def show(self): - pass + self._main.send("showChatWindow",[self._uid]) def hide(self): - pass + self._main.send("hideChatWindow",[self._uid]) def add(self): + print "aMSNChatWindow.add" pass def move(self): + print "aMSNChatWindow.move" pass def remove(self): + print "aMSNChatWindow.remove" pass def attach(self): + print "aMSNChatWindow.attach" pass def detach(self): + print "aMSNChatWindow.detach" pass def close(self): + print "aMSNChatWindow.close" pass def flash(self): + print "aMSNChatWindow.flash" pass """TODO: move, remove, detach, attach (shouldn't we use add ?), close, flash...""" @@ -44,13 +57,26 @@ class aMSNChatWidget(object): def __init__(self, amsn_conversation, parent): """ create the chat widget for the 'parent' window, but don't attach to it.""" - pass + self._main=parent._main + self._uid=md5.new(str(random.random())).hexdigest() + self._main.send("newChatWidget",[self._uid]) + self._main.addListener("sendMessage",self.sendMessage) + self._amsn_conversation=amsn_conversation + + def sendMessage(self,smL): + if smL[0]==self._uid: + stmess = StringView() + stmess.appendText(smL[1]) + self._amsn_conversation.sendMessage(stmess) + return True + + def onMessageReceived(self, messageview): """ Called for incoming and outgoing messages message: a MessageView of the message""" - pass + self._main.send("onMessageReceivedChatWidget",[self._uid,messageview.toStringView().toString()]) def nudge(self): - pass + self._main.send("nudgeChatWidget",[self._uid]) diff --git a/amsn2/gui/front_ends/web/contact_list.py b/amsn2/gui/front_ends/web/contact_list.py index 5f69b58a..26c13783 100644 --- a/amsn2/gui/front_ends/web/contact_list.py +++ b/amsn2/gui/front_ends/web/contact_list.py @@ -31,13 +31,14 @@ def setTitle(self, text): """ This will allow the core to change the current window's title @text : a string """ - self._main.send("setTitle",[text]) + self._main.send("setContactListTitle",[text]) pass def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView """ + self._main.send("setMenu") pass def myInfoUpdated(self, view): @@ -53,11 +54,20 @@ class aMSNContactListWidget(object): def __init__(self, amsn_core, parent): self._main = parent._main self.contacts = {} - self.groups = [] + self.groups = {} + self._main.addListener("contactClicked",self.contactClicked) clm = amsn_core._contactlist_manager clm.register(clm.CLVIEW_UPDATED, self.contactListUpdated) clm.register(clm.GROUPVIEW_UPDATED, self.groupUpdated) clm.register(clm.CONTACTVIEW_UPDATED, self.contactUpdated) + + def contactClicked(self,uidL): + uid = uidL.pop() + try: + self.contacts[uid].on_click(uid) + except Exception, inst: + print inst + return True def show(self): """ Show the contact list widget """ @@ -90,6 +100,7 @@ def groupUpdated(self, groupView): may be changed, in which case the UI should update itself accordingly. A contact can also be added or removed from a group using this method """ + self.groups[groupView.uid]=groupView self._main.send("groupUpdated",[groupView.uid,",".join(groupView.contact_ids),groupView.name.toString()]) pass @@ -103,6 +114,7 @@ def contactUpdated(self, contactView): call will be made with the new order of the contacts in the affects groups. """ + self.contacts[contactView.uid]=contactView self._main.send("contactUpdated",[contactView.uid,contactView.name.toString()]) pass diff --git a/amsn2/gui/front_ends/web/main.py b/amsn2/gui/front_ends/web/main.py index 0d88ea38..40c9b72e 100644 --- a/amsn2/gui/front_ends/web/main.py +++ b/amsn2/gui/front_ends/web/main.py @@ -22,15 +22,19 @@ def __init__(self, amsn_core): self._amsn_core.timerAdd(1,self.checkEvent) def show(self): + self.send("showMainWindow",[]) self._amsn_core.idlerAdd(self.__on_show) def hide(self): + self.send("hideMainWindow",[]) pass def setTitle(self,title): + self.send("setMainWindowTitle",[title]) pass def setMenu(self,menu): + print "aMSNMainWindow.setMenu" pass def __on_show(self): diff --git a/amsn2/gui/front_ends/web/splash.py b/amsn2/gui/front_ends/web/splash.py index 8e1f03ab..4e743f51 100644 --- a/amsn2/gui/front_ends/web/splash.py +++ b/amsn2/gui/front_ends/web/splash.py @@ -5,23 +5,28 @@ def __init__(self, amsn_core, parent): """Initialize the interface. You should store the reference to the core in here as well as a reference to the window where you will show the splash screen """ + self._amsn_core=amsn_core + self._main=parent pass def show(self): """ Draw the splashscreen """ + self._main.send("showSplashScreen",[]) pass def hide(self): """ Hide the splashscreen """ + self._main.send("hideSplashScreen",[]) pass def setText(self, text): """ Shows a different text inside the splashscreen """ + self._main.send("setTextSplashScreen",[text]) pass def setImage(self, image): """ Set the image to show in the splashscreen. This is an ImageView object """ - + self._main.send("setImageSplashScreen",["..."]) pass diff --git a/amsn2/gui/front_ends/web/window.py b/amsn2/gui/front_ends/web/window.py index 503b7426..0e558d14 100644 --- a/amsn2/gui/front_ends/web/window.py +++ b/amsn2/gui/front_ends/web/window.py @@ -6,20 +6,24 @@ def __init__(self, amsn_core): def show(self): """ This launches the window, creates it, etc..""" + print "aMSNWindow.show" pass def hide(self): """ This should hide the window""" + print "aMSNWindow.hide" pass def setTitle(self, text): """ This will allow the core to change the current window's title @text : a string """ + print "aMSNWindow.setTitle" pass def setMenu(self, menu): """ This will allow the core to change the current window's main menu @menu : a MenuView """ + print "aMSNWindow.setMenu" pass diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index d971a0c9..4b1aea98 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -48,4 +48,3 @@ def changeNick(self, nick): def changeMessage(self, message): self.profile.personal_message = message.toString() - From a9de8b2c79edae74c603d54e12b927c7303480c9 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 01:07:25 +0200 Subject: [PATCH 094/147] forgot to remove console from __init__.py --- amsn2/gui/front_ends/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/amsn2/gui/front_ends/__init__.py b/amsn2/gui/front_ends/__init__.py index 5a0519a8..a7ec5a30 100644 --- a/amsn2/gui/front_ends/__init__.py +++ b/amsn2/gui/front_ends/__init__.py @@ -1,6 +1,5 @@ import efl import cocoa -import console import curses import gtk import qt4 From d0acb7aff72211e5b7d52b797ed4330bce9e8080 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 01:09:18 +0200 Subject: [PATCH 095/147] And forgot to remove image from base/__init__.py --- amsn2/gui/base/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/amsn2/gui/base/__init__.py b/amsn2/gui/base/__init__.py index edc92a0e..37cfd527 100644 --- a/amsn2/gui/base/__init__.py +++ b/amsn2/gui/base/__init__.py @@ -6,4 +6,3 @@ from splash import * from chat_window import * from skins import * -from image import * From 4753feadd1ccd641ad366cb001ffca2f5d06acf2 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 01:18:03 +0200 Subject: [PATCH 096/147] [GTK] fix against latest api changes --- amsn2/gui/front_ends/gtk/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/image.py b/amsn2/gui/front_ends/gtk/image.py index 553c5579..4f668b1b 100644 --- a/amsn2/gui/front_ends/gtk/image.py +++ b/amsn2/gui/front_ends/gtk/image.py @@ -25,10 +25,9 @@ from amsn2.gui import base from amsn2.core.views import imageview -class Image(gtk.Image, base.aMSNImage): +class Image(gtk.Image): def __init__(self, theme_manager, view): gtk.Image.__init__(self) - base.aMSNImage.__init__(self, theme_manager, view) def _loadFromFilename(self, filename, view, index): # TODO: Implement support for emblems and other embedded images From 1dcc9e3655e65a258b59a93315a521b228f52597 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 01:19:34 +0200 Subject: [PATCH 097/147] htmltextview.py has no reason to be executable --- amsn2/gui/front_ends/gtk/htmltextview.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 amsn2/gui/front_ends/gtk/htmltextview.py diff --git a/amsn2/gui/front_ends/gtk/htmltextview.py b/amsn2/gui/front_ends/gtk/htmltextview.py old mode 100755 new mode 100644 From 4451cc9f9a56de07c853e4750f9c6d959385c975 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Fri, 29 May 2009 01:25:48 +0200 Subject: [PATCH 098/147] chmod -x --- amsn2/plugins/developers.py | 0 amsn2/plugins/gui.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 amsn2/plugins/developers.py mode change 100755 => 100644 amsn2/plugins/gui.py diff --git a/amsn2/plugins/developers.py b/amsn2/plugins/developers.py old mode 100755 new mode 100644 diff --git a/amsn2/plugins/gui.py b/amsn2/plugins/gui.py old mode 100755 new mode 100644 From ace647f9bc40f10127b57ff3b2b6ebcec9298d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Fri, 29 May 2009 01:19:23 +0100 Subject: [PATCH 099/147] curses: Implemented initial Contact list navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Bisinger --- amsn2/gui/front_ends/curses/contact_list.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index fef0e4d3..ea4afb31 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -28,6 +28,14 @@ def _on_char_cb(self, ch): import sys print >> sys.stderr, "Length is %d" % len(ch) print >> sys.stderr, "Received %s in Contact List" % ch.encode("UTF-8") + if ch == "KEY_UP": + self._clwidget.move(-1) + elif ch == "KEY_DOWN": + self._clwidget.move(1) + elif ch == "KEY_NPAGE": + self._clwidget.move(10) + elif ch == "KEY_PPAGE": + self._clwidget.move(-10) class aMSNContactListWidget(base.aMSNContactListWidget): @@ -44,6 +52,14 @@ def __init__(self, amsn_core, parent): self._thread.daemon = True self._thread.setDaemon(True) self._thread.start() + self._selected = 1 + + def move(self, num): + self._selected += num + if self._selected < 1: + self._selected = 1 + self.__repaint() + def contactListUpdated(self, clView): # Acquire the lock to do modifications @@ -122,7 +138,7 @@ def __repaint(self): cids.reverse() for c in cids: if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: - if i == y - 3: + if i == y - self._selected: self._win.bkgdset(curses.color_pair(1)) self._win.insstr(self._contacts[c]['cView'].name.toString()) self._win.bkgdset(curses.color_pair(0)) @@ -133,7 +149,7 @@ def __repaint(self): self._win.insertln() self._win.bkgdset(curses.color_pair(0)) i += 1 - if i == y - 3: + if i == y - self._selected: self._win.bkgdset(curses.color_pair(1)) self._win.insstr(self._groups[g].name.toString()) self._win.bkgdset(curses.color_pair(0)) From 54c999034b41e4b815ff4d2b5ab9d16aca4fb1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Fri, 29 May 2009 14:09:43 +0100 Subject: [PATCH 100/147] Added a warning for Qt4 when missing deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a warning when started with the Qt4 front-end and the ui_*.py files are missing. These are generated by generateFiles.sh, as specified in the README Signed-off-by: Stéphane Bisinger --- amsn2/gui/front_ends/qt4/chat_window.py | 7 ++++++- amsn2/gui/front_ends/qt4/contact_list.py | 7 ++++++- amsn2/gui/front_ends/qt4/login.py | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/amsn2/gui/front_ends/qt4/chat_window.py b/amsn2/gui/front_ends/qt4/chat_window.py index 521a0c82..72e03f00 100644 --- a/amsn2/gui/front_ends/qt4/chat_window.py +++ b/amsn2/gui/front_ends/qt4/chat_window.py @@ -23,7 +23,12 @@ from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4 import * -from ui_chatWindow import Ui_ChatWindow +try: + from ui_chatWindow import Ui_ChatWindow +except ImportError, e: + # FIXME: Should do that with logging... + print "WARNING: To use the QT4 you need to run the generateFiles.sh, check the README" + raise e from amsn2.core.views import ContactView, StringView class InputWidget(QTextEdit): diff --git a/amsn2/gui/front_ends/qt4/contact_list.py b/amsn2/gui/front_ends/qt4/contact_list.py index ada09631..70047935 100644 --- a/amsn2/gui/front_ends/qt4/contact_list.py +++ b/amsn2/gui/front_ends/qt4/contact_list.py @@ -24,7 +24,12 @@ from PyQt4.QtGui import * from contact_model import ContactModel from contact_item import ContactItem -from ui_contactlist import Ui_ContactList +try: + from ui_contactlist import Ui_ContactList +except ImportError, e: + # FIXME: Should do that with logging... + print "WARNING: To use the QT4 you need to run the generateFiles.sh, check the README" + raise e from styledwidget import StyledWidget from amsn2.core.views import StringView, ContactView from amsn2.gui import base diff --git a/amsn2/gui/front_ends/qt4/login.py b/amsn2/gui/front_ends/qt4/login.py index b1155445..008ec460 100644 --- a/amsn2/gui/front_ends/qt4/login.py +++ b/amsn2/gui/front_ends/qt4/login.py @@ -2,7 +2,12 @@ from PyQt4.QtCore import * from PyQt4.QtGui import * -from ui_login import Ui_Login +try: + from ui_login import Ui_Login +except ImportError, e: + # FIXME: Should do that with logging... + print " WARNING: To use the QT4 you need to run the generateFiles.sh, check the README" + raise e from styledwidget import StyledWidget From d4e1d57d3822f0bd870faf35abf96db88c043a89 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Sat, 30 May 2009 16:14:37 +0200 Subject: [PATCH 101/147] [core] added presence field to aMSNProfile --- amsn2/core/profile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py index e6d5e57b..15136748 100644 --- a/amsn2/core/profile.py +++ b/amsn2/core/profile.py @@ -161,6 +161,7 @@ def __init__(self, email, profiles_dir): self.alias = self.email self.password = None self.account = None + self.presence = 'online' self.directory = os.path.join(profiles_dir, self.email) self.config = aMSNProfileConfiguration(self) ###self.plugins_configs must be a dictionary like {"Plugin1_name":aMSNPluginConfiguration(self,"Plugin1_name",{"option1":123}), From 24414ad8ec6ac8eed236552105bddc149a13b279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sat, 30 May 2009 15:28:50 +0100 Subject: [PATCH 102/147] Added display of warnings when running amsn2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Bisinger --- amsn2/core/amsn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index ed4e7a11..1a41d253 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -67,9 +67,11 @@ def __init__(self, options): papyon.Presence.INVISIBLE:"hidden", papyon.Presence.OFFLINE:"offline"} + import logging if self._options.debug: - import logging logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.WARNING) def run(self): self._main.show(); From 90eace2713665b25cec281922c83b7e3172aa459 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Sat, 30 May 2009 16:49:35 +0200 Subject: [PATCH 103/147] [GTK] fix image loading --- amsn2/gui/front_ends/gtk/image.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/amsn2/gui/front_ends/gtk/image.py b/amsn2/gui/front_ends/gtk/image.py index 4f668b1b..47786e56 100644 --- a/amsn2/gui/front_ends/gtk/image.py +++ b/amsn2/gui/front_ends/gtk/image.py @@ -28,6 +28,19 @@ class Image(gtk.Image): def __init__(self, theme_manager, view): gtk.Image.__init__(self) + self._theme_manager = theme_manager + self.load(view) + + def load(self, view): + i = 0 + for (resource_type, value) in view.imgs: + try: + loadMethod = getattr(self, "_loadFrom%s" % resource_type) + except AttributeError, e: + print "From load in gtk/image.py:\n\t(resource_type, value) = (%s, %s)\n\tAttributeError: %s" % (resource_type, value, e) + else: + loadMethod(value, view, i) + i += 1 def _loadFromFilename(self, filename, view, index): # TODO: Implement support for emblems and other embedded images @@ -40,6 +53,17 @@ def _loadFromFilename(self, filename, view, index): print e print "Error loading image %s" % filename + def _loadFromTheme(self, resource_name, view, index): + # TODO: Implement support for emblems and other embedded images + if (index != 0): return + + _, filename = self._theme_manager.get_value(resource_name) + + if filename is not None: + self._loadFromFilename(filename, view, index) + else: + print 'Error loading image %s from theme' %resource_name + def to_pixbuf(self, width, height): #print 'image.py -> to_pixbuf: filename=%s' % self._filename try: From 128fc775ede2772c9c74700e1e687d56ece8717d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bisinger?= Date: Sat, 30 May 2009 16:22:55 +0100 Subject: [PATCH 104/147] Updated reference to papyon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Bisinger --- papyon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papyon b/papyon index ffcfd408..265594dc 160000 --- a/papyon +++ b/papyon @@ -1 +1 @@ -Subproject commit ffcfd4082f5c6b1625e54efb131fb2dfd6720078 +Subproject commit 265594dc867a0ab1c6f338044535bdc750c771c0 From e819cd878e6e0a40d3e14081f305ea470bf01521 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Mon, 1 Jun 2009 15:32:10 +0200 Subject: [PATCH 105/147] Remove PresenceDP --- amsn2/core/personalinfo_manager.py | 4 ---- amsn2/core/views/personalinfoview.py | 11 +---------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 653f88df..962a12fc 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -48,10 +48,6 @@ def _onDPChanged(self, new_dp): # TODO: manage msn_objects self._papyon_profile.msn_object = new_dp - def _onPresenceDPChanged(self, new_presence, new_dp): - # TODO: manage msn_objects - self._papyon_profile.presence_msn_object = presence, new_dp - def _onPSMCMChanged(self, new_psm, new_media): self._papyon_profile.personal_message_current_media = new_psm, new_media diff --git a/amsn2/core/views/personalinfoview.py b/amsn2/core/views/personalinfoview.py index 64f442d2..34ef3fed 100644 --- a/amsn2/core/views/personalinfoview.py +++ b/amsn2/core/views/personalinfoview.py @@ -7,7 +7,7 @@ def rw_property(f): class PersonalInfoView(object): def __init__(self, personalinfo_manager): self._personalinfo_manager = personalinfo_manager - + self._nickname = StringView() self._psm = StringView() self._current_media = StringView() @@ -61,15 +61,6 @@ def fset(self, presence): self._personalinfo_manager._onPresenceChanged(presence) return locals() - # custom presence - @rw_property - def presence_dp(): - def fget(self): - return (self.presence, self.dp) - def fset(self, presence, dpv): - self._personalinfo_manager._onPresenceDPChanged(presence, dpv) - return locals() - @rw_property def psm_current_media(): def fget(self): From 1cf3e1f872e03891373bfc215cf5539faa41c7ba Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Mon, 1 Jun 2009 18:50:17 +0200 Subject: [PATCH 106/147] Fix to the account manager --- amsn2/core/__init__.py | 1 - amsn2/core/account_manager.py | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/amsn2/core/__init__.py b/amsn2/core/__init__.py index a0503c34..d1a1b31f 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -1,6 +1,5 @@ from amsn import * -from profile import * from views import * from lang import * from contactlist_manager import * diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 29e67f7a..a6c0a601 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -1,7 +1,13 @@ import os -from xml.etree.ElementTree import Element, SubElement, ElementTree -import xml.parsers.expat import __builtin__ +"""ElementTree independent from the available distribution""" +try: + from xml.etree.cElementTree import * +except ImportError: + try: + from cElementTree import * + except ImportError: + from elementtree.ElementTree import * from views import AccountView from views import StringView @@ -15,7 +21,8 @@ def __init__(self, core, accountview, account_dir): self.view = accountview self.account_dir = account_dir self.do_save = accountview.save - self.backend_manager = core.backend_manager + self.backend_manager = core._backend_manager + #self.config = aMSNConfig() self.lock() #TODO @@ -55,7 +62,7 @@ def save(self): #dp #TODO ask the backend dpElmt = SubElement(root_section, "dp", - backend=self.dp_backend) + backend='DefaultBackend') #TODO #TODO: save or not, preferred_ui From 5a6dff1a580ae629b82524d8883265ad1366d8e3 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Tue, 2 Jun 2009 06:46:02 +0200 Subject: [PATCH 107/147] Fix loading accounts --- amsn2/backend/__init__.py | 2 -- amsn2/backend/backend.py | 2 +- amsn2/core/__init__.py | 1 + amsn2/core/account_manager.py | 20 ++++++++++++-------- amsn2/core/amsn.py | 2 +- amsn2/core/config.py | 0 6 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 amsn2/core/config.py diff --git a/amsn2/backend/__init__.py b/amsn2/backend/__init__.py index 5cc3095b..47a88d24 100644 --- a/amsn2/backend/__init__.py +++ b/amsn2/backend/__init__.py @@ -1,4 +1,2 @@ from backend import aMSNBackendManager __all__ = ['aMSNBackendManager', 'defaultbackend'] - -print dir() diff --git a/amsn2/backend/backend.py b/amsn2/backend/backend.py index b99d450e..95344a31 100644 --- a/amsn2/backend/backend.py +++ b/amsn2/backend/backend.py @@ -23,7 +23,7 @@ def setBackendForFunc(self, funcname, backendname): self.__setattr__(funcname, f) def getPassword(self, passwdElmt): - backendname = password.Elmt["backend"] + backendname = passwdElmt.attrib['backend'] try: m = __import__(backendname, globals(), locals(), [], -1) except ImportError: diff --git a/amsn2/core/__init__.py b/amsn2/core/__init__.py index d1a1b31f..d52ef2a2 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -2,6 +2,7 @@ from amsn import * from views import * from lang import * +from config import * from contactlist_manager import * from account_manager import * from personalinfo_manager import * diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index a6c0a601..275905e0 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -72,7 +72,7 @@ def save(self): os.makedirs(self.account_dir, 0700) accpath = os.path.join(self.account_dir, "account.xml") xml_tree = ElementTree(root_section) - xml_tree.write(accpath) + xml_tree.write(accpath, encoding='utf-8') class aMSNAccountManager(object): @@ -112,19 +112,23 @@ def reload(self): break for account_dir in account_dirs: - self.accountviews.append(self.loadAccount(account_dir)) + accv = self.loadAccount(os.path.join(self._accounts_dir, account_dir)) + if accv: + self.accountviews.append(accv) def loadAccount(self, dir): accview = None accpath = os.path.join(dir, "account.xml") - root_tree = parse(accpath) - account = root_tree.find("aMSNAccount") - if account is not None: - accview = Accountview() + accfile = file(accpath, "r") + root_tree = ElementTree(file=accfile) + accfile.close() + account = root_tree.getroot() + if account.tag == "aMSNAccount": + accview = AccountView() #email emailElt = account.find("email") - accview.email = text + accview.email = emailElt.text #nick nickElmt = account.find("nick") accview.nick.appendText(nickElmt.text) @@ -134,7 +138,7 @@ def loadAccount(self, dir): accview.status = statusElmt.text #password passwordElmt = account.find("password") - accview.password = passwordElmt.text + accview.password = self.core._backend_manager.getPassword(passwordElmt) #TODO: use backend & all #dp dpElt = account.find("dp") diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 59943d94..cc56ffc4 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -52,9 +52,9 @@ def __init__(self, options): self._main = None self.loadUI(self._options.front_end) + self._backend_manager = aMSNBackendManager() self._account_manager = aMSNAccountManager(self, options) self._account = None - self._backend_manager = aMSNBackendManager() self._theme_manager = aMSNThemeManager() self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) diff --git a/amsn2/core/config.py b/amsn2/core/config.py new file mode 100644 index 00000000..e69de29b From 858c517a38df352579e6a5b5805cd6e54357d5d2 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Wed, 3 Jun 2009 00:58:17 +0200 Subject: [PATCH 108/147] Fix + fake loading config --- amsn2/backend/backend.py | 2 ++ amsn2/backend/defaultbackend.py | 22 +++++++++++++++++++ amsn2/core/account_manager.py | 34 +++++++++++++++++++----------- amsn2/core/amsn.py | 4 +--- amsn2/core/config.py | 15 +++++++++++++ amsn2/core/contactlist_manager.py | 2 +- amsn2/core/personalinfo_manager.py | 17 +++++++++------ amsn2/core/views/accountview.py | 2 +- amsn2/protocol/client.py | 12 +++++------ amsn2/protocol/events/client.py | 2 +- amsn2/protocol/events/mailbox.py | 2 +- 11 files changed, 83 insertions(+), 31 deletions(-) diff --git a/amsn2/backend/backend.py b/amsn2/backend/backend.py index 95344a31..98ecec9a 100644 --- a/amsn2/backend/backend.py +++ b/amsn2/backend/backend.py @@ -10,6 +10,8 @@ class aMSNBackendManager(object): def __init__(self): self.setBackendForFunc('setPassword', 'defaultbackend') + self.setBackendForFunc('saveConfig', 'defaultbackend') + self.setBackendForFunc('loadConfig', 'defaultbackend') def setBackendForFunc(self, funcname, backendname): try: diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py index be07d6fa..e3ecb6f6 100644 --- a/amsn2/backend/defaultbackend.py +++ b/amsn2/backend/defaultbackend.py @@ -6,6 +6,7 @@ from cElementTree import * except ImportError: from elementtree.ElementTree import * +from amsn2.core.config import aMSNConfig def getPassword(passwdElmt): return passwdElmt.text @@ -14,3 +15,24 @@ def setPassword(password, root_section): elmt = SubElement(root_section, "password", backend='DefaultBackend') elmt.text = password return elmt + + +""" +TODO: Give an aMSNAccount as argument so that the backend can store information +on how to get/set stuff?? +""" + +def saveConfig(config, name): + #TODO + pass + +def loadConfig(name): + #TODO + if name == 'General': + c = aMSNConfig() + c._config = {"ns_server":'messenger.hotmail.com', + "ns_port":1863, + } + return c + else: + return None diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 275905e0..fc05f5bb 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -22,7 +22,7 @@ def __init__(self, core, accountview, account_dir): self.account_dir = account_dir self.do_save = accountview.save self.backend_manager = core._backend_manager - #self.config = aMSNConfig() + self.config = self.backend_manager.loadConfig('General') self.lock() #TODO @@ -53,9 +53,9 @@ def save(self): nick = self.view.nick.toString() nickElmt = SubElement(root_section, "nick") nickElmt.text = nick - #status - statusElmt = SubElement(root_section, "status") - statusElmt.text = self.view.status + #presence + presenceElmt = SubElement(root_section, "presence") + presenceElmt.text = self.view.presence #password passwordElmt = self.backend_manager.setPassword(self.view.password, root_section) passwordElmt.text = self.view.password @@ -98,7 +98,8 @@ def __init__(self, core, options): if options.account is not None: pv = [p for p in self.accountviews if p.email == options.account] if pv: - self.accountviews.remove(pv[0]) + pv = pv[0] + self.accountviews.remove(pv) else: pv = AccountView() pv.email = options.account @@ -127,21 +128,30 @@ def loadAccount(self, dir): if account.tag == "aMSNAccount": accview = AccountView() #email - emailElt = account.find("email") - accview.email = emailElt.text + emailElmt = account.find("email") + if not emailElmt: + return None + accview.email = emailElmt.text #nick nickElmt = account.find("nick") - accview.nick.appendText(nickElmt.text) + if not nickElmt: + return None + if nickElmt.text: + accview.nick.appendText(nickElmt.text) #TODO: parse... - #status - statusElmt = account.find("status") - accview.status = statusElmt.text + #presence + presenceElmt = account.find("presence") + if not presenceElmt: + return None + accview.presence = presenceElmt.text #password passwordElmt = account.find("password") + if not passwordElmt: + return None accview.password = self.core._backend_manager.getPassword(passwordElmt) #TODO: use backend & all #dp - dpElt = account.find("dp") + dpElmt = account.find("dp") #TODO #TODO: preferred_ui ? diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index cc56ffc4..799477ee 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -126,7 +126,7 @@ def signinToAccount(self, login_window, accountview): self._account = self._account_manager.signinToAccount(accountview) self._account.login = login_window self._account.client = protocol.Client(self, self._account) - self._account.client.connect() + self._account.client.connect(accountview.email, accountview.password) def connectionStateChanged(self, account, state): @@ -143,8 +143,6 @@ def connectionStateChanged(self, account, state): if state in status_str: account.login.onConnecting((state + 1)/ 7., status_str[state]) elif state == papyon.event.ClientState.OPEN: - account.login.onConnecting((state + 1)/ 7., status_str[state]) - elif state == pymsn.event.ClientState.OPEN: clwin = self._gui.gui.aMSNContactListWindow(self, self._main) clwin.account = account account.clwin = clwin diff --git a/amsn2/core/config.py b/amsn2/core/config.py index e69de29b..cc67790e 100644 --- a/amsn2/core/config.py +++ b/amsn2/core/config.py @@ -0,0 +1,15 @@ + + + +class aMSNConfig: + def __init__(self): + self._config = {} + + def getKey(self, key, default = None): + try: + return self._config[key] + except KeyError: + return default + + def setKey(self, key, value): + self._config[key] = value diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index e3a78bc5..34c39b8d 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -34,7 +34,7 @@ def onContactDPChanged(self, papyon_contact): c.dp.load("Theme", "dp_nopic") if (papyon_contact.presence is not papyon.Presence.OFFLINE and papyon_contact.msn_object): - self._core._profile.client._msn_object_store.request(papyon_contact.msn_object, + self._core._account.client._msn_object_store.request(papyon_contact.msn_object, (self.onDPdownloaded, papyon_contact.id)) diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 370ef003..e4308350 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -14,23 +14,28 @@ def setAccount(self, amsn_account): # could be overriden by the one set in the saved account # TODO: add setting display picture and saved personal message strv = StringView() - if amsn_account.username == amsn_account.email: - strv.appendText(self._papyon_profile.display_name) + nick = amsn_account.view.nick.toString() + if nick and nick != amsn_account.view.email: + strv.appendText(nick) else: - strv.appendText(amsn_account.username) + strv.appendText(self._papyon_profile.display_name) self._personalinfoview.nick = strv # set login presence, from this moment the client appears to the others - self._personalinfoview.presence = amsn_account.presence + self._personalinfoview.presence = amsn_account.view.presence """ Actions from ourselves """ def _onNickChanged(self, new_nick): # TODO: parsing - self._papyon_profile.display_name = new_nick.toString() + str = new_nick.toString() + if str: + self._papyon_profile.display_name = str def _onPSMChanged(self, new_psm): # TODO: parsing - self._papyon_profile.personal_message = new_psm.toString() + str = new_psm.toString() + if str: + self._papyon_profile.personal_message = str def _onPresenceChanged(self, new_presence): # TODO: manage custom presence diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py index 8a8f2194..5309f4f5 100644 --- a/amsn2/core/views/accountview.py +++ b/amsn2/core/views/accountview.py @@ -8,7 +8,7 @@ def __init__(self): self.email = None self.password = None self.nick = StringView() - self.status = papyon.Presence.ONLINE + self.presence = papyon.Presence.ONLINE self.dp = ImageView() self.save = False diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 35587973..dba35587 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -29,11 +29,11 @@ from events.mailbox import * class Client(papyon.Client): - def __init__(self, amsn_core, profile): - self._amsn_profile = profile + def __init__(self, amsn_core, account): + self._amsn_account = account self._amsn_core = amsn_core - server = (self._amsn_profile.getConfigKey("ns_server", "messenger.hotmail.com"), - self._amsn_profile.getConfigKey("ns_port", 1863)) + server = (self._amsn_account.config.getKey("ns_server", "messenger.hotmail.com"), + self._amsn_account.config.getKey("ns_port", 1863)) papyon.Client.__init__(self, server) self._client_events_handler = ClientEvents(self, self._amsn_core) @@ -44,8 +44,8 @@ def __init__(self, amsn_core, profile): self._profile_events_handler = ProfileEvents(self, self._amsn_core._personalinfo_manager) self._mailbox_events_handler = MailboxEvents(self, self._amsn_core) - def connect(self): - self.login(self._amsn_profile.email, self._amsn_profile.password) + def connect(self, email, password): + self.login(email, password) def changeNick(self, nick): self.profile.display_name = nick.toString() diff --git a/amsn2/protocol/events/client.py b/amsn2/protocol/events/client.py index 86b88c1f..2db78d38 100644 --- a/amsn2/protocol/events/client.py +++ b/amsn2/protocol/events/client.py @@ -9,7 +9,7 @@ def __init__(self, client, amsn_core): papyon.event.ClientEventInterface.__init__(self, client) def on_client_state_changed(self, state): - self._amsn_core.connectionStateChanged(self._client._amsn_profile, state) + self._amsn_core.connectionStateChanged(self._client._amsn_account, state) def on_client_error(self, error_type, error): print "ERROR :", error_type, " ->", error diff --git a/amsn2/protocol/events/mailbox.py b/amsn2/protocol/events/mailbox.py index 6dda5e44..b6851a1f 100644 --- a/amsn2/protocol/events/mailbox.py +++ b/amsn2/protocol/events/mailbox.py @@ -7,7 +7,7 @@ def __init__(self, client, amsn_core): self._amsn_core = amsn_core papyon.event.MailboxEventInterface.__init__(self, client) - def on_mailbox_unread_mail_count_changed(self, unread_mail_count, + def on_mailbox_unread_mail_count_changed(self, unread_mail_count, initial=False): """The number of unread mail messages""" pass From 31c7230f05aeac95d6079fdea41af625cbf9ec1e Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Thu, 4 Jun 2009 23:07:58 +0200 Subject: [PATCH 109/147] Update papyon submodule to its master --- papyon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papyon b/papyon index 265594dc..5059b889 160000 --- a/papyon +++ b/papyon @@ -1 +1 @@ -Subproject commit 265594dc867a0ab1c6f338044535bdc750c771c0 +Subproject commit 5059b889b35c5ffb8eeab7d123d0f8e3b98645a1 From 225c3ec9d8b2e0b4b8e1fdcc6f358792884d68b7 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 5 Jun 2009 01:02:11 +0200 Subject: [PATCH 110/147] fix a nonsense if when updating DP --- amsn2/core/contactlist_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index e3a78bc5..62b072f0 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -30,7 +30,7 @@ def onContactDPChanged(self, papyon_contact): c = self.getContact(papyon_contact.id, papyon_contact) if ("Theme", "dp_nopic") in c.dp.imgs: c.dp.load("Theme", "dp_loading") - elif papyon_contact is None: + elif papyon_contact.msn_object is None: c.dp.load("Theme", "dp_nopic") if (papyon_contact.presence is not papyon.Presence.OFFLINE and papyon_contact.msn_object): From 63330a9d8ff6ffc026b3078181d710c7d3145822 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 5 Jun 2009 01:10:15 +0200 Subject: [PATCH 111/147] [GTK] finally see the buddy DPs.. --- amsn2/gui/front_ends/gtk/contact_list.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 663c9ef9..f1911fa5 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -361,10 +361,8 @@ def contactUpdated(self, contactview): citer = self.__search_by_id(contactview.uid) if citer is None: return - # TODO: Verify if DP exist - #img = Image(self._cwin._theme_manager, contactview.dp) - #dp = img.to_pixbuf(28, 28) - img = Image(self._cwin._theme_manager, contactview.icon) + img = Image(self._cwin._theme_manager, contactview.dp) + #img = Image(self._cwin._theme_manager, contactview.icon) dp = img.to_pixbuf(28, 28) self._model.set_value(citer, 0, dp) From e3aaaa449c21c4afb9c3e3cc1688c937698dde3a Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Fri, 5 Jun 2009 01:27:19 +0200 Subject: [PATCH 112/147] Fix a bug when a buddy removes his DP --- amsn2/core/contactlist_manager.py | 5 +++++ amsn2/protocol/events/contact.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index 62b072f0..490759b5 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -32,6 +32,11 @@ def onContactDPChanged(self, papyon_contact): c.dp.load("Theme", "dp_loading") elif papyon_contact.msn_object is None: c.dp.load("Theme", "dp_nopic") + self._em.emit(self._em.events.AMSNCONTACT_UPDATED, c) + cv = ContactView(self._core, c) + self._em.emit(self._em.events.CONTACTVIEW_UPDATED, cv) + return + if (papyon_contact.presence is not papyon.Presence.OFFLINE and papyon_contact.msn_object): self._core._profile.client._msn_object_store.request(papyon_contact.msn_object, diff --git a/amsn2/protocol/events/contact.py b/amsn2/protocol/events/contact.py index f17dfe19..21a2ab36 100644 --- a/amsn2/protocol/events/contact.py +++ b/amsn2/protocol/events/contact.py @@ -21,6 +21,12 @@ def on_contact_current_media_changed(self, contact): self._contact_manager.onContactChanged(contact) def on_contact_msn_object_changed(self, contact): + # if the msnobject has been removed, just remove the buddy's DP + print 'hhhhh %s' % contact.msn_object + if contact.msn_object is None: + self._contact_manager.onContactDPChanged(contact) + return + # TODO: filter objects if contact.msn_object._type is papyon.p2p.MSNObjectType.DISPLAY_PICTURE: self._contact_manager.onContactDPChanged(contact) From 9c5843a80dfd5276b3cecbd8bb87661424ee9804 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sat, 6 Jun 2009 12:01:52 +0200 Subject: [PATCH 113/147] generateFiles.sh checks if pyuic4 is installed --- amsn2/gui/front_ends/qt4/generateFiles.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/amsn2/gui/front_ends/qt4/generateFiles.sh b/amsn2/gui/front_ends/qt4/generateFiles.sh index bd10a7d5..0303db67 100755 --- a/amsn2/gui/front_ends/qt4/generateFiles.sh +++ b/amsn2/gui/front_ends/qt4/generateFiles.sh @@ -2,6 +2,12 @@ #pyrcc4 -o src/faigaresource_rc.py ui/faigaresource.qrc +which pyuic4>/dev/null +if [ $? != 0 ]; then + echo >&2 "This script requires pyuic4 installed." + exit 255 +fi + pyuic4 -o ui_login.py login.ui pyuic4 -o ui_contactlist.py contactlist.ui pyuic4 -o ui_chatWindow.py chatWindow.ui From 792c00aa7ecbc7f0f80aa2df6c706f2ed6b6dd68 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sat, 6 Jun 2009 13:43:15 +0200 Subject: [PATCH 114/147] Use "in" operator where possible and fixed parameters order. --- amsn2/protocol/events/conversation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/amsn2/protocol/events/conversation.py b/amsn2/protocol/events/conversation.py index 0a487cc2..563a7633 100644 --- a/amsn2/protocol/events/conversation.py +++ b/amsn2/protocol/events/conversation.py @@ -50,10 +50,10 @@ def on_conversation_message_received(self, sender, message): them into the stringview """ #TODO: have Smiley object in the stringView to keep image+trigger strv = StringView() - if message.msn_objects.keys().__contains__(message.content) == True: + if message.content in message.msn_objects.keys(): print "single emoticon" strv.appendImage(message.msn_objects[message.content]._location) - self._amsn_conversation.onMessageReceived(sender.id, strv) + self._amsn_conversation.onMessageReceived(strv, sender.id) return strlist = [message.content] @@ -70,7 +70,7 @@ def on_conversation_message_received(self, sender, message): strlist = newlist for str in strlist: - if message.msn_objects.keys().__contains__(str) == True: + if str in message.msn_objects.keys(): strv.appendImage(str) else: strv.appendText(str) From a482838262c3021aa5939d0a450d3c35f0893ee6 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sat, 6 Jun 2009 14:11:27 +0200 Subject: [PATCH 115/147] Now you can see your own nickname when you send a message. --- amsn2/core/conversation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index ea8c14ea..b06fb280 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -71,8 +71,7 @@ def onMessageReceived(self, message, sender_uid=None, formatting=None): #TODO: messageView mv = MessageView() if sender_uid is None: - #TODO - mv.sender.appendText("/me") + mv.sender.appendStringView(self._core._personalinfo_manager._personalinfoview.nick) else: c = self._core._contactlist_manager.getContact(sender_uid) mv.sender_icon = c.icon From 5c3bff7fdaea98b4104103262aca77e442d9ef8a Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sat, 6 Jun 2009 15:11:52 +0200 Subject: [PATCH 116/147] Removed toString() method from StringView. Use str() instead. Also changed all references from toString() to str(). --- amsn2/core/conversation.py | 2 +- amsn2/core/personalinfo_manager.py | 4 +-- amsn2/core/views/stringview.py | 13 ++++------ amsn2/gui/front_ends/curses/contact_list.py | 4 +-- amsn2/gui/front_ends/efl/chat_window.py | 2 +- amsn2/gui/front_ends/efl/contact_list.py | 4 +-- amsn2/gui/front_ends/gtk/chat_window.py | 2 +- amsn2/gui/front_ends/gtk/contact_list.py | 8 +++--- amsn2/gui/front_ends/qt4/chat_window.py | 10 ++++---- amsn2/gui/front_ends/qt4/contact_list.py | 28 ++++++++++----------- amsn2/gui/front_ends/web/chat_window.py | 2 +- amsn2/gui/front_ends/web/contact_list.py | 6 ++--- amsn2/protocol/client.py | 4 +-- 13 files changed, 43 insertions(+), 46 deletions(-) diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index b06fb280..95e393ee 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -89,7 +89,7 @@ def sendMessage(self, msg, formatting=None): # for the moment, no formatting, no smiley substitution... (TODO) # peacey: Added formatting of styles self.onMessageReceived(msg, formatting=formatting) - message = papyon.ConversationMessage(msg.toString(), formatting) + message = papyon.ConversationMessage(str(msg), formatting) self._conv.send_text_message(message) def sendNudge(self): diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 962a12fc..cbe717ad 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -27,11 +27,11 @@ def set_profile_connected(self, amsn_profile): """ Actions from ourselves """ def _onNickChanged(self, new_nick): # TODO: parsing - self._papyon_profile.display_name = new_nick.toString() + self._papyon_profile.display_name = str(new_nick) def _onPSMChanged(self, new_psm): # TODO: parsing - self._papyon_profile.personal_message = new_psm.toString() + self._papyon_profile.personal_message = str(new_psm) def _onPresenceChanged(self, new_presence): # TODO: manage custom presence diff --git a/amsn2/core/views/stringview.py b/amsn2/core/views/stringview.py index 05ef2d8b..9a522d20 100644 --- a/amsn2/core/views/stringview.py +++ b/amsn2/core/views/stringview.py @@ -140,13 +140,6 @@ def appendElementsFromHtml(self, string): # TODO: Not so easy... maybe there is a python HTML parser we can use? pass - def toString(self): - out = "" - for x in self._elements: - if x.getType() == StringView.TEXT_ELEMENT: - out += x.getValue() - return out - def toHtmlString(self): """ This method returns a formatted html string with all the data in the stringview """ @@ -180,7 +173,11 @@ def toHtmlString(self): return out def __str__(self): - return self.toString() + out = "" + for x in self._elements: + if x.getType() == StringView.TEXT_ELEMENT: + out += x.getValue() + return out def __repr__(self): out = "{" diff --git a/amsn2/gui/front_ends/curses/contact_list.py b/amsn2/gui/front_ends/curses/contact_list.py index ea4afb31..1827b8d1 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -140,7 +140,7 @@ def __repaint(self): if self._contacts.has_key(c) and self._contacts[c]['cView'] is not None: if i == y - self._selected: self._win.bkgdset(curses.color_pair(1)) - self._win.insstr(self._contacts[c]['cView'].name.toString()) + self._win.insstr(str(self._contacts[c]['cView'].name)) self._win.bkgdset(curses.color_pair(0)) self._win.insch(' ') self._win.insch(curses.ACS_HLINE) @@ -151,7 +151,7 @@ def __repaint(self): i += 1 if i == y - self._selected: self._win.bkgdset(curses.color_pair(1)) - self._win.insstr(self._groups[g].name.toString()) + self._win.insstr(str(self._groups[g].name)) self._win.bkgdset(curses.color_pair(0)) self._win.insch(' ') self._win.insch(curses.ACS_LLCORNER) diff --git a/amsn2/gui/front_ends/efl/chat_window.py b/amsn2/gui/front_ends/efl/chat_window.py index 8a7069b0..34f0b3b7 100644 --- a/amsn2/gui/front_ends/efl/chat_window.py +++ b/amsn2/gui/front_ends/efl/chat_window.py @@ -89,7 +89,7 @@ def onUserTyping(self, contact): print "%s is typing" % (contact,) def onMessageReceived(self, messageview): - self.__outputAppendMsg(messageview.toStringView().toString()) + self.__outputAppendMsg(str(messageview.toStringView())) def nudge(self): #TODO diff --git a/amsn2/gui/front_ends/efl/contact_list.py b/amsn2/gui/front_ends/efl/contact_list.py index ae3f934a..97096bfa 100644 --- a/amsn2/gui/front_ends/efl/contact_list.py +++ b/amsn2/gui/front_ends/efl/contact_list.py @@ -102,7 +102,7 @@ def contact_updated(self, contactview): c = self.contacts_dict[contactview.uid] except KeyError: return - c.part_text_set("contact_data", contactview.name.toString()) + c.part_text_set("contact_data", str(contactview.name)) if DP_IN_CL: # add the dp @@ -238,7 +238,7 @@ def num_contacts(self): return self.contact_holder.num_contacts() def group_updated(self, groupview): - self.part_text_set("group_name", groupview.name.toString()) + self.part_text_set("group_name", str(groupview.name)) self.contact_holder.groupViewUpdated(groupview) # Private methods diff --git a/amsn2/gui/front_ends/gtk/chat_window.py b/amsn2/gui/front_ends/gtk/chat_window.py index ad5accb8..63c56040 100644 --- a/amsn2/gui/front_ends/gtk/chat_window.py +++ b/amsn2/gui/front_ends/gtk/chat_window.py @@ -299,7 +299,7 @@ def onMessageReceived(self, messageview, formatting=None): nick, msg = text.split('\n', 1) nick = str(nick.replace('\n', '
')) msg = str(msg.replace('\n', '
')) - sender = messageview.sender.toString() + sender = str(messageview.sender) # peacey: Check formatting of styles and perform the required changes if formatting: diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 663c9ef9..9428a966 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -193,10 +193,10 @@ def myInfoUpdated(self, view): # TODO: image, ... self._myview = view nk = view.nick - self.nicklabel.set_markup(nk.toString()) + self.nicklabel.set_markup(str(nk)) psm = view.psm cm = view.current_media - message = psm.toString()+' '+cm.toString() + message = str(psm)+' '+str(cm) self.psmlabel.set_markup(''+message+'') self.status.set_active(self.status_values[view.presence]) @@ -335,7 +335,7 @@ def groupUpdated(self, groupview): giter = self.__search_by_id(groupview.uid) self._model.set_value(giter, 1, groupview) self._model.set_value(giter, 2, '%s' % common.escape_pango( - groupview.name.toString())) + str(groupview.name))) try: cuids = self.contacts[groupview.uid] @@ -370,7 +370,7 @@ def contactUpdated(self, contactview): self._model.set_value(citer, 0, dp) self._model.set_value(citer, 1, contactview) self._model.set_value(citer, 2, common.escape_pango( - contactview.name.toString())) + str(contactview.name))) del dp gc.collect() diff --git a/amsn2/gui/front_ends/qt4/chat_window.py b/amsn2/gui/front_ends/qt4/chat_window.py index 72e03f00..c49a66da 100644 --- a/amsn2/gui/front_ends/qt4/chat_window.py +++ b/amsn2/gui/front_ends/qt4/chat_window.py @@ -143,24 +143,24 @@ def appendImageAtCursor(self, image): self.ui.inputWidget.textCursor().insertHtml(QString("")) def onUserJoined(self, contact): - self.ui.textEdit.append(unicode(""+QString.fromUtf8(contact.name.toString())+" "+self.tr("has joined the conversation")+(""))) + self.ui.textEdit.append(unicode(""+QString.fromUtf8(str(contact.name))+" "+self.tr("has joined the conversation")+(""))) pass def onUserLeft(self, contact): - self.ui.textEdit.append(unicode(""+QString.fromUtf8(contact.name.toString())+" "+self.tr("has left the conversation")+(""))) + self.ui.textEdit.append(unicode(""+QString.fromUtf8(str(contact.name))+" "+self.tr("has left the conversation")+(""))) pass def onUserTyping(self, contact): - self._statusBar.showMessage(unicode(QString.fromUtf8(contact.name.toString()) + " is typing"), 7000) + self._statusBar.showMessage(unicode(QString.fromUtf8(str(contact.name)) + " is typing"), 7000) def onMessageReceived(self, sender, message): print "Ding!" - self.ui.textEdit.append(unicode(""+QString.fromUtf8(sender.name.toString())+" "+self.tr("writes:")+(""))) + self.ui.textEdit.append(unicode(""+QString.fromUtf8(str(sender.name))+" "+self.tr("writes:")+(""))) self.ui.textEdit.append(unicode(message.toHtmlString())) pass def onNudgeReceived(self, sender): - self.ui.textEdit.append(unicode(""+sender.name.toString()+" "+self.tr("sent you a nudge!")+(""))) + self.ui.textEdit.append(unicode(""+str(sender.name)+" "+self.tr("sent you a nudge!")+(""))) pass diff --git a/amsn2/gui/front_ends/qt4/contact_list.py b/amsn2/gui/front_ends/qt4/contact_list.py index 70047935..24c3b5fb 100644 --- a/amsn2/gui/front_ends/qt4/contact_list.py +++ b/amsn2/gui/front_ends/qt4/contact_list.py @@ -98,12 +98,12 @@ def contactListUpdated(self, view): pass def contactUpdated(self, contact): - print unicode("Contact Updated: " + QString.fromUtf8(contact.name.toString())) + print unicode("Contact Updated: " + QString.fromUtf8(str(contact.name))) l = self._model.findItems("*", Qt.MatchWildcard | Qt.MatchRecursive) for itm in l: - if itm.data(40).toString() == contact.uid: - itm.setText(QString.fromUtf8(contact.name.toString())) + if str(itm.data(40)) == contact.uid: + itm.setText(QString.fromUtf8(str(contact.name))) break def groupUpdated(self, group): @@ -112,22 +112,22 @@ def groupUpdated(self, group): for itm in l: - if itm.data(40).toString() == group.uid: + if str(itm.data(40)) == group.uid: - itm.setText(QString.fromUtf8(group.name.toString())) + itm.setText(QString.fromUtf8(str(group.name))) for contact in group.contacts: for ent in l: - if ent.data(40).toString() == contact.uid: - itm.setText(QString.fromUtf8(contact.name.toString())) + if str(ent.data(40)) == contact.uid: + itm.setText(QString.fromUtf8(str(contact.name))) continue - print " * " + contact.name.toString() + print " * " + contact.name contactItem = ContactItem() - contactItem.setContactName(QString.fromUtf8(contact.name.toString())) + contactItem.setContactName(QString.fromUtf8(str(contact.name))) contactItem.setData(QVariant(contact.uid), 40) itm.appendRow(contactItem) @@ -159,7 +159,7 @@ def setContactCallback(self, cb): self.__slotContactCallback) def __slotContactCallback(self, index): - data = str(index.data(40).toString()) + data = str(str(index.data(40))) if self._callback is not None: self._callback(self._contactDict[data]) @@ -168,22 +168,22 @@ def setContactContextMenu(self, cb): pass def groupAdded(self, group): - print group.name.toString() + print group.name pi = self._model.invisibleRootItem(); # Adding Group Item groupItem = QStandardItem() - groupItem.setText(QString.fromUtf8(group.name.toString())) + groupItem.setText(QString.fromUtf8(str(group.name))) groupItem.setData(QVariant(group.uid), 40) pi.appendRow(groupItem) for contact in group.contacts: - print " * " + contact.name.toString() + print " * " + contact.name contactItem = ContactItem() - contactItem.setContactName(QString.fromUtf8(contact.name.toString())) + contactItem.setContactName(QString.fromUtf8(str(contact.name))) contactItem.setData(QVariant(contact.uid), 40) groupItem.appendRow(contactItem) diff --git a/amsn2/gui/front_ends/web/chat_window.py b/amsn2/gui/front_ends/web/chat_window.py index b965fe2d..98b1fe66 100644 --- a/amsn2/gui/front_ends/web/chat_window.py +++ b/amsn2/gui/front_ends/web/chat_window.py @@ -75,7 +75,7 @@ def sendMessage(self,smL): def onMessageReceived(self, messageview): """ Called for incoming and outgoing messages message: a MessageView of the message""" - self._main.send("onMessageReceivedChatWidget",[self._uid,messageview.toStringView().toString()]) + self._main.send("onMessageReceivedChatWidget", [self._uid, str(messageview.toStringView())]) def nudge(self): self._main.send("nudgeChatWidget",[self._uid]) diff --git a/amsn2/gui/front_ends/web/contact_list.py b/amsn2/gui/front_ends/web/contact_list.py index 26c13783..5628864c 100644 --- a/amsn2/gui/front_ends/web/contact_list.py +++ b/amsn2/gui/front_ends/web/contact_list.py @@ -46,7 +46,7 @@ def myInfoUpdated(self, view): ourself, such as DP, nick, psm, the current media being played,... @view: the contactView of the ourself (contains DP, nick, psm, currentMedia,...)""" - self._main.send("myInfoUpdated",[view.name.toString()]) + self._main.send("myInfoUpdated",[str(view.name)]) pass class aMSNContactListWidget(object): @@ -101,7 +101,7 @@ def groupUpdated(self, groupView): A contact can also be added or removed from a group using this method """ self.groups[groupView.uid]=groupView - self._main.send("groupUpdated",[groupView.uid,",".join(groupView.contact_ids),groupView.name.toString()]) + self._main.send("groupUpdated",[groupView.uid,",".join(groupView.contact_ids),str(groupView.name)]) pass def contactUpdated(self, contactView): @@ -115,6 +115,6 @@ def contactUpdated(self, contactView): in the affects groups. """ self.contacts[contactView.uid]=contactView - self._main.send("contactUpdated",[contactView.uid,contactView.name.toString()]) + self._main.send("contactUpdated", [contactView.uid, str(contactView.name)]) pass diff --git a/amsn2/protocol/client.py b/amsn2/protocol/client.py index 35587973..c529081f 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -48,7 +48,7 @@ def connect(self): self.login(self._amsn_profile.email, self._amsn_profile.password) def changeNick(self, nick): - self.profile.display_name = nick.toString() + self.profile.display_name = str(nick) def changeMessage(self, message): - self.profile.personal_message = message.toString() + self.profile.personal_message = str(message) From 74d252b57a833e7dc3f7056a225dcb4299696193 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sat, 6 Jun 2009 18:35:02 +0200 Subject: [PATCH 117/147] Added constants to ImageView class --- amsn2/core/views/imageview.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/amsn2/core/views/imageview.py b/amsn2/core/views/imageview.py index a536fad7..d05030f0 100644 --- a/amsn2/core/views/imageview.py +++ b/amsn2/core/views/imageview.py @@ -5,6 +5,10 @@ class ImageView(object): - Theme - None """ + + FILENAME = "Filename" + THEME = "Theme" + def __init__(self, resource_type=None, value=None): self.imgs = [] if resource_type is not None and value is not None: From 714545d7531138361dd89e015e0771a7f3238a47 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sat, 6 Jun 2009 18:36:32 +0200 Subject: [PATCH 118/147] [GTK] Now you can change your psm. --- amsn2/gui/front_ends/gtk/contact_list.py | 36 ++++++++++++++++++++---- amsn2/protocol/events/profile.py | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 663c9ef9..bccb8750 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -79,9 +79,8 @@ def __create_controls(self): self.btnNickname.set_alignment(0,0) self.btnNickname.connect("clicked",self.__on_btnNicknameClicked) - self.psm = gtk.Entry() - self.psmlabel = gtk.Label() + self.psmlabel.set_alignment(0, 0) self.psmlabel.set_use_markup(True) self.psmlabel.set_ellipsize(pango.ELLIPSIZE_END) self.psmlabel.set_markup('<Personal message>') @@ -90,6 +89,7 @@ def __create_controls(self): self.btnPsm.add(self.psmlabel) self.btnPsm.set_relief(gtk.RELIEF_NONE) self.btnPsm.set_alignment(0,0) + self.btnPsm.connect("clicked", self.__on_btnPsmClicked) # status list self.status_values = {} @@ -136,7 +136,6 @@ def __create_box(self): boxPsm = gtk.HBox(False, 0) boxPsm.pack_start(self.btnPsm, True, True) - boxPsm.pack_start(self.psm, True, True) headerRight = gtk.VBox(False, 0) headerRight.pack_start(boxNick, False, False) @@ -160,9 +159,6 @@ def __create_box(self): self.pack_start(bottom, False, False, 2) def __setup_window(self): - self.psm.hide() - self.btnPsm.show() - _, filename = self._theme_manager.get_dp('dp_nopic') pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, 64) self.display.set_from_pixbuf(pixbuf) @@ -219,9 +215,11 @@ def __switchToNickInput(self): #label = self.btnNickname.get_child() self.btnNickname.get_child().destroy() entry = gtk.Entry() + entry.set_text(str(self._myview.nick)) self.btnNickname.add(entry) entry.show() entry.connect("activate", self.__switchFromNickInput) + #TODO: If user press ESC then destroy gtk.Entry def __switchFromNickInput(self, source): """ When in the editing state of nickname, change back to the uneditable @@ -235,6 +233,32 @@ def __switchFromNickInput(self, source): self.btnNickname.add(entry) entry.show() + def __on_btnPsmClicked(self, source): + self.__switchToPsmInput() + + def __switchToPsmInput(self): + """ Switches the psm button into a text area for editing of the psm.""" + + self.btnPsm.get_child().destroy() + entry = gtk.Entry() + entry.set_text(str(self._myview.psm)) + self.btnPsm.add(entry) + entry.show() + entry.connect("activate", self.__switchFromPsmInput) + #TODO: If user press ESC then destroy gtk.Entry + + def __switchFromPsmInput(self, source): + """ When in the editing state of psm, change back to the uneditable + label state. + """ + strv = StringView() + strv.appendText(source.get_text()) + self._myview.psm = strv + self.btnPsm.get_child().destroy() + entry = self.psmlabel + self.btnPsm.add(entry) + entry.show() + class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): """Constructor""" diff --git a/amsn2/protocol/events/profile.py b/amsn2/protocol/events/profile.py index e47c79ce..bc08ccdc 100644 --- a/amsn2/protocol/events/profile.py +++ b/amsn2/protocol/events/profile.py @@ -14,7 +14,7 @@ def on_profile_display_name_changed(self): self._personalinfo_manager.onNickUpdated(self._client.profile.display_name) def on_profile_personal_message_changed(self): - self._personalinfo_manager.onPMUpdated(self._client.profile.personal_message) + self._personalinfo_manager.onPSMUpdated(self._client.profile.personal_message) def on_profile_current_media_changed(self): self._personalinfo_manager.onCMUpdated(self._client.profile.current_media) From d3d6b657e42892187d06a6be7e11ba5812fe68bf Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 7 Jun 2009 17:20:21 +0200 Subject: [PATCH 119/147] save/load config --- amsn2/backend/defaultbackend.py | 37 +++++++++++++++++++++++++-------- amsn2/core/account_manager.py | 9 ++++---- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py index e3ecb6f6..e6fd5e82 100644 --- a/amsn2/backend/defaultbackend.py +++ b/amsn2/backend/defaultbackend.py @@ -1,3 +1,4 @@ +import os """ElementTree independent from the available distribution""" try: from xml.etree.cElementTree import * @@ -17,22 +18,40 @@ def setPassword(password, root_section): return elmt -""" -TODO: Give an aMSNAccount as argument so that the backend can store information -on how to get/set stuff?? -""" +def saveConfig(account, config, name): + #TODO: improve + if name == 'General': + root_section = Element("aMSNConfig") + for e in config._config: + val = config._config[e] + elmt = SubElement(root_section, "entry", + type=type(val).__name__, + name=str(e)) + elmt.text = str(val) -def saveConfig(config, name): - #TODO - pass + accpath = os.path.join(account.account_dir, "config.xml") + xml_tree = ElementTree(root_section) + xml_tree.write(accpath, encoding='utf-8') -def loadConfig(name): - #TODO +def loadConfig(account, name): if name == 'General': c = aMSNConfig() c._config = {"ns_server":'messenger.hotmail.com', "ns_port":1863, } + configpath = os.path.join(account.account_dir, "config.xml") + configfile = file(configpath, "r") + root_tree = ElementTree(file=configfile) + configfile.close() + config = root_tree.getroot() + if config.tag == "aMSNConfig": + lst = config.findall("entry") + for elmt in lst: + if elmt.attrib['type'] == 'int': + c.setKey(elmt.attrib['name'], int(elmt.text)) + else: + c.setKey(elmt.attrib['name'], elmt.text) + print repr(c._config) return c else: return None diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index fc05f5bb..8ab13511 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -22,10 +22,8 @@ def __init__(self, core, accountview, account_dir): self.account_dir = account_dir self.do_save = accountview.save self.backend_manager = core._backend_manager - self.config = self.backend_manager.loadConfig('General') - self.lock() - #TODO + self.load() def signOut(self): self.save() @@ -40,10 +38,13 @@ def unlock(self): pass def load(self): - #TODO + #TODO: + self.config = self.backend_manager.loadConfig(self, 'General') pass def save(self): + self.backend_manager.saveConfig(self, self.config, 'General') + #TODO: integrate with personnalinfo if self.view is not None and self.view.email is not None: root_section = Element("aMSNAccount") #email From 28d99e73045e0aa8e59be6338a0835b066ecb391 Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Sun, 7 Jun 2009 20:45:27 +0200 Subject: [PATCH 120/147] [gtk] added account management at login [personalinfomanager] start to integrate with aMSNAccounts --- amsn2/core/account_manager.py | 2 -- amsn2/core/personalinfo_manager.py | 2 +- amsn2/core/views/accountview.py | 4 ++- amsn2/gui/front_ends/gtk/login.py | 49 +++++++++++++++++++++--------- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 8ab13511..68026e36 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -40,7 +40,6 @@ def unlock(self): def load(self): #TODO: self.config = self.backend_manager.loadConfig(self, 'General') - pass def save(self): self.backend_manager.saveConfig(self, self.config, 'General') @@ -167,7 +166,6 @@ def getAllAccountViews(self): def getAvailableAccountViews(self): return [v for v in self.accountviews if not self.isAccountLocked(v)] - pass def signinToAccount(self, accountview): accdir = os.path.join(self._accounts_dir, diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index e24ce032..7a270cc1 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -14,7 +14,7 @@ def setAccount(self, amsn_account): # could be overriden by the one set in the saved account # TODO: add setting display picture and saved personal message strv = StringView() - nick = amsn_account.view.nick.toString() + nick = str(amsn_account.view.nick) if nick and nick != amsn_account.view.email: strv.appendText(nick) else: diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py index 5309f4f5..85b5d21b 100644 --- a/amsn2/core/views/accountview.py +++ b/amsn2/core/views/accountview.py @@ -8,9 +8,11 @@ def __init__(self): self.email = None self.password = None self.nick = StringView() - self.presence = papyon.Presence.ONLINE + self.presence = 'online' self.dp = ImageView() self.save = False + self.save_password = False + self.autologin = False self.preferred_ui = None diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 1f5c5fbe..3f8a8b81 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -36,7 +36,6 @@ def __init__(self, amsn_core, parent): gtk.VBox.__init__(self, spacing=10) self._amsn_core = amsn_core - #self.switch_to_profile(None) self._main_win = parent self._skin = amsn_core._skin_manager.skin self._theme_manager = self._amsn_core._theme_manager @@ -74,7 +73,7 @@ def __init__(self, amsn_core, parent): userCompletion.add_attribute(userPixbufCell, 'pixbuf', 1) userCompletion.set_text_column(0) #userCompletion.connect('match-selected', self.matchSelected) - #self.user.connect("changed", self.on_comboxEntry_changed) + self.user.connect("changed", self.on_comboxEntry_changed) #self.user.connect("key-release-event", self.on_comboxEntry_keyrelease) userbox.pack_start(userlabel, False, False) userbox.pack_start(self.user, False, False) @@ -164,7 +163,6 @@ def __init__(self, amsn_core, parent): self.show_all() self._main_win.set_view(self) self.user.grab_focus() - #self.switch_to_profile(None) def __animation(self): path = os.path.join("amsn2", "themes", "default", "images", @@ -194,27 +192,48 @@ def hide(self): if (self.timer is not None): gobject.source_remove(self.timer) - def switch_to_profile(self, profile): - self.current_profile = profile - if profile is not None: - self._username = self.current_profile.username - self.user.get_children()[0].set_text(self._username) - self._password = self.current_profile.password - self.password.set_text(self._password) + def __switch_to_account(self, email): + accv = [accv for accv in self._account_views if accv.email == email] + if not accv: + accv = AccountView() + accv.email = email + else: + accv = accv[0] + + self.user.get_children()[0].set_text(accv.email) + if accv.password: + self.password.set_text(accv.password) + + def setAccounts(self, accountviews): + self._account_views = accountviews + if len(accountviews)>0 : + # first in the list, default + self.__switch_to_account(self._account_views[0].email) + def signin(self): - self.current_profile.username = self.user.get_active_text() - self.current_profile.email = self.user.get_active_text() - self.current_profile.password = self.password.get_text() + email = self.user.get_active_text() + accv = [accv for accv in self._account_views if accv.email == email] + if not accv: + accv = AccountView() + accv.email = email + else: + accv = accv[0] + + accv.password = self.password.get_text() status = self.statusCombo.get_active() for key in self.status_values: if self.status_values[key] == status: break - self.current_profile.presence = key - self._amsn_core.signinToAccount(self, self.current_profile) + accv.presence = key + + self._amsn_core.signinToAccount(self, accv) self.timer = gobject.timeout_add(40, self.__animation) def onConnecting(self, progress, message): self.status.set_text(message) self.pgbar.set_fraction(progress) + def on_comboxEntry_changed(self, entry): + pass + From bf9756ee849742b07e8aef5f919ec6bbd69e775a Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Mon, 8 Jun 2009 00:01:34 +0200 Subject: [PATCH 121/147] Fix account loading in the account_manager --- amsn2/core/account_manager.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 68026e36..65a5031d 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -50,7 +50,7 @@ def save(self): emailElmt = SubElement(root_section, "email") emailElmt.text = self.view.email #nick - nick = self.view.nick.toString() + nick = str(self.view.nick) nickElmt = SubElement(root_section, "nick") nickElmt.text = nick #presence @@ -111,7 +111,6 @@ def reload(self): for root, dirs, files in os.walk(self._accounts_dir): account_dirs = dirs break - for account_dir in account_dirs: accv = self.loadAccount(os.path.join(self._accounts_dir, account_dir)) if accv: @@ -129,24 +128,24 @@ def loadAccount(self, dir): accview = AccountView() #email emailElmt = account.find("email") - if not emailElmt: + if emailElmt is None: return None accview.email = emailElmt.text #nick nickElmt = account.find("nick") - if not nickElmt: + if nickElmt is None: return None if nickElmt.text: accview.nick.appendText(nickElmt.text) #TODO: parse... #presence presenceElmt = account.find("presence") - if not presenceElmt: + if presenceElmt is None: return None accview.presence = presenceElmt.text #password passwordElmt = account.find("password") - if not passwordElmt: + if passwordElmt is None: return None accview.password = self.core._backend_manager.getPassword(passwordElmt) #TODO: use backend & all From d28413f7dfc6743981be3e1eae8907057ef69bcd Mon Sep 17 00:00:00 2001 From: Luca Dariz Date: Mon, 8 Jun 2009 01:02:11 +0200 Subject: [PATCH 122/147] Add null and base backends, and save account only if required --- amsn2/backend/backend.py | 23 +++++------ amsn2/backend/basebackend.py | 17 ++++++++ amsn2/backend/defaultbackend.py | 69 ++++++++++++++++----------------- amsn2/backend/nullbackend.py | 22 +++++++++++ amsn2/core/account_manager.py | 22 +++++++---- amsn2/core/views/accountview.py | 2 +- 6 files changed, 98 insertions(+), 57 deletions(-) create mode 100644 amsn2/backend/basebackend.py create mode 100644 amsn2/backend/nullbackend.py diff --git a/amsn2/backend/backend.py b/amsn2/backend/backend.py index 98ecec9a..dc4b6483 100644 --- a/amsn2/backend/backend.py +++ b/amsn2/backend/backend.py @@ -9,9 +9,7 @@ class aMSNBackendManager(object): def __init__(self): - self.setBackendForFunc('setPassword', 'defaultbackend') - self.setBackendForFunc('saveConfig', 'defaultbackend') - self.setBackendForFunc('loadConfig', 'defaultbackend') + self.switchToBackend('nullbackend') def setBackendForFunc(self, funcname, backendname): try: @@ -20,18 +18,15 @@ def setBackendForFunc(self, funcname, backendname): m = __import__('defaultbackend', globals(), locals(), [], -1) try: f = getattr(m, funcname) + self.__setattr__(funcname, f) except AttributeError: - return - self.__setattr__(funcname, f) - - def getPassword(self, passwdElmt): - backendname = passwdElmt.attrib['backend'] - try: - m = __import__(backendname, globals(), locals(), [], -1) - except ImportError: - m = __import__('defaultbackend', globals(), locals(), [], -1) - - return m.getPassword(passwdElmt) + self.__setattr__(funcname, self.__missingFunc) + def switchToBackend(self, backend): + self.setBackendForFunc('setPassword', backend) + self.setBackendForFunc('saveConfig', backend) + self.setBackendForFunc('loadConfig', backend) + def __missingFunc(*args): + print 'Function not implemented for this backend' diff --git a/amsn2/backend/basebackend.py b/amsn2/backend/basebackend.py new file mode 100644 index 00000000..d7bef7e0 --- /dev/null +++ b/amsn2/backend/basebackend.py @@ -0,0 +1,17 @@ +""" +Base backend, should be used as a model to implement others backends +As it is right now it's not used directly by aMSN2's code +""" + +def getPassword(passwdElmt): + raise NotImplementedError + +def setPassword(password, root_section): + raise NotImplementedError + +def saveConfig(account, config): + raise NotImplementedError + +def loadConfig(account): + raise NotImplementedError + diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py index e6fd5e82..9c4ebb3a 100644 --- a/amsn2/backend/defaultbackend.py +++ b/amsn2/backend/defaultbackend.py @@ -1,3 +1,5 @@ +""" Backend used to save the config on the home directory of the user """ + import os """ElementTree independent from the available distribution""" try: @@ -20,38 +22,35 @@ def setPassword(password, root_section): def saveConfig(account, config, name): #TODO: improve - if name == 'General': - root_section = Element("aMSNConfig") - for e in config._config: - val = config._config[e] - elmt = SubElement(root_section, "entry", - type=type(val).__name__, - name=str(e)) - elmt.text = str(val) - - accpath = os.path.join(account.account_dir, "config.xml") - xml_tree = ElementTree(root_section) - xml_tree.write(accpath, encoding='utf-8') - -def loadConfig(account, name): - if name == 'General': - c = aMSNConfig() - c._config = {"ns_server":'messenger.hotmail.com', - "ns_port":1863, - } - configpath = os.path.join(account.account_dir, "config.xml") - configfile = file(configpath, "r") - root_tree = ElementTree(file=configfile) - configfile.close() - config = root_tree.getroot() - if config.tag == "aMSNConfig": - lst = config.findall("entry") - for elmt in lst: - if elmt.attrib['type'] == 'int': - c.setKey(elmt.attrib['name'], int(elmt.text)) - else: - c.setKey(elmt.attrib['name'], elmt.text) - print repr(c._config) - return c - else: - return None + root_section = Element("aMSNConfig") + for e in config._config: + val = config._config[e] + elmt = SubElement(root_section, "entry", + type=type(val).__name__, + name=str(e)) + elmt.text = str(val) + + accpath = os.path.join(account.account_dir, "config.xml") + xml_tree = ElementTree(root_section) + xml_tree.write(accpath, encoding='utf-8') + +def loadConfig(account): + c = aMSNConfig() + c._config = {"ns_server":'messenger.hotmail.com', + "ns_port":1863, + } + configpath = os.path.join(account.account_dir, "config.xml") + configfile = file(configpath, "r") + root_tree = ElementTree(file=configfile) + configfile.close() + config = root_tree.getroot() + if config.tag == "aMSNConfig": + lst = config.findall("entry") + for elmt in lst: + if elmt.attrib['type'] == 'int': + c.setKey(elmt.attrib['name'], int(elmt.text)) + else: + c.setKey(elmt.attrib['name'], elmt.text) + print repr(c._config) + return c + diff --git a/amsn2/backend/nullbackend.py b/amsn2/backend/nullbackend.py new file mode 100644 index 00000000..e659ad3a --- /dev/null +++ b/amsn2/backend/nullbackend.py @@ -0,0 +1,22 @@ +""" Backend that will not save anything, used for on-the-fly-sessions """ + +from amsn2.core.config import aMSNConfig + +def getPassword(passwdElmt): + return passwdElmt.text + +def setPassword(password, root_section): + elmt = SubElement(root_section, "password", backend='NullBackend') + elmt.text = password + return elmt + +def saveConfig(account, config): + pass + +def loadConfig(account): + c = aMSNConfig() + c._config = {"ns_server":'messenger.hotmail.com', + "ns_port":1863, + } + return c + diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 65a5031d..fb46309d 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -26,7 +26,8 @@ def __init__(self, core, accountview, account_dir): self.load() def signOut(self): - self.save() + if self.do_save: + self.save() self.unlock() def lock(self): @@ -39,10 +40,10 @@ def unlock(self): def load(self): #TODO: - self.config = self.backend_manager.loadConfig(self, 'General') + self.config = self.backend_manager.loadConfig(self) def save(self): - self.backend_manager.saveConfig(self, self.config, 'General') + self.backend_manager.saveConfig(self, self.config) #TODO: integrate with personnalinfo if self.view is not None and self.view.email is not None: root_section = Element("aMSNAccount") @@ -80,7 +81,7 @@ class aMSNAccountManager(object): and retreiving all the account. """ def __init__(self, core, options): - self.core = core + self._core = core if os.name == "posix": self._accounts_dir = os.path.join(os.environ['HOME'], ".amsn2") elif os.name == "nt": @@ -167,9 +168,16 @@ def getAvailableAccountViews(self): return [v for v in self.accountviews if not self.isAccountLocked(v)] def signinToAccount(self, accountview): - accdir = os.path.join(self._accounts_dir, - accountNameToDirName(accountview.email)) - acc = aMSNAccount(self.core, accountview, accdir) + if accountview.save: + # save the backend type in the account? + self._core._backend_manager.switchToBackend('defaultbackend') + accdir = os.path.join(self._accounts_dir, + accountNameToDirName(accountview.email)) + else: + # fake dir, fix it + accdir = "" + acc = aMSNAccount(self._core, accountview, accdir) + acc.lock() return acc def isAccountLocked(self, accountview): diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py index 85b5d21b..b9c02ef6 100644 --- a/amsn2/core/views/accountview.py +++ b/amsn2/core/views/accountview.py @@ -1,7 +1,6 @@ from imageview import * from stringview import * -import papyon class AccountView: def __init__(self): @@ -16,3 +15,4 @@ def __init__(self): self.autologin = False self.preferred_ui = None + From 02f029d4c8fd329cbb10e79c42297349228e8730 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Mon, 8 Jun 2009 03:04:32 +0200 Subject: [PATCH 123/147] Remove useless _isDict method --- amsn2/core/lang.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/amsn2/core/lang.py b/amsn2/core/lang.py index 18f88c3c..ceede5da 100644 --- a/amsn2/core/lang.py +++ b/amsn2/core/lang.py @@ -99,7 +99,7 @@ def getKey(self, key, replacements=[]): return key # Perform any replacements necessary. - if self._isDict(replacements): + if type(replacements) is dict: # Replace from a dictionary. for key, val in replacements.iteritems(): r = r.replace(key, val) @@ -112,13 +112,6 @@ def getKey(self, key, replacements=[]): return r - def _isDict(self, test): - try: - test.keys() - return True - except AttributeError: - return False - def clearKeys(self): self.lang_keys = {} From d369c3f2ca34a1e853633fbb509f73df75343588 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Mon, 8 Jun 2009 11:05:51 +0200 Subject: [PATCH 124/147] Skin and SkinManager in frontends must inherit from base.Skin and base.SkinManager. --- amsn2/gui/front_ends/cocoa/skins.py | 4 ++-- amsn2/gui/front_ends/efl/skins.py | 4 ++-- amsn2/gui/front_ends/gtk/skins.py | 4 ++-- amsn2/gui/front_ends/qt4/skins.py | 4 ++-- amsn2/gui/front_ends/web/skins.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/amsn2/gui/front_ends/cocoa/skins.py b/amsn2/gui/front_ends/cocoa/skins.py index 57b53530..b67916d1 100644 --- a/amsn2/gui/front_ends/cocoa/skins.py +++ b/amsn2/gui/front_ends/cocoa/skins.py @@ -20,7 +20,7 @@ import os.path -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path pass @@ -33,7 +33,7 @@ def setKey(self, key, value): -class SkinManager(object): +class SkinManager(base.SkinManager): def __init__(self, core): self._core = core self.skin = Skin(core, "skins") diff --git a/amsn2/gui/front_ends/efl/skins.py b/amsn2/gui/front_ends/efl/skins.py index 15d1a639..614a637c 100644 --- a/amsn2/gui/front_ends/efl/skins.py +++ b/amsn2/gui/front_ends/efl/skins.py @@ -1,6 +1,6 @@ import os.path -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path self._dict = {} @@ -45,7 +45,7 @@ def setKey(self, key, value): -class SkinManager(object): +class SkinManager(base.SkinManager): def __init__(self, core): self._core = core self.skin = Skin(core, "skins") diff --git a/amsn2/gui/front_ends/gtk/skins.py b/amsn2/gui/front_ends/gtk/skins.py index 6b7b7568..e5995fcc 100644 --- a/amsn2/gui/front_ends/gtk/skins.py +++ b/amsn2/gui/front_ends/gtk/skins.py @@ -23,7 +23,7 @@ import os -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path self._dict = {} @@ -76,7 +76,7 @@ def setKey(self, key, value): self._dict[key] = value -class SkinManager(object): +class SkinManager(base.SkinManager): def __init__(self, core): self._core = core self.skin = Skin(core, "skins") diff --git a/amsn2/gui/front_ends/qt4/skins.py b/amsn2/gui/front_ends/qt4/skins.py index 57b53530..b67916d1 100644 --- a/amsn2/gui/front_ends/qt4/skins.py +++ b/amsn2/gui/front_ends/qt4/skins.py @@ -20,7 +20,7 @@ import os.path -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path pass @@ -33,7 +33,7 @@ def setKey(self, key, value): -class SkinManager(object): +class SkinManager(base.SkinManager): def __init__(self, core): self._core = core self.skin = Skin(core, "skins") diff --git a/amsn2/gui/front_ends/web/skins.py b/amsn2/gui/front_ends/web/skins.py index 36474730..b8294bf7 100644 --- a/amsn2/gui/front_ends/web/skins.py +++ b/amsn2/gui/front_ends/web/skins.py @@ -1,6 +1,6 @@ import os.path -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path pass @@ -13,7 +13,7 @@ def setKey(self, key, value): -class SkinManager(object): +class SkinManager(base.SkinManager): def __init__(self, core): self._core = core self.skin = Skin(core, "skins") From 9e9d73d174fcf2d46c0f83e8d908a3d58fe7e961 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Mon, 8 Jun 2009 03:03:49 +0200 Subject: [PATCH 125/147] First attempt to document type of params --- amsn2/core/account_manager.py | 24 +++++++++++++++++++ amsn2/core/amsn.py | 30 ++++++++++++++++++++++++ amsn2/core/config.py | 20 ++++++++++++++++ amsn2/core/contactlist_manager.py | 4 ++++ amsn2/core/conversation.py | 7 ++++++ amsn2/core/conversation_manager.py | 4 ++++ amsn2/core/event_manager.py | 4 ++++ amsn2/core/oim_manager.py | 4 ++++ amsn2/core/personalinfo_manager.py | 4 ++++ amsn2/core/views/menuview.py | 20 +++++++++------- amsn2/gui/base/contact_list.py | 11 +++++---- amsn2/gui/base/login.py | 9 ++++--- amsn2/gui/base/main.py | 4 ++++ amsn2/gui/base/main_loop.py | 17 ++++++++++++-- amsn2/gui/base/skins.py | 8 +++++++ amsn2/gui/base/window.py | 18 ++++++++++---- amsn2/gui/front_ends/gtk/contact_list.py | 7 +++--- amsn2/gui/front_ends/gtk/main.py | 2 +- amsn2/gui/gui.py | 5 ++++ 19 files changed, 176 insertions(+), 26 deletions(-) diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 65a5031d..a1a79ee9 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -18,6 +18,12 @@ class aMSNAccount(object): """ #TODO: use the personnal info stuff instead of the view def __init__(self, core, accountview, account_dir): + """ + @type core: aMSNCore + @type accountview: AccountView + @type account_dir: str + """ + self.view = accountview self.account_dir = account_dir self.do_save = accountview.save @@ -167,16 +173,34 @@ def getAvailableAccountViews(self): return [v for v in self.accountviews if not self.isAccountLocked(v)] def signinToAccount(self, accountview): + """ + @type accountview: AccountView + @rtype: aMSNAccount + """ + accdir = os.path.join(self._accounts_dir, accountNameToDirName(accountview.email)) acc = aMSNAccount(self.core, accountview, accdir) return acc def isAccountLocked(self, accountview): + """ + @type accountview: AccountView + @rtype: bool + @return: True if accountview is locked + """ + #TODO return False def accountNameToDirName(acc): + """ + @type acc: str + @param acc: account email + @rtype: str + @return: account email parsed to use as dir name + """ + #Having to do that just sucks return acc.lower().strip().replace("@","_at_") diff --git a/amsn2/core/amsn.py b/amsn2/core/amsn.py index 799477ee..8720aa2e 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -82,6 +82,11 @@ def run(self): self._loop.run(); def loadUI(self, ui_name): + """ + @type ui_name: str + @param ui_name: The name of the User Interface + """ + self._gui_name = ui_name self._gui = gui.GUIManager(self, self._gui_name) self._loop = self._gui.gui.aMSNMainLoop(self) @@ -89,6 +94,11 @@ def loadUI(self, ui_name): self._skin_manager = self._gui.gui.SkinManager(self) def switchToUI(self, ui_name): + """ + @type ui_name: str + @param ui_name: The name of the User Interface + """ + #TODO: unloadUI + stop loops??? + loadUI + run pass @@ -122,6 +132,11 @@ def getMainWindow(self): return self._main def signinToAccount(self, login_window, accountview): + """ + @type login_window: aMSNLoginWindow + @type accountview: AccountView + """ + print "Signing in to account %s" % (accountview.email) self._account = self._account_manager.signinToAccount(accountview) self._account.login = login_window @@ -129,6 +144,11 @@ def signinToAccount(self, login_window, accountview): self._account.client.connect(accountview.email, accountview.password) def connectionStateChanged(self, account, state): + """ + @type account: aMSNAccount + @type state: L{papyon.event.ClientState} + @param state: New state of the Client. + """ status_str = \ { @@ -155,9 +175,19 @@ def connectionStateChanged(self, account, state): self._contactlist_manager.onCLDownloaded(account.client.address_book) def idlerAdd(self, func): + """ + @type func: function + """ + self._loop.idlerAdd(func) def timerAdd(self, delay, func): + """ + @type delay: int + @param delay: delay in seconds? + @type func: function + """ + self._loop.timerAdd(delay, func) def quit(self): diff --git a/amsn2/core/config.py b/amsn2/core/config.py index cc67790e..7ddcff31 100644 --- a/amsn2/core/config.py +++ b/amsn2/core/config.py @@ -6,10 +6,30 @@ def __init__(self): self._config = {} def getKey(self, key, default = None): + """ + Get a existing config key or a default value in any other case. + + @type key: str + @param key: name of the config key. + @type default: Any + @param default: default value to return if key doesn't exist. + @rtype: Any + @return: config key value. + """ + try: return self._config[key] except KeyError: return default def setKey(self, key, value): + """ + Set a key value + + @type key: str + @param key: name of the config key. + @type value: Any + @param value: value of the key to be set. + """ + self._config[key] = value diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index acf16f72..7d57cfcb 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -6,6 +6,10 @@ class aMSNContactListManager: def __init__(self, core): + """ + @type core: aMSNCore + """ + self._core = core self._em = core._event_manager self._contacts = {} diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index 95e393ee..d0f39269 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -25,6 +25,13 @@ class aMSNConversation: def __init__(self, core, conv_manager, conv = None, contacts_uid = None): + """ + @type core: aMSNCore + @type conv_manager: aMSNConversationManager + @type conv: + @type contacts_uid: + """ + if (contacts_uid is None): raise ValueError, InvalidArgument diff --git a/amsn2/core/conversation_manager.py b/amsn2/core/conversation_manager.py index d7d7c2fb..db60534e 100644 --- a/amsn2/core/conversation_manager.py +++ b/amsn2/core/conversation_manager.py @@ -3,6 +3,10 @@ class aMSNConversationManager: def __init__(self, core): + """ + @type core: aMSNCore + """ + self._core = core self._convs = [] self._wins = [] diff --git a/amsn2/core/event_manager.py b/amsn2/core/event_manager.py index e9eb49f5..a9fbde24 100644 --- a/amsn2/core/event_manager.py +++ b/amsn2/core/event_manager.py @@ -9,6 +9,10 @@ class aMSNEvents: class aMSNEventManager: def __init__(self, core): + """ + @type core: aMSNCore + """ + self._core = core self._events_cbs = [ [[], []] for e in dir(aMSNEvents) if e.isupper()] self._events_tree = [aMSNEventTree(None) for e in dir(aMSNEvents) if e.isupper()] diff --git a/amsn2/core/oim_manager.py b/amsn2/core/oim_manager.py index 477530af..c77f5152 100644 --- a/amsn2/core/oim_manager.py +++ b/amsn2/core/oim_manager.py @@ -20,5 +20,9 @@ class aMSNOIMManager: def __init__(self, core): + """ + @type core: aMSNCore + """ + self._core = core diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py index 7a270cc1..d695028f 100644 --- a/amsn2/core/personalinfo_manager.py +++ b/amsn2/core/personalinfo_manager.py @@ -2,6 +2,10 @@ class aMSNPersonalInfoManager: def __init__(self, core): + """ + @type core: aMSNCore + """ + self._core = core self._em = core._event_manager self._personalinfoview = PersonalInfoView(self) diff --git a/amsn2/core/views/menuview.py b/amsn2/core/views/menuview.py index e5d4f7fe..7de68f8f 100644 --- a/amsn2/core/views/menuview.py +++ b/amsn2/core/views/menuview.py @@ -10,18 +10,20 @@ class MenuItemView(object): def __init__(self, type, label = None, icon = None, accelerator = None, radio_value = None, checkbox_value = False, disabled = False, command = None): """ Create a new MenuItemView - @type : the type of item, can be cascade, checkbutton, radiobutton, + @param type: the type of item, can be cascade, checkbutton, radiobutton, radiogroup, separator or command - @label : the label for the item, unused for separator items - @accelerator : the accelerator (KeyBindingView) to access this item. + @param label: the label for the item, unused for separator items + @param icon: an optional icon to show next to the menu item, unused for separator items + @param accelerator: the accelerator (KeyBindingView) to access this item. If None, an '&' preceding a character of the menu label will set that key with Ctrl- as an accelerator - @icon : an optional icon to show next to the menu item, unused for separator items - @radio_value : the value to set when the radiobutton is enabled - @checkbox_value : whether the checkbox/radiobutton is set or not - @disabled : true if the item's state should be disabled - @command : the command to call for setting the value for checkbutton and radiobutton items, or the command in case of a 'command' item + @param radio_value: the value to set when the radiobutton is enabled + @type checkbox_value: bool + @param checkbox_value: whether the checkbox/radiobutton is set or not + @type disabled: bool + @param disabled: true if the item's state should be disabled + @param command: the command to call for setting the value for checkbutton and radiobutton items, or the command in case of a 'command' item - TODO: dynamic menus (use 'command' in CASCADE_MENU) + @todo: dynamic menus (use 'command' in CASCADE_MENU) """ if ((type is MenuItemView.SEPARATOR and diff --git a/amsn2/gui/base/contact_list.py b/amsn2/gui/base/contact_list.py index 82e7b712..eef5f273 100644 --- a/amsn2/gui/base/contact_list.py +++ b/amsn2/gui/base/contact_list.py @@ -26,20 +26,21 @@ def hide(self): def setTitle(self, text): """ This will allow the core to change the current window's title - @text : a string + @type text: str """ raise NotImplementedError def setMenu(self, menu): """ This will allow the core to change the current window's main menu - @menu : a MenuView + @type menu: MenuView """ raise NotImplementedError def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the PersonalInfoView of the ourself (contains DP, nick, psm, + @type view: PersonalInfoView + @param view: the PersonalInfoView of the ourself (contains DP, nick, psm, currentMedia,...)""" raise NotImplementedError @@ -66,7 +67,9 @@ def contactListUpdated(self, clView): It will be called initially to feed the contact list with the groups that the CL should contain. It will also be called to remove any group that needs to be removed. - @cl : a ContactListView containing the list of groups contained in + + @type clView: ContactListView + @param clView : contains the list of groups contained in the contact list which will contain the list of ContactViews for all the contacts to show in the group.""" raise NotImplementedError diff --git a/amsn2/gui/base/login.py b/amsn2/gui/base/login.py index dc4a40cd..828fab90 100644 --- a/amsn2/gui/base/login.py +++ b/amsn2/gui/base/login.py @@ -16,7 +16,8 @@ def hide(self): def setAccounts(self, accountviews): """ This method will be called when the core needs the login window to let the user select among some accounts. - @accountviews: list of accountviews describing accounts + + @param accountviews: list of accountviews describing accounts The first one in the list should be considered as default. """ raise NotImplementedError @@ -27,9 +28,11 @@ def signin(self): def onConnecting(self, progress, message): """ This method will be called to notify the UI that we are connecting. - @progress: the current progress (as float) of the connexion (to be + + @type progress: float + @param progress: the current progress of the connexion (to be exploited as a progress bar, for example) - @message: the message to show while loging in """ + @param message: the message to show while loging in """ raise NotImplementedError diff --git a/amsn2/gui/base/main.py b/amsn2/gui/base/main.py index f2f7d514..5426fc90 100644 --- a/amsn2/gui/base/main.py +++ b/amsn2/gui/base/main.py @@ -6,6 +6,10 @@ class aMSNMainWindow(object): """ def __init__(self, amsn_core): + """ + @type amsn_core: aMSNCore + """ + pass def show(self): diff --git a/amsn2/gui/base/main_loop.py b/amsn2/gui/base/main_loop.py index d25c740f..f9b11288 100644 --- a/amsn2/gui/base/main_loop.py +++ b/amsn2/gui/base/main_loop.py @@ -3,6 +3,10 @@ class aMSNMainLoop(object): """ This Interface represents the main loop abstraction of the application. Everythin related to the main loop will be delegates here """ def __init__(self, amsn_core): + """ + @type amsn_core: aMSNCore + """ + raise NotImplementedError def run(self): @@ -10,11 +14,20 @@ def run(self): raise NotImplementedError def idlerAdd(self, func): - """ This will add an idler function into the main loop's idler """ + """ + This will add an idler function into the main loop's idler + + @type func: function + """ raise NotImplementedError def timerAdd(self, delay, func): - """ This will add a timer into the main loop which will call a function""" + """ + This will add a timer into the main loop which will call a function + + @type delay: + @type func: function + """ raise NotImplementedError def quit(self): diff --git a/amsn2/gui/base/skins.py b/amsn2/gui/base/skins.py index 36474730..2995cc15 100644 --- a/amsn2/gui/base/skins.py +++ b/amsn2/gui/base/skins.py @@ -2,6 +2,11 @@ class Skin(object): def __init__(self, core, path): + """ + @type core: aMSNCore + @type path: + """ + self._path = path pass @@ -15,6 +20,9 @@ def setKey(self, key, value): class SkinManager(object): def __init__(self, core): + """ + @type core: aMSNCore + """ self._core = core self.skin = Skin(core, "skins") diff --git a/amsn2/gui/base/window.py b/amsn2/gui/base/window.py index d7039328..877f767d 100644 --- a/amsn2/gui/base/window.py +++ b/amsn2/gui/base/window.py @@ -2,6 +2,10 @@ class aMSNWindow(object): """ This Interface represents a window of the application. Everything will be done from here """ def __init__(self, amsn_core): + """ + @type amsn_core: aMSNCore + """ + raise NotImplementedError def show(self): @@ -13,13 +17,19 @@ def hide(self): raise NotImplementedError def setTitle(self, text): - """ This will allow the core to change the current window's title - @text : a string """ + This will allow the core to change the current window's title + + @type text: str + """ + raise NotImplementedError def setMenu(self, menu): - """ This will allow the core to change the current window's main menu - @menu : a MenuView """ + This will allow the core to change the current window's main menu + + @type menu: MenuView + """ + raise NotImplementedError diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 2243f8d5..15b429b5 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -177,15 +177,16 @@ def setTitle(self, text): def setMenu(self, menu): """ This will allow the core to change the current window's main menu - @menu : a MenuView + @type menu: MenuView """ pass def myInfoUpdated(self, view): """ This will allow the core to change pieces of information about ourself, such as DP, nick, psm, the current media being played,... - @view: the PersonalInfoView of the ourself (contains DP, nick, psm, - currentMedia,...)""" + @type view: PersonalInfoView + @param view: ourself (contains DP, nick, psm, currentMedia,...) + """ # TODO: image, ... self._myview = view nk = view.nick diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index 1fc74261..234d7d0d 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -37,7 +37,7 @@ def hide(self): def setMenu(self, menu): """ This will allow the core to change the current window's main menu - @menu : a MenuView + @type menu: MenuView """ chldn = self.main_menu.get_children() if len(chldn) is not 0: diff --git a/amsn2/gui/gui.py b/amsn2/gui/gui.py index e6fb67ff..67fb2f58 100644 --- a/amsn2/gui/gui.py +++ b/amsn2/gui/gui.py @@ -12,6 +12,11 @@ class GUIManager(object): front_ends = {} def __init__(self, core, gui_name): + """ + @type core: aMSNCore + @type gui_name: str + """ + self._core = core self._name = gui_name From c6d8a6c2aee09be7c588d573a050c7821569c5c9 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Mon, 8 Jun 2009 11:45:13 +0200 Subject: [PATCH 126/147] Removed 'is True' comparisons and unneeded type castings --- amsn2/core/lang.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/amsn2/core/lang.py b/amsn2/core/lang.py index ceede5da..30513f7d 100644 --- a/amsn2/core/lang.py +++ b/amsn2/core/lang.py @@ -18,13 +18,13 @@ def loadLang(self, lang_code, force_reload=False): # Don't reload the same lang unless forced. return - hasVariant = bool(self.langRe.match(lang_code) is not None) + hasVariant = (self.langRe.match(lang_code) is not None) # Check for lang variants. - if hasVariant is True: + if hasVariant: root = str(self.langRe.split(lang_code)[1]) else: - root = str(lang_code) + root = lang_code if lang_code is self.base_lang: # Clear the keys if we're loading the base lang. @@ -34,7 +34,7 @@ def loadLang(self, lang_code, force_reload=False): # If it's not the default lang, load the base first. self.loadLang(self.base_lang) - if hasVariant is True: + if hasVariant: # Then we have a variant, so load the root. self.loadLang(root) @@ -60,8 +60,8 @@ def loadLang(self, lang_code, force_reload=False): f.close() # If we've loaded a lang file, set the new lang code. - if fileWasLoaded is True: - self.lang_code = str(lang_code) + if fileWasLoaded: + self.lang_code = lang_code def addLangDir(self, lang_dir): self.lang_dirs.append(str(lang_dir)) From e24612e599cd7c54babdff8b1c32c3ae528d1cfd Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Tue, 9 Jun 2009 00:25:24 +0200 Subject: [PATCH 127/147] Various fixes --- amsn2/backend/defaultbackend.py | 5 ++- amsn2/core/account_manager.py | 4 +- amsn2/core/conversation.py | 2 +- amsn2/gui/front_ends/efl/contact_list.py | 50 ++++++++++-------------- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py index e6fd5e82..487d75b8 100644 --- a/amsn2/backend/defaultbackend.py +++ b/amsn2/backend/defaultbackend.py @@ -40,7 +40,10 @@ def loadConfig(account, name): "ns_port":1863, } configpath = os.path.join(account.account_dir, "config.xml") - configfile = file(configpath, "r") + try: + configfile = file(configpath, "r") + except IOError: + return c root_tree = ElementTree(file=configfile) configfile.close() config = root_tree.getroot() diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 65a5031d..ee02c9da 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -42,6 +42,8 @@ def load(self): self.config = self.backend_manager.loadConfig(self, 'General') def save(self): + if not os.path.isdir(self.account_dir): + os.makedirs(self.account_dir, 0700) self.backend_manager.saveConfig(self, self.config, 'General') #TODO: integrate with personnalinfo if self.view is not None and self.view.email is not None: @@ -68,8 +70,6 @@ def save(self): #TODO: save or not, preferred_ui #TODO: backend for config/logs/... - if not os.path.isdir(self.account_dir): - os.makedirs(self.account_dir, 0700) accpath = os.path.join(self.account_dir, "account.xml") xml_tree = ElementTree(root_section) xml_tree.write(accpath, encoding='utf-8') diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index 95e393ee..8d089d88 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -36,7 +36,7 @@ def __init__(self, core, conv_manager, conv = None, contacts_uid = None): papyon_contacts = [core._contactlist_manager.getContact(uid) for uid in contacts_uid] papyon_contacts = [c._papyon_contact for c in papyon_contacts if c is not None] #if c was None.... wtf? - self._conv = papyon.Conversation(self._core._profile.client, papyon_contacts) + self._conv = papyon.Conversation(self._core._account.client, papyon_contacts) else: #From an existing conversation self._conv = conv diff --git a/amsn2/gui/front_ends/efl/contact_list.py b/amsn2/gui/front_ends/efl/contact_list.py index 97096bfa..6aed062c 100644 --- a/amsn2/gui/front_ends/efl/contact_list.py +++ b/amsn2/gui/front_ends/efl/contact_list.py @@ -19,18 +19,16 @@ def __init__(self, amsn_core, parent): self._parent = parent self._skin = amsn_core._skin_manager.skin elementary.Box.__init__(self, parent) - self._clwidget = aMSNContactListWidget(amsn_core, self) self._parent.resize_object_add(self) self.size_hint_weight_set(1.0, 1.0) - self.pack_start(self._clwidget) - self._clwidget.show() + self.show() - def show(self): + self._clwidget = aMSNContactListWidget(amsn_core, self._parent) + self.pack_start(self._clwidget) + self._parent.resize_object_add(self._clwidget) + self._parent.resize_object_add(self._clwidget._edje) self._clwidget.show() - def hide(self): - self._clwidget.hide() - def setTitle(self, text): self._parent.setTitle(text) @@ -46,28 +44,24 @@ def __init__(self, amsn_core, parent): base.aMSNContactListWidget.__init__(self, amsn_core, parent) self._core = amsn_core self._evas = parent._evas - self._skin = parent._skin + self._skin = amsn_core._skin_manager.skin elementary.Scroller.__init__(self, parent) + self.size_hint_weight_set(1.0, 1.0) - edje.frametime_set(1.0 / 30) - try: - self._edje = edje.Edje(self._evas, file=THEME_FILE, - group="contact_list") - except edje.EdjeLoadError, e: - raise SystemExit("error loading %s: %s" % (THEME_FILE, e)) - - - self.group_holder = GroupHolder(self._evas, self) + self._edje = elementary.Layout(self) + self._edje.file_set(filename=THEME_FILE, + group="contact_list") + self.group_holder = GroupHolder(self._evas, self._edje, self._skin) - self._edje.part_swallow("groups", self.group_holder); - #elementary.Scroller.resize_object_add(self._edje) + self._edje.content_set("groups", self.group_holder); self._edje.size_hint_weight_set(1.0, 1.0) - self.content_set(self._edje) self._edje.show() + self.content_set(self._edje) def contactUpdated(self, contact): + print contact for gi in self.group_holder.group_items_list: if contact.uid in gi.contact_holder.contacts_dict: gi.contact_holder.contact_updated(contact) @@ -81,11 +75,6 @@ def contactListUpdated(self, clview): self.group_holder.viewUpdated(clview) - - def size_request_set(self, w,h): - self.size_hint_request_set(w,h) - - class ContactHolder(evas.SmartObject): def __init__(self, ecanvas, parent): @@ -266,13 +255,13 @@ def _clip_unset(self): class GroupHolder(evas.SmartObject): - def __init__(self, ecanvas, parent): + def __init__(self, ecanvas, parent, skin): evas.SmartObject.__init__(self, ecanvas) self.evas_obj = ecanvas self.group_items_list = [] self.group_items_dict = {} self._parent = parent - self._skin = parent._skin + self._skin = skin def add_group(self, uid): new_group = GroupItem(self, self.evas_obj, uid) @@ -297,13 +286,14 @@ def resize(self, w, h): self.update_widget(w, h) def show(self): + """ #FIXME: #ugly fix to get the correct clip - self.clip_set(self._parent._edje.clip_get()) + self.clip_set(self._parent.clip_get()) self.update_widget(self.size[0], self.size[1]) + """ for g in self.group_items_list: g.show() - def hide(self): for g in self.group_items_list: g.hide() @@ -338,7 +328,7 @@ def update_widget(self, w, h): i.move(x, y) i.size = (w, item_height) y += item_height + spacing - self._parent.size_request_set(w,y) + self._parent.size_hint_request_set(w,y) def clip_set(self, obj): for g in self.group_items_list: From 2ad4d43428a621d3e00e30616942ab6e0b7a9d7b Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 10 Jun 2009 01:12:09 +0200 Subject: [PATCH 128/147] Oops, forgot to import base --- amsn2/gui/front_ends/gtk/skins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/amsn2/gui/front_ends/gtk/skins.py b/amsn2/gui/front_ends/gtk/skins.py index e5995fcc..6a91aba8 100644 --- a/amsn2/gui/front_ends/gtk/skins.py +++ b/amsn2/gui/front_ends/gtk/skins.py @@ -21,6 +21,8 @@ # #=================================================== +from amsn2.gui import base + import os class Skin(base.Skin): From e6822cffe2811b6719271c8831f7af965573e7dc Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 10 Jun 2009 01:18:27 +0200 Subject: [PATCH 129/147] Oops, forgot to import base 2 --- amsn2/gui/front_ends/cocoa/skins.py | 1 + amsn2/gui/front_ends/efl/skins.py | 1 + amsn2/gui/front_ends/qt4/skins.py | 1 + amsn2/gui/front_ends/web/skins.py | 1 + 4 files changed, 4 insertions(+) diff --git a/amsn2/gui/front_ends/cocoa/skins.py b/amsn2/gui/front_ends/cocoa/skins.py index b67916d1..4f0b09e9 100644 --- a/amsn2/gui/front_ends/cocoa/skins.py +++ b/amsn2/gui/front_ends/cocoa/skins.py @@ -19,6 +19,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os.path +from amsn2.gui import base class Skin(base.Skin): def __init__(self, core, path): diff --git a/amsn2/gui/front_ends/efl/skins.py b/amsn2/gui/front_ends/efl/skins.py index 614a637c..19cbded7 100644 --- a/amsn2/gui/front_ends/efl/skins.py +++ b/amsn2/gui/front_ends/efl/skins.py @@ -1,4 +1,5 @@ import os.path +from amsn2.gui import base class Skin(base.Skin): def __init__(self, core, path): diff --git a/amsn2/gui/front_ends/qt4/skins.py b/amsn2/gui/front_ends/qt4/skins.py index b67916d1..4f0b09e9 100644 --- a/amsn2/gui/front_ends/qt4/skins.py +++ b/amsn2/gui/front_ends/qt4/skins.py @@ -19,6 +19,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os.path +from amsn2.gui import base class Skin(base.Skin): def __init__(self, core, path): diff --git a/amsn2/gui/front_ends/web/skins.py b/amsn2/gui/front_ends/web/skins.py index b8294bf7..ddf93d3d 100644 --- a/amsn2/gui/front_ends/web/skins.py +++ b/amsn2/gui/front_ends/web/skins.py @@ -1,4 +1,5 @@ import os.path +from amsn2.gui import base class Skin(base.Skin): def __init__(self, core, path): From 4fe65e25bf45debca92e11e92795692c9a76f944 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 10 Jun 2009 01:27:40 +0200 Subject: [PATCH 130/147] Now clean script removes compiled Qt4 user interfaces --- clean | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clean b/clean index c44aa021..6376d080 100755 --- a/clean +++ b/clean @@ -1,2 +1,8 @@ find . -name "*.pyc" -exec rm '{}' \; find . -name "*.pyo" -exec rm '{}' \; + +#Remove compiled Qt4 user interfaces +rm amsn2/gui/front_ends/qt4/ui_login.py +rm amsn2/gui/front_ends/qt4/ui_contactlist.py +rm amsn2/gui/front_ends/qt4/ui_chatWindow.py + From 8e0d25c01b3d295dddc2e03bab505732e7581194 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Thu, 11 Jun 2009 02:16:34 +0200 Subject: [PATCH 131/147] More doc strings --- amsn2/gui/front_ends/gtk/contact_list.py | 4 ++++ amsn2/gui/front_ends/gtk/main.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 15b429b5..e24b646f 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -383,6 +383,10 @@ def groupUpdated(self, groupview): def contactUpdated(self, contactview): + """ + @type contactview: ContactView + """ + citer = self.__search_by_id(contactview.uid) if citer is None: return diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index 234d7d0d..456d1993 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -6,6 +6,10 @@ import gtk class aMSNMainWindow(base.aMSNMainWindow): + """ + @ivar main_win: + @type main_win: gtk.Window + """ main_win = None def __init__(self, amsn_core): From f54fb2d1176f5d8a91496cde7d1cf4c8b8b349c4 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Thu, 11 Jun 2009 02:57:28 +0200 Subject: [PATCH 132/147] [GTK] Better usability --- amsn2/gui/front_ends/gtk/contact_list.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index ef008124..7f4c2843 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -220,8 +220,7 @@ def __on_btnPsmClicked(self, source): self.__switchToInput(source) def __switchToInput(self, source): - """ Switches the nick button into a text area for editing of the nick - name.""" + """ Switches the nick and psm buttons into a text area for editing them.""" #label = self.btnNickname.get_child() source.remove(source.get_child()) entry = gtk.Entry() @@ -235,15 +234,15 @@ def __switchToInput(self, source): def __handleInput(self, source, event): """ Handle various inputs from the nicknameEntry-box """ - if(event.type == gtk.gdk.FOCUS_CHANGE): #user clickd outside textfield - self.__switchFromInput(source, False) + if(event.type == gtk.gdk.FOCUS_CHANGE): #user clicked outside textfield + self.__switchFromInput(source, True) elif (event.type == gtk.gdk.KEY_PRESS): #user wrote something, esc perhaps? if event.keyval == gtk.keysyms.Escape: self.__switchFromInput(source, False) def __switchFromInput(self, source, isNew): - """ When in the editing state of nickname, change back to the uneditable - label state. + """ When in the editing state of nickname and psm, change back + to the uneditable label state. """ if(isNew): if(source == self.btnNickname.get_child()): @@ -259,7 +258,7 @@ def __switchFromInput(self, source, isNew): else: if(source == self.btnNickname.get_child()): # User discards input newText = self.nicklabel.get_text() # Old nickname - if(source == self.btnPsm.get_child()): + elif(source == self.btnPsm.get_child()): newText = self.psmlabel.get_text() parentWidget = source.get_parent() From 9bc3cd7209decfcbdabf1e3c9204afbaef70b386 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Thu, 11 Jun 2009 11:33:24 +0200 Subject: [PATCH 133/147] [GTK] Fixed bug when you pressed Escape in a CW, close window instead. --- amsn2/core/views/contactlistview.py | 1 - amsn2/gui/front_ends/gtk/chat_window.py | 29 ++++++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/amsn2/core/views/contactlistview.py b/amsn2/core/views/contactlistview.py index 77f79dd4..6f5d815d 100644 --- a/amsn2/core/views/contactlistview.py +++ b/amsn2/core/views/contactlistview.py @@ -38,7 +38,6 @@ def __init__(self, core, amsn_contact): #TODO: apply emblem on dp self.dp = amsn_contact.dp.clone() self.dp.appendImageView(amsn_contact.emblem) - self.name = StringView self.name = StringView() # TODO : default colors self.name.openTag("nickname") self.name.appendStringView(amsn_contact.nickname) # TODO parse diff --git a/amsn2/gui/front_ends/gtk/chat_window.py b/amsn2/gui/front_ends/gtk/chat_window.py index 63c56040..d010b296 100644 --- a/amsn2/gui/front_ends/gtk/chat_window.py +++ b/amsn2/gui/front_ends/gtk/chat_window.py @@ -245,19 +245,22 @@ def __on_chat_send(self, entry, event_keyval, event_keymod): entry.grab_focus() if (msg == ''): return False - color = self.button_color.get_color() - hex8 = "%.2x%.2x%.2x" % ((color.red/0x101), (color.green/0x101), (color.blue/0x101)) - style = papyon.TextFormat.NO_EFFECT - if self.button_bold.get_active(): style |= papyon.TextFormat.BOLD - if self.button_italic.get_active(): style |= papyon.TextFormat.ITALIC - if self.button_underline.get_active(): style |= papyon.TextFormat.UNDERLINE - if self.button_strikethrough.get_active(): style |= papyon.TextFormat.STRIKETHROUGH - font_name = self.button_font.get_font_name() - font_family = pango.FontDescription(font_name).get_family() - format = papyon.TextFormat(font=font_family, color=hex8, style=style) - strv = StringView() - strv.appendText(msg) - self._amsn_conversation.sendMessage(strv, format) + color = self.button_color.get_color() + hex8 = "%.2x%.2x%.2x" % ((color.red/0x101), (color.green/0x101), (color.blue/0x101)) + style = papyon.TextFormat.NO_EFFECT + if self.button_bold.get_active(): style |= papyon.TextFormat.BOLD + if self.button_italic.get_active(): style |= papyon.TextFormat.ITALIC + if self.button_underline.get_active(): style |= papyon.TextFormat.UNDERLINE + if self.button_strikethrough.get_active(): style |= papyon.TextFormat.STRIKETHROUGH + font_name = self.button_font.get_font_name() + font_family = pango.FontDescription(font_name).get_family() + format = papyon.TextFormat(font=font_family, color=hex8, style=style) + strv = StringView() + strv.appendText(msg) + self._amsn_conversation.sendMessage(strv, format) + + elif event_keyval == gtk.keysyms.Escape: + self._parent.destroy() def __on_clear_textview(self, widget): buffer = self.textview.get_buffer() From fda29379e5c9b77aa4b588e0f731f2d11f0a39d5 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Mon, 15 Jun 2009 16:36:27 +0200 Subject: [PATCH 134/147] More doc --- amsn2/core/contactlist_manager.py | 34 +++++++++++++++++++++++++---- amsn2/core/views/contactlistview.py | 4 ++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index 7d57cfcb..8cb7b62d 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -12,13 +12,15 @@ def __init__(self, core): self._core = core self._em = core._event_manager - self._contacts = {} + self._contacts = {} #Dictionary where every contact_uid has an associated aMSNContact self._groups = {} self._papyon_addressbook = None #TODO: sorting contacts & groups def onContactChanged(self, papyon_contact): + """ Called when a contact changes either its presence, nick, psm or current media.""" + #1st/ update the aMSNContact object c = self.getContact(papyon_contact.id, papyon_contact) c.fill(self._core, papyon_contact) @@ -29,6 +31,8 @@ def onContactChanged(self, papyon_contact): #TODO: update the group view def onContactDPChanged(self, papyon_contact): + """ Called when a contact changes its Display Picture. """ + #TODO: add local cache for DPs #Request the DP... c = self.getContact(papyon_contact.id, papyon_contact) @@ -102,14 +106,23 @@ def onDPdownloaded(self, msn_object, uid): self._em.emit(self._em.events.CONTACTVIEW_UPDATED, cv) - def getContact(self, cid, papyon_contact=None): + def getContact(self, uid, papyon_contact=None): + """ + @param uid: uid of the contact + @type uid: str + @param papyon_contact: + @type papyon_contact: + @return: aMSNContact of that contact + @rtype: aMSNContact + """ + #TODO: should raise UnknownContact or sthg like that try: - return self._contacts[cid] + return self._contacts[uid] except KeyError: if papyon_contact is not None: c = aMSNContact(self._core, papyon_contact) - self._contacts[cid] = c + self._contacts[uid] = c self._em.emit(self._em.events.AMSNCONTACT_UPDATED, c) return c else: @@ -122,6 +135,12 @@ def getContact(self, cid, papyon_contact=None): """ class aMSNContact(): def __init__(self, core, papyon_contact): + """ + @type core: aMSNCore + @param papyon_contact: + @type papyon_contact: papyon.profile.Contact + """ + self.uid = papyon_contact.id self.dp = ImageView() if papyon_contact.msn_object is None: @@ -131,6 +150,13 @@ def __init__(self, core, papyon_contact): self.fill(core, papyon_contact) def fill(self, core, papyon_contact): + """ + Fills the aMSNContact structure. + + @type core: aMSNCore + @type papyon_contact: papyon.profile.Contact + """ + self.icon = ImageView() self.icon.load("Theme","buddy_" + core.p2s[papyon_contact.presence]) self.emblem = ImageView() diff --git a/amsn2/core/views/contactlistview.py b/amsn2/core/views/contactlistview.py index 77f79dd4..9eddcc88 100644 --- a/amsn2/core/views/contactlistview.py +++ b/amsn2/core/views/contactlistview.py @@ -31,6 +31,10 @@ def __init__(self, uid, name, contact_ids=[], active=0): """ a view of a contact on the contact list """ class ContactView: def __init__(self, core, amsn_contact): + """ + @type core: aMSNCore + @type amsn_contact: aMSNContact + """ self.uid = amsn_contact.uid From c481babef5ea94095d41e445a665d18a08b51355 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Tue, 16 Jun 2009 02:18:07 +0200 Subject: [PATCH 135/147] Fixed bug when you ran amsn2 from another folder and it couldn't find papyon --- amsn2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/amsn2.py b/amsn2.py index 69990a57..759c8680 100755 --- a/amsn2.py +++ b/amsn2.py @@ -2,6 +2,7 @@ import sys import os import optparse +os.chdir(os.path.dirname(os.path.abspath(sys.argv[0]))) sys.path.insert(0, "./papyon") import locale locale.setlocale(locale.LC_ALL, '') From 5cb582132284557c203dcbda03020a1c8f77e3a6 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Tue, 16 Jun 2009 02:22:18 +0200 Subject: [PATCH 136/147] [GTK] Added missing import --- amsn2/gui/front_ends/gtk/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 3f8a8b81..85bc7083 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -27,7 +27,7 @@ import string from image import * -from amsn2.core.views import ImageView +from amsn2.core.views import AccountView, ImageView class aMSNLoginWindow(gtk.VBox, base.aMSNLoginWindow): From 31690aafc5c3ee8243abee222a1204b9811192de Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Tue, 16 Jun 2009 03:40:56 +0200 Subject: [PATCH 137/147] Added __str__ to accountview --- amsn2/core/views/accountview.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py index 85b5d21b..9945c6e7 100644 --- a/amsn2/core/views/accountview.py +++ b/amsn2/core/views/accountview.py @@ -16,3 +16,10 @@ def __init__(self): self.autologin = False self.preferred_ui = None + + + def __str__(self): + out = "{ email=" + str(self.email) + " presence=" + str(self.presence) + out += " save=" + str(self.save) + " save_password=" + str(self.save_password) + out += " autologin=" + str(self.autologin) + "}" + return out From ab9b389bd031400272acb360868df477964bfdec Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Tue, 16 Jun 2009 14:25:52 +0200 Subject: [PATCH 138/147] Use setKey() to set default ns_server and ns_port --- amsn2/backend/defaultbackend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py index 487d75b8..600597fc 100644 --- a/amsn2/backend/defaultbackend.py +++ b/amsn2/backend/defaultbackend.py @@ -36,9 +36,8 @@ def saveConfig(account, config, name): def loadConfig(account, name): if name == 'General': c = aMSNConfig() - c._config = {"ns_server":'messenger.hotmail.com', - "ns_port":1863, - } + c.setKey("ns_server", "messenger.hotmail.com") + c.setKey("ns_port", 1863) configpath = os.path.join(account.account_dir, "config.xml") try: configfile = file(configpath, "r") From 99f0d787682262076c8b168670d9cf7f6e8eb540 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Tue, 16 Jun 2009 04:45:35 +0200 Subject: [PATCH 139/147] [GTK] Done some work with login screen checkboxes. Now you can also change between accounts. --- amsn2/core/account_manager.py | 29 +++++++++++++--- amsn2/gui/front_ends/gtk/login.py | 57 ++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index 3f5aafcf..ceef4b67 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -65,8 +65,9 @@ def save(self): presenceElmt = SubElement(root_section, "presence") presenceElmt.text = self.view.presence #password - passwordElmt = self.backend_manager.setPassword(self.view.password, root_section) - passwordElmt.text = self.view.password + if self.view.save_password: + passwordElmt = self.backend_manager.setPassword(self.view.password, root_section) + passwordElmt.text = self.view.password #dp #TODO ask the backend dpElmt = SubElement(root_section, "dp", @@ -74,6 +75,13 @@ def save(self): #TODO #TODO: save or not, preferred_ui + # + #save password + savePassElmt = SubElement(root_section, "save_password") + savePassElmt.text = str(self.view.save_password) + #autologin + autologinElmt = SubElement(root_section, "autoconnect") + autologinElmt.text = str(self.view.autologin) #TODO: backend for config/logs/... accpath = os.path.join(self.account_dir, "account.xml") @@ -152,8 +160,21 @@ def loadAccount(self, dir): #password passwordElmt = account.find("password") if passwordElmt is None: - return None - accview.password = self.core._backend_manager.getPassword(passwordElmt) + accview.password = None + else: + accview.password = self.core._backend_manager.getPassword(passwordElmt) + #save_password + savePassElmt = account.find("save_password") + if savePassElmt.text == "False": + accview.save_password = False + else: + accview.save_password = True + #autoconnect + saveAutoConnect = account.find("autoconnect") + if saveAutoConnect == "False": + accview.autologin = False + else: + accview.autologin = True #TODO: use backend & all #dp dpElmt = account.find("dp") diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 85bc7083..46fce1a1 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -73,7 +73,9 @@ def __init__(self, amsn_core, parent): userCompletion.add_attribute(userPixbufCell, 'pixbuf', 1) userCompletion.set_text_column(0) #userCompletion.connect('match-selected', self.matchSelected) - self.user.connect("changed", self.on_comboxEntry_changed) + self.user.connect("key-press-event", self.__on_user_comboxEntry_changed) + #FIXME: focus-out-event not working, i don't know why + self.user.connect("focus-out-event", self.__on_user_comboxEntry_changed) #self.user.connect("key-release-event", self.on_comboxEntry_keyrelease) userbox.pack_start(userlabel, False, False) userbox.pack_start(self.user, False, False) @@ -85,6 +87,7 @@ def __init__(self, amsn_core, parent): self.password = gtk.Entry(128) self.password.set_visibility(False) self.password.connect('activate' , self.__login_clicked) + self.password.connect("changed", self.__on_passwd_comboxEntry_changed) passbox.pack_start(passlabel, False, False) passbox.pack_start(self.password, False, False) @@ -139,6 +142,10 @@ def __init__(self, amsn_core, parent): self.rememberPass = gtk.CheckButton('Remember password', True) self.autoLogin = gtk.CheckButton('Auto-Login', True) + self.rememberMe.connect("toggled", self.__on_toggled_cb) + self.rememberPass.connect("toggled", self.__on_toggled_cb) + self.autoLogin.connect("toggled", self.__on_toggled_cb) + checkboxes.pack_start(self.rememberMe, False, False) checkboxes.pack_start(self.rememberPass, False, False) checkboxes.pack_start(self.autoLogin, False, False) @@ -193,7 +200,10 @@ def hide(self): gobject.source_remove(self.timer) def __switch_to_account(self, email): + print "Switching to account", email + accv = [accv for accv in self._account_views if accv.email == email] + if not accv: accv = AccountView() accv.email = email @@ -204,12 +214,22 @@ def __switch_to_account(self, email): if accv.password: self.password.set_text(accv.password) + self.rememberMe.set_active(accv.save) + self.rememberPass.set_active(accv.save_password) + self.autoLogin.set_active(accv.autologin) + def setAccounts(self, accountviews): self._account_views = accountviews + + for accv in self._account_views: + self.user.append_text(accv.email) + if len(accountviews)>0 : # first in the list, default self.__switch_to_account(self._account_views[0].email) - + + if self._account_views[0].autologin: + self.signin() def signin(self): email = self.user.get_active_text() @@ -234,6 +254,35 @@ def onConnecting(self, progress, message): self.status.set_text(message) self.pgbar.set_fraction(progress) - def on_comboxEntry_changed(self, entry): - pass + def __on_user_comboxEntry_changed(self, entry, event): + if event.type == gtk.gdk.FOCUS_CHANGE or \ + (event.type == gtk.gdk.KEY_PRESS and event.keyval == gtk.keysyms.Tab): + self.__switch_to_account(entry.get_active_text()) + + def __on_passwd_comboxEntry_changed(self, entry): + if len(entry.get_text()) == 0: + self.rememberPass.set_sensitive(False) + self.autoLogin.set_sensitive(False) + else: + self.rememberPass.set_sensitive(True) + self.autoLogin.set_sensitive(True) + + def __on_toggled_cb(self, source): + + accv = [accv for accv in self._account_views if accv.email == self.user.get_active_text()] + + if not accv: + accv = AccountView() + accv.email = self.user.get_active_text() + else: + accv = accv[0] + if source is self.rememberMe: + accv.save = source.get_active() + self.rememberPass.set_sensitive(source.get_active()) + self.autoLogin.set_sensitive(source.get_active()) + elif source is self.rememberPass: + accv.save_password = source.get_active() + self.autoLogin.set_sensitive(source.get_active()) + elif source is self.autoLogin: + accv.autologin = source.get_active() From 682cbef30f16c0817dcf52e59feadbc17edbc28c Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 17 Jun 2009 11:50:59 +0200 Subject: [PATCH 140/147] Fixed typo --- amsn2/core/account_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index ceef4b67..8765bd67 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -171,7 +171,7 @@ def loadAccount(self, dir): accview.save_password = True #autoconnect saveAutoConnect = account.find("autoconnect") - if saveAutoConnect == "False": + if saveAutoConnect.text == "False": accview.autologin = False else: accview.autologin = True From eb841f5af5c41692d3c63b1bb205356f3537192c Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 17 Jun 2009 12:06:37 +0200 Subject: [PATCH 141/147] [GTK] Don't login if any user/password is empty. Also some minor changes. --- amsn2/gui/front_ends/gtk/login.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index 46fce1a1..cd710fd7 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -167,9 +167,7 @@ def __init__(self, amsn_core, parent): self.pack_start(checkAlign, True, False) self.pack_start(button_box, True, False) - self.show_all() self._main_win.set_view(self) - self.user.grab_focus() def __animation(self): path = os.path.join("amsn2", "themes", "default", "images", @@ -193,6 +191,11 @@ def __login_clicked(self, *args): self.signin() def show(self): + if self.user.get_active_text() == "": + self.user.grab_focus() + elif self.password.get_text() == "": + self.password.grab_focus() + self.show_all() def hide(self): @@ -232,6 +235,14 @@ def setAccounts(self, accountviews): self.signin() def signin(self): + + if self.user.get_active_text() == "": + self.user.grab_focus() + return + elif self.password.get_text() == "": + self.password.grab_focus() + return + email = self.user.get_active_text() accv = [accv for accv in self._account_views if accv.email == email] if not accv: From 64654bee378751457732aa7bec792d4547c1784d Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 17 Jun 2009 12:48:53 +0200 Subject: [PATCH 142/147] Removed useless code. GUIManager does that stuff. Also set gtk as default frontend. --- amsn2.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/amsn2.py b/amsn2.py index 759c8680..e0775d09 100755 --- a/amsn2.py +++ b/amsn2.py @@ -12,32 +12,8 @@ if __name__ == '__main__': account = None passwd = None - default_front_end = "console" + default_front_end = "gtk" - # Detect graphical toolkit available. - # Format - 'default_front_end : module name' - # cocoa > efl > qt4 > gtk > console - toolkits = {'cocoa' : 'amsn2.gui.front_ends.cocoa', - 'elf' : 'ecore', - 'qt4' : 'PyQt4.QtGui', - 'gtk' : 'gtk', - 'console' : None} - for toolkit in toolkits: - try: - module_name = toolkits[toolkit] - module = __import__(module_name) - default_front_end = toolkit - vars()[module_name] = module - # Debug - # print 'Imported toolkit "%s" with module "%s"' % (toolkit, module) - break - except ImportError: - # Debug - # print 'Couldn\'t import %s - doesn\'t exist!' % module_name - pass - except TypeError: - pass - parser = optparse.OptionParser() parser.add_option("-a", "--account", dest="account", default=None, help="The account's username to use") From e5dec1da798d0761b2686407ad69641442fb59f8 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 17 Jun 2009 16:44:47 +0200 Subject: [PATCH 143/147] [GTK] Added new getAccountViewFromEmail method --- amsn2/gui/front_ends/gtk/login.py | 40 +++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/login.py b/amsn2/gui/front_ends/gtk/login.py index cd710fd7..eb3e2f46 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -205,13 +205,11 @@ def hide(self): def __switch_to_account(self, email): print "Switching to account", email - accv = [accv for accv in self._account_views if accv.email == email] + accv = self.getAccountViewFromEmail(email) - if not accv: + if accv is None: accv = AccountView() accv.email = email - else: - accv = accv[0] self.user.get_children()[0].set_text(accv.email) if accv.password: @@ -244,12 +242,11 @@ def signin(self): return email = self.user.get_active_text() - accv = [accv for accv in self._account_views if accv.email == email] - if not accv: + accv = self.getAccountViewFromEmail(email) + + if accv is None: accv = AccountView() accv.email = email - else: - accv = accv[0] accv.password = self.password.get_text() status = self.statusCombo.get_active() @@ -280,13 +277,12 @@ def __on_passwd_comboxEntry_changed(self, entry): def __on_toggled_cb(self, source): - accv = [accv for accv in self._account_views if accv.email == self.user.get_active_text()] + email = self.user.get_active_text() + accv = self.getAccountViewFromEmail(email) - if not accv: + if accv is None: accv = AccountView() - accv.email = self.user.get_active_text() - else: - accv = accv[0] + accv.email = email if source is self.rememberMe: accv.save = source.get_active() @@ -297,3 +293,21 @@ def __on_toggled_cb(self, source): self.autoLogin.set_sensitive(source.get_active()) elif source is self.autoLogin: accv.autologin = source.get_active() + + + def getAccountViewFromEmail(self, email): + """ + Search in the list of account views and return the view of the given email + + @type email: str + @param email: email to find + @rtype: AccountView + @return: Returns AccountView if it was found, otherwise return None + """ + + accv = [accv for accv in self._account_views if accv.email == email] + + if len(accv) == 0: + return None + else: + return accv[0] From 6b4db547032bfb3a24566f2d4d651bfe235815a9 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 17 Jun 2009 17:14:22 +0200 Subject: [PATCH 144/147] [GTK] Improved usability changing PSM/Username. It's weird, I think I had already committed it. :? --- amsn2/gui/front_ends/gtk/contact_list.py | 46 +++++++----------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/amsn2/gui/front_ends/gtk/contact_list.py b/amsn2/gui/front_ends/gtk/contact_list.py index 7f4c2843..99d65404 100644 --- a/amsn2/gui/front_ends/gtk/contact_list.py +++ b/amsn2/gui/front_ends/gtk/contact_list.py @@ -215,7 +215,7 @@ def onStatusChanged(self, combobox): def __on_btnNicknameClicked(self, source): self.__switchToInput(source) - + def __on_btnPsmClicked(self, source): self.__switchToInput(source) @@ -224,6 +224,12 @@ def __switchToInput(self, source): #label = self.btnNickname.get_child() source.remove(source.get_child()) entry = gtk.Entry() + + if source is self.btnNickname: + entry.set_text(str(self._myview.nick)) + elif source is self.btnPsm: + entry.set_text(str(self._myview.psm)) + source.add(entry) entry.show() entry.grab_focus() @@ -245,30 +251,30 @@ def __switchFromInput(self, source, isNew): to the uneditable label state. """ if(isNew): - if(source == self.btnNickname.get_child()): + if source is self.btnNickname.get_child(): newText = source.get_text() strv = StringView() strv.appendText(newText) self._myview.nick = strv - elif (source == self.btnPsm.get_child()): + elif source is self.btnPsm.get_child(): newText = source.get_text() strv = StringView() strv.appendText(newText) self._myview.psm = strv else: - if(source == self.btnNickname.get_child()): # User discards input + if source is self.btnNickname.get_child(): # User discards input newText = self.nicklabel.get_text() # Old nickname - elif(source == self.btnPsm.get_child()): + elif source is self.btnPsm.get_child(): newText = self.psmlabel.get_text() parentWidget = source.get_parent() currWidget = parentWidget.get_child() currWidget.disconnect(self.focusOutId) # Else we trigger focus-out-event; segfault. - + parentWidget.remove(currWidget) entry = gtk.Label() entry.set_markup(newText) - + parentWidget.add(entry) entry.show() parentWidget.set_relief(gtk.RELIEF_NONE) # remove cool elevated effect @@ -296,32 +302,6 @@ def __onDisplayClicked(self, source): chooser.destroy() - def __on_btnPsmClicked(self, source): - self.__switchToPsmInput() - - def __switchToPsmInput(self): - """ Switches the psm button into a text area for editing of the psm.""" - - self.btnPsm.get_child().destroy() - entry = gtk.Entry() - entry.set_text(str(self._myview.psm)) - self.btnPsm.add(entry) - entry.show() - entry.connect("activate", self.__switchFromPsmInput) - #TODO: If user press ESC then destroy gtk.Entry - - def __switchFromPsmInput(self, source): - """ When in the editing state of psm, change back to the uneditable - label state. - """ - strv = StringView() - strv.appendText(source.get_text()) - self._myview.psm = strv - self.btnPsm.get_child().destroy() - entry = self.psmlabel - self.btnPsm.add(entry) - entry.show() - class aMSNContactListWidget(base.aMSNContactListWidget, gtk.TreeView): def __init__(self, amsn_core, parent): """Constructor""" From 7ab95ba0527b4caa118b5b2b2294e6ee8020d201 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sat, 20 Jun 2009 20:27:43 +0200 Subject: [PATCH 145/147] Tiny indentation fix :) --- amsn2/core/contactlist_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amsn2/core/contactlist_manager.py b/amsn2/core/contactlist_manager.py index d9ea0ac8..6157cd17 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -31,7 +31,7 @@ def onContactChanged(self, papyon_contact): #TODO: update the group view def onContactDPChanged(self, papyon_contact): - """ Called when a contact changes its Display Picture. """ + """ Called when a contact changes its Display Picture. """ #TODO: add local cache for DPs #Request the DP... From c9ffb5170cf8da7c68c6871615c91d69e7a43012 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sun, 21 Jun 2009 09:18:23 +0800 Subject: [PATCH 146/147] More tiny fixes Signed-off-by: Boris Faure --- amsn2/backend/backend.py | 1 + amsn2/backend/defaultbackend.py | 2 +- amsn2/core/account_manager.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/amsn2/backend/backend.py b/amsn2/backend/backend.py index dc4b6483..dfb5a2fb 100644 --- a/amsn2/backend/backend.py +++ b/amsn2/backend/backend.py @@ -23,6 +23,7 @@ def setBackendForFunc(self, funcname, backendname): self.__setattr__(funcname, self.__missingFunc) def switchToBackend(self, backend): + self.setBackendForFunc('getPassword', backend) self.setBackendForFunc('setPassword', backend) self.setBackendForFunc('saveConfig', backend) self.setBackendForFunc('loadConfig', backend) diff --git a/amsn2/backend/defaultbackend.py b/amsn2/backend/defaultbackend.py index d8fee172..940cbd22 100644 --- a/amsn2/backend/defaultbackend.py +++ b/amsn2/backend/defaultbackend.py @@ -20,7 +20,7 @@ def setPassword(password, root_section): return elmt -def saveConfig(account, config, name): +def saveConfig(account, config): #TODO: improve root_section = Element("aMSNConfig") for e in config._config: diff --git a/amsn2/core/account_manager.py b/amsn2/core/account_manager.py index d60f20ea..cff8bafb 100644 --- a/amsn2/core/account_manager.py +++ b/amsn2/core/account_manager.py @@ -163,7 +163,7 @@ def loadAccount(self, dir): if passwordElmt is None: accview.password = None else: - accview.password = self.core._backend_manager.getPassword(passwordElmt) + accview.password = self._core._backend_manager.getPassword(passwordElmt) #save_password savePassElmt = account.find("save_password") if savePassElmt.text == "False": From 3b223746ac2874e3d2ca566f0395f72ae4a413e0 Mon Sep 17 00:00:00 2001 From: Boris 'billiob' Faure Date: Sun, 21 Jun 2009 23:55:20 +0200 Subject: [PATCH 147/147] Remove debug output --- amsn2/protocol/events/contact.py | 1 - 1 file changed, 1 deletion(-) diff --git a/amsn2/protocol/events/contact.py b/amsn2/protocol/events/contact.py index 21a2ab36..1bdb54e0 100644 --- a/amsn2/protocol/events/contact.py +++ b/amsn2/protocol/events/contact.py @@ -22,7 +22,6 @@ def on_contact_current_media_changed(self, contact): def on_contact_msn_object_changed(self, contact): # if the msnobject has been removed, just remove the buddy's DP - print 'hhhhh %s' % contact.msn_object if contact.msn_object is None: self._contact_manager.onContactDPChanged(contact) return