YouTube's 'Related Videos' feature

YouTube's 'Related Videos' feature has a serious issue with privacy. According to Shumeet Baluja's paper entitled Video Suggestion and Discovery for YouTube: Taking Random Walks Through the View Graph [pdf], they're using a variant of the adsorption algorithm that is based on co-views statistics. That is: a bunch of people, who watches this video that you're watching, also watches the following videos that you might also enjoy, so here they are.

Here's the problem. Let's say I'm John Doe. I opened a YouTube account called johndoe and uploaded some home videos here. I also have an alterego named James Bond so I created a second account where I post work related videos there. I am careful to keep the two egos separate: I don't cross link videos from one account to another. But from my home computer I do occasionally review videos from both accounts together. All of a sudden, my James Bond videos show up on the 'Related Videos' list to my John Doe videos. And boom! The whole world knows that John Doe is James Bond, thanks to YouTube.

This is an extreme and fictional scenario, but the issue is very real.

HOW TO retrieve the host name of an IP address

Use:

ping -a <ip-address>

VB6 BadImplementsRefInCompatLib and .NET-COM Interop

You've created a VB6 COM library, MyLib1 (1.0).
You then have two usage scenarios for MyLib1:
  1. MyLib1 is, in turn, used by another VB6 library, MyLib2

  2. MyLib1 is used to generate a COM Interop assembly which is consumed by a .NET assembly called MyDotNetLib, which itself is also a COM interop library.



It so happened that you needed to add some new public methods to MyLib1, which would bring its versioning to 1.1. You were careful to craft the change such that it's backward compatible. Yet, something happened--you don't yet know exactly what it was--and when to try to recompile the above two libraries, you get the following problem scenarios:

  1. Recompile MyLib2, you get this error: BadImplementsRefInCompatLib

  2. Recompile MyDotNetLib, you get:
    Error 63 The assembly "..MyDotNetLib.dll" could not be converted to a type library. Type library exporter encountered an error while processing 'MyDotNetLib.MyComponent, MyDotNetLib'. Error: Referenced type is defined in managed component, which is imported from a type library that could not be loaded (type: 'MyLib1._IComponent'; component: '...\Interop.MyLib1.dll')



What did you do wrong?
As it turns out, the "something" in "something happened" above was an IDE debug session that had previously crashed, and MyLib1.vbp (1.1) remained registered in the system.

Why did that cause the above problems to occur?
The IDE looks for the latest version of the library (same GUID) to bind to.

How to avoid it?
Well, you can't avoid it, any more than you can avoid making VB6 crash.

What to do when it happens?

  • Temporarily remove reference to MyLib1 from your MyLib2 project, then attempt to re-add the reference, looking through the list of registered libraries to see how many instances of MyLib1 are registered--there should be only one. If you see a MyLib1.vbp showing in your list, you'll need to go through the registry and manually remove all CLSIDs with InprocServer32="MyLib1.vbp". The automatic registry clean tools, that I've tried, couldn't remove these entries for me.

  • Retrace your changes to make sure that you haven't made any modification to public interfaces that could break binary compatibility.

  • Use the OLE/COM Object Viewer tool to view Type Library info of MyLib1.dll. Then compare the output with a previous version of the binary to see if any of the interface GUIDs got inadvertently changed.


---
Pre-emptive comment:
Yes, I know VB6 (the IDE) has reached its end-of-life, which means MS support for it will be scarce--all the more reason for writing this post.

NUnitForms rocks!

This NunitForms tool is really cool. Kudos to Luke Maxon and co. I'm running v2.0 alpha 5 and so far, my favourite feature has got to be ControlTester.FireEvent(). For instance, I can simulate the drag operation with something like this:


// initialize control tester for our Finder control
ControlTester myControl = new ControlTester("picFinder", "MyTestForm");

// simulate drag to coordinate (105,205)
myControl.FireEvent("MouseDown", (EventArgs)null);
Cursor.Position = new Point(105, 205); // drag mouse to here
myControl.FireEvent("MouseMove", (EventArgs)null);
myControl.FireEvent("MouseUp", (EventArgs)null);

// Assert MyTestForm GUI states
...


Pretty neat.

spamc.exe hangs

