def deserialize(rv, peer, text, translator, serializer, filter=None,
    debug=False):

    items = []

    trans = translator(rv)

    inbound, extra = serializer.deserialize(rv, text)

    for alias, rsExternal in inbound.items():

        uuid = trans.getUUIDForAlias(alias)
        if uuid:
            item = rv.findUUID(uuid)
        else:
            item = None

        if rsExternal is not None:

            if item is not None: # Item already exists
                if not pim.has_stamp(item, shares.SharedItem):
                    shares.SharedItem(item).add()
                shared = shares.SharedItem(item)
                state = shared.getPeerState(peer)
                rsInternal= eim.RecordSet(trans.exportItem(item))

            else: # Item doesn't exist yet
                state = shares.State(itsView=rv, peer=peer)
                rsInternal = eim.RecordSet()

            dSend, dApply, pending = state.merge(rsInternal, rsExternal,
                isDiff=False, filter=filter, debug=debug)

            state.updateConflicts(item)

            if dApply:
                logger.debug("Applying: %s %s", uuid, dApply)
                trans.startImport()
                trans.importRecords(dApply)
                trans.finishImport()

            uuid = trans.getUUIDForAlias(alias)
            if uuid:
                item = rv.findUUID(uuid)
            else:
                item = None

            if item is not None and item.isLive():
                if not pim.has_stamp(item, shares.SharedItem):
                    shares.SharedItem(item).add()
                shares.SharedItem(item).addPeerState(state, peer)

                items.append(item)

    return items
示例#2
0
def outbound(peers, item, filter=None, debug=False):

    rv = peers[0].itsView

    if filter is None:
        filter = lambda rs: rs
    else:
        filter = filter.sync_filter

    # At some point, which serializer and translator to use should be
    # configurable
    serializer = eimml.EIMMLSerializer  # only using class methods
    trans = translator.SharingTranslator(rv)

    rsInternal = {}

    items = [item]
    version = str(item.itsVersion)

    if pim.has_stamp(item, pim.EventStamp):
        for mod in pim.EventStamp(item).modifications or []:
            # modifications that have been changed purely by
            # auto-triage shouldn't have recordsets created for them
            if not (isinstance(mod, pim.Note)
                    and pim.EventStamp(mod).isTriageOnlyModification()
                    and pim.EventStamp(mod).simpleAutoTriage()
                    == mod._triageStatus):
                items.append(mod)

    for item in items:
        alias = trans.getAliasForItem(item)
        rsInternal[alias] = filter(eim.RecordSet(trans.exportItem(item)))

        if not pim.has_stamp(item, shares.SharedItem):
            shares.SharedItem(item).add()

        shared = shares.SharedItem(item)

        # Abort if pending conflicts
        if shared.conflictingStates:
            raise errors.ConflictsPending(_(u"Conflicts pending."))

        for peer in peers:
            state = shared.getPeerState(peer)
            # Set agreed state to what we have locally
            state.agreed = rsInternal[alias]

        # Repository identifier:
        if rv.repository is not None:
            repoId = rv.repository.getSchemaInfo()[0].str16()
        else:
            repoId = ""

    text = serializer.serialize(rv,
                                rsInternal,
                                rootName="item",
                                repo=repoId,
                                version=version)

    return text
    def verify(self):

        if self.item is None:
            self.discard()
            return False

        if self.pendingRemoval:
            # Someone else removed this item from share.contents
            peer = self.state.peer
            if peer and isinstance(peer, Share):
                if self.item in peer.contents:
                    # local item still in share.contents, still conflict
                    return True

                # item no longer in share.contents, no longer in conflict
                self.discard()
                return False

            # Some non-Share peer, keep it a pending removal conflict
            return True

        # generate records for local item
        translator = self.state.getTranslator()

        items = [self.item]
        if self.item.hasLocalAttributeValue('inheritFrom'):
            # need to check if the conflict is actually on the master
            items.append(self.item.inheritFrom)

        for item in items:
            rs = eim.RecordSet(translator.exportItem(item))
            if self.change.exclusions:  # the conflict is a record removal
                change = list(self.change.exclusions)[0]
                # search rs for matching key
                for r in rs.inclusions:
                    if r.getKey() == change.getKey():
                        # record still applies to local item, still a conflict
                        return True

                # didn't find a match, so the record has been removed on the
                # item, thus this is no longer a conflict

            else:  # the conflict is on a field change
                change = list(self.change.inclusions)[0]
                for r in rs.inclusions:
                    if r.getKey() == change.getKey():
                        # found matching record, now see if local values match
                        # pending change
                        for field in type(r).__fields__:
                            changedField = getattr(change, field.name)
                            if changedField != nc:
                                if getattr(r, field.name) != changedField:
                                    # mismatch, conflict still valid
                                    return True

                # pending changes all match local item, no more conflict

        self.discard()
        return False
