Skip to content

Plugins

IS4 edited this page Jul 20, 2023 · 2 revisions

Plugins are a way to add custom components that provide support for new features or augment existing ones. They are loaded from the plugins directory, or via the --plugin command-line option.

A plugin is generally packed as a ZIP file, storing a DLL file with the same bare name as the archive (when loaded from the plugins directory, it may also be unzipped and loaded as a directory inside plugins). The DLL file should be a .NET assembly containing the component types that should be loaded and instantiated. Hence a plugin should reference the IS4.SFI assembly, otherwise no component will be loaded.

All public types in the plugin assembly are browsed to see if they can be used as a component. In addition, types forwarded to other assemblies (as indicated by [assembly: TypeForwardedTo]) are also taken into account.

Plugin sources

Via the --plugin option, a plugin may be loaded from a multitude of sources, including paths or URIs. It is also possible to load a plugin from NuGet using the nuget:id/version syntax (version is optional), which will also pull all its dependencies if needed. To load official SFI components, it is possible to use sfi:, for example sfi:formats.images, which is the same as nuget:is4.sfi.formats.images.

Component types

In order for a type to be loaded as a component, it should implement an interface based on one of the component collections. This type can be determined via the about command. By default, the expected interfaces are:

  • IS4.SFI.Services.IEntityAnalyzer<T> ‒ for the analyzer collection, able to analyze entities of type T.
  • IS4.SFI.Formats.IBinaryFileFormat ‒ for the data-format collection, used by the data analyzer.
  • IS4.SFI.Formats.IXmlDocumentFormat ‒ for the xml-format collection, used by the XML analyzer.
  • IS4.SFI.Services.IDataHashAlgorithm ‒ for the data-hash collection, used by the data analyzer (also mirrored in pixel-hash).
  • IS4.SFI.Services.IFileHashAlgorithm ‒ for the file-hash collection, used by the file analyzer.
  • IS4.SFI.Services.IContainerAnalyzerProvider ‒ for the container-format collection to analyze complex node structures.
  • IS4.SFI.Services.IObjectHashAlgorithm<System.Drawing.Image> ‒ for the image-hash collection, used by the image analyzer.

A type can implement all of these interfaces, in which case it will be instantiated only once and added to all supported collections. In addition to that, a type may also implement IEnumerable<T> or IAsyncEnumerable<T> where T matches one of the already recognized types, in which case it is instantiated and enumerated to obtain the final components.

In order to be selected, component types should not be internal, abstract, generic, nested, or marked [Browsable(false)], in which case they are skipped entirely even if they match one of the interfaces above.

Construction

When such a type is encountered, dependency injection is used to select the appropriate constructor. These services are provided to it, distinguished by their type:

  • Inspector – the inspector instance that is loading the plugin,
  • ILogger – an instance used for logging messages,
  • IDirectoryInfo – the directory where the main assembly of the plugin is located.

Configuration

Components can be configured from the application, allowing it to assign their properties to different values, or to specify other components that they can use. For this purpose, .NET's design-time services are used instead of reflection, with TypeDescriptor.GetProperties being called to obtain the list of configurable properties (which defaults to reflection if no other provider is registered). Properties marked [Browsable(false)] are removed from any consideration.

Assignable properties

A property must satisfy a couple of criteria in order to be accessible through command-line configuration or XML:

  • It must be public (other properties are not accessible by TypeDescriptor.GetProperties).
  • It must not be read-only (usually have a set accessor).
  • Since the command-line arguments are text only, its value must be convertible to and from string using the property's converter:
    • All types are initially convertible to string by the virtue of having the ToString method.
    • Conversion from string can achieved by using primitive types, enums, or types using [TypeConverter] to provide the conversion. This attribute can also be placed on the property itself. When using a custom converter, it is also recommended to implement GetStandardValues to give hints as to what values are commonly used for the type.

Additionally, in order for the conversion to be considered successful, it must return a non-null value (unless the original string is empty).

A type may also implement ISupportInitialize, which is used to enclose property assignment in a single batch by the application.

Clone this wiki locally