Difference between revisions of "Writing an Extension"
m (Extending ccPublisher 2 moved to Writing an Extension) |
|
(No difference)
|
Revision as of 14:15, 17 April 2006
P6 based applications (including ccPublisher 2) can be extended by third-party developers using a loosely-coupled component architecture. At the time ccPublisher 2.0 is released CC will provide a sample extension which pings a user's blog when they successfully publish media, along with an installer which can be used as a reference.
THIS PAGE IS LARGELY THEORETICAL -- THIS IS HOW WE THINK THINGS WILL WORK, BUT THERE'S NOT MUCH CODE BEHIND IT -- YET
Contents
Extension Anatomy
Extensions are bits of code and configuration that can add or modify the functionality of a P6 Application. By default extensions are application-agnostic: they are only aware that they are operating in a P6 application, but have no direct knowledge of the specific application. This implies that when installed the extension will add it's functionality to all installed P6 Applications on a user's system.
An extension consists of (at least) two parts:
- code to perform your new functionality
- a ZCML file which registers your events, adapters and handlers
An extension may also contain:
- additional wizard pages to be presented in the application
- a configuration UI which allows the user to configure your extension
Registering Your Extension
Extension registration and configuration is done using ZCML. An extension's installation program should add the ZCML to P6's site-extensions directory. The location of the site-extensions directory can be found using the p6.api module.
A sample registration ZCML would look something like this:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:i18n="http://namespaces.zope.org/i18n" i18n_domain="org_cc_p6"> <!-- P6 Sample Extension Configuration (c) 2005, Nathan R. Yergler, Creative Commons licensed under the GNU GPL 2 --> <!-- If the extensions Python files are not installed on the regular P6 PythonPath, specify new path element(s) here --> <path>${site-extensions}/blog_ping</path> <extension id="org.cc.p6.sample.blog_ping" name="Blog Ping Example" config_factory="blogping.config" > <subscriber for="p6.storage.events.IStored" handler="blogping.handlers.afterStored" /> </extension> </configure>
In this example we first declare a <configure>
object, which all of our configuration will take place within. The i18n_domain attribute will be used for transparent internationalization.
The first non-comment inside configuration is our <path>
element. You can specify any number of path elements; the contents are interpolated and appended to the PYTHON_PATH of the application. This must be the first element if you are referencing code which is not in the standard Python path.
The remainder of the configuration takes place within an <extension>
element. The extension element defines an id and name (both required), and an optional config_factory. The value of config_factory is a Python callable which will display the extensions configuration UI when called. Note that Python must be able to import the package path (the value, less the final element); in this case, Python will attempt to import the blogping
module.
Finally, we define a <subscriber>
which registers a callable (blogping.handlers.afterStored) which will be called after all storage providers have processed the submission. This callable should be a standard Zope3 event-handler; that is, it will be passed a single parameter. That parameter will be an event object which implements p6.storage.events.IStored
.
Writing Handler Code
XXX: Need some sample code; working on fleshing out customization docs first.
Extension Patterns
Metadata Provider: Provide Metadata for a Specific File Type
The Metadata Provider pattern is used to extract additional metadata from files or retrieve it from outside sources. For example, ccPublisher uses this pattern to extract the title and artist from an MP3 using ID3 tags. There are two steps to implementing this pattern: listening for ItemSelected events and, if appropriate, publishing UpdateMetadata events.
Because a particular P6 application will have a specific metadata grouping, we use canonical URIs to specify fields outside of the group-field structure. For example, ccPublisher uses the following declaration in app.zcml to declare the Work Title field:
<field id="title" label="Title of Work" type="p6.metadata.types.ITextField" validator="ccpublisher.validators.validateTitle" canonical="http://purl.org/dc/elements/1.1/title" />
The canonical
property specifies the URI to use, regardless of grouping. In general, metadata providers included with P6 will map attributes to Dublin Core elements whenever possible. See Canonical IDs for more information.
To examine the details of a metadata provider we'll examine some relevant bits of the ID3 provider included with P6. The source for the provider is located at p6/storage/providers/id3.py. The provider function is registered in configure.zcml
as a subscriber:
<!-- Metadata provider registrations --> <subscriber for="p6.storage.events.IItemSelected" handler=".providers.id3.itemSelected" />
Listening for the IItemSelected events will notify the provider function, id3.itemSelected in this case, when the user selects a file. The actual provider function has the following signature:
def itemSelected(event): """IItemSelected event subscriber which provides ID3 metadata for MP3 files."""
The value passed in for event will be an object implementing IItemSelected. The ID3 provider then checks to make sure we're dealing with an actual file (as opposed to a stream, URI, etc) since that's what it knows how to handle:
# make sure a FileItem was selected if (p6.storage.interfaces.IFileItem in zope.interface.implementedBy(event.item.__class__)):
The IFileItem interface states that the .getIdentifier()
method will return the filename. We use this to retrieve the ID3 information. Once we have the metadata extracted, we publish an UpdateMetadataEvent for each field we found:
updateEvent = p6.metadata.events.UpdateMetadataEvent( event.item, 'http://purl.org/dc/elements/1.1/title', id3.getTitle() ) zope.component.handle(updateEvent)
Note that we specify event.item
as the item we're updating metadata for, and that we use the canonical URI for the field.
Extension Ideas and Plans
We hope to ship sample extensions with ccPublisher 2.0. Currently planning on:
- Blog ping extension: post to your blog when you upload new media
Other ideas we'd like to see implemented:
- Upload to my website (via FTP, etc) for self-hosting
- Upload to .Mac; this might also integrate with the DotMac SDK to allow easy signup for new users
Future Directions
Ideas which will probably not be implemented for ccPublisher 2 / P6 1.0 are:
- user interface for selectively enabling/disabling extensions in an application
- application-specific extensions