(purple) Main loop, server→add-on→app communication

Now the purple add-on's starting to come together with a clear
structure:
	* Add-on sends IM_MESSAGES etc to the server for processing
	* Server sends all (reply/etc) messages to add-on, which sends
	  to app

It's worth noting that on the add-on's side, no looper or handler is
used for receiving messages, it's all through sending serialized
BMessages to the add-on's connect_thread buffer.

PurpleAccounts are now reliably associated with Cardie's account names
and the thread ID of their respective connect_thread.

The GLib main-loop is gone over regularly thanks to a message runner.

Now, the add-on can log into/create accounts, connect to them, and send
the IM_PROTOCOL_READY notification to Cardie as appropriate.
This commit is contained in:
Jaidyn Ann 2021-06-27 16:46:38 -05:00
parent 66df891577
commit 588b32b9c3
7 changed files with 217 additions and 42 deletions

24
protocols/purple/Purple.h Normal file
View File

@ -0,0 +1,24 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _PURPLE_H
#define _PURPLE_H
#define PURPLE_SIGNATURE "application/x-vnd.cardie.purple"
#endif // _PURPLE_H

View File

@ -25,6 +25,13 @@
#include <glib.h> #include <glib.h>
#include <libpurple/purple.h> #include <libpurple/purple.h>
#include <MessageRunner.h>
#include <ChatProtocolMessages.h>
#include "Purple.h"
#include "PurpleMessages.h"
int int
main(int arc, char** argv) main(int arc, char** argv)
@ -37,30 +44,32 @@ main(int arc, char** argv)
PurpleApp::PurpleApp() PurpleApp::PurpleApp()
: :
BApplication("application/x-vnd.cardie.purple") BApplication(PURPLE_SIGNATURE),
fGloop(g_main_loop_new(NULL, false))
{ {
if (init_libpurple() != B_OK) if (init_libpurple() != B_OK)
std::cerr << "libpurple initialization failed. Please report!\n"; std::cerr << "libpurple initialization failed. Please report!\n";
_GetProtocolsInfo(); _GetProtocolsInfo();
fGRunner = new BMessageRunner(this, new BMessage(G_MAIN_LOOP), 100000, -1);
} }
void void
PurpleApp::MessageReceived(BMessage* msg) PurpleApp::MessageReceived(BMessage* msg)
{ {
int64 thread_id;
switch (msg->what) switch (msg->what)
{ {
case PURPLE_REQUEST_PROTOCOL_COUNT: case PURPLE_REQUEST_PROTOCOL_COUNT:
{ {
int64 thread_id;
if (msg->FindInt64("thread_id", &thread_id) != B_OK) return; if (msg->FindInt64("thread_id", &thread_id) != B_OK) return;
send_data(thread_id, fProtocols.CountItems(), NULL, 0); send_data(thread_id, fProtocols.CountItems(), NULL, 0);
break; break;
} }
case PURPLE_REQUEST_PROTOCOL_INFO: case PURPLE_REQUEST_PROTOCOL_INFO:
{ {
int64 thread_id;
if (msg->FindInt64("thread_id", &thread_id) != B_OK) return; if (msg->FindInt64("thread_id", &thread_id) != B_OK) return;
int32 index = msg->FindInt32("index", 0); int32 index = msg->FindInt32("index", 0);
ProtocolInfo* info = fProtocols.ItemAt(index); ProtocolInfo* info = fProtocols.ItemAt(index);
@ -68,14 +77,8 @@ PurpleApp::MessageReceived(BMessage* msg)
BMessage protocolInfo = info->settingsTemplate; BMessage protocolInfo = info->settingsTemplate;
protocolInfo.AddString("name", info->name); protocolInfo.AddString("name", info->name);
protocolInfo.AddString("id", info->id); protocolInfo.AddString("id", info->id);
SendMessage(thread_id, protocolInfo);
// Send message to requester
ssize_t size = protocolInfo.FlattenedSize();
char buffer[size];
send_data(thread_id, size, NULL, 0);
protocolInfo.Flatten(buffer, size);
send_data(thread_id, 0, buffer, size);
break; break;
} }
case PURPLE_LOAD_ACCOUNT: case PURPLE_LOAD_ACCOUNT:
@ -83,12 +86,51 @@ PurpleApp::MessageReceived(BMessage* msg)
_ParseCardieSettings(msg); _ParseCardieSettings(msg);
break; break;
} }
case PURPLE_REGISTER_THREAD:
{
msg->PrintToStream();
BString accName = msg->FindString("account_name");
BString username = fAccounts.ValueFor(accName);
int64 thread;
if (username.IsEmpty() == true
|| msg->FindInt64("thread_id", &thread) != B_OK)
break;
fAccountThreads.AddItem(username, thread);
break;
}
case G_MAIN_LOOP:
g_main_context_iteration(g_main_loop_get_context(fGloop), false);
break;
default: default:
BApplication::MessageReceived(msg); BApplication::MessageReceived(msg);
} }
} }
void
PurpleApp::SendMessage(thread_id thread, BMessage msg)
{
ssize_t size = msg.FlattenedSize();
char buffer[size];
send_data(thread, size, NULL, 0);
msg.Flatten(buffer, size);
send_data(thread, 0, buffer, size);
}
void
PurpleApp::SendMessage(PurpleAccount* account, BMessage msg)
{
const char* username = purple_account_get_username(account);
thread_id thread = fAccountThreads.ValueFor(BString(username));
if (thread > 0)
SendMessage(thread, msg);
else
std::cerr << "Failed to send message: " << msg.what << std::endl;
}
void void
PurpleApp::_GetProtocolsInfo() PurpleApp::_GetProtocolsInfo()
{ {
@ -217,7 +259,7 @@ PurpleApp::_ParseCardieSettings(BMessage* settings)
{ {
PurplePlugin* plugin = _PluginFromMessage(settings); PurplePlugin* plugin = _PluginFromMessage(settings);
PurplePluginProtocolInfo* info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); PurplePluginProtocolInfo* info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
const char* protoId = settings->FindString("signature"); const char* protoId = settings->FindString("protocol");
if (plugin == NULL || info == NULL) if (plugin == NULL || info == NULL)
return; return;
@ -245,10 +287,11 @@ PurpleApp::_ParseCardieSettings(BMessage* settings)
PurpleAccount* account = purple_accounts_find(username.String(), protoId); PurpleAccount* account = purple_accounts_find(username.String(), protoId);
if (account == NULL) { if (account == NULL) {
account = purple_account_new(username.String(), protoId); account = purple_account_new(username.String(), protoId);
purple_account_set_password(account, password.String());
purple_accounts_add(account); purple_accounts_add(account);
} }
purple_account_set_password(account, password.String());
// Set all protocol settings // Set all protocol settings
GList* prefIter = info->protocol_options; GList* prefIter = info->protocol_options;
for (int i = 0; prefIter != NULL; prefIter = prefIter->next) for (int i = 0; prefIter != NULL; prefIter = prefIter->next)
@ -295,15 +338,24 @@ PurpleApp::_ParseCardieSettings(BMessage* settings)
value.String()); value.String());
} }
} }
fAccounts.AddItem(settings->FindString("account_name"), username); fAccounts.AddItem(settings->FindString("account_name"), username);
purple_account_set_enabled(account, PURPLE_UI_ID, true);
} }
PurplePlugin* PurplePlugin*
PurpleApp::_PluginFromMessage(BMessage* msg) PurpleApp::_PluginFromMessage(BMessage* msg)
{ {
return purple_plugins_find_with_id(msg->FindString("signature")); return purple_plugins_find_with_id(msg->FindString("protocol"));
}
PurpleAccount*
PurpleApp::_AccountFromMessage(BMessage* msg)
{
BString protocol = msg->FindString("protocol");
BString username = fAccounts.ValueFor(msg->FindString("account_name"));
return purple_accounts_find(username.String(), protocol.String());
} }
@ -324,14 +376,56 @@ static PurpleEventLoopUiOps _glib_eventloops =
status_t status_t
init_libpurple() init_libpurple()
{ {
purple_eventloop_set_ui_ops(&_glib_eventloops); init_ui_ops();
if (!purple_core_init("cardie")) if (!purple_core_init(PURPLE_UI_ID))
return B_ERROR; return B_ERROR;
purple_set_blist(purple_blist_new());
purple_blist_load();
init_signals();
return B_OK; return B_OK;
} }
void
init_ui_ops()
{
purple_eventloop_set_ui_ops(&_glib_eventloops);
}
void
init_signals()
{
int handle;
purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
PURPLE_CALLBACK(signal_signed_on), NULL);
purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
PURPLE_CALLBACK(signal_connection_error), NULL);
}
static void
signal_signed_on(PurpleConnection* gc)
{
BMessage readyMsg(IM_MESSAGE);
readyMsg.AddInt32("im_what", IM_PROTOCOL_READY);
PurpleApp* app = (PurpleApp*)be_app;
app->SendMessage(purple_connection_get_account(gc), readyMsg);
}
static void
signal_connection_error(PurpleConnection* gc, PurpleConnectionError err,
const gchar* desc)
{
std::cout << "Connection failed: " << (const char*)desc << std::endl;
}
static guint _purple_glib_input_add(gint fd, PurpleInputCondition condition, static guint _purple_glib_input_add(gint fd, PurpleInputCondition condition,
PurpleInputFunction function, gpointer data) PurpleInputFunction function, gpointer data)
{ {

View File

@ -29,14 +29,16 @@
#include <libsupport/KeyMap.h> #include <libsupport/KeyMap.h>
#include "PurpleMessages.h"
typedef KeyMap<BString, BString> Accounts; // Cardie username → Purple username
typedef KeyMap<BString, thread_id> AccountThreads; // Purple username → Thread
typedef KeyMap<BString, BString> Accounts; const uint32 G_MAIN_LOOP = 'GLml';
#define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR) #define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
#define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL) #define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
#define PURPLE_UI_ID "cardie"
typedef struct _PurpleGLibIOClosure { typedef struct _PurpleGLibIOClosure {
@ -59,6 +61,8 @@ class PurpleApp : public BApplication {
public: public:
PurpleApp(); PurpleApp();
virtual void MessageReceived(BMessage* msg); virtual void MessageReceived(BMessage* msg);
void SendMessage(thread_id thread, BMessage msg);
void SendMessage(PurpleAccount* account, BMessage msg);
private: private:
void _GetProtocolsInfo(); void _GetProtocolsInfo();
@ -68,18 +72,29 @@ private:
void _ParseCardieSettings(BMessage* settings); void _ParseCardieSettings(BMessage* settings);
PurplePlugin* _PluginFromMessage(BMessage* msg); PurplePlugin* _PluginFromMessage(BMessage* msg);
PurpleAccount* _AccountFromMessage(BMessage* msg);
Accounts fAccounts; Accounts fAccounts;
AccountThreads fAccountThreads;
BObjectList<ProtocolInfo> fProtocols; BObjectList<ProtocolInfo> fProtocols;
GMainLoop* fGloop;
BMessageRunner* fGRunner;
}; };
status_t init_libpurple(); status_t init_libpurple();
void init_ui_ops();
void init_signals();
// Connection signals
static void signal_signed_on(PurpleConnection* gc);
static void signal_connection_error(PurpleConnection* gc,
PurpleConnectionError err, const gchar* desc);
static guint _purple_glib_input_add(gint fd, PurpleInputCondition condition, static guint _purple_glib_input_add(gint fd, PurpleInputCondition condition,
PurpleInputFunction function, gpointer data); PurpleInputFunction function, gpointer data);
static gboolean _purple_glib_io_invoke(GIOChannel *source, static gboolean _purple_glib_io_invoke(GIOChannel *source,
GIOCondition condition, gpointer data); GIOCondition condition, gpointer data);
#endif // _PURPLE_APP_H #endif // _PURPLE_APP_H

View File

@ -44,8 +44,15 @@ enum purple_message {
* Just the account's settings message from Cardie's end. * Just the account's settings message from Cardie's end.
* It's the server's job to tie the Cardie account name * It's the server's job to tie the Cardie account name
* to the PurpleAccount. */ * to the PurpleAccount. */
PURPLE_LOAD_ACCOUNT = 'PAla' PURPLE_LOAD_ACCOUNT = 'PAla',
/*! Associate account with thread →Server
* Makes the server associate the given account with
* the given thread. All subsequent ServerAdd-On
* messages related to the account will be sent to this
* thread.
* Requires: String account_name, int64 thread_id */
PURPLE_REGISTER_THREAD = 'PArl'
}; };
#endif // _PURPLE_MESSAGES_H #endif // _PURPLE_MESSAGES_H

View File

@ -24,6 +24,7 @@
#include <ChatProtocolMessages.h> #include <ChatProtocolMessages.h>
#include "Purple.h"
#include "PurpleMessages.h" #include "PurpleMessages.h"
@ -40,18 +41,11 @@ protocol_at(int32 i)
msg->AddInt32("index", i); msg->AddInt32("index", i);
msgr->SendMessage(msg); msgr->SendMessage(msg);
thread_id sender; BMessage protoInfo = receive_message();
BString name = protoInfo.FindString("name");
BString id = protoInfo.FindString("id");
int32 size = receive_data(&sender, NULL, 0); return (ChatProtocol*)new PurpleProtocol(name, id, protoInfo);
char buffer[size];
receive_data(&sender, buffer, size);
BMessage temp;
temp.Unflatten(buffer);
BString name = temp.FindString("name");
BString id = temp.FindString("id");
return (ChatProtocol*)new PurpleProtocol(name, id, temp);
} }
@ -93,7 +87,7 @@ ensure_app_messenger()
{ {
if (kAppMessenger == NULL || kAppMessenger->IsValid() == false) { if (kAppMessenger == NULL || kAppMessenger->IsValid() == false) {
ensure_app(); ensure_app();
kAppMessenger = new BMessenger("application/x-vnd.cardie.purple"); kAppMessenger = new BMessenger(PURPLE_SIGNATURE);
} }
return kAppMessenger; return kAppMessenger;
} }
@ -103,7 +97,7 @@ void
ensure_app() ensure_app()
{ {
BRoster roster; BRoster roster;
if (roster.IsRunning("application/x-vnd.cardie.purple") == true) if (roster.IsRunning(PURPLE_SIGNATURE) == true)
return; return;
app_info aInfo; app_info aInfo;
@ -115,12 +109,32 @@ ensure_app()
entry_ref protoRef; entry_ref protoRef;
BEntry(protoPath.Path()).GetRef(&protoRef); BEntry(protoPath.Path()).GetRef(&protoRef);
roster.Launch(&protoRef); roster.Launch(&protoRef);
snooze(100000);
} }
status_t status_t
connect_thread(void* data) connect_thread(void* data)
{ {
PurpleProtocol* protocol = (PurpleProtocol*)data;
while (true) {
BMessage msg = receive_message();
protocol->SendMessage(new BMessage(msg));
}
}
BMessage
receive_message()
{
thread_id sender;
int32 size = receive_data(&sender, NULL, 0);
char buffer[size];
receive_data(&sender, buffer, size);
BMessage temp;
temp.Unflatten(buffer);
return temp;
} }
@ -159,16 +173,21 @@ status_t
PurpleProtocol::UpdateSettings(BMessage* msg) PurpleProtocol::UpdateSettings(BMessage* msg)
{ {
ensure_app(); ensure_app();
fPrplMessenger = new BMessenger("application/x-vnd.cardie.purple"); fPrplMessenger = new BMessenger(PURPLE_SIGNATURE);
msg->what = PURPLE_LOAD_ACCOUNT; msg->what = PURPLE_LOAD_ACCOUNT;
_SendPrplMessage(msg); _SendPrplMessage(msg);
// thread_id thread = spawn_thread(connect_thread, "connect_thread",
// B_NORMAL_PRIORITY, (void*)this);
// if (thread < B_OK) thread_id thread = spawn_thread(connect_thread, "bird_superiority",
// return B_ERROR; B_NORMAL_PRIORITY, (void*)this);
// resume_thread(thread); if (thread < B_OK)
return B_ERROR;
BMessage* account = new BMessage(PURPLE_REGISTER_THREAD);
account->AddInt64("thread_id", thread);
_SendPrplMessage(account);
resume_thread(thread);
return B_OK; return B_OK;
} }
@ -270,11 +289,21 @@ PurpleProtocol::MessengerInterface() const
} }
void
PurpleProtocol::SendMessage(BMessage* msg)
{
if (!msg)
return;
msg->AddString("protocol", fSignature);
fMessenger->SendMessage(msg);
}
void void
PurpleProtocol::_SendPrplMessage(BMessage* msg) PurpleProtocol::_SendPrplMessage(BMessage* msg)
{ {
msg->AddString("account_name", fName); msg->AddString("account_name", fName);
msg->AddString("signature", fSignature); msg->AddString("protocol", fSignature);
if (fPrplMessenger->IsValid()) if (fPrplMessenger->IsValid())
fPrplMessenger->SendMessage(msg); fPrplMessenger->SendMessage(msg);
} }

View File

@ -37,7 +37,7 @@ BMessenger* ensure_app_messenger();
void ensure_app(); void ensure_app();
status_t connect_thread(void* data); status_t connect_thread(void* data);
BMessage receive_message();
class PurpleProtocol : public ChatProtocol { class PurpleProtocol : public ChatProtocol {
public: public:
@ -74,8 +74,11 @@ public:
virtual ChatProtocolMessengerInterface* virtual ChatProtocolMessengerInterface*
MessengerInterface() const; MessengerInterface() const;
void SendMessage(BMessage* msg);
private: private:
void _SendPrplMessage(BMessage* msg); void _SendPrplMessage(BMessage* msg);
ChatProtocolMessengerInterface* fMessenger; ChatProtocolMessengerInterface* fMessenger;
BMessenger* fPrplMessenger; BMessenger* fPrplMessenger;
thread_id fServerThread; thread_id fServerThread;

View File

@ -1,3 +1,6 @@
#include "Purple.h"
resource app_signature PURPLE_SIGNATURE;
resource app_version { resource app_version {
major = 0, major = 0,