Add UCMD extension methods to plugin API.

The user command (UCMD) extension to the ADC protocol allows for sending
hub-specific commands to clients. These commands have a name (which
generally appears in a menu in the client) and a command string which
the client sends back to the hub when the command is selected. These
command strings can contain keyword substitutions for useful
information, such as the SID of another user that was selected in the
client.

This commit adds some support for sending user commands to the uhub
plugin API. It currently restricts the command string to containing main
chat messages (BMSG commands in ADC parlance).

A plugin_ucmd structure is added to store the details of a user command,
and four methods are added to the plugin_handle.hub structure:

* plugin->hub.ucmd_create(plugin, name, length) creates a new user
  command.
* plugin->hub.ucmd_add_chat(plugin, ucmd, message, me) adds a main chat
  message to the list of commands to be run when the user command is
  selected. The me flag allows IRC /me style messages.
* plugin->hub.ucmd_send(plugin, user, ucmd) sends the command to a user.
* plugin->hub.ucmd_free(plugin, ucmd) frees the memory taken by a user
  command.

The structure has a number of flags (categories, remove, separator,
constrained) which correspond to the flags in the UCMD specification.
The categories flag must be set prior to sending to one (or more, via
a logical OR) of the ucmd_category_* enumeration values defined in
plugin_api/types.h.

The PLUGIN_API_VERSION has been increased to 2 to mark the change.
This commit is contained in:
Blair Bonnett 2012-08-02 02:26:54 +12:00
parent b0aa690cb4
commit 66854bc204
7 changed files with 338 additions and 1 deletions

View File

@ -152,6 +152,7 @@ libuhub_SOURCES := \
src/core/plugincallback.c \ src/core/plugincallback.c \
src/core/plugininvoke.c \ src/core/plugininvoke.c \
src/core/pluginloader.c \ src/core/pluginloader.c \
src/core/pluginucmd.c \
src/network/backend.c \ src/network/backend.c \
src/network/connection.c \ src/network/connection.c \
src/network/epoll.c \ src/network/epoll.c \

View File

@ -198,6 +198,10 @@ void plugin_register_callback_functions(struct plugin_handle* handle)
handle->hub.set_name = cbfunc_set_hub_name; handle->hub.set_name = cbfunc_set_hub_name;
handle->hub.get_description = cbfunc_get_hub_description; handle->hub.get_description = cbfunc_get_hub_description;
handle->hub.set_description = cbfunc_set_hub_description; handle->hub.set_description = cbfunc_set_hub_description;
handle->hub.ucmd_create = cbfunc_ucmd_create;
handle->hub.ucmd_add_chat = cbfunc_ucmd_add_chat;
handle->hub.ucmd_send = cbfunc_ucmd_send;
handle->hub.ucmd_free = cbfunc_ucmd_free;
} }
void plugin_unregister_callback_functions(struct plugin_handle* handle) void plugin_unregister_callback_functions(struct plugin_handle* handle)

223
src/core/pluginucmd.c Normal file
View File

