Scripting Manual

Shallot has a plugin interface which allows to add functionality to the core from outside. This document is for people who are going to create a new Shallot plugin in order to bring some new functionality to Shallot.

A Shallot plugin developer should have at least basic skills in the Python programming language and should have read the entire section Scripting Manual (From the Feature Overview, it might be okay to read just the parts relevant for you).

After the Scripting Manual, you find the complete interface reference. You should use it for finding some technical details (which classes exist, which methods are there, which arguments do they have, …). It lists the structure of the interface from different perspectives. Some of them are quite useful, others ones are more technical and only interesting in rare situations. Good starting points are those sections:

  • The “Class List” provides an overview of all existing classes.

  • The “shallot Namespace Reference” gives a coarser overview of all existing subparts.

General Notes

This section covers some notes and hints, which you should know about before you begin with developing Shallot plugins.

Plugin Management Assistant

Shallot includes a plugin manager. It helps to work with plugins as it provides an overview of all installed plugins and some management actions you can execute on them (e.g. deleting, disabling).

It is not enabled by default, since it is not required for day-to-day use. For plugin development, you should enable the Plugin Management in the Tuning dialog.

It also provides a collection of sample plugins. They can either be used as some kind of a tiny step-by-step tutorial or as templates for your new plugins.

General Plugin Design

Shallot plugins are Python scripts in a single file with the filename [pluginname].py (replacing [pluginname] with the actual name). They must import shallot and use the methods provided there for interacting with the Shallot core.

Implement & Register

For the most kinds of functionality you can add to Shallot, the development pattern is as follows (everything happening in your Python plugin file):

  • At first you subclass one of the Shallot base classes and put your functionality into the class implementation. The documentation of the base class tells which methods may and/or must be implemented in your subclass.

  • Then, in the main section of that plugin - this is the unindented root block as it is in typical executable Python scripts - the code must add this new subclass to the Shallot runtime. This happens with one of the register* methods in the Shallot interface.

Which particular elements of the Shallot interface are used, depends on what kind of functionality you want to add. The section Feature Overview below describes the details.

Position Indexes

For some kinds of pluggable functionality, Shallot keeps an ordered list of plugged objects. The semantic of the order can differ, e.g. it can be used for displaying purposes or for an execution order.

The technical mechanism is similar on most places in the interface. Particular functions (i.e. some constructors and register methods) have this two arguments amongst others:

  • positionGroup: A coarse order information. This must be set to one of the elements in a particular enumeration (see the function documentation for details).

  • positionIndex: An integer between 0 and (at least) 10000 which controls the order within the chosen group. Lower values mean sooner placement in the order. They are not required to be continuous but can have holes and also duplicates (although the order of duplicates to each other is then undefined). Existing larger indexes will also not shift (as they would do in a typical list) on insertion of a new item.

The documentation texts of them explain what the order is used for. Those two arguments together control the placement in the ordered object list. Typically both ones are optional and have a default:

  • positionGroup: Some fixed default group. The function documentation might mention details.

  • positionIndex: Some random-like integer, which is derived from your class name (and so is always the same in each run on each machine).

If so, you can decide to use those defaults, or to manually set either one or both of them.

If the order is critical (e.g. because it is the execution order of a process which only succeeds in a certain order), it is probably required to manually set them. Obviously you must then find out the values from the related objects (e.g. by trial-and-error) and tune your values according to them.

If not, you are fine with the defaults in many cases.

The ‘_ApiProxy’ Class

Studying the reference, you will see the class shallot._ApiProxy be referred at many places. Whenever this is the case, e.g. as a superclass of some interface classes, this is mostly a technical information with no big impact on the developer. It is a common superclass for many classes in the Shallot scripting interface, since it gives them access to some internal parts of the infrastructure. You should not directly come in touch with any specifics of this class.

Exceptions

The base class for exceptions in Shallot is shallot.Exceptions.ScriptedException. The shallot.Exceptions namespace contains more interesting stuff.

Try to only throw those exceptions (either directly or indirectly ) from your code to be failsafe. You should use one of the subclasses in raise statements. You should never use subclasses in expect [Foo] statements but only shallot.Exceptions.ScriptedException. Use shallot.Exceptions.ScriptedException.isExceptionClass for checking against a certain class (as string) and rethrow if needed. This is because a exception in Shallot typically has more classes than the direct Python side hierarchy would look like.

Localization

Shallot plugins can support more than just one language. For multi-language support, you should create a global instance of shallot.IntlStringMap at the beginning and take strings from there when they are used for displaying:

