////////////////////////////////////////////////// // Blabber [TalkManager.cpp] ////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui/ModalAlertFactory.h" #include "BlabberSettings.h" #include "JabberSpeak.h" #include "MessageRepeater.h" #include "Messages.h" #include "SoundSystem.h" #include "TalkManager.h" TalkManager *TalkManager::_instance = NULL; TalkManager *TalkManager::Instance() { if (_instance == NULL) { _instance = new TalkManager(); } return _instance; } TalkManager::TalkManager() { // Create/open the avatars cache directory BPath path; find_directory(B_USER_CACHE_DIRECTORY, &path); BDirectory cacheRoot(path.Path()); if (!cacheRoot.Contains("Renga")) { BDirectory temp; cacheRoot.CreateDirectory("Renga", &temp); if (!temp.Contains("avatars")) { temp.CreateDirectory("avatars", &fAvatarCache); } } fAvatarCache.SetTo(&cacheRoot, "Renga/avatars"); } TalkManager::~TalkManager() { _instance = NULL; } void TalkManager::CreateTalkSession(const gloox::Message::MessageType type, const gloox::JID* user, string group_room, string group_username, gloox::MessageSession* session, bool sound_on_new) { TalkView *window = NULL; // is there a window already? if (type != gloox::Message::Groupchat) { // This code for when we get called from clicking a user in the roster. // There is possibly already a session but the roster doesn't know about it // so we have to search it (in a quite inefficient way). // FIXME remove all this junk and have each TalkWindow be a SessionHandler // for its own session instead of centralizing everything here. gloox::JID fullJID = *user; if (session == NULL) { gloox::RosterManager* rm = JabberSpeak::Instance()->GlooxClient()->rosterManager(); gloox::RosterItem* ri = rm->getRosterItem(fullJID); if (ri && !ri->resources().empty()) { std::string res = ri->resources().begin()->first; fullJID.setResource(res); } for (TalkMap::iterator i = fTalkMap.begin(); i != fTalkMap.end(); ++i) { if ((*i).first->target().bare() == user->bare()) { session = (*i).first; break; } } } if (session && fTalkMap.find(session) != fTalkMap.end()) { window = fTalkMap.at(session); } else { // Actually create the session, there isn't one matching. if (session == NULL) { session = new gloox::MessageSession( JabberSpeak::Instance()->GlooxClient(), fullJID); } else { // It's a freshly created session for an incoming message, // we just need to create the matching window } // create a new window window = new TalkView(user, group_room, group_username, session); if (sound_on_new) { // play a sound SoundSystem::Instance()->PlayNewMessageSound(); } // add it to the known list fTalkMap[session] = window; SendNotices(kWindowList); session->registerMessageHandler(this); } } else { if (IsExistingWindowToGroup(group_room)) window = fGroupMap.at((gloox::MUCRoom*)IsExistingWindowToGroup(group_room)); else { // create a new window window = new TalkView(user, group_room, group_username, NULL); gloox::JID jid(group_room); jid.setResource(group_username); gloox::MUCRoom* room = new gloox::InstantMUCRoom( JabberSpeak::Instance()->GlooxClient(), jid, this); // add it to the known list fGroupMap[room] = window; room->join(); SendNotices(kWindowList); } } BMessage message(kAddTalkView); message.AddPointer("view", window); BlabberMainWindow::Instance()->PostMessage(&message); } void TalkManager::handleMessage(const gloox::Message& msg, gloox::MessageSession* session) { // First check if it's a carbon if (msg.hasEmbeddedStanza()) { // get the possible carbon extension const gloox::Carbons *carbon = msg.findExtension( gloox::ExtCarbons); // if the extension exists and contains a message, use it as the real message if (carbon && carbon->embeddedStanza()) { const gloox::Message* message = static_cast( carbon->embeddedStanza()); try { TalkView* window = fTalkMap.at(session); BMessage notification(kIncomingMessage); notification.AddString("content", message->body().c_str()); BMessenger(window).SendMessage(¬ification); } catch (const std::out_of_range&) { // In case we get a carbon for a chat we have not joined? } return; } } const gloox::PubSub::Event* event = msg.findExtension(gloox::ExtPubSubEvent); if (event) { if (event->node() == "urn:xmpp:avatar:metadata") { for (auto item: event->items()) { GetAvatar(msg.from(), item->item); return; } } else { printf("Got unexpected pubsub event node %s\n", event->node().c_str()); } } try { TalkView* window = fTalkMap.at(session); // submit the chat BMessage notification(kIncomingMessage); notification.AddString("content", msg.body().c_str()); BMessenger(window).SendMessage(¬ification); printf("notify %p\n", window); } catch(const std::out_of_range&) { printf("%s: no window found for session %p with %s, ignoring message\n", __func__, session, session ? session->target().full().c_str(): "NULL"); } } void TalkManager::handleMessageSession(gloox::MessageSession* session) { // don't create a window for the carbons session, but still register to // get the messages. if (session->threadID().empty() && session->types() == gloox::Message::Headline) { session->registerMessageHandler(this); return; } // create the window CreateTalkSession((gloox::Message::MessageType)session->types(), &session->target(), "", "", session, true); } //#pragma mark - PubSub ResultHandler void TalkManager::handleItem(const gloox::JID&, const std::string&, const gloox::Tag*) { puts(__func__); } void TalkManager::handleItems(const std::string&, const gloox::JID& jid, const std::string& node, const gloox::PubSub::ItemList& itemList, const gloox::Error*) { if (node == "urn:xmpp:avatar:data") { for (auto item: itemList) { // item->id() has the checksum std::string base64 = item->tag()->findChild("data")->cdata(); // need to remove all newlines from the base64... size_t pos; for (;;) { pos = base64.find('\n'); if (pos == std::string::npos) break; base64.erase(pos, 1); } const std::string decoded = gloox::Base64::decode64(base64); BMessage message(kAvatarUpdate); message.AddString("jid", jid.full().c_str()); message.AddData("avatar", B_RAW_TYPE, decoded.data(), decoded.length()); SendNotices(kAvatarUpdate, &message); // store it in cache BFile file; fAvatarCache.CreateFile(item->id().c_str(), &file); file.Write(decoded.data(), decoded.length()); } } else { printf("unknown pubsub item in %s(%s, %s)\n", __func__, jid.full().c_str(), node.c_str()); } } void TalkManager::handleItemPublication(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::ItemList&, const gloox::Error*) { puts(__func__); } void TalkManager::handleItemDeletion(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::ItemList&, const gloox::Error*) { puts(__func__); } void TalkManager::handleSubscriptionResult(const std::string&, const gloox::JID&, const std::string&, const std::string&, const gloox::JID&, gloox::PubSub::SubscriptionType, const gloox::Error*) { puts(__func__); } void TalkManager::handleUnsubscriptionResult(const std::string&, const gloox::JID&, const gloox::Error*) { puts(__func__); } void TalkManager::handleSubscriptionOptions(const std::string&, const gloox::JID&, const gloox::JID&, const std::string&, const gloox::DataForm*, const std::string&, const gloox::Error*) { puts(__func__); } void TalkManager::handleSubscriptionOptionsResult(const std::string&, const gloox::JID&, const gloox::JID&, const std::string&, const std::string&, const gloox::Error*) { puts(__func__); } void TalkManager::handleSubscribers(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::SubscriptionList&, const gloox::Error*) { puts(__func__); } void TalkManager::handleSubscribersResult(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::SubscriberList*, const gloox::Error*) { puts(__func__); } void TalkManager::handleAffiliates(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::AffiliateList*, const gloox::Error*) { puts(__func__); } void TalkManager::handleAffiliatesResult(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::AffiliateList*, const gloox::Error*) { puts(__func__); } void TalkManager::handleNodeConfig(const std::string&, const gloox::JID&, const std::string&, const gloox::DataForm*, const gloox::Error*) { puts(__func__); } void TalkManager::handleNodeConfigResult(const std::string&, const gloox::JID&, const std::string&, const gloox::Error*) { puts(__func__); } void TalkManager::handleNodeCreation(const std::string&, const gloox::JID&, const std::string&, const gloox::Error*) { puts(__func__); } void TalkManager::handleNodeDeletion(const std::string&, const gloox::JID&, const std::string&, const gloox::Error*) { puts(__func__); } void TalkManager::handleNodePurge(const std::string&, const gloox::JID&, const std::string&, const gloox::Error*) { puts(__func__); } void TalkManager::handleSubscriptions(const std::string&, const gloox::JID&, const gloox::PubSub::SubscriptionMap&, const gloox::Error*) { puts(__func__); } void TalkManager::handleAffiliations(const std::string&, const gloox::JID&, const gloox::PubSub::AffiliationMap&, const gloox::Error*) { puts(__func__); } void TalkManager::handleDefaultNodeConfig(const std::string&, const gloox::JID&, const gloox::DataForm*, const gloox::Error*) { puts(__func__); } void* TalkManager::IsExistingWindowToGroup(string group_room) { // check names for (auto i = fGroupMap.begin(); i != fGroupMap.end(); ++i) { if ((*i).second->GetGroupRoom() == group_room) { return (*i).first; } } // no matches return nullptr; } void TalkManager::RemoveWindow(TalkView* window) { for (GroupMap::iterator i = fGroupMap.begin(); i != fGroupMap.end(); ++i) { if ((*i).second == window) { delete (*i).first; fGroupMap.erase(i); SendNotices(kWindowList); return; } } for (TalkMap::iterator i = fTalkMap.begin(); i != fTalkMap.end(); ++i) { if ((*i).second == window) { fTalkMap.erase(i); SendNotices(kWindowList); return; } } } void TalkManager::Reset() { MessageRepeater::Instance()->PostMessage(JAB_CLOSE_TALKS); fTalkMap.clear(); fGroupMap.clear(); } void TalkManager::GetAvatar(const gloox::JID& jid, const std::string& hash) { // check the cache first in case we already have it BFile file(&fAvatarCache, hash.c_str(), B_READ_ONLY); if (file.InitCheck() == B_OK) { off_t size; file.GetSize(&size); char buffer[size]; file.Read(buffer, size); BMessage message(kAvatarUpdate); message.AddString("jid", jid.full().c_str()); message.AddData("avatar", B_RAW_TYPE, buffer, size); SendNotices(kAvatarUpdate, &message); } else { // Get it from PubSub JabberSpeak::Instance()->RequestPubSubItem(jid, "urn:xmpp:avatar:data", hash, this); } } void TalkManager::handleMUCParticipantPresence(gloox::MUCRoom *room, const gloox::MUCRoomParticipant participant, const gloox::Presence &presence) { BMessage msg; BString fullRoom; fullRoom.SetToFormat("%s@%s", room->name().c_str(), room->service().c_str()); msg.AddString("room", fullRoom); msg.AddString("server", room->service().c_str()); msg.AddString("username", participant.nick->resource().c_str()); msg.AddInt32("affiliation", participant.affiliation); if (presence.subtype() == gloox::Presence::Available) { msg.what = JAB_GROUP_CHATTER_ONLINE; } else if (presence.subtype() == gloox::Presence::Unavailable) { msg.what = JAB_GROUP_CHATTER_OFFLINE; } SendNotices(msg.what, &msg); } void TalkManager::handleMUCMessage(gloox::MUCRoom *room, const gloox::Message &msg, bool priv __attribute__((unused))) { TalkView *window = NULL; string group_username = msg.from().resource(); window = fGroupMap.at(room); // submit the chat if (window) { window->LockLooper(); if (group_username.empty()) { window->AddToTalk("System:", msg.body(), TalkView::OTHER); } else { bool highlight; // Highlight messages when they mention the nickname // TODO also use metadata in the message that may indicate an highlight if (BString(msg.body().c_str()).IFindFirst(room->nick().c_str()) != B_ERROR) { // NOTE: This will erronously pop up for backlog messages aswell, can be improved // once we have MAM BNotification notification(B_INFORMATION_NOTIFICATION); notification.SetGroup(room->name().c_str()); notification.SetTitle(group_username.c_str()); notification.SetContent(msg.body().c_str()); notification.Send(); BlabberMainWindow::Instance()->FlagBookmarkItem(msg.from().bare(), BookmarkItem::NICKNAME_HIGHLIGHT); highlight = true; } else { BlabberMainWindow::Instance()->FlagBookmarkItem(msg.from().bare(), BookmarkItem::ACTIVITY); highlight = false; } // TODO: compare with JID instead? if (group_username == window->GetGroupUsername()) window->AddToTalk(group_username, msg.body(), TalkView::LOCAL, highlight); else window->AddToTalk(group_username, msg.body(), TalkView::MAIN_RECIPIENT, highlight); } window->UnlockLooper(); } } bool TalkManager::handleMUCRoomCreation(gloox::MUCRoom *room __attribute__((unused))) { fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); return false; } void TalkManager::handleMUCSubject(gloox::MUCRoom *room, const std::string &nick, const std::string &subject) { TalkView* window = fGroupMap.at(room); BString topic; topic.SetToFormat("set topic to %s\n", subject.c_str()); // FIXME just send a BMessage to the view and let it handle this window->LockLooper(); window->SetStatus(subject); window->AddToTalk(nick, topic.String(), TalkView::OTHER); window->UnlockLooper(); } void TalkManager::handleMUCInviteDecline(gloox::MUCRoom *room __attribute__((unused)), const gloox::JID &invitee __attribute__((unused)), const std::string &reason __attribute__((unused))) { fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); } void TalkManager::handleMUCError(gloox::MUCRoom *room, gloox::StanzaError error) { fprintf(stderr, "%s(%s, %d)\n", __PRETTY_FUNCTION__, room->name().c_str(), error); } void TalkManager::handleMUCInfo(gloox::MUCRoom *room __attribute__((unused)), int features __attribute__((unused)), const std::string &name __attribute__((unused)), const gloox::DataForm *infoForm __attribute__((unused))) { fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); } void TalkManager::handleMUCItems(gloox::MUCRoom *room __attribute__((unused)), const gloox::Disco::ItemList &items __attribute__((unused))) { fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); }