Showing posts with label os-x. Show all posts
Showing posts with label os-x. Show all posts

Thursday, 19 June 2008

Web Sharing and PHP on Mac OS-X Leopard

Mac OS-X comes with a version the Apache web server that is configured to allow users of the system to publish their own web pages directly from the Sites directory in their home folder. This is of limited use for the average user but is just great for web developers who can test their work directly, using a real web server. However, there is a glitch: if you upgrade from OS-X Tiger (10.4) to Leopard (10.5), existing users will suddenly get an HTTP error 403 Forbidden when navigating to their web pages. This is because in Leopard, Apache's security is tightened by default.

Apple provide an article that describes how to re-enable access for those users. However, their version will still deny access to sub-directories. So I slightly adapted the shortname.conf file to make it more flexible. Here is my version:

<Directory "/Users/shortname/Sites/*">
Options Indexes MultiViews
AllowOverride FileInfo
Order allow,deny
Allow from all
</Directory>

The star (*) at the end of the directory name on the first line ensures that the rule applies not only to the ~/Sites directory but also all sub-directories. The FileInfo value for the AllowOverride option on the third line tells Apache to allow settings override in a .htaccess file in that directory or any sub-directory thus allowing much finer grained control.

After getting this to work, it appears that, although PHP5 is installed, it is not enabled in Leopard's Apache installation. Enabling it is very easy and very well explained at Foundation PHP.

There you go: a full blown web server with PHP support is just what you need to locally test drive you beautiful web creations and you don't even have to install any extra software.

Saturday, 29 December 2007

Photographic Metadata

When I first started with an SLR camera, some 13 years ago, all camera magazines had the same advice for beginners: to improve your pictures, write down all the settings you used, such as aperture or shutter speed, so that you can go back to this information once you have the prints and understand why they came out the way they did. As a result, a serious photographer would always have a small notebook with him to write all this down. It was quite a time consuming process but essential for who wanted to improve. In this age of digital photography, it would seem sensible for the camera to store this information with the picture so that you can retrieve it later. And indeed they do, in metadata called EXIF data that is embedded in the image file. Software like Photoshop is able to read EXIF data but it's a bit overkill to fire Photoshop just to look at this data. And it would also be nice to be able to write scripts based on it, such as a script that selects all pictures taken at a particular focal length.

Such a tool exists: it's called, quite simply, ExifTool. The tool is written in Perl so should work on any system that has Perl installed. There is a package for Mac OS-X that makes it really trivial to install. A proper install on Linux is slightly more convoluted so here's how to do it on Ubuntu:

  1. Download the latest version from the web site, in my case version 7.08;
  2. Extract the content of the file:
    $ tar -xzf ./Image-ExifTool-7.08.tar.gz
    
  3. Install the Perl libraries so that they can be used by other Perl scripts:
    $ cd ./Image-ExifTool-7.08
    $ perl Makefile.PL
    $ make
    $ make test
    $ sudo make install
    
  4. Install the main script:
    $ sudo cp exiftool /usr/local/bin
    

Alternatively, you can use ExifTool directly from the directory where you extracted it if you just want to try it out. Using ExifTool in the command line is very easy, just call:

$ exiftool myfile.jpg

And it will output all the metadata tags it knows about. There are a number of options available, in particular, you can select what tags you want to see. It's all very well explained in the man page. And if you run exiftool without arguments, it will actually display said man page. So what sort of fun stuff can we do now? Here is an example that selects all the files with a .JPG extension in the current directory and below that are photographs taken with a focal length of 105mm:

$ for f in `find . -name "*.JPG"`; do
> if [ -n "`exiftool -FocalLength $f | grep '105.0mm'`" ]; then
> echo $f
> fi
> done

Note that ExifTool formats its output such that it prints out the name of the tag followed by a colon and the value. If you want to strip that name and only keep the value, you can do something like this:

$ exiftool -FocalLength myfile.jpg | sed 's/^[^:]*: //'

