Plugin Development

From TimeSnapper
Jump to: navigation, search

What is a TimeSnapper Plugin?

A plugin is a way of extending and customizing the way TimeSnapper behaves.

At the code level, it is a .net class that implements an interface (ITimeSnapperPlugin) that TimeSnapper will seek out talk to.

At the file level, a TimeSnapper plugin is a dll that ends with the word "Plugin" -- for example "SamplePlugin.dll". It sits in a subfolder called "Plugins" located under the TimeSnapper folder. For example "C:\Program Files\TimeSnapper\Plugins".

A plugin can subscribe to TimeSnapper events, and it can add items to menus inside TimeSnapper.

What events can a plugin subscribe to?

A plugin can choose to be notified when any of these things happen:

  • When a snapshot is taken
  • When a snapshot is about to be saved
  • When a snapshot has been saved
  • When the flag dialog pops up (if you use the auto popup feature)
  • When a flag is about to be saved
  • When a flag has been saved
  • When the plugins have finished loading
  • When archiving is about to begin
  • When archiving is complete
  • When a snapshot is about to be deleted
  • When a snapshot has been deleted.
  • When the computer is idle.

What services does TimeSnapper provide to developers?

When TimeSnapper raises a event, a special services (of type IServices) variable will be provided - giving access to internal TimeSnapper functionality:

  • FlagServices - enables developers to set a flag, delete one or search for flags.
    • GetFlag by id or a flagdate
    • GetFlags by a date range
    • SaveFlag
    • DeleteFlag
  • SettingsServices - gives the developer access to TimeSnapper settings (read & write)
    • GetPropertyValue
    • GetPropertyValueDateTime
    • SetPropertyValue
  • DatabaseServices - Gives access to running arbitrary queries against TimeSnapper database
    • ExecuteQuery
  • WindowsServices - Gives access to TimeSnapper's main windows
    • ShowDayBrowser
    • ShowNewFlagForm
    • ShowForm(main | reports | timesheet | options | popup config | activitysearch)

Example of usage:

DataTable dt = services.DatabaseServices.ExecuteQuery("select count(*) from activity", "table1");

Where can a plugin extend TimeSnapper's menus?

In addition to subscribing to events, a plugin can tell TimeSnapper to put extra menu items into some of the context menus in the TimeSnapper application.

In particular a plugin can add menu items to these locations:

  • The context menu on the main image in the Day Browser screen (i.e. when you right click on a snap shot in the playback screen)
  • The context menu on the TimeBar, in the Day Browser screen. (i.e. when you right click on the little bar graph at the top of the playback screen)
  • The 'tools' menu on the Main Dialog
  • The context menu on the grid in the Activity Overview screen. (You get to that screen by pressing 'Find' on the Main Dialog, or in any of the go to menus.)

What can you do inside a plugin?

Just about anything you like, of course.

The standard answer we've given to tricky questions for the last year has always been "If you want to do that, you'll need to write a plugin".

A sample that ships with the latest version of TimeSnapper is called 'the Animated GIF' plugin. It lets you export a series of images as an animated gif.

