Remoto - VFS: The Skeleton Plugin
Remoto - VFS
The Skeleton Plugin

The skeleton plugin represents the most minimal amount of code needed to write a VFS plugin. It is included in the documentation as a starting place for new node packages and to show how a plugin identifies itself, names itself, and is compiled.

A plugin is composed of 3 main parts:

  • A .pro file for qmake, which will tell Qt/qmake to compile it as a plugin, which files are to be compiled, and what to name the resulting dynamic/shared library.
  • A VFS_node_interface subclass, which will register itself as a plugin with the VFS executable and Qt plugin loader, and provide an entry point for the node bundling mechanism in VFS_creator.
  • The actual VFS_node subclasses themselves, which will usually be the bulk of a plugin.

Optionally, a plugin may include .qrc resources for images, code, stylesheets, or any other resource, which is visited in the Animated Clock example.

For this example, we will look at this directory structure for a plugin:

/skeleton.pro
/src/skeletonPlugin.h
/src/skeletonPlugin.cpp
/src/skeleton.h
/src/skeleton.cpp

This is the minumum amount of code and apparatus required to create a plugin.

Attention
The complete skeleton plugin can be found in the 'server/src/plugins/skeleton/' folder. If you intend to build it, you should copy it to a work folder.

The skeleton.pro file

This file will be used with qmake to build the makefiles and ultimately compile the library.

# skeleton.pro
# Read VFS_DIR from the environment
VFS_DIR=$$(VFS_DIR)
isEmpty(VFS_DIR){
error(VFS_DIR must be defined to compile a plugin)
}
# The name of the resulting dynamic lbrary
TARGET = $$qtLibraryTarget(skeleton)
# A series of settings which are global to all plugins. Requires VFS_PLUGINS_DIR to be set.
include($$VFS_DIR/VFS-plugin.pri)
# Plugins may include any additional Qt modules here: network, xml, opengl, etc.
#QT += network
# Include sources and headers to compile
SOURCES += src/skeletonPlugin.cpp \
src/skeleton.cpp
HEADERS += src/skeletonPlugin.h \
src/skeleton.h
# Resources for this project (unused in this example)
#RESOURCES += src/skeleton.qrc
Attention
You will want to rename your plugin to something other than "skeleton", and you will want to include your own (renamed) source files.
Note
if using Qt Creator, you will need to set the VFS_DIR and VFS_PLUGINS_DIR environment variables. This can be done by either setting them on the terminal and opening Creator on the command line, or by opening the project and adding VFS_DIR and VFS_PLUGINS_DIR as variables in the Build Environment section of the project settings. Paths are relative to the skeleton.pro file.
VFS_DIR must be set to the directory containing src/ in the server source tree.
VFS_PLUGINS_DIR must be set to the destination folder to place the compiled dynamic library. This variable is referenced in the VFS-plugin.pri include file.

The VFS_node_interface subclass header

//skeletonPlugin.h
#ifndef SKELETON_PLUGIN_H
#define SKELETON_PLUGIN_H
class skeletonPlugin : public QObject, public VFS_node_interface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.remoto.plugins.skeleton")
Q_INTERFACES(VFS_node_interface)
public:
//virtual bool initialize(); //optional, defaults to true
virtual QString description(QString type); //optional, but highly recommended!
virtual QString arguments(QString type); //optional, but highly recommended!
//virtual bool licensed(QString type); //optional, defaults to true
virtual QStringList provides(); //pure virtual, must be defined
virtual VFS_node *create(QString type, QVariantMap env, QDomElement child); //pure virtual, must be defined
virtual QString code(QString nodename, QString libname, QString &error); //optional, defaults to error
};
#endif // SKELETON_PLUGIN_H
The interface class for dynamically loaded plugins.
virtual QString description(QString type)
Descriptive text about an individual node.
virtual QString arguments(QString type)
Describe the arguments needed to create a node.
virtual QString code(QString nodename, QString libname, QString &error)
Request code or other resource from a plugin bundle.
virtual QStringList provides()
A string list of node names that this plugin is prepared to create.
virtual VFS_node * create(QString type, QVariantMap env, QDomElement child)
Create a new node instance.
VFS_node is the base class from which all other VFS_node classes derive.
Definition: VFS_node.h:143

Define the VFS_node_interface class, and identify it as a Qt plugin by providing a metadata identifier with the Q_PLUGIN_METADATA(name) macro. Any string can be used here, but the example shows the common format.

Use the Q_INTERFACES() macro to tell the compiler what functions to expose in the dynamic library. This should always be VFS_node_interface.

