Spectrum can now works as IRC/whatever bouncer. Configurable using adhoc commands.

This commit is contained in:
HanzZ 2012-12-21 09:26:34 +01:00
parent 7e9dfbdead
commit 7e9ea5150d
14 changed files with 269 additions and 21 deletions

View file

@ -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<std::string, Participant> m_participants;
boost::shared_ptr<Swift::Message> m_subject;
bool m_sentInitialPresence;
std::list<boost::shared_ptr<Swift::Message> > m_cachedMessages;
};
}

View file

@ -73,6 +73,7 @@ class ConversationManager {
void resetResources();
void removeJID(const Swift::JID &jid);
void clearJIDs();
private:
void handleMessageReceived(Swift::Message::ref message);

View file

@ -55,6 +55,7 @@ class SettingsAdHocCommandFactory : public AdHocCommandFactory {
public:
SettingsAdHocCommandFactory() {
m_userSettings["send_headlines"] = "0";
m_userSettings["stay_connected"] = "0";
}
virtual ~SettingsAdHocCommandFactory() {}

View file

@ -124,6 +124,12 @@ class User : public Swift::EntityCapsProvider {
return m_settings[key];
}
void setCacheMessages(bool cacheMessages);
bool shouldCacheMessages() {
return m_cacheMessages;
}
boost::signal<void ()> onReadyToConnect;
boost::signal<void (Swift::Presence::ref presence)> onPresenceChanged;
boost::signal<void (const Swift::JID &who, const std::string &room, const std::string &nickname, const std::string &password)> onRoomJoined;
@ -154,6 +160,7 @@ class User : public Swift::EntityCapsProvider {
int m_reconnectCounter;
std::list<Swift::Presence::ref> m_joinedRooms;
std::map<std::string, std::string> m_settings;
bool m_cacheMessages;
};
}

View file

@ -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<Swift::Message> &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<Swift::Delay> delay(boost::make_shared<Swift::Delay>());
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<boost::shared_ptr<Swift::Message> >::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();

View file

@ -99,6 +99,12 @@ void ConversationManager::removeJID(const Swift::JID &jid) {
}
}
void ConversationManager::clearJIDs() {
for (std::map<std::string, Conversation *>::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

View file

@ -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());

View file

@ -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() {

View file

@ -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<Swift::DummyTimerFactory *>(factories->getTimerFactory())->setTime(10);
dynamic_cast<Swift::DummyTimerFactory *>(factories->getTimerFactory())->setTime(100);
loop->processEvents();
CPPUNIT_ASSERT_EQUAL(0, userManager->getUserCount());

View file

@ -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<Swift::DummyTimerFactory *>(factories->getTimerFactory())->setTime(10);
loop->processEvents();
CPPUNIT_ASSERT(user->shouldCacheMessages());
// reset resources should not touch this resource
user->getConversationManager()->resetResources();
boost::shared_ptr<Swift::Message> msg(new Swift::Message());
msg->setBody("hi there!");
conv->handleMessage(msg, "anotheruser");
boost::shared_ptr<Swift::Message> 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<Swift::Payload>(payload));
injectPresence(response);
loop->processEvents();
CPPUNIT_ASSERT_EQUAL(4, (int) received.size());
CPPUNIT_ASSERT(dynamic_cast<Swift::Message *>(getStanza(received[2])));
CPPUNIT_ASSERT_EQUAL(std::string("hi there!"), dynamic_cast<Swift::Message *>(getStanza(received[2]))->getBody());
CPPUNIT_ASSERT_EQUAL(std::string("user@localhost/resource"), dynamic_cast<Swift::Message *>(getStanza(received[2]))->getTo().toString());
CPPUNIT_ASSERT_EQUAL(std::string("#room@localhost/anotheruser"), dynamic_cast<Swift::Message *>(getStanza(received[2]))->getFrom().toString());
CPPUNIT_ASSERT(dynamic_cast<Swift::Message *>(getStanza(received[3])));
CPPUNIT_ASSERT_EQUAL(std::string("hi there2!"), dynamic_cast<Swift::Message *>(getStanza(received[3]))->getBody());
CPPUNIT_ASSERT_EQUAL(std::string("user@localhost/resource"), dynamic_cast<Swift::Message *>(getStanza(received[3]))->getTo().toString());
CPPUNIT_ASSERT_EQUAL(std::string("#room@localhost/anotheruser"), dynamic_cast<Swift::Message *>(getStanza(received[3]))->getFrom().toString());
}
void handleGroupchatMessagesTwoResources() {
connectSecondResource();
received2.clear();

View file

@ -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<Swift::Payload>(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<Swift::Payload>(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<Swift::Payload>(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();

View file

@ -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<Swift::Presence *>(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<Swift::DummyTimerFactory *>(factories->getTimerFactory())->setTime(10);
loop->processEvents();
CPPUNIT_ASSERT_EQUAL(1, userManager->getUserCount());
CPPUNIT_ASSERT_EQUAL(0, (int) received.size());
CPPUNIT_ASSERT(dynamic_cast<Swift::Presence *>(getStanza(received[0])));
CPPUNIT_ASSERT(userManager->getUser("user@localhost"));
userManager->removeAllUsers();
loop->processEvents();
CPPUNIT_ASSERT_EQUAL(0, userManager->getUserCount());
}
};
CPPUNIT_TEST_SUITE_REGISTRATION (UserManagerTest);

View file

@ -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());

View file

@ -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)) {