Achieving this inside a plugin was quite trivial (we did it by following Jon Galloway's lead when he built the same plugin feature for Cropper. We tinkered with the NGif project at codeproject, and then leveraged public domain portions of the animated gif plugin that Jon wrote.)

Here are some more Plugin Ideas.

How do I write a TimeSnapper Plugin?

Create a new project in visual studio, of type "Class Library". (In other words, we will be creating a DLL.)

Add a reference to "ITimeSnapperPlugin.dll"

Give a suitable name to your class, and then implement "ITimeSnapperPlugin"

    using System;
    using TimeSnapperPluginAPI;
 
    namespace CSharpSamplePlugin
    {
        class AnotherPluginSample : ITimeSnapperPlugin 
        {

Create a new guid. We'll use this to make sure our plugin doesn't clash with any other plugins.

Return the guid you've created, from the 'PluginID' property getter.

Return the name of your plugin from the "Friendly Name" property getter:

        string ITimeSnapperPlugin.FriendlyName
        {
            get { return "My Sample Plugin"; }
        }

Next, you should return a paragraph describing your plugin from the "Description" property getter.

        string ITimeSnapperPlugin.Description
        {
            get { return "This plugin demonstrates some of the ways that plugins work."; }
        }

The two properties above are used by TimeSnapper for providing information about the plugin, inside the 'Options' dialog:

How do I subscribe to an event?

When TimeSnapper first loads up our plugin, it will ask it which events it wants to subscribe to.

It asks this question by calling the "SubscribesTo" method.

From "Subscribes to" you return an array of enums, chosen from the enumeration "TimeSnapperEvent"

So, if you want your plugin to subscribe to the "Flag Saved" event and the "Snapshot Saved" event, you would write this code:

        TimeSnapperEvent[] ITimeSnapperPlugin.SubscribesTo()
        {
            return new TimeSnapperEvent[] 
                    { TimeSnapperEvent.FlagSaved, 
                      TimeSnapperEvent.SnapshotSaved };
        }

How do I add menu items to TimeSnapper?

Similarly, when TimeSnapper loads up our plugin, it asks if there are any menu items it wants to add to any menus.

It asks this question by calling the "MenuItems" method, from which the plugin can return an array of TimeSnapperMenuItem classes.

How do I handle a TimeSnapper event?

When the event itself occurs, you are notified in a method called "HandleEvent".

In 'HandleEvent' you write a switch statement, (a Select case, in Visual Basic speak), with one case for each event you want to handle.

        Select Case TimeSnapperEvent
            Case TimeSnapperEvent.SnapshotSaved
                MessageBox.Show("A snap shot saved!")

Here's one for our example:

        object ITimeSnapperPlugin.HandleEvent
                (ByVal timeSnapperEvent As TimeSnapperEvent, ByVal services As IServices, ByVal args As EventArgs)
        {
            switch (TimeSnapperEvent)
            {
                case TimeSnapperEvent.FlagSaved :
                    Debug.WriteLine("A flag was saved");
                    break;
                case TimeSnapperEvent.SnapshotSaved :
                    Debug.WriteLine("A snapshot was saved");
                    break;
                default:
                    Debug.Assert(false, "Hey! I didn't subscribe to " + 
                          TimeSnapperEvent.ToString() + 
                          "... so this default case won't occur");
                    break;
            }
            return null;
        }

How can I let the end user configure my plugin?

Each plugin has a readonly Boolean property named "Configurable."

Most simple plugins do not need any configuration by the end user, so their authors would simply return 'false' from this property, like so:

        bool ITimeSnapperPlugin.Configurable
        {
            get { return false; }
        }

But if your Plugin is a little more complex, and requires the end user to configure it, you will want to return "true" from this property.

What exactly does that do? It means that the "Configure" button, in the "Plugin" tab of the Options dialog will be enabled, when your plugin is selected.

If "Configurable" returns true, then the button will be enabled. When the user presses the button, TimeSnapper will call the "Configure" method on your plugin.

It's up to you, as the plugin author, to decide what your plugin will do when the Configure method is called. You could show a modal windows dialog, you could launch a url inside a browser, you could show a WPF form -- anything you like.

It's also up to you how you will persist the user's choices across sessions (if required). You might for example, write an xml file into the user's app settings folder, or record settings in the registry.

Why do some of the events have 'Cancel' on the end of their name?

The full list of events you can subscribe to is provided in the enumeration TimeSnapperPluginAPI.TimeSnapperEvent and it looks like this:

    public enum TimeSnapperEvent
    {
        SnapshotTakingCancel = 0,
        SnapshotTakenSavingCancel = 1,
        SnapshotSaved = 2,
        AutoPoppingUpCancel = 3,
        FlagSavingCancel = 4,
        FlagSaved = 5,
        PluginsLoaded = 6,
        Closing = 7,
        ArchivingCancel = 8,
        Archived = 9,
        SnapshotDeletingCancel = 10,
        SnapshotDeleted = 11,
    }

Some events have the word 'Cancel' on the end of their name, because they are events that give you the opportunity to Cancel an activity from occuring.

For example, if you subscribe to the SnapshotTakenSavingCancel event, you will be notified whenever a snapshot has been taken and it is about to be saved. You will have the opportunity to cancel the saving of the snapshot.

All events pass an 'eventArgs' parameter -- whose base class is the familiar .net class "System.EventArg".

The events whose name ends with cancel, pass through a subclass of this, using the familiar "System.ComponentModel.CancelEventArgs" derivation.

You cancel an event by setting the Cancel property of the event args to 'True'.

If you cancel an event, then no other plugin will see that event, and the action won't go ahead. (The saving won't occur, in this example.) You can think about that from another point of view as well: any other plugin may cancel an event before it gets to you.

Here is how you cancel an event...

Make sure the event is one of the ones that end in 'Cancel'. Cast it to a System.ComponentModel.CancelEventArgs, and set it's cancel property to true.

        object ITimeSnapperPlugin.HandleEvent
                 (TimeSnapperEvent TimeSnapperEvent, EventArgs args)
        {
            switch (TimeSnapperEvent)
            {
                case TimeSnapperEvent.AutoPoppingUpCancel:
                    //No! we won't let the auto popup occurr...
                    ((System.ComponentModel.CancelEventArgs)EventArgs).Cancel = true;
                    break;

Here's a similar example in VB.net, where we stop a snap shot from being taken...

    Public Function HandleEvent(ByVal TimeSnapperEvent As TimeSnapperEvent, _
                      ByVal args As EventArgs) _
                      As Object Implements ITimeSnapperPlugin.HandleEvent
        Select Case TimeSnapperEvent
            Case TimeSnapperEvent.SnapshotTakingCancel
                'You plan to take a snapshot hey. Let me cancel that!
                Dim realargs As System.ComponentModel.CancelEventArgs
                realargs = CType(args, System.ComponentModel.CancelEventArgs)
                realargs.Cancel = True 'Stop it from happening!

Can I download some sample code, in C#?

Here is a very simple example of a plugin, written in C#

Sample Plugin -- C# (zip)

Can I download some sample code, in Visual Basic .net?

Here is a very simple example of a plugin, written in Visual Basic .net

Sample Plugin -- Visual Basic.net (zip)

Can I download some sample code, in F#, IronPython and IronRuby?

Sorry, example are not currently provided in languages other than C# and VB.net. Why not write one and share it with the world?

Why isn't my plugin loaded?

TimeSnapper looks for plugins in a subfolder called "Plugins" located under the TimeSnapper application folder.

For example "C:\Program Files\TimeSnapper\Plugins".

Here's the trickiest part... TimeSnapper only looks inside files whose name ends with "Plugin.dll".

So for example, "SamplePlugin.dll" would be inspected for ITimeSnapeprPlugin's -- but PluginSample.dll would NOT be inspected and loaded.

Just repeating that one more time: your DLL's name must end with "Plugin.dll" or it will not be loaded.

(Why is this done? This is so that if a plugin references a lot of supporting dlls, they can live in that same folder, without slowing down the startup time for TimeSnapper.)

How can I debug my plugin?

If errors occur during loading or activating a plugin, details are written to the TimeSnapper log file.

Also, you can emit debug statements from your plugin, and then look at them using a debug listener, such as the one provided by sysinternals.

Alternatively, you can set a break-point in Visual Studio and attach the debugger to Timesnapper.exe.

Can I share a plugin I've writen?

You are most welcome to share your plugins!

We intend to provide a gallery of plugins from TimeSnapper.com, to encourage their usage, and I would like to further promote them on my blog at secretGeek.net.

It is still very early days, but we are hoping for some activity here.

Can I make money from writing plugins?

You certainly may.

You are welcome to sell or to give away your plugins. You can include your own licensing registration mechanism within your plugin if you wish. We don't ask for a share of any royalties from your plugin, if it does make money. The only caveat we have is that we insist you don't use your plugins to circumvent the goals of TimeSnapper, or to damage our existing licensing and protection.

What plugins are available?

There are (at least) four plugins available currently. See Plugins for more information.

I have a great idea for a plugin. What do you think about this... ?

See Plugin_Ideas for some of the ideas we've developed or received so far. Add your own, or contact us and tell us.