Redesign add-on disconnection

Currently, add-ons are disconnected when ChatProtocol::Shutdown() is
called, which the add-on can do by itself― but there is no standard way
for add-ons to notify the app about their Shutdown. Because of this,
they tend to not call Shutdown()― instead (as in the case of the Jabber
add-on), they display a BAlert (IM_ERROR) notifying the user of the
connection error, but the account is considered active by Cardie (and
its threads are still existant, including its ProtocolLooper).

Zombies are bad, so this is redesigned somewhat with this commit:
Protocols should no longer call ChatProtocol::Shutdown() themselves,
they must send an IM_MESSAGE of IM_PROTOCOL_DISABLE to the app.

This will delete its ProtocolLooper, which in turn will send a
notification to the user and delete the ChatProtocol, and so
calling ChatProtocol::Shutdown().

In the included protocols, an IM_ERROR is sent right before
IM_PROTOCOL_DISABLE is sent if due to a connection error. This is not
required, but it is courteous to inform your user about the "why." :)
This commit is contained in:
Jaidyn Ann 2021-07-18 17:52:36 -05:00
parent 6dcb6f4405
commit 4905dbbe6c
12 changed files with 97 additions and 50 deletions

View File

@ -48,7 +48,7 @@ const uint32 APP_MOVE_UP = 'CYmu';
const uint32 APP_MOVE_DOWN = 'CYmd'; const uint32 APP_MOVE_DOWN = 'CYmd';
//! Disable a given account //! Disable a given account
const uint32 APP_DISABLE_ACCOUNT = 'CYda'; const uint32 APP_ACCOUNT_DISABLED = 'CYda';
//! Request a "help" message //! Request a "help" message
const uint32 APP_REQUEST_HELP = 'CYhm'; const uint32 APP_REQUEST_HELP = 'CYhm';

View File

@ -412,12 +412,13 @@ enum im_what_code {
Should be sent after connection is established, initialization done */ Should be sent after connection is established, initialization done */
IM_PROTOCOL_READY = 1002, IM_PROTOCOL_READY = 1002,
/*! Account has just been disabled →App /*! Deletion of protocol requested →App
Sent as the account disconnects, and will not be automatically This requests that the app delete the ChatProtocol and its
reconnected on the add-on's side. ProtocolLooper so invoking ChatProtocol::Shutdown().
Should be sent in two cases: When connection fails due to an error, This should be sent by the protocol after connection errors or a
or when account has been closed by user (ChatProtocol::Shutdown()) */ disconnect, when the addon doesn't have anything left to do (other
IM_PROTOCOL_DISABLED = 1003 than yearn for the sweet hand of death). */
IM_PROTOCOL_DISABLE = 1003
}; };
#endif // _CHAT_PROTOCOL_MESSAGES_H #endif // _CHAT_PROTOCOL_MESSAGES_H

View File

@ -12,9 +12,11 @@
#include "ProtocolLooper.h" #include "ProtocolLooper.h"
#include <Bitmap.h>
#include <String.h> #include <String.h>
#include "Account.h" #include "Account.h"
#include "AppMessages.h"
#include "Conversation.h" #include "Conversation.h"
#include "ConversationAccountItem.h" #include "ConversationAccountItem.h"
@ -42,6 +44,13 @@ ProtocolLooper::ProtocolLooper(ChatProtocol* protocol, int64 instance)
ProtocolLooper::~ProtocolLooper() ProtocolLooper::~ProtocolLooper()
{ {
BMessage* msg = new BMessage(APP_ACCOUNT_DISABLED);
BBitmap* icon = fProtocol->Icon();
icon->Archive(msg);
msg->AddString("name", fProtocol->GetName());
fProtocol->MessengerInterface()->SendMessage(msg);
fProtocol->Shutdown(); fProtocol->Shutdown();
delete fProtocol; delete fProtocol;
} }

View File