@ -0,0 +1,223 @@
/*
* uhub - A tiny ADC p2p connection hub
* Copyright (C) 2007-2011, Jan Vidar Krey
* Copyright (C) 2012, Blair Bonnett
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "uhub.h"
/* Internal helper function - expand the ucmd->tt member to accomodate the
* given number of byes size. Copies and then frees any existing data. All
* unused bytes will be set to zero. Returns 1 on success, 0 if the memory
* could not be allocated.
*
* If size is less than the existing capacity, no change is made.
*
* NB. one extra byte is always allocated to act as the end-of-string
* terminator, i.e., the size can be the length of the string ignoring the
* terminator. Since unused bytes are set to zero, there should always be a
* terminator.
*/
int ucmd_expand_tt(struct plugin_ucmd* ucmd, size_t size)
{
if(size < ucmd->capacity) return 1;
/* Try to allocate the space. NB we add one to the space to enforce a null
* byte. */
char* newtt = (char*)malloc(size+1);
if(newtt == NULL) return 0;
/* Empty the contents. */
memset(newtt, 0, size+1);
/* Copy any existing data. */
if(ucmd->tt != NULL)
{
memcpy(newtt, ucmd->tt, ucmd->length);
free(ucmd->tt);
}
/* Update the structure. */
ucmd->tt = newtt;
ucmd->capacity = size;
return 1;
}
struct plugin_ucmd* cbfunc_ucmd_create(struct plugin_handle* plugin, const char* name, size_t length){
/* Need a name. */
if(name == NULL)
{
plugin->error_msg = "Each user command needs a name.";
return NULL;
}
/* Allocate space for the command structure. */
struct plugin_ucmd* cmd = (struct plugin_ucmd*)malloc(sizeof(struct plugin_ucmd));
if(cmd == NULL)
{
plugin->error_msg = "Not enough memory to create user command.";
return NULL;
}
/* Store the name and initialise the flags. */
cmd->categories = 0;
cmd->remove = 0;
cmd->separator = 0;
cmd->constrained = 0;
cmd->name = adc_msg_escape(name);
cmd->namelen = strlen(cmd->name);
cmd->tt = NULL;
cmd->length = 0;
cmd->capacity = 0;
/* Allocate space for the command data. 18 bytes is the overhead for a chat
* message so we need to add space for this. */
length = ((length < 0) ? 0 : length) + 18;
int result = ucmd_expand_tt(cmd, length);
if(result == 0)
{
plugin->error_msg = "Not enough memory to store user command data.";
cbfunc_ucmd_free(plugin, cmd);
return NULL;
}
/* Done. */
return cmd;
}
int cbfunc_ucmd_add_chat(struct plugin_handle* plugin, struct plugin_ucmd* ucmd, const char* message, int me)
{
/* Double-escape the message - once for when the client sends it back, and
* then again to insert it into the user command message we send to them. */
char* temp = adc_msg_escape(message);
char* escmsg = adc_msg_escape(temp);
free(temp);
size_t msglen = strlen(escmsg);
/* Format of a chat message: "BMSG\s%[mySID]\s<double-escaped message>\n".
* Format of a 'me' chat message: "BMSG\s%[mySID]\s<double-escaped message>\sME1\n". */
size_t required = 18 + msglen + (me ? 5 : 0);
if(required > (ucmd->capacity - ucmd->length))
{
if(ucmd_expand_tt(ucmd, ucmd->capacity + required) == 0)
{
plugin->error_msg = "Could not expand memory to store chat message.";
free(escmsg);
return 0;
}
}
/* Add in the chat command and placeholder for the client SID. */
strncpy(ucmd->tt + ucmd->length, "BMSG\\s%[mySID]\\s", 16);
ucmd->length += 16;
/* Copy the message. */
strncpy(ucmd->tt + ucmd->length, escmsg, msglen);
ucmd->length += msglen;
free(escmsg);
/* If it is a 'me' message, add the flag. */
if(me)
{
strncpy(ucmd->tt + ucmd->length, "\\sME1", 5);
ucmd->length += 5;
}
/* Add the (escaped) line break. */
ucmd->tt[ucmd->length++] = '\\';
ucmd->tt[ucmd->length++] = 'n';
/* Done. */
return 1;
}
int cbfunc_ucmd_send(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_ucmd* ucmd)
{
/* Make sure we have a command. */
if(!ucmd->length && !(ucmd->separator || ucmd->remove))
{
plugin->error_msg = "Cannot send without adding a message.";
return 0;
}
/* Make sure the category is valid. */
if(!ucmd->remove)
{
if(ucmd->categories < 1 || ucmd->categories > 15)
{
plugin->error_msg = "Need a valid category to send.";
return 0;
}
}
/* Calculate the size needed for the message. */
size_t length = ucmd->namelen;
if(ucmd->remove || ucmd->separator) length += 4;
else
{
length += 10 + ucmd->length; /* "_TT<arg>\n CTnn" = 10 extra chars. */
if(ucmd->constrained) length += 4;
}
/* Create the message structure. */
struct adc_message* message = adc_msg_construct(ADC_CMD_ICMD, length);
if(message == NULL)
{
plugin->error_msg = "Cannot allocate space for ADC message.";
return 0;
}
/* Always have the name. */
adc_msg_add_argument(message, ucmd->name);
/* Remove / separator. */
if(ucmd->remove) adc_msg_add_argument(message, "RM1");
if(ucmd->separator)
{
adc_msg_add_argument(message, "SP1");
adc_msg_add_named_argument_int(message, "CT", ucmd->categories);
}
/* Add in the message. */
else
{
adc_msg_add_named_argument(message, "TT", ucmd->tt);
if(ucmd->constrained) adc_msg_add_argument(message, "CO1");
adc_msg_add_named_argument_int(message, "CT", ucmd->categories);
}
/* Send it. */
route_to_user(plugin_get_hub(plugin), (struct hub_user*)user, message);
/* Success. */
adc_msg_free(message);
return 1;
}
void cbfunc_ucmd_free(struct plugin_handle* plugin, struct plugin_ucmd* ucmd){
if(ucmd->name != NULL)
{
free(ucmd->name);
ucmd->name = NULL;
}
if(ucmd->tt != NULL)
{
free(ucmd->tt);
ucmd->tt = NULL;
}
free(ucmd);
}

47
src/core/pluginucmd.h Normal file
View File

