Difference between revisions of "Plugin Development"

From TimeSnapper
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
== What is a TimeSnapper Plugin? ==
 
== What is a TimeSnapper Plugin? ==
  
A plugin is a way of extending and customizing the way TimeSnapper behaves.
+
A plugin is a way of extending and customizing the way [http://timesnapper.com 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 code level, it is a .net class that implements an interface (ITimeSnapperPlugin) that TimeSnapper will seek out talk to.
Line 24: Line 24:
 
* When a snapshot has been deleted.
 
* When a snapshot has been deleted.
 
* When the computer is idle.
 
* 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? ==
 
== Where can a plugin extend TimeSnapper's menus? ==
Line 55: Line 78:
 
Give a suitable name to your class, and then implement "ITimeSnapperPlugin"
 
Give a suitable name to your class, and then implement "ITimeSnapperPlugin"
  
 +
<source lang="csharp">
 
     using System;
 
     using System;
 
     using TimeSnapperPluginAPI;
 
     using TimeSnapperPluginAPI;
Line 62: Line 86:
 
         class AnotherPluginSample : ITimeSnapperPlugin  
 
         class AnotherPluginSample : ITimeSnapperPlugin  
 
         {
 
         {
 +
</source>
 
Create a new guid. We'll use this to make sure our plugin doesn't clash with any other plugins.
 
Create a new guid. We'll use this to make sure our plugin doesn't clash with any other plugins.
  
Line 68: Line 93:
 
Return the name of your plugin from the "Friendly Name" property getter:
 
Return the name of your plugin from the "Friendly Name" property getter:
  
 +
<source lang="csharp">
 
         string ITimeSnapperPlugin.FriendlyName
 
         string ITimeSnapperPlugin.FriendlyName
 
         {
 
         {
 
             get { return "My Sample Plugin"; }
 
             get { return "My Sample Plugin"; }
 
         }
 
         }
 +
</source>
 +
 
Next, you should return a paragraph describing your plugin from the "Description" property getter.
 
Next, you should return a paragraph describing your plugin from the "Description" property getter.
 
+
<source lang="csharp">
 
         string ITimeSnapperPlugin.Description
 
         string ITimeSnapperPlugin.Description
 
         {
 
         {
 
             get { return "This plugin demonstrates some of the ways that plugins work."; }
 
             get { return "This plugin demonstrates some of the ways that plugins work."; }
 
         }
 
         }
 +
</source>
 +
 
The two properties above are used by TimeSnapper for providing information about the plugin, inside the 'Options' dialog:
 
The two properties above are used by TimeSnapper for providing information about the plugin, inside the 'Options' dialog:
  
Line 90: Line 120:
 
So, if you want your plugin to subscribe to the "Flag Saved" event and the "Snapshot Saved" event, you would write this code:
 
So, if you want your plugin to subscribe to the "Flag Saved" event and the "Snapshot Saved" event, you would write this code:
  
 +
<source lang="csharp">
 
         TimeSnapperEvent[] ITimeSnapperPlugin.SubscribesTo()
 
         TimeSnapperEvent[] ITimeSnapperPlugin.SubscribesTo()
 
         {
 
         {
Line 96: Line 127:
 
                       TimeSnapperEvent.SnapshotSaved };
 
                       TimeSnapperEvent.SnapshotSaved };
 
         }
 
         }
 +
</source>
  
 
== How do I add menu items to TimeSnapper? ==
 
== How do I add menu items to TimeSnapper? ==
Line 109: Line 141:
 
In 'HandleEvent' you write a switch statement, (a Select case, in Visual Basic speak), with one case for each event you want to handle.
 
In 'HandleEvent' you write a switch statement, (a Select case, in Visual Basic speak), with one case for each event you want to handle.
  
 +
<source lang="vbnet">
 
         Select Case TimeSnapperEvent
 
         Select Case TimeSnapperEvent
 
             Case TimeSnapperEvent.SnapshotSaved
 
             Case TimeSnapperEvent.SnapshotSaved
 
                 MessageBox.Show("A snap shot saved!")  
 
                 MessageBox.Show("A snap shot saved!")  
 +
</source>
 +
 
Here's one for our example:
 
Here's one for our example:
  
 +
<source lang="csharp">
 
         object ITimeSnapperPlugin.HandleEvent
 
         object ITimeSnapperPlugin.HandleEvent
                 (TimeSnapperEvent TimeSnapperEvent, EventArgs args)
+
                 (ByVal timeSnapperEvent As TimeSnapperEvent, ByVal services As IServices, ByVal args As EventArgs)
 
         {
 
         {
 
             switch (TimeSnapperEvent)
 
             switch (TimeSnapperEvent)
Line 133: Line 169:
 
             return null;
 
             return null;
 
         }
 
         }
 +
</source>
  
 
== How can I let the end user configure my plugin? ==
 
== How can I let the end user configure my plugin? ==
Line 140: Line 177:
 
Most simple plugins do not need any configuration by the end user, so their authors would simply return 'false' from this property, like so:
 
Most simple plugins do not need any configuration by the end user, so their authors would simply return 'false' from this property, like so:
  
 +
<source lang="csharp">
 
         bool ITimeSnapperPlugin.Configurable
 
         bool ITimeSnapperPlugin.Configurable
 
         {
 
         {
 
             get { return false; }
 
             get { return false; }
 
         }
 
         }
 +
</source>
 
 
 
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.
 
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.
Line 159: Line 198:
 
The full list of events you can subscribe to is provided in the enumeration TimeSnapperPluginAPI.TimeSnapperEvent and it looks like this:
 
The full list of events you can subscribe to is provided in the enumeration TimeSnapperPluginAPI.TimeSnapperEvent and it looks like this:
  
 +
<source lang="csharp">
 
     public enum TimeSnapperEvent
 
     public enum TimeSnapperEvent
 
     {
 
     {
         SnapshotTaking'''Cancel''' = 0,
+
         SnapshotTakingCancel = 0,
         SnapshotTakenSaving'''Cancel''' = 1,
+
         SnapshotTakenSavingCancel = 1,
 
         SnapshotSaved = 2,
 
         SnapshotSaved = 2,
         AutoPoppingUp'''Cancel''' = 3,
+
         AutoPoppingUpCancel = 3,
         FlagSaving'''Cancel''' = 4,
+
         FlagSavingCancel = 4,
 
         FlagSaved = 5,
 
         FlagSaved = 5,
 
         PluginsLoaded = 6,
 
         PluginsLoaded = 6,
 
         Closing = 7,
 
         Closing = 7,
         Archiving'''Cancel''' = 8,
+
         ArchivingCancel = 8,
 
         Archived = 9,
 
         Archived = 9,
         SnapshotDeleting'''Cancel''' = 10,
+
         SnapshotDeletingCancel = 10,
 
         SnapshotDeleted = 11,
 
         SnapshotDeleted = 11,
 
     }
 
     }
 +
</source>
 +
 
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.
 
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.
  
Line 189: Line 231:
  
 
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.
 
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.
+
 
 +
<source lang="csharp">
 
         object ITimeSnapperPlugin.HandleEvent
 
         object ITimeSnapperPlugin.HandleEvent
 
                 (TimeSnapperEvent TimeSnapperEvent, EventArgs args)
 
                 (TimeSnapperEvent TimeSnapperEvent, EventArgs args)
Line 199: Line 242:
 
                     ((System.ComponentModel.CancelEventArgs)EventArgs).Cancel = true;
 
                     ((System.ComponentModel.CancelEventArgs)EventArgs).Cancel = true;
 
                     break;
 
                     break;
 +
</source>
 +
 
Here's a similar example in VB.net, where we stop a snap shot from being taken...
 
Here's a similar example in VB.net, where we stop a snap shot from being taken...
  
 +
<source lang="vbnet">
 
     Public Function HandleEvent(ByVal TimeSnapperEvent As TimeSnapperEvent, _
 
     Public Function HandleEvent(ByVal TimeSnapperEvent As TimeSnapperEvent, _
 
                       ByVal args As EventArgs) _
 
                       ByVal args As EventArgs) _
Line 210: Line 256:
 
                 realargs = CType(args, System.ComponentModel.CancelEventArgs)
 
                 realargs = CType(args, System.ComponentModel.CancelEventArgs)
 
                 realargs.Cancel = True 'Stop it from happening!
 
                 realargs.Cancel = True 'Stop it from happening!
 +
</source>
  
 
== Can I download some sample code, in C#? ==
 
== Can I download some sample code, in C#? ==
Line 253: Line 300:
 
You are most welcome to share your plugins!
 
You are most welcome to share your plugins!
  
We intend to provide a gallery of plugins from on TimeSnapper.com, to encourage their usage, and I would like to further promote them on my blog at secretGeek.net.
+
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.
 
It is still very early days, but we are hoping for some activity here.
Line 264: Line 311:
  
 
== What plugins are available? ==
 
== 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 [http://timesnapper.com/contact.aspx contact us] and tell us.
 +
 +
  
Currently, the only plugins available are the Animated Gif plugin and the sample plugins. We hope to have more available soon. (Written March 2009)
+
[[Category:Plugins]]

Latest revision as of 06:58, 7 November 2009

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"

<source lang="csharp">

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

</source> 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:

<source lang="csharp">

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

</source>

Next, you should return a paragraph describing your plugin from the "Description" property getter. <source lang="csharp">

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

</source>

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:

<source lang="csharp">

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

</source>

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.

<source lang="vbnet">

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

</source>

Here's one for our example:

<source lang="csharp">

       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;
       }

</source>

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:

<source lang="csharp">

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

</source>

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:

<source lang="csharp">

   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,
   }

</source>

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.

<source lang="csharp">

       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;

</source>

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

<source lang="vbnet">

   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!

</source>

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.