@ -106,6 +106,12 @@ Server::Filter(BMessage* message, BHandler **target)
filter_result result = B_DISPATCH_MESSAGE; filter_result result = B_DISPATCH_MESSAGE;
switch (message->what) { switch (message->what) {
case IM_MESSAGE:
result = ImMessage(message);
break;
case IM_ERROR:
ImError(message);
break;
case APP_CLOSE_CHAT_WINDOW: case APP_CLOSE_CHAT_WINDOW:
{ {
BString id = message->FindString("chat_id"); BString id = message->FindString("chat_id");
@ -116,12 +122,24 @@ Server::Filter(BMessage* message, BHandler **target)
result = B_SKIP_MESSAGE; result = B_SKIP_MESSAGE;
break; break;
} }
case IM_MESSAGE: case APP_ACCOUNT_DISABLED:
result = ImMessage(message); {
break; BString name;
case IM_ERROR: int64 instance;
ImError(message); if (message->FindInt64("instance", &instance) != B_OK)
return result;
// "Whoops" notification
if (AppPreferences::Item()->NotifyProtocolStatus == true
&& message->FindString("name", &name) == B_OK) {
BBitmap* icon = new BBitmap(message);
BString content("%user% has been disabled!");
content.ReplaceAll("%user%", name);
_SendNotification("Disabled", content, icon);
}
break; break;
}
case APP_REPLICANT_MESSENGER: case APP_REPLICANT_MESSENGER:
{ {
BMessenger* messenger = new BMessenger(); BMessenger* messenger = new BMessenger();
@ -138,18 +156,6 @@ Server::Filter(BMessage* message, BHandler **target)
accountManager->ReplicantStatusNotify(accountManager->Status()); accountManager->ReplicantStatusNotify(accountManager->Status());
break; break;
} }
case APP_DISABLE_ACCOUNT:
{
int64 instance = 0;
if (message->FindInt64("instance", &instance) != B_OK) {
result = B_SKIP_MESSAGE;
break;
}
RemoveProtocolLooper(instance);
break;
}
case APP_USER_INFO: case APP_USER_INFO:
{ {
User* user = _EnsureUser(message); User* user = _EnsureUser(message);
@ -159,7 +165,6 @@ Server::Filter(BMessage* message, BHandler **target)
} }
break; break;
} }
case APP_REQUEST_HELP: case APP_REQUEST_HELP:
{ {
BString body; BString body;
@ -200,12 +205,10 @@ Server::Filter(BMessage* message, BHandler **target)
chat->ImMessage(help); chat->ImMessage(help);
break; break;
} }
default: default:
// Dispatch not handled messages to main window // Dispatch not handled messages to main window
break; break;
} }
return result; return result;
} }
@ -610,12 +613,14 @@ Server::ImMessage(BMessage* msg)
} }
break; break;
} }
case IM_PROTOCOL_DISABLED: case IM_PROTOCOL_DISABLE:
{ {
// "Whoops" notification int64 instance = 0;
if (AppPreferences::Item()->NotifyProtocolStatus == true) if (msg->FindInt64("instance", &instance) != B_OK) {
_ProtocolNotification(_LooperFromMessage(msg), result = B_SKIP_MESSAGE;
BString("Disabled"), BString("%user% has been disabled!")); break;
}
RemoveProtocolLooper(instance);
break; break;
} }
default: default:
@ -983,12 +988,20 @@ Server::_ProtocolNotification(ProtocolLooper* looper, BString title,
if (looper == NULL || title.IsEmpty() == true) return; if (looper == NULL || title.IsEmpty() == true) return;
title.ReplaceAll("%user%", looper->Protocol()->GetName()); title.ReplaceAll("%user%", looper->Protocol()->GetName());
desc.ReplaceAll("%user%", looper->Protocol()->GetName()); desc.ReplaceAll("%user%", looper->Protocol()->GetName());
_SendNotification(title, desc, looper->Protocol()->Icon(), type);
}
void
Server::_SendNotification(BString title, BString content, BBitmap* icon,
notification_type type)
{
BNotification notification(type); BNotification notification(type);
notification.SetGroup(BString(APP_NAME)); notification.SetGroup(BString(APP_NAME));
notification.SetTitle(title); notification.SetTitle(title);
if (desc.IsEmpty() == false) if (content.IsEmpty() == false)
notification.SetContent(desc); notification.SetContent(content);
notification.SetIcon(looper->Protocol()->Icon()); if (icon != NULL)
notification.SetIcon(icon);
notification.Send(); notification.Send();
} }

