From 0b0e020cdf915d46f1cbb9de159b00e3a6982b62 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Tue, 18 Dec 2012 10:59:52 +0100 Subject: [PATCH] Add general API to set per user settings using adhoc commands which can be defaulted from config file. Add 'Send messages as headlines' Transport setting --- include/transport/adhoccommandfactory.h | 7 +++ include/transport/adhocmanager.h | 2 + include/transport/protocol.proto | 1 + include/transport/settingsadhoccommand.h | 5 +- include/transport/user.h | 9 +++ src/adhocmanager.cpp | 23 ++++++++ src/conversation.cpp | 9 ++- src/conversationmanager.cpp | 7 +-- src/networkpluginserver.cpp | 4 ++ src/settingsadhoccommand.cpp | 5 ++ src/tests/basictest.h | 9 +++ src/tests/conversationmanager.cpp | 50 ++++++++++++++++ src/tests/settingsadhoccommand.cpp | 73 ++++++++++++++++++++++++ 13 files changed, 196 insertions(+), 8 deletions(-) diff --git a/include/transport/adhoccommandfactory.h b/include/transport/adhoccommandfactory.h index 9f1691a5..93c9d4a5 100644 --- a/include/transport/adhoccommandfactory.h +++ b/include/transport/adhoccommandfactory.h @@ -44,6 +44,13 @@ class AdHocCommandFactory { virtual std::string getNode() = 0; virtual std::string getName() = 0; + + virtual std::map &getUserSettings() { + return m_userSettings; + } + + protected: + std::map m_userSettings; }; } diff --git a/include/transport/adhocmanager.h b/include/transport/adhocmanager.h index 75e31d09..8b418640 100644 --- a/include/transport/adhocmanager.h +++ b/include/transport/adhocmanager.h @@ -66,6 +66,8 @@ class AdHocManager : public Swift::Responder { private: virtual bool handleGetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, boost::shared_ptr payload); virtual bool handleSetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, boost::shared_ptr payload); + + void handleUserCreated(User *user); Component *m_component; DiscoItemsResponder *m_discoItemsResponder; diff --git a/include/transport/protocol.proto b/include/transport/protocol.proto index 4475b707..5a1afd92 100644 --- a/include/transport/protocol.proto +++ b/include/transport/protocol.proto @@ -70,6 +70,7 @@ message ConversationMessage { optional string nickname = 4; optional string xhtml = 5; optional string timestamp = 6; + optional bool headline = 7; } message Room { diff --git a/include/transport/settingsadhoccommand.h b/include/transport/settingsadhoccommand.h index c9702865..9b0943dc 100644 --- a/include/transport/settingsadhoccommand.h +++ b/include/transport/settingsadhoccommand.h @@ -53,7 +53,10 @@ class SettingsAdHocCommand : public AdHocCommand { class SettingsAdHocCommandFactory : public AdHocCommandFactory { public: - SettingsAdHocCommandFactory() {} + SettingsAdHocCommandFactory() { + m_userSettings["send_headlines"] = "0"; + } + virtual ~SettingsAdHocCommandFactory() {} AdHocCommand *createAdHocCommand(Component *component, UserManager *userManager, StorageBackend *storageBackend, const Swift::JID &initiator, const Swift::JID &to) { diff --git a/include/transport/user.h b/include/transport/user.h index 1719b13d..e2201735 100644 --- a/include/transport/user.h +++ b/include/transport/user.h @@ -116,6 +116,14 @@ class User : public Swift::EntityCapsProvider { return m_resources; } + void addUserSetting(const std::string &key, const std::string &value) { + m_settings[key] = value; + } + + const std::string &getUserSetting(const std::string &key) { + return m_settings[key]; + } + boost::signal onReadyToConnect; boost::signal onPresenceChanged; boost::signal onRoomJoined; @@ -145,6 +153,7 @@ class User : public Swift::EntityCapsProvider { int m_resources; int m_reconnectCounter; std::list m_joinedRooms; + std::map m_settings; }; } diff --git a/src/adhocmanager.cpp b/src/adhocmanager.cpp index 8cda07d6..4ee20cfc 100644 --- a/src/adhocmanager.cpp +++ b/src/adhocmanager.cpp @@ -42,6 +42,8 @@ AdHocManager::AdHocManager(Component *component, DiscoItemsResponder *discoItems m_collectTimer = m_component->getNetworkFactories()->getTimerFactory()->createTimer(20); m_collectTimer->onTick.connect(boost::bind(&AdHocManager::removeOldSessions, this)); m_collectTimer->start(); + + m_userManager->onUserCreated.connect(boost::bind(&AdHocManager::handleUserCreated, this, _1)); } AdHocManager::~AdHocManager() { @@ -66,6 +68,21 @@ void AdHocManager::stop() { m_sessions.clear(); } +void AdHocManager::handleUserCreated(User *user) { + if (!m_storageBackend) { + return; + } + + for (std::map::const_iterator it = m_factories.begin(); it != m_factories.end(); it++) { + for (std::map::const_iterator it2 = it->second->getUserSettings().begin(); it2 != it->second->getUserSettings().end(); it2++) { + std::string value = CONFIG_STRING_DEFAULTED(m_component->getConfig(), it->second->getNode() + "." + it2->first, it2->second); + int type = (int) TYPE_BOOLEAN; + m_storageBackend->getUserSetting(user->getUserInfo().id, it2->first, type, value); + user->addUserSetting(it2->first, value); + } + } +} + void AdHocManager::addAdHocCommand(AdHocCommandFactory *factory) { if (m_factories.find(factory->getNode()) != m_factories.end()) { LOG4CXX_ERROR(logger, "Command with node " << factory->getNode() << " is already registered. Ignoring this attempt."); @@ -159,6 +176,12 @@ bool AdHocManager::handleSetRequest(const Swift::JID& from, const Swift::JID& to // Command completed, so we can remove it now if (response->getStatus() == Swift::Command::Completed || response->getStatus() == Swift::Command::Canceled) { + // update userSettings map if user is already connected + User *user = m_userManager->getUser(from.toBare().toString()); + if (user) { + handleUserCreated(user); + } + m_sessions[from].erase(command->getId()); if (m_sessions[from].empty()) { m_sessions.erase(from); diff --git a/src/conversation.cpp b/src/conversation.cpp index 9912bf25..95f601a9 100644 --- a/src/conversation.cpp +++ b/src/conversation.cpp @@ -82,7 +82,14 @@ void Conversation::handleMessage(boost::shared_ptr &message, con message->setType(Swift::Message::Groupchat); } else { - message->setType(Swift::Message::Chat); + if (message->getType() != Swift::Message::Headline) { + if (m_conversationManager->getUser()->getUserSetting("send_headlines") == "1") { + message->setType(Swift::Message::Headline); + } + else { + message->setType(Swift::Message::Chat); + } + } } std::string n = nickname; diff --git a/src/conversationmanager.cpp b/src/conversationmanager.cpp index 066a4603..b2c22ecf 100644 --- a/src/conversationmanager.cpp +++ b/src/conversationmanager.cpp @@ -39,12 +39,7 @@ ConversationManager::ConversationManager(User *user, Component *component){ } ConversationManager::~ConversationManager() { - while(!m_convs.empty()) { - LOG4CXX_INFO(logger, m_user->getJID().toString() << ": Removing conversation " << (*m_convs.begin()).first); - (*m_convs.begin()).second->destroyRoom(); - delete (*m_convs.begin()).second; - m_convs.erase(m_convs.begin()); - } + deleteAllConversations(); } void ConversationManager::deleteAllConversations() { diff --git a/src/networkpluginserver.cpp b/src/networkpluginserver.cpp index 1a232faa..830d065e 100644 --- a/src/networkpluginserver.cpp +++ b/src/networkpluginserver.cpp @@ -649,6 +649,10 @@ void NetworkPluginServer::handleConvMessagePayload(const std::string &data, bool msg->setBody(payload.message()); } + if (payload.headline()) { + msg->setType(Swift::Message::Headline); + } + // Add xhtml-im payload. if (CONFIG_BOOL(m_config, "service.enable_xhtml") && !payload.xhtml().empty()) { msg->addPayload(boost::make_shared(payload.xhtml())); diff --git a/src/settingsadhoccommand.cpp b/src/settingsadhoccommand.cpp index b56dd8af..0470a64e 100644 --- a/src/settingsadhoccommand.cpp +++ b/src/settingsadhoccommand.cpp @@ -40,6 +40,11 @@ SettingsAdHocCommand::SettingsAdHocCommand(Component *component, UserManager *us field->setName("enable_transport"); field->setLabel("Enable transport"); addFormField(field); + + field = Swift::BooleanFormField::create(CONFIG_STRING_DEFAULTED(component->getConfig(), "settings.send_headlines", "0") == "1"); + field->setName("send_headlines"); + field->setLabel("Send messages as headlines"); + addFormField(field); } SettingsAdHocCommand::~SettingsAdHocCommand() { diff --git a/src/tests/basictest.h b/src/tests/basictest.h index a1d9a0f1..00374c66 100644 --- a/src/tests/basictest.h +++ b/src/tests/basictest.h @@ -193,6 +193,15 @@ class TestingStorageBackend : public StorageBackend { settings[userId][variable] = value; } + void dumpUserSettings() { + std::cout << "\n\nUserSettings dump:\n"; + for (std::map >::const_iterator it = settings.begin(); it != settings.end(); it++) { + for (std::map::const_iterator it2 = it->second.begin(); it2 != it->second.end(); it2++) { + std::cout << it->first << ":" << it2->first << "=" << it2->second << "\n"; + } + } + } + virtual void beginTransaction() {} virtual void commitTransaction() {} }; diff --git a/src/tests/conversationmanager.cpp b/src/tests/conversationmanager.cpp index 480a7289..176aab06 100644 --- a/src/tests/conversationmanager.cpp +++ b/src/tests/conversationmanager.cpp @@ -24,6 +24,7 @@ using namespace Transport; class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST_SUITE(ConversationManagerTest); CPPUNIT_TEST(handleNormalMessages); + CPPUNIT_TEST(handleNormalMessagesHeadline); CPPUNIT_TEST(handleGroupchatMessages); CPPUNIT_TEST(handleGroupchatMessagesTwoResources); CPPUNIT_TEST(handleChatstateMessages); @@ -204,6 +205,55 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe received.clear(); } + void handleNormalMessagesHeadline() { + User *user = userManager->getUser("user@localhost"); + user->addUserSetting("send_headlines", "1"); + + TestingConversation *conv = new TestingConversation(user->getConversationManager(), "buddy1@test"); + user->getConversationManager()->addConversation(conv); + conv->onMessageToSend.connect(boost::bind(&ConversationManagerTest::handleMessageReceived, this, _1, _2)); + + boost::shared_ptr msg(new Swift::Message()); + msg->setBody("hi there<>!"); + + // Forward it + conv->handleMessage(msg); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::Message::Headline, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT_EQUAL(std::string("hi there<>!"), dynamic_cast(getStanza(received[0]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("user@localhost"), dynamic_cast(getStanza(received[0]))->getTo().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); + + received.clear(); + user->addUserSetting("send_headlines", "0"); + // Forward it - Conversation should keep the Headline type, because msg->getType() is Headline + conv->handleMessage(msg); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::Message::Headline, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT_EQUAL(std::string("hi there<>!"), dynamic_cast(getStanza(received[0]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("user@localhost"), dynamic_cast(getStanza(received[0]))->getTo().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); + + received.clear(); + msg->setType(Swift::Message::Chat); + // Forward it + conv->handleMessage(msg); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::Message::Chat, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT_EQUAL(std::string("hi there<>!"), dynamic_cast(getStanza(received[0]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("user@localhost"), dynamic_cast(getStanza(received[0]))->getTo().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); + } + void handleGroupchatMessages() { User *user = userManager->getUser("user@localhost"); TestingConversation *conv = new TestingConversation(user->getConversationManager(), "#room", true); diff --git a/src/tests/settingsadhoccommand.cpp b/src/tests/settingsadhoccommand.cpp index 7576fe08..a969dfea 100644 --- a/src/tests/settingsadhoccommand.cpp +++ b/src/tests/settingsadhoccommand.cpp @@ -33,6 +33,8 @@ class SettingsAdHocCommandTest : public CPPUNIT_NS :: TestFixture, public BasicT CPPUNIT_TEST(executeBadSessionID); CPPUNIT_TEST(executeNotRegistered); CPPUNIT_TEST(cancel); + CPPUNIT_TEST(propagateUserSetting); + CPPUNIT_TEST(defaultAccordingToConfig); CPPUNIT_TEST_SUITE_END(); public: @@ -327,6 +329,77 @@ class SettingsAdHocCommandTest : public CPPUNIT_NS :: TestFixture, public BasicT CPPUNIT_ASSERT_EQUAL(Swift::Command::Canceled, getStanza(received[0])->getPayload()->getStatus()); } + void propagateUserSetting() { + connectUser(); + User *user = userManager->getUser("user@localhost"); + CPPUNIT_ASSERT_EQUAL(std::string("0"), user->getUserSetting("send_headlines")); + + boost::shared_ptr payload(new Swift::Command("settings")); + boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Set, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::IQ::Result, dynamic_cast(getStanza(received[0]))->getType()); + + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + CPPUNIT_ASSERT_EQUAL(std::string("settings"), getStanza(received[0])->getPayload()->getNode()); + CPPUNIT_ASSERT_EQUAL(Swift::Command::Executing, getStanza(received[0])->getPayload()->getStatus()); + + // form element + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()->getForm()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()->getForm()->getField("send_headlines")); + + // set enabled_transport = 0 + Swift::FormField::ref f = getStanza(received[0])->getPayload()->getForm()->getField("send_headlines"); + boost::dynamic_pointer_cast(f)->setValue(true); + + std::string sessionId = getStanza(received[0])->getPayload()->getSessionID(); + + // finish the command + payload = boost::shared_ptr(new Swift::Command("settings")); + payload->setSessionID(sessionId); + payload->setForm(getStanza(received[0])->getPayload()->getForm()); + iq = Swift::IQ::createRequest(Swift::IQ::Set, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + received.clear(); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(std::string("1"), user->getUserSetting("send_headlines")); + } + + void defaultAccordingToConfig() { + std::istringstream ifs("service.server_mode = 1\nservice.jid_escaping=0\nservice.jid=localhost\nsettings.send_headlines=1\n"); + cfg->load(ifs); + connectUser(); + User *user = userManager->getUser("user@localhost"); + CPPUNIT_ASSERT_EQUAL(std::string("1"), user->getUserSetting("send_headlines")); + boost::shared_ptr payload(new Swift::Command("settings")); + boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Set, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::IQ::Result, dynamic_cast(getStanza(received[0]))->getType()); + + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + CPPUNIT_ASSERT_EQUAL(std::string("settings"), getStanza(received[0])->getPayload()->getNode()); + CPPUNIT_ASSERT_EQUAL(Swift::Command::Executing, getStanza(received[0])->getPayload()->getStatus()); + + // form element + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()->getForm()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()->getForm()->getField("send_headlines")); + Swift::FormField::ref f = getStanza(received[0])->getPayload()->getForm()->getField("send_headlines"); + CPPUNIT_ASSERT_EQUAL(true, boost::dynamic_pointer_cast(f)->getValue()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION (SettingsAdHocCommandTest);