user warning: Got error 28 from storage engine query: SELECT DISTINCT b.* FROM blocks b LEFT JOIN blocks_roles r ON b.module = r.module AND b.delta = r.delta WHERE b.theme = 'pixture' AND b.status = 1 AND (r.rid IN (1) OR r.rid IS NULL) ORDER BY b.region, b.weight, b.module in /var/www/zeroflag/jshot.info/web/modules/block/block.module on line 433.

Plugin development

JShot plugin development


Hello World screencast


Video tutorial: How to create a "Hello World" plugin for JShot in 5 minutes.


Introduction

JShot provides an Java API to develop your own uploader plugins. Plugins can be developed in Java programming language, or other languages supported by the JVM (for example Groovy). The main purpose of plugin development is to support custom or thirdparty services which are not supported by JShot out of the box.

An uploader plugin works in the following way: It receives a DataSource which typically holds the screenshot, sends the data to the specified service (using HTTP, FTP or any other method), and provides zero or more result object to the user (see UploadResult). The latter is typically a URL which identifies the location of the uploaded picture.

Plugins interact with the user via InputDialogs or configuration forms. An InputDialog can be popped up before the upload begins. This is what you see when using the ImageShack uploader. The plugin configuration is a persistent object which contains the options related to the uploader. Several different configuration can belong to the same plugin. These are called uploader profiles from the user point of view.

Note: The Uploader name is used because of historical reason. It is not necessary to implement an actual upload. For example the Skype plugin doesn't upload anything, just sends the screenshot to the Skype application as a command line parameter.

Pre-Requirements:

  • Java Development Kit 1.6 or newer.
  • JShot 1.3.7 or newer.


Environment setup:

Plugin development doesn't require special environment, just a standard Java project with the required compile time libraries. The most easiest way to do that is adding everything from the lib directory of JShot (C:\Program Files\JShot\lib on Windows) to the classpath.

The structure of a plugin

Plugins are packaged in standard JAR files, and loaded by JShot in runtime according to and XML descriptor file. The JAR file must be placed in the uploaders directory under its own folder along with the plugin.xml descriptor file. This folder is located at the following place: <home>/.jshot/plugins/uploaders. For example if the name of a plugin is test you have to copy the JAR and XML to <home>/.jshot/plugins/uploaders/test.


Loading sequence

Before every upload operation, a new Plugin instance will be instantiated. This instance won't be reused at the next upload.

  1. JShot reads the plugin.xml and finds the Uploader and the Config class in the JAR file.
  2. JShot instantiates the Uploader class.
  3. JShot instantiates the Config class and fills it with the deserialized profile configuration.
  4. JShot sets the profile and the global configuration to the Uploader using Plugin#setProfileConfig(Config), Plugin#setPluginConfig(GlobalPluginConfig).
  5. JShot pops up an InputDialog if the Plugin has an InputPanel.
  6. JShot sets the progress listeners and invokes the Uploader#upload(DataSource) method of the plugin.
  7. After the upload operation finished, JShot displays the UploadResult if it is not empty.
  8. If the profile config was changed (Config#isChanged() returns true), JShot saves the profile into an XML.


Classes

The plugin must contain a class which implements the Uploader interface or extends from the AbstractUploader.

For example:

Here is the Uploader class of the Hello World plugin.

public class TestUploader extends AbstractUploader {

    private TestConfig config;

    public UploadResult upload(DataSource ds) {
        return UploadResult.createSingleResult("test", "Hello " + config.getName() );
    }

    public void abort() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setProfileConfig(Config config) {
        this.config = (TestConfig) config;
    }

    public Config getProfileConfig() {
        return config;
    }
}

The upload method takes a DataSource parameter which contains the image, and returns an UploadResult object which will be displayed after the upload finished. The other important class which is not mandatory but in most cases is required, is the Config class. The config class is simple Java object that can be used to store various user definied configuration settings. JShot generates the GUI from this class and serializes the content into an XML file. Before the upload begins this XML will be deserialized into the Config object and passed to your plugin using the setProfileConfig method. If your plugin changes some fields in this object, you can use the Config#setChanged(boolean) method to force JShot to save it after the upload.

Here is the TestConfig class of the Hello World plugin:

@Root
public class TestConfig extends Config {

    @GuiMapping(description="Say hello to:", bind=ControlTypes.TEXT_FIELD)
    @Element
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

As you can see the fields are annotated with 2 types of annotations. The @GuiMapping indicates that this field will be displayed by the GUI. The @Element and @Root annotations are used by the serialization process (See http://simple.sourceforge.net). The getters and setters are mandatory for each annotated field.


During the upload you can notify JShot about the progress of the data transfer or the current state of the operation. To do this, use AbstractUploader#notifyProgressListeners(int) and AbstractUploader#notifyStateChangeListeners(UploaderState) methods. If the progress cannot be determined you can use -1 as a parameter.

For example:

try {
	this.notifyStateChangeListeners( UploaderState.CONNECTING );
	// connect to the service
	this.notifyStateChangeListeners( UploaderState.AUTHING );
	// authenticate to the service
	this.notifyStateChangeListeners( UploaderState.UPLOADING );
	// upload the image
} catch (Exception e) {
	this.notifyStateChangeListeners( UploaderState.ERROR );
	// returns error result
} finally {
	this.notifyStateChangeListeners( UploaderState.CLOSING );
	// disconnect from the service
}

notifyStateChangeListeners( UploaderState.DONE );
// returns the result


The return value

Uploaders return an UploadResult object. In most cases this object contains one or more URLs which point to a HTTP address where the picture is located. If you don't want to return such URLs, you can use an empty result, like the Skype plugin. In this case the result window won't be displayed after the upload.

The UploadResult has multiple factory methods for generating different results.

For example:

return UploadResult.createEmptyResult("no result");
return UploadResult.createErrorResult( "Cannot connect to service", exception );
return UploadResult.createSingleResult( "Web address", link );

or

UploadResult result = new UploadResult();
result.addURLResult( new URLResult("Image link", imageLinks ) );
result.addURLResult( new URLResult("Thumbnail link", thumbnailLink ) );
return result

After the plugin returned with the result, the user will see something similar:



The plugin descriptor file

The plugin.xml is a special file that can contain information about the plugin. It identifies the Uploader and Config classes of the plugin, along with other plugin related information and an optional dependency list.

For example:

<plugin_manifest>
	<main_jar>FTPUploader.jar</main_jar>
	<main_class>hu.jshot.plugins.uploaders.ftpuploader.FTPUploader</main_class>
	<config_class>hu.jshot.plugins.uploaders.ftpuploader.FTPConfig</config_class>
	<plugin_descriptor>
		<short_name>FTP Uploader</short_name>
		<description>FTP Uploader plugin</description>
		<author_name>Attila Magyar</author_name>
		<webpage>http://jshot.info</webpage>
		<contacts>email@email.com</contacts>
		<version>1.3.1</version>
	</plugin_descriptor>
	<jar_dependency length="1">
		<jar>uFTPLib.jar</jar>
	</jar_dependency>
</plugin_manifest>

As you can see the FTP Uploader has one JAR dependency. The dependencies must be put in the libs directory under the plugin <home>/.jshot/plugins/uploaders/<pluginname>