Music Rescue 4.0 Postmortem
After lots of months and several deadlines missed, we’ve finally shipped Music Rescue 4.0 to the public. This release has seen a major shift in the development methods we used, and essentially marks the move from a company with one guy doing all the coding to one with multiple people working on the same thing.
As such, I thought I’d do a bit of a postmortem on our techniques and my thoughts on them, for anyone interested.
First, some history…
PodUtil 1.x
I invented PodUtil in 2002, in a physics lesson at 6th form when I should have been learning about… something. My computer’s music library had broken and I wanted the music back. The program simply scanned the iPod’s file system for MP3 files and extracted their info from the ID3 tags. It wasn’t very good.
PodUtil 2.x
This version of PodUtil scanned the database of the iPod, and was much faster. However, the database parser wasn’t very good, and I can’t post any screenshots up here because it doesn’t work with any of my current iPods. Yay!
PodUtil/Music Rescue 3.x
PodUtil became Music Rescue at version 3.1, and was the version that hit it big-time, as it were. Until yesterday afternoon, Music Rescue 3.1.6 was the newest version of Music Rescue available from our website. It works with all of the iPods available today (other than the touch/iPhone) and sold well.
What next?
When thinking about Music Rescue 4.0 back in 2007, it was fairly clear that I’d have to re-write Music Rescue from scratch. The codebase was, to be frank, disgusting. Music Rescue 3.x was a mishmash of code with no real design, and still had parts from early betas of version 2.x, just patched up to work better. Inserting features I wanted to add would mean ripping it all apart and refactoring it so extensively that scrapping the codebase altogether and starting fresh was looking like the only way forward.
I took this opportunity to change the language Music Rescue was written in. Versions up to 3.1.6 are written in REALbasic - a cross platform tool that allows you to have one set of code compile an application on Windows and Mac OS X. It’s great for our earlier situation where there was just me writing the program, but now I have manpower at my disposal so it was decided to write the Windows version in .NET and the Mac version in, of course, Objective-C/Cocoa.
Development and Design
After incrementally updating Music Rescue 3.x for years, I’d forgotten what a massive project it is - I was originally hoping to release in early May, but the final release ended up in late July.
It was important to design Music Rescue in perhaps a more compartmentalised manner than I normally would. Apple is constantly evolving the iPod line and to keep up, I need a codebase that’s easily extensible. A good example is photos. I actually wrote a parser to parse and load images from an iPod’s photo database pretty soon after Apple released the first iPod photo in 2004. However, there was absolutely no way I could wedge it into PodUtil/Music Rescue without a lot of work - too much to justify it with more pressing matters at hand.
Now, Music Rescue 4.0 still doesn’t have photos. However, it’s internally designed so it’ll be very, very easy to plug in that functionality in version 4.1 or whenever I write an up-to-date iPod photo engine.
The core application code doesn’t actually know about anything about iPod tracks, at all. The core application simply declares interfaces for:
- CopyableItem Something which can be copied to a file on the local filesystem.
- PlayableItem Something which can produce a QuickTime movie to play in the media player.
- DataProvider Something that appears in the source list and has a view for the user to interact with. For example, “Music” in Music Rescue source list is a DataProvider, along with Podcasts, Video, etc. DataProviders can optionally have subproviders which will be plaed in their own group, such as playlists or photo albums.
- CopyTask Something that can copy CopyableItems to the local filesystem. DataProviders can optionally create CopyTasks. If they do, they’ll be included in QuickRecover and the copy setup sheet.
... you get the idea. When Music Rescue loads an iPod, the iPod instance itself is responsible for the DataProviders for that iPod - when the NSWindowController for the main window is populating the source list, for instance, it looks through the [[[self document] iPod] dataProviders] array.
Let’s see how this fits together in the UI:
The items in red are individual DataProviders from the iPod represented by the window’s NSDocument. The ‘PLAYLISTS’ group are the sub providers provided by the Music Provider.
The blue area is the NSView provided by the selected DataProvider.
When copying, you’ll see this UI:
This time, the red items represent the CopyTasks provided by the DataProviders, and the blue area is the configuration UI for the chosen task.
Hopefully by now you’re noticing a trend - the application doesn’t really make any assumptions about what’s actually on the iPod. It just provides interfaces for something else that knows how to deal with specific media, how to show it to the user and how to put it somewhere useful on the local machine.
This is a huge shift in paradigm from how previous versions worked. When you clicked a playlist in Music Rescue 3, a playlist object gets set as the current playlist, and the table view on the main window shows the tracks in that playlist. That’s fine, until you get things that aren’t playlists. Again, in Music Rescue 3, the program copies files from the iPod to a folder on disk. Which, again, is fine until you encounter photos (which are embedded in a database in some strange format) or an iPod touch (which doesn’t mount on the filesystem).
Instead, version 4 has controllers that deal with the objects that deal with specific data. The NSWindowController and NSDocument pair deal with showing the DataProviders to the user, and a thing called CopyProcessor that takes the CopyTasks that are generated by the Providers, loops through them one by one and tells them to do their thang while it sits there and waits for the copyDidComplete: delegate method to be called.
Something that wasn’t done by accident is that this model lends itself very well to plugins. It’d be trivially more work for me to implement photo support as an external plugin - in fact, the internal Music, Podcasts, etc providers are essentially plugins whose classes sit in the same binary as the rest of the program.
Drawbacks
There are some drawbacks to this method, though.
The first is that it can cause a lot of duplication. For example, the Music, Podcasts, and Audiobooks providers in Music Rescue are all showing the same kind of object in much the same way, and yet they all separately need to manage their items, whether they’re to be copied, etc etc etc. Much of this can be dealt with by having them all subclassing a general purpose “iPod audio track” provider, but there’s still some unavoidable duplication, especially in the nib files.
The second is that it can cause a lot of extra work early on. This is more of a morale issue than anything - you spend a lot of time building protocols and laying the frameworks for all this, but for a long time you don’t seem to be making any visible progress - when you run your app, there’s no new functionality there and it can become very tempting to bypass your hard work and take shortcuts. This, though, it somewhat offset by how easy it is to to add functionality to a well-designed framework.
I *heart* Cocoa
This is the first major project I’ve done in Cocoa, and is honestly the most fun I’ve ever had programming in my life. There’s so much completely awesome stuff in there that it makes coding in anything else less enjoyable than it was.
This causes problems for my colleague, though. He’s writing Music Rescue in .NET, which doesn’t have a lot of the awesome stuff I’m using - but we’re trying to keep the internals as similar as possible between the Cocoa and .NET versions for consistency’s sake. In the end, I ended up writing a few Cocoa classes in .NET to keep functionality the same. They were:
- NSNotificationCenter/NSNotification
- NSTokenField
- NSTableView and everything it needs - NSTableColumn, NSCell, NSActionCell, NSTextCell, NSButtonCell, etc etc.
That last one was hard. NSTableView is a beast, and custom-writing a table is a pain in the ass. However, .NET’s built-in table is so completely useless that it was necessary.
Personal Issues
Quite an odd one, this, and something I won’t have to experience again. Up until version 4.0, every single line of code in Music Rescue was written by me. Those lines of code have gone on to pay for my cars, mortgage, and general living enough to do it full time. Music Rescue is, in essence, my baby.
Switching it over to multiple developers was a tough thing for me to do - and I’ve been constantly uneasy about not being in complete control of my program any more. However, it’s paid off - it’s allowed Music Rescue to become a much better product that it could be with just me developing it.
Conclusion
Now version 4.0 is released, I can say the decisions I made about the development methods have paid off. I’m really excited about version 4.0, and it’s the best program I’ve ever written.
What would I do differently if I was to have to do it again?
Well, nothing really. That’s not because I did it perfectly, but because I don’t know enough about software design to think of a better way to do it. I’m sure than in a few years I’ll look back at my code and cringe, just like I do now at version 3.x’s!



