From 08a5e487db0c2f288eb0a56a11b3917b8fb33922 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Tue, 27 Nov 2012 10:33:04 +0100 Subject: [PATCH 1/5] Better error message when backends can't be started --- backends/twitter/main.cpp | 3 ++- src/networkpluginserver.cpp | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backends/twitter/main.cpp b/backends/twitter/main.cpp index 5cbf2def..f68f25ab 100644 --- a/backends/twitter/main.cpp +++ b/backends/twitter/main.cpp @@ -39,7 +39,8 @@ int main (int argc, char* argv[]) { StorageBackend *storagebackend; storagebackend = StorageBackend::createBackend(cfg, error); if (storagebackend == NULL) { - LOG4CXX_ERROR(logger, "Error creating StorageBackend! " << error) + LOG4CXX_ERROR(logger, "Error creating StorageBackend! " << error); + LOG4CXX_ERROR(logger, "Twitter backend needs storage backend configured to work! " << error); return -2; } diff --git a/src/networkpluginserver.cpp b/src/networkpluginserver.cpp index bcea34f7..5061d1ce 100644 --- a/src/networkpluginserver.cpp +++ b/src/networkpluginserver.cpp @@ -332,7 +332,13 @@ void NetworkPluginServer::start() { if (result != 0) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { - LOG4CXX_ERROR(logger, "Backend can not be started, exit_code=" << WEXITSTATUS(status) << ", possible error: " << strerror(WEXITSTATUS(status))); + if (status == 254) { + LOG4CXX_ERROR(logger, "Backend can not be started, because it needs database to store data, but the database backend is not configured."); + } + else { + LOG4CXX_ERROR(logger, "Backend can not be started, exit_code=" << WEXITSTATUS(status) << ", possible error: " << strerror(WEXITSTATUS(status))); + } + LOG4CXX_ERROR(logger, "Check backend log for more details"); continue; } } From 110a366bfb8667978c79f1cdffd275f93667a40c Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Tue, 27 Nov 2012 10:50:49 +0100 Subject: [PATCH 2/5] Added NetworkPlugin::StorageBackendNeeded instead of -2 constant --- backends/twitter/main.cpp | 2 +- include/transport/networkplugin.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backends/twitter/main.cpp b/backends/twitter/main.cpp index f68f25ab..bb28d601 100644 --- a/backends/twitter/main.cpp +++ b/backends/twitter/main.cpp @@ -41,7 +41,7 @@ int main (int argc, char* argv[]) { if (storagebackend == NULL) { LOG4CXX_ERROR(logger, "Error creating StorageBackend! " << error); LOG4CXX_ERROR(logger, "Twitter backend needs storage backend configured to work! " << error); - return -2; + return NetworkPlugin::StorageBackendNeeded; } else if (!storagebackend->connect()) { diff --git a/include/transport/networkplugin.h b/include/transport/networkplugin.h index 6c4ababd..a5e123f7 100644 --- a/include/transport/networkplugin.h +++ b/include/transport/networkplugin.h @@ -35,6 +35,7 @@ namespace Transport { /// development. class NetworkPlugin { public: + enum ExitCode { StorageBackendNeeded = -2 }; class PluginConfig { public: From 8b6973539f23ea00dfb950f7e109f2d5f2139d51 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Tue, 27 Nov 2012 12:51:19 +0100 Subject: [PATCH 3/5] Postpone room subject forwarding if we haven't sent initial presences yet --- backends/libcommuni/ircnetworkplugin.cpp | 12 ++++++++++++ backends/libcommuni/ircnetworkplugin.h | 1 + backends/libcommuni/session.cpp | 10 +++++++++- include/transport/conversation.h | 2 ++ plugin/cpp/networkplugin.cpp | 2 +- src/conversation.cpp | 12 ++++++++++++ src/tests/conversationmanager.cpp | 25 ++++++++++++++++-------- src/usermanager.cpp | 3 ++- 8 files changed, 56 insertions(+), 11 deletions(-) diff --git a/backends/libcommuni/ircnetworkplugin.cpp b/backends/libcommuni/ircnetworkplugin.cpp index f2fc4574..bda4cc4d 100644 --- a/backends/libcommuni/ircnetworkplugin.cpp +++ b/backends/libcommuni/ircnetworkplugin.cpp @@ -11,6 +11,7 @@ DEFINE_LOGGER(logger, "IRCNetworkPlugin"); IRCNetworkPlugin::IRCNetworkPlugin(Config *config, Swift::QtEventLoop *loop, const std::string &host, int port) { this->config = config; m_currentServer = 0; + m_firstPing = true; m_socket = new QTcpSocket(); m_socket->connectToHost(FROM_UTF8(host), port); connect(m_socket, SIGNAL(readyRead()), this, SLOT(readData())); @@ -48,6 +49,17 @@ void IRCNetworkPlugin::readData() { if (availableBytes == 0) return; + if (m_firstPing) { + m_firstPing = false; + // Users can join the network without registering if we allow + // one user to connect multiple IRC networks. + if (m_servers.empty()) { + NetworkPlugin::PluginConfig cfg; + cfg.setNeedRegistration(false); + sendConfig(cfg); + } + } + std::string d = std::string(m_socket->readAll().data(), availableBytes); handleDataRead(d); } diff --git a/backends/libcommuni/ircnetworkplugin.h b/backends/libcommuni/ircnetworkplugin.h index 043089cd..d4a84d73 100644 --- a/backends/libcommuni/ircnetworkplugin.h +++ b/backends/libcommuni/ircnetworkplugin.h @@ -46,4 +46,5 @@ class IRCNetworkPlugin : public QObject, public NetworkPlugin { std::vector m_servers; int m_currentServer; std::string m_identify; + bool m_firstPing; }; \ No newline at end of file diff --git a/backends/libcommuni/session.cpp b/backends/libcommuni/session.cpp index 91a7fee8..cb0b6c92 100644 --- a/backends/libcommuni/session.cpp +++ b/backends/libcommuni/session.cpp @@ -189,6 +189,7 @@ void MyIrcSession::on_messageReceived(IrcMessage *message) { void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { QString channel; QStringList members; + std::string nick; IrcNumericMessage *m = (IrcNumericMessage *) message; switch (m->code()) { @@ -196,7 +197,14 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { m_topicData = TO_UTF8(m->parameters().value(2)); break; case 333: - np->handleSubject(user, TO_UTF8(m->parameters().value(1)) + suffix, m_topicData, TO_UTF8(m->parameters().value(2))); + nick = TO_UTF8(m->parameters().value(2)); + if (nick.find("!") != std::string::npos) { + nick = nick.substr(0, nick.find("!")); + } + if (nick.find("/") != std::string::npos) { + nick = nick.substr(0, nick.find("/")); + } + np->handleSubject(user, TO_UTF8(m->parameters().value(1)) + suffix, m_topicData, nick); break; case 353: channel = m->parameters().value(2); diff --git a/include/transport/conversation.h b/include/transport/conversation.h index e137b566..ff109aa2 100644 --- a/include/transport/conversation.h +++ b/include/transport/conversation.h @@ -148,6 +148,8 @@ class Conversation { Swift::JID m_jid; std::list m_jids; std::map m_participants; + boost::shared_ptr m_subject; + bool m_sentInitialPresence; }; } diff --git a/plugin/cpp/networkplugin.cpp b/plugin/cpp/networkplugin.cpp index b5622326..3dde9c53 100644 --- a/plugin/cpp/networkplugin.cpp +++ b/plugin/cpp/networkplugin.cpp @@ -63,7 +63,7 @@ NetworkPlugin::~NetworkPlugin() { } void NetworkPlugin::sendConfig(const PluginConfig &cfg) { - std::string data = "[registration]"; + std::string data = "[registration]\n"; data += std::string("needPassword=") + (cfg.m_needPassword ? "1" : "0") + "\n"; data += std::string("needRegistration=") + (cfg.m_needRegistration ? "1" : "0") + "\n"; diff --git a/src/conversation.cpp b/src/conversation.cpp index 67a5c386..9cd8f739 100644 --- a/src/conversation.cpp +++ b/src/conversation.cpp @@ -33,6 +33,7 @@ Conversation::Conversation(ConversationManager *conversationManager, const std:: // m_conversationManager->addConversation(this); m_muc = isMUC; m_jid = m_conversationManager->getUser()->getJID().toBare(); + m_sentInitialPresence = false; } Conversation::~Conversation() { @@ -128,6 +129,11 @@ void Conversation::handleMessage(boost::shared_ptr &message, con 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; + } m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(message); } } @@ -169,6 +175,7 @@ Swift::Presence::ref Conversation::generatePresence(const std::string &nick, int Swift::MUCUserPayload::StatusCode c; c.code = 110; p->addStatusCode(c); + m_sentInitialPresence = true; } @@ -217,6 +224,11 @@ void Conversation::handleParticipantChanged(const std::string &nick, int flag, i if (!newname.empty()) { handleParticipantChanged(newname, flag, status, statusMessage); } + + if (m_sentInitialPresence && m_subject) { + m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(m_subject); + m_subject.reset(); + } } } diff --git a/src/tests/conversationmanager.cpp b/src/tests/conversationmanager.cpp index ef31975d..c182146c 100644 --- a/src/tests/conversationmanager.cpp +++ b/src/tests/conversationmanager.cpp @@ -94,26 +94,35 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe void handleSubjectMessages() { User *user = userManager->getUser("user@localhost"); - - TestingConversation *conv = new TestingConversation(user->getConversationManager(), "buddy1"); - user->getConversationManager()->addConversation(conv); + TestingConversation *conv = new TestingConversation(user->getConversationManager(), "#room", true); + conv->onMessageToSend.connect(boost::bind(&ConversationManagerTest::handleMessageReceived, this, _1, _2)); + conv->setNickname("nickname"); + conv->addJID("user@localhost/resource"); boost::shared_ptr msg(new Swift::Message()); msg->setSubject("subject"); + msg->setType(Swift::Message::Groupchat); - // Forward it conv->handleMessage(msg); loop->processEvents(); + + // No response, because presence with code 110 has not been sent yet and we must not send + // subject before this one. + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + // this user presence - status code 110 + conv->handleParticipantChanged("nickname", 1, Swift::StatusShow::Away, "my status message"); + loop->processEvents(); - CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); - CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); - CPPUNIT_ASSERT_EQUAL(std::string("subject"), dynamic_cast(getStanza(received[0]))->getSubject()); + CPPUNIT_ASSERT_EQUAL(2, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[1]))); + CPPUNIT_ASSERT_EQUAL(std::string("subject"), dynamic_cast(getStanza(received[1]))->getSubject()); received.clear(); // send response msg->setFrom("user@localhost/resource"); - msg->setTo("buddy1@localhost/bot"); + msg->setTo("#room@localhost"); injectMessage(msg); loop->processEvents(); diff --git a/src/usermanager.cpp b/src/usermanager.cpp index 4e6e4b01..fd4305ce 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -247,7 +247,8 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { // We allow auto_register feature in gateway-mode. This allows IRC user to register // the transport just by joining the room. if (!m_component->inServerMode()) { - if (!registered && CONFIG_BOOL(m_component->getConfig(), "registration.auto_register")) { + if (!registered && (CONFIG_BOOL(m_component->getConfig(), "registration.auto_register") || + !CONFIG_BOOL_DEFAULTED(m_component->getConfig(), "registration.needRegistration", true))) { res.password = ""; res.jid = userkey; From 827bac538d8df2b45250a211e50b8c608e90d134 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Tue, 27 Nov 2012 12:53:46 +0100 Subject: [PATCH 4/5] Fix qt-moc error while parsing boost headers --- backends/libcommuni/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backends/libcommuni/CMakeLists.txt b/backends/libcommuni/CMakeLists.txt index 38e7507b..699b02bd 100644 --- a/backends/libcommuni/CMakeLists.txt +++ b/backends/libcommuni/CMakeLists.txt @@ -1,10 +1,14 @@ cmake_minimum_required(VERSION 2.6) FILE(GLOB SRC *.cpp) FILE(GLOB HEADERS *.h) -QT4_WRAP_CPP(SRC ${HEADERS}) +QT4_WRAP_CPP(SRC ${HEADERS} OPTIONS -DBOOST_TT_HAS_OPERATOR_HPP_INCLUDED) ADD_EXECUTABLE(spectrum2_libcommuni_backend ${SRC}) -target_link_libraries(spectrum2_libcommuni_backend ${IRC_LIBRARY} ${QT_LIBRARIES} transport pthread) +if (NOT WIN32) + target_link_libraries(spectrum2_libcommuni_backend ${IRC_LIBRARY} ${QT_LIBRARIES} transport pthread) +else () + target_link_libraries(spectrum2_libcommuni_backend ${IRC_LIBRARY} ${QT_LIBRARIES} transport) +endif() INSTALL(TARGETS spectrum2_libcommuni_backend RUNTIME DESTINATION bin) From 9388d599156cb6677278cd31471a684671aff6ad Mon Sep 17 00:00:00 2001 From: HanzZ Date: Tue, 27 Nov 2012 22:18:15 +0100 Subject: [PATCH 5/5] Store last tweet ID in database so all tweets are not forwarded on reconnect. Send proper timestamp of tweets --- backends/twitter/TwitterPlugin.cpp | 44 +++++++++++++++++----- backends/twitter/TwitterPlugin.h | 2 + backends/twitter/TwitterResponseParser.cpp | 16 ++++++-- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/backends/twitter/TwitterPlugin.cpp b/backends/twitter/TwitterPlugin.cpp index e6a0d52e..ef84bbf3 100644 --- a/backends/twitter/TwitterPlugin.cpp +++ b/backends/twitter/TwitterPlugin.cpp @@ -322,7 +322,7 @@ void TwitterPlugin::pollForTweets() std::set::iterator it = onlineUsers.begin(); while(it != onlineUsers.end()) { std::string user = *it; - tp->runAsThread(new TimelineRequest(userdb[user].sessions, user, "", userdb[user].mostRecentTweetID, + tp->runAsThread(new TimelineRequest(userdb[user].sessions, user, "", getMostRecentTweetIDUnsafe(user), boost::bind(&TwitterPlugin::displayTweets, this, _1, _2, _3, _4))); it++; } @@ -517,14 +517,39 @@ void TwitterPlugin::updateLastTweetID(const std::string user, const std::string { boost::mutex::scoped_lock lock(userlock); userdb[user].mostRecentTweetID = ID; + + UserInfo info; + if(storagebackend->getUser(user, info) == false) { + LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!") + return; + } + + storagebackend->updateUserSetting((long)info.id, "twitter_last_tweet", ID); +} + +std::string TwitterPlugin::getMostRecentTweetIDUnsafe(const std::string user) +{ + std::string ID = ""; + if(onlineUsers.count(user)) { + ID = userdb[user].mostRecentTweetID; + if (ID.empty()) { + int type; + UserInfo info; + if(storagebackend->getUser(user, info) == false) { + LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!") + } + else { + storagebackend->getUserSetting(info.id, "twitter_last_tweet", type, ID); + } + } + } + return ID; } std::string TwitterPlugin::getMostRecentTweetID(const std::string user) -{ - boost::mutex::scoped_lock lock(userlock); - std::string ID = "-1"; - if(onlineUsers.count(user)) ID = userdb[user].mostRecentTweetID; - return ID; +{ + boost::mutex::scoped_lock lock(userlock); + return getMostRecentTweetIDUnsafe(user); } void TwitterPlugin::updateLastDMID(const std::string user, const std::string ID) @@ -622,16 +647,17 @@ void TwitterPlugin::displayTweets(std::string &user, std::string &userRequested, std::map lastTweet; std::map::iterator it; - for(int i=0 ; i= 0 ; i--) { if(userdb[user].twitterMode != CHATROOM) { - timeline += " - " + tweets[i].getUserData().getScreenName() + ": " + tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")\n"; + std::string m = " - " + tweets[i].getUserData().getScreenName() + ": " + tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")\n"; + handleMessage(user, adminLegacyName, m, "", "", tweets[i].getCreationTime()); std::string scrname = tweets[i].getUserData().getScreenName(); if(lastTweet.count(scrname) == 0 || cmp(tweets[lastTweet[scrname]].getID(), tweets[i].getID()) <= 0) lastTweet[scrname] = i; } else { handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName, - tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")", tweets[i].getUserData().getScreenName()); + tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")", tweets[i].getUserData().getScreenName(), "", tweets[i].getCreationTime()); } } diff --git a/backends/twitter/TwitterPlugin.h b/backends/twitter/TwitterPlugin.h index b49ea866..ab4aca3b 100644 --- a/backends/twitter/TwitterPlugin.h +++ b/backends/twitter/TwitterPlugin.h @@ -134,6 +134,8 @@ class TwitterPlugin : public NetworkPlugin { /***********************************************************************************/ private: + std::string getMostRecentTweetIDUnsafe(const std::string user); + enum status {NEW, WAITING_FOR_PIN, CONNECTED, DISCONNECTED}; enum mode {SINGLECONTACT, MULTIPLECONTACT, CHATROOM}; diff --git a/backends/twitter/TwitterResponseParser.cpp b/backends/twitter/TwitterResponseParser.cpp index 4ccc5e85..4324b310 100644 --- a/backends/twitter/TwitterResponseParser.cpp +++ b/backends/twitter/TwitterResponseParser.cpp @@ -11,6 +11,15 @@ static std::string tolowercase(std::string inp) return out; } +static std::string toIsoTime(std::string in) { + time_t now = time(0); + struct tm *mtime = gmtime(&now); + strptime(in.c_str(), "%a %b %d %H:%M:%S %z %Y", mtime); + char buf[80]; + strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", mtime); + return buf; +} + EmbeddedStatus getEmbeddedStatus(const Swift::ParserElement::ref &element, const std::string xmlns) { EmbeddedStatus status; @@ -19,7 +28,8 @@ EmbeddedStatus getEmbeddedStatus(const Swift::ParserElement::ref &element, const return status; } - status.setCreationTime( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ); + status.setCreationTime( toIsoTime(std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ) ); + status.setID( std::string( element->getChild(TwitterReponseTypes::id, xmlns)->getText() ) ); status.setTweet( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ); status.setTruncated( std::string( element->getChild(TwitterReponseTypes::truncated, xmlns)->getText() )=="true" ); @@ -60,7 +70,7 @@ Status getStatus(const Swift::ParserElement::ref &element, const std::string xml return status; } - status.setCreationTime( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ); + status.setCreationTime( toIsoTime ( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ) ); status.setID( std::string( element->getChild(TwitterReponseTypes::id, xmlns)->getText() ) ); status.setTweet( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ); status.setTruncated( std::string( element->getChild(TwitterReponseTypes::truncated, xmlns)->getText() )=="true" ); @@ -82,7 +92,7 @@ DirectMessage getDirectMessage(const Swift::ParserElement::ref &element, const s return DM; } - DM.setCreationTime( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ); + DM.setCreationTime( toIsoTime ( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ) ); DM.setID( std::string( element->getChild(TwitterReponseTypes::id, xmlns)->getText() ) ); DM.setMessage( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ); DM.setSenderID( std::string( element->getChild(TwitterReponseTypes::sender_id, xmlns)->getText() ) );