559 lines
15 KiB
C++
559 lines
15 KiB
C++
//////////////////////////////////////////////////
|
|
// Blabber [TalkManager.cpp]
|
|
//////////////////////////////////////////////////
|
|
|
|
#include <gloox/base64.h>
|
|
#include <gloox/carbons.h>
|
|
#include <gloox/instantmucroom.h>
|
|
#include <gloox/pubsubevent.h>
|
|
#include <gloox/pubsubitem.h>
|
|
#include <gloox/rostermanager.h>
|
|
|
|
#include <cstdio>
|
|
#include <stdexcept>
|
|
|
|
#include <File.h>
|
|
#include <FindDirectory.h>
|
|
#include <Notification.h>
|
|
#include <Path.h>
|
|
#include <interface/Window.h>
|
|
|
|
#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<const gloox::Carbons>(
|
|
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<gloox::Message*>(
|
|
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::PubSub::Event>(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__);
|
|
}
|