Changeset 1777

Show
Ignore:
Timestamp:
02/20/07 11:50:11 (22 months ago)
Author:
abaumann
Message:

first pass at updating moto-sync to new API and python wrappers
this is incomplete, and guaranteed not to work :)

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • plugins/moto-sync/motosync.py

    r1719 r1777  
    44""" 
    55 
    6 # Copyright (C) 2006  Andrew Baumann <andrewb@cse.unsw.edu.au> 
     6# Copyright (C) 2006-2007  Andrew Baumann <andrewb@cse.unsw.edu.au> 
    77# 
    88#  This program is free software; you can redistribute it and/or modify 
     
    368368 
    369369 
    370 class OpenSyncError(Exception): 
    371     """Simple exception class carrying a message and an opensync error number. 
    372  
    373     These errors are reported back to opensync by the stdexceptions decorator 
    374     on SyncClass methods. 
    375     """ 
    376     def __init__(self, msg, errnum=opensync.ERROR_GENERIC): 
    377         Exception.__init__(self) 
    378         self.msg = msg 
    379         self.num = errnum 
    380     def __str__(self): 
    381         return self.msg 
    382     def report(self, context): 
    383         """Report myself as an error to the given OSyncContext object.""" 
    384         context.report_error(self.num, self.msg) 
    385  
    386  
    387370class UnsupportedDataError(Exception): 
    388371    """Exception raised by PhoneEntry classes when the data cannot be stored.""" 
     
    400383    """ 
    401384    def __init__(self, device): 
     385        self.devstr = device 
    402386        self.__calendar_open = False 
    403387        self.__fd = self.__btsock = None 
    404  
    405         if BT_MAC_RE.match(device): 
     388        self.max_events = None 
     389        self.num_events = None 
     390        self.event_name_len = None 
     391        self.event_max_exceptions = None 
     392        self.min_contact_pos = None 
     393        self.max_contact_pos = None 
     394        self.contact_data_len = None 
     395        self.contact_name_len = None 
     396 
     397    def connect(self): 
     398        if BT_MAC_RE.match(self.devstr): 
    406399            assert(USE_BLUETOOTH_MODULE, 
    407400                   "MAC address specified, but pybluez module is not available") 
     
    409402            # search for the port to use on the device 
    410403            port = BT_DEFAULT_CHANNEL 
    411             found = bluetooth.find_service(name=BT_SERVICE_NAME, address=device) 
     404            found = bluetooth.find_service(name=BT_SERVICE_NAME, address=self.devstr) 
    412405            if found: 
    413406                assert(found[0]['protocol'] == 'RFCOMM') 
    414407                port = found[0]['port'] 
    415408            self.__btsock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) 
    416             self.__btsock.connect((device, port)) 
    417         else: 
    418             self.__fd = os.open(device, os.O_RDWR) 
     409            self.__btsock.connect((self.devstr, port)) 
     410        else: 
     411            self.__fd = os.open(self.devstr, os.O_RDWR) 
    419412            if USE_TTY_MODULE: 
    420413                tty.setraw(self.__fd) 
     
    443436        self.contact_name_len = namelen 
    444437 
    445     def __del__(self): 
     438    def disconnect(self): 
    446439        self.close_calendar() 
    447440        if self.__fd: 
    448441            os.close(self.__fd) 
     442            self.__fd = None 
    449443        if self.__btsock: 
    450444            self.__btsock.close() 
     445            self.__btsock = None 
    451446 
    452447    def read_serial(self): 
     
    508503            for (expos, exnum, extype) in self.__parse_results('MDBRE', data): 
    509504                if extype != 1: # haven't seen anything else 
    510                     raise OpenSyncError('unexpected exception type %d' % extype) 
     505                    raise opensync.Error('unexpected exception type %d' % extype) 
    511506                if not exceptions.has_key(expos): 
    512507                    exceptions[expos] = [] 
     
    637632        debug('<-- ' + ret) 
    638633        if c == '': # EOF, shouldn't happen 
    639             raise OpenSyncError('Unexpected EOF talking to phone', 
    640                                 opensync.ERROR_IO_ERROR) 
     634            raise opensync.Error('Unexpected EOF talking to phone', 
     635                                 opensync.ERROR_IO_ERROR) 
    641636        return ret 
    642637 
     
    664659            return ret 
    665660        else: 
    666             raise OpenSyncError("Error in phone command '%s'" % cmd, 
    667                                 opensync.ERROR_IO_ERROR) 
     661            raise opensync.Error("Error in phone command '%s'" % cmd, 
     662                                 opensync.ERROR_IO_ERROR) 
    668663 
    669664    def __parse_results(self, restype, lines): 
     
    14771472                i += 1 
    14781473            if i > self.maxpos: 
    1479                 raise OpenSyncError('No %s positions free' % self.objtype) 
     1474                raise opensync.Error('No %s positions free' % self.objtype) 
    14801475            self.used.add(i) 
    14811476            ret.append(i) 
     
    14901485    def __init__(self, comms): 
    14911486        self.comms = comms 
     1487        self.serial = None 
     1488        self.positions = {} 
     1489        self.categories = {} 
     1490        self.revcategories = {} 
     1491 
     1492    def connect(self): 
     1493        self.comms.connect() 
    14921494        self.serial = comms.read_serial() 
    14931495 
     
    14961498        for (bit, desc) in REQUIRED_FEATURES: 
    14971499            if not features[bit]: 
    1498                 raise OpenSyncError(desc + ' feature not present', 
    1499                                     opensync.ERROR_NOT_SUPPORTED) 
     1500                raise opensync.Error(desc + ' feature not present', 
     1501                                     opensync.ERROR_NOT_SUPPORTED) 
    15001502 
    15011503        # read current time on the phone, check if it matches our local time 
     
    15071509            msg = ("ERROR: Phone appears to be in a different timezone!\n" 
    15081510                   + "Phone time is %s" % time.strftime('%c', phone_now)) 
    1509             raise OpenSyncError(msg) 
     1511            raise opensync.Error(msg) 
    15101512 
    15111513        # initialise the position allocators 
    1512         self.positions = {} 
    15131514        self.positions['event'] = PosAllocator('event', 0, 
    15141515                                               self.comms.max_events - 1) 
     
    15181519 
    15191520        # initialise the category mappings 
    1520         self.categories = {} 
    1521         self.revcategories = {} 
    15221521        self.__init_categories() 
    15231522 
    1524     def list_changes(self, objtype, member): 
     1523    def disconnect(self): 
     1524        self.comms.disconnect() 
     1525 
     1526    def list_changes(self, objtype, info): 
    15251527        """Return a list of change objects for all entries of the given type.""" 
    15261528 
     
    15421544        ret = [] 
    15431545        for entry in entries: 
    1544             change = opensync.OSyncChange() 
     1546            change = opensync.Change() 
    15451547            change.member = member 
    15461548            change.objtype = objtype 
     
    15771579        objtype = change.objtype 
    15781580        if change.format != 'xml-%s-doc' % objtype: 
    1579             raise OpenSyncError("unhandled data format %s" % change.format, 
    1580                                 opensync.ERROR_NOT_SUPPORTED) 
     1581            raise opensync.Error("unhandled data format %s" % change.format, 
     1582                                 opensync.ERROR_NOT_SUPPORTED) 
    15811583        try: 
    15821584            if objtype == 'event': 
     
    16751677 
    16761678 
    1677 def stdexceptions(func): 
    1678     """Decorator used to wrap every method in SyncClass with the same 
    1679     exception handlers. Reports any exception back to opensync as an error, 
    1680     otherwise reports success. 
    1681     """ 
    1682     def new_func(*args, **kwds): 
    1683         """Invoke func, report success, otherwise report an error.""" 
    1684  
    1685         context = args[1] # context is always the first argument after 'self' 
    1686  
    1687         # if the bluetooth module is present, handle its exceptions as IO errors 
    1688         if USE_BLUETOOTH_MODULE: 
    1689             ioerrors = (IOError, OSError, bluetooth.BluetoothError) 
    1690         else: 
    1691             ioerrors = (IOError, OSError) 
    1692  
    1693         try: 
    1694             func(*args, **kwds) 
    1695             context.report_success() 
    1696         except OpenSyncError, e: 
    1697             e.report(context) 
    1698         except ioerrors, e: 
    1699             context.report_error(opensync.ERROR_IO_ERROR, str(e)) 
    1700         except: 
    1701             context.report_error(opensync.ERROR_GENERIC, traceback.format_exc()) 
    1702     new_func.func_name = func.func_name 
    1703     return new_func 
    1704  
    1705  
    1706 class SyncClass: 
    1707     """Synchronisation class used by OpenSync.""" 
    1708  
    1709     def __init__(self, member): 
    1710         self.member = member 
    1711         self.comms = self.access = None 
    1712         self.config = {} 
    1713         self.__parse_config(self.member.config) 
    1714         self.hashtable = opensync.OSyncHashTable() 
    1715         if not self.hashtable.load(self.member): 
    1716             raise OpenSyncError('hashtable load failed', 
    1717                                 opensync.ERROR_INITIALIZATION) 
    1718  
    1719     @stdexceptions 
    1720     def connect(self, ctx): 
     1679class MotoSink(opensync.ObjTypeSink): 
     1680    """Event synchronisation class used by OpenSync.""" 
     1681 
     1682    def __init__(self, objtype, info, access): 
     1683        opensync.ObjTypeSink.__init__(self, objtype, self) 
     1684        self.objtype = objtype 
     1685        self.add_objformat("xml-%s-doc" % objtype) 
     1686        self.access = access 
     1687        hashpath = os.path.join(info.configdir, "%s-hash.db" % objtype) 
     1688        self.hashtable = opensync.HashTable(hashpath, objtype) 
     1689 
     1690    def connect(self, info, ctx): 
    17211691        """Connect to the phone.""" 
    1722         self.comms = PhoneComms(self.config['device']) 
    1723         self.access = PhoneAccess(self.comms) 
    1724  
    1725     @stdexceptions 
    1726     def get_changeinfo(self, ctx): 
     1692        self.access.connect() 
     1693 
     1694    def get_changes(self, info, ctx): 
    17271695        """Report all OSyncChange objects for entries on the phone.""" 
    1728         for objtype in SUPPORTED_OBJTYPES: 
    1729             if self.member.objtype_enabled(objtype): 
    1730                 if self.member.get_slow_sync(objtype): 
    1731                     self.hashtable.set_slow_sync(objtype) 
    1732  
    1733                 for change in self.access.list_changes(objtype, self.member): 
    1734                     self.hashtable.detect_change(change) 
    1735                     if change.changetype != opensync.CHANGE_UNMODIFIED: 
    1736                         change.report(ctx) 
    1737                         self.hashtable.update_hash(change) 
    1738  
    1739                 self.hashtable.report_deleted(ctx, objtype) 
    1740  
    1741     @stdexceptions 
    1742     def commit_change(self, ctx, change): 
     1696        if self.slowsink: 
     1697            self.hashtable.reset() 
     1698        for change in self.access.list_changes(self.objtype, info): 
     1699            self.hashtable.report(change.uid) 
     1700            change.changetype = self.hashtable.get_changetype(change.uid, change.hash) 
     1701            if change.changetype != opensync.CHANGE_UNMODIFIED: 
     1702                self.hashtable.update_hash(change.changetype, change.uid, change.hash) 
     1703                ctx.report_change(change) 
     1704        for uid in self.hashtable.get_deleted(): 
     1705            change = opensync.Change() 
     1706            change.uid = uid 
     1707            change.changetype = opensync.CHANGE_DELETED 
     1708            ctx.report_change(change) 
     1709            hashtable.update_hash(opensync.CHANGE_DELETED, uid, None) 
     1710 
     1711    def commit(self, info, ctx, change): 
    17431712        """Write a change to the phone.""" 
    1744         if change.objtype not in SUPPORTED_OBJTYPES: 
    1745             raise OpenSyncError('unsupported objtype %s' % change.objtype, 
    1746                                 opensync.ERROR_NOT_SUPPORTED) 
     1713        if change.objtype != self.objtype: 
     1714            raise opensync.Error('unsupported objtype %s' % change.objtype, 
     1715                                 opensync.ERROR_NOT_SUPPORTED) 
    17471716        if change.changetype == opensync.CHANGE_DELETED: 
    17481717            success = (self.access.uid_seen(change.uid) 
     
    17541723            # the old one was deleted, to keep it consistent 
    17551724            if (success and old_uid != change.uid): 
    1756                 fake_change = opensync.OSyncChange() 
    1757                 fake_change.uid = old_uid 
    1758                 fake_change.changetype = opensync.CHANGE_DELETED 
    1759                 self.hashtable.update_hash(fake_change) 
     1725                self.hashtable.update_hash(opensync.CHANGE_DELETED, old_uid, None) 
    17601726        else: 
    17611727            success = self.access.update_entry(change) 
    17621728        if success: 
    1763             self.hashtable.update_hash(change) 
    1764  
    1765     @stdexceptions 
    1766     def sync_done(self, ctx): 
     1729            self.hashtable.update_hash(change.changetype, change.uid, change.hash) 
     1730 
     1731    def sync_done(self, info, ctx): 
    17671732        """Called when the sync is complete.""" 
    17681733        self.hashtable.forget() 
    1769  
    1770     @stdexceptions 
    1771     def disconnect(self, ctx): 
     1734        self.hashtable.close() 
     1735 
     1736    def disconnect(self, info, ctx): 
    17721737        """Called to disconnect from the phone.""" 
    1773         del self.access 
    1774         del self.comms 
    1775  
    1776     def finalize(self): 
    1777         """Called just before we are cleaned up.""" 
    1778         self.hashtable.close() 
    1779         del self.hashtable 
    1780         del self.member 
    1781  
    1782     def __parse_config(self, configstr): 
    1783         """Parse the config data and return a hash of config values.""" 
    1784         try: 
    1785             doc = xml.dom.minidom.parseString(configstr) 
    1786         except: 
    1787             raise OpenSyncError('failed to parse config data', 
    1788                                 opensync.ERROR_MISCONFIGURATION) 
    1789  
    1790         self.config['device'] = getXMLField(doc, 'device').strip() 
    1791         if self.config['device'] == '': 
    1792             raise OpenSyncError('device not specified in config file', 
    1793                                 opensync.ERROR_MISCONFIGURATION) 
    1794  
    1795  
    1796 def initialize(member): 
    1797     """Called by python-module plugin wrapper, returns instance of SyncClass.""" 
    1798     return SyncClass(member) 
    1799  
    1800 def get_info(info): 
     1738        self.access.disconnect() 
     1739 
     1740 
     1741def parse_config(configstr): 
     1742    """Parse the config data and return the device string.""" 
     1743    try: 
     1744        doc = xml.dom.minidom.parseString(configstr) 
     1745    except: 
     1746        raise opensync.Error('failed to parse config data', 
     1747                             opensync.ERROR_MISCONFIGURATION) 
     1748 
     1749    ret = getXMLField(doc, 'device').strip() 
     1750    if ret == '': 
     1751        raise opensync.Error('device not specified in config file', 
     1752                             opensync.ERROR_MISCONFIGURATION) 
     1753    return ret 
     1754 
     1755def initialize(info): 
     1756    """Called by python-module plugin wrapper, registers sync classes.""" 
     1757    comms = PhoneComms(parse_config(info.config)) 
     1758    access = PhoneAccess(comms) 
     1759    for objtype in SUPPORTED_OBJTYPES: 
     1760        info.add_objtype(MotoSink(objtype, info, access)) 
     1761 
     1762def discover(info): 
     1763    """Called by python-module plugin wrapper, discovers capabilities of device.""" 
     1764    for sink in info.objtypes: 
     1765        sink.available = True 
     1766    info.version = opensync.Version() 
     1767    info.version.plugin = "moto-sync" 
     1768 
     1769def get_sync_info(plugin): 
    18011770    """Called by python-module plugin wrapper, returns plugin metadata.""" 
    1802     info.name = "moto-sync" 
    1803     info.longname = "Motorola synchronisation plugin" 
    1804     info.description = ("Plugin to synchronise phone book and calendar entries " 
    1805                       + "on a Motorola mobile phone using extended AT commands") 
    1806     for objtype in SUPPORTED_OBJTYPES: 
    1807         info.accept_objtype(objtype) 
    1808         info.accept_objformat(objtype, "xml-%s-doc" % objtype) 
     1771    plugin.name = "moto-sync" 
     1772    plugin.longname = "Motorola synchronisation plugin" 
     1773    plugin.description = ("OpenSync plugin to synchronise phone book and" 
     1774        " calendar entries on a locally-connected Motorola mobile phone using" 
     1775        " extended AT commands. Please see the README file for configuration" 
     1776        " instructions and a list of supported models.") 
     1777    plugin.config_type = opensync.PLUGIN_NEEDS_CONFIGURATION