WebObjects 5.3 is bundled with XCode

WebObjects 5.3 Release Notes

See previous post -- too much to learn. Among others, Aaron Hillegass has praised WebObjects. He has held it up as a standard against which to measure recent changes to Cocoa (and its Controller layer), and has sometimes found the latter lacking.

Haven't yet had a chance to do much with CoreData and the modeling tools in XCode 2.0. Now XCode 2.1 may help me understand what the fuss was all about.

Fun times.

Google's Singularity machine

Epeus' epigone - Kevin Marks weblog, on Google's Singularity Machine


Map/Reduce

"Map/Reduce" evokes and . (Danny Hillis wrote about the CM architecture in the late 1980s.) Would there were time enough in the day to get hands-on with parallel and distributed algorithms.

Ceviche

What is ?

Sadly, this reminds me of my neighbor across the hall at Los Piñones apartments. He was not much older than me, in his early 40s I think. He was fond of seafood. In late summer of 2003, when my job and the health of my parents both took bad turns, he began to lose his life.

Suddenly taken ill, my neighbor was hospitalized with what was, for the first month, an unknown but deadly serious infection. The hospital staff in Albuquerque did what they could. Finally one doctor, after a flash of insight, walked in and bluntly asked whether my neighbor had recently eaten seafood.

From that point it was clear how to treat him, but it was too late. The infection had done too much damage. I can't remember all of the complications he suffered; I believe pneumonia and kidney failure were among them. Just before Thanksgiving, he died.

The restaurant was not shut down. As far as I know, they didn't even remove uncooked seafood from their menu, or add any cautions to it. Granted that would have been bad for business. And life is full of risk anyway. But some indication of regret would have been nice.

TortoiseSVN

Some of my co-workers dislike using the command-line to access Subversion repositories. For those running Windows, TortoiseSVN is looking good.

Once it's installed, subversion operations become available from the context menu of Windows Explorer. Checkouts, commits, configuration of TortoiseSVN, it's all right there.

When you're browsing a local checkout of an SVN repository, document icons are decorated with green checkmarks, red exclamation marks, etc. to show whether they are in sync with the repository, whether you have made local modifications, etc.

So far the only problem I've had with TortoiseSVN has been in figuring out how to do a checkout from a read-write repository. There may be better ways of solving the problem, but here's what has worked for me.

First, use svn+ssh to access the repository. In the URL, specify your username on the subversion host. And of course this means you need to use the absolute path to the repository in the URL:

svn+ssh://my_username@my_svn_server/abs_path_to_repository/my_project/trunk

Next, modify the TortoiseSVN settings to enable ssh tunneling. I'm not sure this is really necessary. But I did make the change, via the "Edit" button next to the "Subversion configuration file" label. Once notepad popped up with the contents of the config file, I uncommented the lines for ssh tunneling:

[tunnels]
ssh = $SVN_SSH ssh

svn+ssh: Using Subversion with SSH

I've been trying to learn how to enable secure shell (SSH) access to Subversion, on both the client and server sides. It's kind of a pain.

The whole point of using SSH is to avoid sending your source code in the clear, so maybe the pain is worth it. Or maybe this is such a common-sense usage of Subversion that somebody (like, say, me) should put some effort into making it easier to do. But I digress.

To connect to an svnserve daemon via SSH you use this repository syntax:

$ svn co svn+ssh://.../

This requires some careful preparation. First, you need to have the following lines uncommented in your client-side ~/.subversion/config:

[tunnels]
ssh = $SVN_SSH ssh

Then, on the client, your URL must include the absolute path to the repository, followed by the path of the project you want to check out from the repository.

For example, suppose you ordinarily use this command to check out a project:

$ svn co svn://svn_server/project/trunk local_name

In order to perform the same checkout using an svn+ssh URL, you'd have to use this command:

$ svn co svn+ssh://svn_server/abs_path_to_repository/project/trunk local_name

You also need an account on the SVN server, with the same username as your account on the client machine. Or you need to specify the username when performing your svn checkout, like so:

$ svn co --username bubba ...

Or you need an entry like this in your ~/.ssh/config, to tell ssh as whom it should connect to the server:

Host svnserver
Hostname svnserver.blah.com
User bubba

On the server, you need to ensure that svnserve is in the path of the user account under which you run svnserve.

You also need to ensure that the account under which you run svnserve, as well as your user account on the server and any other userr accounts which need access to the repository, are all members of the same group. (subversion would be a good name for the group.)

You also need to make sure that your umask is set to 002 (group write-able). Otherwise other users of the remote repository might find themselves suddenly unable to change some file that you've added.

Got all of that? Good! Of course, I probably forgot a few steps...

DarwinPorts and Subversion on Mac OS X

This is a recap of instructions posted by Bill Bumgarner (here and here) and James Duncan Davidson (here), on installing Subversion and Apache2 via DarwinPorts on Mac OS X 10.3.

Install DarwinPorts

Get DarwinPorts from CVS and build and install it. Assuming default installation locations are okay, this script should work:

#!/bin/sh

cvs -d :pserver:anonymous@anoncvs.opendarwin.org:/Volumes/src/cvs/od login
cvs -d :pserver:anonymous@anoncvs.opendarwin.org:/Volumes/src/cvs/od co -P darwinports
cd darwinports/base
./configure
make
sudo make install

Now you need to add the DarwinPorts binaries directory to your PATH. Depending on whether you prefer sh-like shells such as bash or csh-like shells, one of the following should work:

export PATH=$PATH:/opt/local/bin
set path=($path /opt/local/bin)

Install subversion and apache2

Next, using James's instructions as a guide, configure and install subversion and apache2. To do so, run the following script using sudo:

#!/bin/sh
port install apache2
port install subversion +mod_dav_svn +python
port install DarwinPortsStartup

