Add instance variables through a category

November 10, 2006

I saw this in Apple’s Cocoa-dev mailing list recently and thought it was clever. It’s a hack, and I don’t know of any situation off the top of my head where I’d use it, but if you need an instance variable in one of your categories ((For non-Cocoa programmers, a category is a powerful Objective-C tool that allows you to add new methods to an existing class. Although you can access existing instance variables you can’t add new ones, which is what this work-around accomplishes.)) I can’t see why it wouldn’t work.


static NSDictionary *ivarHolder = nil;

@implementation NSSomeClass (Additions)

-(NSString *)magicalValue
{
    if ( ivarHolder == nil )
        ivarHolder = [[NSDictionary alloc] init];

    NSDictionary *vars = [ivarHolder objectForKey:[NSNumber numberWithInt:self]];
    return [vars objectForKey:@"magicalValue"]
}

@end

Thanks to Jonathan del Strother on the Cocoa-dev list for sharing this.

More OSX software freebies

November 06, 2006

Right on the heels of MacHeist, there’s Mac App A Day, offering a free shareware OSX application each and every day through the month of December.

I don’t know where this trend came from, or if the developers are getting enough free exposure to make it worthwhile, but I know I’m enjoying it. When My Dream App was still in progress, I picked up a couple neat little programs I probably never would have bought otherwise.

Caught by the hype machine

November 06, 2006

Phill Ryu started his follow-up to My Dream App last week, MacHeist. Phill is a genius at viral marketing, and I couldn’t help getting a bit excited about what’s in store with MacHeist (hint: it involves free OSX software). The official start date is Wednesday (although there’s already one freebie, if you hunt for it), and I’m looking forward to it.

MacHeist is currently invite-only, but you can enter your email address to be notified when there’s an opening.

SpamSieve is my new favorite OSX application

November 01, 2006

Like most hosting companies, 1&1 includes free anti-spam protection. It includes bayesian filtering and a few other tricks, but routinely lets 3 or 4 through a day; not a big deal, but those interruptions get annoying quickly.

Thanks to John Gruber mentioning it on Monday, I gave SpamSieve a try. It’s incredible. SpamSieve doesn’t have a prettied-up UI, and it takes a little work to get it set up, but it’s extremely effective. My email addresses have been harvested by spam bots time and time again, and out of the hundreds of spam emails I get each day SpamSieve has let zero through once I trained it properly. That includes those .gif “stock tips,” the Japanese spam, everything; and I haven’t seen any false positives yet either. I love it.

MacBook Pro iSight indicator coolness

October 28, 2006

The new MacBook Pros aren’t just a faster processor in the same old wrapping, as this Engadget post shows. Little touches like this are the reason I bought my own PowerBook years ago.

Using .NET and WebDAV to access an Exchange server

October 26, 2006

If your company runs on Exchange (and who doesn’t), there will come a day when you’ll need to gather or modify data from an external application or a web page you’re building. There are many tools for doing this, from expensive third-party libraries to dead technologies buried in MSDN. The method I found best was a mix of C#, .NET and WebDAV, all of which are modern, flexible, and recommended by Microsoft. WebDAV (basically an HTTP request that can write as well as read) is used in many applications, including Microsoft’s own Entourage. It’s reliable and easy to use, but there are plenty of potential problems if you’re starting from scratch. Here’s a short tutorial that should give you all the basics for building Exchange support into your own application.

Getting Started

Because WebDAV is built on standard HTTP requests, the .NET library classes can handle many of the gritty details. We need to start by authenticating with a set of login credentials to get an authorization cookie, request the data we need, and extract it into a format we understand. To keep things simple, I’m only going to create one class, WebDAVRequest. I’m going to stick a few variables in here, to define things like the username and server address. Remember to URL encode the path if it contains any non-alphanumeric characters.


using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;
using System.Text.RegularExpressions;

namespace DowntownSoftwareHouse.ExchangeExample
{
    public class WebDAVRequest
    {
        private static string server = "https://mail.mycompany.com";
        private static string path = "/public/Calendars/Board%20Room";
        private static string username = "user"
        private static string password = "password";
        private CookieContainer authCookies = null;
    }
}

Authentication

We’re going to be using forms based authentication in this example. The code you need to write does pretty much the same thing as when you log into your OWA server using a web browser; the local client checks the server’s certificate, attempts to authenticate using your credentials, and stores a cookie for later requests. Put these methods into the class we just wrote.


/// 
/// Add code here to check the server certificate, if you think it's necessary. Note that this
/// will only be called once when the application is first started.
/// 

