Ejemplo n.º 1
0
def comparevcards(vcard, localvcard, auth):
    """ look for local version of this vcard and compare
    should return tuple(action, xml, id, name) """
    id = vcard.uid.value
    name = vcard.fn.value
    # compare
    if vcard.serialize() == localvcard.serialize():
        return localvcard
    # compare REV strings
    if 'rev' in vcard.contents and 'rev' in localvcard.contents:
        # TODO google returns utc times - should make this timezone aware
        vcardrev = datetime.strptime(vcard.rev.value, _dtformat)
        localvcardrev = datetime.strptime(localvcard.rev.value, _dtformat)
        logger.debug(u'Comparing revision times: R{0}, L{1}.'.format(
                vcard.rev.value, localvcard.rev.value))
        if vcardrev > localvcardrev:
            # write new local vcard
            logger.info(u'Local version of contact "{0}" updated.'.format(name))
            return vcard
        elif localvcardrev > vcardrev:
            # update remote
            xmlobj = vcf2xml.toXml(localvcard)
            xml = vcf2xml.ET.tostring(xmlobj, encoding=_encoding)
            response = sendcontact(options['user'], auth, xml, id)
            localvcard = xml2vcf.readXml(response)[0]
            logger.info(u'Remote version of contact "{0}" updated.'.format(name))
            return localvcard
        else:
            logger.debug(u'Revision times are equal.')
    else:
        # use default resolution
        logger.debug(u'One or both versions missing revision time, ' +
                'choosing default resolution method.')
    r = u'Contact "{0}" differs: '.format(name)
    if options['defaultresolution'] == 'prefer local' \
            or runoptions.preferlocal is True:
        xmlobj = vcf2xml.toXml(localvcard)
        xml = vcf2xml.ET.tostring(xmlobj, encoding=_encoding)
        response = sendcontact(options['user'], auth, xml, id)
        localvcard = xml2vcf.readXml(response)[0]
        logger.info(r + u'remote version updated.')
        return localvcard
    elif options['defaultresolution'] == 'prefer remote' \
            or runoptions.preferlocal is False:
        logger.info(r + u'local version updated.')
        return vcard
    elif options['defaultresolution'] == 'do nothing':
        logger.warning(r + u'unable to resolve.')
        versions = u'Local version:\n{0}Remoteversion:\n{1}'.format(
                unicode(localvcard.serialize(), _encoding),
                unicode(vcard.serialize(), _encoding))
        logger.warning(versions)
        return localvcard
Ejemplo n.º 2
0
def execute():
    localcontacts = getlocalcontacts()
    localadditions, localchanges, localdeletions = getlocalchanges(localcontacts)

    # get (recently changed) contacts from google
    data = {'max-results': len(localcontacts) + 50}
    if 'lastsync' in contactdb.keys() and not runoptions.getall:
        data['updated-min'] = contactdb['lastsync']
    logger.debug(u'Logging into Google Contacts.')
    auth = authenticate(options['user'], options['password'])
    logger.debug(u'Retrieving contact list.')
    contactsxml = getcontacts(options['user'], auth, data=data)
    # store xml for reference
    if options['loglevel'] == 'debug':
        savexml(contactsxml)

    # parse into individual vcards
    logger.debug(u'Parsing contacts from Google.')
    contacts = xml2vcf.readXml(contactsxml)
    logger.info(u'Received {0} contacts from Google.'.format(len(contacts)))
    for c in contacts:
        if c.uid.value in localdeletions:
            # don't bother comparing if we're going to delete it anyway
            logger.debug(u'Ignoring "{0}": in deletion list.'.format(c.fn.value))
            continue

        if c.uid.value in localcontacts:
            #logger.debug(u'Comparing "{0}".'.format(c.fn.value))
            localcontacts[c.uid.value] = comparevcards(c,
                    localcontacts[c.uid.value], auth)
        elif 'fn' in c.contents:
            localcontacts[c.uid.value] = c
            logger.info(u'New contact "{0}" added.'.format(c.fn.value))
        else:
            logger.debug(u'New unparseable remote contact ' +
                    u'"{0}" ignored.'.format(c.uid.value))

        if c.uid.value in localchanges:
            # already compared, so delete from localchanges list
            del localchanges[localchanges.index(c.uid.value)]

    # local additions
    # TODO: additions go to general contact list, not My Contacts, and have to
    # be moved manually in Gmail. Fix this.
    logger.debug(u'Sending local additions.')
    for n in localadditions:
        xmlobj = vcf2xml.toXml(localcontacts[n])
        xml = vcf2xml.ET.tostring(xmlobj, encoding=_encoding)
        response = sendcontact(options['user'], auth, xml)
        localvcard = xml2vcf.readXml(response)[0]
        # replace original with uid/etagged version from google
        del localcontacts[n]
        localcontacts[localvcard.uid.value] = localvcard
        logger.info(u'Local contact "{0}" added to Google.'.format(n))

    # TODO: deal with remote deletions
    # remote deletions should have only <atom:id> and <gd:deleted> for 30 days
    # need special query? xml2vcf.readXml might fail as it's invalid vcard
    # without N, FN
    for cuid in localdeletions:
        #logger.debug(u'Deleting "{0}": deleted locally.'.format(c.fn.value))
        #response = sendcontact(options['user'], auth, contactxml, contactid, True)
        # TODO: deal with response
        # sendcontact returns False if no contactid specified
        # add to list to delete until this is worked out
        logger.debug(u'Recording deletion of contact {0}.'.format(cuid))
        try:
            contactdb['todelete'].append(cuid)
            # NB Shelf.append() is dependent on writeback=True
        except KeyError:
            contactdb['todelete'] = [cuid]
        pass

    # remaining localchanges
    logger.debug(u'Examining local changes.')
    for cuid in localchanges:
        logger.debug(u'Retrieving "{0}".'.format(localcontacts[cuid].fn.value))
        contactxml = getcontacts(options['user'], auth, cuid)
        if contactxml is not None:
            logger.debug(u'Parsing contact from Google.')
            contact = xml2vcf.readXml(contactxml)[0]
            logger.debug(u'Comparing "{0}".'.format(localcontacts[cuid].fn.value))
            localcontacts[cuid] = comparevcards(contact, localcontacts[cuid], auth)
        else:
            logger.error(u'Contact not found at Google.')

    # write out contacts file
    logger.debug(u'Writing new local file.')
    contactsfile = codecs.open(os.path.expanduser(options['contacts']), 'w',
            _encoding)
    # sort list by cuid so diffs are easier
    for cuid, c in sorted(localcontacts.items()):
        contactsfile.write(c.serialize().decode(_encoding))
        # should this be?
        #contactsfile.write(unicode(c.serialize(), _encoding))
    contactsfile.close()

    # set last sync time in config: now() or utcnow()?
    logger.debug(u'Recording sync details.')
    contactdb['lastsync'] = datetime.strftime(datetime.utcnow(), _dtformat)
    contactdb['cuids'] = localcontacts.keys()
    contactdb.close()