Using Mercurial and SVN

At Sony we're looking for a distributed version control system to replace Subversion—primarily Git and Mercurial. I'm very familiar with Git but hadn't done much with Mercurial so it seemed like a good idea to use it for a couple of weeks and learn the quirks. Since I'm stuck using Subversion I decided to see if it would be feasible to use Mercurial as a "super client" working locally then pushing changes back to svn. A little Googling turned up two candidates hgsvn and
hgsubversion. hgsubversion extends the commands pushing and pulling changes, giving a more native experience, so it seemed like the best choice for learning the system.

The setup is actually pretty simple but I'm documenting it for my own future reference.

Use MacPorts to Install hgsubversion:

sudo port install py26-hgsubversion

Enable the rebase and hgsubversion extensions:

printf "[extensions]\nrebase=\nhgsubversion =\n" >> ~/.hgrc

Check that the extension was enabled:

hg help extensions

It should list something like:
    enabled extensions:

     hgsubversion
                 integration with Subversion repositories

Now you're good to checkout from SVN (note: using the svn+ prefix in the URL lets you use passwords stored in your keychain):

hg clone svn+http://example.com/svn/repo/trunk

I actually ran into an issue where the clone was exploding with a stack trace. The bug had been fixed in 1.1.2 but MacPorts hadn't yet been updated so I rolled a patch to update it.

MAMP + memcache = drilling a screw in my eye

Here's yet another blog post to document something so stupid that I hope to never do it again, but know I will. I spent the better part of the afternoon trying to get the PECL memcache extension working with the PHP 5.2 part of a MAMP installation and finally managed to get it working.

Install XCode.

Open up a terminal and become the root user:

sudo su

Make all the MAMP PHP binaries executable:

chmod u+x /Applications/MAMP/bin/php5.2/bin/p*

Now get the memcache source, compile it and copy the library into PHP's extensions directory:

cd /tmp

wget http://pecl.php.net/get/memcache-2.2.5.tgz

tar -zxvf memcache-2.2.5.tgz

cd memcached-2.2.5

/Applications/MAMP/bin/php5.2/bin/phpize

MACOSX_DEPLOYMENT_TARGET=10.6 CFLAGS='-O3 -fno-common -arch i386 -arch x86_64' LDFLAGS='-O3 -arch i386 -arch x86_64' CXXFLAGS='-O3 -fno-common -arch i386 -arch x86_64' ./configure

make

cp modules/memcache.so /Applications/MAMP/bin/php5.2/lib/php/extensions/no-debug-non-zts-20060613/

Tell PHP to load the memcache extension:

echo 'extension=memcache.so' > /Applications/MAMP/conf/php5.2/php.ini

Sources:
http://www.php.net/manual/en/memcache.installation.php#95063
http://blog.m-schmidt.eu/2010/03/30/develop-memcached-web-apps-with-xamp...

iOS 4 is a total waste of time on iPhone 3G

After two days of trying to use OS 4 on my iPhone 3G I'm sad to report that it's a very un-Apple like release. It's slower, it breaks a bunch of games and doesn't offer any compelling features. I'd go as far as to say they should probably have only released it for the iPhone 3GS.

Correctly accessing CCK fields in SQL queries

Twice today I've had to deal with writing a SQL query that needed data in a CCK field. The naive approach is to just look at the table and field names and plug them into your query:

<?php
$result
= db_query("SELECT COUNT(*) AS count FROM {node} n
INNER JOIN {term_node} tn ON n.vid = tn.vid
INNER JOIN {content_type_date} ctd ON n.vid = ctd.vid
WHERE tn.tid = 25 AND ctd.field_date_value > NOW() AND n.changed > %d"
, $newtime);
?>

Often this will work just fine but since CCK can dynamically alter the database schema (when you add a field to a second content type or change the number of values) the query may break.

Fortunately CCK provides functions for finding a field's table and column names so it's simple to do it correctly:

<?php
$field
= content_fields('field_date');
$db_info = content_database_info($field);
?>

A var_dump($db_info) gives:

array(2) {
  ["table"]=>
  string(17) "content_type_date"
  ["columns"]=>
  array(2) {
    ["value"]=>
    array(6) {
      ["type"]=>
      string(7) "varchar"
      ["length"]=>
      int(20)
      ["not null"]=>
      bool(false)
      ["sortable"]=>
      bool(true)
      ["views"]=>
      bool(true)
      ["column"]=>
      string(16) "field_date_value"
    }
    ["value2"]=>
    array(6) {
      ["type"]=>
      string(7) "varchar"
      ["length"]=>
      int(20)
      ["not null"]=>
      bool(false)
      ["sortable"]=>
      bool(true)
      ["views"]=>
      bool(false)
      ["column"]=>
      string(17) "field_date_value2"
    }
  }
}

After noting that the field has two columns and making our choice, we've got the pieces to plug into the query:

