Spam Filtering / Mozilla Thunderbird

The system administrator at work recently installed or subscribed to (I’m not entirely sure how it works) spam filtering software supplied by VeriSign.  Now, instead of a hundred or more junk email messages clogging my mailbox every day, suspect messages are quarantined and I’m given the opportunity to use a Web browser to sift through the messages.  I’m happy to see my Inbox shrink, and it’s good to know that internal messages (messages that originate from inside the company firewall) won’t be marked as spam, but I’m disappointed that I have to go to yet another Web site and use yet another crappy browser interface to sift through the suspect messages.  You’d think that VeriSign could afford a better interface; maybe even borrow a few ideas from POPFile.

Speaking of POPFile, I’ve stopped using it.  At least, I’m not using the standalone version.  I’ve installed Mozilla Thunderbird on my primary Windows machine and have moved my email back from the Linux box.  Thunderbird is a much nicer email client than Evolution, and it has a built-in adaptive spam filter that appears to work rather well.  Somebody said that this is a version of POPFile, something which I haven’t taken the time to verify.  Whatever the case, there’s a lot to like in Thunderbird.  I’ll give it a mini review here after I get a little more comfortable with it.

The Grab Bag

I’ve been very busy here keeping up with work, bicycling, and a few odd projects that have been hanging fire for far too long.  I have a whole grab bag full of stuff to write about, but little time to spend serious effort on it.  Some quick notes:

  • Monday CBS news issued a statement saying that they are unable to prove the authenticity of the suspect Bush National Guard memos.  They “deeply regret” running the 60 Minutes segment that started it all.  Dan Rather, trying to sound intelligent while removing his foot from his mouth, insisted that the memos’ veracity was irrelevant and that the important issue was that Bush shirked his National Guard duties.  Seeing as how the suspect memos were the only tangible evidence supporting the case, I’d say that their authenticity is the only issue.  Don’t choke on that foot, Dan.  This case of sensationalism outweighing professionalism illustrates very succinctly the reason I and many others have come to distrust the major news outlets.
  • Being an avid cyclist, I follow the local weather closely, but this is the first year I’ve paid much attention to global weather, especially hurricanes.  Since I discovered the National Hurricane Center‘s Web site a couple of weeks ago, I’ve been keeping close watch on the storms coming our way.  I’m intrigued by the forecast models that they mention in their discussions.  I’m also somewhat amused by the meandering track that hurricane Jeanne has taken:  coming up from the south Atlantic, dumping on Puerto Rico and Hispaniola, wandering around in the Atlantic for a while and then doing a loop and heading west again.  From the looks of things, the Bahamas are going to get slammed on Saturday and Florida on Sunday.  Meanwhile, the remains of hurricane Ivan are on the Texas/Louisiana cost and will be dumping rain on me over the weekend.
  • The Naval Research Laboratory has a Hurricanes, Typhoons and Tropical Cyclones page that shows active storms and maintains a history of storms back to 1997.  It has some good satellite photos and also displays the past and projected track of all active storms.  Well worth bookmarking if you’re interested in watching the storms.
  • I failed to follow up on a couple of previous entries.  My truck survived the timing belt replacement (see August 17).  It cost me almost as much as the truck is worth, but it’s cheaper than buying a new vehicle.  Even with General Motors and Ford offering zero percent financing for six years, a replacement would still cost me over $300 per month, plus the increase in insurance premiums on the new car.  At those prices, I’ll keep driving this truck for another eight years.
  • The problem I’ve been having with ADO.NET (see September 3) appears to be related to a documented bug described in this Microsoft Knowledge Base article.  I wasn’t able to find any specific mention of Relations in the bug report or discussions of it online, but the symptom (the exception) is the same and the cause (changing a related child key column) is the same, so I’ll call it a manifestation of the same bug and hope that Microsoft fixes it with the next service pack.
  • The person throwing things out his car window at Debra and me when we’re riding (see September 2) struck again on Monday.  This time it was another drink bottle that struck my rear wheel.  That’s four attempts that I know of and three hits.  I admire his accuracy, but don’t really like being a target.  I verified his license plate and called the Sheriff’s office again.  It’s probably a good thing that I’m too cheap to pay the $30 it’d take to run the plate myself.
  • Just six more weeks until the election.  It won’t come soon enough.  It’s gotten to the point that I dread even looking at the newspaper or any online news feeds.  I stopped listening to the radio a month ago.  I’m hoping there’s a “none of the above” selection on the ballot.  It’s easy to blame the candidates for focusing on emotional topics rather than offering realistic approaches to real issues, but it’s not really their fault.  The American people seem to view politics, especially Presidential politics, as some kind of cross between a no-holds-barred football game and the Keystone Kops.  I can’t figure out if I should be upset, depressed, or amused by the whole thing.

User interface matters