Note
VFS_node_interface contains pure virtual methods, so initialize(), description(), licensed(), and code() are optional, but provides() and create() not optional.
Attention
You will want to rename the class and the metadata to your plugin name, and don't forget the preprocessor defines.
See also
VFS_node_interface

The VFS_node_interface subclass source

//skeletonPlugin.cpp
#include "VFS_creator.h"
#include "skeletonPlugin.h"
#include "skeleton.h"
//provide a description for each node in this bundle
QString skeletonPlugin::description(QString type)
{
if (type == "skeleton")
return "A node provided as an example for the documentation. It does nothing.";
}
//provide a constructor signature for each node in this bundle
QString skeletonPlugin::arguments(QString type)
{
if (type == "skeleton")
return argumentString( skeleton::staticMetaObject );
}
//tell VFS_creator what nodes are included in this bundle
QStringList skeletonPlugin::provides()
{
QStringList p;
p << "skeleton"; //the node name, as written in the config file
return p;
}
//actually create a new instance of a node
VFS_node *skeletonPlugin::create(QString type, QVariantMap env, QDomElement child)
{
if (type=="skeleton")
{
//In this example case, skeleton needs an alpha and beta parameter in its constructor.
QString alpha = VFS_creator::configAttribute(env,child,"alpha",false,"alpha");
int beta = VFS_creator::configAttribute(env,child,"beta",false,"123").toInt();
return new skeleton(alpha, beta);
}
//If the node type is not found, return null. No node will be created.
return nullptr;
}
//return code or data associated with a node
QString skeletonPlugin::code(QString nodename, QString libname, QString &error)
{
//this will produce an error because skeleton doesn't serve any code
if (nodename == "skeleton")
return skeleton::code(nodename,libname,error);
return VFS_node_interface::code(nodename,libname,error);
}
static QString configAttribute(QVariantMap env, QDomElement e, QString attr, bool req=true, QString def="")
Fetch an XML node attribute value and resolve it against an environment.
setter type
a setter DOCME
setter error
Set the error value of this widget.

The plugin bundle functions need to service requests for every node type defined in VFS_node_interface::provides(). The logic in each method is up to the developer. It can be complex or simple depending on your needs. For instance, If the plugin is always licensed, licensed() could just return true.

See also
VFS_node_interface

The VFS_node subclass implementation

Since our skeletonPlugin serves the "skeleton" node, we have to define it. In this case, we define a node that does nothing but construct and destruct.

Header:

//skeleton.h
#ifndef SKELETON_H
#define SKELETON_H
#include "VFS_node.h"
class skeleton : public VFS_node
{
Q_OBJECT
public:
Q_INVOKABLE explicit skeleton(QString alpha, int beta);
virtual ~skeleton();
private:
QString _alpha;
int _beta;
};
#endif // SKELETON_H

Source:

//skeleton.cpp
#include "VFS.h"
#include "skeleton.h"
skeleton::skeleton(QString alpha, int beta)
, _alpha(alpha)
, _beta(beta)
{
VFS::WARN( QString("Created skeleton instance: %1 / %2").arg(_alpha).arg(_beta));
}
skeleton::~skeleton()
{
VFS::WARN( "skeleton destructor" );
}
static void WARN(QString message, int level=0, QString user="server")
Send a message to the VFS::_warnings VFS_stream.
Definition: VFS.cpp:258
See also
VFS_node
Note
If your plugin is relatively small, all of this (both plugin and node) could be included in single source/header files. But generally, plugins tend to swell over time as additional functionality is added. It can be difficult to separate all the files later, so it is recommended to follow the directory structure above.
Attention
Much of the setup work when using the skeleton as a starting place is in replacing "skeleton" with the proper name of your plugin. It is recommended you use "grep -i skeleton *" to find all instances and replace them to avoid future name collisions.

Building and Using the Plugin

If everything is configured properly, building the plugin is straightforward:

export VFS_DIR=[path/to/vfs_server/server]
export VFS_PLUGINS_DIR=[path/to/plugins/dir]
qmake skeleton.pro && make

Now that the plugin is successfully compiled as a dynamic library, it can be included in a Config File to be created when the application launches. Because provides() returns "skeleton" as the name, we will need to use that as an entry in the config file.

//skeleton.xml
<vfs>
<skeleton name='yorick' alpha='human' beta='120' />
</vfs>

Running the exectuable with this config file will create a VFS that uses this plugin, and will print a warning message when the node is created (or destroyed):

VFS skeleton.xml -plugins $VFS_PLUGINS_DIR

This VFS is not very interesting because we have no way to talk to it and it doesn't do anything useful. The Log Viewer and Animated Clock examples will take things a few steps further.

See also
XML Config File(s)
Invoking the Server