Monday, May 26, 2008

How to tell how many documents are being opened in a multi document application

If you've subclassed NSDocumentController, when the user opens multiple documents, the method

- (id)openDocumentWithContentsOfURL:(NSURL *)absoluteURL display:(BOOL)displayDocument error:(NSError **)outError

will be called once per each document.

If you need to figure out how many documents are being opened (you might to do something after the last document that is part of a batch has been opened), you need to do a couple of things.

For documents opened via the open panel, things are easy:
In your NSDocumentController subclass, override the implementation for URLsFromRunningOpenPanel:


- (NSArray *)URLsFromRunningOpenPanel
{
NSArray * a = [super URLsFromRunningOpenPanel];
documentsPendingOpen = [a count];

return a;
}


For documents being opened through Applescript events (such as documents dropped over the application icon), this is my not very elegant but efficient solution:


- (id)openDocumentWithContentsOfURL:(NSURL *)absoluteURL display:(BOOL)displayDocument error:(NSError **)outError
{
NSAppleEventDescriptor * aed = [[[NSAppleEventManager sharedAppleEventManager]
currentAppleEvent]
paramDescriptorForKeyword:keyDirectObject];
if(aed != nil && cachedAppleEventDescriptor != nil && ![[aed data] isEqualToData:[cachedAppleEventDescriptor data]])
{
cachedAppleEventDescriptor = aed;
documentsPendingOpen = [aed numberOfItems];
}
[..]

Sunday, May 25, 2008

XML command line tools in Mac OS X (1)

xmllint

xmllint is a parser for XML files that allows you to check for the well-formed state of the input.

xmllint can re-format the output, taking one long line of unreadable input and indenting it in a more readable form:


diciu$ cat test.xml
<?xml version="1.0" encoding="utf-8"?>

<a> <m> <p>one, two, three</p> </m> <m> <p>three, four</p> </m> <m> <p>five</p> </m> </a>

cristi:~ diciu$ xmllint --format test.xml
<?xml version="1.0" encoding="utf-8"?>
<a>
<m>
<p>one, two, three</p>
</m>
<m>
<p>three, four</p>
</m>
<m>
<p>five</p>
</m>
</a>



In the Leopard versions, xmlllint and most of the other XML tools have a built in XPath browser implemented through a shell, allowing you to browse an XML's file hierarchy of nodes just like you would browse a filesystem:


diciu$ cat test.xml
<?xml version="1.0" encoding="utf-8"?>

<a>
<m>
<p>one, two, three</p>
</m>

<m>
<p>three, four</p>
</m>

<m>
<p>five</p>
</m>
</a>


The built in commands (viewable with the help command), include cat, cd, ls, pwd that work in similar ways to their Unix cousins:

diciu$ xmllint --shell test.xml
/ > cat a
-------
<a>
<m>
<p>one, two, three</p>
</m>

<m>
<p>three, four</p>
</m>

<m>
<p>five</p>
</m>
</a>
/ > cd a
a > pwd
/a
a > cd m[position()=1]
m > pwd
/a/m[1]
m > ls
t-- 2
--- 1 p
t-- 1

Monday, May 19, 2008

Testing your product's resilience to network errors

Sometimes it's useful to test your product in adverse network conditions. Networking is a very complex layer and it's fortunate that the popularity of TCP throughout the last decade shields us from the implementation that has been moved within the kernel code.
Many bugs in applications using networking are obscured by the developer's use of a fast network - he may use the product on a daily basis and still not hit certain conditions that are only met in conjunction with using the product on a slow network.


If your product has auto-update functionality, there's some code that will try to query the home server for a new version. This usually occurs over HTTP (that's how Sparkle does it).

Have you ever wondered how your product's auto-update code behaves when there's a 10 second delay on the network?

The IP firewall running on your Leopard box makes it very easy to find out.
Here's how you create a 10 second delay for your HTTP traffic towards a given host:

First we configure a pipe that delays packets by 10000 milliseconds:

sudo ipfw pipe 1 config noerror delay 10000


Then we redirect the traffic we want to delay through that given pipe:

sudo ipfw add prob 1 pipe 1 tcp from any to www.home.com http



Listing the rules from the IP firewalls shows the new rule we've just added:

cristi:~ diciu$ sudo ipfw list
Password:
00100 pipe 1 tcp from any to 192.168.1.1 dst-port 80
65535 allow ip from any to any


Testing with network delays

Once you've configured the firewall, you can test your product. As a side note, the auto-update from Sparkle behaves admirably - it will run the query in the background so the 10 second delay is not experienced within the application.

Resetting the delay rule

To clear the ipfw rules once you're done testing:

cristi:~ diciu$ sudo ipfw flush
Are you sure? [yn] y

Flushed all rules.