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::signal(const Event& event, BuddyPtr pSource) { UT_DEBUGMSG(("AccountHandler::signal()\n")); // we will not forward an event over this account that came from another // account: if you do that, then you very easily get packets running around // forever. if (pSource && pSource->getHandler() != this) return; // broadcast this event over our network (if applicable for each message type) const std::vector<BuddyPtr> vRecipients = (event.isBroadcast() ? getBuddies() : event.getRecipients()); for (std::vector<BuddyPtr>::const_iterator cit = vRecipients.begin(); cit != vRecipients.end(); cit++) { BuddyPtr pRecipient = *cit; UT_continue_if_fail(pRecipient); if (!pSource || (pSource != pRecipient)) { send(&event, pRecipient); } else { // the event originated from this buddy, so make sure not to send it // back to him, as it would result in a broadcast storm and // kill the network really fast } } }
void AbiCollab::_restartAsMaster() { UT_DEBUGMSG(("AbiCollab::_restartAsMaster()\n")); m_Import.masterInit(); m_Export.masterInit(); // the session controller will never have to revert individual changerecords, // so we can re-enable changerecord coalescing // FIXME: enable this //pDoc->setCoalescingMask(true); // inform everyone that we can restart this session SessionReconnectAckPacket srap(m_sId, m_pDoc->getDocUUIDString(), m_pDoc->getCRNumber()); 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); AccountHandler* pHandler = pBuddy->getHandler(); UT_continue_if_fail(pHandler); pHandler->send(&srap, pBuddy); } // we're the master now! m_eTakeoveState = STS_NONE; _pushOutgoingQueue(); }
void AbiCollab::addCollaborator(BuddyPtr pCollaborator) { UT_DEBUGMSG(("AbiCollab::addCollaborator()\n")); UT_return_if_fail(pCollaborator) // check if this buddy is in the access control list if we are hosting // this session if (isLocallyControlled()) { AccountHandler* pAccount = pCollaborator->getHandler(); UT_return_if_fail(pAccount); if (!pAccount->hasAccess(m_vAcl, pCollaborator)) { UT_ASSERT(UT_NOT_IMPLEMENTED); return; } } // check for duplicates (as long as we assume a collaborator can only be part of a collaboration session once) std::map<BuddyPtr, std::string>::iterator it = m_vCollaborators.find(pCollaborator); if (it != m_vCollaborators.end()) { UT_DEBUGMSG(("Attempting to add buddy '%s' twice to a collaboration session!", pCollaborator->getDescription().utf8_str())); UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); return; } m_vCollaborators[pCollaborator] = ""; // will fill the remote document UUID later once we receive a packet from this buddy }
// should we move this to AbiCollab.cpp ? void AbiCollabSessionManager::updateAcl(AbiCollab* pSession, AccountHandler* pAccount, const std::vector<std::string> vAcl) { UT_return_if_fail(pSession); UT_return_if_fail(pAccount); // check if all current collaborators are still allowed to collaborate; if not, // then remove them from the session const std::map<BuddyPtr, std::string> collaborators = pSession->getCollaborators(); for (std::map<BuddyPtr, std::string>::const_iterator cit = collaborators.begin(); cit != collaborators.end(); cit++) { BuddyPtr pBuddy = (*cit).first; UT_continue_if_fail(pBuddy); AccountHandler* pBuddyAccount = pBuddy->getHandler(); UT_continue_if_fail(pBuddyAccount); UT_continue_if_fail(pBuddyAccount == pAccount); if (!pBuddyAccount->hasAccess(vAcl, pBuddy)) { // this current collaborator has been banned from the session, so // disconnect him UT_ASSERT_HARMLESS(UT_NOT_IMPLEMENTED); } } // set the new ACL on the account handler pAccount->setAcl(pSession, vAcl); // set the new access control list on the session pSession->setAcl(vAcl); }
void AbiCollabSessionManager::joinSessionInitiate(BuddyPtr pBuddy, DocHandle* pDocHandle) { UT_return_if_fail(pBuddy); UT_return_if_fail(pDocHandle); UT_DEBUGMSG(("Initiating join on buddy |%s|, document |%s|\n", pBuddy->getDescription().utf8_str(), pDocHandle->getSessionId().utf8_str())); AccountHandler* pHandler = pBuddy->getHandler(); UT_return_if_fail(pHandler); pHandler->joinSessionAsync(pBuddy, *pDocHandle); // TODO: do some accounting here, so we know we send an auth request in ::joinSession() }
/*! * 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::_populateBuddyModel(bool refresh) { UT_DEBUGMSG(("AP_UnixDialog_CollaborationShare::_populateBuddyModel()\n")); UT_return_if_fail(m_pBuddyModel); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); AccountHandler* pHandler = _getActiveAccountHandler(); UT_return_if_fail(pHandler); if (refresh) { // signal the account to refresh its buddy list ... pHandler->getBuddiesAsync(); // this function is really sync() atm; we need to rework this dialog to make it proper async // fetch the current ACL m_vAcl = _getSessionACL(); } // clear out the old contents, if any _freeBuddyList(); GtkTreeIter iter; for (UT_uint32 i = 0; i < pHandler->getBuddies().size(); i++) { BuddyPtr pBuddy = pHandler->getBuddies()[i]; UT_continue_if_fail(pBuddy); if (!pBuddy->getHandler()->canShare(pBuddy)) { UT_DEBUGMSG(("Not allowed to share with buddy: %s\n", pBuddy->getDescription().utf8_str())); continue; } // crap, we can't store shared pointers in the list store; use a // hack to do it (which kinda defies the whole shared pointer thingy, // but alas...) BuddyPtrWrapper* pWrapper = new BuddyPtrWrapper(pBuddy); gtk_list_store_append (m_pBuddyModel, &iter); gtk_list_store_set (m_pBuddyModel, &iter, SHARE_COLUMN, _populateShareState(pBuddy), DESC_COLUMN, pBuddy->getDescription().utf8_str(), BUDDY_COLUMN, pWrapper, -1); } gtk_widget_show_all(m_wBuddyTree); }
bool AP_Dialog_CollaborationShare::_populateShareState(BuddyPtr pBuddy) { AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_val_if_fail(pManager, false); PD_Document *pDoc = static_cast<PD_Document*>(XAP_App::getApp()->getLastFocussedFrame()->getCurrentDoc()); UT_return_val_if_fail(pDoc, false); if (!pManager->isInSession(pDoc)) { AccountHandler* pHandler = pBuddy->getHandler(); UT_return_val_if_fail(pHandler, false); return pHandler->defaultShareState(pBuddy); } return _inAcl(m_vAcl, pBuddy); }
void AbiCollab::_shutdownAsMaster() { UT_DEBUGMSG(("AbiCollab::_shutdownAsMaster()\n")); UT_return_if_fail(m_pController == BuddyPtr()); UT_return_if_fail(!m_bProposedController); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); // the session takeover is complete; inform everyone that all session data // is flushed, and that everyone should talk to the new master from now on SessionFlushedPacket sfp(m_sId, m_pDoc->getDocUUIDString()); 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); pBuddy->getHandler()->send(&sfp, pBuddy); } // session takeover is done as far as the leaving session contoller is concerned pManager->endAsyncOperation(this); }
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; } } } }
bool AbiCollab::push(SessionPacket* pPacket, BuddyPtr collaborator) { UT_return_val_if_fail(pPacket, false); UT_return_val_if_fail(collaborator, false); AccountHandler* pHandler = collaborator->getHandler(); UT_return_val_if_fail(pHandler, false); // record if (m_pRecorder) m_pRecorder->storeOutgoing(const_cast<const SessionPacket*>( pPacket ), collaborator); // 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->getDescription().utf8_str())); } return res; }
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 TelepathyAccountHandler::signal(const Event& event, BuddyPtr pSource) { UT_DEBUGMSG(("TelepathyAccountHandler::signal\n")); // NOTE: do NOT let AccountHandler::signal() send broadcast packets! // It will send them to all buddies, including the ones we created // to list the available documents: TelepathyBuddies. They are just fake // buddies however, and can't receive real packets. Only DTubeBuddy's // can be sent packets // Note: there is no real need to pass the PCT_CloseSessionEvent and // PCT_DisjoinSessionEvent signals to the AccountHandler::signal() // function: that one will send all buddies the 'session is closed' // signal. However, on this backend, Telepathy will handle that for us AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); switch (event.getClassType()) { case PCT_CloseSessionEvent: { UT_DEBUGMSG(("Got a PCT_CloseSessionEvent\n")); const CloseSessionEvent cse = static_cast<const CloseSessionEvent&>(event); // check if this event came from this account in the first place if (pSource && pSource->getHandler() != this) { // nope, a session was closed on some other account; ignore this... return; } UT_return_if_fail(!pSource); // we shouldn't receive these events over the wire on this backend UT_DEBUGMSG(("Disconnecting the tube for room with session id %s\n", cse.getSessionId().utf8_str())); TelepathyChatroomPtr pChatroom = _getChatroom(cse.getSessionId()); UT_return_if_fail(pChatroom); pChatroom->stop(); } break; case PCT_DisjoinSessionEvent: { UT_DEBUGMSG(("Got a PCT_DisjoinSessionEvent\n")); const DisjoinSessionEvent dse = static_cast<const DisjoinSessionEvent&>(event); // check if this event came from this account in the first place if (pSource && pSource->getHandler() != this) { // nope, a session was closed on some other account; ignore this... return; } UT_return_if_fail(!pSource); // we shouldn't receive these events over the wire on this backend UT_DEBUGMSG(("Disconnecting the tube for room with session id %s\n", dse.getSessionId().utf8_str())); TelepathyChatroomPtr pChatroom = _getChatroom(dse.getSessionId()); UT_return_if_fail(pChatroom); pChatroom->stop(); } break; default: // I think we can ignore all other signals on this backend, at // least for now break; } }