EOAST #4 – Evolution of a software thingy Part 4
So its been ages since my last post on this pet project of mine. Its time to get down to the nitty gritty on this and see if it is actually working the way I want it to. Hopefully, you have read parts one, two and three in previous posts to get a good idea on what I am trying to achieve.
Basically, its a collect, store and display mechanism to store almost any information in a SQL database. You only need concentrate on implementing collection logic, or only display logic or both if you so choose. Its up to you. The storage and querying of this information is done for you, as is the scheduling of collection. Additionally, its gotta be real robust. If someone writes a crappy information provider, the system should not die. Also, I want to be able to map an information provider to one or more display providers, all easily.
I have dubbed this the InformationAggregator.
So I am happy to say its actually working reasonably well at the moment. Since its a pet project of mine, the fact it is actually up to a reasonable level of functionality is quite an achievement. Normally I kinda let things go once I have satisfied my initial curiosity, or whatever it was that got me interested in doing it. What I haven’t really spent much time with is the user interface. Its really just a set of buttons on a form to exercise the API.
At any rate, the basic details are as follows.
There are 2 main assemblies: InformationAggregator.Core.dll and Information.Aggregator.Engine.dll.
If you want to implement an information collection provider or an information display module, you only need to reference the Core assembly. The Engine assembly contains all the functionality to start the hosting and collection engine.
Note: Since this codebase is always being revised, its prone to change so there maybe much refactoring in the future. In fact, part of it will be happening after this post ":-)
Writing an Information Collection provider
Say you want to write a module which collects email, accepts user input, reads a users twitter entries, or some other information collection mechanism. You only need to Implement 2 interfaces: IPluginModule, & IInformationProvider.
IPluginModule has only 1 method,
QueryPluginResponse QueryPluginModule()
which acts to provide some metadata around itself. Here is an example:
public QueryPluginResponse QueryPluginModule()
{
QueryPluginResponse response = new QueryPluginResponse();
response.MajorVersion = 1;
response.MinorVersion = 0;
response.PluginDescription = "Test Manual Collection. Only sends information to engine once collected from keyboard entry";
response.PluginName = "ManualCollector";
response.Publisher = "Glav";
response.PublisherLink = "http://www.theglavs.com";
response.RevisionNumber = 0;
response.SupportsCollectionNotification = false;
response.TypeOfService = ServiceType.InformationProvider;
return response;
}
IInformationProvider also requires the implementation of only 1 method,
List<GenericInformationItem> GetAllInformation()
Here is an example:
public List<GenericInformationItem> GetAllInformation()
{
List<GenericInformationItem> list = new List<GenericInformationItem>();
Random rnd = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < 3; i++)
{
GenericInformationItem item = new GenericInformationItem();
item.Author = "Paul Glavich";
item.BodyTextData = string.Format("This is dummy body entry ID:# {0}. The current date is {1} and the time is {2}", rnd.Next(1,200),DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString());
item.InformationSource = "Unit Test";
item.ReceivedDateTime = DateTime.Now;
item.Originator = "TestInformationProvider";
item.PublishDate = DateTime.Now;
item.SourceProvider = PROVIDER_NAME;
item.Summary = "Just a test item";
item.Title = string.Format("Test Item #{0}", i);
list.Add(item);
}
return list;
}
This implementation simply sends 3 items of information to the InformationAggregator engine when its asked to do so. This particular implementation is well suited to periodic, or scheduled collection. The engine can be asked to collect information from modules every minute, hour, day, week etc.
The engine also supports manual collection where it does not schedule collection at specific intervals, but waits for the provider to send it information. A third option is where the engine instructs the module to begin collection, then returns to its business, allowing that module to take its sweet time about collection (it may be very time consuming) and then send it the collected items. Status events of the collection process is also supported.
Note that the implementation is very simple. It would be easy to write some logic to query some RSS feeds, twitter feeds, or whatever. All the scheduling and storage is performed via the engine through configuration. Currently I have a very crude user interface (and a I mean very crude) to control all this. Basically some buttons to exercise the functionality of the API:
Only collection modules that have been registered, have their items accepted and stored via the engine in the database. When registering, the IQueryModule interface is invoked to gather and display some metadata to display for confirmation when registering:
Writing an Information Display provider
To display information, again we implement only 2 interfaces: IPluginModule, & IInformationDiplay.
IPluginModule was already discussed above and is a pre-requisite for any module, either display or collection.
IInformationDiplay has 2 methods to implement:
void PushInformation(List<GenericInformationItem> infoItems );
void PushStatus(QueryPluginResponse plugin, int percentCollectionCompleted, CollectionStatus statusIndicator, string statusMessage);
The ‘PushInformation’ method has the information ‘pushed’ to it, and you can do what you want with the info from there. (Note: Not really happy with these method names, but I’ll leave them for now)
The ‘PushStatus’ method gets called repeatedly during the collection phase, if the information provider is providing this information.
The display provider can be a simply console app, or more typically, a WPF application. These all need to be registered as described above.
However, one more pre-requisite is required if its a WPF application. A parameterised constructor must be provided that accepts the WPF window to be used for display. The engine will search the WPF application for this constructor and construct an instance of this window when creating this module, and maintain this, so that display items can be sent to the module, and threading affinity can be easily achieved.
Linking providers to display modules.
So we can easily create an information collection provider. We can also easily create a display provider. How do we get information from collection provider to display provider. We simply map one or more collection modules to a display provider. Here is an example of the (again very crude) UI to do this:
This UI shows 2 information providers, ManualCollector and TestInformationProvider. Currently, only the WPFDisplayProvider#2 is mapped to the TestInformationProvider, with the BalloonPopup display provider available, but not mapped to this module. This means that any information that the TestInformationProvider collects, is currently automatically sent to the WPFDisplayProvider#2. If we want that information to be sent to 2 display providers, we can simply click on the BalloonPopup display provider and move it into the mapped display providers.
Now when the engine starts, all information collected by the TestInformationProvider is automatically sent to both the WPFDisplayProvider#2 and the BalloonPopup display provider. Basically you can pick and choose how your information is collected, and how it is displayed.
So thats the concept. Control the collection of your information, and choose a display method that matches what you want. Additionally, have the information stored in an easily queryable store.
There is actually much more, but I’ll leave it for now. Next step will be a demo, so look for a short video on how it works and specifically the WPF implementation of the display providers.