For the past several weeks, our ESA Sink has been clogging up once in a while. This is because spamc.exe, the SpamAssassin client program spawned by ESA, simply hung (probably due to a particularly large email).

I was thinking of rewriting spamc using Uwe Keim's ZetaSpamAssassin Wrapper, but that's probably a weekend type of project that I might entertain in the future. In the mean time, I wrote this simple little Windows service that occasionally checks and reaps stale spamc.exe processes, allowing the filter to continue. Source code (C#) and binary are available here.

Rhino.Mocks ExpectationViolationException

I'm trying out Rhino.Mocks in my NUnit test suites. Why Rhino.Mocks instead of NMock, you ask? Well, because I've heard that Rhino.Mocks can mock classes as well as interfaces.

Anyway, I was running into a little bit of a rookie's setback. I set up my mock objects like this:


1 const string TESTNAME = @"My Component Name";
2 MockRepository mocks = new MockRepository();
3 MyLib.MyTestClass tw = mocks.CreateMock<MyLib.MyTestClass>();
4 Expect.Call(tw.get_Name()).Return(TESTNAME);
5 mocks.ReplayAll();
// Test first invocation of get_Name()
6 Assert.AreEqual(tw.get_Name(), TESTNAME);
// Test second invocation of get_Name()
7 Assert.AreEqual(tw.get_Name(), TESTNAME);



I should probably add that MyLib.MyTestClass is a COM Interop class.

Executing line 7 resulted in this exception: Rhino.Mocks.Exceptions.ExpectationViolationException : _MyTestClass.get_Name(); Expected #1, Actual #2.

A little bit of googling did not immediately hint at the proper solution for me, but looking at the API documentation turned up this method: Repeat.AtLeastOnce(). So adjusting line 4 above to this did the trick:


4 Expect.Call(tw.get_Name()).Return(TESTNAME).Repeat.AtLeastOnce();

PMOG

Heard about this on CBC RadioOne's Spark this morning:
The Passively Multiplayer Online Game (PMOG):


Cool idea!

Why it's good to use [System] Hungarian notation

One of our components has a property called--for reason of obscurity say--URL. The code behind it is quite simple:


public string URL {
get {
return mstrURL;
}
set {
mstrURL = value;
}
}



This property is being edited through our Property Editor component via a sort of reflection mechanism.

Sometime ago, this component broke because the Property Editor was no longer able to "reflect" on the URL property.

A bit of googling turned up this MSKB article: "Type library identifiers are not case sensitive by design".

It turned out that somebody recently added this method in a completely different class, within the same assembly, but entirely unrelated to the component above:

public void LoadUrl(string url) {
...
}



So the code that gets called by RegAsm to generate the type library must have found the url parameter of the LoadUrl method first, and decided to reuse it as the moniker for our URL property.

I guess the moral of this story is: Stick with System Hungarian Notation.

public void LoadUrl(string strUrl) {
...
}


(Side Note: A good article on Hungarian Notation can be found at Joel On Software).

TODO: Add my AppConnector related posts to karora.com's blogroll

Here's the feed URL:
http://th2tran.blogspot.com/feeds/posts/default/-/AppConnector

Citrix published apps: no software to install? no problem.

Here's a challenge: You've got 2 web apps, pushed out to client PCs via Citrix as published apps, running side by side on two browser instances. How do you make them talk to each other (i.e. exchange data between the two) without having to modify the 2 apps?

That's the challenge that I was presented today as I was helping a partner build a POC for a prospect. The kicker of it was, I was told one of our biggest competitors was given this challenge and couldn't do it.

Here's the specific scenario that we were able to get implemented within about 3 hours:
  • user logs into Citrix web interface.

  • user clicks on icon of published app--in this case it was JD Edwards EnterpriseOne web--to launch it.

  • user navigates to JDE Supplier Ledger inquiry screen, brings up a supplier number and PO number.

  • AppConnector KoolBar appears on the local desktop with a button labeled Get Image.

  • User clicks on button, which triggers a search against Imaging system and brings up the scanned invoice image.


And the kicker of it is, there is no AppConnector footprint on the local desktop.

svn over putty over http (over proxy)

A while ago, someone asked me about accessing their svn server from inside a customer network, which blocks everything except port 80. I didn't have an answer at the time. Then, I was at a customer site last week and ran into a similar problem. This is how I overcame it:

  1. Set up your HTTP server to proxy SSH. See here.

  2. Tunnel the svn port through PuTTY. See here.

  3. Add an entry to your %windir%\system32\drivers\etc\hosts file:

    # SVN Server
    127.0.0.1 svnhost.mycompany.com

    This is done so that you don't have to change your current SVN client settings.

  4. If the customer network happens to use a web proxy, enter the proxy settings in the PuTTY's Connection\Proxy panel.

My Google Desktop Search is Locked!

I accidentally locked my Desktop Search. And as far as I can tell, the systray memu item called "Unlock Search..." is just there for decoration (I have version 5.7.801.1629), because it did nothing for me when I clicked on it. I could only surmise that the menu item was trying to launch a URL in my default browser, failed for some reason, and decided to keep quiet about it.

Eventually managed to find out the port (4664), went there and unlocked it via the web interface: http://127.0.0.1:4664/



I don't know how I had been able to live without Google Desktop. I use it primarily for searching my inbox (8 years' worth of emails).

