/* -*- 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 * * 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "ut_assert.h" #include "ut_debugmsg.h" #include "xap_App.h" #include "xap_Frame.h" #include "fv_View.h" #include "xav_View.h" #include "xav_Listener.h" #include "fl_BlockLayout.h" #include "pd_Document.h" #include "CommandLine.h" #include "ie_types.h" #include "ut_types.h" #include "ut_misc.h" #include "ut_units.h" #include "ap_Strings.h" #include "xap_Prefs.h" #include "ap_Frame.h" #ifdef WIN32 #include #endif #include "AbiCollab_Export.h" #include "AbiCollab_Import.h" #include "AbiCollab.h" #include "AbiCollabSessionManager.h" #include #include /* bool AbiCollabFactoryContainer::extractParams(UT_UTF8String & sCommandLine, UT_UTF8String & sServer, UT_UTF8String & sPort, UT_UTF8String & sUsername, UT_UTF8String & sPassword, UT_UTF8String ** psRemoteUser, UT_UTF8String ** psRemoteServer, UT_UTF8String ** psPathName) { int _argc = 0; char **_argv = NULL; if (g_shell_parse_argv (sCommandLine.utf8_str(), &_argc, &_argv, NULL)) { if(_argc < 4) { return false; } sServer = _argv[0]; sPort = _argv[1]; sUsername = _argv[2]; sPassword = _argv[3]; if (_argc > 4) { *psRemoteUser = new UT_UTF8String(_argv[4]); } if (_argc > 5) { *psRemoteServer = new UT_UTF8String(_argv[5]); } if (_argc > 6) { *psPathName = new UT_UTF8String(_argv[6]); } } } */ ChangeAdjust::ChangeAdjust(PT_DocPosition iDocPos, PT_DocPosition iOrigDocPos, UT_sint32 iAdjust, UT_sint32 iRev, UT_sint32 iLength, UT_sint32 iOrigRemoteSeen, const char* pDocUUID) : m_iDocPos(iDocPos), m_iOrigDocPos(iOrigDocPos), m_iAdjust(iAdjust), m_iCRNumber(iRev), m_iLength(iLength), m_iOrigRemoteSeen(iOrigRemoteSeen) { m_pDocUUID = g_strdup(pDocUUID); } ChangeAdjust::~ChangeAdjust() { g_free(m_pDocUUID); } /*AbiCollab::AbiCollab(AbiCollabFactoryContainer* pFactory, PD_Document * pDoc, UT_UTF8String sID, bool bAsCommandLine, UT_UTF8String * pPathName) : m_pFactory(pFactory), m_pDoc(pDoc), m_pImport(NULL), m_pExport(NULL), m_sID(sID), m_iDocListenerId(0), m_bOffering(false), m_pCommandLine(NULL), m_bCloseNow(false), m_bExportMasked(false), m_pRemoteListener(NULL), m_bIsMaster(false), m_iServiceID(0), m_pServiceExport(NULL) { // TODO: we should lazy-create these; ie. only if there are remote clients bool bLoadOK = false; if (bAsCommandLine) { m_pCommandLine = new CommandLine(); if (pPathName != NULL) { bLoadOK = m_pCommandLine->loadDocument(*pPathName); if (!bLoadOK) { m_pCommandLine->newDocument(); } } else { m_pCommandLine->newDocument(); } UT_ASSERT(m_pCommandLine->getCurrentDocument()); setDocument(m_pCommandLine->getCurrentDocument()); } }*/ // Use this constructor to host a collaboration session AbiCollab::AbiCollab(PD_Document* pDoc) : m_pDoc(pDoc), m_Import(this, pDoc), m_Export(this, pDoc), m_bExportMasked(false), m_pController(NULL), m_iDocListenerId(0), m_iOrigDocPos(-1), m_iOrigRemoteSeen(-1), m_bIsReverting(false) { // TODO: this can be made a lil' more efficient, as setDocument // will create import and export listeners, which is kinda useless // when there is no single collaborator yet _setDocument(pDoc); XAP_App* pApp = XAP_App::getApp(); UT_UUID* pUUID = pApp->getUUIDGenerator()->createUUID(); pUUID->toString(m_sId); UT_DEBUGMSG(("Inited AbiCollab Session with UUID: %s\n", m_sId.utf8_str())); } // Use this constructor to join a collaboration session AbiCollab::AbiCollab(const UT_UTF8String& sSessionId, PD_Document* pDoc, const UT_UTF8String& docUUID, UT_sint32 iRev, Buddy* pController) : m_sId(sSessionId), m_pDoc(pDoc), m_Import(this, pDoc), m_Export(this, pDoc), m_bExportMasked(false), m_pController(pController), m_iDocListenerId(0), m_iOrigDocPos(-1), m_iOrigRemoteSeen(-1), m_bIsReverting(false) { // TODO: this can be made a lil' more efficient, as setDocument // will create import and export listeners, which is kinda useless // when there is no single collaborator yet _setDocument(pDoc); m_Import.setInitialRemoteRev(pController->getName(), iRev); m_Export.addFakeImportAdjust(docUUID, iRev); // we will manually have to coalesce changerecords, as we will need // to be able to revert every individual changerecord for // collision handling if the session controller tells us too pDoc->setCoalescingMask(true); addCollaborator(pController); } AbiCollab::~AbiCollab(void) { UT_DEBUGMSG(("AbiCollab::~AbiCollab()\n")); if (m_iDocListenerId != 0) m_pDoc->removeListener(m_iDocListenerId); m_iDocListenerId = 0; } void AbiCollab::removeCollaborator(Buddy* pCollaborator) { UT_return_if_fail(pCollaborator); for (UT_sint32 i = UT_sint32(m_vecCollaborators.size()) - 1; i >= 0; i--) { Buddy* pBuddy = m_vecCollaborators[i]; UT_continue_if_fail(pBuddy); if (pBuddy->getName() == pCollaborator->getName()) _removeCollaborator(i); } } void AbiCollab::_removeCollaborator(UT_sint32 index) { UT_DEBUGMSG(("AbiCollab::_removeCollaborator() - index: %d\n", index)); UT_return_if_fail(index >= 0 && index < m_vecCollaborators.size()); // TODO: signal the removal of the buddy!!! // ... Buddy* pCollaborator = m_vecCollaborators[index]; UT_return_if_fail(pCollaborator); // remove this buddy from the import 'seen revision list' m_Import.getRemoteRevisions()[pCollaborator->getName().utf8_str()] = 0; m_vecCollaborators.erase( m_vecCollaborators.begin() + size_t(index) ); } void AbiCollab::addCollaborator(Buddy* pCollaborator) { UT_DEBUGMSG(("AbiCollab::addCollaborator()\n")); // check for duplicates (as long as we assume a collaborator can only be part of a collaboration session once) for (UT_sint32 i = 0; i < m_vecCollaborators.size(); i++) { Buddy* pBuddy = m_vecCollaborators[i]; if (pBuddy) { if (pBuddy->getName() == pCollaborator->getName()) { UT_DEBUGMSG(("Attempting to add buddy '%s' twice to a collaboration session!", pCollaborator->getName().utf8_str())); UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); return; } } } m_vecCollaborators.push_back(pCollaborator); } void AbiCollab::removeCollaboratorsForAccount(AccountHandler* pHandler) { UT_DEBUGMSG(("AbiCollab::removeCollaboratorsForAccount()\n")); UT_return_if_fail(pHandler); for (UT_sint32 i = UT_sint32(m_vecCollaborators.size())-1; i >= 0; i--) { Buddy* pBuddy = m_vecCollaborators[i]; UT_continue_if_fail(pBuddy); if (pBuddy->getHandler() == pHandler) _removeCollaborator(i); } } void AbiCollab::_setDocument(PD_Document* pDoc) { UT_DEBUGMSG(("AbiCollab::setDocument()\n")); UT_return_if_fail(pDoc); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); // assume clean state UT_return_if_fail(m_iDocListenerId==0); // update the frame m_pDoc = pDoc; // if the document doesn't belong to a frame already, then create a // new frame for this session (except when the document in the current // frame is not dirty, doesn't have a filename yet (which means it // is a brand new empty document), and isn't being shared at the moment) XAP_Frame *pFrame = XAP_App::getApp()->getLastFocussedFrame(); PD_Document * pFrameDoc = static_cast(pFrame->getCurrentDoc()); if (pFrameDoc != pDoc) { const char* szName = pFrameDoc->getFilename(); if (!szName && !pFrameDoc->isDirty() && !pManager->isInSession(pFrameDoc)) { // we can replace the document in this frame safely, as it is // brand new, and doesn't have any contents yet } else { // the current frame has already a document loaded, let's create // a new frame pFrame = XAP_App::getApp()->newFrame(); } } pFrame->loadDocument(m_pDoc); // this will also delete the old document (or at least, it should) // TODO: the frame shouldn't be raised // add the new export listeners UT_uint32 lid = 0; pDoc->addListener(static_cast(&m_Export), &lid); _setDocListenerId(lid); UT_DEBUGMSG(("Added document listener %d\n", lid)); } void AbiCollab::_fillRemoteRev( Packet* pPacket, const Buddy& oBuddy ) { UT_return_if_fail(pPacket); if (pPacket->getClassType() >= _PCT_FirstChangeRecord && pPacket->getClassType() <= _PCT_LastChangeRecord) { ChangeRecordSessionPacket* pSessionPacket = static_cast( pPacket ); pSessionPacket->setRemoteRev( m_Import.getRemoteRevisions()[oBuddy.getName().utf8_str()] ); } else if (pPacket->getClassType() == PCT_GlobSessionPacket) { GlobSessionPacket* pSessionPacket = static_cast( pPacket ); const std::vector& globPackets = pSessionPacket->getPackets(); for (std::vector::const_iterator cit = globPackets.begin(); cit != globPackets.end(); cit++) { SessionPacket* globPacket = *cit; UT_continue_if_fail(globPacket); _fillRemoteRev(globPacket, oBuddy); } } } /*! * Send this packet. Note, the specified packet does still belong to the calling class. * So if we want to store it (for masking), we HAVE to clone it first */ void AbiCollab::push( Packet* pPacket ) { UT_DEBUGMSG(("AbiCollab::push()\n")); UT_return_if_fail(pPacket); if (m_bIsReverting) { UT_DEBUGMSG(("This packet was generated by a local revert triggerd in the import; dropping on the floor!\n")); } else if (m_bExportMasked) { m_vecMaskedPackets.push_back( pPacket->clone() ); } else { // TODO: this could go in the session manager UT_DEBUGMSG(("Pusing packet to %d collaborators\n", m_vecCollaborators.size())); for (UT_sint32 i = 0; i < m_vecCollaborators.size(); i++) { Buddy* pCollaborator = m_vecCollaborators[i]; if (pCollaborator) { UT_DEBUGMSG(("Pushing packet to collaborator with name: %s\n", pCollaborator->getName().utf8_str())); AccountHandler* pHandler = pCollaborator->getHandler(); if (pHandler) { // overwrite remote revision for this collaborator _fillRemoteRev( pPacket, *pCollaborator ); // send! bool res = pHandler->send(pPacket, *pCollaborator); if (!res) UT_DEBUGMSG(("Error sending a packet!\n")); } } else UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } } } bool AbiCollab::push( Packet* pPacket, const Buddy& collaborator) { UT_return_val_if_fail(pPacket, false); AccountHandler* pHandler = collaborator.getHandler(); UT_return_val_if_fail(pHandler, false); // overwrite remote revision for this collaborator _fillRemoteRev( pPacket, collaborator ); // send! bool res = pHandler->send(pPacket, collaborator); if (!res) UT_DEBUGMSG(("Error sending a packet to collaborator %s!\n", collaborator.getName().utf8_str())); return res; } void AbiCollab::maskExport() { m_bExportMasked = true; m_vecMaskedPackets.clear(); } const std::vector& AbiCollab::unmaskExport() { m_bExportMasked = false; return m_vecMaskedPackets; } void AbiCollab::import(SessionPacket* pPacket, const Buddy& collaborator) { UT_DEBUGMSG(("AbiCollab::import()\n")); UT_return_if_fail(pPacket); // TODO: do this masking in the session manager maskExport(); m_Import.import(*pPacket, collaborator); const std::vector& maskedPackets = unmaskExport(); UT_DEBUGMSG(("AbiCollab::import() - TODO: handle masked packets\n")); if (isLocallyControlled() && maskedPackets.size() > 0) { // It seems we are in the center of a collaboration session. // It's our duty to reroute the packets to the other collaborators for (UT_sint32 i = 0; i < m_vecCollaborators.size(); i++) { // send all masked packets during import to everyone, except to the // person who initialy sent us the packet Buddy* pBuddy = m_vecCollaborators[i]; if (pBuddy && pBuddy->getName() != collaborator.getName()) { for (std::vector::const_iterator cit=maskedPackets.begin(); cit!=maskedPackets.end(); cit++) { Packet* maskedPacket = (*cit); AccountHandler* pHandler = pBuddy->getHandler(); if (pHandler) { // overwrite remote revision for this collaborator _fillRemoteRev( pPacket, *pBuddy ); // send! bool res = pHandler->send( pPacket, *pBuddy ); if (!res) UT_DEBUGMSG(("Error sending a packet!\n")); } else UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } } } } } void AbiCollab::addChangeAdjust(ChangeAdjust* pAdjust) { UT_return_if_fail(pAdjust); if (m_bIsReverting) { UT_DEBUGMSG(("This changeadjust was generated by a local revert triggerd in the import; dropping on the floor!\n")); return; } m_Export.getAdjusts()->addItem(pAdjust); } /* bool AbiCollab::handleImportEvent() { XAP_Frame *pFrame = XAP_App::getApp()->getLastFocussedFrame(); // If the remote user disconnects, disconnect here too. if (isClose()) { UT_UTF8String sID = getID(); UT_DEBUGMSG(("Removing This connection from remote signal \n")); // remove the main loop idle handler if(m_pRemoteListener) { m_pRemoteListener->stop(); } DELETEP(m_pRemoteListener); m_pFactory->destroy(const_cast(getDocument()),sID); } return TRUE; } */