void AbiCollab::initiateSessionTakeover(BuddyPtr pNewMaster) { UT_return_if_fail(pNewMaster); UT_DEBUGMSG(("AbiCollab::initiateSessionTakeover() - pNewMaster: %s\n", pNewMaster->getDescriptor(true).utf8_str())); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); // this could lead to us never exiting; add a timeout or something somewhere :) pManager->beginAsyncOperation(this); // NOTE: we only allow slaves in the session takeover process // that are on the same account as the proposed master is. The // others are dropped from the session. At least for now. // TODO: implement me // reset any old session takeover state m_bProposedController = false; m_pProposedController = pNewMaster; m_vApprovedReconnectBuddies.clear(); m_mAckedSessionTakeoverBuddies.clear(); m_bSessionFlushed = false; if (m_vOutgoingQueue.size() > 0) UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); m_vOutgoingQueue.clear(); // send a SessionTakeoverRequest packet to the new master std::vector<std::string> buddyIdentifiers; for (std::map<BuddyPtr, std::string>::iterator it = m_vCollaborators.begin(); it != m_vCollaborators.end(); it++) { BuddyPtr pBuddy = (*it).first; UT_continue_if_fail(pBuddy); if (pNewMaster != pBuddy) buddyIdentifiers.push_back(pBuddy->getDescriptor(true).utf8_str()); } SessionTakeoverRequestPacket strp_promote(m_sId, m_pDoc->getDocUUIDString(), true, buddyIdentifiers); pNewMaster->getHandler()->send(&strp_promote, pNewMaster); // send a SessionTakeoverRequest packet to the other slaves (if any) buddyIdentifiers.clear(); buddyIdentifiers.push_back(pNewMaster->getDescriptor(true).utf8_str()); SessionTakeoverRequestPacket strp_normal(m_sId, m_pDoc->getDocUUIDString(), false, buddyIdentifiers); for (std::map<BuddyPtr, std::string>::iterator it = m_vCollaborators.begin(); it != m_vCollaborators.end(); it++) { BuddyPtr pBuddy = (*it).first; UT_continue_if_fail(pBuddy); if (pNewMaster != pBuddy) pBuddy->getHandler()->send(&strp_normal, pBuddy); } m_eTakeoveState = STS_SENT_TAKEOVER_REQUEST; }
void AccountHandler::_reportProtocolError(UT_sint32 remoteVersion, UT_sint32 errorEnum, BuddyPtr pBuddy) { #ifndef DEBUG UT_UNUSED(remoteVersion); UT_UNUSED(errorEnum); #endif UT_DEBUGMSG(("_reportProtocolError: remoteVersion=%d errorEnum=%d\n", remoteVersion, errorEnum)); UT_return_if_fail(pBuddy); static std::set<std::string> reportedBuddies; if (reportedBuddies.insert( pBuddy->getDescriptor(false).utf8_str() ).second) { UT_UTF8String msg; switch (errorEnum) { case PE_Invalid_Version: msg = UT_UTF8String_sprintf("Your buddy %s is using version %d of AbiCollab, while you are using version %d.\n" "Please make sure you are using the same AbiWord version.", pBuddy->getDescription().utf8_str(), remoteVersion, ABICOLLAB_PROTOCOL_VERSION); break; default: msg = UT_UTF8String_sprintf("An unknown error code %d was reported by buddy %s.", errorEnum, pBuddy->getDescription().utf8_str()); break; } XAP_App::getApp()->getLastFocussedFrame()->showMessageBox( msg.utf8_str(), XAP_Dialog_MessageBox::b_O, XAP_Dialog_MessageBox::a_OK); } }
void TCPAccountHandler::addBuddy(BuddyPtr pBuddy) { UT_DEBUGMSG(("TCPAccountHandler::addBuddy()\n")); UT_return_if_fail(pBuddy); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); if (getProperty("allow-all") == "true") { const UT_GenericVector<AbiCollab *> pSessions = pManager->getSessions(); for (UT_sint32 i = 0; i < pSessions.size(); i++) { AbiCollab* pSession = pSessions.getNthItem(i); UT_continue_if_fail(pSession); if (pSession->getAclAccount() != this) continue; pSession->appendAcl(pBuddy->getDescriptor(false).utf8_str()); } } AccountHandler::addBuddy(pBuddy); }
bool AccountHandler::hasAccess(const std::vector<std::string>& vAcl, BuddyPtr pBuddy) { UT_return_val_if_fail(pBuddy, false); for (UT_uint32 i = 0; i < vAcl.size(); i++) if (vAcl[i] == pBuddy->getDescriptor(false)) return true; return false; }
bool AP_Dialog_CollaborationShare::_inAcl(const std::vector<std::string>& vAcl, BuddyPtr pBuddy) { UT_return_val_if_fail(pBuddy, false); for (UT_uint32 i = 0; i < vAcl.size(); i++) { if (vAcl[i] == pBuddy->getDescriptor(false).utf8_str()) return true; } return false; }
/*! * 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(SessionPacket* 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")); return; } if (m_bExportMasked) { m_vecMaskedPackets.push_back(static_cast<SessionPacket*>(pPacket->clone())); // TODO: make this a shared ptr, so we don't need to clone the packet return; } if (!isLocallyControlled() && m_eTakeoveState != STS_NONE) { // TODO: revert ack packets should still go to old master // (or be dropped on the floor, as he probably is not even around anymore) UT_DEBUGMSG(("We're in the middle of a session takeover; holding on to the packet until the new master is ready")); m_vOutgoingQueue.push_back(static_cast<SessionPacket*>(pPacket->clone())); // TODO: make this a shared ptr, so we don't need to clone the packet return; } // record if (m_pRecorder) m_pRecorder->storeOutgoing( const_cast<const SessionPacket*>( pPacket ) ); // TODO: this could go in the session manager UT_DEBUGMSG(("Pusing packet to %d collaborators\n", m_vCollaborators.size())); for (std::map<BuddyPtr, std::string>::iterator it = m_vCollaborators.begin(); it != m_vCollaborators.end(); it++) { BuddyPtr pCollaborator = (*it).first; UT_continue_if_fail(pCollaborator); UT_DEBUGMSG(("Pushing packet to collaborator with descriptor: %s\n", pCollaborator->getDescriptor(true).utf8_str())); AccountHandler* pHandler = pCollaborator->getHandler(); UT_continue_if_fail(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")); } } }
void AP_UnixDialog_CollaborationShare::_getSelectedBuddies(std::vector<std::string>& vACL) { UT_DEBUGMSG(("AP_UnixDialog_CollaborationShare::_getSelectedBuddies()\n")); vACL.clear(); GtkTreeIter iter; if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL (m_pBuddyModel), &iter)) return; do { gboolean bshare; gpointer buddy_wrapper = NULL; gtk_tree_model_get (GTK_TREE_MODEL (m_pBuddyModel), &iter, SHARE_COLUMN, &bshare, -1); gtk_tree_model_get (GTK_TREE_MODEL (m_pBuddyModel), &iter, BUDDY_COLUMN, &buddy_wrapper, -1); if (bshare && buddy_wrapper) { BuddyPtr pBuddy = reinterpret_cast<BuddyPtrWrapper*>(buddy_wrapper)->getBuddy(); vACL.push_back(pBuddy->getDescriptor(false).utf8_str()); } } while (gtk_tree_model_iter_next(GTK_TREE_MODEL (m_pBuddyModel), &iter)); }
void AbiCollab::_checkRevokeAccess(BuddyPtr pCollaborator) { UT_DEBUGMSG(("AbiCollab::_checkRevokeAccess()\n")); UT_return_if_fail(pCollaborator); UT_return_if_fail(isLocallyControlled()); UT_return_if_fail(m_pAclAccount); // remove this buddy from the access control list if his access rights // are not persistent if (!pCollaborator->getHandler()->hasPersistentAccessControl()) { for (std::vector<std::string>::iterator it = m_vAcl.begin(); it != m_vAcl.end(); it++) { if (pCollaborator->getDescriptor(false) == (*it)) { UT_DEBUGMSG(("Dropping %s from the ACL\n", (*it).c_str())); m_vAcl.erase(it); break; } } } }
void AccountHandler::_handlePacket(Packet* packet, BuddyPtr buddy) { // packet and buddy must always be set UT_return_if_fail(packet); UT_return_if_fail(buddy); // as must the session manager AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); // manager didn't handle it, see what we can do switch (packet->getClassType()) { case PCT_JoinSessionRequestEvent: { JoinSessionRequestEvent* jse = static_cast<JoinSessionRequestEvent*>(packet); // lookup session AbiCollab* pSession = pManager->getSessionFromSessionId(jse->getSessionId()); UT_return_if_fail(pSession); // check if this buddy is allowed to access this document // TODO: this should be done for every session packet, not just join session packets if (!hasAccess(pSession->getAcl(), buddy)) { // we should only reach this point if someone is brute forcing trying // out session IDs while not being on the ACL. Ban this uses. UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); return; } // lookup exporter ABI_Collab_Export* pExport = pSession->getExport(); UT_return_if_fail(pExport); // lookup adjusts const UT_GenericVector<ChangeAdjust *>* pExpAdjusts = pExport->getAdjusts(); UT_return_if_fail(pExpAdjusts); PD_Document* pDoc = pSession->getDocument(); // add this author to the document if we don't recognize him UT_sint32 iAuthorId = -1; UT_UTF8String buddyDescriptor = buddy->getDescriptor(); UT_GenericVector<pp_Author*> authors = pDoc->getAuthors(); UT_DEBUGMSG(("Scanning %d authors to see if we recognize this buddy\n", authors.getItemCount())); for (UT_sint32 i = 0; i < authors.getItemCount(); i++) { pp_Author* pAuthor = authors.getNthItem(i); UT_continue_if_fail(pAuthor); const gchar* szDescriptor = NULL; pAuthor->getProperty("abicollab-descriptor", szDescriptor); if (!szDescriptor) continue; if (buddyDescriptor != szDescriptor) continue; // yay, we know this author! iAuthorId = pAuthor->getAuthorInt(); UT_DEBUGMSG(("Found known author with descriptior %s, id %d!\n", buddyDescriptor.utf8_str(), iAuthorId)); break; } if (iAuthorId == -1) { // we don't know this author yet, create a new author object for him iAuthorId = pDoc->findFirstFreeAuthorInt(); pp_Author * pA = pDoc->addAuthor(iAuthorId); PP_AttrProp * pPA = pA->getAttrProp(); pPA->setProperty("abicollab-descriptor", buddyDescriptor.utf8_str()); pDoc->sendAddAuthorCR(pA); UT_DEBUGMSG(("Added a new author to the documument with descriptor %s, id %d\n", buddyDescriptor.utf8_str(), iAuthorId)); } // serialize entire document into string JoinSessionRequestResponseEvent jsre(jse->getSessionId(), iAuthorId); if (AbiCollabSessionManager::serializeDocument(pDoc, jsre.m_sZABW, false /* no base64 */) == UT_OK) { // set more document properties jsre.m_iRev = pDoc->getCRNumber(); jsre.m_sDocumentId = pDoc->getDocUUIDString(); if (pDoc->getFilename()) jsre.m_sDocumentName = UT_go_basename_from_uri(pDoc->getFilename()); // send to buddy! send(&jsre, buddy); // add this buddy to the collaboration session pSession->addCollaborator(buddy); } break; } case PCT_JoinSessionRequestResponseEvent: { JoinSessionRequestResponseEvent* jsre = static_cast<JoinSessionRequestResponseEvent*>( packet ); PD_Document* pDoc = 0; if (AbiCollabSessionManager::deserializeDocument(&pDoc, jsre->m_sZABW, false) == UT_OK) { if (pDoc) { // NOTE: we could adopt the same document name here, but i'd // rather not at the moment - MARCM pDoc->forceDirty(); if (jsre->m_sDocumentName.size() > 0) { gchar* fname = g_strdup(jsre->m_sDocumentName.utf8_str()); pDoc->setFilename(fname); } // The default ownership when joining is FALSE, as that seems // to make sense for the generic case. The person sharing the // document by default owns the document (and is thus allowed // to modify the ACL). pManager->joinSession(jsre->getSessionId(), pDoc, jsre->m_sDocumentId, jsre->m_iRev, jsre->getAuthorId(), buddy, this, false, NULL); } else { UT_DEBUGMSG(("AccountHandler::_handlePacket() - deserializing document failed!\n")); } } break; } case PCT_GetSessionsEvent: { GetSessionsResponseEvent gsre; const UT_GenericVector<AbiCollab *> sessions = pManager->getSessions(); for (UT_sint32 i = 0; i < sessions.getItemCount(); i++) { AbiCollab* pSession = sessions.getNthItem(i); if (pSession && pSession->isLocallyControlled()) { // check if the buddy has access to this session if (!hasAccess(pSession->getAcl(), buddy)) { UT_DEBUGMSG(("Buddy %s denied access to session %s by ALC\n", buddy->getDescriptor(true).utf8_str(), pSession->getSessionId().utf8_str())); continue; } const PD_Document * pDoc = pSession->getDocument(); UT_continue_if_fail(pDoc); // determine name UT_UTF8String documentBaseName; if (pDoc->getFilename()) documentBaseName = UT_go_basename_from_uri(pDoc->getFilename()); // set session info gsre.m_Sessions[ pSession->getSessionId() ] = documentBaseName; } } send(&gsre, buddy); break; } case PCT_GetSessionsResponseEvent: { GetSessionsResponseEvent* gsre = static_cast<GetSessionsResponseEvent*>( packet ); UT_GenericVector<DocHandle*> vDocHandles; for (std::map<UT_UTF8String,UT_UTF8String>::iterator it=gsre->m_Sessions.begin(); it!=gsre->m_Sessions.end(); ++it) { DocHandle* pDocHandle = new DocHandle((*it).first, (*it).second); vDocHandles.addItem(pDocHandle); } pManager->setDocumentHandles(buddy, vDocHandles); break; } default: { UT_DEBUGMSG(("Unhandled packet class: 0x%x\n", packet->getClassType())); UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } } }
bool AbiCollab::_handleSessionTakeover(AbstractSessionTakeoverPacket* pPacket, BuddyPtr collaborator) { UT_DEBUGMSG(("AbiCollab::_handleSessionTakeover()\n")); UT_return_val_if_fail(pPacket, false); UT_return_val_if_fail(collaborator, false); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_val_if_fail(pManager, false); switch (m_eTakeoveState) { case STS_NONE: { // we only accept a SessionTakeoverRequest or MasterChangeRequest packet UT_return_val_if_fail(pPacket->getClassType() == PCT_SessionTakeoverRequestPacket, false); // we can only allow such a packet from the controller UT_return_val_if_fail(m_pController == collaborator, false); // handle the SessionTakeoverRequestPacket packet m_pProposedController = BuddyPtr(); m_vApprovedReconnectBuddies.clear(); SessionTakeoverRequestPacket* strp = static_cast<SessionTakeoverRequestPacket*>(pPacket); m_bProposedController = strp->promote(); if (m_bProposedController) { for (std::vector<std::string>::const_iterator cit = strp->getBuddyIdentifiers().begin(); cit != strp->getBuddyIdentifiers().end(); cit++) m_vApprovedReconnectBuddies[*cit] = false; } else { UT_return_val_if_fail(strp->getBuddyIdentifiers().size() == 1, false); BuddyPtr pBuddy = pManager->constructBuddy(strp->getBuddyIdentifiers()[0], collaborator); UT_return_val_if_fail(pBuddy, false); m_pProposedController = pBuddy; } // inform the master that we received the takeover request SessionTakeoverAckPacket stap(m_sId, m_pDoc->getDocUUIDString()); collaborator->getHandler()->send(&stap, collaborator); m_eTakeoveState = STS_SENT_TAKEOVER_ACK; return true; } return false; case STS_SENT_TAKEOVER_REQUEST: { // we only accept SessionTakeoverAck packets UT_return_val_if_fail(pPacket->getClassType() == PCT_SessionTakeoverAckPacket, false); // we can only receive SessionTakeoverAck packets when we are the master UT_return_val_if_fail(!m_pController, false); // we should have a proposed master UT_return_val_if_fail(m_pProposedController, false); // a slave should only ack once UT_return_val_if_fail(!_hasAckedSessionTakeover(collaborator), false); // handle the SessionTakeoverAck packet m_mAckedSessionTakeoverBuddies[collaborator] = true; // check if every slave has acknowledged the session takeover // TODO: handle dropouts if (m_vCollaborators.size() == 1 || m_mAckedSessionTakeoverBuddies.size() == m_vCollaborators.size()) { // ... our tour of duty is done _shutdownAsMaster(); m_eTakeoveState = STS_NONE; return true; } } return true; case STS_SENT_TAKEOVER_ACK: // we only accept a SessionFlushed or SessionReconnectRequest packet UT_return_val_if_fail( pPacket->getClassType() == PCT_SessionFlushedPacket || pPacket->getClassType() == PCT_SessionReconnectRequestPacket, false ); if (pPacket->getClassType() == PCT_SessionReconnectRequestPacket) { // we only accept a SessionReconnectRequest when we are the proposed master UT_return_val_if_fail(m_bProposedController, false); // we only allow an incoming SessionReconnectRequest packet from a buddy // that is in the buddy list we received from the master, and we didn't receive // such a packet from him before bool allow = false; for (std::map<std::string, bool>::iterator it = m_vApprovedReconnectBuddies.begin(); it != m_vApprovedReconnectBuddies.end(); it++) { // TODO: is it a good idea to compare descriptors with full session information? if ((*it).first == collaborator->getDescriptor(true) && (*it).second == false) { (*it).second = true; allow = true; break; } } UT_return_val_if_fail(allow, false); // handle the SessionReconnectRequest packet addCollaborator(collaborator); _checkRestartAsMaster(); return true; } else if (pPacket->getClassType() == PCT_SessionFlushedPacket) { // we can only allow a SessionFlushed packet from the controller UT_return_val_if_fail(m_pController == collaborator, false); // handle the SessionFlushed packet m_bSessionFlushed = true; if (m_bProposedController) { // as far we we're concerned now, the old master is dead _becomeMaster(); _checkRestartAsMaster(); return true; } else { // as far we we're concerned now, the old master is dead _switchMaster(); // inform the new master that we want to rejoin the session SessionReconnectRequestPacket srrp(m_sId, m_pDoc->getDocUUIDString()); m_pProposedController->getHandler()->send(&srrp, m_pProposedController); m_eTakeoveState = STS_SENT_SESSION_RECONNECT_REQUEST; } return true; } return false; case STS_SENT_SESSION_RECONNECT_REQUEST: { // we only accept a SessionReconnectAck packet UT_return_val_if_fail(pPacket->getClassType() == PCT_SessionReconnectAckPacket, false); // we only accept said packet when we are a slave UT_return_val_if_fail(m_pController, false); // we only accept said packet when we are not the proposed master UT_return_val_if_fail(!m_bProposedController, false); // we only accept said packet from the proposed master UT_return_val_if_fail(m_pProposedController == collaborator, false); // handle the SessionReconnectAck packet SessionReconnectAckPacket* srap = static_cast<SessionReconnectAckPacket*>(pPacket); // Nuke the current collaboration state, and restart with the // given revision from the proposed master UT_return_val_if_fail(_restartAsSlave(srap->getDocUUID(), srap->getRev()), false); } return true; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } return false; }
void AbiCollabSessionManager::setDocumentHandles(BuddyPtr pBuddy, const UT_GenericVector<DocHandle*>& vDocHandles) { UT_DEBUGMSG(("Setting document handles for buddy %s\n", pBuddy->getDescriptor().utf8_str())); UT_return_if_fail(pBuddy); // create a copy of the current document handles, which // we'll use to determine which document handles do not exist anymore std::vector<DocHandle*> oldDocHandles(pBuddy->getDocHandles()); for (UT_sint32 i = 0; i < vDocHandles.size(); i++) { DocHandle* pDocHandle = vDocHandles.getNthItem(i); UT_continue_if_fail(pDocHandle); // sanity checking UT_UTF8String sId = pDocHandle->getSessionId(); UT_continue_if_fail(sId.size() > 0); // construct a nice document name UT_UTF8String sDocumentName = pDocHandle->getName(); if (sDocumentName.size() == 0) { // this document has no name yet; give it an untitled name const XAP_StringSet * pSS = XAP_App::getApp()->getStringSet(); std::string sUntitled; pSS->getValueUTF8(XAP_STRING_ID_UntitledDocument, sUntitled); UT_UTF8String_sprintf(sDocumentName, sUntitled.c_str(), 0); // TODO: as should append a number here, but at the moment // XAP_Frame::m_iUntitled is not accessible from here } // check to see if we already have a document handle with this ID DocHandle* pCurDocHandle = pBuddy->getDocHandle(sId); if (!pCurDocHandle) { // Ok, all set. Get the buddy from the AccountHandler, and assign // the document handle to the buddy DocHandle * pNewDocHandle = new DocHandle(sId, sDocumentName); pBuddy->addDocHandle(pNewDocHandle); UT_DEBUGMSG(("Added DocHandle (%s) to buddy (%s)\n", sId.utf8_str(), pBuddy->getDescription().utf8_str())); // signal that a buddy has a new session AccountBuddyAddDocumentEvent event(pNewDocHandle); signal(event, pBuddy); } else { UT_DEBUGMSG(("Found an existing DocHandle (%s) for buddy (%s)\n", sId.utf8_str(), pBuddy->getDescription().utf8_str())); // we already have a handle for this document, remove it from the old document handles copy for (std::vector<DocHandle*>::iterator it = oldDocHandles.begin(); it != oldDocHandles.end(); it++) { DocHandle* pOldDocHandle = *it; if (pCurDocHandle == pOldDocHandle) { oldDocHandles.erase(it); break; } } } } // every document that is still in the old document handles list does not // exist anymore, so let's delete it std::vector<DocHandle*>::iterator it = oldDocHandles.begin(); while (it != oldDocHandles.end()) { DocHandle* pDocHandle = *it; UT_continue_if_fail(pDocHandle); // TODO: when we are a part of this session, then handle that properly UT_DEBUGMSG(("Purging existing DocHandle (%s) for buddy (%s)\n", pDocHandle->getSessionId().utf8_str(), pBuddy->getDescription().utf8_str())); UT_UTF8String pDestroyedSessionId = pDocHandle->getSessionId(); pBuddy->destroyDocHandle(pDestroyedSessionId); CloseSessionEvent event(pDestroyedSessionId); signal(event, pBuddy); it = oldDocHandles.erase(it); } }