/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */ /* AbiCollab- Code to enable the modification of remote documents. * Copyright (C) 2005 by Martin Sevior * Copyright (C) 2006 by Marc Maurer * Copyright (C) 2007 One Laptop Per Child * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include "pd_Document.h" #include "px_CR_SpanChange.h" #include "px_CR_FmtMarkChange.h" #include "px_CR_SpanChange.h" #include "px_CR_FmtMark.h" #include "px_CR_Span.h" #include "px_CR_Glob.h" #include "px_CR_StruxChange.h" #include "px_CR_ObjectChange.h" #include "px_CR_Strux.h" #include "px_CR_Object.h" #include "ut_bytebuf.h" #include "AbiCollab_Export.h" #include "AbiCollab_Import.h" #include "AbiCollab.h" #include "AbiCollabSessionManager.h" ABI_Collab_Export::ABI_Collab_Export(AbiCollab * pAbiCollab, PD_Document* pDoc) : m_pDoc(pDoc), m_pAbiCollab(pAbiCollab) { UT_DEBUGMSG(("Constructing AbiCollab Exporter \n")); _init(); } ABI_Collab_Export::~ABI_Collab_Export() { UT_DEBUGMSG(("AbiCollab Export deleted %p \n",this)); _cleanup(); } /*! * Helper function for buildPacket, used on 'real' change record packets only, so * PACKET_CHANGERECORD is always correct. */ template struct PacketFactory { static _PacketType* create( const PX_ChangeRecord * pcr, AbiCollab * pAbiCollab, PD_Document* pDoc ) { UT_DEBUGMSG(("Creating packet with doc UUID: %s\n", pDoc->getOrigDocUUIDString())); return new _PacketType( pAbiCollab->getSessionId(), /* session identifier */ pcr->getType(), /* type of change */ pDoc->getOrigDocUUIDString(), /* document identifier */ pcr->getPosition(), /* position */ pcr->getCRNumber(), /* local revision */ -1 /* remote revision unknown at this point */ ); }; }; PLListenerType ABI_Collab_Export::getType() const { return PTL_CollabExport; } /*! * This method converts a change record into a AbiCollab session packet */ ChangeRecordSessionPacket* ABI_Collab_Export::_buildPacket( const PX_ChangeRecord * pcr ) { UT_return_val_if_fail(pcr, NULL); UT_sint32 index = static_cast(pcr->getIndexAP()); switch(pcr->getType()) { case PX_ChangeRecord::PXT_GlobMarker: { const PX_ChangeRecord_Glob* pcrg = reinterpret_cast(pcr); // build change record packet Glob_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust packet->setLength(0); packet->setAdjust(0); // set special properties packet->m_iGLOBType = pcrg->getFlags(); return packet; } case PX_ChangeRecord::PXT_InsertSpan: { // build change record packet InsertSpan_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust const PX_ChangeRecord_SpanChange * pcrc = static_cast(pcr); packet->setLength( pcrc->getLength() ); packet->setAdjust( pcrc->getLength() ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); // set special properties PT_BufIndex bi = pcrc->getBufIndex(); const UT_UCS4Char* pChars = m_pDoc->getPointer(bi); packet->m_sText.appendUCS4(pChars, pcrc->getLength() ); return packet; } case PX_ChangeRecord::PXT_DeleteSpan: { // build change record packet ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust const PX_ChangeRecord_SpanChange * pcrc = static_cast(pcr); packet->setLength( pcrc->getLength() ); packet->setAdjust( -pcrc->getLength() ); return packet; } case PX_ChangeRecord::PXT_ChangeSpan: { // build change record Props_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length const PX_ChangeRecord_SpanChange * pcrc = static_cast (pcr); packet->setLength( pcrc->getLength() ); packet->setAdjust( 0 ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); return packet; } case PX_ChangeRecord::PXT_InsertStrux: { // build change record packet ChangeStrux_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust const PX_ChangeRecord_Strux * pcrx = static_cast (pcr); packet->m_eStruxType = pcrx->getStruxType(); packet->setLength( 1 ); packet->setAdjust( 1 ); // set properties // we do not always want to map our attrs/props (why not?? - MARCM) bool mapAttributes = false; switch (packet->m_eStruxType) { case PTX_Block: if (index != m_iBlockIndex) { m_iBlockIndex = index; mapAttributes = true; } break; case PTX_Section: case PTX_SectionHdrFtr: // TODO; is this correct??? i have _no_ clue - MARCM if (index != m_iSectionIndex) { m_iSectionIndex = index; mapAttributes = true; } break; case PTX_SectionFootnote: case PTX_SectionAnnotation: case PTX_SectionTOC: case PTX_SectionEndnote: case PTX_SectionTable: case PTX_SectionCell: case PTX_SectionFrame: mapAttributes = true; break; default: break; } if (mapAttributes) _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); return packet; } case PX_ChangeRecord::PXT_ChangeStrux: { // build change record packet ChangeStrux_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust const PX_ChangeRecord_StruxChange * pcrx = static_cast (pcr); packet->m_eStruxType = pcrx->getStruxType(); packet->setLength( 1 ); packet->setAdjust( 0 ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); return packet; } case PX_ChangeRecord::PXT_DeleteStrux: { // build change record packet DeleteStrux_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust packet->setLength( 1 ); packet->setAdjust( -1 ); // set properties const PX_ChangeRecord_Strux * pcrx = static_cast (pcr); packet->m_eStruxType = pcrx->getStruxType(); return packet; } case PX_ChangeRecord::PXT_InsertObject: { // build change record packet Object_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust packet->setLength( 1 ); // TODO: is this correct? - MARCM packet->setAdjust( 1 ); // TODO: is this correct? - MARCM // set properties const PX_ChangeRecord_Object * pcro = static_cast(pcr); packet->setObjectType(pcro->getObjectType()); _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); return packet; } case PX_ChangeRecord::PXT_ChangeObject: { // build change record packet Object_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust packet->setLength( 1 ); // TODO: is this correct? - MARCM packet->setAdjust( 0 ); // TODO: is this correct? - MARCM // set properties const PX_ChangeRecord_ObjectChange * pcro = static_cast(pcr); packet->setObjectType(pcro->getObjectType()); _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); return packet; } case PX_ChangeRecord::PXT_DeleteObject: { // build change record packet Object_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust packet->setLength( 1 ); packet->setAdjust( -1 ); // set properties const PX_ChangeRecord_Object * pcro = static_cast(pcr); packet->setObjectType(pcro->getObjectType()); return packet; } case PX_ChangeRecord::PXT_InsertFmtMark: case PX_ChangeRecord::PXT_ChangeFmtMark: { // build change record Props_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); // set length and adjust packet->setLength( 0 ); packet->setAdjust( 0 ); return packet; } case PX_ChangeRecord::PXT_DeleteFmtMark: case PX_ChangeRecord::PXT_ChangePoint: case PX_ChangeRecord::PXT_ListUpdate: case PX_ChangeRecord::PXT_StopList: case PX_ChangeRecord::PXT_UpdateField: case PX_ChangeRecord::PXT_RemoveList: // should this be here? - MARCM case PX_ChangeRecord::PXT_UpdateLayout: { // build change record ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust packet->setLength( 0 ); packet->setAdjust( 0 ); return packet; } case PX_ChangeRecord::PXT_CreateDataItem: { // build change record Data_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); // fetch data item data const PP_AttrProp * pAP = NULL; PT_AttrPropIndex indexAP = static_cast(index); bool b = m_pDoc->getAttrProp(indexAP, &pAP); UT_return_val_if_fail(b,NULL); const gchar * pszDataName = NULL; (pAP)->getAttribute(PT_DATAITEM_ATTRIBUTE_NAME,pszDataName); if(pszDataName == NULL) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return NULL; } if(g_str_has_prefix(pszDataName,"snapshot-png-") == TRUE) { // // Drop the snapshot, let the remote document create it's own // delete packet; return NULL; } UT_ConstByteBufPtr pBuf; std::string mime_type; PD_DataItemHandle pHandle = NULL; m_pDoc->getDataItemDataByName(pszDataName, pBuf, &mime_type, &pHandle); // put into our vector size_t length = pBuf->getLength(); packet->m_vecData.resize( length ); memcpy( &packet->m_vecData[0], pBuf->getPointer(0), length ); if (!mime_type.empty()) { packet->m_bTokenSet = true; packet->m_sToken = mime_type; } else { packet->m_bTokenSet = false; } // set length and adjust packet->setLength( 0 ); packet->setAdjust( 0 ); return packet; } case PX_ChangeRecord::PXT_ChangeDocProp: { // build change record Props_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); // set length and adjust // Document Properties have not size packet->setLength( 0 ); packet->setAdjust( 0 ); return packet; } case PX_ChangeRecord::PXT_ChangeDocRDF: { // build change record packet RDF_ChangeRecordSessionPacket* packet = PacketFactory::create( pcr, m_pAbiCollab, m_pDoc ); // set length and adjust // Document Properties have not size packet->setLength( 0 ); packet->setAdjust( 0 ); // set properties _mapPropsAtts( index, packet->getPropMap(), packet->getAttMap() ); return packet; } default: UT_DEBUGMSG(("Unimplemented pcr->getType(): %d\n", pcr->getType())); UT_ASSERT_HARMLESS(UT_NOT_IMPLEMENTED); return NULL; } } /*! * This method extracts all the attributes (including the property string * from the index provides and maps them to a utf-8 string. * The result can be placed inside an XML name space. */ void ABI_Collab_Export::_mapPropsAtts( UT_sint32 indx, std::map& props, std::map& atts ) { // fetch data const PP_AttrProp * pAP = NULL; PT_AttrPropIndex indexAP = static_cast(indx); bool b = m_pDoc->getAttrProp(indexAP, &pAP); UT_return_if_fail(b); // necessary vars UT_sint32 i = 0; const gchar * szName = NULL; const gchar * szVal = NULL; // map attributes atts.clear(); UT_sint32 nAtts = static_cast(pAP->getAttributeCount()); for(i=0; igetNthAttribute(i,szName,szVal); if (szName && szVal) { UT_DEBUGMSG(("ABI_Collab_Export::mapPropsAtts: '%s' = '%s'\n", szName, szVal)); atts[ szName ] = static_cast( szVal ); } } // map properties props.clear(); UT_sint32 nProps = static_cast(pAP->getPropertyCount()); for(i=0; igetNthProperty(i,szName,szVal); if (szName && szVal) { props[static_cast( szName )] = static_cast( szVal ); } } } /*! * Returns true if the stop flag matches the start. */ bool ABI_Collab_Export::_isGlobEnd(UT_Byte istart, UT_Byte istop) { if((istart == PX_ChangeRecord_Glob::PXF_UserAtomicStart) && (istop == PX_ChangeRecord_Glob::PXF_UserAtomicEnd)) return true; if((istart == PX_ChangeRecord_Glob::PXF_MultiStepStart) && (PX_ChangeRecord_Glob::PXF_MultiStepEnd)) return true; return false; } /*! * Implements the "change" method of the document listener class. * It takes the supplied change record and makes an XML-like representation * of it. Eventually these can either be stored in a file or sent over the * internet to a remote AbiWord where it can be translated back. */ bool ABI_Collab_Export::change(fl_ContainerLayout* /*sfh*/, const PX_ChangeRecord * pcr) { // build new packet ChangeRecordSessionPacket * newPacket = _buildPacket( pcr ); UT_return_val_if_fail(newPacket, true); // unhandled change record type // update internal state if (pcr->getType() == PX_ChangeRecord::PXT_GlobMarker) { const PX_ChangeRecord_Glob * pcrg = static_cast(pcr); if (m_pGlobPacket) { UT_return_val_if_fail(m_pGlobPacket->getPackets().size() > 0, true); UT_return_val_if_fail(m_pGlobPacket->getPackets()[0]->getClassType() == PCT_Glob_ChangeRecordSessionPacket, true); Glob_ChangeRecordSessionPacket* m_pOpeningGlobMarker = static_cast(m_pGlobPacket->getPackets()[0]); if (_isGlobEnd(m_pOpeningGlobMarker->m_iGLOBType, pcrg->getFlags())) { m_pGlobPacket->addPacket( newPacket ); // push packet m_pAbiCollab->push( m_pGlobPacket ); m_pAbiCollab->addChangeAdjust( new ChangeAdjust( *m_pGlobPacket, (m_pAbiCollab->getActivePacket() ? m_pAbiCollab->getActivePacket()->getPos() : -1), m_pDoc->getMyUUIDString()) ); // reset glob state DELETEP(m_pGlobPacket); m_pGlobPacket = 0; return true; } else { // not end of glob? then this must be at least true UT_return_val_if_fail(pcrg->getFlags() != PX_ChangeRecord_Glob::PXF_UserAtomicStart, false); } } else { m_pGlobPacket = new GlobSessionPacket( newPacket->getSessionId(), newPacket->getDocUUID() ); } } _handleNewPacket( newPacket, pcr ); return true; } /*! * Handles new packet, by either adding it to a glob if we're doing a glob, * or by pushing it to the collab session directly */ void ABI_Collab_Export::_handleNewPacket(ChangeRecordSessionPacket* pPacket, const PX_ChangeRecord* /*pcr*/) { UT_return_if_fail( pPacket ); if (m_pGlobPacket) { m_pGlobPacket->addPacket( pPacket ); } else { m_pAbiCollab->push( pPacket ); m_pAbiCollab->addChangeAdjust( new ChangeAdjust( *pPacket, (m_pAbiCollab->getActivePacket() ? m_pAbiCollab->getActivePacket()->getPos() : -1), m_pDoc->getMyUUIDString()) ); DELETEP(pPacket); } } /*! * Implements the "insertStrux" method of the document listener class. * It takes the supplied change record and makes an XML-like representation * of it. Eventually these can either be stored in a file or sent over the * internet to a remote AbiWord where it can be translated back. */ bool ABI_Collab_Export::insertStrux(fl_ContainerLayout* sfh, const PX_ChangeRecord * pcr, pf_Frag_Strux* sdh, PL_ListenerId lid, void (* pfnBindHandles)(pf_Frag_Strux* sdhNew, PL_ListenerId lid, fl_ContainerLayout* sfhNew)) { if(pfnBindHandles) { // Ugly hack for MSVC and GCC comlilant warnings #ifdef __GNUC__ #warning not sure #else #pragma message("WARNING: not sure") #endif fl_ContainerLayout* sfhNew = sfh; // WAS static_cast(this); pfnBindHandles(sdh,lid,sfhNew); } ChangeRecordSessionPacket* newPacket = _buildPacket( pcr ); UT_return_val_if_fail(newPacket, true); // unhandled change record type _handleNewPacket( newPacket, pcr ); return true; } /*! * Implements the signal() method of the Document listener class. */ bool ABI_Collab_Export::signal(UT_uint32 iSignal) { UT_DEBUGMSG(("ABI_Collab_Export::signal()! %d \n",iSignal)); if(iSignal == PD_SIGNAL_SAVEDOC) { return true; } SignalSessionPacket* ssp = new SignalSessionPacket(m_pAbiCollab->getSessionId(), m_pDoc->getOrigDocUUIDString(), iSignal); if(m_pGlobPacket) { m_pGlobPacket->addPacket( ssp ); } else { m_pAbiCollab->push( ssp ); delete ssp; } return true; } /*! * This virtual method is called from the AbiWord main tree upon doing a replace document with an attached * AbiCollab_Export connected to the document. * * Note: this is a really weird signal, coming from a PD_Document * Note: If anything, a Frame should emit this signal to its listeners */ void ABI_Collab_Export::setNewDocument(PD_Document * /*pDoc*/) { UT_DEBUGMSG(("ABI_Collab_Export::setNewDocument()\n")); // inform the session manager to kill off this session, as that is the only // thing we can do if the document is replaced AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); // WARNING: Don't do anything after this line, as the disconnectSession() call will // have destroyed ourselves (yes, that is ugly). pManager->disconnectSession(m_pAbiCollab); // FIXME: The AbiCollab destructor will unregister this object as a PD_Document listener, // while the PD_Document will also unregister us as soon as this function returns. // Unregistering the same object twice works, but it is ugly as hell. Fix this someday. } /*! * This virtual method is called if the attached document is deleted with an attached * AbiCollab_Export connected to the document. */ void ABI_Collab_Export::removeDocument(void) { UT_DEBUGMSG(("ABI_Collab_Export::removeDocument()\n")); // inform the session manager that this session is being (forcefully) closed AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); // WARNING: Don't do anything after this line, as the disconnectSession() call will // have destroyed ourselves (yes, that is ugly). pManager->disconnectSession(m_pAbiCollab); } void ABI_Collab_Export::masterInit() { UT_DEBUGMSG(("ABI_Collab_Export::masterInit()\n")); // NOTE: it's important that this function resets all state, as it can be // called in the middle of an already running collaboration session // (eg. when a session takeover happens) _cleanup(); _init(); } void ABI_Collab_Export::slaveInit(const std::string& docUUID, UT_sint32 iRemoteRev) { UT_DEBUGMSG(("ABI_Collab_Export::slaveInit() - docUUID: %s, iRev: %d\n", docUUID.c_str(), iRemoteRev)); // NOTE: it's important that this function resets all state, as it can be // called in the middle of an already running collaboration session // (eg. when a session takeover happens) _cleanup(); _init(); // initialize the adjustment stack ChangeRecordSessionPacket voidPacket; voidPacket.setDocUUID(docUUID); voidPacket.setRev(iRemoteRev); m_pAbiCollab->addChangeAdjust(new ChangeAdjust(voidPacket, static_cast(0), docUUID)); } void ABI_Collab_Export::_init() { m_chgMaskCached = 0; m_bCacheChanges = false; m_iBlockIndex = -1; m_iSectionIndex = -1; m_pGlobPacket = NULL; m_vecAdjusts.clear(); } void ABI_Collab_Export::_cleanup() { UT_sint32 i = static_cast(m_vecAdjusts.getItemCount()); while(i > 0) { delete m_vecAdjusts.getNthItem(i-1); i--; } DELETEP( m_pGlobPacket ); }