You'd think I'd only have to learn this once, wouldn't you?
For various reasons, I'm still using SharpReader as my RSS reader (I could just export my OPML and move, but I'm not happy to hit the panic button just yet. I did try to migrate to a much older version of RSS Bandit, but found lots of little bugs while importing my back catalogue, and it used just as much memory as SharpReader).
Unfortunately, SharpReader is a little, um, basic. It mostly suits my needs, but it would be really nice to have all the items grouped by published date (today, yesterday, last week, etc).
So I thought I'd add it. Hey, it's got a plugin model, it's almost asking to be hacked.
And oh boy, is this a hack. I have a class that implements IBlogExtension, but it doesn't implement any of the methods (so if you try and Blog This, it'll crash!). Instead, the plugin class subscribes to the Application.Idle event, spelunks around for the list view and subclasses it, using the lovely NativeWindow.AssignHandle. And that's just for starters.
I add the range of groups to the list view (the range is something like "next week", "later this week", "tomorrow", "today", "yesterday", "earlier this week", "last week", "two weeks ago", etc) and then loop through all the items in the list. Fortunately for me, Luke puts the RSS item class in the Tag property of the item, so I can get at it, check the date and assign the correct group.
There's a little bit of jiggery pokery with comments (indented items are comments, so should be in the same group as their parents, not their date. And of course, SharpReader is .net 1, so the IndentCount property of the ListViewItem isn't set) but I've now got a list of RSS items, grouped by date.
Where does the moral come in?
Things started to get a bit hairy when adding new items. Thanks to my pack rat mentality, I could have up to a couple of thousand items in the list view. Adding a new item shouldn't affect those I've already got grouped. And of course, when a new feed is downloaded, many items could be added one after the other. Again, we want to handle this as efficiently as we can.
Which to me meant only doing the update on application idle, and only updating the new items.
So, listening on WndProc for changes to the list (OCM_NOTIFY + LVN_INSERTITEM), looking up the corresponding ListViewItem and adding it to a dictionary.
On idle, I'd spin through the items in the dictionary and set the group. Disappointingly, this means that items get added to the end of the group, rather than being added into the group in the expected order. Let's chuck the unordered group into another dictionary and keep adding. Once all the items are done, loop through all affected groups, unassign all items, sort the items and add them back.
Hairy, right? And of course, because I was trying to be efficient, and store things in lookup tables, and only do things once, it's dead slow.
What I should have done was try the naive method first. The current code simply sets a flag when a new item is inserted. When the application goes idle, it unassigns all items from all groups, and reassigns. And it's dead fast.
Keep it simple, stupid.
(Oh, and about the horrifying nature of the code - hacks like this have their place, as long as you know and advertise that they are hacks and could fail in the worst possible way. Don't try and pass this off as production code. I know I'm running with scissors, but I'm an adult, they're my scissors, and I'm doing this in the safety of my own home.)