示例#1
0
def poll(account_id, provider):
    """Query a remote contacts provider for updates and persist them to the
    database.

    Parameters
    ----------
    account_id: int
        ID for the account whose contacts should be queried.
    db_session: sqlalchemy.orm.session.Session
        Database session

    provider: Interface to the remote contact data provider.
        Must have a PROVIDER_NAME attribute and implement the get_contacts()
        method.
    """
    provider_name = provider.PROVIDER_NAME
    with session_scope() as db_session:
        account = db_session.query(Account).get(account_id)

        # Contact data reflecting any local modifications since the last sync
        # with the remote provider.
        local_contacts = db_session.query(Contact).filter_by(
            source='local', account=account,
            provider_name=provider_name).all()
        # Snapshot of contact data from immediately after last sync.
        cached_contacts = db_session.query(Contact).filter_by(
            source='remote', account=account,
            provider_name=provider_name).all()
        log.info('Query: have {0} contacts, {1} cached.'.format(
            len(local_contacts), len(cached_contacts)))

        cached_contact_dict = {contact.uid: contact for contact in
                               cached_contacts}
        local_contact_dict = {contact.uid: contact for contact in
                              local_contacts}

        change_counter = Counter()
        last_sync = or_none(account.last_synced_contacts,
                            datetime.datetime.isoformat)
        to_commit = []
        for remote_contact in provider.get_contacts(last_sync):
            remote_contact.account = account
            assert remote_contact.uid is not None, \
                'Got remote contact with null uid'
            assert isinstance(remote_contact.uid, str)
            cached_contact = cached_contact_dict.get(remote_contact.uid)
            local_contact = local_contact_dict.get(remote_contact.uid)
            # If the remote contact was deleted, purge the corresponding
            # database entries.
            if remote_contact.deleted:
                if cached_contact is not None:
                    db_session.delete(cached_contact)
                    change_counter['deleted'] += 1
                if local_contact is not None:
                    db_session.delete(local_contact)
                continue
            # Otherwise, update the database.
            if cached_contact is not None:
                # The provider gave an update to a contact we already have.
                if local_contact is not None:
                    try:
                        # Attempt to merge remote updates into local_contact
                        merge(cached_contact, remote_contact, local_contact)
                        # And update cached_contact to reflect both local and
                        # remote updates
                        cached_contact.copy_from(local_contact)
                    except MergeError:
                        log.error('Conflicting local and remote updates to '
                                  'contact.\nLocal: {0}\ncached: {1}\n '
                                  'remote: {2}'.format(local_contact,
                                                       cached_contact,
                                                       remote_contact))
                        # TODO(emfree): Come up with a strategy for handling
                        # merge conflicts. For now, just don't update if there
                        # is a conflict.
                        continue
                else:
                    log.warning('Contact {0} already present as remote but '
                                'not local contact'.format(cached_contact))
                    cached_contact.copy_from(remote_contact)
                change_counter['updated'] += 1
            else:
                # This is a new contact, create both local and remote DB
                # entries.
                local_contact = Contact()
                local_contact.copy_from(remote_contact)
                local_contact.source = 'local'
                to_commit.append(local_contact)
                to_commit.append(remote_contact)
                change_counter['added'] += 1

        account.last_synced_contacts = datetime.datetime.now()

        log.info('Added {0} contacts.'.format(change_counter['added']))
        log.info('Updated {0} contacts.'.format(change_counter['updated']))
        log.info('Deleted {0} contacts.'.format(change_counter['deleted']))

        db_session.add_all(to_commit)
        db_session.commit()
示例#2
0
def poll(account_id, provider):
    """Query a remote contacts provider for updates and persist them to the
    database.

    Parameters
    ----------
    account_id: int
        ID for the account whose contacts should be queried.
    db_session: sqlalchemy.orm.session.Session
        Database session

    provider: Interface to the remote contact data provider.
        Must have a PROVIDER_NAME attribute and implement the get_contacts()
        method.
    """
    provider_name = provider.PROVIDER_NAME
    with session_scope() as db_session:
        account = db_session.query(Account).get(account_id)
        change_counter = Counter()
        last_sync = or_none(account.last_synced_contacts,
                            datetime.datetime.isoformat)
        to_commit = []
        for remote_contact in provider.get_contacts(last_sync):
            remote_contact.account = account
            assert remote_contact.uid is not None, \
                'Got remote contact with null uid'
            assert isinstance(remote_contact.uid, str)
            matching_contacts = db_session.query(Contact).filter(
                Contact.account == account,
                Contact.provider_name == provider_name,
                Contact.uid == remote_contact.uid)
            # Snapshot of contact data from immediately after last sync:
            cached_contact = matching_contacts. \
                filter(Contact.source == 'remote').first()
            # Contact data reflecting any local modifications since the last
            # sync with the remote provider:
            local_contact = matching_contacts. \
                filter(Contact.source == 'local').first()
            # If the remote contact was deleted, purge the corresponding
            # database entries.
            if remote_contact.deleted:
                if cached_contact is not None:
                    db_session.delete(cached_contact)
                    change_counter['deleted'] += 1
                if local_contact is not None:
                    db_session.delete(local_contact)
                continue
            # Otherwise, update the database.
            if cached_contact is not None:
                # The provider gave an update to a contact we already have.
                if local_contact is not None:
                    try:
                        # Attempt to merge remote updates into local_contact
                        merge(cached_contact, remote_contact, local_contact)
                        # And update cached_contact to reflect both local and
                        # remote updates
                        cached_contact.copy_from(local_contact)
                    except MergeError:
                        log.error('Conflicting local and remote updates to '
                                  'contact.\nLocal: {0}\ncached: {1}\n '
                                  'remote: {2}'.format(local_contact,
                                                       cached_contact,
                                                       remote_contact))
                        # TODO(emfree): Come up with a strategy for handling
                        # merge conflicts. For now, just don't update if there
                        # is a conflict.
                        continue
                else:
                    log.warning('Contact {0} already present as remote but '
                                'not local contact'.format(cached_contact))
                    cached_contact.copy_from(remote_contact)
                change_counter['updated'] += 1
            else:
                # This is a new contact, create both local and remote DB
                # entries.
                local_contact = Contact()
                local_contact.copy_from(remote_contact)
                local_contact.source = 'local'
                to_commit.append(local_contact)
                to_commit.append(remote_contact)
                change_counter['added'] += 1

        account.last_synced_contacts = datetime.datetime.now()

        log.info('Added {0} contacts.'.format(change_counter['added']))
        log.info('Updated {0} contacts.'.format(change_counter['updated']))
        log.info('Deleted {0} contacts.'.format(change_counter['deleted']))

        db_session.add_all(to_commit)
        db_session.commit()