BookmarkSync Win32 Client Design

NOTE: This concerns the Win32 client (in source or binary form).

To get the most out of this, you should have a passing familiarity with C++, with the Standard Template library (STL), and the Windows Base API. Some of the classes are modelled after the Java class library, particularly java.io. No knowledge of MFC, ATL, .NET, or anything else is needed.

All Windows-based programs must have a main-event loop. This is a single loop that pulls the next event off of the Windows event queue, and dispatches accordingly. A normal program would have code like:

while (GetMessage(&msg, 0, 0, NULL)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

If you're familiar with Windows C programming, you've seen code like this a gadjillion times. The BookmarkSync client is a bit more complex, because it wants to deal with GUI events (above) with File HANDLE Events (something completely different). File handles are much like file descriptors in Unix. Asynchronous events, like thread exit, or network IO completion, or file change notifications are tracked by file handle events.

So, if you look in SyncIT/Synchronizer.cxx, you'll find a function Synchronizer::driver(). This function is the main event loop for BookmarkSync. It's state-based: in certain states it does the main event loop like a normal windows application, in other states it calls MsgWaitForMultipleObjects, the Windows function that merges File handle events with GUI events.

The entire "trick" of BookmarkSync is the FileChangeNotification function in Windows. An application can register a directory to "watch" using FindFirstChangeNotification, then wait for a change using the file handle notification discussed above, calling FindNextChangeNotification each time a notification occurs.

Each browser type requires slightly different calls to FindFirstChangeNotification. This is why there's an abstract base class Browser (found in SyncIT/Browser.h) and different implementations (found in SyncIT/MicrosoftBrowser.cxx, NetscapeBrowser.cxx, and OperaBrowser.cxx). The actual code to read and write bookmark files is kept in the BookmarkLib directory, in files like NetscapeInput.cxx, NetscapeOutput.cxx, WinFavoritesInput.cxx, etc.

The code in NetscapeBrowser.cxx is a lot more complex than the other browser's code, because if you edit Netscape's bookmark file while Netscape is still running, Netscape will just overwrite its own changes. Yuck. So we maintain a DDE connection to Netscape so we can tell when Netscape starts up or shuts down. This code is found in SyncIT/NetscapeBrowser.cxx in the Callback function. This Callback function is called by DDE, this is set up in the NetscapeBrowser::NetscapeBrowser() function.

So we know when to read bookmark files (when the directories that the browsers store their bookmark files in change) and we know when to write bookmark files (anytime, for most browsers except for Netscape) and we know the bookmark file formats.

How do we sync?

To do this requires a hierarchical "diff" algorithm. This is found in BookmarkLib/BookmarkModel.cxx in the function diffBookmarks. The idea here is to give this function two separate bookmark sets, and a "listener" that will be told of differences, ie add a folder, delete a bookmark, etc. One listener edits a target bookmark set, one listener handles almost all edits except deletes (that's the code that does the "merge") and one listener builds the set of instructions to send to the server (in SyncProtocol.cxx).

When do we sync?

The file SyncIT/Synchronizer.cxx is a giant state machine. Events like "File change" or "Timeout expired" or "Start" trigger certain events. The state machine is documented above Synchronizer::onStartErr() in SyncIT/Synchronizer.cxx. When it is time to actually make a connection to the BookmarkSync server, a thread is created, and the code in SyncIT/SyncProtocol.cxx is run. The Input/Output code looks a *LOT* like the Java IO class library did 5 years ago.

Our internal file format for storing what the bookmarks look like during sync is XBEL. Since we wrote this in the early days of XML, we wrote our own XML parser and API. SAX was still buggy when we wrote this.

We have an abstract Socket class that can be overridden by SocksSocket if there's a SOCKS proxy. Ditto with an HttpClient connection, that can be overridden with the WININET DLL. Look in SyncLib/HttpRequest.{h, cxx} and SyncLib/WinInetHttpRequest.{h, cxx}.

Terence Way (originally to the bookmarksync mailing list).