The plugin class
This page shows the class that every plugin has, ITildePlugin, and how Tilde orders the plugins, registers them and starts their services.
1. Make the project
Section titled “1. Make the project”A plugin is a class library in the plugins/ folder of the repository. It references Tilde.Abstraction (the interfaces of mail, chat, configuration and the services) and Tilde.Core (the plugin interface and the bootstrap). This is the part of Tilde.Plugin.Mail.Mbox.csproj that does it:
<ItemGroup> <InternalsVisibleTo Include="Tilde.Tests" /></ItemGroup>
<ItemGroup> <ProjectReference Include="..\..\src\Tilde.Abstraction\Tilde.Abstraction.csproj" /> <ProjectReference Include="..\..\src\Tilde.Core\Tilde.Core.csproj" /></ItemGroup>Add the project to Tilde.slnx, so dotnet build Tilde.slnx builds it.
2. Write the class
Section titled “2. Write the class”The class implements ITildePlugin: a Metadata property and a RegisterAsync method. This is MboxPlugin.cs without its logger:
public sealed class MboxPlugin : ITildePlugin{ /// <inheritdoc /> public PluginMetadata Metadata { get; } = new("tilde.mail.mbox", "Mbox", new Version(1, 0)) { Author = "tom (tom@orivega.io)", Url = new Uri("https://codeberg.org/squidd/Tilde") };
/// <inheritdoc /> public ValueTask RegisterAsync(Container container, IConfigService config, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(container); ArgumentNullException.ThrowIfNull(config); cancellationToken.ThrowIfCancellationRequested();
container.Register<IConnectionFactory<IMailStore>, MboxMailStoreFactory>( Reuse.Singleton, ifAlreadyRegistered: IfAlreadyRegistered.AppendNotKeyed );
return ValueTask.CompletedTask; }}ITildePlugin is in Tilde.Core.Interfaces.Plugins, not in Tilde.Abstraction, because it hands the plugin the DryIoc Container.
What the metadata says
Section titled “What the metadata says”PluginMetadata is a record:
Idis unique and compared ignoring case. By convention it is lower case and dotted, astilde.mail.mbox. A blank id, or a second plugin with the same id, stops the start.Nameis shown to the user.Versionis the version of the plugin.AuthorisName (email), andUrlis the repository or the home page.Dependencieslists the plugins that must register first, asPluginDependencyrecords. The rules plugin needs the scripting plugin:
Dependencies = [new PluginDependency("tilde.scripting")]A dependency can also name the oldest version it accepts, as new PluginDependency("tilde.scripting", new Version(1, 0)). 1.0 and 1.0.0 are the same version.
What RegisterAsync does
Section titled “What RegisterAsync does”RegisterAsync only registers. It reads the sections of config.toml that the plugin owns and puts its services in the container. It does not resolve services and does not start work. Work that runs with the application goes in a service that implements IMoongateStartupService (StartAsync and StopAsync), registered with AddMoongateService and a priority:
container.AddMoongateService<IRuleEngineService, RuleEngineService>((int)ServicePriorityType.Plugins);That line comes from RulesPlugin.cs. Lower priorities start first and stop last (ServicePriorityType):
| Priority | Value | Who |
|---|---|---|
Core |
0 | the services of Tilde.Core, such as the event hub |
Plugins |
100 | the services of the plugins |
Accounts |
200 | the account service, which needs the factories of the plugins |
AfterAccounts |
300 | services that use the accounts when they start, such as the Lua engine |
How Tilde loads a plugin
Section titled “How Tilde loads a plugin”Tilde does not load plugin files. tcli and tt both call TildeHostBuilder.AddDefaultPlugins in src/Tilde.Host/TildeHostBuilder.cs, which adds the plugins in this order: IMAP, SMTP, Maildir, IRC, XMPP, OpenPGP, the mail cache, the Lua scripts and the rules. A new plugin of the program is added there, and its project is referenced by src/Tilde.Host/Tilde.Host.csproj:
bootstrap.AddPlugin<MaildirPlugin>();The mbox plugin is not in that list yet: its tests add it by hand. Loading the same plugin contract from a DLL file is planned and not done.
Then TildeBootstrap does this, in ConfigureAsync and StartAsync:
- It reads the core sections (
accounts,logging,jobs,keys,ui) and registers the core services. - It checks the whole batch of plugins: a missing dependency, one that is too old or a cycle stops here, before any
RegisterAsyncruns. - It calls
RegisterAsyncof each plugin, each one after the plugins it needs. Plugins that do not need each other keep the order they were added in. - It starts the services by priority, then in the order they were registered. If one fails, the ones already started are stopped.
An exception in RegisterAsync stops the start. Under tcli check it becomes one line, The plugin '<id>' did not start: …, and the other plugins go on.
Check that it is active
Section titled “Check that it is active”tcli plugins listId Name Version Author Needstilde.mail.imap IMAP 1.0 tom (tom@orivega.io) -tilde.scripting Lua scripting 1.0 tom (tom@orivega.io) -tilde.rules Rules 1.0 tom (tom@orivega.io) tilde.scriptingThat is a part of the list. From C#, IPluginService gives the same list, and IsActive("tilde.pgp") tells whether one plugin is there.
The plugin API version
Section titled “The plugin API version”A plugin binds to the assembly version of Tilde.Abstraction and Tilde.Core. That version is 1.0.0.0 and changes only on a breaking change of the API, never with a release (Directory.Build.props, checked by PluginApiVersionTests.cs). The version of the release, 0.4.0 today, is the file version.
Next: a mail plugin or a chat plugin.