In Petmail, plugins are twisted.application.service.MultiServices, which means they are children of some parent node. They get notified of various events, the most interesting of which are when messages are sent and received. The plugins have the opportunity to modify or inspect the message somehow, usually by adding and examining child nodes of the message body.
There are three places where plugins can be added (three possible parents). The location of the plugin determines which messages it gets to examine:
- the Entry (AddressBookEntry): these plugins look at each message to/from a given recipient. This is the place to do things like maintain shared state with a peer plugin in somebody else's agent, or keep track of what information has been sent to the remote end.
- the Owner: Owner plugins look at each message to/from the given Owner
- the Agent: Agent plugins get to look at all messages, which means each message of each recipient for all Owners.
The messages are examined in this order, so the Entry Plugin can, for example, remove a node from the message before the Agent Plugin sees it.
Plugins are expected to be subclassed from petmail.plugin.Plugin, which provides default functionality (plugins which do not subclass Plugin may suffer bitrot faster than those which do). They have the following methods invoked (and should remember to upcall to their parent class for most of them):
.startService
: run when the Agent begins running, or the plugin is first added to a running agent..stopService
: run when the Agent is shutting down (but after the plugin has been persisted), or when the plugin is removed..outboundMessage(msg)
: run with an outbound message. The plugin can manipulate the message as it likes, but remember that other plugins are active too, so it should not do anything to the message that could interfere with other plugins. This method can return a Deferred to make the delivery process wait for it. (TODO: dependency checking, tags to indicate what features this plugin provide)msg
: a Message instancemsg.recipientEntry
: the AddressBookEntry to which the message is being sent. For AddressBookEntryPlugins, this is the same asself.entry()
.msg.senderEntry
: the OwnerEntry which is sending the message. For both AddressBookEntryPlugins and OwnerPlugins, this is the same asself.owner()
.
.inboundMessage(msg)
: run with an inbound message. The plugin can inspect the message for anything it likes. The plugin can return a Deferred if it wants to hold off on delivering the message to the user for some reason. (TODO: dependency checking, etc).msg
: a ReceivedMessage instancemsg.senderEntry
: the AddressBookEntry to which the message is being sent. For AddressBookEntryPlugins, this is the same asself.entry()
.msg.recipientEntry
: the OwnerEntry which is sending the message. For both AddressBookEntryPlugins and OwnerPlugins, this is the same asself.owner()
.
EntryPlugins also have the following additional methods:
.entryChanged(action) action="add": the plugin was just added action="change": the Entry has been modified action="delete": the Entry is about to be deleted
Owner Plugins also get the following:
.entryChanged(action, entry): action="add": the Entry was just added action="change": the Entry has been modified action="delete": the Entry is about to be deleted
Agent Plugins also get the following:
.entryChanged(action, entry): action="add": the Entry was just added (to some child Owner) action="change": the Entry has been modified action="delete": the Entry is about to be deleted .ownerChanged(action, owner) action="add": the Owner was just added action="change": the Owner has been modified action="delete": the Owner is about to be deleted
Installing Plugins
good question
Once you have a Plugin instance, it is easy to add or remove it:
agent.addPlugin(plugin) ; agent.removePlugin(plugin) owner.addPlugin(plugin) ; owner.removePlugin(plugin) entry.addPlugin(plugin) ; entry.removePlugin(plugin)
Some standard plugins are always added: the UpdateTheirIDRecord plugin is added to an Entry as soon as that Entry is added to an AddressBook.
Other standard plugins are added if the IDRecord specifies that the other end needs it: the SURBSupply plugin is added to the Entry upon receipt of a SURB node, and the SURBStocker plugin is added to the Owner if its "secure" flag is set.
New plugins are created and added by higher-level plugins. Adding an Agent Plugin is the petmail extension mechanism. When this extension is first added, it can look through all Owners and Entries, and may add a plugin to each one.
It may also add a plugin in response to an incoming message node. For example, the "Cute Furry Lamb" plugin displays an animated sheep in the addressbook for each correspondent who also has the CuteFurryLamb plugin. This sheep is updated with each new message. The IDRecord contains a node which indicates that the recipient has this plugin enabled and wishes to exchange sheep data with similarly-capable correspondents.
The CuteFurryLambAgentPlugin will do something (TBD) to give the owner the option of enabling the sheep for each Owner: some kind of configuration interface. When this is turned on, it adds a CuteFurryLambOwnerPlugin to that owner, which then lets the user configure what their sheep looks like, etc.
The CuteFurryLambOwnerPlugin, when activated or when the configuration changes, modifies the IDRecord to include the sheep-advertisement node. It also schedules the IDRecord for updating / republishing.
On the receiving side, CuteFurryLambOwnerPlugin.entryChanged("add"/"change") notices the sheep-advertising node in the IDRecord, and adds a CuteFurryLambEntryPlugin to the corresponding entry. From then on, each time a message is sent to that correspondent, the EntryPlugin is given a chance to add sheep-updating nodes to the message. Likewise, each time a message arrives from the correspondent, the EntryPlugin gets to extract the sheep-update node and copy its cotents into the local plugin state. Later, when the address book is displayed, the EntryPlugins will (TBD) get a chance to influence how the entry is displayed, and can include the sheep animation.
SURBStockers
The SURBStocker is responsible for keeping something (either a SURB Server or a correspondent's SURBSupply) supplied with SURBs. The process starts when an Owner is created with the "secure" flag set. This adds a SURBStockerOwnerPlugin to the OwnerEntry, which has two jobs: keeping the SURB Server supplied, and adding a response block to outgoing messages to new recipients. It also watches for new Entries to be created and installs SURBStockerEntryPlugins in them.
The SURBStockerEntryPlugin keeps track of how many valid SURBs a given correspondent has available, and adds SURB nodes to outgoing messages as necessary to keep a reasonable supply.
SURBSupply
The SURBStocker talks to a SURBSupply. The always-present SURBSupplyAgentPlugin looks for SURB nodes in all received messages. These indicate that the other end wishes to keep us stocked, but that no EntryPlugin caught the node first. The SURBSupplyAgentPlugin responds by creating a SURBSupplyEntryPlugin and giving it the SURB. Once installed, the SURBSupplyEntryPlugin will spontaneously ask the remote end for more SURBs (piggybacking on other messages, if possible) to maintain its supply. The outbound transport logic knows to ask any SURBSupplyEntryPlugins for a SURBTransport before falling back to whatever the IDRecord specifies.
UpdateTheirIDRecord
All Entries have an UpdateTheirIDRecord EntryPlugin, which is responsible for making sure the remote correspondent has a reasonably up-to-date copy of the owner's IDRecord. (TBD: what is "reasonable"? like DNS but more complicated). This EntryPlugin is installed as soon as the Entry is added to the addressbook.