protected bool CheckCert( Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors )
{
    return true;
}

/// 
/// Default WebDAVRequest constructor. Set the certificate callback here.
/// 

public WebDAVRequest()
{
    ServicePointManager.ServerCertificateValidationCallback += CheckCert;
}

The CheckCert event handler is called once when you first connect to the Exchange server. You’ll see that I’m cheating here; this code doesn’t actually do anything except allow us to move on to the next step. Whether or not you verify the certificate’s authenticity depends on the security of your network, and I’ll leave that decision up to you. For now, let’s move on.


/// 
/// Authenticate against the Exchange server and store the authorization cookie so we can use
/// it for future WebDAV requests.
/// 

public void Authenticate()
{
    string authURI = server + "/exchweb/bin/auth/owaauth.dll";

    // Create the web request body:

    string body = string.Format( "destination={0}&username={1}&password={2}", server + path, username, password );
    byte[] bytes = Encoding.UTF8.GetBytes( body );

    // Create the web request:

    HttpWebRequest request = (HttpWebRequest)System.Net.WebRequest.Create( authURI );
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";
    request.CookieContainer = new CookieContainer();
    request.ContentLength = bytes.Length;

    // Create the web request content stream:

    using ( Stream stream = request.GetRequestStream() )
    {
        stream.Write( bytes, 0, bytes.Length );
        stream.Close();
    }

    // Get the response & store the authentication cookies:

    HttpWebResponse response = (HttpWebResponse)request.GetResponse();

    if ( response.Cookies.Count < 2 )
        throw new AuthenticationException( "Login failed. Is the login / password correct?" );

    cookies = new CookieContainer();
    foreach ( Cookie myCookie in response.Cookies )
    {
        cookies.Add( myCookie );
    }

    response.Close();
}

Things are getting a bit trickier now. This might be a little unfamiliar if you haven’t worked with HTTP data streams in the past, but .NET encapsulates most of it. All we’re doing is sending an HTTP request (which contains our login credentials, as well as the URL to the resource we’re requesting access to) to the Exchange OWA server’s authorization .dll, and reading back the response. If everything goes okay, we’ll get a few cookies which will be used later to access the resource we need. Note that we have to move these from a CookieCollection to a CookieContainer; for reasons I don’t know .NET doesn’t let you intermix the containers for requests and responses.

Don’t forget to close the response stream; you’ll get errors the next time you make a request if you don’t.

Gathering Data

At this point, we’re finally ready for whatever it is we need to do with Exchange. In this example, I’m going to poke around in the public folders and gather information for the day’s calendar appointments. I’ll also use a regular expression to find the organizer’s full name; the full string contains their name and email address, which might not be suitable for what you’re trying to display.


/// 
/// Find today's appointments in the public folder calendar and print the results.
/// 

public void RunQuery()
{
    string uri = server + path;
    HttpWebRequest request;
    WebResponse response;
    byte[] bytes;

    string start = DateTime.Today.ToString( "yyyy/MM/dd" );
    string end = DateTime.Today.ToString( "yyyy/MM/dd" );

    // Note that deep traversals don't work on public folders. In other words, if you
    // need to dig deeper you'll need to split your query into multiple requests.

    string format =
        @"
            <g:searchrequest xmlns:g=""DAV:"">
                
                    SELECT
                        ""urn:schemas:calendar:dtstart"", ""urn:schemas:calendar:dtend"",
                        ""urn:schemas:httpmail:subject"", ""urn:schemas:calendar:organizer"",
                        ""DAV:parentname""
                    FROM
                        Scope('SHALLOW TRAVERSAL OF ""{0}""')
                    WHERE
                        NOT ""urn:schemas:calendar:instancetype"" = 1
                        AND ""DAV:contentclass"" = 'urn:content-classes:appointment'
                        AND ""urn:schemas:calendar:dtstart"" > '{1}'
                        AND ""urn:schemas:calendar:dtend"" < '{2}'
                    
            </g:searchrequest>";

    bytes = Encoding.UTF8.GetBytes( String.Format( format, uri, start, end ) );

    // Use the authorization cookies we stored in the authentication method.

    request = (HttpWebRequest)HttpWebRequest.Create( uri );
    request.CookieContainer = authCookies;
    request.Method = "SEARCH";
    request.ContentLength = bytes.Length;
    request.ContentType = "text/xml";

    using ( Stream requestStream = request.GetRequestStream() )
    {
        requestStream.Write( bytes, 0, bytes.Length );
        requestStream.Close();
    }

    response = (HttpWebResponse)request.GetResponse();

    using ( Stream responseStream = response.GetResponseStream() )
    {
        // Parse the XML response to find the data we need.

        XmlDocument document = new XmlDocument();
        document.Load( responseStream );

        XmlNodeList subjectNodes = document.GetElementsByTagName( "e:subject" );
        XmlNodeList locationNodes = document.GetElementsByTagName( "a:parentname" );
        XmlNodeList startTimeNodes = document.GetElementsByTagName( "d:dtstart" );
        XmlNodeList endTimeNodes = document.GetElementsByTagName( "d:dtend" );
        XmlNodeList organizerNodes = document.GetElementsByTagName( "d:organizer" );

        for ( int index = 0; index < subjectNodes.Count; index++ )
        {
            string subject = subjectNodes[index].InnerText;
            string organizer = organizerNodes[index].InnerText;
            string location = ParentName( locationNodes[index].InnerText );
            DateTime startTime = DateTime.Parse( startTimeNodes[index].InnerText );
            DateTime endTime = DateTime.Parse( endTimeNodes[index].InnerText );

            // Use a regex to get just the user's first and last names. Note that
            // some appointments may not have a valid user name.

            string pattern = @"""(?.*?)""";
            Regex regex = new Regex( pattern, RegexOptions.None );
            Match matchedText = regex.Match( organizer );

            if ( matchedText.Success && matchedText.Groups["name"] != null )
                organizer = matchedText.Groups["name"].Value;

            // Print the results to the console.

            Console.WriteLine( "{0} - {1}: {2}", startTime.ToShortTimeString(), endTime.ToShortTimeString(), subject );
            Console.WriteLine( "{0} ({1})", location, organizer );
            Console.WriteLine();
        }

    }

    response.Close();
}
</code></pre>