An interesting application, if you have a GPS receiver is to combine the GPS trace log with the EXIF time information to geo-tag your photographs. There are a number of links on the ExifTool web site that point to such utilities. Once geo-tagged, online photo sites like flickr will use this information to position the pictures on a map. Or more simply, to come back to what I was talking about at the beginning of this article, you could compare basic shot settings between pictures to understand why one is better than another.

Note that there are other tools than ExifTool to do this, some of them offer a graphic front-end that may be easier to use for those who are not comfortable with the command line, but ExifTool is by far the most complete and powerful. Just google for exif reader if you want to find other options.

Saturday, 27 October 2007

Geeky experiments with bash functions

return doesn't mean what you think it does

Modern UNIX shells like bash have the ability to define functions. Functions are a great way to factorise parts of code that you need to use in several areas of your script or isolate discrete pieces of logic. In most programming languages, one fundamental aspect of functions is that they can return a value which is the result of whatever computation they were doing. And indeed a shell like bash has a built in return command. But, hang on, if you read up on return, you realise that it can only return integer values. The reason for this is that return works the same way as exit: it sets the $? variable with the value given as argument, or 0 if no argument is given, and aborts the function. exit aborts the whole script instead. So, if you use return, use it to provide the calling code with an error code. This doesn't solve the original problem though: how can we return a value from a function, such as a character string?

As often with UNIX, the answer is deceptively simple and consistent with everything you know about scripts: just echo the value you want to return and call your function as if it was a full blown script, with inverted quotes or the $(...) construct, as in the example below.

#!/bin/bash

function f {
  echo "[ $1 ]"
  return 1
}

s=`f "abc"`
echo "\$?=$?"
echo "\$s=$s"

Save this in a file called fn.sh, make it executable and run it:

$ ./fn.sh
$?=1
$s=[ abc ]

As you can see, the $? special variable was set with the value 1 and the $s variable was updated with the result of the function.

Recursive fun

Once you know how to return a value from your function, the next thing you need is to know how to pass it some parameters. Once again, it works exactly the same as in a full blown script: you just use the $n positional variables. Recursion works as you expect as well. So let's demonstrate with a classic textbook example: a recursive factorial.

#!/bin/bash

