Changeset 1777
- Timestamp:
- 02/20/07 11:50:11 (22 months ago)
- Files:
-
- 1 modified
-
plugins/moto-sync/motosync.py (modified) (17 diffs)
Legend:
- Unmodified
- Added
- Removed
-
plugins/moto-sync/motosync.py
r1719 r1777 4 4 """ 5 5 6 # Copyright (C) 2006 Andrew Baumann <andrewb@cse.unsw.edu.au>6 # Copyright (C) 2006-2007 Andrew Baumann <andrewb@cse.unsw.edu.au> 7 7 # 8 8 # This program is free software; you can redistribute it and/or modify … … 368 368 369 369 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 decorator374 on SyncClass methods.375 """376 def __init__(self, msg, errnum=opensync.ERROR_GENERIC):377 Exception.__init__(self)378 self.msg = msg379 self.num = errnum380 def __str__(self):381 return self.msg382 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 387 370 class UnsupportedDataError(Exception): 388 371 """Exception raised by PhoneEntry classes when the data cannot be stored.""" … … 400 383 """ 401 384 def __init__(self, device): 385 self.devstr = device 402 386 self.__calendar_open = False 403 387 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): 406 399 assert(USE_BLUETOOTH_MODULE, 407 400 "MAC address specified, but pybluez module is not available") … … 409 402 # search for the port to use on the device 410 403 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) 412 405 if found: 413 406 assert(found[0]['protocol'] == 'RFCOMM') 414 407 port = found[0]['port'] 415 408 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) 419 412 if USE_TTY_MODULE: 420 413 tty.setraw(self.__fd) … … 443 436 self.contact_name_len = namelen 444 437 445 def __del__(self):438 def disconnect(self): 446 439 self.close_calendar() 447 440 if self.__fd: 448 441 os.close(self.__fd) 442 self.__fd = None 449 443 if self.__btsock: 450 444 self.__btsock.close() 445 self.__btsock = None 451 446 452 447 def read_serial(self): … … 508 503 for (expos, exnum, extype) in self.__parse_results('MDBRE', data): 509 504 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) 511 506 if not exceptions.has_key(expos): 512 507 exceptions[expos] = [] … … 637 632 debug('<-- ' + ret) 638 633 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) 641 636 return ret 642 637 … … 664 659 return ret 665 660 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) 668 663 669 664 def __parse_results(self, restype, lines): … … 1477 1472 i += 1 1478 1473 if i > self.maxpos: 1479 raise OpenSyncError('No %s positions free' % self.objtype)1474 raise opensync.Error('No %s positions free' % self.objtype) 1480 1475 self.used.add(i) 1481 1476 ret.append(i) … … 1490 1485 def __init__(self, comms): 1491 1486 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() 1492 1494 self.serial = comms.read_serial() 1493 1495 … … 1496 1498 for (bit, desc) in REQUIRED_FEATURES: 1497 1499 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) 1500 1502 1501 1503 # read current time on the phone, check if it matches our local time … … 1507 1509 msg = ("ERROR: Phone appears to be in a different timezone!\n" 1508 1510 + "Phone time is %s" % time.strftime('%c', phone_now)) 1509 raise OpenSyncError(msg)1511 raise opensync.Error(msg) 1510 1512 1511 1513 # initialise the position allocators 1512 self.positions = {}1513 1514 self.positions['event'] = PosAllocator('event', 0, 1514 1515 self.comms.max_events - 1) … … 1518 1519 1519 1520 # initialise the category mappings 1520 self.categories = {}1521 self.revcategories = {}1522 1521 self.__init_categories() 1523 1522 1524 def list_changes(self, objtype, member): 1523 def disconnect(self): 1524 self.comms.disconnect() 1525 1526 def list_changes(self, objtype, info): 1525 1527 """Return a list of change objects for all entries of the given type.""" 1526 1528 … … 1542 1544 ret = [] 1543 1545 for entry in entries: 1544 change = opensync. OSyncChange()1546 change = opensync.Change() 1545 1547 change.member = member 1546 1548 change.objtype = objtype … … 1577 1579 objtype = change.objtype 1578 1580 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) 1581 1583 try: 1582 1584 if objtype == 'event': … … 1675 1677 1676 1678 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): 1679 class 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): 1721 1691 """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): 1727 1695 """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): 1743 1712 """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) 1747 1716 if change.changetype == opensync.CHANGE_DELETED: 1748 1717 success = (self.access.uid_seen(change.uid) … … 1754 1723 # the old one was deleted, to keep it consistent 1755 1724 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) 1760 1726 else: 1761 1727 success = self.access.update_entry(change) 1762 1728 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): 1767 1732 """Called when the sync is complete.""" 1768 1733 self.hashtable.forget() 1769 1770 @stdexceptions 1771 def disconnect(self, ctx):1734 self.hashtable.close() 1735 1736 def disconnect(self, info, ctx): 1772 1737 """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 1741 def 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 1755 def 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 1762 def 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 1769 def get_sync_info(plugin): 1801 1770 """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