def exportFile(rv,
               path,
               collection,
               activity=None,
               translatorClass=translator.SharingTranslator,
               serializerClass=ics.ICSSerializer,
               filters=None,
               debug=False):

    if filters is None:
        filter = lambda rs: rs
    else:
        filter = eim.Filter(None, u'Temporary filter')
        for uri in filters:
            filter += eim.lookupSchemaURI(uri)
        filter = filter.sync_filter

    trans = translatorClass(rv)
    trans.startImport()

    total = len(collection)
    if activity:
        activity.update(totalWork=total)

    outbound = {}
    for item in collection:
        if (isinstance(item, pim.Note)
                and pim.EventStamp(item).isTriageOnlyModification()):
            continue  # skip triage-only modifications

        alias = trans.getAliasForItem(item)
        outbound[alias] = filter(eim.RecordSet(trans.exportItem(item)))
        if activity:
            activity.update(work=1, msg=_(u"Exporting items..."))

    text = serializerClass.serialize(rv,
                                     outbound,
                                     name=collection.displayName,
                                     monolithic=True)

    output = open(path, "wb")
    output.write(text)
    output.close()

    if activity:
        activity.update(totalWork=None, msg=_(u"Exporting complete."))
def serialize(rv, items, translator, serializer, filter=None, debug=False):

    if filter is None:
        filter = lambda rs: rs
    else:
        filter = filter.sync_filter

    trans = translator(rv)

    rsInternal = { }

    for item in items:
        alias = trans.getAliasForItem(item)
        rsInternal[alias] = filter(eim.RecordSet(trans.exportItem(item)))

        if not pim.has_stamp(item, shares.SharedItem):
            shares.SharedItem(item).add()

        shared = shares.SharedItem(item)

    text = serializer.serialize(rv, rsInternal)

    return text
    def merge(self,
              rsInternal,
              inbound=None,
              isDiff=True,
              filter=None,
              readOnly=False,
              debug=False):
        assert isinstance(rsInternal, eim.RecordSet)

        if filter is None:
            filter = lambda rs: rs
        else:
            filter = filter.sync_filter

        pending = self.pending
        agreed = self.agreed

        # We need to set rsExternal to equal the entire external state

        # If we're passing in a diff, apply it to agreed + pending
        if isDiff:
            rsExternal = agreed + pending + (inbound or eim.Diff())
        elif isinstance(inbound, eim.Diff):
            rsExternal = eim.RecordSet() + inbound
        else:
            rsExternal = inbound or eim.RecordSet()
        assert isinstance(rsInternal, eim.RecordSet)

        internalDiff = filter(rsInternal - agreed)
        externalDiff = filter(rsExternal - agreed)
        ncd = internalDiff | externalDiff

        msgs = list()
        add = msgs.append
        add("----------- Beginning merge")
        add("   old agreed: %s" % agreed)
        add("   old pending: %s" % pending)
        add("   rsInternal: %s" % rsInternal)
        add("   internalDiff: %s" % internalDiff)
        add("   rsExternal: %s" % rsExternal)
        add("   externalDiff: %s" % externalDiff)
        add("   ncd: %s" % ncd)

        if readOnly:
            # This allows a read-only subscriber to maintain local changes
            # that only conflict when someone else makes a conflicting change,
            # and not *every* time they sync.

            # We don't want internal changes from
            # reaching self.agreed or dSend.  We *do* want to be alerted to
            # conflicts between internal and external changes, however.  To
            # do this, we generate a recordset representing how things would
            # be if the external changes won, and another recordset
            # representing how things would be if the internal changes won,
            # and we diff the two.

            extWins = agreed + internalDiff + pending + externalDiff
            intWins = agreed + pending + externalDiff + internalDiff
            add("   extWins: %s" % extWins)
            add("   intWins: %s" % intWins)
            pending = self.pending = filter(extWins - intWins)

            agreed = self.agreed = filter(rsExternal)
            dSend = eim.Diff()

        else:
            agreed = self.agreed = agreed + ncd
            # add("   agreed+=ncd: %s" % agreed)
            dSend = self._cleanDiff(rsExternal, ncd)
            # add("   dSend cleanDiff: %s" % dSend)
            rsExternal += dSend
            # add("   rsExternal+=dSend: %s" % rsExternal)
            pending = self.pending = filter(rsExternal - agreed)

        dApply = self._cleanDiff(rsInternal, ncd)

        add(" - - - - Results - - - - ")
        add("   dSend: %s" % dSend)
        add("   dApply: %s" % dApply)
        add("   new agreed: %s" % agreed)
        add("   new pending: %s" % pending)
        add(" ----------- Merge complete")

        if pending:
            msgs.insert(0, "Conflict detected:")

        doLog = logger.info if (debug or pending) else logger.debug
        for msg in msgs:
            doLog(msg)

        return dSend, dApply, pending
 def getAgreed(self):
     if hasattr(self, '_agreed'):
         return eim.RecordSet(cPickle.loads(self._agreed))
     else:
         return eim.RecordSet()