function fact {
  if [ $# -lt 1 ]; then
    return 1
  elif [ $1 -lt 1 ]; then
    return 2
  elif [ $1 -eq 1 ]; then
    r=1
  else
    r=$(( $1 * `fact $(( $1 - 1 ))` ))
  fi
  echo "$r"
}

fact $1

Save it, run it and you should get something like the following. Don't give it too high a value though, we'll see why in a second: 10 should be enough to demonstrate that it works.

$ ./fact.sh 10
3628800

While we're here, let's have a quick look at this function as it has a couple of interesting constructs. It does the following:

  • check the number of parameters it has been passed, using the $# variable, and returns an error if less than 1,
  • check that the first parameter is positive, as a negative value is invalid and return an error code in this case,
  • check the termination condition of the recursion and set the result if we have reached that condition,
  • finally calculate the factorial by calling itself recursively.

Note the use of the $((...)) construct to do the relevant arithmetic calculations: one is needed inside the recursive call to the function, to tell ensure the value passed is the result if $1 - 1 rather than the three parameters $1, - and 1; another one is needed outside the call to calculate the product.

This script also proves that when using functions in this way, the variables defined inside the function are local and not overwritten by a subsequent call. This is because the use of the back quotes actually forks a new process in which the function is called. You can verify this by adding a sleep statement inside the function, running the script in the background and running ps:

$ ps
  PID  TT  STAT      TIME COMMAND
  394  p1  S      0:00.07 -bash
 2414  p1  S      0:00.01 /bin/bash ./fact.sh 10
 2415  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2416  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2417  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2418  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2419  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2420  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2421  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2422  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2423  p1  S      0:00.00 /bin/bash ./fact.sh 10
 2424  p1  S      0:00.00 sleep 5

Each child process has its own context and variables and doesn't interfere with the other ones. However, this means that you have to be extremely careful when using functions this way as you could quite easily spawn a large number of processes. Recursion in particular could be deadly.

Pipe dreams

Finally, if a function generally works like a script, can we pipe it? yes but if you want it to be on the consuming side of the pipe, you will need to adapt the function to take its input from stdin rather than a parameter. And you can even make it work so that it can do both. Here is a modified version of the very first script:

#!/bin/bash

function f {
  if [ $# -ge 1 ]; then
    echo "[ $1 ]"
  else
    while read line; do
      if [ -n "$line" ]; then
        echo `f "$line"`
      fi
    done
  fi
}

find ~ -type f -print | f

You could apply this construct to most functions: check if there are any parameters, in which case you can use them normally, otherwise read each input line and call the function recursively using the line as parameter. Don't forget to enclose it between quotes though, so that it is passed as a single parameter and blank lines don't trigger an infinite recursion. Run this script and you should get a list of all files in your home directory, with each file enclosed in square brackets.

That's it for functions. Please tell me if any of the examples above don't work for you. I have tested them on Ubuntu Linux, Sun Solaris Express and Mac OS-X so they should be fairly portable but you never know. They may not work with shells other than bash but feel free to experiment.

Sunday, 23 September 2007

Bad Interface Design

Apple Computers are reknown for some of the best user interfaces. Like everybody else, they can occasionally get it wrong. I just had an amusing example of this. If you go to the HSBC web site and download any of their PDF documents such as their terms and conditions, you will notice that there is a slight bug that adds ;jsessionid= followed by a lot of gobbledygook at the end of the file name, after the extension, thus producing a file with an unusually long extension that OS-X doesn't recognise. So the first thing you'd want to do is change that file name and remove all the jsessionid malarkey at the end of the file name. When you do that, OS-X thinks you want to change the extension and are in risk of ending up with a file name it can't handle automatically. So it warns you and asks if you really want to do this, assuming you don't, as shown below:

Error dialogue with inaccessible button

Error dialogue with inaccessible button

In this example, you can just about see the other button pushed all the way left and therefore click on it as you really, really want to change that extension. But if you had just one more letter or if you changed the j in the ID for an M, the right button would be that little bit much wider and the left button would completely disappear. As you can't change the dialogue window's size, you're stuffed and you have no choice but to click on the highlighted button and leave your file name as is. The only way I found around this is to open the terminal and use the command line to change the name. That's one thing that OS-X has going for it: as it's UNIX underneath, you can always bypass the user interface when it gets in the way. On the other hand, that's not something that is very accessible to the average user.

There are a couple of solutions that Apple could apply to their dialogue boxes when such a problem occurs:

  • make the dialogue window's resizable and/or scrollable;
  • extend the window accordingly, although you'd get the same problem if you got to stupid extension lengths as long as the width of the screen;
  • make the buttons stack up and extend the window vertically.

The moral of the story is: when designing user interfaces, test them with very stupid values that make it break. Someone is bound to use such values one day or another, if only by accident.

Monday, 11 June 2007

Broadband purgatory

Any experienced network administrator will tell you: the worse problems are not when nothing works but when it sort of works but not quite. So when I started having performance problems with my broadband connection 6 weeks ago, I knew I was in trouble. How to explain the problem to my ISP's helpdesk? I could download pages, even though they sometimes timed out but I couldn't upload any file. So my flickr photo stream started to dry out.

Of course, the first reaction of my ISP was to blame my equipment. As I had the same problem on a Power Mac G5 running OS-X and an IBM laptop running Windows XP, with Firefox as well as Internet Explorer, whether I was connected with to the network wirelesly or with a cable, it quickly came apparent that the only potential culprit on my side was the broadband router. As I was due for an upgrade anyway, I bought a new one and proved that I still had the problem with two different routers.

Then followed the various requests for test outputs to see what could go wrong. All this was a very slow process as I could not test during the day while my ISP's helpdesk was only able to review the tests during working hours. So a routine set in: I do a test during the evening, they look at it the following day, they suggest something else, I do a new test, etc. I quickly became frustrated and ended up writing a shell script to automate calls to traceroute followed by ping in order to get any decent statistics. And, lo and behold! It then became obvious that, anywhere on my network I had no packet loss whatsoever, whereas as soon as I reached my ISP's first router, I had between 5% and 50% packet loss depending on packet size. Now, 5% loss is huge and definitely way too high for TCP/IP to function properly. 50% loss doesn't even bear thinking about. The most likely culprit was now my broadband line. So my ISP passed the call to BT and, miracle, my connection now works like a charm! It only took 6 weeks to get there.

It's scary how we get used to facilities such as broadband though. Those 6 weeks really felt more like 6 months of frustration. Every time I clicked a link or a button on a page, I had no guarantee that it would load properly. Interestingly enough, the pages that were most affected by the problem were pages that depended on AJAX, GMail in particular. I suspect that this is because, when a normal page fails to load completely, the only downside is missing images and suchlike. The page is still usable. But an AJAX page can be completely crippled when it can't load everything: some essential functionality is missing. In fact, I saw exactly the same problem at work recently on my current project: a web application that depends heavily on Javascript and is completely broken if some of the Javascript source files fail to load properly. So, there's a moral for all AJAX developers out there: one of the rules to follow to build bulletproof AJAX is to ensure that your application still works, even if some of the code fails to load.

Following this, I just created a project on SourceForge, where I will maintain the script I developed during this incident. Obviously, this assumes the project gets approved by SourceForge. Assuming it does, I hope this tool proves useful to others and if anybody can contribute by testing it on other operating systems such as Linux or Solaris and helping me make it generic enough to run on those, it'd be much appreciated.

Monday, 26 February 2007

Typography

I just tried to simplify the typography for sans-serif text on this web site by limiting the choice of fonts to two: Lucida Grande and Lucida Sans Unicode, the former being available on Mac OS-X and the latter on Windows. The web site now looks much better on the Mac but not on my Windows laptop, presumably because the Mac has decent font anti-aliasing whereas the Windows laptop doesn't seem to know anything about anti-aliasing. I'll think about that conundrum later, it's time to go to bed.

Tuesday, 16 January 2007

OS-X Instant Messaging

Apart from iChat, that comes bundled with OS-X but can only connect to a limited number of networks, most OS-X versions of instant messaging clients, like MSN Messenger, are fairly poor compared to their PC counterparts. An interesting alternative I discovered recently is Adium. It is quite basic in terms os features so far but it can connect to an impressive list of networks, including MSN, Yahoo, Google Chat, ICQ and quite a few obscure ones, which means that you only ever need one chat client open. It currently doesn't support voice or video but it's definitely on their to-do list. Nor does it support extended animated smileys like MSN does, although you could argue that's rather a good thing. A very nice feature is the ability to use tabs rather than individual windows for each thread of discussion. You can even have a hybrid setting whereby you have one window per group of contacts and tabs inside each window. And of course, in typical open source geeky fashion, most settings can be changed through the preferences and you can completely customise it using AppleScript.

Monday, 30 October 2006

Sync your Nokia phone with your Mac

I have a Nokia 6234, a model that unfortunately is not supported by iSync on the Mac out of the box. I have been looking for a solution more or less since I got the phone and today I found it on .mactomster. They offer an iSync plugin for a large variety of Nokia mobile phones. The site is all in German so it took me a few minutes to work out that I had to register before I could download the plugin but I eventually got there. Luckily the installation instructions also come in English but they are so simple it would be difficult to get it wrong. Once I had downloaded the plugin, it took the whole of 5 minutes to have it installed and my data sync'ed. What more could I ask for? This is what software should be like!

So, if your Nokia phone is one of the unsupported models on the Mac, get this plugin and you'll be sync'ing in minutes. Then make sure you click the Paypal button as those guys definitely deserve your support.