import shallot

Strings = shallot.IntlStringMap(
    HelloWorld = {"en":"Hello world!", "de":"Hallo Welt!"},
    SomethingElse = {"en":"Something different", "de":"Etwas anderes"},
)

shallot.Logging.log_info(Strings.HelloWorld)

Deployment

The deployment of a Shallot plugin is as easy as copying the plugin file. However, the destination differs among the various operating systems. Since there is a system-global location and a per-user location, there is one more degree of variation.

On the target system, you can find out the actual destination paths by means of the ‘Find in filesystem’ action in the Plugin Management Assistant.

Once the destination path is known, you can choose an arbitrary deployment technique - e.g. manual file copying, or using the native package manager from the operating system - for bringing the plugin file in place.

Feature Overview

This sections introduces the different kinds of functionality you can add by means of this plugin interface. Those are the technical building blocks you have to use for implementing your plugin logic.

Most of the sections begin with an abstract explanation of the mechanisms. Read ahead! It should become clear later in the text. Each section includes links to the elements in the scripting interface reference which you need to know. You should read the documentations of this elements.

Actions

As a first approximation, an action is some piece of code for execution. Conceptually an action is similar to a simple Python function (although it technically is not just a function as you will read later). However, executing them brings a lot of additional functionality (e.g. dialog support for user communication) and infrastructure.

Actions are used in different ways in Shallot:

  • The easiest thing to do with an action is to simply execute it. This is similar to directly executing the code, but using the additional functionality.

  • Another common usage is to offer them in the user interface for some files or directories, so the user can decide to execute them.

  • There are some other places in the plugin interface, which work with actions somehow. Read those documentations for details.

The first way gives a very versatile tool. In most places in the plugin code, you can put some parts of your routine into an action and execute it in order to use the additional functionality we will see later.

The second way enables your plugin to provide some code for a given selection of files and/or directories for the user, so the user can manually trigger it. Those are typically represented in context menus or the toolbar.

_images/actionsintoolbar.png

At this point, our first approximation must see some corrections:

An action is a (indirect) subclass of shallot.Actions.AbstractAction. It has the following subclasses, which are typical base classes for custom implementations:

  • shallot.Actions.ActionAction: This kind of action is executable.

  • shallot.Actions.SubmenuAction: A submenu is a list of other actions. It is not directly executable, but can be presented as a submenu to the user. This allows to bundle multiple shallot.Actions.ActionAction implementations which provide related functionality for better overview.

A shallot.Actions.ActionAction instance (i.e. an instance of your subclass) can be executed by initializing it at first (calling shallot.Actions.ActionAction.initialize_sync) and calling shallot.Actions.ActionAction.execute.

For sample code, read the sources of the ‘action’ sample plugin (in the Plugin Management Assistant).

In order to provide your action subclass in the user interface, call shallot.Actions.register.

For sample code, read the sources of the ‘action.advanced’ sample plugin.

The additional functionality comes with the info parameter you get in your shallot.Actions.ActionAction.action implementation. It is an instance of shallot.Actions.ExecutionInfo. Read this documentation for a full overview. One essential thing you can do with such an object is to have dialogs with the user. The next section shows how to do this.

The shallot.Actions namespace contains more interesting stuff.

User Feedback

User feedback is everything about giving some information to the user and waiting for its answer in an interactive way.

Typical situations where user feedback operations take place are Action executions. There an instance of shallot.Actions.ExecutionInfo is available. shallot.Actions.ExecutionInfo.userfeedback instance gives you access to an instance of shallot.Actions.ExecutionUserFeedback, which is the key element to all kinds of feedback operations.

_images/userfeedback.png

For sample code, read the sources of the ‘userfeedback’ sample plugin (in the Plugin Management Assistant).

Configuration Values

A plugin can use own configuration values which are stored by Shallot and are visible to the user in the Tuning dialog.

_images/configvalues.png

They are fine for making stuff configurable for special situations, which should typically should stay at default. For many other situations, e.g. when the user is expected to changes this value regularly, check if the Settings features fit better.

Using an own configuration value begins with subclassing shallot.ConfigurationValue. Afterwards, you should create one instance of this class globally and use its methods. No registration is required.

For sample code, read the sources of the ‘configurationvalue’ sample plugin (in the Plugin Management Assistant).

Settings

Settings are a common mechanism for various kinds of customizations the user can make in its Shallot environment. The user can store them, bind them to particular subdirectories and profiles and manage them in the Settings dialog.

_images/setting.png

