-
Notifications
You must be signed in to change notification settings - Fork 60
InfinotedPluginDevelopment
It is possible to develop third-party plugins for infinoted. Infinoted plugins need to be written in C or C++. A infinoted plugin basically consists of a shared object file (.so
on Linux or .dll
on Windows) with a specified entry point. Infinoted picks up all plugins in $PREFIX/lib/infinoted-0.6/plugins/
, where $PREFIX
is the prefix that infinoted has been installed in, typically /usr
or /usr/local
. Custom plugins therefore need to be installed in this directory.
Infinoted plugins need to link against libinfinoted-plugin-manager-0.6
, which ships with libinfinity. This is a small library which provides access to the state of the infinoted server to plugins.
Every plugin needs to export a symbol called INFINOTED_PLUGIN
, of type [InfinotedPlugin](http://infinote.0x539.de/libinfinity/API/libinfinoted-plugin-manager/InfinotedPluginManager.html#InfinotedPlugin)
. The InfinotedPlugin
is declared in infinoted/infinoted-plugin-manager.h
, and contains basic information about the plugin, such as its name and its entry point. Below is how the structure looks like, and a description for each field.
struct _InfinotedPlugin {
const gchar* name;
const gchar* description;
const InfinotedParameterInfo* options;
gsize info_size;
gsize connection_info_size;
gsize session_info_size;
const gchar* session_type;
void(*on_info_initialize)(gpointer plugin_info);
gboolean(*on_initialize)(InfinotedPluginManager* manager,
gpointer plugin_info,
GError** error);
void(*on_deinitialize)(gpointer plugin_info);
void(*on_connection_added)(InfXmlConnection* connection,
gpointer plugin_info,
gpointer connection_info);
void(*on_connection_removed)(InfXmlConnection* connection,
gpointer plugin_info,
gpointer connection_info);
void(*on_session_added)(const InfBrowserIter* iter,
InfSessionProxy* proxy,
gpointer plugin_info,
gpointer session_info);
void(*on_session_removed)(const InfBrowserIter* iter,
InfSessionProxy* proxy,
gpointer plugin_info,
gpointer session_info);
};
The meaning of these fields in detail:
-
name: This is the name of the plugin. When the shared object file is called
libinfinoted-plugin-X.so
, then this should be set toX
. - description: A human-readable description of what the plugin does.
- options: A 0-terminated list of input options. See below for a detailed description of how options are passed to plugins.
- info_size: The size, in bytes, that should be allocated for the plugin when it is instantiated. This is typically given by the structure where plugin-specific data is stored. This should be greater than 0.
- connection_info_size: For each client connection, this many bytes are allocated and made available to the plugin to store connection-specific data. This is allowed to be 0 if the plugin does not have any connection-specific data.
- session_info_size: For each active session on the server, this many bytes are allocated and made available to the plugin to store session-specific data. This is allowed to be 0 if the plugin does not have any connection-specific data.
-
session_type: If this is
NULL
, a session_info structure is created for every session on the server, no matter its type. If it is non-%NULL, a structure is only created for sessions of the given type. This can be used for example by plugins which only operate on text documents and not on chat sessions. In this case, this should be set to"InfTextSession"
. - on_info_initialize: This function is called after the plugin structure has been instantiated. It should initialize the structure to sane default values.
-
on_initialize: This is the primary initialization function for the plugin. This is called after the options for the plugin have been parsed, i.e. it can do all initialization based on the options provided to the plugin. It can also return
FALSE
and set theerror
parameter, in which case the server is being shutdown and an error is shown to the user. This can be used for example if some resources that the plugin needs are not available, or if the provided options are not valid. -
on_deinitialize: This is called when the plugin is unloaded, and should free all resources allocated to the plugin. If
on_initialize
fails with an error, this function is also called to clean up the partly-initialized plugin. - on_connection_added: This function is called whenever there is a new client connection to the server, and also for all existing connections at the time the plugin is loaded.
- on_connection_removed: This function is called whenever a connection to a client is closed, and also for all existing connections at the time the plugin is unloaded.
- on_session_added: This function is called when a new session becomes active on the server, and also for all active sessions at the time the plugin is loaded.
- on_session_removed: This function is called when a session becomes inactive on the server and is removed from RAM, and for all active sessions at the time the plugin is unloaded.
Note that the connection-specific and session-specific data and callbacks could also be implemented without being part of the plugin infrastructure. The plugin would just have to install appropriate signal handlers to signals of the InfdDirectory
object. However, since most plugins need at such functionality, it is handled centrally by the plugin manager. Below is an example of how this structure can look like in an actual infinoted plugin:
const InfinotedPlugin INFINOTED_PLUGIN = {
"example",
"An example plugin that writes a greeting message into the log for every user that joins a session.",
INFINOTED_PLUGIN_EXAMPLE_OPTIONS,
sizeof(InfinotedPluginExample),
0,
0,
NULL,
infinoted_plugin_example_info_initialize,
infinoted_plugin_example_initialize,
infinoted_plugin_example_deinitialize,
NULL,
NULL,
infinoted_plugin_example_session_added,
infinoted_plugin_example_session_removed,
};
Options that can be provided to the plugin are declared with the [InfinotedParameterInfo](http://infinote.0x539.de/libinfinity/API/libinfinoted-plugin-manager/libinfinoted-plugin-manager-06-infinoted-parameter.html#InfinotedParameterInfo)
structure. One such structure declares one parameter. An array of parameters is then given to the plugin in its options
field. The InfinotedParameterInfo
structure has the following fields:
struct _InfinotedParameterInfo {
const char* name;
InfinotedParameterType type;
InfinotedParameterFlags flags;
size_t offset;
InfinotedParameterConvertFunc convert;
char short_name;
const char* description;
const char* arg_description;
};
The fields have the following meanings.
-
name: The name of the option. In the infinoted configuration file, it can be set with
name=XXX
, where XXX will be its input value. -
type: Specifies the type of the parameter input value. This can be one of
INFINOTED_PARAMETER_BOOLEAN
,INFINOTED_PARAMETER_INT
,INFINOTED_PARAMETER_STRING', or
INFINOTED_PARAMETER_STRING_LIST`. -
flags: Either 0 or
INFINOTED_PARAMETER_REQUIRED
. If set toINFINOTED_PARAMETER_REQUIRED
, the parameter must be given in the configuration file or an error is generated when the plugin is loaded. -
offset: Offset in bytes into the plugin instance structure where the parameter output value will be stored. This is typically created with a macro such as
offsetof
orG_STRUCT_OFFSET
. -
convert: A conversion function, which converts the input value read from the configuration to an internal representation used by the plugin. It's signature is
gboolean(*)(gpointer, gpointer, GError**)
, where the first pointer points to the output value, and the second pointer to the input value. If the function generates an error, the plugin is not loaded. See Conversion Functions below for more details. - short_name: A short one-character name for the parameter, like for short command line options. This is unused at the moment, but might be used at some point for command line option parsing.
-
description: A human-readable description of the parameter as it would appear in the
--help
output for command line options. -
arg_description: A description for the argument of the parameter, if any, or
NULL
.
When a plugin is loaded, the initialization sequence is the following: First, on_info_initialize
is called on the plugin. Then options are read from the configuration file and their values stored in the plugin instance. If one of the conversion function produces an error, the on_deinitialize
is called and the plugin is not loaded. Otherwise, on_initialize
is called on the plugin. If this produces an error, again on_deinitialize
is called and the plugin unloaded. Otherwise, the plugin is fully initialized.
Due to the sequence outlined above, default values for parameters can be assigned in on_info_initialize
, and then they might be overwritten by the parameter parsing.
Below is an example for a parameter list declaration. Note that the list needs to be 0-terminated.
static const InfinotedParameterInfo INFINOTED_PLUGIN_EXAMPLE_OPTIONS[] = {
{
"greeting-text",
INFINOTED_PARAMETER_STRING,
0, /* no flags, if parameter is not given default is used */
G_STRUCT_OFFSET(InfinotedPluginExample, greeting_text),
infinoted_parameter_convert_string,
0,
"Text that is written into the log for each user",
"TEXT"
}, {
NULL,
0,
0,
0,
NULL
}
};
Conversion functions are used to convert the raw value read from the configuration file, which is one of gboolean
, int
, gchar*
or gchar**
(depending on the type
field in the InfinotedParameterInfo
structure) into the internal form consumed by the plugin. It also validates the parameter, for example it can make sure that for time intervals only positive values are allowed. While all sorts of conversion functions can be written by plugin developers, libinfinoted-plugin-manager provides a set of basic functions described in the following:
-
infinoted_parameter_convert_string (for
INFINOTED_PARAMETER_STRING
parameters): The input value is a pointer to agchar*
, and the output value is a pointer to agchar*
. Takes the input string as the output string. If the output string exists already, it is freed. This means that if you want to set default string values inon_info_initialize
for parameters using this conversion functions, they should be dynamically allocated, for example withg_strdup
. -
infinoted_parameter_convert_string_list (for
INFINOTED_PARAMETER_STRING_LIST
parameters): The input value is a pointer to agchar**
, and the output value is a pointer to agchar**
. Takes the input string list as the output string list. If the output string list exists already, it is freed. -
infinoted_parameter_convert_filename (for
INFINOTED_PARAMETER_STRING
parameters): The input value is a pointer to agchar*
, and the output value is a pointer to agchar*
. Converts the UTF-8 input string to the GLib filename encoding, so that it can be used when using it as a filename to open files. -
infinoted_parameter_convert_boolean (for
INFINOTED_PARAMETER_BOOLEAN
parameters): The input value is a pointer to agboolean
, and the output value is a pointer to agboolean
. Takes the input value as output value. -
infinoted_parameter_convert_port (for
INFINOTED_PARAMETER_INTEGER
parameters): The input value is a pointer to agint
, and the output value is a pointer to aguint
. Takes the input value as output value if it is between 1 and 65535. Otherwise, an error is generated. This should be used for parameters that represent TCP and UDP port numbers. -
infinoted_parameter_convert_nonnegative (for
INFINOTED_PARAMETER_INTEGER
parameters): The input value is a pointer to agint
, and the output value is a pointer to aguint
. Takes the input value as output value if it is greater or equal than 0. Otherwise, an error is generated. -
infinoted_parameter_convert_positive (for
INFINOTED_PARAMETER_INTEGER
parameters): The input value is a pointer to agint
, and the output value is a pointer to aguint
. Takes the input value as output value if it is greater than 0. Otherwise, an error is generated. -
infinoted_parameter_convert_security_policy (for
INFINOTED_PARAMETER_STRING
parameters): The input value is a pointer to agchar*
, and the output value is a pointer to aInfXmppConnectionSecurityPolicy
. The only allowed input values are "no-tls", "allow-tls" and "require-tls", and they map to the valuesINF_XMPP_CONNECTION_SECURITY_ONLY_UNSECURED
,INF_XMPP_CONNECTION_SECURITY_BOTH_PREFER_TLS
, andINF_XMPP_CONNECTION_SECURITY_ONLY_TLS
, respectively.
The InfinotedPluginManager
instance that is passed to the on_initialize
function can be used to access the state of the server. A pointer to it is therefore typically saved in the plugin instance structure. Typically, what a plugin would do, is to wait for various events by installing signal handlers for various signals of objects, such as when text is changed in a document, or a user joins a document.
The reference documentation for the available API is available online.
The libinfinity API is quite extensive, but at some places a bit counter-intuitive to use. In the following, a few tips are gathered together to get you started.
-
The primary server object is
InfdDirectory
, which represents the document tree of the server. However, most operations on it are performed using the API of theInfBrowser
interface, whichInfdDirectory
implements. -
For navigating inside the directory, a
InfBrowserIter
is used. Such an iterator points to a single item in the tree. Theiter
argument to theon_session_added
andon_session_removed
callbacks of a plugin represents the document which is being edited within the session. -
From the directory, a session can be obtained for each document. In this case, a
InfdSessionProxy
object is returned, which again implements theInfSessionProxy
interface. TheInfSessionProxy
can be used to join a user into the session, which is required before being able to make changes to it. The actual session object,InfSession
, is obtained from the session proxy with this code:InfSession* session; g_object_get(G_OBJECT(proxy), "session", &session, NULL); /* do something with session */ g_object_unref(session);
-
The
InfSession
object represents the editing session. It is used to send message between participants of the session. -
The session object can be used to obtain the user table, with
inf_session_get_user_table()
, which allows to access the participants of the session, and be notified when users join or leave. -
The session object can be used to obtain the buffer, with
inf_session_get_buffer()
, which, for text sessions, can be cast toInfTextBuffer
, which allows to access the text of the document, and to be notified when the text changes.
A full example for an infinoted plugin is available here on github: infinoted-plugin-example. It is available under an ISC license. It works fully out-of-the-box, including an autotools setup that installs the plugin in the correct path for infinoted to pick it up, and the code for a small plugin. The plugin writes a short log message into the server log for every user that joins a session.
For further reference, the source code of the available plugins that ship with infinoted can be studied.
.