Application plugins

Writing an application plugin is a very similar process to writing a channel plugin. However, instead of being selected via the Channel Wizard, they are loaded by Awasu when it starts and appear in the Plugins window.

How Awasu loads application plugins

To see an application plugin in action, copy the two files in the SamplePythonPlugin/ directory to the AppPlugins/ directory in the Awasu installation directory, then restart Awasu. You should see a new entry in the Plugins window.

When Awasu starts, it scans AppPlugins/ (and its sub-directories) looking for files with a .plugin extension. This file contains information about the plugin e.g.

Plugin =
AppPluginId = SamplePythonAppPlugin
AppPluginDisplayName = Sample Python App Plugin

The Plugin parameter gives the name of the plugin script, which must reside in the same directory. AppPluginId must be a unique identifier string while AppPluginDisplayName is the display name that the user will see.

How the application plugin's start page is generated

When the user first opens the plugin from the Plugins window, the plugin will be asked to generate an HTML page that will be shown to the user. The plugin script will be run with a Windows INI file as before, but with a few more parameters:

Command = GenerateMainPage
AppServerUrl = ...           
PluginServerUrl = ...

where the Command parameter specifies what the plugin is being asked to do. In this case, it will be GenerateMainPage and the plugin should accordingly print out to the console an HTML page that Awasu will show to the user in a browser window.

Setting up parameters for the application plugin

As before, you can define channel parameters for the plugin. This example also defines a global plugin parameter:

Name = DomainName
Type = string
DefaultValue =
Description = Domain name for generated URL's.
IsRequired = 1

Name = nItems
Type = int
DefaultValue = 5
Description = Number of items to generate.
IsRequired = 1

Name = ItemTitleStem
Type = string
DefaultValue = Item Title
Description = Stem to use for generated item titles.
IsRequired = 1

Name = GenerateDescriptions
Type = bool
DefaultValue = 1
Description = Flags if descriptions should be generated for feed items.

Global plugin parameters (defined in PluginParameterDefinition- sections) are shared by all channels serviced by the plugin i.e. if the user changes their values, it will affect every channel. This example also defines 3 other parameters that can be set independently for each channel.

Writing interactive application plugins

It is possible to embed special links in the generated HTML page that will cause a request to be routed back to the plugin when the user clicks on them. This lets users interact with the plugin.

The general format of these URL's looks like this:

where PLUGIN_SERVER_URL is the string defined by the PluginServerUrl value in the INI file and APP_PLUGIN_ID is the plugin's ID. RequestName and ParamString can be anything you like and will be passed back to the plugin when the user clicks on the link. For example, if your HTML-generation code looked something like this:
url = PLUGIN_SERVER_URL + "/" + APP_PLUGIN_ID + "/foo/bar?p1=hello&p2=world"
print( "<a href='" + url + "'>Click here!</a>" )
when the user clicks on the link, the plugin will be called with the following information in the INI file:
Command = ProcessRequest
PluginRequest = foo/bar
ParamString = p1=Hello&p2=World

Sending requests to Awasu

Calls to the Awasu API can also be sent, using the values specified in the AppServerUrl and AppToken parameters.

For example, to embed a link that lets users subscribe to Awasu's feed, you would do something like this:

url = APP_SERVER_URL + "/channels/subscribe?url="
print( "Click <a href='" + url + "'>here</a> to subscribe to Awasu.")

If you're technically-minded, a description of how this all works can also be found here.

How the sample application plugin works

Getting back to our sample plugin, the script starts off with this piece of code (at the bottom of the file):

# load the INI file passed in to us
parser = configparser.ConfigParser() sys.argv[1] )

# process the requested command
scriptCmd = parser.get( SYSTEM_PARAMETERS_SECTION_NAME, "Command", fallback="" )
if scriptCmd == "GenerateMainPage":
    generate_main_page( parser )
elif scriptCmd == "ProcessRequest":
    plugin_request = parser.get( SYSTEM_PARAMETERS_SECTION_NAME, "PluginRequest", fallback="" )
    param_string = parser.get( SYSTEM_PARAMETERS_SECTION_NAME, "ParamString", fallback="" )
    process_request( parser, plugin_request, param_string )
    raise RuntimeError( "Unknown script command: " + scriptCmd )

We load the INI file passed in to us and determine what we're being asked to do: generate the main page or process a request (because the user clicked on a link that has been routed back to us). An exception is raised if an error occurs which will print a message to the error console. Awasu will detect this and treat it as an error.

This is the generate_main_page() function that builds the plugin's HTML start page:

# get the app settings
app_server_url = parser.get( SYSTEM_PARAMETERS_SECTION_NAME, "AppServerUrl", fallback="" )
if not app_server_url:
    raise RuntimeError( "No app server URL was specified." )
app_plugin_server_url = parser.get( SYSTEM_PARAMETERS_SECTION_NAME, "AppPluginServerUrl", fallback="" )
if not app_plugin_server_url:
    raise RuntimeError( "No app plugin server URL was specified." )

# get the plugin settings
domain_name = parser.get( PLUGIN_PARAMETERS_SECTION_NAME, "DomainName", fallback="" )
if not domain_name:
    raise RuntimeError( "No domain name was specified." )

# generate a URL that will let the user subscribe to a channel that
# we will generate. We embed some information in URL path and parameters
# for demonstration purposes. These will get passed in to process_request()
# when the channel is updated.
api_token = parser.get( SYSTEM_PARAMETERS_SECTION_NAME, "ApiToken", fallback="" )
url = "{}/channels/subscribe?token={}&silent=1".format( app_server_url, api_token )
url += "&url={}/{}/foo/bar?p1=Hello%26p2=World".format( app_plugin_server_url, APP_PLUGIN_ID )

# generate the main page
print( """
<h2> Sample Python Plugin </h2>
<p> This is the main page for the sample Python plugin.
<p> The plugin settings are:
    <li> DomainName: {domain_name}
<p> Click <a href='{url}'>here</a> to subscribe to a test channel.
""".format( **locals() ) )

We insert a link that will send a channel subscription request to Awasu for a URL that will be routed back to us when it is used.

When Awasu goes to update the channel, the URL tells it that the channel is actually being generated by a plugin. The plugin is then run again with the Command parameter set to ProcessRequest and our process_request() function is called:

# get the plugin settings
domain_name = parser.get( PLUGIN_PARAMETERS_SECTION_NAME, "DomainName", fallback="" )
if not domain_name:
    raise RuntimeError( "No domain name was specified." )

# get the channel settings
n_items = int( parser.get( CHANNEL_PARAMETERS_SECTION_NAME, "nItems", fallback=5 ) )
item_title_stem = parser.get( CHANNEL_PARAMETERS_SECTION_NAME, "ItemTitleStem", fallback="Item Title" )
generate_descriptions = int( parser.get( CHANNEL_PARAMETERS_SECTION_NAME, "GenerateDescriptions", fallback=1 ) )

# put together a channel description 
description = ... # code not relevant to this discussion snipped here 

# generate the RSS feed
print( "<rss>" )
print( "<channel>" )
print( "<title>Sample Python Plugin</title>" )
print( "<link>" + domain_name + "</link>" )
print( "<description><![CDATA[" + description + "]]></description>" )
for i in range( 1, n_items+1 ):
    print( "<item>" )
    print( "    <title>{} {}</title>".format( item_title_stem, i ) )
    print( "    <link>{}/item-{}.html</link>".format( domain_name, i ) )
    if generate_descriptions:
        print( "    <description>This is the description for item {}</description>".format( i ) )
    print( "</item>" )
print( "</channel>" )
print( "</rss>" )