A plugin can participate in this mechanism by adding own settings.

This begins with subclassing shallot.Setting. Afterwards, shallot.Setting.register must be used for registering it.

For sample code, read the sources of the ‘setting’ sample plugin (in the Plugin Management Assistant).

Main Window

The shallot.MainWindow class provides some navigation function and more. Read the class documentation for details. The main window object is globally available as shallot.MainWindow.current.

For sample code, read the sources of the ‘mainwindow’ sample plugin (in the Plugin Management Assistant).

EURLs

EURLs are addresses of objects in the filesystem. They are a very basic part of the Shallot foundation.

Conceptually EURLs are similar to [URLs](https://en.wikipedia.org/wiki/Uniform_Resource_Locator). In order to express cascades of locations, EURLs are defined as a superset of URLs. As a meaningful example, this EURL refers to a file in an archive file, which in turn is located on a remote server:

zip:/[ftp://ftpserver/foo/bar.zip]//zippedfolder/zippedfile

This cascades can have arbitrary depths (although this obviously doesn’t make sense in arbitrary combinations).

EURLs are used in many different parts in the scripting interface. They are instances of the class shallot.Eurl, which provide a rich set of getters and creator functions.

For sample code, read the sources of the ‘eurl’ sample plugin (in the Plugin Management Assistant).

Filesystem

Shallot keeps an own model of the entire filesystem tree in memory (technically, it determines and stores stuff just on demand, of course). A Shallot plugin has different ways to work with this model.

At first, there are some methods for requesting some informations directly from the model. Find the static methods in shallot.Filesystem for more details (not all of them request things but some are relevant for the stuff explained later). A key class in interactions with the model, also used at various different parts in the scripting interface, is shallot.Filesystem.Node.

For sample code, read the sources of the ‘filesystemnode’ sample plugin (in the Plugin Management Assistant).

A different way of using it filesystem model is to provide custom implementations and own nodes in the tree.

_images/filesystem.png

This at least requires to subclass shallot.Filesystem.Handler and registering it with shallot.Filesystem.Handler.register.

In typical scenarios it also requires to create a shallot.Filesystem.Node which is associated with this handler and to insert it into the model. Some of the static methods of shallot.Filesystem are used for this procedure. Please note that it is just required to insert root nodes somewhere. The subtree of that node is specified by your handler implementation, but the node insertions (and removals) take place implicitely.

You should also read about Operations, since you might have to deal with the key object of this feature here as well.

For sample code, read the sources of the ‘filesystem’ sample plugin (in the Plugin Management Assistant).

A filesystem handler has a wide range of possibilities for extending the Shallot functionality. One of them are custom detail columns, which are subject of the next section.

Detail Columns

A custom detail column, as it is visible in file views, can offer additional information to the user.

_images/detailcolumns.png

Implementing custom detail columns begins with subclassing shallot.DetailColumn. You should then create one instance of this class and add it to some nodes with shallot.Filesystem.Node.add_detail.

For sample code, read the sources of the ‘filesystem’ sample plugin (in the Plugin Management Assistant).

Operations

Operations are a concept of bundling a bunch of small changes in the filesystem together to one large step. They are also essential for working in cascaded filesystems as they are expressed with EURLs. Operations are entirely a backend concept without any direct representations in the user interface.

A small example explains why this bundling is a required thing: There is a ftp server which contains two archive files; a.zip (A) and b.zip (B). They build the roots of the subtrees zip:/[ftp://foo/a.zip]// and zip:/[ftp://foo/b.zip]//, each containing the contents of the respective archive. The user requests a large file copy action for 1000 files in zip:/[ftp://foo/a.zip]// to somewhere in zip:/[ftp://foo/b.zip]//. In an unbundled way, Shallot would at first fetch A, then extract the first file, then fetch B, put the file content into the archive and upload the new version of B again. Afterwards, it would run the same loop for the second file, then for the third, … In an operation, Shallot would fetch A and B just once, then works on the local versions and just uploads the final new version of B in the end.

This bundling is realized in the shallot.Operations.Operation class. You get an instance of it in those different ways:

  • In the most situations, where filesystem operations potentially make sense, there is an instance directly available as a parameter in a method of your subclasses. The documentation of the Shallot interface classes should help.

  • Sometimes there is an instance available, but it is not directly available from the function parameters. In such cases, the documentation should help as well. A typical example is the execution of Actions. Your implementation of shallot.Actions.ActionAction.action gets called with the parameter info, which is a shallot.Actions.ExecutionInfo instance. It has the member shallot.Actions.ExecutionInfo.operation which returns a operation object.

  • In rare cases you have to explicitely create one. This is when you don’t get an existing instance from somewhere, but you need one for some reasones. You can create a new instance with shallot.Operations.Operation.create, but then it must be manually committed with shallot.Operations.Operation.commit in order to transfer all the new versions to their real destinations.

The key method for using the bundling mechanism is shallot.Operations.Operation.fetch_file (and shallot.Operations.Operation.fetch_container_file, which is just a convenience variant with a subtle difference in the arguments). Whenever you need to read the content of a file in order to eventually replace it with a modified version, you should use it.

Please note that operations are also by the only convenient method you would even have whenever your code potentially works with cascades of filesystems. Whenever you need to get the actual data streams of the affected archives for some location like zip:/[zip:/[ftp://foo/a.zip]//another.zip]//somephoto.jpeg, calling shallot.Operations.Operation.fetch_file for some part of this address is an easy way (e.g. a shallot.Filesystem.Handler implementation for a custom archive format will need to do that all the time).

For sample code, read the sources of the ‘operation’ sample plugin (in the Plugin Management Assistant).

The shallot.Operations.Operation class provide some more. Amongst others, there is the shallot.Operations.Operation.filesystem member, which provides you access to an instance of shallot.Operations.FilesystemOperation. It provides some primitive steps you can also execute in a bundled way. See the class documentation for details.

For sample code, read the sources of the ‘filesystemoperations’ and ‘filesystemoperations.advanced’ sample plugins.

The shallot.Operations namespace contains more interesting stuff.

Panel Details

Panel details can be provided by a plugin in order to provide the user with additional information about a selection of one or more files in the Details part of the Shallot window.

_images/paneldetails.png

This often is used for presenting some special kinds of file metadata.

The first step to a custom panel detail is to subclass either shallot.PanelDetails.SingleSelectionPanelDetail or shallot.PanelDetails.MultiSelectionPanelDetail. An instance of it must be registered with shallot.PanelDetails.PanelDetail.register.

For sample code, read the sources of the ‘paneldetails’ sample plugin (in the Plugin Management Assistant).

The shallot.PanelDetails namespace contains more interesting stuff.

File Property Dialog

The file property dialog is another place where plugins can provide custom pieces of information (and user interactions).

Dialog Tabs

A custom tab in the property dialog can show one or more information pieces in different views. It can also offer buttons for user interaction.

_images/filepropertydialogtab.png

The first step is to subclass shallot.FilePropertyDialog.Tab and to register this class with shallot.FilePropertyDialog.Tab.register.

For sample code, read the sources of the ‘filepropertydialogtab’ sample plugin (in the Plugin Management Assistant).

The shallot.FilePropertyDialog namespace contains more interesting stuff.

File Searches

Custom search criterion implementations bring additional functionality to Shallot file searches.

_images/filesearches.png

The first step is to subclass shallot.FileSearch.SearchCriterion and also shallot.FileSearch.SearchCriterionFactory (the latter one has to refer to the former one in the constructor call; see class documentations). Register the latter one with shallot.FileSearch.SearchCriterionFactory.register.

For sample code, read the sources of the ‘searchcriterion’ sample plugin (in the Plugin Management Assistant).

The shallot.FileSearch namespace contains more interesting stuff.

Thumbnails

Thumbnails are visible in some view modes only. In order to support a new file format or to show certain custom thumbnail content in particular situations, a plugin can add a thumbnail provider implementation to the thumbnail creation mechanism.

_images/thumbnails.png

The first step is to subclass shallot.ThumbnailProvider and to register an instance of this class with shallot.ThumbnailProvider.register.

For sample code, read the sources of the ‘thumbnailprovider’ sample plugin (in the Plugin Management Assistant).

Bookmarks

Bookmarks are shortcuts somewhere in the user interface, which allow the user to fastly jump to some common filesystem locations.

_images/bookmarks.png

The bookmark menu becomes visible once some bookmarks exist. A plugin can deal with bookmarks as well. It can create and manage them with the methods in shallot.Bookmarks.

For sample code, read the sources of the ‘bookmarks’ sample plugin (in the Plugin Management Assistant).

Logging

Plugins can write all kinds of information to the Shallot log in order to find potential issues. See shallot.Logging for details.

Utilities

There are some utilities for working with the environment available in shallot.Environment. This includes shallot.Environment.Thread for running code asynchronously, shallot.Environment.Timer for running a code in regular intervals and more.