View File

@ -80,6 +80,9 @@ private:
void _ProtocolNotification(ProtocolLooper* looper, void _ProtocolNotification(ProtocolLooper* looper,
BString title, BString desc, BString title, BString desc,
notification_type type=B_INFORMATION_NOTIFICATION); notification_type type=B_INFORMATION_NOTIFICATION);
void _SendNotification(BString title, BString content,
BBitmap* icon=NULL,
notification_type type=B_INFORMATION_NOTIFICATION);
void _ReplicantStatusNotify(UserStatus status); void _ReplicantStatusNotify(UserStatus status);

View File

@ -18,8 +18,8 @@
#include "AccountDialog.h" #include "AccountDialog.h"
#include "AccountListItem.h" #include "AccountListItem.h"
#include "AppMessages.h"
#include "ChatProtocol.h" #include "ChatProtocol.h"
#include "ChatProtocolMessages.h"
#include "PreferencesAccounts.h" #include "PreferencesAccounts.h"
#include "ProtocolManager.h" #include "ProtocolManager.h"
#include "ProtocolSettings.h" #include "ProtocolSettings.h"
@ -197,7 +197,8 @@ PreferencesAccounts::MessageReceived(BMessage* msg)
if (found == false) if (found == false)
return; return;
BMessage* remove = new BMessage(APP_DISABLE_ACCOUNT); BMessage* remove = new BMessage(IM_MESSAGE);
remove->AddInt32("im_what", IM_PROTOCOL_DISABLE);
remove->AddInt64("instance", instance); remove->AddInt64("instance", instance);
((TheApp*)be_app)->GetMainWindow()->PostMessage(remove); ((TheApp*)be_app)->GetMainWindow()->PostMessage(remove);

View File

@ -205,7 +205,7 @@ MainWindow::MessageReceived(BMessage* message)
} }
break; break;
} }
case APP_DISABLE_ACCOUNT: case APP_ACCOUNT_DISABLED:
_ToggleMenuItems(); _ToggleMenuItems();
break; break;
case IM_MESSAGE: case IM_MESSAGE:

View File

@ -58,14 +58,21 @@ connect_thread(void* data)
if (irc_connect(session, joinServer.String(), port, password, nick, ident, if (irc_connect(session, joinServer.String(), port, password, nick, ident,
real_name)) real_name))
{ {
printf("Could not connect: %s\n", irc_strerror (irc_errno(session))); BMessage error(IM_ERROR);
error.AddString("error", "Could not connect");
error.AddString("details", irc_strerror(irc_errno(session)));
_SendMessage(&error);
_SendMessage(new BMessage(IM_PROTOCOL_DISABLE));
return B_ERROR; return B_ERROR;
} }
// Start network loop // Start network loop
if (irc_run(session)) { if (irc_run(session)) {
printf("Could not connect or I/O error: %s\n", irc_strerror BMessage error(IM_ERROR);
(irc_errno(session))); error.AddString("error", "Connection or I/O error");
error.AddString("details", irc_strerror(irc_errno(session)));
_SendMessage(&error);
_SendMessage(new BMessage(IM_PROTOCOL_DISABLE));
return B_ERROR; return B_ERROR;
} }
return B_OK; return B_OK;

View File