Just as before, we're making an HTTP request and reading back the response. The body of the request is XML markup that contains the Exchange data store query. The query itself is similar (both in structure and power) to SQL, so you should feel right at home if you've worked with databases before. It's possible to use a deep traversal or specify multiple folders in the FROM clause, as long as you're not looking in the public folders. If you are, you'll need to make multiple WebDAV requests to get the data you need.

The response is also XML, and since .NET has excellent XML support, we don't have to do much work to parse it. As I mentioned before, I used a regular expression to change one of the attributes, but that part is really optional. The format of both the request query and response is complex (to say the least), but everything you need should be in MSDN. I've included a few links at the end of the article to get you started.

Finishing Up

All that's left now is to run the query! I'll also do a little error handling here, and catch the exceptions I'm likely to run into.


static void Main( string[] args )
{
    try
    {
        WebDAVRequest request = new WebDAVRequest();
        request.Authenticate();
        request.RunQuery();
    }
    catch ( SecurityException e )
    {
        Console.WriteLine( "Security Exception" );
        Console.WriteLine( "   Msg: " + e.Message );
        Console.WriteLine( "   Note: The application may not be trusted if run from a network share." );
    }
    catch ( AuthenticationException e )
    {
        Console.WriteLine( "Authentication Exception, are you using a valid login?" );
        Console.WriteLine( "   Msg: " + e.Message );
        Console.WriteLine( "   Note: You must use a valid login / password for authentication." );
    }
    catch ( WebException e )
    {
        Console.WriteLine( "Web Exception" );
        Console.WriteLine( "   Status: " + e.Status );
        Console.WriteLine( "   Reponse: " + e.Response );
        Console.WriteLine( "   Msg: " + e.Message );
    }
    catch ( Exception e )
    {
        Console.WriteLine( "Unknown Exception" );
        Console.WriteLine( "   Msg: " + e.Message );
    }
}
How you integrate this in your own application is up to you, and ultimately depends what it is you're trying to accomplish. One of the things I like about .NET is how flexible and reusable it often is. In my case, I put the Exchange handling code and related classes in its own class library, which is imported into the ASP .NET webapp that displays the data to the user. When it's time to update the library, or create a different ASP .NET project that uses the code, all I have to do is copy a single .dll into the project. Here are a few URLs you might need as you go. Searching Calendar Folders with WebDAV Exchange Store Key Tasks Exchange Programming Technologies

ADC Article: Leopard Technology Series for Developers

October 24, 2006

A new technology article from Apple Developer Connection today, explaining what developers can expect to see in Leopard:

Leopard is the sixth major version of Mac OS X and it will be the most advanced and powerful version yet. For users, it is full of new features and elegant user experience improvements that will make it a joy to use. For developers, things get even better. Leopard contains a cornucopia of cutting-edge new frameworks, streamlined developer tools, new application technologies, and strong system-level foundations. No matter what kind of developer you are, there’s something new in the system that will feel tailor made just for you.

