void ABI_Collab_Import::_calculateCollisionSeqence(UT_sint32 iIncomingRemoteRev, const UT_UTF8String& sIncomingDocUUID, UT_sint32& iStart, UT_sint32& iEnd) { UT_DEBUGMSG(("ABI_Collab_Import::_calculateCollisionSeqence() - iIncomingRemoteRev: %d\n", iIncomingRemoteRev)); // initialization iStart = -1; iEnd = -1; ABI_Collab_Export* pExport = m_pAbiCollab->getExport(); UT_return_if_fail(pExport); const UT_GenericVector<ChangeAdjust *>* pExpAdjusts = pExport->getAdjusts(); UT_return_if_fail(pExpAdjusts); // worst case: the whole outgoing changerecord stack is the collision sequence iStart = 0; iEnd = pExpAdjusts->getItemCount(); // scan back to find the changerecord in our export list the remote has seen, // maybe we can narrow the collision sequence down UT_sint32 i = 0; for (i = pExpAdjusts->getItemCount()-1; i >= 0; i--) { ChangeAdjust * pChange = pExpAdjusts->getNthItem(i); if (pChange) { UT_DEBUGMSG(("Looking at exported changerecord - rev: %d, pos: %d, length: %d adjust %d, queue pos: %d, orig uuid: %s\n", pChange->getLocalRev(), pChange->getLocalPos(), pChange->getLocalLength(), pChange->getLocalAdjust(), i, pChange->getRemoteDocUUID().utf8_str())); if (iIncomingRemoteRev >= pChange->getLocalRev()) { UT_DEBUGMSG(("Found the changerecord the remote side has already seen (iIncomingRemoteRev = %d)\n", iIncomingRemoteRev)); // a new changerecord can't collide with a changerecord the // remote side had already seen, hence the +1 iStart = i+1; break; } } else { UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } } // now move upward, so we kill off the bottom where changes are from the same document as the incoming cr (you can not collide with your own cr's!) for (; iStart < UT_sint32(pExpAdjusts->getItemCount()); iStart++) { ChangeAdjust * pChange = pExpAdjusts->getNthItem(iStart); if (pChange->getRemoteDocUUID() != sIncomingDocUUID) { // not the same document anymore, we can stop break; } else { UT_DEBUGMSG(("Killing off matching change: %d\n", iStart)); } } }
bool AbiCollab_ImportRuleSet::_isSaveInsert(const ChangeAdjust& ca, const AbstractChangeRecordSessionPacket& acrsp, UT_sint32 iRemotePosAdjust) { UT_return_val_if_fail(ca.m_pPacket, false); // if the packets share the same insertion position, then there is nothing we can do if (ca.getLocalPos() == acrsp.getPos()) return false; // allowing overlapping deletions is _really_ tricky; for now, we just disallow it if (ca.getLocalLength() <= 0 || acrsp.getLength() <= 0) return false; if (ca.m_pPacket->getClassType() != PCT_GlobSessionPacket && acrsp.getClassType() != PCT_GlobSessionPacket) { // overlapping inserts are just fine in the case of non-globs, as long as the start positions differ return ca.getLocalPos() != (acrsp.getPos()+iRemotePosAdjust); } // // if we get there, then at least one of the packets is a glob; this makes it a bit harder // // first, check that there are no 'delete' changerecords in the glob(s); // as stated above, deletes are really tricky, so we just disallow those if (ca.m_pPacket->getClassType() == PCT_GlobSessionPacket) for (std::vector<SessionPacket*>::const_iterator cit = static_cast<const GlobSessionPacket*>(ca.m_pPacket)->getPackets().begin(); cit != static_cast<const GlobSessionPacket*>(ca.m_pPacket)->getPackets().end(); cit++) if (AbstractChangeRecordSessionPacket::isInstanceOf(**cit) && static_cast<AbstractChangeRecordSessionPacket*>(*cit)->getAdjust() < 0) return false; if (acrsp.getClassType() == PCT_GlobSessionPacket) for (std::vector<SessionPacket*>::const_iterator cit = static_cast<const GlobSessionPacket&>(acrsp).getPackets().begin(); cit != static_cast<const GlobSessionPacket&>(acrsp).getPackets().end(); cit++) if (AbstractChangeRecordSessionPacket::isInstanceOf(**cit) && static_cast<AbstractChangeRecordSessionPacket*>(*cit)->getAdjust() < 0) return false; // // TODO: allow globs/insertions that really don't touch eachother, caused by the fact that // the 'first' insertion moves up the other packet's position // return false; // just to be on the save side }
// returns true if the import can continue, false otherwise bool ABI_Collab_Import::_handleCollision(UT_sint32 iIncomingRev, UT_sint32 iLocalRev, BuddyPtr pCollaborator) { UT_DEBUGMSG(("_handleCollision() - incoming rev %d collides against local rev %d!!!\n", iIncomingRev, iLocalRev)); UT_return_val_if_fail(pCollaborator, false); if (m_pAbiCollab->isLocallyControlled()) { UT_DEBUGMSG(("We're controlling this session, refusing this changerecord from %s!\n", pCollaborator->getDescription().utf8_str())); // add this collaborator to our revert ack list, so we can ignore his packets // until we get an acknoledgement that he has reverted his local, colliding changes m_revertSet.push_back(std::make_pair(pCollaborator, iIncomingRev)); // send the revert command to the collaborator RevertSessionPacket rsp(m_pAbiCollab->getSessionId(), m_pDoc->getOrigDocUUIDString(), iIncomingRev); m_pAbiCollab->push(&rsp, pCollaborator); return false; } else { UT_DEBUGMSG(("We're NOT controlling this session, reverting local changes and accepting changerecord!\n")); ABI_Collab_Export* pExport = m_pAbiCollab->getExport(); UT_return_val_if_fail(pExport, false); UT_GenericVector<ChangeAdjust *>* pAdjusts = pExport->getAdjusts(); UT_return_val_if_fail(pAdjusts, false); m_pAbiCollab->setIsReverting(true); // mask all changes in the exporter // undo our cool local changes, and nuke our exported packet list as well up to (and including) iLocalRev for (UT_sint32 i = pAdjusts->getItemCount() - 1; i >= 0; i--) { ChangeAdjust* pChange = pAdjusts->getNthItem(i); if (pChange) { if (pChange->getLocalRev() >= iLocalRev) { if (strcmp(m_pDoc->getOrigDocUUIDString(), pChange->getRemoteDocUUID().utf8_str()) == 0) { UT_DEBUGMSG(("UNDO-ING AND NUKING LOCAL CHANGE: EXPORT POSITION %d, pChange->m_iCRNumber: %d!\n", i, pChange->getLocalRev())); // undo the change locally m_pDoc->undoCmd(1); // fix up the positions on the change stack for (UT_sint32 j = i+1; j < pAdjusts->getItemCount(); j++) { ChangeAdjust* pC = pAdjusts->getNthItem(j); if (pC) { UT_DEBUGMSG(("Looking at fixing up the position of change pos %d\n", j)); if (pChange->getLocalPos() < pC->getLocalPos()) { UT_DEBUGMSG(("Adjusting change pos %d from m_iDocPos: %d to m_iDocPos: %d\n", j, pC->getLocalPos(), pC->getLocalPos() - pChange->getLocalAdjust())); pC->setLocalPos(pC->getLocalPos() - pChange->getLocalAdjust()); } else { UT_DEBUGMSG(("No need to adjust change pos %d\n", j)); } } else { UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } } // kill off the item pAdjusts->deleteNthItem(i); delete pChange; } else { UT_DEBUGMSG(("Skipping undo of remote change\n")); } } else break; } else { UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } } m_pAbiCollab->setIsReverting(false); // unmask all changes in the exporter UT_DEBUGMSG(("Pre-Acknowledging revert of revision %d\n", iLocalRev)); // send the revert acknowledgement command to the session owner RevertAckSessionPacket rasp(m_pAbiCollab->getSessionId(), m_pDoc->getOrigDocUUIDString(), iLocalRev); m_pAbiCollab->push(&rasp, pCollaborator); m_iAlreadyRevertedRevs.push_back(iLocalRev); return true; } }
/*! * Scan back through the CR's we've emitted since this remote CR was sent * and see if any overlap this one. * return true if there is a collision. */ bool ABI_Collab_Import::_checkForCollision(const AbstractChangeRecordSessionPacket& acrsp, UT_sint32& iRev, UT_sint32& iImportAdjustment) { UT_DEBUGMSG(("ABI_Collab_Import::_checkForCollision() - pos: %d, length: %d, UUID: %s, remoterev: %d\n", acrsp.getPos(), acrsp.getLength(), acrsp.getDocUUID().utf8_str(), acrsp.getRemoteRev())); ABI_Collab_Export* pExport = m_pAbiCollab->getExport(); UT_return_val_if_fail(pExport, false); const UT_GenericVector<ChangeAdjust *>* pExpAdjusts = pExport->getAdjusts(); UT_return_val_if_fail(pExpAdjusts, false); iImportAdjustment = 0; // get the collision sequence (if any) UT_sint32 iStart = 0; UT_sint32 iEnd = 0; _calculateCollisionSeqence(acrsp.getRemoteRev(), acrsp.getDocUUID(), iStart, iEnd); UT_return_val_if_fail(iStart >= 0 && iEnd >= 0, false); if (iStart == iEnd) { UT_DEBUGMSG(("Empty collision sequence, no possible collision\n")); return false; } std::deque<int> incAdjs; UT_sint32 iIncomingStateAdjust = _getIncomingAdjustmentForState(pExpAdjusts, iStart, iEnd, acrsp.getPos(), acrsp.getLength(), acrsp.getDocUUID(), incAdjs); UT_DEBUGMSG(("IINCOMMINGSTATEADJUST: %d\n", iIncomingStateAdjust)); // Now scan forward and look for an overlap of the new changerecord with the collision sequence UT_DEBUGMSG(("Checking collision sequence [%d..%d) for overlapping changerecords\n", iStart, iEnd)); bool bDenied = false; for (UT_sint32 i = iStart; i < iEnd; i++) { ChangeAdjust* pChange = pExpAdjusts->getNthItem(i); if (pChange) { UT_DEBUGMSG(("Looking at pChange->getRemoteDocUUID(): %s\n", pChange->getRemoteDocUUID().utf8_str())); if (pChange->getRemoteDocUUID() != acrsp.getDocUUID()) { if (_isOverlapping(acrsp.getPos()+iIncomingStateAdjust, acrsp.getLength(), pChange->getLocalPos(), pChange->getLocalLength()) && !AbiCollab_ImportRuleSet::isOverlapAllowed(*pChange, acrsp, iIncomingStateAdjust)) { UT_DEBUGMSG(("Fatal overlap detected for incoming pos: %d, incoming length: %d, pChange->getLocalPos(): %d, pChange->getLocalLength(): %d\n", acrsp.getPos(), acrsp.getLength(), pChange->getLocalPos(), pChange->getLocalLength())); iRev = pChange->getLocalRev(); bDenied = true; break; } else { UT_DEBUGMSG(("No (fatal) overlap detected for incoming pos: %d, incoming length: %d, pChange->getLocalPos(): %d, pChange->getLocalLength(): %d\n", acrsp.getPos(), acrsp.getLength(), pChange->getLocalPos(), pChange->getLocalLength())); } if (pChange->getLocalPos() < acrsp.getPos()+iIncomingStateAdjust) { UT_DEBUGMSG(("Normal Upward influence detected\n")); iIncomingStateAdjust += pChange->getLocalAdjust(); } } else { UT_DEBUGMSG(("Skipping overlap detection: changerecords came from the same document; incoming pos: %d, incoming length: %d, pChange->getLocalPos(): %d, pChange->getLocalLength(): %d\n", acrsp.getPos(), acrsp.getLength(), pChange->getLocalPos(), pChange->getLocalLength())); if (!incAdjs.empty()) { iIncomingStateAdjust += incAdjs.front(); incAdjs.pop_front(); } else { UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } } UT_DEBUGMSG(("Now: iIncomingStateAdjust: %d\n", iIncomingStateAdjust)); } else UT_return_val_if_fail(false, false); } if (!bDenied && !incAdjs.empty()) { UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } while (!incAdjs.empty()) { UT_DEBUGMSG(("Adding left-over incoming adjustment: %d\n", incAdjs.front())); iIncomingStateAdjust += incAdjs.front(); incAdjs.pop_front(); } iImportAdjustment = iIncomingStateAdjust; UT_DEBUGMSG(("Full import adjustment: %d\n", iImportAdjustment)); return bDenied; }
UT_sint32 ABI_Collab_Import::_getIncomingAdjustmentForState(const UT_GenericVector<ChangeAdjust *>* pExpAdjusts, UT_sint32 iStart, UT_sint32 iEnd, UT_sint32 iIncomingPos, UT_sint32 iIncomingLength, const UT_UTF8String& sIncomingUUID, std::deque<int>& incAdjs) { UT_DEBUGMSG(("ABI_Collab_Import::_getIncomingAdjustmentForState()\n")); UT_return_val_if_fail(pExpAdjusts, 0); UT_sint32 iAdjust = 0; for (UT_sint32 j = iEnd-1; j>=iStart; j--) { ChangeAdjust* pPrev = pExpAdjusts->getNthItem(j); if (sIncomingUUID == pPrev->getRemoteDocUUID()) { UT_DEBUGMSG(("Looking at possible adjustment with queue pos: %d, -adjust: %d\n", j, -pPrev->getLocalAdjust())); if (static_cast<UT_sint32>(pPrev->getRemoteDocPos()) < iIncomingPos+iAdjust) { if (pPrev->getLocalAdjust() > 0) { if (_isOverlapping(pPrev->getRemoteDocPos(), pPrev->getLocalLength(), iIncomingPos+iAdjust, iIncomingLength)) { // NOTE: if the position was in the middle of an insert done previously, // then we only need to take the insertion adjust partially into account UT_DEBUGMSG(("ADJUST OVERLAP DETECTED with queue pos: %d, pPrev->getRemoteDocPos(): %d, pPrev->m_iLength: %d, iIncomingPos: %d, iAdjust: %d\n", j, pPrev->getRemoteDocPos(), pPrev->getLocalLength(), iIncomingPos, iAdjust)); iAdjust -= (iIncomingPos+iAdjust - pPrev->getRemoteDocPos()); incAdjs.push_front(iIncomingPos+iAdjust - pPrev->getRemoteDocPos()); } else { UT_DEBUGMSG(("ADJUSTMENT influenced normally by queue pos: %d\n", j)); iAdjust -= pPrev->getLocalAdjust(); incAdjs.push_front(pPrev->getLocalAdjust()); } } else if (pPrev->getLocalAdjust() < 0) { // TODO: is the < 0 case correctly handled like this? UT_DEBUGMSG(("ADJUSTMENT influence by delete by queue pos: %d, pPrev->m_iProgDocPos: %d, pPrev->getRemoteDocPos(): %d\n", j, pPrev->getRemoteDocPos(), pPrev->getRemoteDocPos())); iAdjust -= pPrev->getLocalAdjust(); incAdjs.push_front(pPrev->getLocalAdjust()); } else { UT_DEBUGMSG(("ADJUSTMENT influence of 0 by queue pos: %d, pPrev->m_iProgDocPos: %d, pPrev->getRemoteDocPos(): %d\n", j, pPrev->getRemoteDocPos(), pPrev->getRemoteDocPos())); incAdjs.push_front(0); } } else if (static_cast<UT_sint32>(pPrev->getRemoteDocPos()) > iIncomingPos+iAdjust) { UT_DEBUGMSG(("no ADJUSTMENT influence (insertion point smaller than checkpoint) by queue pos: %d, pPrev->m_iProgDocPos: %d, pPrev->getRemoteDocPos(): %d\n", j, pPrev->getRemoteDocPos(), pPrev->getRemoteDocPos())); incAdjs.push_front(0); } else { UT_DEBUGMSG(("no ADJUSTMENT influence (insertion point equals checkpoint) by queue pos: %d, pPrev->m_iProgDocPos: %d, pPrev->getRemoteDocPos(): %d\n", j, pPrev->getRemoteDocPos(), pPrev->getRemoteDocPos())); incAdjs.push_front(0); } } } return iAdjust; }