@ -115,7 +115,7 @@ PurpleApp::MessageReceived(BMessage* msg)
SendMessage(thread, _GetCommands(_AccountFromMessage(msg))); SendMessage(thread, _GetCommands(_AccountFromMessage(msg)));
break; break;
} }
case PURPLE_REQUEST_DISCONNECT: case PURPLE_DISCONNECT_ACCOUNT:
{ {
PurpleAccount* account = _AccountFromMessage(msg); PurpleAccount* account = _AccountFromMessage(msg);
if (account == NULL) if (account == NULL)
@ -1034,8 +1034,7 @@ signal_account_signed_off(PurpleAccount* account)
static void static void
signal_account_disabled(PurpleAccount* account) signal_account_disabled(PurpleAccount* account)
{ {
BMessage disabled(IM_MESSAGE); BMessage disabled(PURPLE_SHUTDOWN_ADDON);
disabled.AddInt32("im_what", IM_PROTOCOL_DISABLED);
((PurpleApp*)be_app)->SendMessage(account, disabled); ((PurpleApp*)be_app)->SendMessage(account, disabled);
} }

View File

@ -56,7 +56,13 @@ enum purple_message {
/*! Disconnect add-on's account →Server /*! Disconnect add-on's account →Server
Requires: String account_name */ Requires: String account_name */
PURPLE_REQUEST_DISCONNECT = 'Axwx', PURPLE_DISCONNECT_ACCOUNT = 'Axwx',
/*! Shutdown an add-on instance →Protocol
Send from server after an account is disabled
or errors out; sent so an add-on instance
won't run for an unconnected account. */
PURPLE_SHUTDOWN_ADDON = 'Pxwx',
/*! Register chat commands with proto →Protocol */ /*! Register chat commands with proto →Protocol */
PURPLE_REGISTER_COMMANDS = 'Scmd', PURPLE_REGISTER_COMMANDS = 'Scmd',

View File

@ -19,8 +19,6 @@
#include "PurpleProtocol.h" #include "PurpleProtocol.h"
#include <iostream>
#include <Application.h> #include <Application.h>
#include <Resources.h> #include <Resources.h>
#include <Roster.h> #include <Roster.h>
@ -124,6 +122,13 @@ connect_thread(void* data)
while (true) { while (true) {
BMessage* msg = new BMessage(receive_message()); BMessage* msg = new BMessage(receive_message());
switch (msg->what) { switch (msg->what) {
case PURPLE_SHUTDOWN_ADDON: {
protocol->Shutdown();
BMessage* disabled = new BMessage(IM_MESSAGE);
disabled->AddInt32("im_what", IM_PROTOCOL_DISABLE);
protocol->SendMessage(disabled);
break;
}
case PURPLE_REGISTER_COMMANDS: case PURPLE_REGISTER_COMMANDS:
protocol->Process(msg); protocol->Process(msg);
break; break;
@ -186,7 +191,7 @@ PurpleProtocol::Init(ChatProtocolMessengerInterface* interface)
status_t status_t
PurpleProtocol::Shutdown() PurpleProtocol::Shutdown()
{ {
BMessage* disconnect = new BMessage(PURPLE_REQUEST_DISCONNECT); BMessage* disconnect = new BMessage(PURPLE_DISCONNECT_ACCOUNT);
_SendPrplMessage(disconnect); _SendPrplMessage(disconnect);
kill_thread(fBirdThread); kill_thread(fBirdThread);

View File

@ -693,9 +693,12 @@ JabberHandler::HandleConnectionError(gloox::ConnectionError& e)
} }
break; break;
} }
case gloox::ConnNotConnected: case gloox::ConnNotConnected: {
errMsg.AddString("error", "There is no active connection."); BMessage disconnect(IM_MESSAGE);
disconnect.AddInt32("im_what", IM_PROTOCOL_DISABLE);
_SendMessage(&disconnect);
break; break;
}
default: default:
break; break;
} }