My first thoughts are that the development tools look great. Since I haven’t bought an ADC Premier account I can only look at the screenshots, but I’m really impressed by what I can see. Between Xcode 3.0, Interface Builder and the new Xray profiling tool, it seems like Apple really spent a lot of effort improving and cleaning up some of the more outdated parts of the development tools. I know when I finally get Leopard on my system, playing around with Xcode is one of the first things I’m going to do.

Objective-C 2.0 and Core Animation I’m not terribly excited about. The last two big additions to Cocoa, Bindings and Core Data, had tangible benefits that developers could apply to existing problems in their applications. Core Animation sounds visually appealing, but I doubt it’s something many applications really “need” in order to accomplish their basic workflow. Same thing with Objective-C 2.0; although I’m sure many new .NET and Java developers will find it a little easier to work with, it’s not really going to let existing developers do anything they couldn’t do before. It looks like Objective-C 2.0 will be fully backwards compatible, which is good. I have no real desire to trade in retain / release memory management for the new garbage collection, since in certain conditions you really do need that extra amount of control, even if garbage collection makes things easier 80% of the time.

There are also a few new frameworks which will be worth looking over. The one that really caught my eye is the Calendar Store framework, which provides integration with iCal. Considering how much Web 2.0 calendar and productivity webapps have been in the news lately, I think we’re going to see a lot more integration between different platforms in the near future. I’ve used Plaxo a bit in the past, which is headed in this direction but only supports OSX Address Book data at the moment. Hopefully with frameworks like Calendar Store, by this time next year I’ll be using Outlook at work and on my PDA, iCal at home, and they’ll both sync up seamlessly.

Runner’s Log Project Status

October 23, 2006

It’s been a busy month here (not that it ever isn’t). When I haven’t been distracted by Lost or Battlestar Galactica, I’ve been focusing on completing Runner’s Log, my upcoming OSX application. The past two weeks have been spent fixing the remaining bugs, cleaning parts of the code and generally making sure things are working as they should be.

Anyhow, I’m happy to say that every major feature for Runner’s Log 1.0 is done. My next task is to hire an icon artist to design the toolbar and application icons, and while that’s progressing I’m going to spend my time on QA, making sure there aren’t any critical bugs remaining and tweaking the behavior in a few spots. Since I started working on this project, one of my goals was to get to a beta out to the public before winter, and it looks like I’m still on track.

Stay tuned! I hope to have some screenshots to put up soon.

Free eBook: Become an Xcoder

October 20, 2006

I noticed a free PDF eBook by CocoaLab today, Become an Xcoder. It covers everything from variables and control loops, to more advanced topics like pointers and Objective-C memory management. Pretty basic overall, but it seems like a friendly introduction for any Mac user who wants to jump into programming for the first time.

On a related note, Scott Stevenson over at Cocoa Dev Central recently updated the Learn C for Cocoa tutorial. It’s worth a read if you haven’t had a chance to read through K&R, or just need to refresh your memory on C.

Tangerine for OSX

October 19, 2006

Potion Factory released their newest application today, Tangerine. In short, it analyzes the beats per minute of songs in your iTunes library, which you can use to generate custom playlists. You might use it to create a playlist of fast songs for your workout, slow songs to listen to before you go to bed, or just beat match songs if you want to be a DJ without all the work.

I remember trying to find an “assign beat count” item hidden in the menus somewhere in iTunes years ago when I first bought my Mac. Since then, I’ve kept an eye out for an application like this; I’ve seen a few scripts and applications that served the same purpose, but Tangerine is the first one that looked nice enough to really catch my eye.

(I’m not just writing about this because Potion Factory is running a promotion if you mention Tangerine on your blog; I really am excited about this application.)

Internet Explorer 7 released

October 18, 2006

I haven’t used Internet Explorer for years, but as a part time web designer I’m glad IE7 is finally out (and soon to be included in Windows Update). When I write HTML or CSS I try to keep everything neat, simple and standards compliant, but there always seems to be something that didn’t work quite right on IE6.

Not that it matters too much; having a Mac centric website means around 57% of my users are browsing with Safari, 26% with FireFox, and IE coming in at a low 8%.

Gallery of Computation

October 18, 2006

Take some really complex random mathematical algorithms, throw in some subtle colors, and you get the Gallery of Computation by J. Tarbell. As a computer science major this kind of stuff is very interesting to me, especially since the generated patterns look, well, really nice; enough that I might buy a print once they’re available.

My favorite is Substrate. If it interests you, the source code is also available.