<?php
$field
= content_fields('field_date');
$db_info = content_database_info($field);
$result = db_query("SELECT COUNT(*) AS count FROM {node} n
INNER JOIN {term_node} tn ON n.vid = tn.vid
INNER JOIN {"
. $db_info['table'] ."} ctd ON n.vid = ctd.vid
WHERE tn.tid = 25 AND ctd."
. $db_info['columns']['value']['column'] . " > NOW() AND n.changed > %d", $newtime);
?>

The query is a bit harder to read, but you've future proofed your code so you won't be back to fix six months from now when you reuse that date field on another node type.

Using cURL and the host header to bypass a load balancer

Users are reporting that when they load the site they're occasionally seeing stale content. We have multiple app servers behind a load balancer so my suspicion is that one server isn't expiring content correctly so depending on which one you get you'll see different data. To test this I want to write a simple script to connect to each server save the results and then compare them for differences.

The main problem to tackle is that if I try to connect to www.example.com I'll be connecting to the load balancer and then handed off to a random server. I want to connect directly to each server servers so I'll need to use their IP address. This approach would look something like:

curl --verbose 'http://10.1.1.36:8000/the_url_to_test'

But since the servers use name-based virtual hosts so we'd get a 404 error back. The trick is to have curl send the proper host header:

curl --verbose --header 'Host: www.example.com' 'http://10.1.1.36:8000/the_url_to_test'

This gives us the output we need to start writing the comparison script.

Using logrotate and drush for daily Drupal backups

If you've got Drush installed—and you really should—you can use the following recipe to setup a backup system that will maintain daily backups for the last two weeks. Most of the logrotate configuration is based on a Wikibooks book that I found.

Flash CS4 Gotchas

I've been banging my head against Flash for the last few days and started trying to document a few things.

Can't import fl.controls

For some reason Adobe didn't include them by default so you'll need to add the path to the project.

  1. Open the File > Publish Settings... menu item
  2. Click the Flash tab
  3. Click the Settings... button
  4. Click the Source Path tab
  5. Click the + button and paste in: $(AppConfig)/Component Source/ActionScript 3.0/User Interface

Can't use a Tween on a scrollRect

The Tween class can only change a simple property and the scrollRect need to be changed and the reassigned before it will update. The solution is to add new property to the class and Tween that instead:

  
public function get scrollX():Number {
  if (this.scrollRect) {
    return this.scrollRect.x;
  }
  return 0;
}   

public function set scrollX(value:Number) {
  var r:Rectangle = this.scrollRect;
  if (r) {
    r.x = value;
    this.scrollRect = r;
  }
}

Then you can use a Tween:

  tween = new Tween(this, "scrollX", Strong.easeOut, scrollX, scrollX + 100, 1, true);

Also, you'll want to keep a reference to the Tween object so that it doesn't get garbage collected half way through the animation.

Can't use named HTML entities

Flash's TextField only supports a small subset of named HTML entities (&lt; &gt; &amp; &quot; &apos;). If you're displaying HTML from users or a CMS you'll find that things like &amp;deg; slips by so you'll need to convert the named entities to their numeric versions.

Drupal 6 on OS X 10.6

Running Drupal on OS X 10.5 was a pretty huge pain in the ass. It's much easier in in 10.6 since it includes PHP 5.3 with GD and the PDO out of the box. And Drupal 6.14 resolves the PHP 5.3 incompatibilities.

In this guide I'll walk through the process I used for reinstalling OS X, then installing MacPorts and using it to install MySQL.

Note: I've shortened this up a bunch since it was first posted (originally it was using PHP 5.2 from MacPorts). I also want to make it clear that I am familiar with MAMP but would rather punch myself myself in the face than use it. If you'd like to go right ahead since it's probably easier, and as evidenced by the commenters below, you're in good company. But I'm going to continue to compile my own so I know where everything ends up.

Rainboduino programming via FTDI FT232RL cable

Rainboduino + FTDI FT232RL cable
I bought one of the Rainbowduinos a while back but hadn't tried programming. Turns out it's a bit of a trick. They (sort of) document the process with a Seeedunio but I don't have that particular board one so I decided to try to figure out how to do it using the FTDI FT232RL cable I'd bought from adafruit industries.

After finding the FTDI pinout on the datasheet and discovering that RTS is used to signal reset by the Arduino IDE I came up with the following wiring diagram.

Connect the cable pins on the left to the board pins on the right. Note: that you need to connect GND and VCC even if you're externally powering the Rainboduino.

1 GND Black  <-> GND
3 VCC Red    <-> VCC
4 TXD Orange <-> RXD
5 RXD Yellow <-> TXD
6 RTS Green  <-> DTR

You should now be able to use the Arduino IDE to upload code. Make sure you select the correct serial port and ATmega168 as the board type.

Drupal performance tuning on MediaTemple

First off make sure you've got the root account enabled and the developer tools enabled.

Install APC

Ideally you'd use PECL to install APC but it won't compile on MT's servers.

I was trying to use these instructions for installing APC but found that they no longer worked.

Download the source and extract it:

cd /usr/local/src
wget http://pecl.php.net/get/APC-3.0.19.tgz
tar xvzf APC-3.0.19.tgz
cd APC-3.0.19

Compile and install the extension:

phpize
./configure --enable-apc --enable-apc-mmap --with-apxs2=/usr/sbin/apxs --with-php-config=/usr/bin/php-config
make install

Tell PHP to load the extension and restart the webserver:

echo "extension=apc.so" > /etc/php.d/apc.ini
/etc/init.d/httpd restart

Tweak your Apache config

Disable ETags by adding:
FileETag none
to Drupal's .htaccess file.

Comparing a node's values with its previous version on save

There was a great question on Drupal developers mailing list the other day—one to which I've "rediscovered" the solution to a few times—so I wanted to make sure that everyone was aware of it.

The basic question is:

When a node is being saved, how can you see what values have changed?

The short answer is:

Use the 'presave' operation to load a copy of the node before it's saved, stick it back into the node object, and in your 'update' operation code compare the "before" and "after" versions:

<?php
/**
* Implementation of hook_nodeapi().
*/
function example_nodeapi(&$node, $op, $a3, $a4) {
 
// We want to compare nodes with their previous versions. Ignore new
  // nodes with no nid since there's no previous version to load.
 
if ($op == 'presave' && !empty($node->nid)) {
   
// We don't want to collide with values set by other modules so we'll
    // use the module name as a prefix and a long name to be save.
   
$node->example_presave_node = node_load($node->nid);
  }
  elseif (
$op == 'update') {
   
// On update we pull the previous version out of the node and compare
    // it to the newly saved one.
   
$presave = $node->example_presave_node;
   
// Pretend we're comparing a single value CCK number field here.
   
$field_name = 'field_example';
    if (
$node->$field_name != $presave->$field_name) {
     
drupal_set_message(
       
t("The node's value changed from %previous to %current.", array(
         
'%previous' => $presave->$field_name[0]['value'],
         
'%current' => $node->$field_name[0]['value'],
        ))
      );
    }
  }
}
?>

Simple loop to update nodes

For some reason I find myself rewriting this little bit of code every time I need to update a bunch of nodes on a site. Going to post it here to save myself some time. Be aware that this might time out if you've got a large number of nodes, designed for up to a couple hundred nodes:

<?php
// TODO: Set your basic criteria here:
$result = db_query("SELECT n.nid FROM {node} n WHERE n.type = '%s'", array('task'));
while (
$row = db_fetch_array($result)) {
 
$node = node_load($row);
  if (
$node->nid) {
   
$node->date = $node->created;

   
// TODO: Test and set your own value here:
   
if (empty($node->field_task_status[0]['value'])) {
     
$node->field_task_status[0]['value'] = 'active';
     
$node = node_submit($node);
     
node_save($node);
     
drupal_set_message(t('Updated <a href="!url">%title</a>.', array('!url' => url('node/'. $node->nid), '%title' => $node->title)));
    }
  }
}
?>

Bicycle inflation?

The NY Times—as part of their longtime fixation on Portland—has an article arguing that the price of bicycles is inflated in Portland. There definitely aren't easy deals there, but having now seen the $200 junk bikes that are for sale on the street in Williamsburg I think they are exaggerating. I'm pretty sure that a if they got a ZipCar and drove out to Beaverton they'd find all kinds of deals on jenky bikes.

But here's some of my thoughts (which you can also find on page five or so of the comments):

  • Bikes in Phoenix and Austin are going to last a lot longer due to the difference in climate. Seattle and Portland get much more rain that'll rust bikes locked up outside.
  • Having bought a bike from Costco back in the day I can tell you that it's either un-assembled or incorrectly assembled, and probably unsafe. Knowing that I'd send my friends to a bike shop.
  • You're shopping in the summer. the worst time to buy a bike in portland is when the weather's nice. Wait until the week after the rain starts back up and you'll find a lot more deals—you'll also have a lot easier time finding places to lock your bike

Partying like it's 1999

I just got around to upgrading my website to Drupal 6. In the process I decided that I was going to redo the theme using 960.gs for a nice grid based design and then I got it into my head that I wanted a nice retro 1990 theme. So here it is, my stab at recreating the Netscape 2 experience. I'm not sure how long I'll be able to stand it but I'm going to enjoy it while it lasts.

Update: couldn't handle it anymore, had to "modernize" it a bit.

Simple Arduino serial communications

I've been playing around with some Arduino stuff I bought from adafruit industries and needed to rough out some serial communications between the Arduino and Processing. This just sets the Arduino up to echo characters back at the Processing sketch which buffers a line of output and displays the last line from the serial port.

Syndicate content