示例#8
0
def inbound(peer, text, filter=None, allowDeletion=False, debug=False):

    logger.info("itemcentric.inbound(%s)", str(peer))
    if isinstance(peer, list):
        # Peer email address items can have several that match.  We pass in
        # a list so this code can reuse the one (if any) already associated
        # to the inbound item.
        peers = peer
    else:
        peers = [peer]

    rv = peers[0].itsView

    # At some point, which serializer and translator to use should be
    # configurable
    serializer = eimml.EIMMLSerializer  # only using class methods
    trans = translator.SharingTranslator(rv)

    inbound, extra = serializer.deserialize(rv, text, helperView=rv)

    peerRepoId = extra.get('repo', None)
    peerItemVersion = int(extra.get('version', '-1'))

    itemToReturn = None

    aliases = inbound.keys()
    aliases.sort()
    for alias in aliases:
        rsExternal = inbound[alias]

        uuid = trans.getUUIDForAlias(alias)
        if uuid:
            item = rv.findUUID(uuid)
        else:
            item = None

        if rsExternal is not None:

            if item is not None and not getattr(item, '_fake', False):

                # Item already exists
                if not pim.has_stamp(item, shares.SharedItem):
                    shares.SharedItem(item).add()
                shared = shares.SharedItem(item)

                if not getattr(shared, 'peerStates', False):
                    # has no peer states yet, use the first peer in list
                    peer = peers[0]
                else:
                    for state in shared.peerStates:
                        peerUUID = shared.peerStates.getAlias(state)
                        for peer in peers:
                            if peer.itsUUID.str16() == peerUUID:
                                # peer matches
                                break
                        else:
                            # no match; use the first one passed in
                            peer = peers[0]

                state = shared.getPeerState(peer)
                rsInternal = eim.RecordSet(trans.exportItem(item))

            else:  # Item doesn't exist yet
                peer = peers[0]
                state = shares.State(itsView=rv, peer=peer)
                rsInternal = eim.RecordSet()

            if state.peerRepoId and (peerRepoId != state.peerRepoId):
                # This update is not from the peer repository we last saw.
                # Treat the update is entirely new
                state.clear()

            if uuid is not None:
                masterAlias, recurrenceID = utility.splitUUID(rv, alias)
                if masterAlias != alias and not state.agreed:
                    # This is a new inbound modification
                    state.agreed += eim.RecordSet(
                        recordset_conduit.getInheritRecords(
                            rsExternal.inclusions, alias))

            state.peerRepoId = peerRepoId

            # Only process recordsets whose version is greater than the last one
            # we say.  In the case of null-repository-view testing, versions are
            # always stuck at zero, so process those as well.

            if ((peerItemVersion == 0)
                    or (peerItemVersion > state.peerItemVersion)):

                state.peerItemVersion = peerItemVersion

                dSend, dApply, pending = state.merge(rsInternal,
                                                     rsExternal,
                                                     isDiff=False,
                                                     filter=filter,
                                                     debug=debug)

                state.autoResolve(rsInternal, dApply, dSend)
                state.updateConflicts(item)

                if dApply:
                    logger.debug("Applying: %s %s", uuid, dApply)
                    trans.startImport()
                    trans.importRecords(dApply)
                    trans.finishImport()

                uuid = trans.getUUIDForAlias(alias)
                if uuid:
                    item = rv.findUUID(uuid)
                else:
                    item = None

                if item is not None and item.isLive():
                    if not pim.has_stamp(item, shares.SharedItem):
                        shares.SharedItem(item).add()

                    shares.SharedItem(item).addPeerState(state, peer)

                    if itemToReturn is None:
                        # the item to return should always be a master, not
                        # a modification
                        itemToReturn = getattr(item, 'inheritFrom', item)

            else:
                logger.info("Out-of-sequence update for %s", uuid)
                raise errors.OutOfSequence(
                    "Update %d arrived after %d" %
                    (peerItemVersion, state.peerItemVersion))

        else:  # Deletion

            if item is not None:

                # Remove the state
                if pim.has_stamp(item, shares.SharedItem):
                    shared = shares.SharedItem(item)
                    shared.removePeerState(peer)

                # Remove the item (if allowed)
                if allowDeletion:
                    logger.debug("Deleting item: %s", uuid)
                    item.delete(True)
                    item = None

    return itemToReturn