@ -0,0 +1,47 @@
/*
* uhub - A tiny ADC p2p connection hub
* Copyright (C) 2007-2011, Jan Vidar Krey
* Copyright (C) 2012, Blair Bonnett
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef HAVE_UHUB_PLUGIN_UCMD_H
#define HAVE_UHUB_PLUGIN_UCMD_H
/* Create a new user command object.
* Name: name of the entry in the clients user command menu - must be unique
* within a hub.
* Length: approximate length of message to be sent. Other functions can
* increase memory allocation as needed but having a sufficient size
* now removes the time taken for this resizing. Allow a factor of ~1/3
* for the neccesary escaping.
*/
extern struct plugin_ucmd* cbfunc_ucmd_create(struct plugin_handle* plugin, const char* name, size_t length);
/* Add a message to be sent in the main chat window when the user command is
* clicked.
* Me: If true, makes the message displayed in the same style as a '/me'
* message in IRC (and most hub clients).
*/
extern int cbfunc_ucmd_add_chat(struct plugin_handle* plugin, struct plugin_ucmd* ucmd, const char* message, int me);
/* Send the user command to a user. */
extern int cbfunc_ucmd_send(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_ucmd* ucmd);
/* Free the space used by a user command object. */
extern void cbfunc_ucmd_free(struct plugin_handle* plugin, struct plugin_ucmd* command);
#endif /* HAVE_UHUB_PLUGIN_UCMD_H */

View File

@ -123,6 +123,11 @@ typedef void (*hfunc_set_hub_name)(struct plugin_handle*, const char*);
typedef char* (*hfunc_get_hub_description)(struct plugin_handle*); typedef char* (*hfunc_get_hub_description)(struct plugin_handle*);
typedef void (*hfunc_set_hub_description)(struct plugin_handle*, const char*); typedef void (*hfunc_set_hub_description)(struct plugin_handle*, const char*);
typedef struct plugin_ucmd* (*hfunc_ucmd_create)(struct plugin_handle*, const char*, size_t);
typedef int (*hfunc_ucmd_add_chat)(struct plugin_handle*, struct plugin_ucmd*, const char*, int);
typedef int (*hfunc_ucmd_send)(struct plugin_handle*, struct plugin_user*, struct plugin_ucmd*);
typedef void (*hfunc_ucmd_free)(struct plugin_handle*, struct plugin_ucmd*);
/** /**
* These are functions created and initialized by the hub and which can be used * These are functions created and initialized by the hub and which can be used
* by plugins to access functionality internal to the hub. * by plugins to access functionality internal to the hub.
@ -140,6 +145,10 @@ struct plugin_hub_funcs
hfunc_set_hub_name set_name; hfunc_set_hub_name set_name;
hfunc_get_hub_description get_description; hfunc_get_hub_description get_description;
hfunc_set_hub_description set_description; hfunc_set_hub_description set_description;
hfunc_ucmd_create ucmd_create;
hfunc_ucmd_add_chat ucmd_add_chat;
hfunc_ucmd_send ucmd_send;
hfunc_ucmd_free ucmd_free;
}; };
struct plugin_handle struct plugin_handle

View File

@ -20,7 +20,7 @@
#ifndef HAVE_UHUB_PLUGIN_TYPES_H #ifndef HAVE_UHUB_PLUGIN_TYPES_H
#define HAVE_UHUB_PLUGIN_TYPES_H #define HAVE_UHUB_PLUGIN_TYPES_H
#define PLUGIN_API_VERSION 1 #define PLUGIN_API_VERSION 2
#ifndef MAX_NICK_LEN #ifndef MAX_NICK_LEN
#define MAX_NICK_LEN 64 #define MAX_NICK_LEN 64
@ -103,5 +103,57 @@ enum plugin_command_arg_type
plugin_cmd_arg_type_credentials, plugin_cmd_arg_type_credentials,
}; };
/* Specifies which categories a command appears in. To make a command appear in
* multiple categories, simply OR the appropriate values together. */
enum plugin_ucmd_categories
{
/* Appears in the main hub chat window. */
ucmd_category_hub = 1,
/* Appears in the hub user list. */
ucmd_category_user = 2,
/* Appears in the search results. */
ucmd_category_search = 4,
/* Appears in the file list. */
ucmd_category_file = 8,
/* Special case: appear everywhere. */
ucmd_category_all = 15,
};
/* Holds information about a user command. Note that a unique name is required
* for every command, even if it is only a separator.
*
* You should not create one of these yourself but instead use the
* plugin_handle->hub.ucmd_create() function.
*
* Similarly, you should only manually modify the first four entries below, and
* use the plugin_handle->hub.ucmd_xxxx() functions to make any other changes.
* */
struct plugin_ucmd
{
/* Which categories the command appears in. */
enum plugin_ucmd_categories categories;
/* If true, removes an existing command rather than adding a new one. */
int remove;
/* If true, adds a separator to the user command menu rather than an actual command. */
int separator;
/* Sometimes a command can be sent on multiple users (e.g., in search
* results). If this field is true, the command is limited to run once per
* CID. */
int constrained;
/* Plugins must not modify the following fields. */
char *name;
size_t namelen;
char *tt;
size_t length;
size_t capacity;
};
#endif /* HAVE_UHUB_PLUGIN_TYPES_H */ #endif /* HAVE_UHUB_PLUGIN_TYPES_H */

View File

@ -95,6 +95,7 @@ extern "C" {
#include "core/plugincallback.h" #include "core/plugincallback.h"
#include "core/plugininvoke.h" #include "core/plugininvoke.h"
#include "core/pluginloader.h" #include "core/pluginloader.h"
#include "core/pluginucmd.h"