diff --git a/.gitignore b/.gitignore index ab43a7d2..04a094d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +*.pyo build/ *~ .project @@ -8,3 +9,5 @@ build/ .svn/ Debug/ ui_* +.*.swp +pymsn/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..01e1b92c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "papyon"] + path = papyon + url = git://github.com/Kjir/papyon.git diff --git a/README b/README index 00ef9737..52d23649 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 (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... You can type ./amsn2.py --help for more info... and the front ends can be selected with -f : ./amsn2.py -f efl @@ -21,6 +24,7 @@ Once done, go to ./e17_src/BINDINGS/python and type PKG_CONFIG_PATH=/opt/e17/lib/pkgconfig ./build-all.sh /usr (this will build and install the python extensions into /usr/python2.X/...) 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: @@ -31,3 +35,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. diff --git a/amsn2.py b/amsn2.py old mode 100644 new mode 100755 index bfb9f7d8..e0775d09 --- a/amsn2.py +++ b/amsn2.py @@ -2,38 +2,17 @@ import sys import os import optparse -sys.path.insert(0, "./pymsn") +os.chdir(os.path.dirname(os.path.abspath(sys.argv[0]))) +sys.path.insert(0, "./papyon") +import locale +locale.setlocale(locale.LC_ALL, '') from amsn2.core import aMSNCore if __name__ == '__main__': account = None passwd = None - default_front_end = "console" - - # Detect graphical toolkit available. - # Format - 'default_front_end : module name' - # cocoa > efl > qt4 > gtk > console - toolkits = {'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) - 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 + default_front_end = "gtk" parser = optparse.OptionParser() parser.add_option("-a", "--account", dest="account", 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..47a88d24 --- /dev/null +++ b/amsn2/backend/__init__.py @@ -0,0 +1,2 @@ +from backend import aMSNBackendManager +__all__ = ['aMSNBackendManager', 'defaultbackend'] diff --git a/amsn2/backend/backend.py b/amsn2/backend/backend.py new file mode 100644 index 00000000..dfb5a2fb --- /dev/null +++ b/amsn2/backend/backend.py @@ -0,0 +1,33 @@ +"""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.switchToBackend('nullbackend') + + 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) + self.__setattr__(funcname, f) + except AttributeError: + 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) + + 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 new file mode 100644 index 00000000..940cbd22 --- /dev/null +++ b/amsn2/backend/defaultbackend.py @@ -0,0 +1,57 @@ +""" Backend used to save the config on the home directory of the user """ + +import os +"""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 amsn2.core.config import aMSNConfig + +def getPassword(passwdElmt): + return passwdElmt.text + +def setPassword(password, root_section): + elmt = SubElement(root_section, "password", backend='DefaultBackend') + elmt.text = password + return elmt + + +def saveConfig(account, config): + #TODO: improve + 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.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") + except IOError: + return c + 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) + 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/__init__.py b/amsn2/core/__init__.py index 6a102930..d52ef2a2 100644 --- a/amsn2/core/__init__.py +++ b/amsn2/core/__init__.py @@ -1,6 +1,8 @@ from amsn import * -from profile 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 new file mode 100644 index 00000000..cff8bafb --- /dev/null +++ b/amsn2/core/account_manager.py @@ -0,0 +1,234 @@ +import os +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 + +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 + """ + #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 + self.backend_manager = core._backend_manager + self.lock() + self.load() + + def signOut(self): + if self.do_save: + self.save() + self.unlock() + + def lock(self): + #TODO + pass + + def unlock(self): + #TODO + pass + + def load(self): + #TODO: + self.config = self.backend_manager.loadConfig(self) + + def save(self): + if not os.path.isdir(self.account_dir): + os.makedirs(self.account_dir, 0700) + 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") + #email + emailElmt = SubElement(root_section, "email") + emailElmt.text = self.view.email + #nick + nick = str(self.view.nick) + nickElmt = SubElement(root_section, "nick") + nickElmt.text = nick + #presence + presenceElmt = SubElement(root_section, "presence") + presenceElmt.text = self.view.presence + #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", + backend='DefaultBackend') + #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") + xml_tree = ElementTree(root_section) + xml_tree.write(accpath, encoding='utf-8') + + +class aMSNAccountManager(object): + """ aMSNAccountManager : The account manager that takes care of storing + and retreiving all the account. + """ + 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": + 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, 0700) + except : + pass + + self.reload() + + if options.account is not None: + pv = [p for p in self.accountviews if p.email == options.account] + if pv: + pv = pv[0] + self.accountviews.remove(pv) + else: + pv = AccountView() + pv.email = options.account + pv.password = options.password + self.accountviews.insert(0, pv) + + def reload(self): + self.accountviews = [] + 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: + self.accountviews.append(accv) + + + def loadAccount(self, dir): + accview = None + accpath = os.path.join(dir, "account.xml") + accfile = file(accpath, "r") + root_tree = ElementTree(file=accfile) + accfile.close() + account = root_tree.getroot() + if account.tag == "aMSNAccount": + accview = AccountView() + #email + emailElmt = account.find("email") + if emailElmt is None: + return None + accview.email = emailElmt.text + #nick + nickElmt = account.find("nick") + if nickElmt is None: + return None + if nickElmt.text: + accview.nick.appendText(nickElmt.text) + #TODO: parse... + #presence + presenceElmt = account.find("presence") + if presenceElmt is None: + return None + accview.presence = presenceElmt.text + #password + passwordElmt = account.find("password") + if passwordElmt is None: + 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.text == "False": + accview.autologin = False + else: + accview.autologin = True + #TODO: use backend & all + #dp + dpElmt = 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)] + + def signinToAccount(self, accountview): + """ + @type accountview: AccountView + @rtype: aMSNAccount + """ + 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: + # TODO: accdir should be a tmp dir + accdir = None + acc = aMSNAccount(self._core, accountview, accdir) + acc.lock() + 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 a8f412b1..2500aeed 100644 --- a/amsn2/core/amsn.py +++ b/amsn2/core/amsn.py @@ -18,16 +18,18 @@ # 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 amsn2.backend import aMSNBackendManager +import papyon from views import * +from account_manager import * from contactlist_manager import * from conversation_manager import * from oim_manager import * from theme_manager import * - +from personalinfo_manager import * +from event_manager import * class aMSNCore(object): def __init__(self, options): @@ -41,39 +43,68 @@ 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._event_manager = aMSNEventManager(self) 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._backend_manager = aMSNBackendManager() + self._account_manager = aMSNAccountManager(self, options) + self._account = None self._theme_manager = aMSNThemeManager() self._contactlist_manager = aMSNContactListManager(self) self._oim_manager = aMSNOIMManager(self) self._conversation_manager = aMSNConversationManager(self) + self._personalinfo_manager = aMSNPersonalInfoManager(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"} + + 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(); 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) + self._main = self._gui.gui.aMSNMainWindow(self) + 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 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") @@ -89,27 +120,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.setAccounts(self._account_manager.getAvailableAccountViews()) splash.hide() self._main.setTitle("aMSN 2 - Login") @@ -121,48 +132,68 @@ def mainWindowShown(self): def getMainWindow(self): return self._main - def addProfile(self, account): - return self._profile_manager.addProfile(account) + def signinToAccount(self, login_window, accountview): + """ + @type login_window: aMSNLoginWindow + @type accountview: AccountView + """ - def signinToAccount(self, login_window, profile): - print "Signing in to account %s" % (profile.email) - profile.login = login_window - profile.client = protocol.Client(self, profile) - self._profile = profile - profile.client.connect() + print "Signing in to account %s" % (accountview.email) + self._account = self._account_manager.signinToAccount(accountview) + self._account.login = login_window + self._account.client = protocol.Client(self, self._account) + self._account.client.connect(accountview.email, accountview.password) - def connectionStateChanged(self, profile, state): + def connectionStateChanged(self, account, state): + """ + @type account: aMSNAccount + @type state: L{papyon.event.ClientState} + @param state: New state of the Client. + """ status_str = \ { - pymsn.event.ClientState.CONNECTING : 'Connecting to server...', - pymsn.event.ClientState.CONNECTED : 'Connected', - pymsn.event.ClientState.AUTHENTICATING : 'Authentificating...', - 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: + account.login.onConnecting((state + 1)/ 7., status_str[state]) + elif state == papyon.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._personalinfo_manager.setAccount(account) + 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): + if self._account is not None: + self._account.signOut() self._loop.quit() def createMainMenuView(self): diff --git a/amsn2/core/config.py b/amsn2/core/config.py new file mode 100644 index 00000000..7ddcff31 --- /dev/null +++ b/amsn2/core/config.py @@ -0,0 +1,35 @@ + + + +class aMSNConfig: + 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 9fadbe35..6157cd17 100644 --- a/amsn2/core/contactlist_manager.py +++ b/amsn2/core/contactlist_manager.py @@ -1,68 +1,58 @@ from views import * import os import tempfile -import pymsn +import papyon 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 = [[], [], [], []] + """ + @type core: aMSNCore + """ - self._contacts = {} + self._core = core + self._em = core._event_manager + self._contacts = {} #Dictionary where every contact_uid has an associated aMSNContact self._groups = {} - self._pymsn_addressbook = None + 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 onContactChanged(self, papyon_contact): + """ Called when a contact changes either its presence, nick, psm or current media.""" - def onContactPresenceChanged(self, pymsn_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) + self._em.emit(self._em.events.CONTACTVIEW_UPDATED, cv) #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... - 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, + 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.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._account.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 +70,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: @@ -95,11 +85,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,21 +100,30 @@ 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, 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 + """ - def getContact(self, cid, pymsn_contact=None): #TODO: should raise UnknownContact or sthg like that try: - return self._contacts[cid] + return self._contacts[uid] except KeyError: - if pymsn_contact is not None: - c = aMSNContact(self._core, pymsn_contact) - self._contacts[cid] = c - self.emit(self.AMSNCONTACT_UPDATED, c) + if papyon_contact is not None: + c = aMSNContact(self._core, papyon_contact) + self._contacts[uid] = c + self._em.emit(self._em.events.AMSNCONTACT_UPDATED, c) return c else: raise ValueError @@ -135,29 +134,45 @@ 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): + """ + @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: + self.dp.load("Theme", "dp_nopic") + else: + self.dp.load("Theme", "dp_loading") + self.fill(core, papyon_contact) + + def fill(self, core, papyon_contact): + """ + Fills the aMSNContact structure. + + @type core: aMSNCore + @type papyon_contact: papyon.profile.Contact + """ - def fill(self, core, pymsn_contact): self.icon = ImageView() - self.icon.load("Theme","buddy_" + core.p2s[pymsn_contact.presence]) - self.dp = ImageView() - #TODO: for the moment, use default dp - self.dp.load("Theme", "dp_nopic") + self.icon.load("Theme","buddy_" + core.p2s[papyon_contact.presence]) 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: getPapyonContact(self, core...) or _papyon_contact? + self._papyon_contact = papyon_contact diff --git a/amsn2/core/conversation.py b/amsn2/core/conversation.py index 1f121f04..b1f77743 100644 --- a/amsn2/core/conversation.py +++ b/amsn2/core/conversation.py @@ -18,13 +18,20 @@ # 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 pymsn +import papyon 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 @@ -33,10 +40,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._account.client, papyon_contacts) else: #From an existing conversation self._conv = conv @@ -71,8 +78,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 @@ -90,7 +96,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(str(msg), formatting) self._conv.send_text_message(message) def sendNudge(self): @@ -105,6 +111,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/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 new file mode 100644 index 00000000..a9fbde24 --- /dev/null +++ b/amsn2/core/event_manager.py @@ -0,0 +1,163 @@ +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): + """ + @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()] + self.events = aMSNEvents() + + def emit(self, event, *args): + """ emit the event """ + # rw callback + for cb in self._events_cbs[event][0]: + #TODO: try except + cb(*args) + + # 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][1].append(callback) + + elif type is 'rw': + 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 """ + 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/amsn2/core/lang.py b/amsn2/core/lang.py index 6475d7aa..30513f7d 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) - + + 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. 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: + + if hasVariant: # 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) - + if fileWasLoaded: + self.lang_code = 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,16 +90,16 @@ 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): + if type(replacements) is dict: # Replace from a dictionary. for key, val in replacements.iteritems(): r = r.replace(key, val) @@ -109,19 +109,12 @@ 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..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 - \ No newline at end of file + diff --git a/amsn2/core/personalinfo_manager.py b/amsn2/core/personalinfo_manager.py new file mode 100644 index 00000000..d695028f --- /dev/null +++ b/amsn2/core/personalinfo_manager.py @@ -0,0 +1,100 @@ +from views import * + +class aMSNPersonalInfoManager: + def __init__(self, core): + """ + @type core: aMSNCore + """ + + self._core = core + self._em = core._event_manager + self._personalinfoview = PersonalInfoView(self) + self._papyon_profile = None + + def setAccount(self, amsn_account): + self._papyon_profile = amsn_account.client.profile + + # set nickname at login + # could be overriden by the one set in the saved account + # TODO: add setting display picture and saved personal message + strv = StringView() + nick = str(amsn_account.view.nick) + if nick and nick != amsn_account.view.email: + strv.appendText(nick) + else: + 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.view.presence + + """ Actions from ourselves """ + def _onNickChanged(self, new_nick): + # TODO: parsing + self._papyon_profile.display_name = str(new_nick) + + def _onPSMChanged(self, new_psm): + # TODO: parsing + self._papyon_profile.personal_message = str(new_psm) + + 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 _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 + + """ 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 + """ Actions from outside """ + def _onNewMail(self, info): + self._em.emit(self._em.events.PERSONALINFO_UPDATED, self._personalinfoview) + + + + + + diff --git a/amsn2/core/profile.py b/amsn2/core/profile.py deleted file mode 100644 index 72f385df..00000000 --- a/amsn2/core/profile.py +++ /dev/null @@ -1,494 +0,0 @@ - -import os -import xml.etree.ElementTree -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 - """ - 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) - - -class aMSNProfileManager(object): - """ aMSNProfileManager : The profile manager that takes care of storing - and retreiving all the profiles for our users. - """ - def __init__(self): - 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) - - 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) - - 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") - - for plugin_name, plugin in plugins.iteritems(): - plugin_section = dictToElement(plugin_name, plugin.config) - plugins_section.append(plugin_section) - - 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 - - ### 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) - - 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) - - ### 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) - - ### Finally loads the Profile - self.profiles[profile.email] = profile - - -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/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/__init__.py b/amsn2/core/views/__init__.py index 4dbfb176..6a99b460 100644 --- a/amsn2/core/views/__init__.py +++ b/amsn2/core/views/__init__.py @@ -5,3 +5,5 @@ from tooltipview import * from messageview import * from imageview import * +from accountview import * +from personalinfoview import * diff --git a/amsn2/core/views/accountview.py b/amsn2/core/views/accountview.py new file mode 100644 index 00000000..770b2cbb --- /dev/null +++ b/amsn2/core/views/accountview.py @@ -0,0 +1,23 @@ + +from imageview import * +from stringview import * + +class AccountView: + def __init__(self): + self.email = None + self.password = None + self.nick = StringView() + self.presence = 'online' + self.dp = ImageView() + + self.save = False + self.save_password = False + 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 diff --git a/amsn2/core/views/contactlistview.py b/amsn2/core/views/contactlistview.py index 77f79dd4..d48a757a 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 @@ -38,7 +42,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/core/views/imageview.py b/amsn2/core/views/imageview.py index 2393edd5..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: @@ -30,4 +34,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/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..7de68f8f 100644 --- a/amsn2/core/views/menuview.py +++ b/amsn2/core/views/menuview.py @@ -6,22 +6,24 @@ 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 - @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 @@ -48,7 +50,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 +73,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 +86,7 @@ def __init__(self, type, label = None, icon = None, accelerator = None, def addItem(self, item): self.items.append(item) - + class MenuView(object): @@ -93,4 +95,4 @@ def __init__(self): def addItem(self, item): self.items.append(item) - + diff --git a/amsn2/core/views/personalinfoview.py b/amsn2/core/views/personalinfoview.py new file mode 100644 index 00000000..34ef3fed --- /dev/null +++ b/amsn2/core/views/personalinfoview.py @@ -0,0 +1,71 @@ +from stringview import * +from imageview import * + +def rw_property(f): + return 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() + self._image = ImageView() + self._presence = 'offline' + + # TODO: get more info, how to manage webcams and mail + self._webcam = None + self._mail_unread = None + + def onDPChangeRequest(self): + self._personalinfo_manager._onDPChangeRequest() + + @rw_property + def nick(): + def fget(self): + return self._nickname + def fset(self, nick): + self._personalinfo_manager._onNickChanged(nick) + return locals() + + @rw_property + def psm(): + def fget(self): + return self._psm + def fset(self, psm): + self._personalinfo_manager._onPSMChanged(psm) + return locals() + + @rw_property + def dp(): + def fget(self): + return self._image + def fset(self, imagev): + self._personalinfo_manager._onDPChanged(imagev) + return locals() + + @rw_property + def current_media(): + def fget(self): + return self._current_media + def fset(self, artist, song): + self._personalinfo_manager._onCMChanged((artist, song)) + return locals() + + @rw_property + def presence(): + def fget(self): + return self._presence + def fset(self, presence): + self._personalinfo_manager._onPresenceChanged(presence) + 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() + 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/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/__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 * diff --git a/amsn2/gui/base/contact_list.py b/amsn2/gui/base/contact_list.py index 341ed0d7..eef5f273 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 + em = amsn_core._event_manager + em.register(em.events.PERSONALINFO_UPDATED, self.myInfoUpdated) def show(self): """ Show the contact list window """ @@ -25,30 +26,31 @@ 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 contactView of the ourself (contains DP, nick, psm, + @type view: PersonalInfoView + @param view: the PersonalInfoView of the ourself (contains DP, nick, psm, currentMedia,...)""" raise NotImplementedError 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 """ @@ -65,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/image.py b/amsn2/gui/base/image.py deleted file mode 100644 index a6460951..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 - \ No newline at end of file diff --git a/amsn2/gui/base/login.py b/amsn2/gui/base/login.py index 7c71af6f..828fab90 100644 --- a/amsn2/gui/base/login.py +++ b/amsn2/gui/base/login.py @@ -8,13 +8,18 @@ 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 setAccounts(self, accountviews): + """ This method will be called when the core needs the login window to + let the user select among some accounts. + + @param accountviews: list of accountviews describing accounts + The first one in the list + should be considered as default. """ raise NotImplementedError def signin(self): @@ -23,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 5607922b..5426fc90 100644 --- a/amsn2/gui/base/main.py +++ b/amsn2/gui/base/main.py @@ -6,8 +6,12 @@ class aMSNMainWindow(object): """ def __init__(self, amsn_core): + """ + @type amsn_core: aMSNCore + """ + pass - + def show(self): raise NotImplementedError @@ -16,6 +20,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..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,14 +14,23 @@ 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): """ This will be called when the core wants to exit """ raise NotImplementedError - + 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 768609b7..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/__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 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/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. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= diff --git a/pymsn/pymsn/service/ContentRoaming/constants.py b/amsn2/gui/front_ends/cocoa/skins.py similarity index 54% rename from pymsn/pymsn/service/ContentRoaming/constants.py rename to amsn2/gui/front_ends/cocoa/skins.py index ef28a654..4f0b09e9 100644 --- a/pymsn/pymsn/service/ContentRoaming/constants.py +++ b/amsn2/gui/front_ends/cocoa/skins.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Johann Prieur +# 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 @@ -16,21 +18,29 @@ # 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"] +import os.path +from amsn2.gui import base + +class Skin(base.Skin): + def __init__(self, core, path): + self._path = path + pass + + def getKey(self, key, default): + pass + + def setKey(self, key, value): + pass -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.""" +class SkinManager(base.SkinManager): + def __init__(self, core): + self._core = core + self.skin = Skin(core, "skins") - NOT_SYNCHRONIZED = 0 - """The service is not synchronized yet""" - SYNCHRONIZING = 1 - """The service is being synchronized""" - SYNCHRONIZED = 2 - """The service is already synchronized""" + 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/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 deleted file mode 100644 index a30778b9..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 6bdd2f25..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 f90ca22c..00000000 --- a/amsn2/gui/front_ends/console/contact_list.py +++ /dev/null @@ -1,123 +0,0 @@ -from amsn2.gui import base -import pymsn - -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 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 0c1b68ed..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 3cc8e807..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 1ce6eeaf..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/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 new file mode 100644 index 00000000..8e2881c3 --- /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 df43ff75..1827b8d1 100644 --- a/amsn2/gui/front_ends/curses/contact_list.py +++ b/amsn2/gui/front_ends/curses/contact_list.py @@ -1,130 +1,172 @@ +from __future__ import with_statement from amsn2.gui import base -import pymsn import curses +from threading import Thread +from threading import Condition +import time -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 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._win = curses.newwin(100, 100, 3, 3) + 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) + self._win.bkgd(curses.color_pair(0)) + self._win.border() + self._clwidget = aMSNContactListWidget(amsn_core, self) 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 __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() - + 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") + 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): + + def __init__(self, amsn_core, parent): + super(aMSNContactListWidget, self).__init__(amsn_core, parent) + self._groups_order = [] + self._groups = {} + 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) + 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 + 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 + self._mod_lock.notify() + + def groupUpdated(self, gView): + # Acquire the lock to do modifications + 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 + self._mod_lock.notify() + + def contactUpdated(self, cView): + # Acquire the lock to do modifications + 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() + + 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,1) + 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: + if i == y - self._selected: + self._win.bkgdset(curses.color_pair(1)) + 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) + 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 - self._selected: + self._win.bkgdset(curses.color_pair(1)) + self._win.insstr(str(self._groups[g].name)) + self._win.bkgdset(curses.color_pair(0)) + self._win.insch(' ') + self._win.insch(curses.ACS_LLCORNER) + self._win.insertln() + i += 1 + self._win.border() + self._win.refresh() + self._modified = False + + + def __thread_run(self): + while True: + # We don't want to repaint too often, once every half second is cool + time.sleep(0.5) + with self._mod_lock: + while not self._modified: + self._mod_lock.wait(timeout=1) + self.__repaint() 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..03e13e8b 100644 --- a/amsn2/gui/front_ends/curses/login.py +++ b/amsn2/gui/front_ends/curses/login.py @@ -4,47 +4,87 @@ 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.bkgd(' ', curses.color_pair(0)) + 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) + self._insert(txt) def edit(self): - return self._txtbox.edit() + 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): + self._password = '' + super(PasswordBox, self).__init__(win, y, x, txt) + + 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.BS): + 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): + def __init__(self, amsn_core, parent): self._amsn_core = amsn_core self.switch_to_profile(None) - self._stdscr = self._amsn_core.getMainWindow()._stdscr - self._win = curses.newwin(20, 100, 5, 5) - + self._stdscr = parent._stdscr + + (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.bkgd(' ', curses.color_pair(1)) 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() - + 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 @@ -57,54 +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, "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): + def onConnecting(self, progress, message): 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() - - diff --git a/amsn2/gui/front_ends/curses/main.py b/amsn2/gui/front_ends/curses/main.py index c57b4ee3..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): @@ -7,20 +8,36 @@ 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() + self._command_line = command.CommandLine(self._stdscr, None) + self.__init_colors() + 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 + + 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) + curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_BLUE) + diff --git a/amsn2/gui/front_ends/curses/main_loop.py b/amsn2/gui/front_ends/curses/main_loop.py index 215ef1f4..26f0ddbe 100644 --- a/amsn2/gui/front_ends/curses/main_loop.py +++ b/amsn2/gui/front_ends/curses/main_loop.py @@ -1,10 +1,12 @@ from amsn2.gui import base import gobject +gobject.threads_init() 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 +16,6 @@ def run(self): except KeyboardInterrupt: self.quit() - def idlerAdd(self, func): gobject.idle_add(func) @@ -24,9 +25,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/mine/splash.py b/amsn2/gui/front_ends/curses/splash.py similarity index 79% rename from amsn2/gui/front_ends/mine/splash.py rename to amsn2/gui/front_ends/curses/splash.py index 8e1f03ab..fd6e4962 100644 --- a/amsn2/gui/front_ends/mine/splash.py +++ b/amsn2/gui/front_ends/curses/splash.py @@ -1,10 +1,12 @@ +from amsn2.gui import base -class aMSNSplashScreen(object): - """ This interface will represent the splashscreen of the UI""" +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): @@ -21,8 +23,4 @@ def setText(self, text): def setImage(self, image): """ Set the image to show in the splashscreen. This is an ImageView object """ - pass - - - 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/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 3c2486d1..6aed062c 100644 --- a/amsn2/gui/front_ends/efl/contact_list.py +++ b/amsn2/gui/front_ends/efl/contact_list.py @@ -4,30 +4,31 @@ import edje import ecore import ecore.evas -import etk +import elementary from image import * from amsn2.core.views import StringView from amsn2.gui import base -import pymsn +import papyon -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 - self._clwidget = aMSNContactListWidget(amsn_core, self) - parent.setChild(self._clwidget) - self._clwidget.show() + elementary.Box.__init__(self, parent) + self._parent.resize_object_add(self) + self.size_hint_weight_set(1.0, 1.0) + 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) @@ -38,36 +39,29 @@ 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) - - 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._skin = amsn_core._skin_manager.skin + elementary.Scroller.__init__(self, parent) + self.size_hint_weight_set(1.0, 1.0) - self._etk_evas_object.evas_object = self._edje - self.add_with_viewport(self._etk_evas_object) + 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); + self._edje.content_set("groups", self.group_holder); + self._edje.size_hint_weight_set(1.0, 1.0) self._edje.show() - self._etk_evas_object.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._etk_evas_object.size_request_set(w,h) - - class ContactHolder(evas.SmartObject): def __init__(self, ecanvas, parent): @@ -102,7 +91,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 +227,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 @@ -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: diff --git a/amsn2/gui/front_ends/efl/efl.py b/amsn2/gui/front_ends/efl/efl.py index 49737726..e517c436 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 image import * from splash import * from skins import * -from chat_window import * +#from chat_window 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 14b10223..5dbf9f28 100644 --- a/amsn2/gui/front_ends/efl/login.py +++ b/amsn2/gui/front_ends/efl/login.py @@ -2,72 +2,64 @@ import edje import ecore import ecore.x -import etk +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) - 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) - # 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._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() + #FIXME: those are not hidden by self._edje.hide() self.password.hide() - self.status.hide() self.username.hide() try: getattr(self, "signin_b") @@ -76,19 +68,29 @@ 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: - self.username.text = self.current_profile.username - self.password.text = 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): - # 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._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 + else: + accv = accv[0] + accv.password = password + + self._amsn_core.signinToAccount(self, accv) def onConnecting(self, progress, message): self._edje.signal_emit("connecting", "") @@ -110,6 +112,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, data): self.signin() diff --git a/amsn2/gui/front_ends/efl/main.py b/amsn2/gui/front_ends/efl/main.py index fd1b3da0..f282abe8 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 @@ -22,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): 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/skins.py b/amsn2/gui/front_ends/efl/skins.py index 15d1a639..19cbded7 100644 --- a/amsn2/gui/front_ends/efl/skins.py +++ b/amsn2/gui/front_ends/efl/skins.py @@ -1,6 +1,7 @@ import os.path +from amsn2.gui import base -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path self._dict = {} @@ -45,7 +46,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/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 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 557b1e81..d010b296 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): @@ -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,63 +244,66 @@ 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 = 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 - 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) - 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() 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) 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: 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 +351,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 9495561b..a5d8dd24 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 # @@ -27,12 +27,13 @@ import pango import gobject -#import pymsn +#import papyon from image import * from amsn2.core.views import StringView from amsn2.core.views import GroupView from amsn2.core.views import ContactView from amsn2.core.views import ImageView +from amsn2.core.views import PersonalInfoView from amsn2.gui import base import common @@ -42,57 +43,68 @@ 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._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.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) 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.psm = gtk.Entry() - + self.btnNickname.connect("clicked",self.__on_btnNicknameClicked) + 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>') - + self.btnPsm = gtk.Button() 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 = {} 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) @@ -100,12 +112,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) @@ -113,10 +125,11 @@ 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() - frameDisplay.add(self.display) + frameDisplay.add(self.btnDisplay) self.evdisplay = gtk.EventBox() self.evdisplay.add(frameDisplay) @@ -129,7 +142,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) @@ -139,98 +151,203 @@ 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 + @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 contactView of the ourself (contains DP, nick, psm, - currentMedia,...)""" - print view.name.toString() + @type view: PersonalInfoView + @param view: ourself (contains DP, nick, psm, currentMedia,...) + """ + # TODO: image, ... + self._myview = view + nk = view.nick + self.nicklabel.set_markup(str(nk)) + psm = view.psm + cm = view.current_media + message = str(psm)+' '+str(cm) + self.psmlabel.set_markup(''+message+'') + self.status.set_active(self.status_values[view.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 papyon, gives an error on setting 'offline' + if key != self._myview.presence: + self._myview.presence = key + + def __on_btnNicknameClicked(self, source): + self.__switchToInput(source) + + def __on_btnPsmClicked(self, source): + self.__switchToInput(source) + + def __switchToInput(self, source): + """ 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() + + 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() + 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 __handleInput(self, source, event): + """ Handle various inputs from the nicknameEntry-box """ + 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 and psm, change back + to the uneditable label state. + """ + if(isNew): + if source is self.btnNickname.get_child(): + newText = source.get_text() + strv = StringView() + strv.appendText(newText) + self._myview.nick = strv + elif source is self.btnPsm.get_child(): + newText = source.get_text() + strv = StringView() + strv.appendText(newText) + self._myview.psm = strv + else: + if source is self.btnNickname.get_child(): # User discards input + newText = self.nicklabel.get_text() # Old nickname + 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 + + 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): """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 @@ -238,10 +355,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 @@ -251,9 +368,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 @@ -263,64 +380,66 @@ 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())) - + str(groupview.name))) + 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): + """ + @type contactview: 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) 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/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 old mode 100755 new mode 100644 index 31947cf8..f4396df4 --- 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..47786e56 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,11 +24,23 @@ import gtk 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) + 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,7 +52,18 @@ def _loadFromFilename(self, filename, view, index): except Exception, e: 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: @@ -50,4 +73,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 6907e052..eb3e2f46 100644 --- a/amsn2/gui/front_ends/gtk/login.py +++ b/amsn2/gui/front_ends/gtk/login.py @@ -24,18 +24,18 @@ import os import gtk import gobject +import string from image import * -from amsn2.core.views import ImageView +from amsn2.core.views import AccountView, ImageView -class aMSNLoginWindow(gtk.VBox): +class aMSNLoginWindow(gtk.VBox, base.aMSNLoginWindow): 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 @@ -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,18 +87,25 @@ 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) # 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]) - + + iconCell = gtk.CellRendererPixbuf() iconCell.set_property('xalign', 0.0) txtCell = gtk.CellRendererText() @@ -105,7 +114,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) @@ -133,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) @@ -154,11 +167,8 @@ 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() - #self.switch_to_profile(None) - + def __animation(self): path = os.path.join("amsn2", "themes", "default", "images", "login_screen", "cube") @@ -181,30 +191,123 @@ def __login_clicked(self, *args): self.signin() def show(self): - pass + 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): 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._password = self.current_profile.password - - + def __switch_to_account(self, email): + print "Switching to account", email + + accv = self.getAccountViewFromEmail(email) + + if accv is None: + accv = AccountView() + accv.email = email + + self.user.get_children()[0].set_text(accv.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): - 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.keys()[i] - self._amsn_core.signinToAccount(self, self.current_profile) + + 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 = self.getAccountViewFromEmail(email) + + if accv is None: + accv = AccountView() + accv.email = email + + accv.password = self.password.get_text() + status = self.statusCombo.get_active() + for key in self.status_values: + if self.status_values[key] == status: + break + 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_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): + + email = self.user.get_active_text() + accv = self.getAccountViewFromEmail(email) + + if accv is None: + accv = AccountView() + accv.email = email + + 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() + + + 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] diff --git a/amsn2/gui/front_ends/gtk/main.py b/amsn2/gui/front_ends/gtk/main.py index a0924140..456d1993 100644 --- a/amsn2/gui/front_ends/gtk/main.py +++ b/amsn2/gui/front_ends/gtk/main.py @@ -1,47 +1,93 @@ from amsn2.gui import base +from amsn2.core.views import MenuItemView import skins import gtk class aMSNMainWindow(base.aMSNMainWindow): + """ + @ivar main_win: + @type main_win: gtk.Window + """ main_win = None - + def __init__(self, amsn_core): self._amsn_core = 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() 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 + @type menu: 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() + + 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() - + 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..6a91aba8 100644 --- a/amsn2/gui/front_ends/gtk/skins.py +++ b/amsn2/gui/front_ends/gtk/skins.py @@ -1,69 +1,71 @@ # -*- 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 # #=================================================== +from amsn2.gui import base + import os -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = 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): @@ -76,7 +78,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/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 deleted file mode 100644 index 9e585598..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 0b7fe2a5..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 d075efa8..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 27014cc4..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 778fcad8..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/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..c49a66da 100644 --- a/amsn2/gui/front_ends/qt4/chat_window.py +++ b/amsn2/gui/front_ends/qt4/chat_window.py @@ -23,14 +23,19 @@ 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): 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 +43,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,102 +65,102 @@ 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")+(""))) + 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_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..24c3b5fb 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 @@ -38,7 +43,7 @@ def __init__(self, amsn_core, parent): def show(self): self._clwidget.show() - + def hide(self): self._clwidget.hide() @@ -53,8 +58,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 +74,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,55 +88,55 @@ 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())) + 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): 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())) - + + if str(itm.data(40)) == group.uid: + + 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) 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) @@ -154,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]) @@ -163,24 +168,24 @@ 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) - + 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/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 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/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 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/skins.py b/amsn2/gui/front_ends/qt4/skins.py index 57b53530..4f0b09e9 100644 --- a/amsn2/gui/front_ends/qt4/skins.py +++ b/amsn2/gui/front_ends/qt4/skins.py @@ -19,8 +19,9 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os.path +from amsn2.gui import base -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path pass @@ -33,7 +34,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/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/chat_window.py b/amsn2/gui/front_ends/web/chat_window.py index 8aafb70e..98b1fe66 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, str(messageview.toStringView())]) 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..5628864c 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): @@ -45,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): @@ -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,7 +100,8 @@ 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._main.send("groupUpdated",[groupView.uid,",".join(groupView.contact_ids),groupView.name.toString()]) + self.groups[groupView.uid]=groupView + self._main.send("groupUpdated",[groupView.uid,",".join(groupView.contact_ids),str(groupView.name)]) pass def contactUpdated(self, contactView): @@ -103,6 +114,7 @@ def contactUpdated(self, contactView): call will be made with the new order of the contacts in the affects groups. """ - self._main.send("contactUpdated",[contactView.uid,contactView.name.toString()]) + self.contacts[contactView.uid]=contactView + self._main.send("contactUpdated", [contactView.uid, str(contactView.name)]) pass 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..40c9b72e 100644 --- a/amsn2/gui/front_ends/web/main.py +++ b/amsn2/gui/front_ends/web/main.py @@ -20,17 +20,21 @@ 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.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/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/skins.py b/amsn2/gui/front_ends/web/skins.py index 36474730..ddf93d3d 100644 --- a/amsn2/gui/front_ends/web/skins.py +++ b/amsn2/gui/front_ends/web/skins.py @@ -1,6 +1,7 @@ import os.path +from amsn2.gui import base -class Skin(object): +class Skin(base.Skin): def __init__(self, core, path): self._path = path pass @@ -13,7 +14,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/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 235c4f1f..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/gui/gui.py b/amsn2/gui/gui.py index a3e87094..67fb2f58 100644 --- a/amsn2/gui/gui.py +++ b/amsn2/gui/gui.py @@ -10,29 +10,34 @@ def __str__(self): 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 - + 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(); - + 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 old mode 100755 new mode 100644 index 62c857a5..90821a92 --- 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 old mode 100755 new mode 100644 index dbb4bb02..403c6fa2 --- 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/__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 3684ac32..47175cc7 100644 --- a/amsn2/protocol/client.py +++ b/amsn2/protocol/client.py @@ -1,23 +1,54 @@ +# -*- 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 pymsn -import pymsn.event +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 * +from events.profile import * +from events.mailbox import * - -class ClientEvents(pymsn.event.ClientEventInterface): - def __init__(self, client, amsn_core): +class Client(papyon.Client): + def __init__(self, amsn_core, account): + self._amsn_account = account self._amsn_core = amsn_core - pymsn.event.ClientEventInterface.__init__(self, client) + 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) + 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) + self._profile_events_handler = ProfileEvents(self, self._amsn_core._personalinfo_manager) + self._mailbox_events_handler = MailboxEvents(self, self._amsn_core) + + def connect(self, email, password): + self.login(email, password) - 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 changeNick(self, nick): + self.profile.display_name = str(nick) - def on_client_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - - + def changeMessage(self, message): + self.profile.personal_message = str(message) diff --git a/amsn2/protocol/contact.py b/amsn2/protocol/contact.py deleted file mode 100644 index 83949b68..00000000 --- a/amsn2/protocol/contact.py +++ /dev/null @@ -1,13 +0,0 @@ - -import pymsn -import pymsn.event - -class ContactEvents(pymsn.event.ContactEventInterface): - - def __init__(self, client, contact_manager): - self._contact_manager = contact_manager - pymsn.event.ContactEventInterface.__init__(self, client) - - def on_contact_presence_changed(self, contact): - self._contact_manager.onContactPresenceChanged(contact) - 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 89% rename from amsn2/protocol/addressbook.py rename to amsn2/protocol/events/addressbook.py index add86648..6f4fc7d7 100644 --- a/amsn2/protocol/addressbook.py +++ b/amsn2/protocol/events/addressbook.py @@ -18,14 +18,14 @@ # 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/events/client.py b/amsn2/protocol/events/client.py new file mode 100644 index 00000000..2db78d38 --- /dev/null +++ b/amsn2/protocol/events/client.py @@ -0,0 +1,17 @@ + +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_account, 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 new file mode 100644 index 00000000..1bdb54e0 --- /dev/null +++ b/amsn2/protocol/events/contact.py @@ -0,0 +1,41 @@ + +import papyon +import papyon.event + +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.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): + # if the msnobject has been removed, just remove the buddy's DP + 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) + + 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 + diff --git a/amsn2/protocol/conversation.py b/amsn2/protocol/events/conversation.py similarity index 87% rename from amsn2/protocol/conversation.py rename to amsn2/protocol/events/conversation.py index 181ba711..563a7633 100644 --- a/amsn2/protocol/conversation.py +++ b/amsn2/protocol/events/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) @@ -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) diff --git a/amsn2/protocol/invite.py b/amsn2/protocol/events/invite.py similarity index 57% rename from amsn2/protocol/invite.py rename to amsn2/protocol/events/invite.py index 276a0783..3c63cd35 100644 --- a/amsn2/protocol/invite.py +++ b/amsn2/protocol/events/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/events/mailbox.py b/amsn2/protocol/events/mailbox.py new file mode 100644 index 00000000..b6851a1f --- /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 diff --git a/amsn2/protocol/oim.py b/amsn2/protocol/events/oim.py similarity index 86% rename from amsn2/protocol/oim.py rename to amsn2/protocol/events/oim.py index d678d3bc..6ef5c414 100644 --- a/amsn2/protocol/oim.py +++ b/amsn2/protocol/events/oim.py @@ -18,14 +18,14 @@ # 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/events/profile.py b/amsn2/protocol/events/profile.py new file mode 100644 index 00000000..bc08ccdc --- /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.onPSMUpdated(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) diff --git a/amsn2/protocol/protocol.py b/amsn2/protocol/protocol.py deleted file mode 100644 index cf968d54..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 pymsn -import pymsn.event -from client import * -from contact import * -from invite import * -from oim import * -from addressbook import * - -class Client(pymsn.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) - - 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() - diff --git a/amsn2/themes/default.edj b/amsn2/themes/default.edj index d6cb152d..43b32bd6 100644 Binary files a/amsn2/themes/default.edj and b/amsn2/themes/default.edj differ diff --git a/clean b/clean index f249cb7c..6376d080 100755 --- a/clean +++ b/clean @@ -1 +1,8 @@ -find . -name "*.pyc" | xargs rm +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 + diff --git a/papyon b/papyon new file mode 160000 index 00000000..5059b889 --- /dev/null +++ b/papyon @@ -0,0 +1 @@ +Subproject commit 5059b889b35c5ffb8eeab7d123d0f8e3b98645a1 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/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 b497b458..00000000 --- a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py +++ /dev/null @@ -1,408 +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 == (13,7): - network_id = NetworkID.EXTERNAL - - 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() 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/'):