Chat-O-Matic/application/Conversation.cpp
Jaidyn Ann 5b5840a79e Explicitly tie Conversations, Contacts, and Users to their ProtocolLoopers
Previously, all Conversations/Contacts/Users were stored in the Server,
each in their respective KeyMaps, identified solely by their
identifiers. This leads to the glaring problem of overlap― if the user
has multiple accounts, some users/rooms might be used or present in multiple
accounts at the same time.

Now, each accounts' Contacts, Conversations, and Users are stored in
its ProtocolLooper, making this overlap impossible. An oversight of only
allowing one user identifier to be stored (fMySelf) in Server was also fixed
this way.

This is the bulk of the work required for multi-account support― now,
the user can join the same XMPP room on two seperate accounts, and it
works perfectly.
2021-06-10 15:16:43 -05:00

402 lines
6.8 KiB
C++

/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "Conversation.h"
#include <DateTimeFormat.h>
#include <Locale.h>
#include <StringList.h>
#include "CayaPreferences.h"
#include "CayaProtocolMessages.h"
#include "CayaRenderView.h"
#include "CayaUtils.h"
#include "ConversationItem.h"
#include "ConversationView.h"
#include "MainWindow.h"
#include "NotifyMessage.h"
#include "ProtocolLooper.h"
#include "ProtocolManager.h"
#include "Server.h"
#include "TheApp.h"
Conversation::Conversation(BString id, BMessenger msgn)
:
fID(id),
fName(id),
fMessenger(msgn),
fChatView(NULL),
fLooper(NULL),
fIcon(NULL),
fDateFormatter()
{
fConversationItem = new ConversationItem(fName.String(), this);
RegisterObserver(fConversationItem);
}
BString
Conversation::GetId() const
{
return fID;
}
void
Conversation::ImMessage(BMessage* msg)
{
int32 im_what = msg->FindInt32("im_what");
switch(im_what)
{
case IM_MESSAGE_RECEIVED:
{
_EnsureUser(msg);
_LogChatMessage(msg);
GetView()->MessageReceived(msg);
break;
}
case IM_MESSAGE_SENT:
{
_LogChatMessage(msg);
GetView()->MessageReceived(msg);
break;
}
case IM_SEND_MESSAGE:
{
fMessenger.SendMessage(msg);
break;
}
case IM_ROOM_PARTICIPANT_JOINED:
{
BString user_id;
if (msg->FindString("user_id", &user_id) != B_OK)
break;
if (UserById(user_id) == NULL) {
_EnsureUser(msg);
GetView()->MessageReceived(msg);
}
break;
}
case IM_ROOM_PARTICIPANT_LEFT:
case IM_ROOM_PARTICIPANT_KICKED:
case IM_ROOM_PARTICIPANT_BANNED:
{
BString user_id = msg->FindString("user_id");
User* user;
if (user_id.IsEmpty() == true || (user = UserById(user_id)) == NULL)
break;
GetView()->MessageReceived(msg);
RemoveUser(user);
break;
}
case IM_LOGS_RECEIVED:
default:
GetView()->MessageReceived(msg);
}
}
void
Conversation::ObserveString(int32 what, BString str)
{
GetView()->InvalidateUserList();
}
void
Conversation::ObserveInteger(int32 what, int32 value)
{
GetView()->InvalidateUserList();
}
void
Conversation::ObservePointer(int32 what, void* ptr)
{
GetView()->InvalidateUserList();
}
void
Conversation::SetNotifyName(const char* name)
{
if (BString(name) == fName)
return;
fName = name;
NotifyString(STR_ROOM_NAME, fName.String());
}
void
Conversation::SetNotifySubject(const char* subject)
{
if (BString(subject) == fSubject)
return;
fSubject = subject;
NotifyString(STR_ROOM_SUBJECT, fSubject.String());
}
BMessenger
Conversation::Messenger() const
{
return fMessenger;
}
void
Conversation::SetMessenger(BMessenger messenger)
{
fMessenger = messenger;
}
ProtocolLooper*
Conversation::GetProtocolLooper() const
{
return fLooper;
}
void
Conversation::SetProtocolLooper(ProtocolLooper* looper)
{
fLooper = looper;
}
BBitmap*
Conversation::ProtocolBitmap() const
{
CayaProtocol* protocol = fLooper->Protocol();
CayaProtocolAddOn* addOn
= ProtocolManager::Get()->ProtocolAddOn(protocol->Signature());
return addOn->ProtoIcon();
}
BBitmap*
Conversation::IconBitmap() const
{
return fIcon;
}
BString
Conversation::GetName() const
{
return fName;
}
ConversationView*
Conversation::GetView()
{
if (fChatView != NULL)
return fChatView;
fChatView = new ConversationView(this);
if (fLooper->Protocol()->SaveLogs() == false)
return fChatView;
BMessage logMsg;
if (_GetChatLogs(&logMsg) == B_OK)
fChatView->MessageReceived(&logMsg);
RegisterObserver(fChatView);
return fChatView;
}
void
Conversation::ShowView(bool typing, bool userAction)
{
((TheApp*)be_app)->GetMainWindow()->SetConversation(this);
}
ConversationItem*
Conversation::GetListItem()
{
return fConversationItem;
}
UserMap
Conversation::Users()
{
return fUsers;
}
User*
Conversation::UserById(BString id)
{
bool found = false;
return fUsers.ValueFor(id, &found);
}
void
Conversation::AddUser(User* user)
{
BMessage msg;
msg.AddString("user_id", user->GetId());
msg.AddString("user_name", user->GetName());
_EnsureUser(&msg);
}
void
Conversation::RemoveUser(User* user)
{
fUsers.RemoveItemFor(user->GetId());
user->UnregisterObserver(this);
GetView()->UpdateUserList(fUsers);
}
BString
Conversation::GetOwnId()
{
return fLooper->GetOwnId();
}
void
Conversation::SetRole(BString id, Role* role)
{
Role* oldRole = fRoles.ValueFor(id);
if (oldRole != NULL) {
fRoles.RemoveItemFor(id);
delete oldRole;
}
fRoles.AddItem(id, role);
}
Role*
Conversation::GetRole(BString id)
{
return fRoles.ValueFor(id);
}
void
Conversation::_LogChatMessage(BMessage* msg)
{
BString date;
fDateFormatter.Format(date, time(0), B_SHORT_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
BString id = msg->FindString("user_id");
BString body = msg->FindString("body");
// Binary logs
// TODO: Don't hardcode 21, expose maximum as a setting
BStringList users, bodies;
BMessage logMsg;
if (_GetChatLogs(&logMsg) == B_OK) {
logMsg.FindStrings("body", &bodies);
logMsg.FindStrings("user_id", &users);
bodies.Remove(21);
users.Remove(21);
bodies.Add(body, 0);
users.Add(id, 0);
}
BMessage newLogMsg(IM_MESSAGE);
newLogMsg.AddInt32("im_what", IM_LOGS_RECEIVED);
newLogMsg.AddStrings("body", bodies);
newLogMsg.AddStrings("user_id", users);
BFile logFile(fLogPath.Path(), B_READ_WRITE | B_OPEN_AT_END | B_CREATE_FILE);
WriteAttributeMessage(&logFile, "logs", &newLogMsg);
// Plain-text logs
BString uname;
if (id.IsEmpty() == false)
uname = UserById(id)->GetName();
else
uname = "You";
BString logLine("[");
logLine << date;
logLine << "] ";
logLine << uname;
logLine << ": ";
logLine << body;
logLine << "\n";
logFile.Write(logLine.String(), logLine.Length());
}
status_t
Conversation::_GetChatLogs(BMessage* msg)
{
_EnsureLogPath();
BFile logFile(fLogPath.Path(), B_READ_WRITE | B_CREATE_FILE);
return ReadAttributeMessage(&logFile, "logs", msg);
}
void
Conversation::_EnsureLogPath()
{
if (fLogPath.InitCheck() == B_OK)
return;
fLogPath.SetTo(CayaLogPath(fLooper->Protocol()->GetName()));
fLogPath.Append(fID);
}
User*
Conversation::_EnsureUser(BMessage* msg)
{
BString id = msg->FindString("user_id");
BString name = msg->FindString("user_name");
if (id.IsEmpty() == true) return NULL;
User* user = UserById(id);
User* serverUser = fLooper->UserById(id);
// Not here, but found in server
if (user == NULL && serverUser != NULL) {
fUsers.AddItem(id, serverUser);
user = serverUser;
GetView()->UpdateUserList(fUsers);
}
// Not anywhere; create user
else if (user == NULL) {
user = new User(id,
((TheApp*)be_app)->GetMainWindow()->GetServer()->Looper());
user->SetProtocolLooper(fLooper);
fLooper->AddUser(user);
fUsers.AddItem(id, user);
GetView()->UpdateUserList(fUsers);
if (name.IsEmpty() == false)
user->SetNotifyName(name);
}
user->RegisterObserver(this);
return user;
}