I bought a Casio multi-function digital watch for $35.00 in 1983 to replace a watch that I’d lost or broken.  The Casio had some cool features that were relatively rare at the time in low-cost watches:  an alarm, a stopwatch, a countdown timer, and an hour beep.  I’m not sure what good a beep every hour on the hour is, but the other features were quite useful.  It was quite easy to use, too.  Just 10 minutes with the instruction book and I knew how to set the timer and the alarm, and how to use the stopwatch.  I never looked at the instruction book again for the 17 years that I owned the watch.  Heck, I used to operate the buttons in the dark and then use the backlight to verify that I did it correctly.

I lost that watch in Jamaica and replaced it with a $45.00 Timex Expedition.  Had I been near a Wal-Mart or Target or even a sporting goods store I wouldn’t have picked up the Timex, but pickings were slim on Jamaica and I can’t stand to be without a watch.  The Timex looks okay and has the timer, stopwatch, and alarm that I use, but it also has some “features” that made me dislike it more as time went by.  First, it ticks.  Loudly.  I like the analog face, but that ticking second hand is very annoying.  Of course, I didn’t notice the ticking until we’d left Jamaica.

The Timex has two other flaws that are very annoying.  The user interface to the multiple functions, like most of such watches, consists of four buttons on the side of the watch.  But unlike the Casio that I was able to internalize after a few minutes with the manual, I could never get the hang of programming that darned Timex.  Overloaded buttons and double button pushes do not make for a good user interface.  And the buttons themselves were placed such that if I bent my wrist backward I would depress the buttons and start setting the alarm or the timer, or reset the stopwatch.  All too often I’d be timing myself on the bike only to find out that I had inadvertently stopped the clock.  The Timex has a horrible user interface.  Features don’t mean squat if I can’t get to them.

About 10 days ago I obtained a Nike Oregon Digital watch ($80.00), which immediately replaced the Timex.  This watch is about the same diameter as the Timex, but a little thicker.  It also has a very readable digital display, dual time zones, four alarms, stop watch, and interval timers.  Best of all, the user interface (four buttons again–five if you count the backlight) is simple.  10 minutes with the instruction manual and I know exactly how to use the thing.  Plus, the few extra millimeters of thickness and the button placement make it impossible for me to hit the buttons when I bend my wrist back.  My only complaint is that the hydration alarm isn’t loud enough to be heard when I’m riding on a busy road.  Other than that I’m delighted with the watch.  I can even operate it in the dark like I did the Casio.

Granted, the Timex costs about half what the other two watches cost (figure inflation-adjusted dollars).  But the cost difference isn’t in the user interface.  It’s pretty obvious to me that the Casio and the Nike watches were designed by people who actually understood their customers.  The Timex appears to have been designed to fill out a feature set without any attempt at making those features usable.

User interface matters.

Anybody want a slightly used Timex Expedition wristwatch?

Unexplained VersionNotFound Exception with ADO.NET

I’ve run into a problem that has me pretty confused. I’m getting a VersionNotFoundException when trying to change the RowFilter property of a DataView. This happened in a large system that I’m working on, but I’ve been able to duplicate the behavior in a relatively small (80 lines) sample program that’s at the end of this post.

I posted a message on Microsoft’s forum, but haven’t received any helpful responses. Perhaps somebody who reads this can help me out. A Google search revealed a few similar problems (although no exact matches), but I didn’t see a small reproducible case or any kind of resolution to the problem.

Short explanation of problem:

I have a DataSet with two tables: Legislators and Authors. The Legislators table contains a list of all Legislators who can potentially be an author or coauthor on a bill. The Authors table contains a record for each Legislator who has signed onto the bill as an author or coauthor (the Authors table will contain information for one bill at a time). The DataSet enforces a relation: the LegislatorCode value in an Authors record must match a LegislatorCode that’s in the Legislators table. I use a DataView to maintain a view of the Legislators table showing only those legislators who have not signed onto the bill.

This all works on the initial load, as shown in the program below. However, if I then add an Authors record, the DataView isn’t updated to reflect the change. That is, even though there now is an Authors record for a legislator, that legislator still shows up in the DataView. So, figuring I had to trigger a refresh of the DataView, I set RowFilter to string.Empty, and then set it back to what it was. I then get an exception “System.Data.VersionNotFoundException: There is no Original data to access.”

Further testing shows that this only happens if I accept changes on the Legislators table (i.e. the parent table in the relation). It appears that the code which processes the relation is looking for the child record to have an Original version if the parent has an Original version. It seems to me that it should be looking for the Current version.

I have two questions:

  1. Why doesn’t the DataView update when I enter the new Authors record?
  2. Why do I get this exception?

Any help, especially a workaround or somebody pointing out where I’m doing something wrong, would be appreciated.

The sample program:

// C# program to illustrate unexpected behavior in DataView.RowFilter
using System;
using System.Data;
namespace dvTest
{
    class Class1
    {
        [STAThread]
        static void Main(string[] args)
        {
            DataSet myds = new DataSet();
            // Create the tables
            DataTable tblLegislators = new DataTable("Legislators");
            tblLegislators.Columns.Add("LegislatorCode");
            tblLegislators.Columns.Add("DisplayName");
            DataTable tblAuthors = new DataTable("Authors");
            tblAuthors.Columns.Add("BillNumber");
            tblAuthors.Columns.Add("LegislatorCode");
            // AuthorType:  P = Primary author, C = Coauthor
            tblAuthors.Columns.Add("AuthorType");
            // Add tables to dataset
            myds.Tables.Add(tblLegislators);
            myds.Tables.Add(tblAuthors);
            // Add a relationship.
            myds.Relations.Add(tblLegislators.Columns["LegislatorCode"],
                tblAuthors.Columns["LegislatorCode"]);
            // populate the legislators table
            tblLegislators.Rows.Add(new object[] {"A0123", "Smith (A0123)"});
            tblLegislators.Rows.Add(new object[] {"A1234", "Jones (A1234)"});
            tblLegislators.Rows.Add(new object[] {"A5038", "Jackson (A5038)"});
            tblLegislators.Rows.Add(new object[] {"A0191", "Morton (A0191)"});
            // Populate the Authors table.
            // LegislatorCode must be present in Legislators table
            tblAuthors.Rows.Add(new object[] {"001", "A0123", "P"});
            tblAuthors.Rows.Add(new object[] {"001", "A5038", "C"});
            // Accept changes on the dataset.
            myds.AcceptChanges();
            // dvLegislators is a dataview that contains legislators
            // that do not have a record in tblAuthors.
            DataView dvLegislators = new DataView(
                tblLegislators,
                "Count(Child.LegislatorCode)=0",
                "DisplayName",
                DataViewRowState.CurrentRows);
            // Show contents
            ShowLegislators(dvLegislators);
            // Add another coauthor
            Console.WriteLine("Add Legislator 0191 as a coauthor");
            tblAuthors.Rows.Add(new object[] {"001", "A0191", "C"});
            // Show contents -- why is 0191 still in this DataView?
            ShowLegislators(dvLegislators);
            // Re-filter the DataView
            dvLegislators.RowFilter = "";
            try
            {
                // Why does this throw an exception?
                dvLegislators.RowFilter = "Count(Child.LegislatorCode)=0";
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.WriteLine("Press Enter");
            Console.ReadLine();
        }

        static void ShowLegislators(DataView dvLegislators)
        {
            // Display contents of dvLegislators
            Console.WriteLine("dvLegislators contains:");
            foreach (DataRowView drv in dvLegislators)
            {
                Console.WriteLine(drv["DisplayName"].ToString());
            }
            Console.WriteLine();
        }
    }
}

Catching Up / U.N. Security Council is a Joke

I’ve been very busy the last couple of weeks and haven’t had the mental bandwidth to post entries.  A handful of items here to catch up:

  • Three times in the last three weeks, Debra and I have been attacked while riding our bikes in the morning.  Some idiot in a truck is throwing stuff out his window at us.  The first time it was a McDonald’s drink cup with ice and the remains of a drink.  That hit Debra in the chest.  The second time was a clear miss.  The third time was Monday, when I was struck in the arm by an empty Gatorade bottle.  I saw that one coming, though, and got the license plate of the truck so I could report it to the county sheriff.  I’ll be watching for this guy in the future.  In over four years of riding my bike in this area, these are the only such incidents that I’ve experienced.
  • I’ve been working on a .NET interface to the Microsoft CAB SDK, something similar to the Delphi component that I developed a few years back and published in my article Grab A Cab.  This turns out to be much harder than I expected.  I’ve finished the interface to the decompression portion and am working on the compression side of things.  Marshaling data between .NET managed code and unmanaged DLLs can be difficult.  Marshaling data from unmanaged DLLs to managed callbacks is really tough.
  • I didn’t participate in the Hotter ‘N Hell Hundred last weekend like I had planned.  My riding partners backed out:  one with a broken bike and the other with an injury.  So I did my monthly century alone again, this time leaving the house at 5:15 am so that I could finish at the lake for my company’s annual summer picnic.  I ended up riding 104 miles, with a monster hill at mile 98.  Ouch.  Diving into the lake sure felt good, though.
  • The United Nations Security Council recently has shown why people don’t take it seriously.  Their Resolution 1556 (30 July 2004) does a lot of reaffirming, taking note of, reiterating, condemning, urging, and expressing concern, and generally wringing its hands and blustering, but in the end does nothing to help solve the crisis.  Recent meetings on the issue are similarly lacking in substance.  At this rate, the problem will be solved by default before the Security Council pulls its thumb out of its butt.
  • Just to prove that they can issue statements along with the best of them and make demands that they have no ability or intention of enforcing, the Security Council issued this press release condemning “in the strongest terms” the recent hostage taking in Russia and demanding the “immediate and unconditional release of all hostages of the terrorist attack.”  I wonder if the members of the Security Council understand that they’re being laughed at.  A demand implies some means of forcing the action, or at least some meaningful punishment if the demand isn’t met.  The Security Council can’t back up its demands.  It’s pathetic.