Skip to content

Writting_plugin_dependent_on_other_plugin

Paweł Salawa edited this page Jan 16, 2018 · 4 revisions

Table of Contents

Prerequisites

You must first be familiar with plugin's json file. If you haven't read it yet, do it first, then come back here.

Brief

Sometimes one plugin can provide common code base for several plugins, or common interface for other plugins, etc.

We can have for example a MediaPlayer plugin, which plays sound data extracted from database, but it can have couple of different implementations, depending on the output audio device. It can be a default audio output (speakers), but it can be saved to disk as a file. It also allows for other people to implement their own output plugin and they can use the existing MediaPlayer plugin to have it extract the data from the database and prepare for writting it to the output. In such case there would be at least 2 plugins dependent on MediaPlayer plugin - MediaPlayerSpeakers and MediaPlayerFile.

SQLiteStudio provides full support for inter-plugin dependencies, which also covers conflicts.

Defining paths for compiler and linker

In order to use other plugin's symbols, you must provide proper configuration for includes and libraries, so the compilator and the linker will recognize those symbols.

The usual configuration

Given the standard directory structure for the SQLiteStudio and its plugins sources, edit your plugin's .pro file and add following statements:

INCLUDEPATH += $$PLUGINSDIR/MediaPlayer
DEPENDPATH += $$PLUGINSDIR/MediaPlayer
win32: {
    LIBS += -lMediaPlayer
}

For win32 we need to add linking with the dependency plugin, because Windows expects all symbols to be resolved while linking, even those are shared libraries.

Custom directory for the plugin (not recommended)

If you add plugin outside of the `Plugins` directory (so called "external plugin"), you will need to add its directory to includes and dependencies of `qmake`:
INCLUDEPATH += $$PWD/../../MediaPlayer
DEPENDPATH += $$PWD/../../MediaPlayer

or by relative path to the Plugins dir:

INCLUDEPATH += $$PLUGINSDIR/../MediaPlayer
DEPENDPATH += $$PLUGINSDIR/../MediaPlayer
Declaring plugin dependencies -----------------------------

To declare plugin dependent on other plugin, just define it in plugin's json file, like this:

{
    "type":         "GeneralPurposePlugin",
    "title":        "Speakers output",
    "description":  "Provides support for sending sound to speakers",
    "version":      10000,
    "author":       "An author",
    "dependencies": "MediaPlayer"
}

The "MediaPlayer" here is a name of the main class in the MediaPlayer plugin, the one that implements Plugin interface. The class name and the plugin name is the same in almost all cases (the exception is only if someone doesn't use GenericPlugin to implement his plugin and he defines Plugin::getName() method to return different name).

Note, that for "dependencies" entry you can define more plugin names, using array construction (with [ and ] characters), like this:

    "dependencies": ["MediaPlayer", "OtherPlugin"]

Having such a dependency declared at compile time will result in runtime with SQLiteStudio making sure, that MediaPlayer is loaded before MediaPlayerOutput. If the MediaPlayer fails to load (or is not available), then MediaPlayerOutput will also fail to load.

Also if user unloads MediaPlayer manually, then SQLiteStudio makes sure that all plugins dependent on MediaPlayer are unloaded first.

Plugin's code

You already can include dependent plugin headers:

#include "mediaplayer.h"

If you declared some base class to be inherited/instanitiated in the local plugin, just do it:

SomeMediaPlayerClass* obj = new SomeMediaPlayerClass();

If you need to access the main instance of the dependent plugin, use PluginManager:

#include "service/pluginmanager.h"
// ...

    Plugin* plugin = PLUGINS->getLoadedPlugin("MediaPlayer");
    MediaPlayer* mp = dynamic_cast<MediaPlayer*>(plugin);

More advanced dependency definitions

The dependencies entry in Json file can be defined in various forms. It can be a single string, pointing a single dependency, but it can also be an array of strings to define dependency from numerous plugins.

It can also be Json object, which defines dependency name, minimum and maximum versions. It can albo be an array of such objects.

Example:

    "dependencies": [
        {
            "name":       "SomePlugin1",
            "minVersion": 10001
        },
        {
            "name":       "SomePlugin2",
            "maxVersion": 10300
        }
    ]

The example above defines, that our plugin has dependency from SomePlugin1 and SomePlugin2, with a condition, that the SomePlugin1 is in version at least 1.0.1 and SomePlugin2 is in version at most 1.3.0.

The minVersion and maxVersion can be useful if you know that the dependent plugins changed their API during version releases.

Conflicts

There may be the case, that you want to write a plugin that implements the same features as some other plugin and you don't want those two plugins to be both loaded at the same time.

In that case you can declare your plugin to be conflicting with that other plugin.

If any of two plugins declare a conflict with the other one, SQLiteStudio will prevent from loading them both at the same time. Simple, the first loaded is lucky and the other will fail to load. User can manually unload/load desired plugins, so he/she can decide which one to use. Once the unwonted plugin was unloaded and desired plugin is loaded, the configuration will be saved and the user will always get the right plugin loaded at startup.

Declaring a conflict

This goes almost the same as for dependencies, except the key name is different:

{
    "type":         "GeneralPurposePlugin",
    "title":        "Speakers output",
    "description":  "Provides support for sending sound to speakers",
    "version":      10000,
    "author":       "An author",
    "conflicts":    "MediaPlayerOtherOutput"
}

The "MediaPlayerOtherOutput" is the name of the main class (the one that implements Plugin interface) from the conflicting plugin.

Just like in dependencies, the conflicts can be either single entry, or array of entries, but unlike in dependencies, here only simple names are allowed, no complex objects.