Note a small problem here: This script installs subversion Python bindings, but only for a non-framework installation of Python. If you need bindings for a framework installation of Python, you're on your own.

Create The Repository

Bill created his subversion repository in /svn. I wanted mine in /usr/local/svn-repository, for no good reason.

$ sudo mkdir /usr/local/svn-repository
$ sudo svnadmin create --fs-type fsfs /usr/local/svn-repository/master
$ sudo chown -R www /usr/local/svn-repository

Patch httpd.conf

Bill has provided a patch for httpd.conf. It assumes that your subversion repository resides in /svn. If it resides elsewhere you'll need to change the patch file before applying it.

For example, since I put my subversion repository in /usr/local/svn-repository I had to change every reference to the /svn filesystem location in the patch file to point to /usr/local/svn-repository.)

You can copy the patch file to /opt/local/apache2/conf, and then apply it from there. Patch will ask you if it should assume this is a reverse patch (-R). Say 'y':

$ sudo cp my-httpd-conf-svn.patch /opt/local/apache2/conf/
$ cd /opt/local/apache2/conf/
$ sudo cp httpd-std.conf httpd.conf
$ sudo patch httpd.conf my-httpd-conf-svn.patch
patching file httpd.conf
Reversed (or previously applied) patch detected!  Assume -R? [n] y

Add Repository Users

$ cd /usr/local/svn-repository/
$ sudo -u www htpasswd -c master-auth mitch
New password:
Re-type new password:
Adding password for user mitch

Test The Apache2 Configuration

$ /opt/local/apache2/bin/apachectl configtest
Syntax OK

Start Apache2

It should start up, listening on port 8000.

$ sudo /opt/local/apache2/bin/apachectl restart
httpd not running, trying to start

At last you should be able to view the topmost directory of the (empty) subversion repository in your web browser, by loading http://localhost:8000/svn/.

Rendering GL content to grids and printers

I'd like to render 3D graphics into the cells of a wxGrid, and am considering using an offscreen wxGLCanvas to prepare the contents of each cell as requested. This seems like a common approach; for example, NSTableViews in Cocoa use a single NSCell to paint the contents of each column.

How to render the contents of the wxGLCanvas into each grid cell? The easiest way seems to be to set the size of the canvas to match the size of the current cell, render the scene, copy the content into a wxBitmap, and draw the Bitmap into the cell rect.

Others have shown how to copy wxGLCanvas content into a wxBitmap, e.g. in order to print the contents of a canvas. But the sample code I've found has omitted a key step: a call to glPixelStorei to ensure proper packing of the data provided by glReadPixels.

Anyway, here's what works for me. This code assumes the GLCanvas size has been set and the scene already rendered:

    def _getBitmap(self):
        c = self.canvas
        x, y, w, h = glGetIntegerv(GL_VIEWPORT)
        glPixelStorei(GL_PACK_ALIGNMENT, 1)
        pixels = glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE)
        img = wx.EmptyImage(w, h)
        img.SetData(pixels)
        img = img.Mirror(False)
        self.bm = wx.BitmapFromImage(img)

Temporarily disabling callbacks in wxPython

A data model can have many views. When a user changes one of the views, that change needs to be reflected in other views. If you use wxPython widgets to implement your views, this can set off an update storm. Here's how to avoid it.

It's frustrating that a programmatic change to a wxPython widget causes callbacks associated with the widget to fire. For example, if you've bound wx.EVT_TEXT in a wxTextCtrl then any subsequent call to SetValue can trigger the bound callback.

In contrast other toolkits (e.g. Apple's Cocoa) recognize that, when you change a widget programmatically, you're usually trying to treat it as just a passive MVC view. You just want to reconfigure your widget, not to generate any callback events. Events are for user input.

wxPython widget classes are derived from wxEvtHandler, and there lies the solution to the problem. If you want to update a widget without triggering its callbacks, first call its SetEvtHandlerEnabled method with an argument of False. Make your update, then call SetEvtHandlerEnabled with an argument of True.

It's a lot of extra typing, but it works. I just wish it hadn't taken me so long to notice wxEvtHandler. I could have avoided a lot of grumbling.

But that sure is a lot of extra typing...

Here's a wrapper function which lets you disable callbacks for the duration of a single method invocation, without suffering the typical verbosity of wxPython:

def withoutCBs(method):
    def wrapper(*args, **kw):
        widget = method.im_self

        oldSetting = widget.GetEvtHandlerEnabled()
        widget.SetEvtHandlerEnabled(False)
        try:
            return method(*args, **kw)
        finally:
            widget.SetEvtHandlerEnabled(oldSetting)
    return wrapper

This wrapper makes it easy to perform a single, callback-free widget update:

    def _uiQuietBtnClicked(self, evt):
        withoutCBs(self.field.SetValue)("Quiet")

Of course this is only good for invoking a single method defined directly on the target widget. What if you've composed a complicated method in a controller class, and you need to disable callbacks for the duration of that method? A simple refactoring lets you state explicitly which widget you want to stifle:

def withoutCBs(method, widget=None):
    """Prepare to call method with widget's callbacks
    disabled.

    If widget is None it is assumed to be the widget
    bound to method.
    """
    def wrapper(*args, **kw):
        w = widget or method.im_self

        oldSetting = w.GetEvtHandlerEnabled()
        w.SetEvtHandlerEnabled(False)
        try:
            return method(*args, **kw)
        finally:
            w.SetEvtHandlerEnabled(oldSetting)
    return wrapper

With this modification you can silence a widget for the duration of any operation, regardless of where it's defined:

    def _uiQuietBtnClicked(self, evt):
        withoutCBs(self._performComplexOperation,
                   widget=self.field)()

    def _performComplexOperation(self):
        ...