ROI on process automation

I wrote this corny rant on 2-25-2005 but never posted. Upon re-reading, I think it's worth posting.
---

An A/P Clerk, and AppConnector user, called me up this week and happily proclaimed: "yesterday, I did four days' worth of work in one day".

Wow...that's a 400% productivity improvement by using AppConnector simply to automate a multiple data entry process into the single-point data entry process. I did some simple (read: naive) calculations and...say, for an organization staffing 10 accounting clerks working on a $10/hr salary just keying and searching for invoices, in a month's time, the costs incurred would be as follows:

  • no AppConnector: 10 x 8 x 10 x 5 x 4=$16000
  • with AppConnector = 16000/4 = $4000, a saving of $12000.


This means that in the short order of a few months, AppConnector would have paid for itself. Pretty good ROI, isn't it?

I said that's a naive calculation because, amongst other things, this productivity gain usually has side benefits such as less data re-keying errors, which, in the first instance, would have incurred more costs of correcting the problem after-the-fact. This benefit results in data consistency across disparate systems. Mind you, that's not to say that AppConnector will totally eliminate data entry errors altogether, because if the error was made in the originating system, it would be replicated in the second system as well when the data is brought over. But then this would be an entirely different problem, not one of re-keying errors.

Back in high school, I did an Economics research paper on process automation and fifth generation computers--the kind of computer systems referred as "expert systems" at the time. This was a time where there were rumbling apprehensions in the labour force about how these great evil things called "computers" would eventually take over the world and put people out of a job because everything would be automated by computers/robots. Anyway, my thesis for the paper was that computers were built to help mankind not to replace it. Any "unskilled" job that they might end up replacing would be an opportunity for people to learn new skills and adapt. This is good for humanity because we don't want to be stuck with the same inefficiency all the time--whether we realize that it is an inefficiency or not--but we should strive to better ourselves each and every day. And isn't that what evolution is all about?

But this was high school, and nearly 20 years ago when I was young(er) and (more) stupid, so what did I know!

Definition: A change that is good is one that benefit you, your organization or, more generally, humanity, in the long run.

When change is good, resistance is futile. Be one with the force (of change, that is)!

Microsoft's CTO @ MIX08

Ray Ozzie's keynote @ MIX08:



Source: Microsoft PressPass

ROT Registration requires 'DCOM Server Process Launcher'

Interesting titbit I found yesterday after a two-hour troubleshooting session with a customer. One of our application components registers itself in the ROT so that it can be accessed by other application processes. In this particular instance, the ROT registration step fails with the usual cryptic error message "The system cannot find the file specified".
As it turned out, the user had the 'DCOM Server Process Launcher' service disabled.

More details here.

Thanks, Ken, for providing the diagnostics.

A bug is still a bug by any other name

I don't understand why (some) people are (still?) so afraid of saying the word "bug" to a customer. It is what it is. Why mince words? Although...I've been known to call something a "bug" when in fact it was just a "configuration issue". :-)

Read: A Bug by Any Other Name by James Gleick.