From 7e9ea5150d9c2319da5a73a669a61adae47cd92f Mon Sep 17 00:00:00 2001 From: HanzZ Date: Fri, 21 Dec 2012 09:26:34 +0100 Subject: [PATCH] Spectrum can now works as IRC/whatever bouncer. Configurable using adhoc commands. --- include/transport/conversation.h | 7 ++ include/transport/conversationmanager.h | 1 + include/transport/settingsadhoccommand.h | 1 + include/transport/user.h | 7 ++ src/conversation.cpp | 36 ++++++--- src/conversationmanager.cpp | 6 ++ src/rostermanager.cpp | 2 +- src/settingsadhoccommand.cpp | 5 ++ src/tests/basictest.cpp | 6 +- src/tests/conversationmanager.cpp | 60 +++++++++++++++ src/tests/user.cpp | 94 ++++++++++++++++++++++++ src/tests/usermanager.cpp | 20 +++++ src/user.cpp | 41 ++++++++--- src/usermanager.cpp | 4 + 14 files changed, 269 insertions(+), 21 deletions(-) diff --git a/include/transport/conversation.h b/include/transport/conversation.h index ff109aa2..a8915e75 100644 --- a/include/transport/conversation.h +++ b/include/transport/conversation.h @@ -92,6 +92,10 @@ class Conversation { m_jids.push_back(jid); } + void clearJIDs() { + m_jids.clear(); + } + void removeJID(const Swift::JID &jid) { m_jids.remove(jid); } @@ -136,6 +140,8 @@ class Conversation { void sendParticipants(const Swift::JID &to); + void sendCachedMessages(const Swift::JID &to); + private: Swift::Presence::ref generatePresence(const std::string &nick, int flag, int status, const std::string &statusMessage, const std::string &newname = ""); @@ -150,6 +156,7 @@ class Conversation { std::map m_participants; boost::shared_ptr m_subject; bool m_sentInitialPresence; + std::list > m_cachedMessages; }; } diff --git a/include/transport/conversationmanager.h b/include/transport/conversationmanager.h index 865736b2..c3900b95 100644 --- a/include/transport/conversationmanager.h +++ b/include/transport/conversationmanager.h @@ -73,6 +73,7 @@ class ConversationManager { void resetResources(); void removeJID(const Swift::JID &jid); + void clearJIDs(); private: void handleMessageReceived(Swift::Message::ref message); diff --git a/include/transport/settingsadhoccommand.h b/include/transport/settingsadhoccommand.h index 9b0943dc..3a2450a1 100644 --- a/include/transport/settingsadhoccommand.h +++ b/include/transport/settingsadhoccommand.h @@ -55,6 +55,7 @@ class SettingsAdHocCommandFactory : public AdHocCommandFactory { public: SettingsAdHocCommandFactory() { m_userSettings["send_headlines"] = "0"; + m_userSettings["stay_connected"] = "0"; } virtual ~SettingsAdHocCommandFactory() {} diff --git a/include/transport/user.h b/include/transport/user.h index e2201735..d164852f 100644 --- a/include/transport/user.h +++ b/include/transport/user.h @@ -124,6 +124,12 @@ class User : public Swift::EntityCapsProvider { return m_settings[key]; } + void setCacheMessages(bool cacheMessages); + + bool shouldCacheMessages() { + return m_cacheMessages; + } + boost::signal onReadyToConnect; boost::signal onPresenceChanged; boost::signal onRoomJoined; @@ -154,6 +160,7 @@ class User : public Swift::EntityCapsProvider { int m_reconnectCounter; std::list m_joinedRooms; std::map m_settings; + bool m_cacheMessages; }; } diff --git a/src/conversation.cpp b/src/conversation.cpp index f6b316b8..efc39fdf 100644 --- a/src/conversation.cpp +++ b/src/conversation.cpp @@ -30,7 +30,6 @@ namespace Transport { Conversation::Conversation(ConversationManager *conversationManager, const std::string &legacyName, bool isMUC) : m_conversationManager(conversationManager) { m_legacyName = legacyName; -// m_conversationManager->addConversation(this); m_muc = isMUC; m_jid = m_conversationManager->getUser()->getJID().toBare(); m_sentInitialPresence = false; @@ -140,15 +139,26 @@ void Conversation::handleMessage(boost::shared_ptr &message, con if (n.empty()) { n = " "; } - BOOST_FOREACH(const Swift::JID &jid, m_jids) { - message->setTo(jid); - message->setFrom(Swift::JID(legacyName, m_conversationManager->getComponent()->getJID().toBare(), n)); - // Subject has to be sent after our own presence (the one with code 110) - if (!message->getSubject().empty() && m_sentInitialPresence == false) { - m_subject = message; - return; + + message->setFrom(Swift::JID(legacyName, m_conversationManager->getComponent()->getJID().toBare(), n)); + + if (m_conversationManager->getUser()->shouldCacheMessages()) { + boost::posix_time::ptime timestamp = boost::posix_time::second_clock::universal_time(); + boost::shared_ptr delay(boost::make_shared()); + delay->setStamp(timestamp); + message->addPayload(delay); + m_cachedMessages.push_back(message); + } + else { + BOOST_FOREACH(const Swift::JID &jid, m_jids) { + message->setTo(jid); + // Subject has to be sent after our own presence (the one with code 110) + if (!message->getSubject().empty() && m_sentInitialPresence == false) { + m_subject = message; + return; + } + m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(message); } - m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(message); } } } @@ -161,6 +171,14 @@ void Conversation::sendParticipants(const Swift::JID &to) { } } +void Conversation::sendCachedMessages(const Swift::JID &to) { + for (std::list >::const_iterator it = m_cachedMessages.begin(); it != m_cachedMessages.end(); it++) { + (*it)->setTo(to); + m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(*it); + } + m_cachedMessages.clear(); +} + Swift::Presence::ref Conversation::generatePresence(const std::string &nick, int flag, int status, const std::string &statusMessage, const std::string &newname) { std::string nickname = nick; Swift::Presence::ref presence = Swift::Presence::create(); diff --git a/src/conversationmanager.cpp b/src/conversationmanager.cpp index b2c22ecf..d967df34 100644 --- a/src/conversationmanager.cpp +++ b/src/conversationmanager.cpp @@ -99,6 +99,12 @@ void ConversationManager::removeJID(const Swift::JID &jid) { } } +void ConversationManager::clearJIDs() { + for (std::map::const_iterator it = m_convs.begin(); it != m_convs.end(); it++) { + (*it).second->clearJIDs(); + } +} + void ConversationManager::handleMessageReceived(Swift::Message::ref message) { // std::string name = message->getTo().getUnescapedNode(); // if (name.find_last_of("%") != std::string::npos) { // OK when commented diff --git a/src/rostermanager.cpp b/src/rostermanager.cpp index 0abec9f6..34a2d690 100644 --- a/src/rostermanager.cpp +++ b/src/rostermanager.cpp @@ -145,7 +145,7 @@ void RosterManager::sendBuddyRosterRemove(Buddy *buddy) { void RosterManager::sendBuddyRosterPush(Buddy *buddy) { // user can't receive anything in server mode if he's not logged in. // He will ask for roster later (handled in rosterreponsder.cpp) - if (m_component->inServerMode() && !m_user->isConnected()) + if (m_component->inServerMode() && (!m_user->isConnected() || m_user->shouldCacheMessages())) return; Swift::RosterPayload::ref payload = Swift::RosterPayload::ref(new Swift::RosterPayload()); diff --git a/src/settingsadhoccommand.cpp b/src/settingsadhoccommand.cpp index cc2f7062..3677adf9 100644 --- a/src/settingsadhoccommand.cpp +++ b/src/settingsadhoccommand.cpp @@ -45,6 +45,11 @@ SettingsAdHocCommand::SettingsAdHocCommand(Component *component, UserManager *us field->setName("send_headlines"); field->setLabel("Allow sending messages as headlines"); addFormField(field); + + field = Swift::BooleanFormField::create(CONFIG_STRING_DEFAULTED(component->getConfig(), "settings.stay_connected", "0") == "1"); + field->setName("stay_connected"); + field->setLabel("Stay connected to legacy network when offline on XMPP"); + addFormField(field); } SettingsAdHocCommand::~SettingsAdHocCommand() { diff --git a/src/tests/basictest.cpp b/src/tests/basictest.cpp index 1c0b2eed..eb02de33 100644 --- a/src/tests/basictest.cpp +++ b/src/tests/basictest.cpp @@ -237,9 +237,13 @@ void BasicTest::connectSecondResource() { } void BasicTest::disconnectUser() { + User *user = userManager->getUser("user@localhost"); + if (user) { + user->addUserSetting("stay_connected", "0"); + } received.clear(); userManager->disconnectUser("user@localhost"); - dynamic_cast(factories->getTimerFactory())->setTime(10); + dynamic_cast(factories->getTimerFactory())->setTime(100); loop->processEvents(); CPPUNIT_ASSERT_EQUAL(0, userManager->getUserCount()); diff --git a/src/tests/conversationmanager.cpp b/src/tests/conversationmanager.cpp index d9346db6..4cc56db7 100644 --- a/src/tests/conversationmanager.cpp +++ b/src/tests/conversationmanager.cpp @@ -26,6 +26,7 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe CPPUNIT_TEST(handleNormalMessages); CPPUNIT_TEST(handleNormalMessagesHeadline); CPPUNIT_TEST(handleGroupchatMessages); + CPPUNIT_TEST(handleGroupchatMessagesBouncer); CPPUNIT_TEST(handleGroupchatMessagesTwoResources); CPPUNIT_TEST(handleChatstateMessages); CPPUNIT_TEST(handleSubjectMessages); @@ -295,6 +296,65 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe CPPUNIT_ASSERT_EQUAL(std::string("response!"), m_msg->getBody()); } + void handleGroupchatMessagesBouncer() { + User *user = userManager->getUser("user@localhost"); + user->addUserSetting("stay_connected", "1"); + TestingConversation *conv = new TestingConversation(user->getConversationManager(), "#room", true); + user->getConversationManager()->addConversation(conv); + conv->onMessageToSend.connect(boost::bind(&ConversationManagerTest::handleMessageReceived, this, _1, _2)); + conv->setNickname("nickname"); + conv->addJID("user@localhost/resource"); + + CPPUNIT_ASSERT(!user->shouldCacheMessages()); + + // disconnectUser + userManager->disconnectUser("user@localhost"); + dynamic_cast(factories->getTimerFactory())->setTime(10); + loop->processEvents(); + + CPPUNIT_ASSERT(user->shouldCacheMessages()); + + // reset resources should not touch this resource + user->getConversationManager()->resetResources(); + + boost::shared_ptr msg(new Swift::Message()); + msg->setBody("hi there!"); + conv->handleMessage(msg, "anotheruser"); + + boost::shared_ptr msg2(new Swift::Message()); + msg2->setBody("hi there2!"); + conv->handleMessage(msg2, "anotheruser"); + + loop->processEvents(); + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + userRegistry->isValidUserPassword(Swift::JID("user@localhost/resource"), serverFromClientSession.get(), Swift::createSafeByteArray("password")); + userRegistry->onPasswordValid(Swift::JID("user@localhost/resource")); + loop->processEvents(); + + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo("#room@localhost/hanzz"); + response->setFrom("user@localhost/resource"); + + Swift::MUCPayload *payload = new Swift::MUCPayload(); + payload->setPassword("password"); + response->addPayload(boost::shared_ptr(payload)); + injectPresence(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(4, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[2]))); + CPPUNIT_ASSERT_EQUAL(std::string("hi there!"), dynamic_cast(getStanza(received[2]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("user@localhost/resource"), dynamic_cast(getStanza(received[2]))->getTo().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("#room@localhost/anotheruser"), dynamic_cast(getStanza(received[2]))->getFrom().toString()); + + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[3]))); + CPPUNIT_ASSERT_EQUAL(std::string("hi there2!"), dynamic_cast(getStanza(received[3]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("user@localhost/resource"), dynamic_cast(getStanza(received[3]))->getTo().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("#room@localhost/anotheruser"), dynamic_cast(getStanza(received[3]))->getFrom().toString()); + + } + void handleGroupchatMessagesTwoResources() { connectSecondResource(); received2.clear(); diff --git a/src/tests/user.cpp b/src/tests/user.cpp index 054cc100..0575f979 100644 --- a/src/tests/user.cpp +++ b/src/tests/user.cpp @@ -29,6 +29,9 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST(handlePresenceLeaveRoom); CPPUNIT_TEST(handlePresenceLeaveRoomTwoResources); CPPUNIT_TEST(handlePresenceLeaveRoomTwoResourcesOneDisconnects); + CPPUNIT_TEST(handlePresenceLeaveRoomBouncer); + CPPUNIT_TEST(handlePresenceLeaveRoomTwoResourcesBouncer); + CPPUNIT_TEST(handlePresenceLeaveRoomTwoResourcesOneDisconnectsBouncer); CPPUNIT_TEST(leaveJoinedRoom); CPPUNIT_TEST(joinRoomBeforeConnected); CPPUNIT_TEST(handleDisconnected); @@ -63,6 +66,7 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest { if (!disconnected) { disconnectUser(); } + userManager->removeAllUsers(); tearMeDown(); } @@ -279,6 +283,96 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_ASSERT_EQUAL(Swift::JID("user@localhost/resource2"), conv->getJIDs().front()); } + void handlePresenceLeaveRoomBouncer() { + User *user = userManager->getUser("user@localhost"); + user->addUserSetting("stay_connected", "1"); + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo("#room@localhost/hanzz"); + response->setFrom("user@localhost/resource"); + response->setType(Swift::Presence::Unavailable); + + Swift::MUCPayload *payload = new Swift::MUCPayload(); + payload->setPassword("password"); + response->addPayload(boost::shared_ptr(payload)); + injectPresence(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + CPPUNIT_ASSERT_EQUAL(std::string(""), room); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomPassword); + } + + void handlePresenceLeaveRoomTwoResourcesBouncer() { + User *user = userManager->getUser("user@localhost"); + user->addUserSetting("stay_connected", "1"); + handlePresenceJoinRoomTwoResources(); + received.clear(); + + // User is still connected from resource2, so he should not leave the room + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo("#room@localhost/hanzz"); + response->setFrom("user@localhost/resource"); + response->setType(Swift::Presence::Unavailable); + + Swift::MUCPayload *payload = new Swift::MUCPayload(); + payload->setPassword("password"); + response->addPayload(boost::shared_ptr(payload)); + injectPresence(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + CPPUNIT_ASSERT_EQUAL(std::string(""), room); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomPassword); + + room = "something"; + // disconnect also from resource + // User is still connected from resource2, so he should not leave the room + response = Swift::Presence::create(); + response->setTo("#room@localhost/hanzz"); + response->setFrom("user@localhost/resource2"); + response->setType(Swift::Presence::Unavailable); + + payload = new Swift::MUCPayload(); + payload->setPassword("password"); + response->addPayload(boost::shared_ptr(payload)); + injectPresence(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + CPPUNIT_ASSERT_EQUAL(std::string("something"), room); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomPassword); + } + + void handlePresenceLeaveRoomTwoResourcesOneDisconnectsBouncer() { + room = "something"; + handlePresenceJoinRoomTwoResources(); + received.clear(); + User *user = userManager->getUser("user@localhost"); + + // User is still connected from resource2, so he should not leave the room + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo("localhost/hanzz"); + response->setFrom("user@localhost/resource"); + response->setType(Swift::Presence::Unavailable); + injectPresence(response); + loop->processEvents(); + + + CPPUNIT_ASSERT_EQUAL(std::string(""), room); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomPassword); + + Conversation *conv = user->getConversationManager()->getConversation("#room"); + CPPUNIT_ASSERT_EQUAL(1, (int) conv->getJIDs().size()); + CPPUNIT_ASSERT_EQUAL(Swift::JID("user@localhost/resource2"), conv->getJIDs().front()); + } + void leaveJoinedRoom() { User *user = userManager->getUser("user@localhost"); handlePresenceJoinRoom(); diff --git a/src/tests/usermanager.cpp b/src/tests/usermanager.cpp index 8f59ab8f..dd896309 100644 --- a/src/tests/usermanager.cpp +++ b/src/tests/usermanager.cpp @@ -30,6 +30,7 @@ class UserManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST(connectUserVipOnlyNonVip); CPPUNIT_TEST(handleProbePresence); CPPUNIT_TEST(disconnectUser); + CPPUNIT_TEST(disconnectUserBouncer); CPPUNIT_TEST_SUITE_END(); public: @@ -180,6 +181,25 @@ class UserManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_ASSERT_EQUAL(std::string("status2"), dynamic_cast(getStanza(received2[3]))->getStatus()); } + void disconnectUserBouncer() { + connectUser(); + User *user = userManager->getUser("user@localhost"); + user->addUserSetting("stay_connected", "1"); + received.clear(); + userManager->disconnectUser("user@localhost"); + dynamic_cast(factories->getTimerFactory())->setTime(10); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, userManager->getUserCount()); + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT(userManager->getUser("user@localhost")); + + userManager->removeAllUsers(); + loop->processEvents(); + CPPUNIT_ASSERT_EQUAL(0, userManager->getUserCount()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION (UserManagerTest); diff --git a/src/user.cpp b/src/user.cpp index 558138a2..361cc487 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -47,6 +47,7 @@ User::User(const Swift::JID &jid, UserInfo &userInfo, Component *component, User m_jid = jid.toBare(); m_data = NULL; + m_cacheMessages = false; m_component = component; m_presenceOracle = component->m_presenceOracle; m_entityCapsManager = component->m_entityCapsManager; @@ -184,6 +185,10 @@ void User::setConnected(bool connected) { } } +void User::setCacheMessages(bool cacheMessages) { + m_cacheMessages = cacheMessages; +} + void User::handlePresence(Swift::Presence::ref presence, bool forceJoin) { int currentResourcesCount = m_presenceOracle->getAllPresence(m_jid).size(); @@ -233,19 +238,21 @@ void User::handlePresence(Swift::Presence::ref presence, bool forceJoin) { } } - LOG4CXX_INFO(logger, m_jid.toString() << ": Going to left room " << room); - onRoomLeft(room); + if (getUserSetting("stay_connected") != "1") { + LOG4CXX_INFO(logger, m_jid.toString() << ": Going to left room " << room); + onRoomLeft(room); - BOOST_FOREACH(Swift::Presence::ref &p, m_joinedRooms) { - if (p->getTo() == presence->getTo()) { - m_joinedRooms.remove(p); - break; + BOOST_FOREACH(Swift::Presence::ref &p, m_joinedRooms) { + if (p->getTo() == presence->getTo()) { + m_joinedRooms.remove(p); + break; + } } - } - if (conv) { - m_conversationManager->removeConversation(conv); - delete conv; + if (conv) { + m_conversationManager->removeConversation(conv); + delete conv; + } } } else { @@ -270,6 +277,7 @@ void User::handlePresence(Swift::Presence::ref presence, bool forceJoin) { else { conv->addJID(presence->getFrom()); conv->sendParticipants(presence->getFrom()); + conv->sendCachedMessages(presence->getFrom()); } if (forceJoin) { @@ -340,13 +348,26 @@ void User::handlePresence(Swift::Presence::ref presence, bool forceJoin) { if (m_readyForConnect) { Swift::Presence::ref highest = m_presenceOracle->getHighestPriorityPresence(m_jid.toBare()); if (highest) { + if (highest->getType() == Swift::Presence::Unavailable && getUserSetting("stay_connected") == "1") { + m_resources = 0; + m_conversationManager->clearJIDs(); + setCacheMessages(true); + return; + } Swift::Presence::ref response = Swift::Presence::create(highest); response->setTo(m_jid); response->setFrom(m_component->getJID()); LOG4CXX_INFO(logger, m_jid.toString() << ": Changing legacy network presence to " << response->getType()); onPresenceChanged(highest); + setCacheMessages(false); } else { + if (getUserSetting("stay_connected") == "1") { + m_resources = 0; + m_conversationManager->clearJIDs(); + setCacheMessages(true); + return; + } Swift::Presence::ref response = Swift::Presence::create(); response->setTo(m_jid.toBare()); response->setFrom(m_component->getJID()); diff --git a/src/usermanager.cpp b/src/usermanager.cpp index fd4305ce..61872258 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -331,6 +331,10 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { // Unavailable presence could remove this user, because he could be unavailable if (presence->getType() == Swift::Presence::Unavailable) { if (user) { + if (user->getUserSetting("stay_connected") == "1") { + return; + } + Swift::Presence::ref highest = m_component->getPresenceOracle()->getHighestPriorityPresence(presence->getFrom().toBare()); // There's no presence for this user, so disconnect if (!highest || (highest && highest->getType() == Swift::Presence::Unavailable)) {